秒杀流程演示

1登录页面

http://localhost:8080/login/toLoginjava 秒杀活动 队列 java秒杀流程_session

2.登录成功,进入商品列表页面

http://localhost:8080/goods/toListjava 秒杀活动 队列 java秒杀流程_java_02

3.商品详情

http://localhost:8080/goodsDetail.htm?goodsId=2java 秒杀活动 队列 java秒杀流程_java 秒杀活动 队列_03

3.1 秒杀,进入排队,等待秒杀结果 getResult

java 秒杀活动 队列 java秒杀流程_java_04

3.2 秒杀成功,跳转订单页面 跳转

java 秒杀活动 队列 java秒杀流程_java_05

4.订单页面

http://localhost:8080/orderDetail.htm?orderId=16046java 秒杀活动 队列 java秒杀流程_java_06

1用户登录

1.1页面模板

login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <script type="text/javascript" src="/js/jquery.min.js" ></script>
    <script type="text/javascript" src="/js/md5.min.js" ></script>
    <script type="text/javascript" src="/js/common.js"></script>
</head>
<body>
<div>
    <form action="/login/doLogin" id="loginForm">
        <table>
            <tr>
                <td>用户名</td>
                <td><input type="text" id="mobile" placeholder="请输入用户名"></td>
            </tr>
            <tr>
                <td>密码</td>
                <td><input type="text" id="password" placeholder="请输入密码"></td>
            </tr>
            <tr style="">
                <td></td>
                <td>
                    <button type="button" onclick="login()">提交</button>
                </td>

            </tr>
        </table>
    </form>

</div>
</body>
<script>
    console.log("cch....");
    function login(){
        var salt="1a2b3c4d";
        var mobile=$("#mobile").val();
        var inputpass=$("#password").val();
        console.log("inputpass:"+inputpass)
        var  str=""+salt.charAt(0)+salt.charAt(2)+inputpass+salt.charAt(5)+salt.charAt(4);
        str=""+salt.charAt(0)+salt.charAt(2)+inputpass+salt.charAt(5)+salt.charAt(4);
        var password=md5(str);
        console.log("password:"+password)
        alert("ready");

        $.ajax({
          url:"/login/doLogin",
          type:"post",
          data:{
            mobile:mobile,
            password:password
          },
          success:function (data){
            if(data.code==200){
              alert("登录成功");
              window.location.href="/goods/toList"
            }else{
              alert(data.message);
            }
          },
          error:function (data){
            alert("登录出现问题");
          }
        })

      }

</script>
</html>

common.js

//展示loading
function g_showLoading(){
	var idx = layer.msg('处理中...', {icon: 16,shade: [0.5, '#f5f5f5'],scrollbar: false,offset: '0px', time:100000}) ;  
	return idx;
}
//salt
var g_passsword_salt="1a2b3c4d"
// 获取url参数
function g_getQueryString(name) {
	var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
	var r = window.location.search.substr(1).match(reg);
	if(r != null) return unescape(r[2]);
	return null;
};


//设定时间格式化函数,使用new Date().format("yyyy-MM-dd HH:mm:ss");
Date.prototype.format = function (format) {
	var args = {
		"M+": this.getMonth() + 1,
		"d+": this.getDate(),
		"H+": this.getHours(),
		"m+": this.getMinutes(),
		"s+": this.getSeconds(),
	};
	if (/(y+)/.test(format))
		format = format.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
	for (var i in args) {
		var n = args[i];
		if (new RegExp("(" + i + ")").test(format))
			format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? n : ("00" + n).substr(("" + n).length));
	}
	return format;
};

jquery.js md5.js

jQuery v2.1.4
https://github.com/emn178/js-md5

1.2后端

LoginController

package com.example.miaosha.controller;


import com.example.miaosha.pojo.User;
import com.example.miaosha.service.IUserService;
import com.example.miaosha.utils.CookieUtil;
import com.example.miaosha.vo.LoginVo;
import com.example.miaosha.vo.RespBean;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author cch
 * @since 2021-11-14
 */
