diff --git a/pom.xml b/pom.xml
index cc9f5ae..5a8983f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,13 +17,15 @@
17
-
- org.springframework.boot
- spring-boot-starter-data-redis
-
org.springframework.boot
spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-json
+
+
org.springframework.boot
@@ -38,50 +40,63 @@
mybatis-spring-boot-starter
3.0.0
-
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
org.springframework.boot
spring-boot-devtools
runtime
true
-
- com.mysql
- mysql-connector-j
- runtime
-
-
- org.projectlombok
- lombok
- true
-
org.springframework.boot
spring-boot-starter-test
test
+
+ com.mysql
+ mysql-connector-j
+ runtime
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
com.baomidou
mybatis-plus-boot-starter
3.5.3
+
org.springframework.boot
spring-boot-starter-validation
+
com.github.penggle
kaptcha
2.3.2
+
com.alibaba.fastjson2
fastjson2
- 2.0.22
+ 2.0.21
+
+
+ com.alibaba.fastjson2
+ fastjson2-extension
+ 2.0.21
@@ -96,6 +111,7 @@
3.12.0
+
io.jsonwebtoken
jjwt-api
@@ -113,6 +129,7 @@
0.11.5
compile
+
diff --git a/src/main/java/xyz/playedu/api/bus/BackendBus.java b/src/main/java/xyz/playedu/api/bus/BackendBus.java
new file mode 100644
index 0000000..149686d
--- /dev/null
+++ b/src/main/java/xyz/playedu/api/bus/BackendBus.java
@@ -0,0 +1,16 @@
+package xyz.playedu.api.bus;
+
+import xyz.playedu.api.constant.BackendConstant;
+
+public class BackendBus {
+
+ public static boolean inUnAuthWhitelist(String uri) {
+ for (int i = 0; i < BackendConstant.UN_AUTH_URI_WHITELIST.length; i++) {
+ if (uri.equals(BackendConstant.UN_AUTH_URI_WHITELIST[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/xyz/playedu/api/config/RedisConfig.java b/src/main/java/xyz/playedu/api/config/RedisConfig.java
index b4b71d9..8dc9acd 100644
--- a/src/main/java/xyz/playedu/api/config/RedisConfig.java
+++ b/src/main/java/xyz/playedu/api/config/RedisConfig.java
@@ -1,10 +1,10 @@
package xyz.playedu.api.config;
+import com.alibaba.fastjson2.support.spring.data.redis.FastJsonRedisSerializer;
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
@@ -13,14 +13,17 @@ public class RedisConfig {
@Bean(name = "redisTemplate")
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate<>();
- StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
- GenericJackson2JsonRedisSerializer JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
-
redisTemplate.setConnectionFactory(redisConnectionFactory);
- redisTemplate.setValueSerializer(JsonRedisSerializer);
+
+ // key值采用StringRedisSerializer序列化
+ StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
- redisTemplate.setHashValueSerializer(JsonRedisSerializer);
+
+ // value值采用fastjson2序列化
+ FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
+ redisTemplate.setValueSerializer(fastJsonRedisSerializer);
+ redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
return redisTemplate;
}
diff --git a/src/main/java/xyz/playedu/api/config/WebMvcConfig.java b/src/main/java/xyz/playedu/api/config/WebMvcConfig.java
new file mode 100644
index 0000000..607e380
--- /dev/null
+++ b/src/main/java/xyz/playedu/api/config/WebMvcConfig.java
@@ -0,0 +1,68 @@
+package xyz.playedu.api.config;
+
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
+import com.alibaba.fastjson2.support.config.FastJsonConfig;
+import com.alibaba.fastjson2.support.spring.http.converter.FastJsonHttpMessageConverter;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import xyz.playedu.api.middleware.AdminAuthMiddleware;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Configuration
+@Slf4j
+public class WebMvcConfig implements WebMvcConfigurer {
+
+ @Resource
+ private AdminAuthMiddleware adminAuthMiddleware;
+
+ @Value("${playedu.cors.origins}")
+ private String ConfigOrigins;
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ registry.addInterceptor(adminAuthMiddleware).addPathPatterns("/backend/**");
+ }
+
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ WebMvcConfigurer.super.addResourceHandlers(registry);
+ }
+
+ @Override
+ public void addCorsMappings(CorsRegistry registry) {
+ registry.addMapping("/**").allowedOrigins(ConfigOrigins).allowedHeaders("*").allowedMethods("GET", "POST", "DELETE", "PUT").maxAge(3600);
+ }
+
+ @Override
+ public void configureMessageConverters(List> converters) {
+ converters.add(fastJsonHttpMessageConverter());
+ }
+
+ private HttpMessageConverter> fastJsonHttpMessageConverter() {
+ FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
+
+ // 中文乱码解决方案
+ List mediaTypes = new ArrayList<>();
+ mediaTypes.add(MediaType.APPLICATION_JSON);
+ fastConverter.setSupportedMediaTypes(mediaTypes);
+
+ // 配置
+ FastJsonConfig fastJsonConfig = new FastJsonConfig();
+ fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
+ fastJsonConfig.setWriterFeatures(JSONWriter.Feature.PrettyFormat);
+ fastConverter.setFastJsonConfig(fastJsonConfig);
+
+ return fastConverter;
+ }
+}
diff --git a/src/main/java/xyz/playedu/api/constant/BackendConstant.java b/src/main/java/xyz/playedu/api/constant/BackendConstant.java
new file mode 100644
index 0000000..bb0346d
--- /dev/null
+++ b/src/main/java/xyz/playedu/api/constant/BackendConstant.java
@@ -0,0 +1,5 @@
+package xyz.playedu.api.constant;
+
+public class BackendConstant {
+ public final static String[] UN_AUTH_URI_WHITELIST = {"/backend/v1/system/image-captcha", "/backend/v1/auth/login",};
+}
diff --git a/src/main/java/xyz/playedu/api/controller/admin/AdminUserController.java b/src/main/java/xyz/playedu/api/controller/backend/AdminUserController.java
similarity index 77%
rename from src/main/java/xyz/playedu/api/controller/admin/AdminUserController.java
rename to src/main/java/xyz/playedu/api/controller/backend/AdminUserController.java
index ed3be58..f413de2 100644
--- a/src/main/java/xyz/playedu/api/controller/admin/AdminUserController.java
+++ b/src/main/java/xyz/playedu/api/controller/backend/AdminUserController.java
@@ -1,24 +1,25 @@
-package xyz.playedu.api.controller.admin;
+package xyz.playedu.api.controller.backend;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
-import xyz.playedu.api.constant.SystemConstant;
import xyz.playedu.api.domain.AdminUser;
-import xyz.playedu.api.middleware.AuthMiddleware;
import xyz.playedu.api.service.AdminUserService;
import xyz.playedu.api.types.PaginationResult;
import xyz.playedu.api.types.JsonResponse;
@RestController
+@Slf4j
+@RequestMapping("/backend/v1/admin-user")
public class AdminUserController {
@Autowired
private AdminUserService adminUserService;
- @AuthMiddleware(prv = SystemConstant.JWT_PRV_ADMIN_USER)
- @GetMapping("/admin/user/index")
+ @GetMapping("/index")
public JsonResponse List(@RequestParam(name = "page", defaultValue = "1") Integer page, @RequestParam(name = "size", defaultValue = "10") Integer size) {
PaginationResult result = adminUserService.paginate(page, size, null);
return JsonResponse.data(result);
diff --git a/src/main/java/xyz/playedu/api/controller/admin/LoginController.java b/src/main/java/xyz/playedu/api/controller/backend/LoginController.java
similarity index 88%
rename from src/main/java/xyz/playedu/api/controller/admin/LoginController.java
rename to src/main/java/xyz/playedu/api/controller/backend/LoginController.java
index 06036a8..2a55dd2 100644
--- a/src/main/java/xyz/playedu/api/controller/admin/LoginController.java
+++ b/src/main/java/xyz/playedu/api/controller/backend/LoginController.java
@@ -1,4 +1,4 @@
-package xyz.playedu.api.controller.admin;
+package xyz.playedu.api.controller.backend;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xyz.playedu.api.constant.SystemConstant;
import xyz.playedu.api.domain.AdminUser;
+import xyz.playedu.api.exception.JwtLogoutException;
import xyz.playedu.api.middleware.ImageCaptchaCheckMiddleware;
import xyz.playedu.api.request.LoginRequest;
import xyz.playedu.api.service.AdminUserService;
@@ -22,7 +23,7 @@ import java.util.HashMap;
@Slf4j
@RestController
-@RequestMapping("/admin/v1/auth")
+@RequestMapping("/backend/v1/auth")
public class LoginController {
@Autowired
@@ -57,8 +58,10 @@ public class LoginController {
}
@PostMapping("/logout")
- public JsonResponse logout() {
+ public JsonResponse logout() throws JwtLogoutException {
+ jwtService.logout(RequestUtil.token(), SystemConstant.JWT_PRV_ADMIN_USER);
return JsonResponse.success("success");
+
}
}
diff --git a/src/main/java/xyz/playedu/api/controller/admin/SystemController.java b/src/main/java/xyz/playedu/api/controller/backend/SystemController.java
similarity index 92%
rename from src/main/java/xyz/playedu/api/controller/admin/SystemController.java
rename to src/main/java/xyz/playedu/api/controller/backend/SystemController.java
index 8fd084a..256c261 100644
--- a/src/main/java/xyz/playedu/api/controller/admin/SystemController.java
+++ b/src/main/java/xyz/playedu/api/controller/backend/SystemController.java
@@ -1,4 +1,4 @@
-package xyz.playedu.api.controller.admin;
+package xyz.playedu.api.controller.backend;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@@ -12,7 +12,7 @@ import java.io.IOException;
import java.util.HashMap;
@RestController
-@RequestMapping("/admin/v1/system")
+@RequestMapping("/backend/v1/system")
public class SystemController {
@Autowired
diff --git a/src/main/java/xyz/playedu/api/middleware/AdminAuthMiddleware.java b/src/main/java/xyz/playedu/api/middleware/AdminAuthMiddleware.java
new file mode 100644
index 0000000..f5df9f2
--- /dev/null
+++ b/src/main/java/xyz/playedu/api/middleware/AdminAuthMiddleware.java
@@ -0,0 +1,63 @@
+package xyz.playedu.api.middleware;
+
+import com.alibaba.fastjson2.JSON;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+import xyz.playedu.api.PlayEduThreadLocal;
+import xyz.playedu.api.bus.BackendBus;
+import xyz.playedu.api.constant.SystemConstant;
+import xyz.playedu.api.service.JWTService;
+import xyz.playedu.api.types.JWTPayload;
+import xyz.playedu.api.types.JsonResponse;
+import xyz.playedu.api.util.RequestUtil;
+
+import java.io.IOException;
+
+@Component
+@Slf4j
+public class AdminAuthMiddleware implements HandlerInterceptor {
+
+ @Autowired
+ private JWTService jwtService;
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ if (BackendBus.inUnAuthWhitelist(request.getRequestURI())) {
+ return HandlerInterceptor.super.preHandle(request, response, handler);
+ }
+
+ String token = RequestUtil.token();
+ if (token.length() == 0) {
+ responseTransform(response, 401, "请登录");
+ return false;
+ }
+
+ try {
+ JWTPayload payload = jwtService.parse(token, SystemConstant.JWT_PRV_ADMIN_USER);
+
+ // 用户信息写入context
+ PlayEduThreadLocal.setAdminUserId(payload.getSub());
+
+ return HandlerInterceptor.super.preHandle(request, response, handler);
+ } catch (Exception e) {
+ responseTransform(response, 401, "请重新登录");
+ return false;
+ }
+ }
+
+ private void responseTransform(HttpServletResponse response, int code, String msg) throws IOException {
+ response.setStatus(code);
+ response.setContentType("application/json;charset=utf-8");
+ response.getWriter().print(JSON.toJSONString(JsonResponse.error(msg)));
+ }
+
+ @Override
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+ PlayEduThreadLocal.remove();
+ HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
+ }
+}
diff --git a/src/main/java/xyz/playedu/api/middleware/AuthMiddleware.java b/src/main/java/xyz/playedu/api/middleware/AuthMiddleware.java
deleted file mode 100644
index b324d28..0000000
--- a/src/main/java/xyz/playedu/api/middleware/AuthMiddleware.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package xyz.playedu.api.middleware;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-@Target({ElementType.METHOD})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface AuthMiddleware {
- String prv();
-}
diff --git a/src/main/java/xyz/playedu/api/middleware/ImageCaptchaCheckMiddleware.java b/src/main/java/xyz/playedu/api/middleware/ImageCaptchaCheckMiddleware.java
index 1e1651b..104d4e9 100644
--- a/src/main/java/xyz/playedu/api/middleware/ImageCaptchaCheckMiddleware.java
+++ b/src/main/java/xyz/playedu/api/middleware/ImageCaptchaCheckMiddleware.java
@@ -2,6 +2,7 @@ package xyz.playedu.api.middleware;
import java.lang.annotation.*;
+@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ImageCaptchaCheckMiddleware {
diff --git a/src/main/java/xyz/playedu/api/middleware/impl/AuthMiddlewareImpl.java b/src/main/java/xyz/playedu/api/middleware/impl/AuthMiddlewareImpl.java
deleted file mode 100644
index 140641a..0000000
--- a/src/main/java/xyz/playedu/api/middleware/impl/AuthMiddlewareImpl.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package xyz.playedu.api.middleware.impl;
-
-import lombok.extern.slf4j.Slf4j;
-import org.aspectj.lang.ProceedingJoinPoint;
-import org.aspectj.lang.annotation.Around;
-import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.annotation.Pointcut;
-import org.aspectj.lang.reflect.MethodSignature;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-import xyz.playedu.api.middleware.AuthMiddleware;
-import xyz.playedu.api.service.JWTService;
-
-@Aspect
-@Component
-@Slf4j
-public class AuthMiddlewareImpl {
-
- @Autowired
- private JWTService jwtService;
-
- @Pointcut("@annotation(xyz.playedu.api.middleware.AuthMiddleware)")
- private void doPointcut() {
- }
-
- @Around("doPointcut()")
- public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
- MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
- AuthMiddleware authMiddleware = methodSignature.getMethod().getAnnotation(AuthMiddleware.class);
- log.info("prv的值:" + authMiddleware.prv());
- return joinPoint.proceed();
- }
-
-}
diff --git a/src/main/java/xyz/playedu/api/service/JWTService.java b/src/main/java/xyz/playedu/api/service/JWTService.java
index 6a937b4..a70eef0 100644
--- a/src/main/java/xyz/playedu/api/service/JWTService.java
+++ b/src/main/java/xyz/playedu/api/service/JWTService.java
@@ -1,7 +1,15 @@
package xyz.playedu.api.service;
+import xyz.playedu.api.exception.JwtLogoutException;
+import xyz.playedu.api.types.JWTPayload;
import xyz.playedu.api.types.JwtToken;
public interface JWTService {
JwtToken generate(Integer userId, String iss, String prv);
+
+ boolean isInBlack(String jti);
+
+ void logout(String token, String prv) throws JwtLogoutException;
+
+ JWTPayload parse(String token, String prv) throws JwtLogoutException;
}
diff --git a/src/main/java/xyz/playedu/api/util/RequestUtil.java b/src/main/java/xyz/playedu/api/util/RequestUtil.java
index 19651e2..af76292 100644
--- a/src/main/java/xyz/playedu/api/util/RequestUtil.java
+++ b/src/main/java/xyz/playedu/api/util/RequestUtil.java
@@ -23,6 +23,18 @@ public class RequestUtil {
return null;
}
+ public static String token() {
+ HttpServletRequest request = RequestUtil.handler();
+ if (request == null) {
+ return "";
+ }
+ String token = request.getHeader("Authorization");
+ if (token == null || token.length() == 0 || token.split(" ").length != 2) {
+ return "";
+ }
+ return token.split(" ")[1];
+ }
+
/**
* 获取不带参请求URl
* 示例: https://127.0.0.1:8082/api/system/menu/menus
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index b985512..0aa09bc 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -42,5 +42,7 @@ playedu:
key: "eJTJSLPv13fw9twbuPoeicypLqnSfYWL" #32个字符,加密key用来加密jwt的数据[运行本系统之前请务必修改]
expire: 1296000 #token有效期[单位:秒,默认15天]
cache-black-prefix: "jwt:blk:" #主动注销的token黑名单缓存前缀
-
+ # CORS
+ cors:
+ origins: "*"