目录
三、注册(04.md)需使用事务进行管理@Transactional
*****************************内存泄漏05.md*****************************
一、博客登录
1、JWT技术(03.md)
1.1、jwt介绍
jwt 可以生成 一个加密的token,做为用户登录的令牌,当用户登录成功之后,发放给客户端。
请求需要登录的资源或者接口的时候,将token携带,后端验证token是否合法。
jwt 有三部分组成:A.B.C
A:Header,{"type":"JWT","alg":"HS256"} 固定
B:playload,存放信息,比如,用户id,过期时间等等,可以被解密,不能存放敏感信息
C: 签证,A和B加上秘钥 加密而成,只要秘钥不丢失,可以认为是安全的。
jwt 验证,主要就是验证C部分 是否合法。
1.2、jwt使用
导入依赖包----编写工具类
工具类中涉及md5等加密方式、
自定义秘钥(jwtToken)private static final String jwtToken = "123456Mszlu!@#$$";、
检测是否合格checkToken(解码)
2、利用token(JWT)编写登录方法
2.1实现jwt
在实现类中要设置加密盐 private static final String slat = "mszlu!@#";
进行加密时,需要导入加密依赖,String pwd = DigestUtils.md5Hex(password + slat);
2.2jwt存放缓存redis中,以便后期检测是否登录
//登录成功,使用JWT生成token,返回token和redis中
String token = JWTUtils.createToken(sysUser.getId());
redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);
3、统一错误码,减少大量重复繁琐的返回值书写
package com.mszlu.blog.vo;
public enum ErrorCode {
PARAMS_ERROR(10001,"参数有误"),
ACCOUNT_PWD_NOT_EXIST(10002,"用户名或密码不存在"),
NO_PERMISSION(70001,"无访问权限"),
SESSION_TIME_OUT(90001,"会话超时"),
NO_LOGIN(90002,"未登录"),;
private int code;
private String msg;
ErrorCode(int code, String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
二、获取用户信息和退出登录
1、获取用户信息
前端返回值携带token,先手动检测token是否合法(是否登录或者其他未知情况),检测前端返回token在缓存中是否存在
2、退出登录
清除缓存redis中存放的token即可
三、注册(04.md)需使用事务进行管理@Transactional
四、判断是否登录----拦截器||过滤器
1、拦截器:
自定义LoginInterceptor来判断token是否合法、在WebMVCConfig中添加拦截器
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
/**
* 1. 需要判断请求的接口路径 是否为 HandlerMethod (controller方法),放行静态资源等请求,之拦截controller的方法
* 2. 判断 token是否为空,如果为空 未登录
* 3. 如果token 不为空,登录验证 loginService checkToken
* 4. 如果认证成功 放行即可
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Autowired
private TokenService tokenService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1. 需要判断请求的接口路径 是否为 HandlerMethod (controller方法)
//instanceof-- 实例关系-- 判断handler请求是否和HandlerMethod (controller方法)为实例关系
if (!(handler instanceof HandlerMethod)){
return true;
}
//2. 判断 token是否为空,如果为空 未登录
String token = request.getHeader("Authorization");
log.info("=================request start===========================");
String requestURI = request.getRequestURI();
log.info("request uri:{}",requestURI);
log.info("request method:{}",request.getMethod());
log.info("token:{}", token);
log.info("==================request end===========================");
if (StringUtils.isBlank(token)){
//未登录,进行拦截,返回信息
R r = R.fail(ErrorCode.NO_LOGIN.getCode(), ErrorCode.NO_LOGIN.getMsg());
//返回给前端的信息为json,需要对上面信息处理
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSON.toJSONString(r));
return false;
}
//3. 如果token 不为空,登录验证 loginService checkToken
SysUser sysUser = tokenService.checkToken(token);
if (sysUser == null){
//未登录,进行拦截,返回信息
R r = R.fail(ErrorCode.NO_LOGIN.getCode(), ErrorCode.NO_LOGIN.getMsg());
//返回给前端的信息为json,需要对上面信息处理
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSON.toJSONString(r));
return false;
}
//4. 如果认证成功 放行即可
//我希望在controller中 直接获取用户的信息 怎么获取?
UserThreadLocal.put(sysUser); //此方法利用本地线程,可直接获取sysUser
return true;
}
2、过滤器:
此处列举reggie项目的过滤器
package com.study.reggie.filter;
import com.alibaba.fastjson.JSON;
import com.study.reggie.common.BaseContextUtil;
import com.study.reggie.common.R;
import com.sun.prism.impl.BaseContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 设置过滤名称:filterName = "LoginCheckFilter"
* 设置过滤路径:urlPatterns = "/*"
* 检查用户是否已经完成登录
*/
@Slf4j
@WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*")
public class LoginCheckFilter implements Filter { //自定义过滤器需要实现Filter
//路径匹配器,支持通配符,后面需要用到这个判断两次路径是否相同
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//强制类型转换
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
//1、获取本次请求的URI
String requestURI = request.getRequestURI();
log.info("拦截到请求:{}",requestURI);
//定义不需要处理的请求路径
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**",
"/common/**",
"/user/login",
"/user/sendMsg",
"/doc.html",
"/webjars/**",
"/swagger-resources",
"v2/api-docs"
};
//2、判断本次请求是否需要处理
boolean check = check(urls, requestURI);
//3、如果不需要处理,则直接放行
if (check){
log.info("不需要处理,直接放行{}",requestURI);
//利用filterChain进行放行
filterChain.doFilter(request,response);
return;
}
//4.1、判断客户端登录状态,如果已登录,则直接放行
if (request.getSession().getAttribute("employee")!=null){
log.info("用户已登录,id为{}",request.getSession().getAttribute("employee"));
//获取当前用户的id,set到线程中,以便controller方法和Handler中使用
Long empId = (Long) request.getSession().getAttribute("employee");
BaseContextUtil.setNowId(empId);
filterChain.doFilter(request,response);
return;
}
// 4-2、判断移动端登录状态,如果已登录,则直接放行
if (request.getSession().getAttribute("user") != null) {
log.info("用户已登录,用户id为:{}", request.getSession().getAttribute("user"));
Long userId= (Long) request.getSession().getAttribute("user");
BaseContextUtil.setNowId(userId);
filterChain.doFilter(request, response);
return;
}
//5、如果未登录则返回未登录结果,通过输出流方式向页面响应数据
log.info("用户未登录");
//此返回至客户端的数据NOTLOGIN需要跟前端保持一致,利用这个在前端实现页面跳转
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
return;
}
/**
* 利用路径匹配器判断两次路径是否相同
* @param urls
* @param requestURI
* @return
*/
public boolean check(String[] urls,String requestURI){
//利用for循环遍历urls中的数据到url中,然后用PATH_MATCHER判断url和客户端请求路径是否一致
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if (match){
return true;
}
}
return false;
}
}
五、ThreadLocal保存用户信息
为减少重复查询sql,应将查询之后的sysUser存放在线程中,以便后期调用
public class UserThreadLocal {
private UserThreadLocal(){}
private static final ThreadLocal<SysUser> LOCAL = new ThreadLocal<>();
public static void put(SysUser sysUser){
LOCAL.set(sysUser);
}
public static SysUser get(){
return LOCAL.get();
}
public static void remove(){
LOCAL.remove();
}
}
在调用完线程中的某参数时,应remove,否则可能会存在内存泄漏
*****************************内存泄漏05.md*****************************
编码细节,简要描述,时间不够在今天的笔记细说,在此做记录
long类型id精度丢失:
显示不出来的在ArticleVo中的id属性上面加上
@JsonSerialize(using = ToStringSerializer.class)
线程池保证两操作不干扰
写线程方法执行逻辑,写线程配置类开启多线程
@Async引入多线程........@EnableAsync开启对应线程
interger代替int
int基本类型在执行操作时会默认赋值0,所以在某些操作时若关联到int类型的属性,
但我们又不对其进行操作,最终结果mybatis还是会将int属性赋值0然后返回,需用interger代替int