lixueqiang 6 ヶ月 前
コミット
101572d6ba

+ 92 - 161
AGENTS.md

@@ -1,184 +1,115 @@
-# AGENTS.md - Development Guidelines for LXQ Spring Boot Application
+# AGENTS.md - LXQ Spring Boot Development Guidelines
 
 ## Project Overview
-Java Spring Boot enterprise application (v3.5.9, Java 21) with microservices architecture. Multi-module Maven project using MyBatis-Plus, Redis, RocketMQ, OAuth2, and Swagger for API documentation.
+Java 21, Spring Boot 3.5.9, Maven multi-module project with MyBatis-Plus, Redis, RocketMQ, OAuth2.
 
-## Development Environment Setup
+## Essential Commands
 ```bash
-# Start required services with Docker Compose
-docker-compose up -d lxq-mysql lxq-redis
-
-# Install Maven if not available (required for build commands)
-# Download from: https://maven.apache.org/download.cgi
-
-# IDE Configuration
-# Install Spring Java Format IntelliJ plugin:
-# https://repo1.maven.org/maven2/io/spring/javaformat/spring-javaformat-intellij-idea-plugin
+# Build & Run
+mvn clean compile              # Compile
+mvn clean package              # Package JAR
+mvn spring-boot:run            # Run application
+mvn clean install              # Install to local repo
+
+# Testing
+mvn test                       # Run all tests
+mvn test -Dtest=ClassName      # Single test class
+mvn test -Dtest=ClassName#methodName  # Single test method
+
+# Code Quality
+mvn spring-javaformat:apply    # Format code (MANDATORY before commits)
+mvn validate                   # Check format during build
+
+# Environment builds
+mvn clean package -Pdev        # Development (default)
+mvn clean package -Ptest       # Test environment
+mvn clean package -Pprod       # Production
 ```
 
-## Build & Package Commands
-```bash
-# Standard Maven commands (when Maven available)
-mvn clean compile                    # Compile the project
-mvn clean package                   # Package the application
-mvn clean install                   # Install to local repository
-mvn spring-boot:run                 # Run the application
-mvn test                            # Run all tests
-mvn spring-javaformat:apply        # Format code (MANDATORY before commits)
-
-# Environment-specific builds
-mvn clean package -Pdev            # Development build (default)
-mvn clean package -Ptest           # Test environment build
-mvn clean package -Pprod           # Production build
-
-# Run single test (when tests exist)
-mvn test -Dtest=ClassName#methodName
-mvn test -Dtest=ClassName
-
-# Code formatting validation (runs automatically during validate phase)
-mvn validate                        # Check code format
+## Code Style
+- **Formatter**: Spring Java Format plugin (v0.0.39) - REQUIRED before commits
+- **Indentation**: 4 spaces, no tabs
+- **Line endings**: LF (Unix)
+- **Max line length**: 120 chars
+- **No wildcard imports**
+- **Line comments only** (no block comments)
+
+## Naming Conventions
+| Element | Convention | Examples |
+|---------|------------|----------|
+| Class/Interface | PascalCase | `SysUserController`, `UserDTO` |
+| Method/Variable | camelCase | `getUserById`, `saveUser()` |
+| Constant | UPPER_SNAKE_CASE | `DEFAULT_PAGE_SIZE`, `COMMON_STATUS` |
+| Package | `com.lxq.{module}.biz.{layer}` | `com.lxq.admin.biz.controller` |
+
+## Architecture Layers
 ```
