chao 4 vuotta sitten
vanhempi
commit
44be882e7b

+ 16 - 1
src/main/java/com/caimei365/user/components/RedisService.java

@@ -116,7 +116,22 @@ public class RedisService {
 		}
 		return result;
 	}
-
+    /**
+     * 写入过期时间(秒)
+     * @param key
+     * @param expireTimeSeconds
+     * @return
+     */
+	public boolean expire(String key, Long expireTimeSeconds) {
+		boolean result = false;
+		try {
+			redisTemplate.expire(key, expireTimeSeconds, TimeUnit.SECONDS);
+			result = true;
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return result;
+	}
     /* **************************** 针对list操作的方法 **************************** */
     /**
      * 在key对应list的尾部添加

+ 125 - 0
src/main/java/com/caimei365/user/config/JWTFilter.java

@@ -0,0 +1,125 @@
+package com.caimei365.user.config;
+
+import com.alibaba.fastjson.JSONObject;
+import com.caimei365.user.components.RedisService;
+import com.caimei365.user.utils.JwtUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+/**
+ * JWT Token
+ *
+ * 续签逻辑:
+ * 登录成功后,用户在未过期时间内继续操作,续签token。
+ * 登录成功后,空闲超过过期时间,返回token已失效,重新登录。
+ * 实现逻辑:
+ * 1.登录成功后将token存储到redis里面(这时候k、v值一样都为token),并设置过期时间为token过期时间
+ * 2.当用户请求时token值还未过期,则重新设置redis里token的过期时间。
+ * 3.当用户请求时token值已过期,但redis中还在,则JWT重新生成token并覆盖v值(这时候k、v值不一样了),然后设置redis过期时间。
+ * 4.当用户请求时token值已过期,并且redis中也不存在,则用户空闲超时,返回token已失效,重新登录。
+ *
+ * @author : Charles
+ * @date : 2021/3/30
+ */
+@Slf4j
+@Component
+public class JWTFilter implements WebFilter {
+
+    private static final String AUTH = "X-Token";
+    /**
+     * 需要权限认证的接口路径
+     */
+    private static final String[] PERMISSION_URLS = new String[]{
+        "/user/club/info",
+        "/user/club/info/update",
+        "/user/shop/info",
+        "/user/shop/info/update"
+    };
+    private RedisService redisService;
+    @Autowired
+    public void setRedisService(RedisService redisService) {
+        this.redisService = redisService;
+    }
+
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
+        ServerHttpRequest request = exchange.getRequest();
+        ServerHttpResponse response = exchange.getResponse();
+
+        HttpHeaders header = request.getHeaders();
+        String token = header.getFirst(AUTH);
+        String url = request.getURI().getPath();
+
+        if (StringUtils.isBlank(token) && Arrays.asList(PERMISSION_URLS).contains(url)) {
+            log.error("未经授权,Token为空!");
+            return tokenErrorResponse(response, "未经授权,Token为空!");
+        }
+
+        if (StringUtils.isNotBlank(token)) {
+            String cacheToken = (String) redisService.get(token);
+            // token续签
+            if (StringUtils.isNotBlank(cacheToken) && !"null".equals(cacheToken) && JwtUtil.isVerify(cacheToken)) {
+                int userId = JwtUtil.parseTokenUid(cacheToken);
+                log.error("Token续签,UserId:"+userId+",Token:"+token);
+                // 再次校验token有效性
+                if (!JwtUtil.isVerify(cacheToken)) {
+                    // 生成token
+                    String newToken = JwtUtil.createToken(userId);
+                    // 将token存入redis,并设置超时时间(秒)
+                    redisService.set(token, newToken, JwtUtil.getExpireTime());
+                } else {
+                    // 重新设置超时时间(秒)
+                    redisService.expire(token, JwtUtil.getExpireTime());
+                }
+            } else {
+                // 需要验证的路径
+                if(Arrays.asList(PERMISSION_URLS).contains(url)) {
+                    // Token失效
+                    log.error("Token失效,请重新登录!");
+                    return tokenErrorResponse(response, "Token失效,请重新登录!");
+                }
+            }
+        }
+        //            //TODO 将用户信息存放在请求header中传递给下游业务
+        //            ServerHttpRequest.Builder mutate = request.mutate();
+        //            mutate.header("demo-user-name", username);
+        //            ServerHttpRequest buildReuqest = mutate.build();
+        //
+        //            //todo 如果响应中需要放数据,也可以放在response的header中
+        //            response.setStatusCode(HttpStatus.OK);
+        //            response.getHeaders().add("demo-user-name",username);
+        //            return chain.filter(exchange.mutate()
+        //                    .request(buildReuqest)
+        //                    .response(response)
+        //                    .build());
+        return chain.filter(exchange);
+    }
+
+    private Mono<Void> tokenErrorResponse(ServerHttpResponse response, String responseMsg){
+        response.setStatusCode(HttpStatus.OK);
+        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
+        JSONObject res = new JSONObject();
+        res.put("code", -99);
+        res.put("msg", responseMsg);
+        byte[] responseByte = res.toJSONString().getBytes(StandardCharsets.UTF_8);
+        DataBuffer buffer = response.bufferFactory().wrap(responseByte);
+        return response.writeWith(Flux.just(buffer));
+    }
+
+
+}

