步骤一:准备工作
引入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.11.4</version>
</dependency>
构建拦截器,进行全局请求拦截
构建拦截器
/**
* ThirdSession拦截器,校验每个请求的ThirdSession
*
* @author byChen
* @date 2021/11/22
*/
@Component
@AllArgsConstructor
public class ThirdSessionInterceptor implements HandlerInterceptor {
@Autowired
private final RedisTemplate<String, String> redisTemplate;
// @Autowired
// private static ThirdSessionInterceptor interceptor;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
设置全局拦截
/**
* @author byChen
* @date 2021/11/22
*/
@Configuration
@AllArgsConstructor
public class MyWebAppConfigurer implements WebMvcConfigurer {
private final RedisTemplate<String, String> redisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 多个拦截器组成一个拦截器链
// addPathPatterns 用于添加拦截规则
// excludePathPatterns 用户排除拦截
registry.addInterceptor(new ThirdSessionInterceptor(redisTemplate)).addPathPatterns("/**");
}
}
自建注解,区别哪些过滤哪些不过滤
/**
* 加上此注解,接口必须登录带session才能访问
* @author
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiLogin {
/**
* 必须登录才能访问,默认true
* @return
*/
boolean mustLogin() default true;
}
构建实体类,用以存储登录后获得的信息
/**
* @author byChen
* @date 2021/11/22
*/
@Data
public class ThirdSession implements Serializable {
/**
* 用户类型(1供货商2采购方)
*/
private String userType;
/**
* 用户主键id
*/
private String userId;
/**
* 用户名称
*/
private String userName;
/**
* 用户手机号
*/
private String phone;
}
步骤二:登录
/**
* 供货商登录
*/
@PostMapping("/login")
public R login(@RequestBody ManufactorUser user) {
if (StrUtil.isBlank(user.getPhone())) {
return R.failed("没有维护手机号");
}
ManufactorUser one = service.getOne(Wrappers.<ManufactorUser>lambdaQuery()
.eq(ManufactorUser::getPhone, user.getPhone()));
if (StrUtil.isBlank(user.getPassWord()) || !ENCODER.matches(user.getPassWord(),one.getPassWord())) {
return R.failed("账户密码信息错误");
}
//登录成功,存入全链路跟踪实体,并放入缓存
String s = UUID.randomUUID().toString();
ThirdSession thirdSession = new ThirdSession();
thirdSession.setUserType("1");
thirdSession.setUserId(one.getId());
thirdSession.setUserName(one.getUserName());
thirdSession.setPhone(one.getPhone());
one.setSessionKey(s);
String key = WxConstants.THIRD_SESSION_BEGIN + ":" + s;
redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(thirdSession),WxConstants.TIME_OUT_SESSION, TimeUnit.HOURS);
return R.ok(one);
}
登陆时,判断登录成功后,将需要的信息存入全链路实体中,并存入redis缓存,将生成的UUID返回给前台,前台放入third-session,每次访问都得携带
步骤三:添加全链路跟踪工具类
package com.admin.util;
import com.admin.domain.ThirdSession;
import com.alibaba.ttl.TransmittableThreadLocal;
import lombok.experimental.UtilityClass;
/**
* thirdSession工具类
* @author byChen
* @date 2021/11/22
*/
@UtilityClass
public class ThirdSessionHolder {
private final ThreadLocal<ThirdSession> THREAD_LOCAL_THIRD_SESSION = new TransmittableThreadLocal<>();
/**
* TTL 设置thirdSession
*
* @param thirdSession
*/
public void setThirdSession(ThirdSession thirdSession) {
THREAD_LOCAL_THIRD_SESSION.set(thirdSession);
}
/**
* 获取TTL中的thirdSession
*
* @return
*/
public ThirdSession getThirdSession() {
ThirdSession thirdSession = THREAD_LOCAL_THIRD_SESSION.get();
return thirdSession;
}
/**
* 获取用户主键id
* 登录的时候查询并存入
* @return
*/
public String getUserId(){
if(getThirdSession() != null){
return getThirdSession().getUserId();
}
return null;
}
/**
* 获取用户类型(1供货商2采购方)
* 登录的时候查询并存入
* @return
*/
public String getUserType(){
if(getThirdSession() != null){
return getThirdSession().getUserType();
}
return null;
}
/**
* 清除
*/
public void clear() {
THREAD_LOCAL_THIRD_SESSION.remove();
}
}
步骤四:补充过滤逻辑
/**
* ThirdSession拦截器,校验每个请求的ThirdSession
*
* @author byChen
* @date 2021/11/22
*/
@Component
@AllArgsConstructor
public class ThirdSessionInterceptor implements HandlerInterceptor {
@Autowired
private final RedisTemplate<String, String> redisTemplate;
// @Autowired
// private static ThirdSessionInterceptor interceptor;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("===请求被拦截===");
HandlerMethod method = (HandlerMethod) handler;
//判断访问的control是否添加ApiLogin注解
ApiLogin apiLogin = method.getMethodAnnotation(ApiLogin.class);
String thirdSessionHeader = request.getHeader("third-session");
if (apiLogin != null && apiLogin.mustLogin()) {
if (StrUtil.isNotBlank(thirdSessionHeader)) {
//获取缓存中的ThirdSession
String key = WxConstants.THIRD_SESSION_BEGIN + ":" + thirdSessionHeader;
Object thirdSessionObj = redisTemplate.opsForValue().get(key);
if (thirdSessionObj == null) {//session过期
ThirdSessionHolder.clear();
R r = R.failed(MyReturnCode.ERR_60001.getCode(), MyReturnCode.ERR_60001.getMsg());
this.writerPrint(response, r);
return Boolean.FALSE;
} else {
//不过期就存入全链路跟踪中
String thirdSessionStr = String.valueOf(thirdSessionObj);
ThirdSession thirdSession = JSONUtil.toBean(thirdSessionStr, ThirdSession.class);
ThirdSessionHolder.setThirdSession(thirdSession);
}
} else {
//没有携带session
ThirdSessionHolder.clear();
R r = R.failed(MyReturnCode.ERR_60002.getCode(), MyReturnCode.ERR_60002.getMsg());
this.writerPrint(response, r);
return Boolean.FALSE;
}
}
return Boolean.TRUE;
}
/**
* 向页面返回的
*
* @param response
* @param r
* @throws IOException
*/
private void writerPrint(HttpServletResponse response, R r) throws IOException {
//返回超时错误码
response.setCharacterEncoding("UTF-8");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
PrintWriter writer = response.getWriter();
writer.print(JSONUtil.parseObj(r));
if (writer != null) {
writer.close();
}
}
}
对添加了自建注解ApiLogin 的接口,请求必须携带 third-session 以此来获取信息。
一些枚举类
/**
* @author byChen
* @date 2021/11/22
*/
public enum MyReturnCode {
ERR_60000(60000, "系统错误,请稍候再试"){},//其它错误
ERR_60001(60001, "登录超时,请重新登录"){},
ERR_60002(60002, "session不能为空"){},
ERR_60003(60003, "请先登录"){},
;
MyReturnCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
private int code;
private String 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;
}
@Override
public String toString() {
return "MyReturnCode{" + "code='" + code + '\'' + "msg='" + msg + '\'' + '}';
}
}
缓存的键枚举
/**
* @author byChen
* @date 2021/11/22
*/
public interface WxConstants {
/**
* redis中3rd_session过期时间(单位:小时)
*/
long TIME_OUT_SESSION = 24 * 2;
/**
* redis中3rd_session拼接前缀
*/
String THIRD_SESSION_BEGIN = "app:3rd_session";
}
返回类
/**
* 响应信息主体
* 参照mybatis-plus R类
* @param <T>
* @author
*/
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class R<T> implements Serializable {
private static final long serialVersionUID = 1L;
@Getter
@Setter
private int code;
@Getter
@Setter
private String msg;
@Getter
@Setter
private T data;
public Boolean isOk() {
return code == 0;
}
public static <T> R<T> ok() {
return restResult(null, 0, null);
}
public static <T> R<T> ok(T data) {
return restResult(data, 0, null);
}
public static <T> R<T> ok(T data, String msg) {
return restResult(data, 0, msg);
}
public static <T> R<T> failed() {
return restResult(null, 1, null);
}
public static <T> R<T> failed(String msg) {
return restResult(null, 1, msg);
}
public static <T> R<T> failed(T data) {
return restResult(data, 1, null);
}
public static <T> R<T> failed(T data, String msg) {
return restResult(data, 1, msg);
}
public static <T> R<T> failed(T data, int code, String msg) {
return restResult(data, code, msg);
}
public static <T> R<T> failed(int code, String msg) {
return restResult(null, code, msg);
}
private static <T> R<T> restResult(T data, int code, String msg) {
R<T> apiResult = new R<>();
apiResult.setCode(code);
apiResult.setData(data);
apiResult.setMsg(msg);
return apiResult;
}
}
步骤五:流程测试
登录后将信息存入,并返回third-session
再次访问携带third-session
没有携带报 session不能为空的错误
携带了就可以直接使用
/**
* 供货商登录
*/
@PostMapping("/test123")
@ApiLogin
public R test123() {
String userId = ThirdSessionHolder.getUserId();
ManufactorUser one = service.getOne(Wrappers.<ManufactorUser>lambdaQuery()
.eq(ManufactorUser::getId, userId));
return R.ok(one);
}
可以直接获取到信息
接口不携带自建注解,就不需要前端携带session