标签:mob 计时 into hand mil tor 插入 obj ken
Java秒杀系统方案优化-高性能高并发实战
1:springboot thymeleaf配置
spring.thymeleaf.cache=false
spring.thymeleaf.content-type=text/html
spring.thymeleaf.enabled=true
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.mode=HTML5
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
2:mybatis druid redis 配置
# mybatis
mybatis.type-aliases-package=com.imooc.miaosha.domain
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.default-fetch-size=100
mybatis.configuration.default-statement-timeout=3000
mybatis.mapperLocations = classpath:com/imooc/miaosha/dao/*.xml
# druid
spring.datasource.url=jdbc:mysql://192.168.220.128:3306/miaosha?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.filters=stat
spring.datasource.maxActive=2
spring.datasource.initialSize=1
spring.datasource.maxWait=60000
spring.datasource.minIdle=1
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=select ‘x‘
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true
spring.datasource.maxOpenPreparedStatements=20
#redis
redis.host=192.168.220.128
redis.port=6379
redis.timeout=3
redis.password=123456
redis.poolMaxTotal=10
redis.poolMaxIdle=10
redis.poolMaxWait=3
需要引入以下依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.5</version>
</dependency>
demo
DemoController类
@Controller
@RequestMapping("/demo")
public class DemoController {
@RequestMapping("/")
@ResponseBody
String home() {
return "Hello World!";
}
//1.rest api json输出 2.页面
@RequestMapping("/hello")
@ResponseBody
public Result<String> hello() {
return Result.success("hello,imooc");
// return new Result(0, "success", "hello,imooc");
}
@RequestMapping("/helloError")
@ResponseBody
public Result<String> helloError() {
return Result.error(CodeMsg.SERVER_ERROR);
//return new Result(500102, "XXX");
}
@RequestMapping("/thymeleaf")
public String thymeleaf(Model model) {
model.addAttribute("name", "Joshua");
return "hello";
}
}
public class Result<T> {
   
   private int code;
   private String msg;
   private T data;
   
   /**
    *  成功时候的调用
    * */
   public static  <T> Result<T> success(T data){
      return new Result<T>(data);
   }
   
   /**
    *  失败时候的调用
    * */
   public static  <T> Result<T> error(CodeMsg codeMsg){
      return new Result<T>(codeMsg);
   }
   
   private Result(T data) {
      this.data = data;
   }
   
   private Result(int code, String msg) {
      this.code = code;
      this.msg = msg;
   }
   
   private Result(CodeMsg codeMsg) {
      if(codeMsg != null) {
         this.code = codeMsg.getCode();
         this.msg = codeMsg.getMsg();
      }
   }
   
   public int getCode() {
      return code;
   }
   public void setCode(int code) {
      this.code = code;
   }
   public String getMsg() {
      return msg;
   }
   public void setMsg(String msg) {
      this.msg = msg;
   }
   public T getData() {
      return data;
   }
   public void setData(T data) {
      this.data = data;
   }
}
public class CodeMsg {
   
   private int code;
   private String msg;
   
   //通用的错误码
   public static CodeMsg SUCCESS = new CodeMsg(0, "success");
   public static CodeMsg SERVER_ERROR = new CodeMsg(500100, "服务端异常");
   public static CodeMsg BIND_ERROR = new CodeMsg(500101, "参数校验异常:%s");
   //登录模块 5002XX
   public static CodeMsg SESSION_ERROR = new CodeMsg(500210, "Session不存在或者已经失效");
   public static CodeMsg PASSWORD_EMPTY = new CodeMsg(500211, "登录密码不能为空");
   public static CodeMsg MOBILE_EMPTY = new CodeMsg(500212, "手机号不能为空");
   public static CodeMsg MOBILE_ERROR = new CodeMsg(500213, "手机号格式错误");
   public static CodeMsg MOBILE_NOT_EXIST = new CodeMsg(500214, "手机号不存在");
   public static CodeMsg PASSWORD_ERROR = new CodeMsg(500215, "密码错误");
   
   //商品模块 5003XX
   
   //订单模块 5004XX
   
   //秒杀模块 5005XX
   
   private CodeMsg( ) {
   }
         
   private CodeMsg( int code,String msg ) {
      this.code = code;
      this.msg = msg;
   }
   
   public int getCode() {
      return code;
   }
   public void setCode(int code) {
      this.code = code;
   }
   public String getMsg() {
      return msg;
   }
   public void setMsg(String msg) {
      this.msg = msg;
   }
   