+ 0 - 96
src/main/java/com/caimei365/user/config/TokenAspect.java

@@ -1,96 +0,0 @@
-//package com.caimei365.user.config;
-//
-//import com.caimei365.user.components.RedisService;
-//import com.caimei365.user.utils.JwtUtil;
-//import org.aspectj.lang.JoinPoint;
-//import org.aspectj.lang.annotation.Aspect;
-//import org.aspectj.lang.annotation.Before;
-//import org.aspectj.lang.annotation.Pointcut;
-//import org.springframework.beans.factory.annotation.Autowired;
-//import org.springframework.stereotype.Component;
-//import org.springframework.web.context.request.RequestContextHolder;
-//import org.springframework.web.context.request.ServletRequestAttributes;
-//import org.springframework.web.server.ServerWebExchange;
-//
-//
-///**
-// * 把切面类加入到IOC容器中
-// * 所有请求都会更新redis存token的时间
-// *
-// * @author : Charles
-// * @date : 2020/3/17
-// */
-//@Component
-//@Aspect
-//public class TokenAspect {
-//
-//    private RedisService redisService;
-//    @Autowired
-//    public void setRedisService(RedisService redisService) {
-//        this.redisService = redisService;
-//    }
-//
-//    /**
-//     * 定义一个切入点 我这里是从controller切入
-//     */
-//    @Pointcut("execution(public * com.caimei365.user.controller..*.*(..))")
-//    public void pointCut() {
-//    }
-//
-//    /**
-//     * 前置通知
-//     * 在进入方法前执行 可以对参数进行限制或者拦截
-//     * 通常在这边做日志存储存到数据库中
-//     * @param joinPoint
-//     * @throws Throwable
-//     */
-//    @Before("pointCut()")
-//    public void before(JoinPoint joinPoint) throws Throwable {
-//
-//
-//        ServerWebExchange
-//
-//        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
-//
-//        String token = request.getHeader("X-Token");
-//        String cacheToken = null!=token ? String.valueOf(redisService.get(token)) : null;
-//        // Redis过期后会得到"null"值,所以需判断字符串"null"
-//        if (cacheToken != null && cacheToken.length() != 0 && !"null".equals(cacheToken)) {
-//            if (JwtUtil.isVerify(cacheToken)) {
-//               /* String mobileOrEmail = JwtUtil.parseTokenAud(cacheToken);*/
-//                int userId = JwtUtil.parseTokenUid(cacheToken);
-//                // 刷新token
-//                tokenRefresh(token, userId);
-//            }
-//        }
-//    }
-//
-//    /**
-//     * JWT Token续签:
-//     * 业务逻辑:登录成功后,用户在未过期时间内继续操作,续签token。
-//     * 登录成功后,空闲超过过期时间,返回token已失效,重新登录。
-//     * 实现逻辑:
-//     * 1.登录成功后将token存储到redis里面(这时候k、v值一样都为token),并设置过期时间为token过期时间
-//     * 2.当用户请求时token值还未过期,则重新设置redis里token的过期时间。
-//     * 3.当用户请求时token值已过期,但redis中还在,则JWT重新生成token并覆盖v值(这时候k、v值不一样了),然后设置redis过期时间。
-//     * 4.当用户请求时token值已过期,并且redis中也不存在,则用户空闲超时,返回token已失效,重新登录。
-//     */
-//    public boolean tokenRefresh(String token, Integer userID) {
-//        String cacheToken = String.valueOf(redisService.get(token));
-//        // 过期后会得到"null"值,所以需判断字符串"null"
-//        if (cacheToken != null && cacheToken.length() != 0 && !"null".equals(cacheToken)) {
-//            // 校验token有效性
-//            if (!JwtUtil.isVerify(cacheToken)) {
-//                // 生成token
-//                String newToken = JwtUtil.createToken(userID);
-//                // 将token存入redis,并设置超时时间
-//                redisService.set(token, newToken, JwtUtil.getExpireTime());
-//            } else {
-//                // 重新设置超时时间
-//                redisService.set(token, cacheToken, JwtUtil.getExpireTime());
-//            }
-//            return true;
-//        }
-//        return false;
-//    }
-//}