-
-## Code Style & Formatting Guidelines
-- **MANDATORY**: Spring Java Format plugin (v0.0.39) - must run before commits
-- **Package naming**: `com.lxq.*` convention (e.g., `com.lxq.admin.biz.controller`)
-- **Import organization**: Standard Java conventions, no wildcard imports
-- **Class naming**: PascalCase (e.g., `SysUserController`, `UserDTO`)
-- **Method naming**: camelCase (e.g., `getUserById`, `saveUser`)
-- **Constants**: UPPER_SNAKE_CASE (e.g., `COMMON_STATUS`, `DEFAULT_PAGE_SIZE`)
-- **Variables**: camelCase
-- **Lombok usage**: `@Data`, `@AllArgsConstructor`, `@Slf4j` for boilerplate reduction
-- **Line endings**: Use LF (Unix style)
-- **Indentation**: 4 spaces (no tabs)
-- **Max line length**: 120 characters
-
-## Architecture & Layer Patterns
+com.lxq.{module}.biz.controller/   # REST endpoints
+com.lxq.{module}.biz.service/       # Business logic
+com.lxq.{module}.biz.mapper/        # Data access
+com.lxq.{module}.biz.model/         # Entities
+com.lxq.{module}.biz.domain.dto/    # Input DTOs
+com.lxq.{module}.biz.domain.vo/     # Response VOs
 ```
-com.lxq.admin.biz.controller/    # REST endpoints (@RestController)
-com.lxq.system.biz.service/      # Business logic (extends IService<Entity>)
-com.lxq.system.biz.mapper/       # MyBatis data access (extends BaseMapper<Entity>)
-com.lxq.system.biz.model/        # Entity classes (@TableName)
-com.lxq.system.biz.domain.dto/   # Data transfer objects (@Data, @Schema)
-com.lxq.system.biz.domain.vo/    # View objects for API responses
+
+## Layer Patterns
+- **Controllers**: `@RestController`, `@AllArgsConstructor`, return `R<T>`
+- **Services**: Extend `IService<Entity>`, use `@SysLog`
+- **Mappers**: Extend `BaseMapper<Entity>`
+- **DTOs**: `@Data`, `@Schema`, extend Entity when appropriate
+- **VOs**: `@Data`, `@Schema` for API responses
+
+## Database & ORM
+```java
+@TableName("table_name")
+@TableId(type = IdType.AUTO)
+public class Entity { }  // del_flag for logical deletion
+
+// Query with type-safe wrapper
+Wrappers.<Entity>query().lambda()
+    .eq(Entity::getField, value)
+    .orderByDesc(Entity::getCreateTime);
 ```
+- **Dynamic Datasource**: `@DS("datasource_name")`
+- **Joins**: Use MyBatis-Plus-Join library
 
-**Layer Structure**: Controller → Service → Mapper → Domain
-- **Controllers**: Use `@RestController`, `@RequestMapping`, `@AllArgsConstructor`
-- **Services**: Extend `IService<Entity>` from MyBatis-Plus
-- **Mappers**: Extend `BaseMapper<Entity>` from MyBatis-Plus
-- **DTOs**: Separate from entities, extend entity when appropriate
-- **VOs**: For API responses, use Lombok `@Data`
-
-## API Documentation Standards
-- **Framework**: Swagger/OpenAPI 3 with Knife4j v4.3.0
-- **Required Annotations**:
-  ```java
-  @Tag(description = "module", name = "description")  // On class
-  @Operation(summary = "action", description = "details")  // On methods
-  @SecurityRequirement(name = HttpHeaders.AUTHORIZATION)  // For secured APIs
-  ```
-- **Parameter Documentation**: Use `@ParameterObject` for query parameters
-- **Response Documentation**: Use `@Schema(description = "...")` on DTOs/VOs
-- **Swagger UI**: Available at `/doc.html` when `swagger.enabled=true`
-- **Example**: See `SysUserController.java` for complete annotation patterns
-
-## Database & ORM Guidelines
-- **ORM**: MyBatis-Plus v3.5.15 with Spring Boot 3 integration
-- **Entity Conventions**:
-  ```java
-  @TableName("sys_user")  // Table mapping
-  @TableId(type = IdType.AUTO)  // Auto ID generation
-  // Logical deletion: del_flag (0=not deleted, 1=deleted)
-  ```
-- **Mapper Patterns**: Extend `BaseMapper<Entity>` from MyBatis-Plus
-- **Service Patterns**: Extend `IService<Entity>` from MyBatis-Plus
-- **Dynamic Datasource**: Use `@DS("datasource_name")` annotation
-- **Query Builder**: Use `Wrappers.<Entity>query().lambda()` for type-safe queries
-- **Join Queries**: Use MyBatis-Plus-Join for complex queries
-- **Logical Deletion**: Automatically handled (del_flag: 0/1)
-
-## Security & Authorization
-- **Framework**: Spring Authorization Server v1.1.2 with OAuth2
-- **Security Annotations**:
-  ```java
-  @PreAuthorize("@pms.hasPermission('permission_key')")  // Method security
-  @Inner  // Internal APIs (bypass OAuth2)
-  @EnableLoginResourceServer / @EnableAuthResourceServer  // Configuration
-  ```
-- **Security Utilities**: `SecurityUtils.getUser()` for current user info
-- **Permission Format**: `module_action` (e.g., `sys_user_add`, `sys_user_del`)
-- **Token Handling**: Automatic via resource server configuration
-- **Public Endpoints**: Configure in `security.oauth2.ignore.urls`
-
-## Testing Guidelines
-- **Framework**: Spring Boot Test (`spring-boot-starter-test`)
-- **Test Structure**: Create `src/test/java` following main package structure
-- **Test Naming**: `*Test.java` for test classes (e.g., `SysUserServiceTest`)
-- **Key Annotations**: `@SpringBootTest`, `@Test`, `@MockBean`, `@ExtendWith`
-- **Single Test Execution**: `mvn test -Dtest=ClassName#methodName`
-- **Integration Tests**: Use `@SpringBootTest` with test configuration
-- **Service Testing**: Mock dependencies with `@MockBean`
-- **Note**: No test directories currently exist - create following package structure
-
-## Error Handling & Logging
-- **Response Wrapper**: Always return `R<T>` from controllers
-  ```java
-  return R.ok(data);           // Success response
-  return R.failed("error");    // Error response
-  return R.failed(errorEnum);  // Error with enum
-  ```
-- **Logging**: Use `@SysLog("operation description")` on service methods
-- **Exception Handling**: Use global exception handler with `@RestControllerAdvice`
-- **Logging Framework**: SLF4J with `@Slf4j` annotation
-- **Audit Trail**: `@SysLog` automatically logs business operations
-
-## Configuration Management
-- **Main Config**: `application.yml` in admin module
-- **Environment Files**: `application-{profile}.yml` (dev/test/prod)
-- **Property Injection**: Use `@ConfigurationProperties`
-- **Profile Activation**: `spring.profiles.active=@profiles.active@`
-- **Database Config**: Dynamic datasource with primary/main setup
-- **Swagger Config**: Customizable via `swagger.*` properties
-- **Server Config**: Port 8080, context path `/v1/admin`
-
-## Common Patterns & Examples
-
-### Controller Method Template
+## API Documentation (Knife4j/Swagger)
 ```java
-@SysLog("操作描述")
-@PostMapping("/action")
-@PreAuthorize("@pms.hasPermission('module_action')")
-@Operation(summary = "操作", description = "详细描述")
-public R<ResultVO> action(@Valid @RequestBody RequestDTO request) {
-    return R.ok(service.action(request));
-}
+@Tag(description = "module", name = "description")        // Class
+@Operation(summary = "action", description = "details")   // Method
+@ParameterObject                                        // Query params
+@Schema(description = "...")                            // Fields
+@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)  // Secured APIs
 ```
 