   public CodeMsg fillArgs(Object... args) {
      int code = this.code;
      String message = String.format(this.msg, args);
      return new CodeMsg(code, message);
   }
   @Override
   public String toString() {
      return "CodeMsg [code=" + code + ", msg=" + msg + "]";
   }
   
   
}
3:dao层 访问代码
@Mapper
public interface UserDao {
@Select("select * from user where id = #{id}")
public User getById(@Param("id")int id );
@Insert("insert into user(id, name)values(#{id}, #{name})")
public int insert(User user);
}
4:redis config配置
需要引入以下依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.38</version>
</dependency>
@Component
@ConfigurationProperties(prefix="redis")
public class RedisConfig {
private String host;
private int port;
private int timeout;//秒
private String password;
private int poolMaxTotal;
private int poolMaxIdle;
private int poolMaxWait;//秒
}
5: redis JedisPool 配置
@Service
public class RedisPoolFactory {
@Autowired
RedisConfig redisConfig;
@Bean
public JedisPool JedisPoolFactory() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxIdle(redisConfig.getPoolMaxIdle());
poolConfig.setMaxTotal(redisConfig.getPoolMaxTotal());
poolConfig.setMaxWaitMillis(redisConfig.getPoolMaxWait() * 1000);
JedisPool jp = new JedisPool(poolConfig, redisConfig.getHost(), redisConfig.getPort(),
redisConfig.getTimeout()*1000, redisConfig.getPassword(), 0);
return jp;
}
}
6:redis service
@Service
public class RedisService {
@Autowired
JedisPool jedisPool;
/**
* 获取当个对象
* */
public <T> T get(KeyPrefix prefix, String key, Class<T> clazz) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//生成真正的key
String realKey = prefix.getPrefix() + key;
String str = jedis.get(realKey);
T t = stringToBean(str, clazz);
return t;
}finally {
returnToPool(jedis);
}
}
/**
* 设置对象
* */
public <T> boolean set(KeyPrefix prefix, String key, T value) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
String str = beanToString(value);
if(str == null || str.length() <= 0) {
return false;
}
//生成真正的key
String realKey = prefix.getPrefix() + key;
int seconds = prefix.expireSeconds();
if(seconds <= 0) {
jedis.set(realKey, str);
}else {
jedis.setex(realKey, seconds, str);
}
return true;
}finally {
returnToPool(jedis);
}
}
/**
* 判断key是否存在
* */
public <T> boolean exists(KeyPrefix prefix, String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//生成真正的key
String realKey = prefix.getPrefix() + key;
return jedis.exists(realKey);
}finally {
returnToPool(jedis);
}
}
/**
* 增加值
* */
public <T> Long incr(KeyPrefix prefix, String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//生成真正的key
String realKey = prefix.getPrefix() + key;
return jedis.incr(realKey);
}finally {
returnToPool(jedis);
}
}
/**
* 减少值
* */
public <T> Long decr(KeyPrefix prefix, String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//生成真正的key
String realKey = prefix.getPrefix() + key;
return jedis.decr(realKey);
}finally {
returnToPool(jedis);
}
}
private <T> String beanToString(T value) {
if(value == null) {
return null;
}
Class<?> clazz = value.getClass();
if(clazz == int.class || clazz == Integer.class) {
return ""+value;
}else if(clazz == String.class) {
return (String)value;
}else if(clazz == long.class || clazz == Long.class) {
return ""+value;
}else {
return JSON.toJSONString(value);
}
}
@SuppressWarnings("unchecked")
private <T> T stringToBean(String str, Class<T> clazz) {
if(str == null || str.length() <= 0 || clazz == null) {
return null;
}
if(clazz == int.class || clazz == Integer.class) {
return (T)Integer.valueOf(str);
}else if(clazz == String.class) {
return (T)str;
}else if(clazz == long.class || clazz == Long.class) {
return (T)Long.valueOf(str);
}else {
return JSON.toJavaObject(JSON.parseObject(str), clazz);
}
}
private void returnToPool(Jedis jedis) {
if(jedis != null) {
jedis.close();
}
}
}
7:redis key 设置
(1)接口
public interface KeyPrefix {
      
   public int expireSeconds();
   
   public String getPrefix();
   
}
(2)抽象类
public abstract class BasePrefix implements KeyPrefix{
   
   private int expireSeconds;
   
   private String prefix;
   
   public BasePrefix(String prefix) {//0代表永不过期
      this(0, prefix);
   }
   
