秒杀项目中核心功能的实现
秒杀项目-登录中的重难点
一、两次MD5的作用
做法:进行两次加密,调用MD5Util工具类的md5()进行加密。
用户端:Password=MD5(明文+固定的Salt)===用户输入
服务端:Password=MD5(用户输入+随机salt)
主要是考虑到安全问题。第一次MD5,防止明文密码在网络端进行传输。第二次MD5,防止数据库泄露之后,密码被反推,密码被盗。
代码实现
public class MD5Util { public static String md5(String src){ return DigestUtils.md5Hex(src);//对密码进行加密 } private static final String salt="1a2b3c4d"; public static String inputPassFormPass(String inputPass){//第一次MD5 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){//第二次MD5 String str=""+salt.charAt(0)+salt.charAt(2)+formpass+salt.charAt(5)+salt.charAt(4); return md5(str); } public static String inputPassToDbPass(String input,String saltDB){//转化为数据库中的密码 String s = inputPassFormPass(input); String dbpass = formPassToDBPass(s, saltDB); return dbpass; }
二、分布式Session一致性的四种解决方案
1、cookie和session的区别和联系
cookie是本地客户端用来存储少量的用户数据信息的,保存在客户端,用户很容易读取,安全性不高,存储的数据量小。
session是服务器端用来存储部分用户数据信息的,保存在服务器,用户不容易获取,安全性高。存储的数据量大,但是保存在服务器端,会占有一定的服务器资源。
2、session有什么用?
在一次客户端与服务端的会话中,客户端向浏览器发送请求,首先cookie会携带上次请求存储的数据(jsessionID)到服务器,服务器根据请求参数中的JessionID去服务器的Session 库中看看有没有这个Jsession, 如果存在,那么服务器就知道此用户是谁,如果不存在,就会创建一个JSESSIONID,并在本次请求结束后将JSESSIONID返回给客户端,同时将此JSESSIONID在客户端cookie中进行保存。
客户端和服务器之间是通过http协议进行通信,但是http协议是无状态的,不同次请求会话是没有任何关联的,但是优点是处理速度快。
session是一次浏览器和服务器的交互的会话,当浏览器关闭的时候,会话就结束了,但是会话session还在,默认session是还要保留30分钟的。
3、分布式Session一致性
客户端发一个请求,经过负载均衡后会被分配到其中任意一个服务器上。但是由于不同的服务器含有不同的web服务器,不同的web服务器就不能发现之前的web服务器保存的session信息,就会再生成一个jsessionID,之前的状态就会丢失。
方案(一)客户端存储
直接将信息存储在cookie中,cookie是存储在客户端上的一小段数据,客户端通过http协议和服务器进行cookie交互,通常用来存储一些不敏感信息。
缺点:不安全,cookie大小,存储类型存在限制 ;而且一次请求中cookie过大,会增加网络的开销。
代码
private static String redisKey = "user:session"; /** * 登录成功后生成并保存token * * @param response * @param user * @return */ public boolean login(HttpServletResponse response, User user) { // 验证用户身份 User user = userService.check(……); // salt值建议做成可配置化 String salt = ""; String token = DigestUtils.md5Hex(user.getName() + salt); //这里token作为用户信息唯一标识 addCookie(response, token); return true; } /** * 添加至redis和cookie * * @param response * @param token */ private void addCookie(HttpServletResponse response, String token) { redisTemplate.opsForValue().set(redisKey, token, 366, TimeUnit.DAYS);//放入缓存 Cookie cookie = new Cookie("token", token); cookie.setMaxAge(3600 * 24 * 366); //和Redis缓存失效时间一致 cookie.setPath("/"); response.addCookie(cookie); } /** * 获取已登录的用户信息 * @param response * @return */ public String getByToken(HttpServletResponse response) { String userinfo = redisTemplate.opsForValue().get(redisKey); //延长session有效期,过期时间=最后一次使用+失效时间,cookie可以不延长 if (StringUtils.isNotEmpty(userinfo)) { addCookie(response, userinfo); } return userinfo; }
方案二:session复制
session复制是小型企业应用使用较多的一种服务器集群session管理机制,在真正的开发使用的并不是很多,通过对web服务器(例如Tomcat)进行搭建集群。
存在的问题
session同步的原理是在同一个局域网里面通过发送广播来同步session的,一旦服务器多了,并发上来了,session需要同步的数据量就大了,需要将其他服务器上的session全部同步到本服务器上,会带来一定的网路开销,在用户量特别大的时候,会出现内存不足的情况
优点
服务器之间的session信息都是同步的,任何一台服务器宕机的时候不会影响另外服务器中session的状态,配置相对简单。
Tomcat内部已经支持分布式架构开发管理机制,可以对tomcat修改配置来支持session复制,在集群中的几台服务器之间同步session对象,使每台服务器上都保存了所有用户的session信息,这样任何一台本机宕机都不会导致session数据的丢失,而服务器使用session时,也只需要在本机获取即可。
方案三:session绑定
Nginx是一款自由的、开源的、高性能的http服务器和反向代理服务器。
Nginx能做什么?
反向代理、负载均衡、http服务器(动静代理)、正向代理
如何使用nginx进行session绑定
我们利用nginx的反向代理和负载均衡,之前是客户端会被分配到其中一台服务器进行处理,具体分配到哪台服务器进行处理还得看服务器的负载均衡算法(轮询、随机、ip-hash、权重等),但是我们可以基于nginx的ip-hash策略,可以对客户端和服务器进行绑定,同一个客户端就只能访问该服务器,无论客户端发送多少次请求都被同一个服务器处理。
在nginx安装目录下的conf目录中的nginx.conf文件
upstream aaa { Ip_hash; server 39.105.59.4:8080; Server 39.105.59.4:8081; } server { listen80; server_name www.wanyingjing.cn; #root /usr/local/nginx/html; #index index.html index.htm; location / { proxy_pass http:39.105.59.4; index index.html index.htm; } }
缺点
容易造成单点故障,如果有一台服务器宕机,那么该台服务器上的session信息将会丢失。
前端不能有负载均衡,如果有,session绑定将会出问题。
优点
配置简单
方案四:基于redis存储session方案
原理是使用Redis作为session存储容器,登录时将session信息存储至cookie客户端,同时服务端将session信息存至redis缓存,双重保障,接下来的接口调用直接可以获取到cookie中的token信息作为参数传递进来即可,如果发现token为空,则再从redis中获取,如果两者都为空,则说明session已过期。
本项目采用的是方案四
我们是这样做的。
第一次登陆的时候,会随机产生一个Token字符串,并向cookie中加入Token,在Redis中记录Token与用户信息的映射。Key值为:MiaoShaUserKey:”fjfnernrejfnejnf”。Value:是一个User对象。get MiaoshaUserKey:tk24aaa7309a7d44b1bf7eee9a99045bd0 {"id":15700761667,"lastLoginDate":1627360841000,"loginCount":1,"nickname":"heyuan","password":"b7797cce01b4b131b433b6acf4add449","registerDate":1626237634000,"salt":"1a2b3c4d"}"
客户端随后访问服务端的时候携带Cookie,服务端取出cookie中字段值,访问Redis,就能得到用户的相关信息。
代码
/* 登录功能的实现 */ public boolean login(HttpServletResponse response, LoginVo loginVo){ if(loginVo==null){ throw new GlobalException(CodeMsg.SERVER_ERROR); //return CodeMsg.SERVER_ERROR; } String mobile = loginVo.getMobile(); String password = loginVo.getPassword(); //判断手机号是否存在 MiaoshaUser user=getById(Long.parseLong(mobile));//手机号String 转换为Long if(user==null){ throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST); // return CodeMsg.MOBILE_NOT_EXIST;//手机号码不存在 } //验证密码 String dbpass = user.getPassword();//数据库得到的密码 String saltDB = user.getSalt();// String calcPass = MD5Util.formPassToDBPass(password, saltDB);// if(!calcPass.equals(dbpass)){//用户输入的密码进行加密然后与数据库的密码进行比较 throw new GlobalException(CodeMsg.PASSWORD_ERROR); // return CodeMsg.PASSWORD_ERROR;//输入密码错误 } //生成cookie //1生成token String token= UUIDUtil.uuid(); //2生成cookie addCookie(response,user,token); return true; } /* 往缓存里添加该值; 生成一个新的cookie:包含Token,以及用户信息 有效期设置为永不过期。就是登陆一次,就可以一直地访问,不用担心Cookie过期。 */ private void addCookie(HttpServletResponse response,MiaoshaUser user,String token){ //1、将Token与用户的映射信息加入缓存 redisService.set(MiaoshaUserKey.token,token,user);//放user进入redis缓存,取名为token //2、生成cookie Cookie cookie = new Cookie(COOKIE_NAME_TOKEN, token); cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());//设置有效期 cookie.setPath("/"); //3、将cookie返回给客户端 response.addCookie(cookie);// } @Service public class UserArgumentResolver implements HandlerMethodArgumentResolver { @Autowired MiaoshaUserService userService; @Override public boolean supportsParameter(MethodParameter methodParameter) { Class<?>clazz= methodParameter.getParameterType(); return clazz== MiaoshaUser.class; } /* 一种获取session的方式 从请求中获取用户信息 getToken() */ 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.COOKIE_NAME_TOKEN);//从请求中获得token String cookieToken=getCookieValue(request, MiaoshaUserService.COOKIE_NAME_TOKEN); if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)){ return null; } String token=StringUtils.isEmpty(paramToken)?cookieToken:paramToken;//如果paramToken参数为空,那么token就是cookieToken return userService.getByToken(response,token);//该token就是从cookie中拿到的token. } public String getCookieValue(HttpServletRequest request,String cookieName){ Cookie[] cookies=request.getCookies(); for(Cookie cookie:cookies){ if(cookie.getName().equals(cookieName)){ return cookie.getValue(); } } return null; } } @Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Autowired UserArgumentResolver userArgumentResolver; /* 框架会回调这个方法。把response,等参数添加进去 */ @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(userArgumentResolver); } } /* 获得Token */ public MiaoshaUser getByToken( HttpServletResponse response,String token) { if(StringUtils.isEmpty(token)) { return null; } //System.out.println(redisService.get(MiaoshaUserKey.token, token, MiaoshaUser.class));//拿到的是Miaoshauser的地址 MiaoshaUser user= redisService.get(MiaoshaUserKey.token,token,MiaoshaUser.class); //延长有效期。当我们10点访问的时候,cookie在10点30过期。但是在10点10分访问的时候,那么就在10点40过期了。实现 if(user!=null){ addCookie(response,user,token); } return user; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)