秒杀流程演示
1登录页面
http://localhost:8080/login/toLogin
2.登录成功,进入商品列表页面
http://localhost:8080/goods/toList
3.商品详情
http://localhost:8080/goodsDetail.htm?goodsId=2
3.1 秒杀,进入排队,等待秒杀结果 getResult
3.2 秒杀成功,跳转订单页面 跳转
4.订单页面
http://localhost:8080/orderDetail.htm?orderId=16046
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
第二步,登录时把FromPass 再加密一次,再与数据库中的密码比对
2.分布式会话 共享session
2.1登录时,生成ticket,存入cookie,同时在redis中存入用户对象,key为 “user:”+ticket
redis存值 key(“user:”+ticket)
网页端cookie存值(userTicket)
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);
}