   public BasePrefix( int expireSeconds, String prefix) {
      this.expireSeconds = expireSeconds;
      this.prefix = prefix;
   }
   
   public int expireSeconds() {//默认0代表永不过期
      return expireSeconds;
   }
   public String getPrefix() {
      String className = getClass().getSimpleName();
      return className+":" + prefix;
   }
}
3:userkey
public class UserKey extends BasePrefix{
   private UserKey(String prefix) {
      super(prefix);
   }
   public static UserKey getById = new UserKey("id");
   public static UserKey getByName = new UserKey("name");
}
测试
@RequestMapping("/redis/get")
@ResponseBody
public Result<User> redisGet() {
   User  user  = redisService.get(UserKey.getById, ""+1, User.class);
    return Result.success(user);
}
@RequestMapping("/redis/set")
@ResponseBody
public Result<Boolean> redisSet() {
   User user  = new User();
   user.setId(1);
   user.setName("1111");
   redisService.set(UserKey.getById, ""+1, user);//UserKey:id1
    return Result.success(true);
}
public class MiaoshaUserKey extends BasePrefix{
   public static final int TOKEN_EXPIRE = 3600*24 * 2;
   private MiaoshaUserKey(int expireSeconds, String prefix) {
      super(expireSeconds, prefix);
   }
   public static MiaoshaUserKey token = new MiaoshaUserKey(TOKEN_EXPIRE, "tk");
}
8:MD5前端固定盐值加密和 后端随机盐值加密
9:加密方法
public static String md5(String src) {
   return DigestUtils.md5Hex(src);
}
需要以下依赖
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.6</version>
</dependency>
10:Validate自定义验证
在class中的字段添加如下注解
public class LoginVo {
   
   @NotNull
   @IsMobile
   private String mobile;
   
   @NotNull
   @Length(min=32)
   private String password;
   
   public String getMobile() {
      return mobile;
   }
   public void setMobile(String mobile) {
      this.mobile = mobile;
   }
   public String getPassword() {
      return password;
   }
   public void setPassword(String password) {
      this.password = password;
   }
   @Override
   public String toString() {
      return "LoginVo [mobile=" + mobile + ", password=" + password + "]";
   }
}
注解声明
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {IsMobileValidator.class })
public @interface  IsMobile {
   
   boolean required() default true;
   
   String message() default "手机号码格式错误";
   Class<?>[] groups() default { };
   Class<? extends Payload>[] payload() default { };
}
public class IsMobileValidator implements ConstraintValidator<IsMobile, String> {
   private boolean required = false;
   