-### DTO Template
+## Security
 ```java
-@Data
-@Schema(description = "对象描述")
-@EqualsAndHashCode(callSuper = true)
-public class EntityDTO extends Entity {
-    @Schema(description = "字段描述")
-    private String field;
-}
+@PreAuthorize("@pms.hasPermission('module_action')")  // Permission check
+@Inner  // Internal API (bypass OAuth2)
+SecurityUtils.getUser()  // Current user info
 ```
+- Permission format: `module_action` (e.g., `sys_user_add`)
 
-### Query Template
+## Error Handling
 ```java
-Entity entity = service.getOne(Wrappers.<Entity>query()
-    .lambda()
-    .eq(Entity::getField, value)
-    .orderByDesc(Entity::getCreateTime));
+return R.ok(data);                    // Success
+return R.failed("error message");     // Error
+return R.failed(ErrorEnum);           // Error with enum
+
+@Slf4j  // Logger
+@RestControllerAdvice  // Global exception handler
 ```
 
-## Key Requirements Summary
+## Key Requirements
 1. **ALWAYS** run `mvn spring-javaformat:apply` before commits
-2. **ALWAYS** return `R<T>` from controller methods
-3. **ALWAYS** use `@PreAuthorize` for security checks
+2. **ALWAYS** return `R<T>` from controllers
+3. **ALWAYS** use `@PreAuthorize` for security
 4. **ALWAYS** document APIs with Swagger annotations
 5. **FOLLOW** Controller → Service → Mapper pattern
 6. **USE** DTOs for input, VOs for output