+ 22 - 0
src/main/java/com/caimei365/user/controller/LoginApi.java

@@ -4,6 +4,7 @@ import com.caimei365.user.model.ResponseJson;
 import com.caimei365.user.model.dto.*;
 import com.caimei365.user.model.vo.UserLoginVo;
 import com.caimei365.user.service.LoginService;
+import com.caimei365.user.service.SellerService;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import org.springframework.http.HttpHeaders;
@@ -24,6 +25,7 @@ import java.util.Map;
 public class LoginApi {
 
     private final LoginService loginService;
+    private final SellerService sellerService;
 
     /**
      * 登录(用户名,密码)
@@ -43,6 +45,26 @@ public class LoginApi {
         return loginService.passwordLogin(loginPasswordDto);
     }
 
+    /**
+     * 协销登录(手机号,密码)
+     *
+     * spi旧接口:/seller/login
+     *
+     * @param loginPasswordDto {
+     *                           mobileOrEmail 手机号
+     *                           password 密码
+     *                         }
+     * @return UserLoginVo
+     */
+    @ApiOperation("协销登录(手机号,密码)")
+    @PostMapping("/seller")
+    public ResponseJson<UserLoginVo> sellerLogin(LoginPasswordDto loginPasswordDto) {
+        String mobile = loginPasswordDto.getMobileOrEmail();
+        String password = loginPasswordDto.getPassword();
+        String unionId = loginPasswordDto.getUnionId();
+        return sellerService.passwordLogin(mobile, password, unionId);
+    }
+
     /**
      * 微信授权登录(小程序),用户数据存入Redis,key前缀:wxInfo:applets:
      *

+ 1 - 24
src/main/java/com/caimei365/user/controller/SellerApi.java

@@ -1,13 +1,8 @@
 package com.caimei365.user.controller;
 
-import com.caimei365.user.model.ResponseJson;
-import com.caimei365.user.model.dto.LoginPasswordDto;
-import com.caimei365.user.model.vo.UserLoginVo;
 import com.caimei365.user.service.SellerService;
 import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
 import lombok.RequiredArgsConstructor;
-import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -25,23 +20,5 @@ public class SellerApi {
 
     private final SellerService sellerService;
 
-    /**
-     * 协销登录(手机号,密码)
-     *
-     * spi旧接口:/seller/login
-     *
-     * @param loginPasswordDto {
-     *                           mobileOrEmail 手机号
-     *                           password 密码
-     *                         }
-     * @return UserLoginVo
-     */
-    @ApiOperation("协销登录(手机号,密码)")
-    @PostMapping("/login")
-    public ResponseJson<UserLoginVo> passwordLogin(LoginPasswordDto loginPasswordDto) {
-        String mobile = loginPasswordDto.getMobileOrEmail();
-        String password = loginPasswordDto.getPassword();
-        String unionId = loginPasswordDto.getUnionId();
-        return sellerService.passwordLogin(mobile, password, unionId);
-    }
+
 }

+ 25 - 0
src/main/java/com/caimei365/user/idempotent/IdempotentExceptionHandler.java

@@ -0,0 +1,25 @@
+package com.caimei365.user.idempotent;
+
+import com.caimei365.user.model.ResponseJson;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+/**
+ * ApI幂等异常处理
+ *
+ * @author : Charles
+ * @date : 2021/3/29
+ */
+@Slf4j
+@ControllerAdvice
+public class IdempotentExceptionHandler {
+    @ExceptionHandler(IdempotentException.class)
+    @ResponseBody
+    public ResponseJson convertExceptionMsg(Exception e) {
+        //自定义逻辑,可返回其他值
+        log.error("ApI幂等错误拦截,错误信息:", e);
+        return ResponseJson.error("幂等异常处理:" + e.getMessage());
+    }
+}