   public void initialize(IsMobile constraintAnnotation) {
      required = constraintAnnotation.required();
   }
   public boolean isValid(String value, ConstraintValidatorContext context) {
      if(required) {
         return ValidatorUtil.isMobile(value);
      }else {
         if(StringUtils.isEmpty(value)) {
            return true;
         }else {
            return ValidatorUtil.isMobile(value);
         }
      }
   }
}
使用方法
@RequestMapping("/do_login")
@ResponseBody
public Result<Boolean> doLogin(HttpServletResponse response, @Valid LoginVo loginVo) {
   log.info(loginVo.toString());
   //登录
   userService.login(response, loginVo);
   return Result.success(true);
}
service层 login 方法
public boolean login(HttpServletResponse response, LoginVo loginVo) {
   if(loginVo == null) {
      throw new GlobalException(CodeMsg.SERVER_ERROR);
   }
   String mobile = loginVo.getMobile();
   String formPass = loginVo.getPassword();
   //判断手机号是否存在
   MiaoshaUser user = getById(Long.parseLong(mobile));
   if(user == null) {
      throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
   }
   //验证密码
   String dbPass = user.getPassword();
   String saltDB = user.getSalt();
   String calcPass = MD5Util.formPassToDBPass(formPass, saltDB);
   if(!calcPass.equals(dbPass)) {
      throw new GlobalException(CodeMsg.PASSWORD_ERROR);
   }
   //生成cookie
   String token    = UUIDUtil.uuid();
   addCookie(response, token, user);
   return true;
}
private void addCookie(HttpServletResponse response, String token, MiaoshaUser user) {
   redisService.set(MiaoshaUserKey.token, token, user);
   Cookie cookie = new Cookie(COOKI_NAME_TOKEN, token);
   cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());
   cookie.setPath("/");
   response.addCookie(cookie);
}
11:全局异常处理
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler(value=Exception.class)
public Result<String> exceptionHandler(HttpServletRequest request, Exception e){
e.printStackTrace();
if(e instanceof GlobalException) {
GlobalException ex = (GlobalException)e;
return Result.error(ex.getCm());
}else if(e instanceof BindException) {
BindException ex = (BindException)e;
List<ObjectError> errors = ex.getAllErrors();
ObjectError error = errors.get(0);
return Result.error(CodeMsg.BIND_ERROR.fillArgs(msg));
String msg = error.getDefaultMessage();
}else {
return Result.error(CodeMsg.SERVER_ERROR);
}
}
}
12:UUID生成
public class UUIDUtil {
   public static String uuid() {
      return UUID.randomUUID().toString().replace("-", "");
   }
}
13:CookieValue使用
@CookieValue(value="name",required=false) String name,
14:动态添加参数
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
@Autowired
UserArgumentResolver userArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(userArgumentResolver);
}
@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver {
@Autowired
MiaoshaUserService userService;
public boolean supportsParameter(MethodParameter parameter) {
Class<?> clazz = parameter.getParameterType();
return clazz==MiaoshaUser.class;
}
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
String paramToken = request.getParameter(MiaoshaUserService.COOKI_NAME_TOKEN);
String cookieToken = getCookieValue(request, MiaoshaUserService.COOKI_NAME_TOKEN);
if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
return null;
}
String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
return userService.getByToken(response, token);
}
private String getCookieValue(HttpServletRequest request, String cookiName) {
Cookie[] cookies = request.getCookies();
for(Cookie cookie : cookies) {
if(cookie.getName().equals(cookiName)) {
return cookie.getValue();
}
}
return null;
}
}
service层代码
public MiaoshaUser getByToken(HttpServletResponse response, String token) {
   if(StringUtils.isEmpty(token)) {
      return null;
   }
   MiaoshaUser user = redisService.get(MiaoshaUserKey.token, token, MiaoshaUser.class);
   //延长有效期
   if(user != null) {
      addCookie(response, token, user);
   }
   return user;
}
15:秒杀详情页通过后端判断当前秒杀状态
@RequestMapping("/to_detail/{goodsId}")
public String detail(Model model,MiaoshaUser user,
      @PathVariable("goodsId")long goodsId) {
   model.addAttribute("user", user);
   
   GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
   model.addAttribute("goods", goods);
   
   long startAt = goods.getStartDate().getTime();
   long endAt = goods.getEndDate().getTime();
   long now = System.currentTimeMillis();
   
   int miaoshaStatus = 0;
   int remainSeconds = 0;
   if(now < startAt ) {//秒杀还没开始,倒计时
      miaoshaStatus = 0;
      remainSeconds = (int)((startAt - now )/1000);
   }else  if(now > endAt){//秒杀已经结束
      miaoshaStatus = 2;
      remainSeconds = -1;
   }else {//秒杀进行中
      miaoshaStatus = 1;
      remainSeconds = 0;
   }
   model.addAttribute("miaoshaStatus", miaoshaStatus);
   model.addAttribute("remainSeconds", remainSeconds);
    return "goods_detail";
}
16:获取插入后的id 
@Insert("insert into order_info(user_id, goods_id, goods_name, goods_count, goods_price, order_channel, status, create_date)values("
      + "#{userId}, #{goodsId}, #{goodsName}, #{goodsCount}, #{goodsPrice}, #{orderChannel},#{status},#{createDate} )")
