目录
一、用户验证
1、给findByAccount方法增加 request, response两个参数:
3、页面跳转判断
二、redis缓存完成全局session
1、redis缓存完成session共享
三、自定义redis实现功能
1、操作redis的类
2、编写实现全局session功能
3、解决时间获取异常问题
四、使用参数解析器
1、解决除访问登录界面,都需要凭证的问题(解决重复代码问题)
完成必须登录才能进入商品展示界面
1、给findByAccount方法增加 request, response两个参数:
(
findByAccount(UserVo userVo, HttpServletRequest request, HttpServletResponse response)
)
(1)session保存用户信息
如果同时几个人一起下单,会导致共用一个session,session将会修改为最后进入的那个人的信息,相当于几个人下单全部给了最后下单的那个人
(1)导入帮助类
①、CookieUtils
package com.mwy.seckill.util;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
@Slf4j
public class CookieUtils {
/**
* @Description: 得到Cookie的值, 不编码
*/
public static String getCookieValue(HttpServletRequest request, String cookieName) {
return getCookieValue(request, cookieName, false);
}
/**
* @Description: 得到Cookie的值
*/
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;
}
/**
* @Description: 得到Cookie的值
*/
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;
}
/**
* @Description: 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue) {
setCookie(request, response, cookieName, cookieValue, -1);
}
/**
* @Description: 设置Cookie的值 在指定时间内生效,但不编码
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage) {
setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);
}
/**
* @Description: 设置Cookie的值 不设置生效时间,但编码
* 在服务器被创建,返回给客户端,并且保存客户端
* 如果设置了SETMAXAGE(int seconds),会把cookie保存在客户端的硬盘中
* 如果没有设置,会默认把cookie保存在浏览器的内存中
* 一旦设置setPath():只能通过设置的路径才能获取到当前的cookie信息
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, boolean isEncode) {
setCookie(request, response, cookieName, cookieValue, -1, isEncode);
}
/**
* @Description: 设置Cookie的值 在指定时间内生效, 编码参数
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {
doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);
}
/**
* @Description: 设置Cookie的值 在指定时间内生效, 编码参数(指定编码)
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, int cookieMaxage, String encodeString) {
doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);
}
/**
* @Description: 删除Cookie带cookie域名
*/
public static void deleteCookie(HttpServletRequest request, HttpServletResponse response,
String cookieName) {
doSetCookie(request, response, cookieName, null, -1, false);
}
/**
* @Description: 设置Cookie的值,并使其在指定时间内生效
*/
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);
log.info("========== domainName: {} ==========", domainName);
if (!"localhost".equals(domainName)) {
cookie.setDomain(domainName);
}
}
cookie.setPath("/");
response.addCookie(cookie);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @Description: 设置Cookie的值,并使其在指定时间内生效
*/
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);
log.info("========== domainName: {} ==========", domainName);
if (!"localhost".equals(domainName)) {
cookie.setDomain(domainName);
}
}
cookie.setPath("/");
response.addCookie(cookie);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @Description: 得到cookie的域名
*/
private static final String getDomainName(HttpServletRequest request) {
String domainName = null;
String serverName = request.getRequestURL().toString();
if (serverName == null || serverName.equals("")) {
domainName = "";
} else {
serverName = serverName.toLowerCase();
serverName = serverName.substring(7);
final int end = serverName.indexOf("/");
serverName = serverName.substring(0, end);
if (serverName.indexOf(":") > 0) {
String[] ary = serverName.split("\\:");
serverName = ary[0];
}
final String[] domains = serverName.split("\\.");
int len = domains.length;
if (len > 3 && !isIp(serverName)) {
// 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;
}
}
return domainName;
}
public static String trimSpaces(String IP) {//去掉IP字符串前后所有的空格
while (IP.startsWith(" ")) {
IP = IP.substring(1, IP.length()).trim();
}
while (IP.endsWith(" ")) {
IP = IP.substring(0, IP.length() - 1).trim();
}
return IP;
}
public static boolean isIp(String IP) {//判断是否是一个IP
boolean b = false;
IP = trimSpaces(IP);
if (IP.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")) {
String s[] = IP.split("\\.");
if (Integer.parseInt(s[0]) < 255)
if (Integer.parseInt(s[1]) < 255)
if (Integer.parseInt(s[2]) < 255)
if (Integer.parseInt(s[3]) < 255)
b = true;
}
return b;
}
}
(2)修改UserServiceImpl
package com.mwy.seckill.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.mwy.seckill.exception.BusinessException; import com.mwy.seckill.pojo.User; import com.mwy.seckill.mapper.UserMapper; import com.mwy.seckill.service.IUserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.mwy.seckill.util.CookieUtils; import com.mwy.seckill.util.MD5Utils; import com.mwy.seckill.util.ValidatorUtils; import com.mwy.seckill.util.response.ResponseResult; import com.mwy.seckill.util.response.ResponseResultCode; import com.mwy.seckill.vo.UserVo; import org.springframework.http.HttpRequest; import org.springframework.stereotype.Service; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Date; import java.util.UUID; /** ** 用户信息表 服务实现类 *
* * @author mwy * @since 2022-03-15 */ @Service public class UserServiceImpl extends ServiceImplimplements IUserService { @Override public ResponseResult> findByAccount(UserVo userVo, HttpServletRequest request, HttpServletResponse response) { // 先判断信息是否符合(账号是否是手机号码,密码是不是空) if(!ValidatorUtils.isMobile(userVo.getMobile())){ throw new BusinessException(ResponseResultCode.USER_ACCOUNT_NOT_MOBLIE); } if(StringUtils.isBlank(userVo.getPassword())){ throw new BusinessException(ResponseResultCode.USER_PASSWORD_NOT_MATCH); } // 再去数据库查出对应的用户(mobile) User user=this.getOne(new QueryWrapper ().eq("id",userVo.getMobile())); if(user==null){ throw new BusinessException(ResponseResultCode.USER_ACCOUNT_NOT_FIND); } // 比较密码 // 二重加密(前端->后端,后端->数据库) String salt=user.getSalt(); // 将前台的加密密码和后端的盐再次进行加密 String newPassword=MD5Utils.formPassToDbPass(userVo.getPassword(),salt); if(!newPassword.equals(user.getPassword())){ throw new BusinessException(ResponseResultCode.USER_PASSWORD_NOT_MATCH); } // 修改最后的登录时间 this.update(new UpdateWrapper ().eq("id",userVo.getMobile()).set("last_login_date",new Date()).setSql("login_count=login_count+1")); // 最初的版本(session:全局共用,uuid:不可能重复) String ticket = UUID.randomUUID().toString().replace("-", ""); // request.getSession().setAttribute("user",user); request.getSession().setAttribute(ticket,user); // 将我生成的ticket给用户,用户如何验证? 使用cookie,cookie在客户端,session在服务端 // Cookie cookie=new Cookie("ticket",ticket); // response.addCookie(cookie); 这样写会导致设置时间麻烦 CookieUtils.setCookie(request,response,"ticket",ticket); return ResponseResult.success(); } }
(3)运行测试
除了登录页面不需要身份验证,其他页面都需要身份验证
(1)修改PathController
package com.mwy.seckill.controller; import com.mwy.seckill.exception.BusinessException; import com.mwy.seckill.util.CookieUtils; import com.mwy.seckill.util.response.ResponseResultCode; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; @Controller public class PathController { // 登录跳首页 @RequestMapping("/") public String toPath(){ return "login"; } // 跳所有二级页面 @RequestMapping("/{dir}/{path}") public String toPath(@PathVariable("dir") String dir, @PathVariable("path") String path, HttpServletRequest request) { String ticket= CookieUtils.getCookieValue(request,"ticket"); // 进行页面跳转之前判断用户带过来的信息是否有效 if(ticket==null){ // 没有带信息,抛出业务异常,ticket凭据异常 throw new BusinessException(ResponseResultCode.TICKET_ERROR); } // 去session中取到ticket对应的用户,判断是否有值 Object obj = request.getSession().getAttribute(ticket); if(obj==null){ // 凭据没获得取到,要么是已失效,同样抛出凭证异常 throw new BusinessException(ResponseResultCode.TICKET_ERROR); } return dir+"/"+path; } }
(2)运行测试
①、没有登录访问首页
现在的session还是有问题的,只存在一个服务器中,如果搭建了集群,只有第一个A获得凭证的才能访问,后面的获取不到,同时也访问不到,
(1)需要第三者(redis缓存),将凭证放入第三者中,需要就去拿
(2)导入pom依赖
org.apache.commons
commons-pool2
org.springframework.session
spring-session-data-redis
(3)配置redis缓存
①、application.yml
redis:
host: 127.0.0.1
password: 123
database: 0
port: 6379
(4)运行测试
session的值进入了缓存:
(1)新建config软件包,在建RedisConfig类
①、RedisConfig:
package com.mwy.seckill.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory factory){
//新建一个
RedisTemplate redisTemplate = new RedisTemplate();
//设置一下使用连接工厂
redisTemplate.setConnectionFactory(factory);
//额外设置(string)
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
//额外设置(hash 就是 map 集合)
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
//让设置生效
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
(1)取消原本写的全局session
①、UserServiceImpl
(2)编写service层
①、IRedisService
package com.mwy.seckill.service;
import com.mwy.seckill.pojo.User;
public interface IRedisService {
// 得到凭据放用户的方法
void putUserByTicket(String ticket, User user);
// 取凭据的方法
User getUserByTicket(String ticket);
}
②、实现接口类(RedisServiceImpl)
package com.mwy.seckill.service.impl;
import com.mwy.seckill.pojo.User;
import com.mwy.seckill.service.IRedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class RedisServiceImpl implements IRedisService {
// 缓存参数
@Autowired
private RedisTemplate redisTemplate;
@Override
public void putUserByTicket(String ticket, User user) {
redisTemplate.opsForValue().set("user:"+ticket,user,2L, TimeUnit.HOURS);
}
@Override
public User getUserByTicket(String ticket) {
Object obj = redisTemplate.opsForValue().get("user:"+ticket);
if(obj==null|| !(obj instanceof User)){
return null;
}
return (User) obj;
}
}
(3)获取凭据跳转页面
①、PathController
package com.mwy.seckill.controller; import com.mwy.seckill.exception.BusinessException; import com.mwy.seckill.pojo.User; import com.mwy.seckill.service.IRedisService; import com.mwy.seckill.util.CookieUtils; import com.mwy.seckill.util.response.ResponseResultCode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; @Controller public class PathController { @Autowired private IRedisService redisService; // 登录跳首页 @RequestMapping("/") public String toPath(){ return "login"; } // 跳所有二级页面 @RequestMapping("/{dir}/{path}") public String toPath(@PathVariable("dir") String dir, @PathVariable("path") String path, HttpServletRequest request) { // 获取用户的ticket String ticket= CookieUtils.getCookieValue(request,"ticket"); // 进行页面跳转之前判断用户带过来的信息是否有效 if(ticket==null){ // 没有带信息,抛出业务异常,ticket凭据异常 throw new BusinessException(ResponseResultCode.TICKET_ERROR); } // 去session中取到ticket对应的用户,判断是否有值 // Object obj = request.getSession().getAttribute(ticket); // 去缓存中拿 User user = redisService.getUserByTicket(ticket); if(user==null){ // 凭据没获得取到,要么是已失效,同样抛出凭证异常 throw new BusinessException(ResponseResultCode.TICKET_ERROR); } return dir+"/"+path; } }
(4)将凭证放入缓存
①、UserServiceImpl
package com.mwy.seckill.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.mwy.seckill.exception.BusinessException; import com.mwy.seckill.pojo.User; import com.mwy.seckill.mapper.UserMapper; import com.mwy.seckill.service.IRedisService; import com.mwy.seckill.service.IUserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.mwy.seckill.util.CookieUtils; import com.mwy.seckill.util.MD5Utils; import com.mwy.seckill.util.ValidatorUtils; import com.mwy.seckill.util.response.ResponseResult; import com.mwy.seckill.util.response.ResponseResultCode; import com.mwy.seckill.vo.UserVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpRequest; import org.springframework.stereotype.Service; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Date; import java.util.UUID; /** ** 用户信息表 服务实现类 *
* * @author mwy * @since 2022-03-15 */ @Service public class UserServiceImpl extends ServiceImplimplements IUserService { @Autowired private IRedisService redisService; @Override public ResponseResult> findByAccount(UserVo userVo, HttpServletRequest request, HttpServletResponse response) { // 先判断信息是否符合(账号是否是手机号码,密码是不是空) if(!ValidatorUtils.isMobile(userVo.getMobile())){ throw new BusinessException(ResponseResultCode.USER_ACCOUNT_NOT_MOBLIE); } if(StringUtils.isBlank(userVo.getPassword())){ throw new BusinessException(ResponseResultCode.USER_PASSWORD_NOT_MATCH); } // 再去数据库查出对应的用户(mobile) User user=this.getOne(new QueryWrapper ().eq("id",userVo.getMobile())); if(user==null){ throw new BusinessException(ResponseResultCode.USER_ACCOUNT_NOT_FIND); } // 比较密码 // 二重加密(前端->后端,后端->数据库) String salt=user.getSalt(); // 将前台的加密密码和后端的盐再次进行加密 String newPassword=MD5Utils.formPassToDbPass(userVo.getPassword(),salt); if(!newPassword.equals(user.getPassword())){ throw new BusinessException(ResponseResultCode.USER_PASSWORD_NOT_MATCH); } // 修改最后的登录时间 this.update(new UpdateWrapper ().eq("id",userVo.getMobile()).set("last_login_date",new Date()).setSql("login_count=login_count+1")); // 最初的版本(session:全局共用,uuid:不可能重复) String ticket = UUID.randomUUID().toString().replace("-", ""); // request.getSession().setAttribute("user",user); // 放入全局或者单个服务器的session // request.getSession().setAttribute(ticket,user); // 放入缓存中去 redisService.putUserByTicket(ticket,user); // 将我生成的ticket给用户,用户如何验证? 使用cookie,cookie在客户端,session在服务端 // Cookie cookie=new Cookie("ticket",ticket); // response.addCookie(cookie); 这样写会导致设置时间麻烦 CookieUtils.setCookie(request,response,"ticket",ticket); return ResponseResult.success(); } }
(5)运行测试
解决登录后进去首页出错问题
(1)配置解析器,声明解析规则才能获得时间
(2)修改实体类时间类型
①、将类型改为时间戳
(3)运行测试
直接进去首页
(1)修改PathController
package com.mwy.seckill.controller;
import com.mwy.seckill.exception.BusinessException;
import com.mwy.seckill.pojo.User;
import com.mwy.seckill.service.IRedisService;
import com.mwy.seckill.util.CookieUtils;
import com.mwy.seckill.util.response.ResponseResultCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
@Controller
public class PathController {
// 登录跳首页
@RequestMapping("/")
public String toPath(){
return "login";
}
// 单纯做登录验证使用拦截器,这里为了给参数赋值,所以使用参数解析器
@RequestMapping("/{dir}/{path}")
public String toPath(@PathVariable("dir") String dir, @PathVariable("path") String path,User user) {
return dir+"/"+path;
}
}
(2)新增参数解析器
①、UserArgumentResolvers
package com.mwy.seckill.config;
import com.mwy.seckill.exception.BusinessException;
import com.mwy.seckill.pojo.User;
import com.mwy.seckill.service.IRedisService;
import com.mwy.seckill.util.CookieUtils;
import com.mwy.seckill.util.response.ResponseResultCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
//声明为组件
@Component
public class UserArgumentResolvers implements HandlerMethodArgumentResolver {
//supportsParameter决定了resolveArgument是否运行
@Autowired
private IRedisService redisService;
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterType() == User.class;
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
//参数解析User 因为很多地方需要做登录验证
HttpServletRequest request=(HttpServletRequest)nativeWebRequest.getNativeRequest();
//获取用户的ticket
String ticket= CookieUtils.getCookieValue(request, "ticket");
if(ticket == null){
throw new BusinessException(ResponseResultCode.TICKET_ERROR);
}
//去session中取到ticket对应的用户 判断是否有值
//Object obj = request.getSession().getAttribute(ticket);
//去缓存中拿
User user = redisService.getUserByTicket(ticket);
if(user==null){
throw new BusinessException(ResponseResultCode.TICKET_ERROR);
}
//经过了参数解析之后 参数会变成你这个地方返回的值
return user;
}
}
(3)配置类
①、WebConfig
package com.mwy.seckill.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
//配置类
@Configuration
//打开WebMvc的功能
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
// 引用参数解析器
@Autowired
private UserArgumentResolvers userArgumentsResolvers;
@Override
public void addArgumentResolvers(List resolvers) {
resolvers.add(userArgumentsResolvers);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//静态资源访问映射 映射路径 -> 本地资源路径
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
}
(4)运行测试
①、user的值是解析器里面来的
结束!!!