1.请求参数为空判断

先看一下效果图,需要输入phone、password缺少其中一项时提示缺少的字段
image.png
具体实现步骤

  1. public class LoginCodeDto {
  2. @NotBlank
  3. private String phone;
  4. @NotBlank
  5. private String code;
  6. @NotNull
  7. private Double price;
  8. @NotEmpty
  9. private Set<String> page;
  10. }
  • @NotNull:不能为null,但可以为empty (“”,” “,” “)
  • @NotEmpty:不能为null,而且长度必须大于0 (“ “,” “)
  • @NotBlank:只能作用在String上,不能为null,而且调用trim()后,长度必须大于0 (“test”) 即:必须有实际字符
  • 注意:一定得是装箱类型

捕获该异常信息

  1. @ExceptionHandler(MethodArgumentNotValidException.class)
  2. @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  3. public ResponseVo<String> notValidExceptionHandle(MethodArgumentNotValidException e) {
  4. BindingResult bindingResult = e.getBindingResult();
  5. Objects.requireNonNull(bindingResult.getFieldError());
  6. return ResponseVo.error(ResponseEnum.PARAM_ERROR,
  7. bindingResult.getFieldError().getField() + " " + bindingResult.getFieldError().getDefaultMessage());
  8. }

加上@Valid才能生效

  1. @PostMapping("/passwordLogin")
  2. ResponseVo<LoginDataInfo> passwordLogin(@Valid @RequestBody LoginPasswordDto passwordDto) {
  3. return memberService.passwordLogin(passwordDto);
  4. }

2.解决springboot跨域问题

  1. @Configuration
  2. public class CorsConfig implements WebMvcConfigurer {
  3. @Override
  4. public void addCorsMappings(CorsRegistry registry) {
  5. registry.addMapping("/**")
  6. .allowedOrigins("*")
  7. .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
  8. .allowCredentials(true)
  9. .maxAge(7200)
  10. .allowedHeaders("*");
  11. }
  12. }

3.环境配置注解Profile

使用方法非常的简单注解加到配置类或者Service上面就可以了。
如下两个使用场景:

1.使用在定时任务

希望定时发送邮件的任务在线上跑,但是测试环境和开发环境不跑,那我就这样配置。

  1. @Configuration
  2. @EnableScheduling
  3. @RequiredArgsConstructor(onConstructor = @__(@Autowired))
  4. @Slf4j
  5. @Profile(value = {"prod"})
  6. public class SchedulingTaskServiceImpl implements SchedulingTaskService {}

2.swagger

测试环境才允许看到swagger,线上不允许看到这种隐私的接口文档。

  1. @Configuration
  2. @EnableSwagger2
  3. @Profile(value = { "test"})
  4. public class SwaggerConfiguration implements WebMvcConfigurer {}

4. Redis秒杀超卖问题

1.没有加任何锁的情况下

  1. /**
  2. * @author 杨胖胖
  3. */
  4. public interface OversoldService {
  5. /**
  6. * redis解决超卖的问题
  7. * @param repertory 库存id
  8. * @param userId 用户id
  9. * @return 秒杀结果
  10. */
  11. boolean oversold(String repertory, String userId);
  12. }
  1. /**
  2. * @author 杨胖胖
  3. */
  4. @Service
  5. @RequiredArgsConstructor(onConstructor = @__(@Autowired))
  6. @Slf4j
  7. public class OversoldServiceImpl implements OversoldService {
  8. private final RedisTemplate<String, String> redisTemplate;
  9. @Override
  10. public boolean oversold(String repertory, String userId) {
  11. // 拼接的用户id
  12. String userIdKey = "userId:";
  13. // 库存为null的时候,秒杀还没有开始
  14. String repertoryNum = redisTemplate.opsForValue().get(repertory);
  15. if (StringUtils.isEmpty(repertoryNum)) {
  16. log.info("秒杀未开始");
  17. return false;
  18. }
  19. // 判断用户是否重复秒杀
  20. Boolean isOversold = redisTemplate.opsForSet().isMember(userIdKey, userId);
  21. assert isOversold != null;
  22. if (isOversold) {
  23. log.info("已秒杀成功,不能重复秒杀");
  24. return false;
  25. }
  26. // 商品数量少于1,秒杀结束
  27. if (Integer.parseInt(repertoryNum) <= 0) {
  28. log.info("秒杀已经结束了");
  29. return false;
  30. }
  31. // 库存-1
  32. redisTemplate.opsForValue().decrement(repertory);
  33. // 把秒杀成功用户添加清单里面
  34. redisTemplate.opsForSet().add(userIdKey, userId);
  35. log.info("秒杀成功了");
  36. return true;
  37. }
  38. }
  1. /**
  2. * @author 杨胖胖
  3. */
  4. @RestController
  5. @RequiredArgsConstructor(onConstructor = @__(@Autowired))
  6. public class OversoldController {
  7. private final OversoldService oversoldService;
  8. @GetMapping("oversold")
  9. public boolean oversold(@RequestParam String repertory, @RequestParam String userId) {
  10. return oversoldService.oversold(repertory, userId);
  11. }
  12. }

库存添加10个后执行的效果,这里使用了jmeter作为压测的工具,只有10个商品但是11个人秒杀成功了,商品数量-1,建议并发多测试几次才能看到效果,我是1000个线程并发。
image.png


image.png