@SelectKey(keyColumn="id", keyProperty="id", resultType=long.class, before=false, statement="select last_insert_id()")
public long insert(OrderInfo orderInfo);
17:
(1)页面缓存,url缓存,对象缓存
(2)页面静态化,前后端分离
(3)页面静态资源优化
18:页面缓存
@RequestMapping(value="/to_list", produces="text/html")
@ResponseBody
public String list(HttpServletRequest request, HttpServletResponse response, Model model,MiaoshaUser user) {
model.addAttribute("user", user);
//取缓存
String html = redisService.get(GoodsKey.getGoodsList, "", String.class);
if(!StringUtils.isEmpty(html)) {
return html;
}
List<GoodsVo> goodsList = goodsService.listGoodsVo();
model.addAttribute("goodsList", goodsList);
// return "goods_list";
SpringWebContext ctx = new SpringWebContext(request,response,
request.getServletContext(),request.getLocale(), model.asMap(), applicationContext );
//手动渲染
html = thymeleafViewResolver.getTemplateEngine().process("goods_list", ctx);
if(!StringUtils.isEmpty(html)) {
redisService.set(GoodsKey.getGoodsList, "", html);
}
return html;
}
19:url缓存
   @RequestMapping(value="/to_detail2/{goodsId}",produces="text/html")
    @ResponseBody
    public String detail2(HttpServletRequest request, HttpServletResponse response, Model model,MiaoshaUser user,
          @PathVariable("goodsId")long goodsId) {
       model.addAttribute("user", user);
       
       //取缓存
       String html = redisService.get(GoodsKey.getGoodsDetail, ""+goodsId, String.class);
       if(!StringUtils.isEmpty(html)) {
          return html;
       }
       //手动渲染
       GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
       model.addAttribute("goods", goods);
       
       long startAt = goods.getStartDate().getTime();
       long endAt = goods.getEndDate().getTime();
       long now = System.currentTimeMillis();
       
       int miaoshaStatus = 0;
       int remainSeconds = 0;
       if(now < startAt ) {//秒杀还没开始,倒计时
          miaoshaStatus = 0;
          remainSeconds = (int)((startAt - now )/1000);
       }else  if(now > endAt){//秒杀已经结束
          miaoshaStatus = 2;
          remainSeconds = -1;
       }else {//秒杀进行中
          miaoshaStatus = 1;
          remainSeconds = 0;
       }
       model.addAttribute("miaoshaStatus", miaoshaStatus);
       model.addAttribute("remainSeconds", remainSeconds);
//        return "goods_detail";
       
       SpringWebContext ctx = new SpringWebContext(request,response,
             request.getServletContext(),request.getLocale(), model.asMap(), applicationContext );
       html = thymeleafViewResolver.getTemplateEngine().process("goods_detail", ctx);
       if(!StringUtils.isEmpty(html)) {
          redisService.set(GoodsKey.getGoodsDetail, ""+goodsId, html);
       }
       return html;
    }
