栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

基于SpringBoot、Redis和RabbitMQ的秒杀系统

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

基于SpringBoot、Redis和RabbitMQ的秒杀系统

基于SpringBoot、Redis和RabbitMQ的秒杀系统

文章目录
  • 基于SpringBoot、Redis和RabbitMQ的秒杀系统
    • 登录功能总结
      • 一、两次MD5加密
      • 二、参数校验
      • 三、记录用户信息
      • 四、分布式Session
        • 4.1、Spring Session配合Redis实现分布式Session
        • 4.2、直接将用户信息存入Redis
      • 五、优化登录功能

秒杀系统解决的主要问题:并发读、并发写。并发读的核心优化理念是尽量减少用户到服务端来“读”数据,或者让他们读更少的数据;并发写的处理原则也一样,它要求我们在数据库层面独立出来一个库,做特殊的处理。

登录功能总结 一、两次MD5加密
  1. 前端:Password=MD5(明文+固定Salt)
  2. 后端:Password=MD5(用户输入+随机Salt)

用户端MD5加密是为了防止用户密码在网络中明文传输,服务端MD5加密是为了提高密码安全性,双重保险。

  1. 引入pom.xml

    
        commons-codec
        commons-codec
    
    
  2. 编写MD5工具类

    public class MD5Util {
    
        public static String md5(String src) {
            return DigestUtils.md5Hex(src);
        }
    
        private static final String salt = "1a2b3c4d";
    
        public static String inputPassToFormPass(String inputPass) {
            String str = ""+salt.charAt(0)+salt.charAt(2) + inputPass +salt.charAt(5) + salt.charAt(4);
            return md5(str);
        }
    
        public static String formPassToDBPass(String formPass, String salt) {
            String str = ""+salt.charAt(0)+salt.charAt(2) + formPass +salt.charAt(5) + salt.charAt(4);
            return md5(str);
        }
    
        public static String inputPassToDbPass(String inputPass, String saltDB) {
            String formPass = inputPassToFormPass(inputPass);
            String dbPass = formPassToDBPass(formPass, saltDB);
            return dbPass;
        }
    
        public static void main(String[] args) {
            System.out.println(inputPassToFormPass("123456"));//d3b1294a61a07da9b49b6e22b2cbd7f9
            System.out.println(formPassToDBPass(inputPassToFormPass("123456"), "1a2b3c4d"));
            System.out.println(inputPassToDbPass("123456", "1a2b3c4d"));
        }
    }
    
二、参数校验

每个类都写大量的健壮性判断过于麻烦,我们可以使用 validation 简化我们的代码

  1. pom.xml

    
        org.springframework.boot
        spring-boot-starter-validation
    
    
  2. 自定义手机号码验证规则

    public class IsMobilevalidator implements ConstraintValidator {
    
        private boolean required = false;
    
        @Override
        public void initialize(IsMobile constraintAnnotation) {
            required = constraintAnnotation.required();
        }
    
        @Override
        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);
                }
            }
        }
    }
    
  3. 自定义注解IsMobile

    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
    @Retention(RUNTIME)
    @documented
    @Constraint(validatedBy = {IsMobilevalidator.class})
    public @interface IsMobile {
        boolean required() default true;
        String message() default "手机号码格式错误";
        Class[] groups() default {};
        Class[] payload() default {};
    }
    
  4. 校验手机号工具类

    public class ValidatorUtil {
    
        private static final Pattern mobile_pattern = Pattern.compile("[1]([3-9])[0-9]{9}$");
        public static boolean isMobile(String mobile){
            if (StringUtils.isEmpty(mobile)) {
                return false;
            }
            Matcher matcher = mobile_pattern.matcher(mobile);
            return matcher.matches();
        }
    }
    
  5. 使用方法:直接加在需要校验的字段上

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class LoginVo {
    
        @NotNull
        @IsMobile
        private String mobile;
    
        @NotNull
        @Length(min = 32)
        private String password;
    
    }
    
  6. 登录方法的入参添加@Valid

    @RequestMapping("/doLogin")
    @ResponseBody
    public RespBean doLogin(@Valid LoginVo loginVo) {
       log.info(loginVo.toString());
       return userService.login(loginVo);
    }
    
三、记录用户信息