@Controller
@RequestMapping("/login")
@Slf4j
public class LoginController {

    @Autowired
    private IUserService userService;

    @RequestMapping("/toLogin")
    public String toLgoin(){
        return "login";
    }

    @RequestMapping("/doLogin")
    @ResponseBody
    public RespBean doLgoin(@Valid LoginVo loginVo, HttpServletRequest request, HttpServletResponse response){
        //log.info("{}",loginVo);
        return userService.doLgoin(loginVo,request,response);
    }

    @RequestMapping("/exitLogin")
    @ResponseBody
    public RespBean exitLgoin (HttpSession session, Model model, @CookieValue("userTicket") String ticket, HttpServletRequest request, HttpServletResponse response){
        //log.info("{}",loginVo);
        //退出登录  删除session 中的ticket 删除cookie  删除model
        if(StringUtils.isEmpty(ticket)){
            return RespBean.success();
        }

        User user=(User) session.getAttribute(ticket);
        if(null!=user){
            session.removeAttribute(ticket);
        }

        Cookie cookie = CookieUtil.getCookie(request, "userTicket");
        if(null!=cookie){
            CookieUtil.deleteCookie(response,cookie);
        }

        return RespBean.success();
    }

}

IUserService

package com.example.miaosha.service;

import com.example.miaosha.pojo.User;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.miaosha.vo.LoginVo;
import com.example.miaosha.vo.RespBean;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * <p>
 *  服务类
 * </p>
 *
 * @author cch
 * @since 2021-11-14
 */
public interface IUserService extends IService<User> {
    /**
    * 功能描述:登录
    *
    * @param
    * @return
    *
    * @since:1.0.0
    * @Author:cch
    * */

    RespBean doLgoin(LoginVo loginVo, HttpServletRequest request, HttpServletResponse response);
    User getUserByCookie(String userTicket,HttpServletRequest request, HttpServletResponse response);
    RespBean updatePassword(String userTicket,String password, HttpServletRequest request, HttpServletResponse response);
}

UserServiceImpl

package com.example.miaosha.service.impl;

import com.example.miaosha.exception.GlobalException;
import com.example.miaosha.pojo.User;
import com.example.miaosha.mapper.UserMapper;
import com.example.miaosha.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.miaosha.utils.CookieUtil;
import com.example.miaosha.utils.MD5Util;
import com.example.miaosha.utils.UUIDUtil;
import com.example.miaosha.vo.LoginVo;
import com.example.miaosha.vo.RespBean;
import com.example.miaosha.vo.RespBeanEnum;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author cch
 * @since 2021-11-14
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public RespBean doLgoin(LoginVo loginVo, HttpServletRequest request, HttpServletResponse response) {
        String mobile = loginVo.getMobile();
        String password = loginVo.getPassword();
        //参数校验 健壮性判断
        /*
        if(StringUtils.isEmpty(mobile)||StringUtils.isEmpty(password)){
            return RespBean.error(RespBeanEnum.LOGIN_ERROR);
        }
        if(!ValidatorUtil.isMobile(mobile)){
            return RespBean.error(RespBeanEnum.MOBILE_ERROR);
        }

         */
        //根据手机号获取用户
        User user = userMapper.selectById(mobile);
        if(user==null){
            //return RespBean.error(RespBeanEnum.LOGIN_ERROR);
            throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
        }
        //判断密码是否正确
        if(!MD5Util.fromPassToDBpass(password,user.getSlat()).equals(user.getPassword())){
            //return RespBean.error(RespBeanEnum.LOGIN_ERROR);
            throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
        }
        //生成cookie
        String ticket = UUIDUtil.uuid();

        redisTemplate.opsForValue().set("user:"+ticket,user);

        //request.getSession().setAttribute(ticket,user);
        CookieUtil.setCookie(response,"userTicket",ticket);

        return  RespBean.success(ticket);
    }

    @Override
    public User getUserByCookie(String userTicket,HttpServletRequest request, HttpServletResponse response) {
        if(StringUtils.isEmpty(userTicket)){
            return null;
        }
        User user = (User)redisTemplate.opsForValue().get("user:" + userTicket);
        if(user!=null){
            CookieUtil.setCookie(response,"userTicket",userTicket);
        }
        return user;
    }

    @Override
    @Transactional
    public RespBean updatePassword(String userTicket, String password, HttpServletRequest request, HttpServletResponse response) {
        //更新密码,1.从redis中获取user信息,2更新数据库,3情况redis缓存
        User user = getUserByCookie(userTicket, request, response);
        if(null==user){
            throw new GlobalException(RespBeanEnum.REQUIRE_LOGIN_ERROR);
        }
        user.setPassword(MD5Util.fromPassToDBpass(password,"1a2b3c4d"));
        //2 更新数据库
        int result = userMapper.updateById(user);
        if(1==result){
            //3情况redis缓存
            redisTemplate.delete("user:" + userTicket);
            return RespBean.success();
        }
        return RespBean.error(RespBeanEnum.UPDATE_PASSWORD_ERROR);
    }

}

