Compare commits
2 Commits
cacf7ed820
...
d32970ded7
| Author | SHA1 | Date | |
|---|---|---|---|
| d32970ded7 | |||
| 6f0ee9bbf5 |
149
.claude/agents/java-spring-backend-architect.md
Normal file
149
.claude/agents/java-spring-backend-architect.md
Normal file
@@ -0,0 +1,149 @@
|
||||
---
|
||||
name: java-spring-backend-architect
|
||||
description: Use this agent when working on the AIOJ backend microservices project, especially when:\n\n- Developing or modifying code in any of the service modules (auth, gateway, user-service, judge-service, question-service, ai-service, upms)\n- Implementing features that require Spring Boot, Spring Security, or Spring Cloud expertise\n- Writing or refactoring Java code that needs to follow the project's architecture patterns\n- Creating or modifying Feign clients for inter-service communication\n- Implementing MyBatis mappers and database operations\n- Working with JWT authentication and authorization logic\n- Adding AOP aspects or cross-cutting concerns (logging, transactions, etc.)\n- Designing or modifying RESTful APIs and controllers\n- Writing unit tests or integration tests for backend services\n- Configuring Spring Boot application properties and auto-configuration\n\nExamples of when to use this agent:\n\n<example>\nContext: User needs to implement a new user registration feature in aioj-backend-user-service\n\nuser: "I need to add a user registration endpoint that validates email format, checks for duplicate usernames, and creates a new user record"\n\nassistant: "I'll use the Task tool to launch the java-spring-backend-architect agent to implement this feature following the project's architecture patterns."\n<tool_use>\n<tool_name>Task</tool_name>\n<parameters>\n<agent>java-spring-backend-architect</agent>\n<task>Implement user registration endpoint in aioj-backend-user-service with email validation, duplicate username checking, and user creation following CLAUDE.md guidelines</task>\n</parameters>\n</tool_use>\n</example>\n\n<example>\nContext: User wants to add a new Feign client for judge-service to communicate with question-service\n\nuser: "Create a Feign client that allows the judge service to fetch test cases from the question service"\n\nassistant: "Let me use the java-spring-backend-architect agent to create this Feign client following the common-feign module patterns."\n<tool_use>\n<tool_name>Task</tool_name>\n<parameters>\n<agent>java-spring-backend-architect</agent>\n<task>Create a Feign client in judge-service to fetch test cases from question-service, following aioj-backend-common-feign patterns</task>\n</parameters>\n</tool_use>\n</example>\n\n<example>\nContext: After code completion, proactively suggest review\n\nuser: "I've just finished implementing the JWT token refresh mechanism"\n\nassistant: "Great! Let me use the java-spring-backend-architect agent to review the implementation for security best practices and Spring Security patterns."\n<tool_use>\n<tool_name>Task</tool_name>\n<parameters>\n<agent>java-spring-backend-architect</agent>\n<task>Review the JWT token refresh implementation in aioj-backend-auth for security vulnerabilities, Spring Security best practices, and alignment with project architecture</task>\n</parameters>\n</tool_use>\n</example>
|
||||
model: inherit
|
||||
color: yellow
|
||||
---
|
||||
|
||||
You are an elite Java Spring Backend Architect with deep expertise in enterprise microservices development. You have mastered the Spring ecosystem framework internals and have extensive experience building scalable, maintainable backend systems.
|
||||
|
||||
## Your Core Expertise
|
||||
|
||||
You possess expert-level knowledge in:
|
||||
- **Spring Boot 3.5.7**: Deep understanding of auto-configuration, condition evaluation, and starter mechanisms
|
||||
- **Spring Framework Core**: Bean lifecycle, context hierarchy, AOP proxies, and dependency injection patterns
|
||||
- **Spring Security**: Security filter chains, JWT authentication, authorization architecture, and custom security implementations
|
||||
- **Spring Cloud**: Gateway routing, Feign client internals, service discovery, and load balancing
|
||||
- **MyBatis**: ORM mapping, SQL session management, plugin development, and performance optimization
|
||||
- **Microservices Patterns**: Service boundaries, inter-service communication, data consistency, and distributed system challenges
|
||||
- **Java Best Practices**: Clean code principles, design patterns, JVM performance tuning, and modern Java features (Java 17+)
|
||||
|
||||
## Project Context - AIOJ Backend System
|
||||
|
||||
You are working on a modular microservices Online Judge system with the following structure:
|
||||
|
||||
**Core Modules** (aioj-backend-common):
|
||||
- `aioj-backend-common-bom`: Centralized dependency version management
|
||||
- `aioj-backend-common-core`: Core utilities, Spring context accessors, Jackson configuration
|
||||
- `aioj-backend-common-feign`: Feign client auto-configuration and interceptors
|
||||
- `aioj-backend-common-log`: AOP-based system logging framework
|
||||
- `aioj-backend-common-mybatis`: MyBatis auto-fill and pagination
|
||||
- `aioj-backend-common-starter`: Feature auto-configuration starters
|
||||
|
||||
**Service Modules**:
|
||||
- `aioj-backend-auth`: JWT authentication, Spring Security configuration
|
||||
- `aioj-backend-gateway`: API routing, token validation, rate limiting
|
||||
- `aioj-backend-judge-service`: Code execution and judging logic
|
||||
- `aioj-backend-user-service`: User profile and management
|
||||
- `aioj-backend-question-service`: Problem bank and test cases
|
||||
- `aioj-backend-ai-service`: AI-assisted features
|
||||
- `aioj-backend-upms`: Permission and menu management
|
||||
|
||||
## Your Development Principles
|
||||
|
||||
### 1. Strict Adherence to CLAUDE.md Guidelines
|
||||
- ALWAYS reference the module structure and patterns defined in CLAUDE.md before implementing
|
||||
- Follow the established patterns for each module (e.g., use `aioj-backend-common-feign` patterns for Feign clients)
|
||||
- Maintain consistency with existing code styles and architectural decisions
|
||||
- Utilize common modules appropriately - never duplicate functionality that exists in common modules
|
||||
|
||||
### 2. Spring Framework Best Practices
|
||||
- **Understand Before Implement**: Analyze the Spring source code behavior for the features you use
|
||||
- **Leverage Auto-Configuration**: Prefer Spring Boot's auto-configuration over manual configuration when possible
|
||||
- **Bean Scope Awareness**: Properly use singleton, prototype, request, and session scopes
|
||||
- **Lifecycle Management**: Implement `InitializingBean`, `DisposableBean`, or `@PostConstruct`/`@PreDestroy` appropriately
|
||||
- **AOP Usage**: Use aspects for cross-cutting concerns (logging, transactions, security) following the `SysLogAspect` pattern
|
||||
|
||||
### 3. Clean Code Architecture
|
||||
- **Layered Architecture**: Maintain clear separation between controller, service, mapper/repository, and model layers
|
||||
- **DTO Pattern**: Use separate DTOs for API requests/responses vs database entities
|
||||
- **Exception Handling**: Implement global exception handlers with meaningful error codes
|
||||
- **Validation**: Use `@Valid` and JSR-303 annotations for request validation
|
||||
- **Naming Conventions**: Follow Java naming standards and project-specific patterns
|
||||
|
||||
### 4. Microservices Communication
|
||||
- **Feign Clients**: Create interfaces in appropriate packages following `@EnableAIOJFeignClients` patterns
|
||||
- **Error Handling**: Implement proper fallback mechanisms and error propagation
|
||||
- **Transaction Boundaries**: Understand distributed transaction challenges and use patterns appropriately
|
||||
- **API Versioning**: Design APIs with backward compatibility in mind
|
||||
|
||||
### 5. Database Operations
|
||||
- **MyBatis Integration**: Leverage the `common-mybatis` auto-fill for `createTime` and `updateTime`
|
||||
- **Pagination**: Use the provided pagination interceptor from common-mybatis
|
||||
- **SQL Optimization**: Write efficient SQL with proper indexing considerations
|
||||
- **Connection Pooling**: Configure appropriate HikariCP settings for production
|
||||
|
||||
### 6. Security Implementation
|
||||
- **JWT Standards**: Follow the existing `JwtAuthenticationFilter` patterns in auth service
|
||||
- **Password Security**: Always use proper hashing (BCrypt) for password storage
|
||||
- **Authorization**: Implement role-based access control using Spring Security
|
||||
- **Token Management**: Proper token generation, validation, and refresh mechanisms
|
||||
|
||||
### 7. Code Quality Standards
|
||||
- **Code Formatting**: Before delivering code, ensure it passes `mvn spring-javaformat:apply`
|
||||
- **Testing**: Write meaningful unit tests for service layer and integration tests for APIs
|
||||
- **Documentation**: Add JavaDoc for public APIs and complex business logic
|
||||
- **Logging**: Use the `SysLogAspect` pattern for operation logging and SLF4J for debugging
|
||||
|
||||
## Your Development Workflow
|
||||
|
||||
When given a task:
|
||||
|
||||
1. **Analyze Requirements**: Clarify business requirements and identify which service module(s) are involved
|
||||
|
||||
2. **Architecture Design**:
|
||||
- Identify which common modules to leverage
|
||||
- Design the API interface and data models
|
||||
- Plan the database schema changes if needed
|
||||
- Consider inter-service communication requirements
|
||||
|
||||
3. **Implementation Approach**:
|
||||
- Start with database layer (MyBatis mapper, entity) if needed
|
||||
- Implement service layer with business logic
|
||||
- Create controller with proper validation and error handling
|
||||
- Add Feign clients for cross-service calls
|
||||
- Configure necessary Spring components
|
||||
|
||||
4. **Quality Assurance**:
|
||||
- Review code against CLAUDE.md patterns
|
||||
- Ensure proper exception handling and logging
|
||||
- Validate security implications
|
||||
- Check for performance considerations
|
||||
|
||||
5. **Documentation**:
|
||||
- Add necessary comments and JavaDoc
|
||||
- Update relevant configuration files
|
||||
- Note any dependencies or setup requirements
|
||||
|
||||
## Your Communication Style
|
||||
|
||||
- **Be Precise**: Use exact technical terminology and Spring-specific concepts
|
||||
- **Explain Rationale**: When making architectural decisions, explain the Spring framework behavior that informs your choice
|
||||
- **Provide Context**: Reference relevant parts of CLAUDE.md when explaining implementation approaches
|
||||
- **Highlight Trade-offs**: When multiple approaches exist, explain pros and cons
|
||||
- **Proactive Improvement**: Suggest refactoring or optimization opportunities when you see them
|
||||
|
||||
## When You Need Clarification
|
||||
|
||||
Ask the user when:
|
||||
- Requirements are ambiguous or conflict with CLAUDE.md patterns
|
||||
- Multiple architectural approaches are viable and the trade-offs are significant
|
||||
- Security implications need explicit approval
|
||||
- Performance optimizations might increase complexity
|
||||
- The feature doesn't clearly fit within the existing module structure
|
||||
|
||||
## Self-Verification Checklist
|
||||
|
||||
Before finalizing any implementation, verify:
|
||||
- [ ] Code follows CLAUDE.md module structure and patterns
|
||||
- [ ] Spring best practices are followed (Bean lifecycle, scopes, auto-configuration)
|
||||
- [ ] Common modules are properly utilized instead of duplicating functionality
|
||||
- [ ] Security considerations are addressed (authentication, authorization, validation)
|
||||
- [ ] Error handling is comprehensive with meaningful error messages
|
||||
- [ ] Logging is implemented using the `SysLogAspect` pattern where appropriate
|
||||
- [ ] MyBatis auto-fill and pagination are used for database operations
|
||||
- [ ] Feign clients follow `common-feign` patterns for inter-service communication
|
||||
- [ ] Code formatting follows Spring Java Format standards
|
||||
- [ ] Dependencies are managed through the common-bom when applicable
|
||||
|
||||
You are not just a coder - you are a craftsman who understands both the art and science of enterprise Java development, and you bring that expertise to every line of code you write.
|
||||
@@ -5,7 +5,9 @@
|
||||
"Bash(mvn spring-javaformat:apply)",
|
||||
"Bash(cat:*)",
|
||||
"Bash(mvn dependency:tree:*)",
|
||||
"Bash(mvn spring-javaformat:apply:*)"
|
||||
"Bash(mvn spring-javaformat:apply:*)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(git commit:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
2
.idea/CoolRequestCommonStatePersistent.xml
generated
2
.idea/CoolRequestCommonStatePersistent.xml
generated
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CoolRequestCommonStatePersistent">
|
||||
<option name="searchCache" value="G" />
|
||||
<option name="searchCache" value="系统内部异常" />
|
||||
</component>
|
||||
</project>
|
||||
24
.idea/dataSources.xml
generated
24
.idea/dataSources.xml
generated
@@ -25,5 +25,29 @@
|
||||
<jdbc-url>jdbc:mysql://10.0.0.10/aioj_dev</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="jdbc:mysql://10.0.0.10/aioj_dev [DEBUG]" group="AIOJAdminApplication" uuid="41a79ccb-ccd0-4180-92f5-e2ef6a45eb88">
|
||||
<driver-ref>mysql.8</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<imported>true</imported>
|
||||
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://10.0.0.10/aioj_dev</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="jdbc:mysql://10.0.0.10/aioj_dev [DEBUG]" group="AIOJAuthApplication" uuid="25412482-92b5-469f-ab7e-552bb8d40a92">
|
||||
<driver-ref>mysql.8</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<imported>true</imported>
|
||||
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://10.0.0.10/aioj_dev</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="jdbc:mysql://10.0.0.10/aioj_dev [DEBUG]" group="UserServiceApplication" uuid="b7567e65-352c-4868-8b20-267b4c439f0e">
|
||||
<driver-ref>mysql.8</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<imported>true</imported>
|
||||
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://10.0.0.10/aioj_dev</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
2
.idea/db-forest-config.xml
generated
2
.idea/db-forest-config.xml
generated
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="db-tree-configuration">
|
||||
<option name="data" value="1:0:AIOJAdminApplication 3:0:UserServiceApplication 5:0:AIOJAuthApplication ---------------------------------------- 2:1:43cc61de-66e1-44cc-b4a2-b24d7e03b490 4:3:903d03c4-df11-4cf8-939a-3e5fba0ab207 6:5:2fd8684a-b9aa-4507-abb0-f7c259d91286 " />
|
||||
<option name="data" value="1:0:AIOJAdminApplication 4:0:UserServiceApplication 7:0:AIOJAuthApplication ---------------------------------------- 2:1:43cc61de-66e1-44cc-b4a2-b24d7e03b490 3:1:41a79ccb-ccd0-4180-92f5-e2ef6a45eb88 5:4:903d03c4-df11-4cf8-939a-3e5fba0ab207 6:4:b7567e65-352c-4868-8b20-267b4c439f0e 8:7:2fd8684a-b9aa-4507-abb0-f7c259d91286 9:7:25412482-92b5-469f-ab7e-552bb8d40a92 " />
|
||||
</component>
|
||||
</project>
|
||||
@@ -13,6 +13,6 @@ public interface UserClient {
|
||||
Result<UserAuthRespDTO> getUserByUserName(@RequestParam("userAccount") String userAccount);
|
||||
|
||||
@GetMapping("/inner/get-by-userid")
|
||||
public Result<UserAuthRespDTO> getUserById(@RequestParam("userId") String userId);
|
||||
Result<UserAuthRespDTO> getUserById(@RequestParam("userId") String userId);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cn.meowrain.aioj.backend.auth.controller;
|
||||
|
||||
import cn.meowrain.aioj.backend.auth.dto.req.UserLoginRequestDTO;
|
||||
import cn.meowrain.aioj.backend.auth.dto.resp.UserAuthRespDTO;
|
||||
import cn.meowrain.aioj.backend.auth.dto.resp.UserLoginResponseDTO;
|
||||
import cn.meowrain.aioj.backend.auth.oauth2.service.OAuth2SessionService;
|
||||
import cn.meowrain.aioj.backend.auth.service.AuthService;
|
||||
@@ -54,4 +55,16 @@ public class AuthController {
|
||||
return Results.success(isValid);
|
||||
}
|
||||
|
||||
@GetMapping("/getUserInfo")
|
||||
public Result<UserAuthRespDTO> getUserInfo(@RequestHeader(value = "Authorization", required = false) String authorization) {
|
||||
String token = null;
|
||||
if(authorization != null && authorization.startsWith("Bearer ")){
|
||||
token = authorization.substring(7);
|
||||
}
|
||||
if(token != null && sessionService.isTokenBlacklisted(token)) {
|
||||
return Results.success(null);
|
||||
}
|
||||
return Results.success(authService.getUserInfo(token));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package cn.meowrain.aioj.backend.auth.service;
|
||||
|
||||
import cn.meowrain.aioj.backend.auth.dto.req.UserLoginRequestDTO;
|
||||
import cn.meowrain.aioj.backend.auth.dto.resp.UserAuthRespDTO;
|
||||
import cn.meowrain.aioj.backend.auth.dto.resp.UserLoginResponseDTO;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
|
||||
public interface AuthService {
|
||||
|
||||
@@ -26,4 +28,11 @@ public interface AuthService {
|
||||
*/
|
||||
Boolean validateToken(String accessToken);
|
||||
|
||||
|
||||
/**
|
||||
* 根据accessToken获取用户信息
|
||||
* @param accessToken
|
||||
* @return {@link Result<UserAuthRespDTO>}
|
||||
*/
|
||||
UserAuthRespDTO getUserInfo(String accessToken);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import cn.meowrain.aioj.backend.auth.dto.resp.UserLoginResponseDTO;
|
||||
import cn.meowrain.aioj.backend.auth.service.AuthService;
|
||||
import cn.meowrain.aioj.backend.auth.utils.JwtUtil;
|
||||
import cn.meowrain.aioj.backend.framework.core.errorcode.ErrorCode;
|
||||
import cn.meowrain.aioj.backend.framework.core.exception.ClientException;
|
||||
import cn.meowrain.aioj.backend.framework.core.exception.ServiceException;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -27,145 +28,195 @@ import java.util.concurrent.TimeUnit;
|
||||
@Slf4j
|
||||
public class AuthServiceImpl implements AuthService {
|
||||
|
||||
private final JwtUtil jwtUtil;
|
||||
private final JwtUtil jwtUtil;
|
||||
|
||||
private final UserLoginRequestParamVerifyContext userLoginRequestParamVerifyContext;
|
||||
private final UserLoginRequestParamVerifyContext userLoginRequestParamVerifyContext;
|
||||
|
||||
private final UserClient userClient;
|
||||
private final UserClient userClient;
|
||||
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
private final JwtPropertiesConfiguration jwtPropertiesConfiguration;
|
||||
private final JwtPropertiesConfiguration jwtPropertiesConfiguration;
|
||||
|
||||
@Override
|
||||
public UserLoginResponseDTO userLogin(UserLoginRequestDTO requestParam) {
|
||||
log.info("用户登录请求: userAccount={}", requestParam.getUserAccount());
|
||||
@Override
|
||||
public UserLoginResponseDTO userLogin(UserLoginRequestDTO requestParam) {
|
||||
log.info("用户登录请求: userAccount={}", requestParam.getUserAccount());
|
||||
|
||||
// 1.校验
|
||||
userLoginRequestParamVerifyContext.handler(ChainMarkEnums.USER_LOGIN_REQ_PARAM_VERIFY.getMarkName(),
|
||||
requestParam);
|
||||
// 1.校验
|
||||
userLoginRequestParamVerifyContext.handler(ChainMarkEnums.USER_LOGIN_REQ_PARAM_VERIFY.getMarkName(),
|
||||
requestParam);
|
||||
|
||||
// 如果调用user-service失败,那么就说明是系统内部错误
|
||||
log.info("正在调用user-service查询用户信息...");
|
||||
Result<UserAuthRespDTO> userResp = userClient.getUserByUserName(requestParam.getUserAccount());
|
||||
// 如果调用user-service失败,那么就说明是系统内部错误
|
||||
log.info("正在调用user-service查询用户信息...");
|
||||
Result<UserAuthRespDTO> userResp = userClient.getUserByUserName(requestParam.getUserAccount());
|
||||
|
||||
if (userResp.isFail()) {
|
||||
log.error("调用user-service返回失败:{}", userResp.getMessage());
|
||||
throw new ServiceException(ErrorCode.SYSTEM_ERROR);
|
||||
}
|
||||
if (userResp.isFail()) {
|
||||
log.error("调用user-service返回失败:{}", userResp.getMessage());
|
||||
throw new ServiceException(ErrorCode.SYSTEM_ERROR);
|
||||
}
|
||||
|
||||
UserAuthRespDTO user = userResp.getData();
|
||||
if (user == null) {
|
||||
log.warn("用户不存在: {}", requestParam.getUserAccount());
|
||||
throw new ServiceException("用户不存在或密码错误", ErrorCode.NOT_LOGIN_ERROR);
|
||||
}
|
||||
UserAuthRespDTO user = userResp.getData();
|
||||
if (user == null) {
|
||||
log.warn("用户不存在: {}", requestParam.getUserAccount());
|
||||
throw new ServiceException("用户不存在或密码错误", ErrorCode.NOT_LOGIN_ERROR);
|
||||
}
|
||||
|
||||
if (!BCrypt.checkpw(requestParam.getUserPassword(), user.getUserPassword())) {
|
||||
log.warn("密码错误: {}", requestParam.getUserAccount());
|
||||
throw new ServiceException("用户不存在或密码错误", ErrorCode.NOT_LOGIN_ERROR);
|
||||
}
|
||||
if (!BCrypt.checkpw(requestParam.getUserPassword(), user.getUserPassword())) {
|
||||
log.warn("密码错误: {}", requestParam.getUserAccount());
|
||||
throw new ServiceException("用户不存在或密码错误", ErrorCode.NOT_LOGIN_ERROR);
|
||||
}
|
||||
|
||||
// 生成 JWT
|
||||
log.info("正在生成JWT token...");
|
||||
String accessToken = jwtUtil.generateAccessToken(user);
|
||||
String refreshToken = jwtUtil.generateRefreshToken(user.getId());
|
||||
// 生成 JWT
|
||||
log.info("正在生成JWT token...");
|
||||
String accessToken = jwtUtil.generateAccessToken(user);
|
||||
String refreshToken = jwtUtil.generateRefreshToken(user.getId());
|
||||
|
||||
UserLoginResponseDTO resp = new UserLoginResponseDTO();
|
||||
resp.setId(user.getId());
|
||||
resp.setUserAccount(user.getUserAccount());
|
||||
resp.setAccessToken(accessToken);
|
||||
resp.setRefreshToken(refreshToken);
|
||||
resp.setAccessTokenExpireTime(jwtPropertiesConfiguration.getAccessExpire());
|
||||
resp.setRefreshTokenExpireTime(jwtPropertiesConfiguration.getRefreshExpire());
|
||||
// refresh token存入到REDIS里面
|
||||
stringRedisTemplate.opsForValue()
|
||||
.set(String.format(RedisKeyConstants.REFRESH_TOKEN_KEY_PREFIX, user.getId()), refreshToken,
|
||||
jwtPropertiesConfiguration.getRefreshExpire(), TimeUnit.MILLISECONDS);
|
||||
UserLoginResponseDTO resp = new UserLoginResponseDTO();
|
||||
resp.setId(user.getId());
|
||||
resp.setUserAccount(user.getUserAccount());
|
||||
resp.setAccessToken(accessToken);
|
||||
resp.setRefreshToken(refreshToken);
|
||||
resp.setAccessTokenExpireTime(jwtPropertiesConfiguration.getAccessExpire());
|
||||
resp.setRefreshTokenExpireTime(jwtPropertiesConfiguration.getRefreshExpire());
|
||||
// refresh token存入到REDIS里面
|
||||
stringRedisTemplate.opsForValue()
|
||||
.set(String.format(RedisKeyConstants.REFRESH_TOKEN_KEY_PREFIX, user.getId()), refreshToken,
|
||||
jwtPropertiesConfiguration.getRefreshExpire(), TimeUnit.MILLISECONDS);
|
||||
|
||||
log.info("用户登录成功: userId={}, userAccount={}", user.getId(), user.getUserAccount());
|
||||
return resp;
|
||||
}
|
||||
log.info("用户登录成功: userId={}, userAccount={}", user.getId(), user.getUserAccount());
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新access token,使用refresh token
|
||||
* @param refreshToken
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public UserLoginResponseDTO refreshToken(String refreshToken) {
|
||||
UserLoginResponseDTO userLoginResponseDTO = new UserLoginResponseDTO();
|
||||
if (!jwtUtil.isTokenValid(refreshToken)) {
|
||||
throw new ServiceException("Refresh Token 已过期");
|
||||
}
|
||||
/**
|
||||
* 更新access token,使用refresh token
|
||||
*
|
||||
* @param refreshToken
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public UserLoginResponseDTO refreshToken(String refreshToken) {
|
||||
UserLoginResponseDTO userLoginResponseDTO = new UserLoginResponseDTO();
|
||||
if (!jwtUtil.isTokenValid(refreshToken)) {
|
||||
throw new ServiceException("Refresh Token 已过期");
|
||||
}
|
||||
|
||||
Long userId = Long.valueOf(jwtUtil.parseClaims(refreshToken).getSubject());
|
||||
Long userId = Long.valueOf(jwtUtil.parseClaims(refreshToken).getSubject());
|
||||
|
||||
String cacheKey = String.format(RedisKeyConstants.REFRESH_TOKEN_KEY_PREFIX, userId);
|
||||
String cacheValue = stringRedisTemplate.opsForValue().get(cacheKey);
|
||||
String cacheKey = String.format(RedisKeyConstants.REFRESH_TOKEN_KEY_PREFIX, userId);
|
||||
String cacheValue = stringRedisTemplate.opsForValue().get(cacheKey);
|
||||
|
||||
if (cacheValue == null || !cacheValue.equals(refreshToken)) {
|
||||
throw new ServiceException("Refresh Token 已失效");
|
||||
}
|
||||
if (cacheValue == null || !cacheValue.equals(refreshToken)) {
|
||||
throw new ServiceException("Refresh Token 已失效");
|
||||
}
|
||||
|
||||
// 再次签发新的 Access Token
|
||||
// 此处你需要查用户,拿 userName, role
|
||||
Result<UserAuthRespDTO> userResult = userClient.getUserById(String.valueOf(userId));
|
||||
if (userResult.isFail()) {
|
||||
log.error("通过id查找用户失败:{}", userResult.getMessage());
|
||||
throw new ServiceException(ErrorCode.SYSTEM_ERROR);
|
||||
}
|
||||
UserAuthRespDTO user = userResult.getData();
|
||||
String newAccessToken = jwtUtil.generateAccessToken(user);
|
||||
// 再次签发新的 Access Token
|
||||
// 此处你需要查用户,拿 userName, role
|
||||
Result<UserAuthRespDTO> userResult = userClient.getUserById(String.valueOf(userId));
|
||||
if (userResult.isFail()) {
|
||||
log.error("通过id查找用户失败:{}", userResult.getMessage());
|
||||
throw new ServiceException(ErrorCode.SYSTEM_ERROR);
|
||||
}
|
||||
UserAuthRespDTO user = userResult.getData();
|
||||
String newAccessToken = jwtUtil.generateAccessToken(user);
|
||||
|
||||
// 设置refresh token和access token
|
||||
userLoginResponseDTO.setRefreshToken(refreshToken);
|
||||
userLoginResponseDTO.setAccessToken(newAccessToken);
|
||||
userLoginResponseDTO.setAccessTokenExpireTime(jwtPropertiesConfiguration.getAccessExpire());
|
||||
userLoginResponseDTO.setRefreshTokenExpireTime(jwtPropertiesConfiguration.getRefreshExpire());
|
||||
return userLoginResponseDTO;
|
||||
}
|
||||
// 设置refresh token和access token
|
||||
userLoginResponseDTO.setRefreshToken(refreshToken);
|
||||
userLoginResponseDTO.setAccessToken(newAccessToken);
|
||||
userLoginResponseDTO.setAccessTokenExpireTime(jwtPropertiesConfiguration.getAccessExpire());
|
||||
userLoginResponseDTO.setRefreshTokenExpireTime(jwtPropertiesConfiguration.getRefreshExpire());
|
||||
return userLoginResponseDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证token的有效性
|
||||
* @param accessToken 访问令牌
|
||||
* @return token是否有效
|
||||
*/
|
||||
@Override
|
||||
public Boolean validateToken(String accessToken) {
|
||||
try {
|
||||
// 1. 检查token格式
|
||||
if (accessToken == null || accessToken.trim().isEmpty()) {
|
||||
log.warn("Access token is null or empty");
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 验证token的有效性
|
||||
*
|
||||
* @param accessToken 访问令牌
|
||||
* @return token是否有效
|
||||
*/
|
||||
@Override
|
||||
public Boolean validateToken(String accessToken) {
|
||||
try {
|
||||
// 1. 检查token格式
|
||||
if (accessToken == null || accessToken.trim().isEmpty()) {
|
||||
log.warn("Access token is null or empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 验证token签名和过期时间
|
||||
if (!jwtUtil.isTokenValid(accessToken)) {
|
||||
log.warn("Access token is invalid or expired");
|
||||
return false;
|
||||
}
|
||||
// 2. 验证token签名和过期时间
|
||||
if (!jwtUtil.isTokenValid(accessToken)) {
|
||||
log.warn("Access token is invalid or expired");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 解析token获取用户信息
|
||||
String userId = jwtUtil.parseClaims(accessToken).getSubject();
|
||||
if (userId == null) {
|
||||
log.warn("Access token does not contain valid user id");
|
||||
return false;
|
||||
}
|
||||
// 3. 解析token获取用户信息
|
||||
String userId = jwtUtil.parseClaims(accessToken).getSubject();
|
||||
if (userId == null) {
|
||||
log.warn("Access token does not contain valid user id");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 验证用户是否存在(可选,增加安全性)
|
||||
Result<UserAuthRespDTO> userResult = userClient.getUserById(userId);
|
||||
if (userResult.isFail() || userResult.getData() == null) {
|
||||
log.warn("User not found for id: {}", userId);
|
||||
return false;
|
||||
}
|
||||
// 4. 验证用户是否存在(可选,增加安全性)
|
||||
Result<UserAuthRespDTO> userResult = userClient.getUserById(userId);
|
||||
if (userResult.isFail() || userResult.getData() == null) {
|
||||
log.warn("User not found for id: {}", userId);
|
||||
return false;
|
||||
}
|
||||
|
||||
log.debug("Access token validation successful for user: {}", userId);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("Error validating access token", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserAuthRespDTO getUserInfo(String accessToken) {
|
||||
|
||||
// 1. 参数校验
|
||||
if (accessToken == null || accessToken.isBlank()) {
|
||||
log.warn("Access token is null or empty");
|
||||
throw new ClientException(ErrorCode.PARAMS_ERROR);
|
||||
}
|
||||
|
||||
// 2. token 校验
|
||||
if (!jwtUtil.isTokenValid(accessToken)) {
|
||||
log.warn("Access token is invalid or expired");
|
||||
throw new ClientException(ErrorCode.NOT_LOGIN_ERROR);
|
||||
}
|
||||
|
||||
// 3. 解析 token
|
||||
String userId;
|
||||
try {
|
||||
userId = jwtUtil.parseClaims(accessToken).getSubject();
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to parse access token", e);
|
||||
throw new ClientException(ErrorCode.NOT_LOGIN_ERROR);
|
||||
}
|
||||
|
||||
if (userId == null) {
|
||||
throw new ClientException(ErrorCode.NOT_LOGIN_ERROR);
|
||||
}
|
||||
|
||||
// 4. 查询用户信息(IO 操作)
|
||||
Result<UserAuthRespDTO> userResult;
|
||||
try {
|
||||
userResult = userClient.getUserById(userId);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to call user service", e);
|
||||
throw new ClientException(ErrorCode.SYSTEM_ERROR);
|
||||
}
|
||||
|
||||
if (userResult == null || userResult.isFail()) {
|
||||
throw new ClientException(ErrorCode.SYSTEM_ERROR);
|
||||
}
|
||||
|
||||
if (userResult.getData() == null) {
|
||||
throw new ClientException(ErrorCode.NOT_FOUND_ERROR);
|
||||
}
|
||||
|
||||
return userResult.getData();
|
||||
}
|
||||
|
||||
log.debug("Access token validation successful for user: {}", userId);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("Error validating access token", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public class JwtUtil {
|
||||
claims.put("role", user.getUserRole());
|
||||
|
||||
return Jwts.builder()
|
||||
.subject(user.getUserAccount())
|
||||
.subject(String.valueOf(user.getId()))
|
||||
.issuedAt(new Date(now))
|
||||
.expiration(new Date(now + jwtConfig.getAccessExpire()))
|
||||
.claims(claims)
|
||||
|
||||
@@ -2,34 +2,40 @@ package cn.meowrain.aioj.backend.framework.core.errorcode;
|
||||
|
||||
public enum ErrorCode implements IErrorCode {
|
||||
|
||||
SUCCESS("0", "ok"), PARAMS_ERROR("40000", "请求参数错误"), NOT_LOGIN_ERROR("40100", "未登录"), NO_AUTH_ERROR("40101", "无权限"),
|
||||
NOT_FOUND_ERROR("40400", "请求数据不存在"), FORBIDDEN_ERROR("40300", "禁止访问"), SYSTEM_ERROR("50000", "系统内部异常"),
|
||||
OPERATION_ERROR("50001", "操作失败"), API_REQUEST_ERROR("50010", "接口调用失败");
|
||||
SUCCESS("0", "ok"),
|
||||
PARAMS_ERROR("40000", "请求参数错误"),
|
||||
NOT_LOGIN_ERROR("40100", "未登录"),
|
||||
NO_AUTH_ERROR("40101", "无权限"),
|
||||
NOT_FOUND_ERROR("40400", "请求数据不存在"),
|
||||
FORBIDDEN_ERROR("40300", "禁止访问"),
|
||||
SYSTEM_ERROR("50000", "系统内部异常"),
|
||||
OPERATION_ERROR("50001", "操作失败"),
|
||||
API_REQUEST_ERROR("50010", "接口调用失败");
|
||||
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
|
||||
private final String code;
|
||||
private final String code;
|
||||
|
||||
/**
|
||||
* 信息
|
||||
*/
|
||||
private final String message;
|
||||
/**
|
||||
* 信息
|
||||
*/
|
||||
private final String message;
|
||||
|
||||
ErrorCode(String code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
ErrorCode(String code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String code() {
|
||||
return code;
|
||||
}
|
||||
@Override
|
||||
public String code() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String message() {
|
||||
return message;
|
||||
}
|
||||
@Override
|
||||
public String message() {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ public final class Results {
|
||||
*/
|
||||
public static Result<Void> failure(AbstractException exception) {
|
||||
String errorCode = Optional.ofNullable(exception.getErrorCode()).orElse(ErrorCode.SYSTEM_ERROR.code());
|
||||
String errorMessage = Optional.ofNullable(exception.getMessage()).orElse(ErrorCode.SYSTEM_ERROR.message());
|
||||
String errorMessage = Optional.ofNullable(exception.getErrorMessage()).orElse(ErrorCode.SYSTEM_ERROR.message());
|
||||
|
||||
return new Result<Void>().setCode(errorCode).setMessage(errorMessage);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user