最常用的就是使用cookie+session记录用户信息

  1. cookie工具类

    public class cookieUtil {
    
        
        public static String getcookievalue(HttpServletRequest request, String cookieName) {
            return getcookievalue(request, cookieName, false);
        }
    
        
        public static String getcookievalue(HttpServletRequest request, String cookieName, boolean isDecoder) {
            cookie[] cookieList = request.getcookies();
            if (cookieList == null || cookieName == null) {
                return null;
            }
            String retValue = null;
            try {
                for (int i = 0; i < cookieList.length; i++) {
                    if (cookieList[i].getName().equals(cookieName)) {
                        if (isDecoder) {
                            retValue = URLDecoder.decode(cookieList[i].getValue(),
                                    "UTF-8");
                        } else {
                            retValue = cookieList[i].getValue();
                        }
                        break;
                    }
                }
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return retValue;
        }
    
        
        public static String getcookievalue(HttpServletRequest request, String
                cookieName, String encodeString) {
            cookie[] cookieList = request.getcookies();
            if (cookieList == null || cookieName == null) {
                return null;
            }
            String retValue = null;
            try {
                for (int i = 0; i < cookieList.length; i++) {
                    if (cookieList[i].getName().equals(cookieName)) {
                        retValue = URLDecoder.decode(cookieList[i].getValue(),
                                encodeString);
                        break;
                    }
                }
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return retValue;
        }
    
        
        public static void setcookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookievalue) {
            setcookie(request, response, cookieName, cookievalue, -1);
        }
    
        
        public static void setcookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookievalue, int cookieMaxage) {
            setcookie(request, response, cookieName, cookievalue, cookieMaxage, false);
        }
    
        
        public static void setcookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookievalue, boolean isEncode) {
            setcookie(request, response, cookieName, cookievalue, -1, isEncode);
        }
    
        
        public static void setcookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookievalue, int cookieMaxage, boolean isEncode) {
            doSetcookie(request, response, cookieName, cookievalue, cookieMaxage, isEncode);
        }
    
        
        public static void setcookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookievalue, int cookieMaxage, String encodeString) {
            doSetcookie(request, response, cookieName, cookievalue, cookieMaxage, encodeString);
        }
    
        
        public static void deletecookie(HttpServletRequest request, HttpServletResponse response, String cookieName) {
            doSetcookie(request, response, cookieName, "", -1, false);
        }
    
        
        private static final void doSetcookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookievalue, int cookieMaxage, boolean isEncode) {
            try {
                if (cookievalue == null) {
                    cookievalue = "";
                } else if (isEncode) {
                    cookievalue = URLEncoder.encode(cookievalue, "utf-8");
                }
                cookie cookie = new cookie(cookieName, cookievalue);
                if (cookieMaxage > 0)
                    cookie.setMaxAge(cookieMaxage);
                if (null != request) {// 设置域名的cookie
                    String domainName = getDomainName(request);
                    System.out.println(domainName);
                    if (!"localhost".equals(domainName)) {
                        cookie.setDomain(domainName);
                    }
                }
                cookie.setPath("/");
                response.addcookie(cookie);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        private static final void doSetcookie(HttpServletRequest request,
                                              HttpServletResponse response,
                                              String cookieName, String cookievalue,
                                              int cookieMaxage, String encodeString) {
            try {
                if (cookievalue == null) {
                    cookievalue = "";
                } else {
                    cookievalue = URLEncoder.encode(cookievalue, encodeString);
                }
                cookie cookie = new cookie(cookieName, cookievalue);
                if (cookieMaxage > 0) {
                    cookie.setMaxAge(cookieMaxage);
                }
                if (null != request) {// 设置域名的cookie
                    String domainName = getDomainName(request);
                    System.out.println(domainName);
                    if (!"localhost".equals(domainName)) {
                        cookie.setDomain(domainName);
                    }
                }
                cookie.setPath("/");
                response.addcookie(cookie);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        
        private static final String getDomainName(HttpServletRequest request) {
            String domainName = null;
            // 通过request对象获取访问的url地址
            String serverName = request.getRequestURL().toString();
            if (serverName == null || serverName.equals("")) {
                domainName = "";
            } else {
                // 将url地下转换为小写
                serverName = serverName.toLowerCase();
                // 如果url地址是以http://开头 将http://截取
                if (serverName.startsWith("http://")) {
                    serverName = serverName.substring(7);
                }
                int end = serverName.length();
                // 判断url地址是否包含"/"
                if (serverName.contains("/")) {
                    //得到第一个"/"出现的位置
                    end = serverName.indexOf("/");
                }
                // 截取
                serverName = serverName.substring(0, end);
                // 根据"."进行分割
                final String[] domains = serverName.split("\.");
                int len = domains.length;
                if (len > 3) {
                    // www.xxx.com.cn
                    domainName = domains[len - 3] + "." + domains[len - 2] + "." +
                            domains[len - 1];
                } else if (len <= 3 && len > 1) {
                    // xxx.com or xxx.cn
                    domainName = domains[len - 2] + "." + domains[len - 1];
                } else {
                    domainName = serverName;
                }
            }
            if (domainName != null && domainName.indexOf(":") > 0) {
                String[] ary = domainName.split("\:");
                domainName = ary[0];
            }
            return domainName;
        }
    }
    
  2. UUID工具类

    public class UUIDUtil {
        public static String uuid() {
            return UUID.randomUUID().toString().replace("-", "");
        }
    }
    
  3. 登录业务中加入以下代码

    //生成cookie
    String ticket = UUIDUtil.uuid();
    request.getSession().setAttribute(ticket,user);
    cookieUtil.setcookie(request,response,"userTicket",ticket);
    
四、分布式Session

之前的代码如果所有操作都在一台Tomcat上,没有什么问题。当我们部署多台系统,配合Nginx的时候会出现用户登录的问题,由于 Nginx 使用默认负载均衡策略(轮询),请求将会按照时间顺序逐一分发到后端应用上。也就是说刚开始我们在 Tomcat1 登录之后,用户信息放在 Tomcat1 的 Session 里。过了一会,请求又被 Nginx 分发到了 Tomcat2 上,这时 Tomcat2 上 Session 里还没有用户信息,于是又要登录。

4.1、Spring Session配合Redis实现分布式Session
  1. pom.xml

    
    	org.springframework.boot
    	spring-boot-starter-data-redis
    
    
    
    	org.apache.commons
    	commons-pool2
    
    
    
    	org.springframework.session
    	spring-session-data-redis
    
    
  2. 配置redis,修改application.yml

    spring:
     redis:
       #超时时间
       timeout: 10000ms
       #服务器地址
       host: xxx.xxx.xxx.xxx
       #服务器端口
       port: 6379
       #数据库
       database: 0
       #密码
       password: root
       lettuce:
         pool:
           #最大连接数,默认8
           max-active: 1024
           #最大连接阻塞等待时间,默认-1
           max-wait: 10000ms
           #最大空闲连接
           max-idle: 200
           #最小空闲连接
           min-idle: 5
    
  3. 完成上述操作后进行用户登录会在redis中产生用户信息相关的数据

4.2、直接将用户信息存入Redis
  1. Redis配置类RedisConfig.java

    @Configuration
    public class RedisConfig {
    
        @Bean
        public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory){
            RedisTemplate redisTemplate = new RedisTemplate<>();
            //key序列器
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            //value序列器
            redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
            //Hash类型 key序列器
            redisTemplate.setHashKeySerializer(new StringRedisSerializer());
            //Hash类型 value序列器
            redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
            redisTemplate.setConnectionFactory(connectionFactory);
            return redisTemplate;
        }
    }
    
  2. 修改之前添加cookie的代码

    //生成cookie
    String ticket = UUIDUtil.uuid();
    redisTemplate.opsForValue().set("user:" + ticket, user);
    // request.getSession().setAttribute(ticket,user);
    cookieUtil.setcookie(request,response,"userTicket",ticket);
    

此时有一个弊端:用户登录后直接将用户信息存入Redis中了,没有设置过期时间,cookie过期后,重新登录又会产生新的cookie并加到Redis中。

五、优化登录功能

想象一下用户登录后,随意跳转到其它页面是不是都要传入cookie判断用户是否存在,这样重复琐碎的操作可以使用MVC配置类在传入参数前就进行校验。若没有做此层的优化,后续在秒杀功能时,创建的订单将无用户id,原因就是取不到前面传入的用户信息。

  1. UserArgumentResolver.java

    @Component
    public class UserArgumentResolver implements HandlerMethodArgumentResolver {
    
        @Autowired
        private IUserService userService;
    
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            Class clazz = parameter.getParameterType();
            return clazz == User.class;
        }
    
        @Override
        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 ticket = cookieUtil.getcookievalue(request, "userTicket");
            if (StringUtils.isEmpty(ticket)) {
                return null;
            }
            return userService.getUserBycookie(ticket, request, response);
        }
    }
    
  2. MVC配置类WebConfig.java

    @Configuration
    @EnableWebMvc
    public class WebConfig implements WebMvcConfigurer {
    
        @Autowired
        private UserArgumentResolver userArgumentResolver;
    
        @Override
        public void addArgumentResolvers(List resolvers) {
            resolvers.add(userArgumentResolver);
        }
        
    }
    
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/458891.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号