UUIDUtil

package com.example.miaosha.utils;

import java.util.UUID;

public class UUIDUtil {
    public static String uuid() {
        return UUID.randomUUID().toString().replace("-", "");
    }
}

CookieUtil

package com.example.miaosha.utils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CookieUtil {

    /**
     * 根据Cookie名称得到Cookie的值,没有返回Null
     *
     * @param request
     * @param name
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String name) {
        Cookie cookie = getCookie(request, name);
        if (cookie != null) {
            return cookie.getValue();
        } else {
            return null;
        }
    }

    /**
     * 根据Cookie名称得到Cookie对象,不存在该对象则返回Null
     *
     * @param request
     * @param name
     * @return
     */
    public static Cookie getCookie(HttpServletRequest request, String name) {
        Cookie cookies[] = request.getCookies();
        if (cookies == null || name == null || name.length() == 0) {
            return null;
        }

        Cookie cookie = null;
        for (int i = 0; i < cookies.length; i++) {
            if (!cookies[i].getName().equals(name)) {
                continue;
            }

            cookie = cookies[i];
            if (request.getServerName().equals(cookie.getDomain())) {
                break;
            }
        }

        return cookie;
    }

    /**
     * 删除指定Cookie
     *
     * @param response
     * @param cookie
     */
    public static void deleteCookie(HttpServletResponse response, Cookie cookie) {
        if (cookie != null) {
            cookie.setPath("/");
            cookie.setValue("");
            cookie.setMaxAge(0);
            response.addCookie(cookie);
        }
    }

    /**
     * 删除指定Cookie
     *
     * @param response
     * @param cookie
     */
    public static void deleteCookie(HttpServletResponse response, Cookie cookie, String domain) {
        if (cookie != null) {
            cookie.setPath("/");
            cookie.setValue("");
            cookie.setMaxAge(0);
            cookie.setDomain(domain);
            response.addCookie(cookie);
        }
    }

    /**
     * 添加一条新的Cookie信息,默认有效时间为一个月
     *
     * @param response
     * @param name
     * @param value
     */
    public static void setCookie(HttpServletResponse response, String name, String value) {
        setCookie(response, name, value, 0x278d00);
    }

    /**
     * 添加一条新的Cookie信息,可以设置其最长有效时间(单位:秒)
     *
     * @param response
     * @param name
     * @param value
     * @param maxAge
     */
    public static void setCookie(HttpServletResponse response, String name, String value, int maxAge) {
        if (value == null) {
            value = "";
        }

        Cookie cookie = new Cookie(name, value);
        cookie.setMaxAge(maxAge);
        cookie.setPath("/");
        response.addCookie(cookie);
    }

    /**
     * 添加一条新的Cookie信息,可以设置其Name,Value,MaxAge,Path,Domain(单位:秒)
     *
     * @param response
     * @param name
     * @param value
     * @param maxAge
     */
    public static void setCookie(HttpServletResponse response, String name, String value, int maxAge, String path, String domain) {
        if (value == null) {
            value = "";
        }

        Cookie cookie = new Cookie(name, value);
        cookie.setMaxAge(maxAge);
        cookie.setPath(path);
        cookie.setDomain(domain);
        response.addCookie(cookie);
    }




    /**
     * 获取所有的cookie
     *
     * @return
     */
    public static Cookie[] getCookies(HttpServletRequest request) {
        if (request == null) {
            return null;
        }
        Cookie[] cookies = request.getCookies();
        if (cookies == null || cookies.length == 0) {
            return null;
        }
        return cookies;
    }


}