20:静态资源配置
#static
spring.resources.add-mappings=true
spring.resources.cache-period= 3600
spring.resources.chain.cache=true
spring.resources.chain.enabled=true
spring.resources.chain.gzipped=true
spring.resources.chain.html-application-cache=true
spring.resources.static-locations=classpath:/static/
21:避免超卖
@Update("update miaosha_goods set stock_count = stock_count - 1 where goods_id = #{goodsId} and stock_count > 0")
public int reduceStock(MiaoshaGoods g);
22:秒杀接口优化
1:redis预减库存减少数据库访问
2:内存标记减少数据库访问
3:请求先写入队列异步下单
22:超卖问题
1:数据库添加唯一索引防止用户重复下单
2:sql减库存数量判断,防止库存变为负数
23:秒杀最终代码
public Result<Integer> miaosha(Model model,MiaoshaUser user,
@RequestParam("goodsId")long goodsId,
@PathVariable("path") String path) {
model.addAttribute("user", user);
if(user == null) {
return Result.error(CodeMsg.SESSION_ERROR);
}
//验证path
boolean check = miaoshaService.checkPath(user, goodsId, path);
if(!check){
return Result.error(CodeMsg.REQUEST_ILLEGAL);
}
//内存标记,减少redis访问
boolean over = localOverMap.get(goodsId);
if(over) {
return Result.error(CodeMsg.MIAO_SHA_OVER);
}
//预减库存
long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock, ""+goodsId);//10
if(stock < 0) {
localOverMap.put(goodsId, true);
return Result.error(CodeMsg.MIAO_SHA_OVER);
}
//判断是否已经秒杀到了
MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
if(order != null) {
return Result.error(CodeMsg.REPEATE_MIAOSHA);
}
//入队
MiaoshaMessage mm = new MiaoshaMessage();
mm.setUser(user);
mm.setGoodsId(goodsId);
sender.sendMiaoshaMessage(mm);
return Result.success(0);//排队中
}
24:库存redis初始化
public class MiaoshaController implements InitializingBean {
private HashMap<Long, Boolean> localOverMap = new HashMap<Long, Boolean>();
/**
* 系统初始化
* */
public void afterPropertiesSet() throws Exception {
List<GoodsVo> goodsList = goodsService.listGoodsVo();
if(goodsList == null) {
return;
}
for(GoodsVo goods : goodsList) {
redisService.set(GoodsKey.getMiaoshaGoodsStock, ""+goods.getId(), goods.getStockCount());
localOverMap.put(goods.getId(), false);
}
}
}
25:秒杀接口地址隐藏
秒杀按钮点击的时候先去获取秒杀接口地址,同时再去请求秒杀接口地址的时候判断地址是否合法
@AccessLimit(seconds=5, maxCount=5, needLogin=true)
@RequestMapping(value="/path", method=RequestMethod.GET)
@ResponseBody
public Result<String> getMiaoshaPath(HttpServletRequest request, MiaoshaUser user,
@RequestParam("goodsId")long goodsId,
@RequestParam(value="verifyCode", defaultValue="0")int verifyCode
) {
if(user == null) {
return Result.error(CodeMsg.SESSION_ERROR);
}
boolean check = miaoshaService.checkVerifyCode(user, goodsId, verifyCode);
if(!check) {
return Result.error(CodeMsg.REQUEST_ILLEGAL);
}
String path =miaoshaService.createMiaoshaPath(user, goodsId);
return Result.success(path);
}
public String createMiaoshaPath(MiaoshaUser user, long goodsId) {
   if(user == null || goodsId <=0) {
      return null;
   }
   String str = MD5Util.md5(UUIDUtil.uuid()+"123456");
       redisService.set(MiaoshaKey.getMiaoshaPath, ""+user.getId() + "_"+ goodsId, str);
   return str;
}
26:在获取秒杀地址之前添加验证码验证
这样做的好处1:避免机器防刷2:可以起到分流的作用
27:接口限流
1添加注解
@Retention(RUNTIME)
@Target(METHOD)
public @interface AccessLimit {
int seconds();
int maxCount();
boolean needLogin() default true;
}
使用
@AccessLimit(seconds=5, maxCount=5, needLogin=true)
添加拦截器拦截注解
@Service
public class AccessInterceptor extends HandlerInterceptorAdapter{
@Autowired
MiaoshaUserService userService;
@Autowired
RedisService redisService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if(handler instanceof HandlerMethod) {
MiaoshaUser user = getUser(request, response);
UserContext.setUser(user);
HandlerMethod hm = (HandlerMethod)handler;
AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
if(accessLimit == null) {
return true;
}
int seconds = accessLimit.seconds();
int maxCount = accessLimit.maxCount();
boolean needLogin = accessLimit.needLogin();
String key = request.getRequestURI();
if(needLogin) {
if(user == null) {
render(response, CodeMsg.SESSION_ERROR);
return false;
}
key += "_" + user.getId();
}else {
//do nothing
}
AccessKey ak = AccessKey.withExpire(seconds);
Integer count = redisService.get(ak, key, Integer.class);
if(count == null) {
redisService.set(ak, key, 1);
}else if(count < maxCount) {
redisService.incr(ak, key);
}else {
render(response, CodeMsg.ACCESS_LIMIT_REACHED);
return false;
}
}
return true;
}
private void render(HttpServletResponse response, CodeMsg cm)throws Exception {
response.setContentType("application/json;charset=UTF-8");
OutputStream out = response.getOutputStream();
String str = JSON.toJSONString(Result.error(cm));
out.write(str.getBytes("UTF-8"));
out.flush();
out.close();
}
private MiaoshaUser getUser(HttpServletRequest request, HttpServletResponse response) {
String paramToken = request.getParameter(MiaoshaUserService.COOKI_NAME_TOKEN);
String cookieToken = getCookieValue(request, MiaoshaUserService.COOKI_NAME_TOKEN);
if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
return null;
}
String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
return userService.getByToken(response, token);
}
private String getCookieValue(HttpServletRequest request, String cookiName) {
Cookie[] cookies = request.getCookies();
if(cookies == null || cookies.length <= 0){
return null;
}
for(Cookie cookie : cookies) {
if(cookie.getName().equals(cookiName)) {
return cookie.getValue();
}
}
return null;
}
}
添加当前用户到threadlocal
public class UserContext {
   
   private static ThreadLocal<MiaoshaUser> userHolder = new ThreadLocal<MiaoshaUser>();
   
   public static void setUser(MiaoshaUser user) {
      userHolder.set(user);
   }
   
   public static MiaoshaUser getUser() {
      return userHolder.get();
   }
}
添加拦截器和方法参数
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
@Autowired
UserArgumentResolver userArgumentResolver;
@Autowired
AccessInterceptor accessInterceptor;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(userArgumentResolver);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(accessInterceptor);
}
}
标签:mob 计时 into hand mil tor 插入 obj ken
原文地址:https://www.cnblogs.com/itpy/p/11824596.html