-7. **APPLY** `@SysLog` for business operations
-8. **USE** MyBatis-Plus Wrappers for type-safe queries
+7. **USE** `@SysLog` for business operations
+
+## Configuration
+- Main config: `lxq-admin/src/main/resources/application.yml`
+- Profiles: `application-{dev|test|prod}.yml`
+- Server: Port 8080, context path `/v1/admin`

+ 17 - 31
lxq-admin/src/main/java/com/lxq/admin/biz/controller/ImageCodeController.java

@@ -1,10 +1,8 @@
 package com.lxq.admin.biz.controller;
 
-
 import cn.hutool.captcha.CaptchaUtil;
 import cn.hutool.captcha.LineCaptcha;
 import cn.hutool.captcha.generator.RandomGenerator;
-import cn.hutool.core.util.StrUtil;
 import com.lxq.common.core.constant.CacheConstants;
 import com.lxq.common.core.constant.SecurityConstants;
 import com.lxq.common.core.exception.GlobalCustomerException;
@@ -12,7 +10,6 @@ import com.lxq.common.security.annotation.Inner;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.RequiredArgsConstructor;
 import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.serializer.StringRedisSerializer;
 import org.springframework.http.MediaType;
 import org.springframework.util.FastByteArrayOutputStream;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -23,53 +20,42 @@ import org.springframework.web.bind.annotation.RestController;
 import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 
+
 /**
  * 图形验证码
  * @author lixueqiang
  */
-@RequiredArgsConstructor
-@RequestMapping("/code")
+
 @RestController
+@RequestMapping("/code")
+@RequiredArgsConstructor
 public class ImageCodeController {
 
-
     private final RedisTemplate<String, Object> redisTemplate;
 
-    private static final Integer DEFAULT_IMAGE_WIDTH = 100;
-
-    private static final Integer DEFAULT_IMAGE_HEIGHT = 40;
+    private static final int DEFAULT_IMAGE_WIDTH = 100;
+    private static final int DEFAULT_IMAGE_HEIGHT = 40;
+    private static final String CAPTCHA_CHARS = "0123456789";
+    private static final int CAPTCHA_LENGTH = 4;
 
-    /**
-     * 获取验证码
-     * @param randomStr 随机数
-     */
-    @Inner(value = false)
-    @GetMapping()
-    public void getImageCode(@RequestParam("randomStr") String randomStr, HttpServletResponse response) {
-
-        // 生成验证码
-        RandomGenerator randomGenerator = new RandomGenerator("0123456789", 4);
+    @Inner(false)
+    @GetMapping
+    public void getImageCode(@RequestParam String randomStr, HttpServletResponse response) {
+        RandomGenerator randomGenerator = new RandomGenerator(CAPTCHA_CHARS, CAPTCHA_LENGTH);
         LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT);
         lineCaptcha.setGenerator(randomGenerator);
 
-        redisTemplate.setKeySerializer(new StringRedisSerializer());
-        if(StrUtil.isNotBlank(randomStr)){
-            redisTemplate.opsForValue()
-                    .set(CacheConstants.DEFAULT_CODE_KEY + randomStr, lineCaptcha.getCode(), SecurityConstants.CODE_TIME,
-                            TimeUnit.SECONDS);
-        }
+        redisTemplate.opsForValue()
+                .set(CacheConstants.DEFAULT_CODE_KEY + randomStr, lineCaptcha.getCode(),
+                        SecurityConstants.CODE_TIME, TimeUnit.SECONDS);
+
         try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {
-            // 生成图片数据
+            response.setContentType(MediaType.IMAGE_PNG_VALUE);
             lineCaptcha.write(os);
-
-            // 写入响应流
             response.getOutputStream().write(os.toByteArray());
-            response.setContentType(MediaType.IMAGE_PNG_VALUE);
             response.getOutputStream().flush();
         } catch (IOException e) {
             throw new GlobalCustomerException("生成验证码失败");
         }
     }
-
-
 }