MD5Util

package com.example.miaosha.utils;

import org.apache.commons.codec.digest.DigestUtils;

public class MD5Util {

    public static String md5(String src){
        return DigestUtils.md5Hex(src);
    }

    public static final String salt="1a2b3c4d";
    public static String inputPassToFromPass(String inputpass){
        String str=""+salt.charAt(0)+salt.charAt(2)+inputpass+salt.charAt(5)+salt.charAt(4);
        return md5(str);
    }
    public static String fromPassToDBpass(String frompass,String salt){
        String str=""+salt.charAt(0)+salt.charAt(2)+frompass+salt.charAt(5)+salt.charAt(4);
        return md5(str);
    }

    public static String inputPassToToDBpass(String inputpass,String salt){
        String fromPass=inputPassToFromPass(inputpass);
        return fromPassToDBpass(fromPass,salt);
    }

    public static void main(String[] args) {
        String inputPass="123456";
        System.out.println(inputPassToFromPass(inputPass));
        //d3b1294a61a07da9b49b6e22b2cbd7f9

        System.out.println(fromPassToDBpass("d3b1294a61a07da9b49b6e22b2cbd7f9","1a2b3c4d"));
        //b7797cce01b4b131b433b6acf4add449

        System.out.println(inputPassToToDBpass(inputPass,"1a2b3c4d"));
        //b7797cce01b4b131b433b6acf4add449

    }

}

1.3 二次md5加密

InputPass 原始密码
FromPass 页面提交时,第一次加密后密码
DBpass 后端收到第一次加密后的密码后,存入数据库之前,再一次md5加密

第一步,在 提交 /login/doLogin时,先加密用户password

java 秒杀活动 队列 java秒杀流程_java_07

第二步,登录时把FromPass 再加密一次,再与数据库中的密码比对

java 秒杀活动 队列 java秒杀流程_redis_08

2.分布式会话 共享session

2.1登录时,生成ticket,存入cookie,同时在redis中存入用户对象,key为 “user:”+ticket

redis存值 key(“user:”+ticket)

java 秒杀活动 队列 java秒杀流程_session_09

网页端cookie存值(userTicket)

java 秒杀活动 队列 java秒杀流程_redis_10

UserServiceImpl.doLgoin()

//生成cookie
 String ticket = UUIDUtil.uuid();

 redisTemplate.opsForValue().set("user:"+ticket,user);

 //request.getSession().setAttribute(ticket,user);
 CookieUtil.setCookie(response,"userTicket",ticket);

2.2 参数校验时,通过cookie中的userTicket,去redis查找当前登录的User对象

UserArgumentResolve resolveArgument

@Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        return UserContext.getUser();
        /*
        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);
        */


    }

UserContext --后来把获取用户信息,集成到 UserContext的ThreadLocal中

package com.example.miaosha.config;

import com.example.miaosha.pojo.User;

public class UserContext {
    private static ThreadLocal<User> userHolder=new ThreadLocal<User>();
    public static void setUser(User user){
        userHolder.set(user);
    }

    public  static User getUser(){
        return userHolder.get();
    }
}

AccessLimitInterceptor getUser

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    if(handler instanceof HandlerMethod){
        User user=getUser(request,response);
        UserContext.setUser(user);
	}
	return true;
}


private User getUser(HttpServletRequest request,HttpServletResponse response) {

        String ticket = CookieUtil.getCookieValue(request, "userTicket");
        if(StringUtils.isEmpty(ticket)){
            return null;
        }

        return userService.getUserByCookie(ticket, request, response);
    }