2.使用redis的乐观锁解决

  1. /**
  2. * @author 杨胖胖
  3. */
  4. @Service
  5. @RequiredArgsConstructor(onConstructor = @__(@Autowired))
  6. @Slf4j
  7. public class OversoldServiceImpl implements OversoldService {
  8. private final RedisTemplate<String, String> redisTemplate;
  9. @Override
  10. public boolean oversold(String repertory, String userId) {
  11. // 拼接的用户id
  12. String userIdKey = "userId:";
  13. // 监视库存
  14. redisTemplate.watch(repertory);
  15. // 开启事务不然会报错
  16. redisTemplate.setEnableTransactionSupport(true);
  17. // 库存为null的时候,秒杀还没有开始
  18. String repertoryNum = redisTemplate.opsForValue().get(repertory);
  19. if (StringUtils.isEmpty(repertoryNum)) {
  20. log.info("秒杀未开始");
  21. return false;
  22. }
  23. // 判断用户是否重复秒杀
  24. Boolean isOversold = redisTemplate.opsForSet().isMember(userIdKey, userId);
  25. assert isOversold != null;
  26. if (isOversold) {
  27. log.info("已秒杀成功,不能重复秒杀");
  28. return false;
  29. }
  30. // 商品数量少于1,秒杀结束
  31. if (Integer.parseInt(repertoryNum) <= 0) {
  32. log.info("秒杀已经结束了");
  33. return false;
  34. }
  35. // 使用事务
  36. redisTemplate.multi();
  37. // 组队操作
  38. redisTemplate.opsForValue().decrement(repertory);
  39. redisTemplate.opsForSet().add(userIdKey, userId);
  40. // 执行
  41. List<Object> result = redisTemplate.exec();
  42. if(result.size() == 0){
  43. log.info("秒杀失败了");
  44. return false;
  45. }
  46. log.info("秒杀成功了");
  47. return true;
  48. }
  49. }

2.1 乐观锁引发的库存问题

库存500,并发数1000个就可以导致这个问题,我的电脑上面是这样。
image.png

2.2 使用lua脚本解决库存问题

  1. /**
  2. * @author 杨胖胖
  3. */
  4. @Service
  5. @RequiredArgsConstructor(onConstructor = @__(@Autowired))
  6. @Slf4j
  7. public class OversoldServiceImpl implements OversoldService {
  8. private final RedisTemplate<String, String> redisTemplate;
  9. @Override
  10. public boolean oversold(String repertory, String userId) {
  11. Long skillResult = skill(repertory, userId);
  12. if (skillResult == 0) {
  13. log.info("已抢空!!");
  14. return true;
  15. } else if (skillResult == 1) {
  16. log.info("抢购成功!!!!");
  17. return true;
  18. } else if (skillResult == 2) {
  19. log.info("该用户已抢过!!");
  20. return true;
  21. } else {
  22. log.info("抢购异常!!");
  23. return true;
  24. }
  25. }
  26. public Long skill(String repertory, String userId) {
  27. String secKillScript = "local userid=KEYS[1];\r\n" +
  28. "local prodid=KEYS[2];\r\n" +
  29. "local qtkey=prodid;\r\n" +
  30. "local usersKey='userid';\r\n" +
  31. "local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +
  32. "if tonumber(userExists)==1 then \r\n" +
  33. " return 2;\r\n" +
  34. "end\r\n" +
  35. "local num= redis.call(\"get\" ,qtkey);\r\n" +
  36. "if tonumber(num)<=0 then \r\n" +
  37. " return 0;\r\n" +
  38. "else \r\n" +
  39. " redis.call(\"decr\",qtkey);\r\n" +
  40. " redis.call(\"sadd\",usersKey,userid);\r\n" +
  41. "end\r\n" +
  42. "return 1";
  43. List<String> keys = new ArrayList<>();
  44. keys.add(userId);
  45. keys.add(repertory);
  46. DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(secKillScript, Long.class);
  47. return redisTemplate.execute(redisScript, keys, userId, repertory);
  48. }
  49. }

同样是500的库存,1000的线程数,执行后库存为0,购买成功人数500,完美解决!!!


3.最后献上我的jmeter脚本

image.png

image.png

5.Redis配置代码

  1. import org.springframework.context.annotation.Bean;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.data.redis.connection.RedisConnectionFactory;
  4. import org.springframework.data.redis.core.RedisTemplate;
  5. import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
  6. import org.springframework.data.redis.serializer.RedisSerializer;
  7. import org.springframework.data.redis.serializer.StringRedisSerializer;
  8. import java.util.Map;
  9. /**
  10. * @author 杨胖胖
  11. */
  12. @Configuration
  13. public class RedisConfig {
  14. @Bean(name = "redisTemplate")
  15. public RedisTemplate<String, Object> getRedisTemplate(RedisConnectionFactory factory) {
  16. RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
  17. redisTemplate.setConnectionFactory(factory);
  18. // key的序列化类型
  19. redisTemplate.setKeySerializer(new StringRedisSerializer());
  20. // value的序列化类型
  21. redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
  22. return redisTemplate;
  23. }
  24. @Bean(name = "redisTemplateMap")
  25. public RedisTemplate<String, Map<String, String>> getRedisMapTemplate(RedisConnectionFactory factory) {
  26. RedisTemplate<String, Map<String, String>> redisTemplate = new RedisTemplate<>();
  27. redisTemplate.setConnectionFactory(factory);
  28. // key的序列化类型
  29. redisTemplate.setKeySerializer(RedisSerializer.string());
  30. // value的序列化类型
  31. redisTemplate.setValueSerializer(RedisSerializer.string());
  32. redisTemplate.setHashValueSerializer(RedisSerializer.string());
  33. redisTemplate.setHashKeySerializer(RedisSerializer.string());
  34. return redisTemplate;
  35. }
  36. }