commit 385b377b10d04ac0aab115d9a2c76c5135ef9db5
Author: forely <1605769034@qq.com>
Date: Tue Mar 18 19:24:33 2025 +0800
基本实现登录注册
diff --git a/software_teamwork/README.md b/software_teamwork/README.md
new file mode 100644
index 0000000..f321b36
--- /dev/null
+++ b/software_teamwork/README.md
@@ -0,0 +1,2 @@
+# software_teamwork
+
diff --git a/software_teamwork/作业截图及文件/第三次_项目任务书/珞珈岛-项目任务书v1.docx b/software_teamwork/作业截图及文件/第三次_项目任务书/珞珈岛-项目任务书v1.docx
new file mode 100644
index 0000000..152a237
Binary files /dev/null and b/software_teamwork/作业截图及文件/第三次_项目任务书/珞珈岛-项目任务书v1.docx differ
diff --git a/software_teamwork/作业截图及文件/第二次_第二周课后实践练习/前端/待办列表-李泽童.png b/software_teamwork/作业截图及文件/第二次_第二周课后实践练习/前端/待办列表-李泽童.png
new file mode 100644
index 0000000..6b2572d
Binary files /dev/null and b/software_teamwork/作业截图及文件/第二次_第二周课后实践练习/前端/待办列表-李泽童.png differ
diff --git a/software_teamwork/作业截图及文件/第二次_第二周课后实践练习/后端/id查找成功.png b/software_teamwork/作业截图及文件/第二次_第二周课后实践练习/后端/id查找成功.png
new file mode 100644
index 0000000..a23f4ae
Binary files /dev/null and b/software_teamwork/作业截图及文件/第二次_第二周课后实践练习/后端/id查找成功.png differ
diff --git a/software_teamwork/作业截图及文件/第二次_第二周课后实践练习/后端/数据库可视化.png b/software_teamwork/作业截图及文件/第二次_第二周课后实践练习/后端/数据库可视化.png
new file mode 100644
index 0000000..7d446e8
Binary files /dev/null and b/software_teamwork/作业截图及文件/第二次_第二周课后实践练习/后端/数据库可视化.png differ
diff --git a/software_teamwork/作业截图及文件/第二次_第二周课后实践练习/后端/注册请求成功.png b/software_teamwork/作业截图及文件/第二次_第二周课后实践练习/后端/注册请求成功.png
new file mode 100644
index 0000000..128e6fe
Binary files /dev/null and b/software_teamwork/作业截图及文件/第二次_第二周课后实践练习/后端/注册请求成功.png differ
diff --git a/software_teamwork/作业截图及文件/第二次_第二周课后实践练习/后端/用户名查找成功.png b/software_teamwork/作业截图及文件/第二次_第二周课后实践练习/后端/用户名查找成功.png
new file mode 100644
index 0000000..dbdb555
Binary files /dev/null and b/software_teamwork/作业截图及文件/第二次_第二周课后实践练习/后端/用户名查找成功.png differ
diff --git a/software_teamwork/作业截图及文件/第四次_AI辅助项目选题和获取需求/chat with ai.docx b/software_teamwork/作业截图及文件/第四次_AI辅助项目选题和获取需求/chat with ai.docx
new file mode 100644
index 0000000..dbc75c4
Binary files /dev/null and b/software_teamwork/作业截图及文件/第四次_AI辅助项目选题和获取需求/chat with ai.docx differ
diff --git a/software_teamwork/作业截图及文件/第四次_AI辅助项目选题和获取需求/chat with deepseek.png b/software_teamwork/作业截图及文件/第四次_AI辅助项目选题和获取需求/chat with deepseek.png
new file mode 100644
index 0000000..6409874
Binary files /dev/null and b/software_teamwork/作业截图及文件/第四次_AI辅助项目选题和获取需求/chat with deepseek.png differ
diff --git a/software_teamwork/作业截图及文件/第四次_AI辅助项目选题和获取需求/珞珈岛-项目任务书v2.docx b/software_teamwork/作业截图及文件/第四次_AI辅助项目选题和获取需求/珞珈岛-项目任务书v2.docx
new file mode 100644
index 0000000..a0c4d26
Binary files /dev/null and b/software_teamwork/作业截图及文件/第四次_AI辅助项目选题和获取需求/珞珈岛-项目任务书v2.docx differ
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/.gitignore b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/.gitignore
new file mode 100644
index 0000000..35410ca
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/.gitignore
@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/compiler.xml b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/compiler.xml
new file mode 100644
index 0000000..8b9a28a
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/compiler.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/dataSources.xml b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/dataSources.xml
new file mode 100644
index 0000000..2cdd411
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/dataSources.xml
@@ -0,0 +1,31 @@
+
+
+
+
+ mysql.8
+ true
+ true
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://192.168.59.129:3306/luojia_channel?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
+
+
+
+
+
+ $ProjectFileDir$
+
+
+ redis
+ true
+ true
+ jdbc.RedisDriver
+ jdbc:redis://192.168.59.129:6379/0
+
+
+
+
+
+ $ProjectFileDir$
+
+
+
\ No newline at end of file
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/encodings.xml b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/encodings.xml
new file mode 100644
index 0000000..e845c72
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/encodings.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/jarRepositories.xml b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/jarRepositories.xml
new file mode 100644
index 0000000..abb532a
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/jarRepositories.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/misc.xml b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/misc.xml
new file mode 100644
index 0000000..fdc35ea
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/settings.json b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/settings.json
new file mode 100644
index 0000000..152dce8
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/settings.json
@@ -0,0 +1,17 @@
+
+{
+ "luatools_path": "~/go/bin",
+ "dto_dir": "./ngx_conf/dto",
+ "swagger.docs.path": "./docs",
+ "swagger.excludes": "./client",
+ "swagger.file.type": "json",
+ "swagger.main.lua.path": "./main.lua",
+ "swagger.name": "swagger",
+ "swagger.search_dirs": "./ngx_conf,./config/cn/online",
+ "validator_dir": "./ngx_conf/validator",
+ "yapi.config.file": "docs/swagger-yapi.json",
+ "yapi.config.mode": "mergin",
+ "yapi.config.server": "https://api.yapi.net",
+ "yapi.config.token": "xxxxxxxxx"
+}
+
\ No newline at end of file
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/sqldialects.xml b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/sqldialects.xml
new file mode 100644
index 0000000..36966c2
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/sqldialects.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/uiDesigner.xml b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/vcs.xml b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/vcs.xml
new file mode 100644
index 0000000..288b36b
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/.idea/vcs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/pom.xml b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/pom.xml
new file mode 100644
index 0000000..cc28f5e
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/pom.xml
@@ -0,0 +1,67 @@
+
+
+ 4.0.0
+
+ com.luojia
+ luojia_channel
+ 1.0.0
+
+
+ common
+
+
+
+
+
+ org.redisson
+ redisson-spring-boot-starter
+
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.14.0
+
+
+ com.alibaba
+ transmittable-thread-local
+ 2.13.0
+
+
+
+ io.jsonwebtoken
+ jjwt-api
+ 0.11.5
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ 0.11.5
+ runtime
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ 0.11.5
+ runtime
+
+
+
+ org.springframework
+ spring-web
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/advice/GlobalExceptionHandler.java b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/advice/GlobalExceptionHandler.java
new file mode 100644
index 0000000..7034f3b
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/advice/GlobalExceptionHandler.java
@@ -0,0 +1,22 @@
+package com.luojia_channel.common.advice;
+
+
+import com.luojia_channel.common.domain.Result;
+import com.luojia_channel.common.exception.BaseException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+// 全局异常处理
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+ // 处理自定义业务异常
+ @ExceptionHandler(BaseException.class)
+ public Object handleBadRequestException(BaseException e) {
+ log.error("自定义异常 -> {} , 异常原因:{} ",e.getClass().getName(), e.getMessage());
+ log.debug("", e);
+ return Result.fail(e.getErrorCode(), e.getErrorMessage());
+ }
+
+}
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/config/MybatisConfig.java b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/config/MybatisConfig.java
new file mode 100644
index 0000000..5c2b3bf
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/config/MybatisConfig.java
@@ -0,0 +1,7 @@
+package com.luojia_channel.common.config;
+
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MybatisConfig {
+}
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/config/RedisConfig.java b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/config/RedisConfig.java
new file mode 100644
index 0000000..4502db3
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/config/RedisConfig.java
@@ -0,0 +1,45 @@
+package com.luojia_channel.common.config;
+
+import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.config.Config;
+import org.springframework.beans.factory.annotation.Value;
+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.StringRedisSerializer;
+
+@Configuration
+public class RedisConfig {
+ @Bean
+ public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+
+ RedisTemplate redisTemplate = new RedisTemplate<>();
+ redisTemplate.setConnectionFactory(redisConnectionFactory);
+ // 设置key和value的序列化规则
+ redisTemplate.setKeySerializer(new StringRedisSerializer());
+ redisTemplate.setValueSerializer(new GenericFastJsonRedisSerializer());
+ redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+ redisTemplate.setHashValueSerializer(new GenericFastJsonRedisSerializer());
+
+ return redisTemplate;
+ }
+
+ // Redisson分布式锁
+ @Value("${spring.data.redis.host}")
+ private String host;
+ @Value("${spring.data.redis.port}")
+ private String port;
+ @Value("${spring.data.redis.password}")
+ private String password;
+ @Bean
+ public RedissonClient redissonClient(){
+ //配置
+ Config config=new Config();
+ config.useSingleServer().setAddress("redis://"+host+":"+port).setPassword(password);
+ //创建对并且返回
+ return Redisson.create(config);
+ }
+}
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/config/WebMvcConfig.java b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/config/WebMvcConfig.java
new file mode 100644
index 0000000..c1ea840
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/config/WebMvcConfig.java
@@ -0,0 +1,30 @@
+package com.luojia_channel.common.config;
+
+import com.luojia_channel.common.interceptor.AuthInterceptor;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+@RequiredArgsConstructor
+public class WebMvcConfig implements WebMvcConfigurer {
+ private final AuthInterceptor authInterceptor;
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ // 登录拦截器
+ registry.addInterceptor(authInterceptor)
+ .excludePathPatterns("/user/login",
+ "/user/register"
+ );
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+}
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/constants/RedisConstant.java b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/constants/RedisConstant.java
new file mode 100644
index 0000000..2f86484
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/constants/RedisConstant.java
@@ -0,0 +1,8 @@
+package com.luojia_channel.common.constants;
+
+public class RedisConstant {
+ // redis存储的refreshToken前缀
+ public static final String REFRESH_TOKEN_PREFIX = "refresh_token:";
+ // redis存储的黑名单前缀
+ public static final String BLACKLIST_PREFIX = "blacklist:";
+}
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/domain/Result.java b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/domain/Result.java
new file mode 100644
index 0000000..5c754b5
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/domain/Result.java
@@ -0,0 +1,28 @@
+package com.luojia_channel.common.domain;
+
+import lombok.Data;
+
+// 统一返回前端的结果
+@Data
+public class Result {
+ private int code;
+ private String msg;
+ private T data;
+ public Result(int code, String msg, T data) {
+ this.code = code;
+ this.msg = msg;
+ this.data = data;
+ }
+ public static Result success(T data) {
+ return new Result<>(200, "success", data);
+ }
+ public static Result success() {
+ return new Result<>(200, "success", null);
+ }
+ public static Result fail(String msg) {
+ return new Result<>(500, msg, null);
+ }
+ public static Result fail(int code, String msg) {
+ return new Result<>(code, msg, null);
+ }
+}
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/domain/UserDTO.java b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/domain/UserDTO.java
new file mode 100644
index 0000000..3d2feeb
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/domain/UserDTO.java
@@ -0,0 +1,17 @@
+package com.luojia_channel.common.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class UserDTO {
+ private Long userId;
+ private String username;
+ private String accessToken;
+ private String refreshToken;
+}
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/exception/BaseException.java b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/exception/BaseException.java
new file mode 100644
index 0000000..76fc30a
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/exception/BaseException.java
@@ -0,0 +1,15 @@
+package com.luojia_channel.common.exception;
+
+import lombok.Getter;
+
+@Getter
+public class BaseException extends RuntimeException {
+ private final int errorCode;
+ private final String errorMessage;
+
+ public BaseException(int errorCode, String errorMessage) {
+ super(errorMessage);
+ this.errorCode = errorCode;
+ this.errorMessage = errorMessage;
+ }
+}
\ No newline at end of file
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/exception/UserException.java b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/exception/UserException.java
new file mode 100644
index 0000000..b243e3e
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/exception/UserException.java
@@ -0,0 +1,7 @@
+package com.luojia_channel.common.exception;
+
+public class UserException extends BaseException{
+ public UserException(String msg){
+ super(500, msg);
+ }
+}
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/interceptor/AuthInterceptor.java b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/interceptor/AuthInterceptor.java
new file mode 100644
index 0000000..c937694
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/interceptor/AuthInterceptor.java
@@ -0,0 +1,50 @@
+package com.luojia_channel.common.interceptor;
+
+import com.alibaba.fastjson.JSON;
+import com.luojia_channel.common.domain.Result;
+import com.luojia_channel.common.domain.UserDTO;
+import com.luojia_channel.common.exception.UserException;
+import com.luojia_channel.common.utils.JWTUtil;
+import com.luojia_channel.common.utils.UserContext;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+@Component
+@RequiredArgsConstructor
+public class AuthInterceptor implements HandlerInterceptor {
+
+ private final JWTUtil jwtUtil;
+
+ @Override
+ public boolean preHandle(HttpServletRequest request,
+ HttpServletResponse response,
+ Object handler) throws Exception {
+ // 获取请求中的双Token
+ String accessToken = request.getHeader("Authorization");
+ String refreshToken = request.getHeader("X-Refresh-Token");
+ try {
+ // 验证Token并处理自动刷新
+ UserDTO user = jwtUtil.checkLogin(accessToken, refreshToken);
+ // 将新Token写入响应头
+ if (user.getAccessToken() != null) {
+ response.setHeader("New-Access-Token", JWTUtil.TOKEN_PREFIX + user.getAccessToken());
+ response.setHeader("New-Refresh-Token", user.getRefreshToken());
+ }
+ UserContext.setUser(user);
+ return true;
+ } catch (UserException ex) {
+ // Token验证失败处理
+ response.setContentType("application/json");
+ response.setCharacterEncoding("UTF-8");
+ response.setStatus(HttpStatus.UNAUTHORIZED.value());
+ response.getWriter().write(
+ JSON.toJSONString(Result.fail(ex.getMessage()))
+ );
+ return false;
+ }
+ }
+}
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/utils/JWTUtil.java b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/utils/JWTUtil.java
new file mode 100644
index 0000000..e3c134a
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/utils/JWTUtil.java
@@ -0,0 +1,179 @@
+package com.luojia_channel.common.utils;
+
+import com.alibaba.fastjson.JSON;
+import com.luojia_channel.common.domain.UserDTO;
+import com.luojia_channel.common.exception.UserException;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import static com.luojia_channel.common.constants.RedisConstant.BLACKLIST_PREFIX;
+import static com.luojia_channel.common.constants.RedisConstant.REFRESH_TOKEN_PREFIX;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public final class JWTUtil {
+
+ private static final long ACCESS_EXPIRATION = 60 * 60 * 1000; //一小时
+ private static final long REFRESH_EXPIRATION = 60 * 60 * 24 * 15 * 1000; //15天
+ private static final Long NEED_REFRESH_TTL = 7L;
+ private static final String USER_ID_KEY = "userId";
+ private static final String USER_NAME_KEY = "username";
+ public static final String TOKEN_PREFIX = "Bearer ";
+ public static final String ISS = "luojiachannel";
+ public static final String SECRET = "SecretKey5464Created2435By54377Forely02345239354893543157956476525685754352976546564766315468763584576";
+
+ private final RedisUtil redisUtil;
+
+ /**
+ * 生成accessToken
+ * @param userInfo
+ * @return
+ */
+ public String generateAccessToken(UserDTO userInfo) {
+ Map customerUserMap = new HashMap<>();
+ customerUserMap.put(USER_ID_KEY, userInfo.getUserId());
+ customerUserMap.put(USER_NAME_KEY, userInfo.getUsername());
+ String jwtToken = Jwts.builder()
+ .signWith(SignatureAlgorithm.HS512, SECRET)
+ .setIssuedAt(new Date())
+ .setIssuer(ISS)
+ .setSubject(JSON.toJSONString(customerUserMap))
+ .setExpiration(new Date(System.currentTimeMillis() + ACCESS_EXPIRATION))
+ .compact();
+ return jwtToken;
+ }
+
+ /**
+ * 生成refreshToken
+ * @param userInfo
+ * @return
+ */
+ public String generateRefreshToken(UserDTO userInfo) {
+ Map customerUserMap = new HashMap<>();
+ customerUserMap.put(USER_ID_KEY, userInfo.getUserId());
+ customerUserMap.put(USER_NAME_KEY, userInfo.getUsername());
+ String jwtToken = Jwts.builder()
+ .signWith(SignatureAlgorithm.HS512, SECRET)
+ .setIssuedAt(new Date())
+ .setIssuer(ISS)
+ .setSubject(JSON.toJSONString(customerUserMap))
+ .setExpiration(new Date(System.currentTimeMillis() + REFRESH_EXPIRATION))
+ .compact();
+ return jwtToken;
+ }
+
+ /**
+ * 解析token获得用户
+ * @param accessToken
+ * @return
+ */
+ public UserDTO parseJwtToken(String accessToken) {
+ if (!StringUtils.hasText(accessToken) || !accessToken.startsWith(TOKEN_PREFIX)) {
+ throw new UserException("Token格式错误");
+ }
+ String rawAccessToken = accessToken.replace(TOKEN_PREFIX, "");
+ try {
+ Claims claims = parseToken(rawAccessToken);
+ Date expiration = claims.getExpiration();
+ if (expiration.after(new Date())) {
+ String subject = claims.getSubject();
+ return JSON.parseObject(subject, UserDTO.class);
+ }
+ } catch (ExpiredJwtException ignored) {
+ } catch (Exception ex) {
+ log.error("JWT Token解析失败,请检查", ex);
+ }
+ return null;
+ }
+
+ /**
+ * 检查登录
+ * @param accessToken
+ * @param refreshToken
+ * @return
+ */
+ public UserDTO checkLogin(String accessToken, String refreshToken) {
+ if (!StringUtils.hasText(accessToken) || !accessToken.startsWith(TOKEN_PREFIX)) {
+ throw new UserException("Token格式错误");
+ }
+ String rawAccessToken = accessToken.replace(TOKEN_PREFIX, "");
+ // 在黑名单中
+ if (redisUtil.hasKey(BLACKLIST_PREFIX + rawAccessToken)) {
+ throw new UserException("Token已失效");
+ }
+ try {
+ Claims claims = parseToken(rawAccessToken);
+ UserDTO user = extractUserFromClaims(claims);
+ if (needRefresh(claims)) {
+ return refreshTokens(user, refreshToken);
+ }
+ return user;
+ } catch (ExpiredJwtException ex) {
+ // accessToken过期,尝试用refreshToken刷新
+ // 注意,这里不能直接用过期时间与现在时间做差判断,因为过期会抛异常
+ return handleExpiredToken(ex, refreshToken);
+ } catch (Exception ex) {
+ throw new UserException("Token无效");
+ }
+ }
+
+ private boolean needRefresh(Claims claims) {
+ long remaining = claims.getExpiration().getTime() - System.currentTimeMillis();
+ return remaining < 30 * 60 * 1000; // 剩余30分钟刷新
+ }
+
+ private UserDTO refreshTokens(UserDTO user, String refreshToken) {
+ String redisKey = REFRESH_TOKEN_PREFIX + user.getUserId();
+ String storedRefreshToken = redisUtil.get(redisKey, String.class);
+ if (!refreshToken.equals(storedRefreshToken)) {
+ throw new UserException("refreshToken无效");
+ }
+ // 生成新token并更新redis
+ String newAccessToken = generateAccessToken(user);
+ String newRefreshToken = generateRefreshToken(user);
+ // 惰性刷新refreshToken
+ Long ttl = redisUtil.getExpire(redisKey, TimeUnit.DAYS);
+ if(ttl < NEED_REFRESH_TTL)
+ redisUtil.set(redisKey, newRefreshToken, REFRESH_EXPIRATION, TimeUnit.MILLISECONDS);
+ user.setAccessToken(newAccessToken);
+
+ return user;
+ }
+
+ private UserDTO handleExpiredToken(ExpiredJwtException ex, String refreshToken) {
+ // 从过期token中提取用户信息
+ UserDTO user = extractUserFromClaims(ex.getClaims());
+ // 刷新双 Token
+ return refreshTokens(user, refreshToken);
+ }
+
+ private Claims parseToken(String token) {
+ return Jwts.parser()
+ .setSigningKey(SECRET)
+ .parseClaimsJws(token)
+ .getBody();
+ }
+
+ private UserDTO extractUserFromClaims(Claims claims) {
+ String subject = claims.getSubject();
+ return JSON.parseObject(subject, UserDTO.class);
+ }
+
+ public long getRemainingTime(String rawToken){
+ Claims claims = parseToken(rawToken);
+ return claims.getExpiration().getTime()-System.currentTimeMillis();
+ }
+
+}
\ No newline at end of file
diff --git a/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/utils/RedisUtil.java b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/utils/RedisUtil.java
new file mode 100644
index 0000000..25f0160
--- /dev/null
+++ b/software_teamwork/珞珈岛-项目相关文件/luojia_island/common/src/main/java/com/luojia_channel/common/utils/RedisUtil.java
@@ -0,0 +1,168 @@
+package com.luojia_channel.common.utils;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ZSetOperations;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+@Component
+@RequiredArgsConstructor
+public class RedisUtil {
+
+ private final RedisTemplate redisTemplate;
+ private static final Long DEFAULT_TIMEOUT = 30000L;
+ private static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MILLISECONDS;
+
+ /**
+ * redis基础操作
+ */
+
+ public T get(String key, Class type) {
+ Object value = redisTemplate.opsForValue().get(key);
+ return value != null ? type.cast(value) : null;
+ }
+
+ public void set(String key, Object value) {
+ redisTemplate.opsForValue().set(key, value, DEFAULT_TIMEOUT, DEFAULT_TIME_UNIT);
+ }
+
+ public void set(String key, Object value, long timeout, TimeUnit unit) {
+ redisTemplate.opsForValue().set(key, value, timeout, unit);
+ }
+
+ public void expire(String key, long time, TimeUnit timeUnit) {
+ redisTemplate.expire(key, time, timeUnit);
+ }
+
+ public void delete(String key) {
+ redisTemplate.delete(key);
+ }
+
+ public boolean hasKey(String key) {
+ Boolean result = redisTemplate.hasKey(key);
+ return result != null;
+ }
+
+ public Long getExpire(String key, TimeUnit timeUnit){
+ return redisTemplate.getExpire(key, timeUnit);
+ }
+
+ /**
+ * set操作
+ */
+ private List convertSetToList(Set