# 操作日志功能优化实施计划 > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **目标**: 完善操作日志功能,修复已知问题,增强功能特性,提升用户体验和系统可维护性。 **架构**: 在现有注解驱动AOP架构基础上,修复H2数据库兼容性问题,添加集成测试和E2E测试,实现查询导出、统计分析、定时清理等增强功能。 **技术栈**: Java 21, Spring Boot 3.5.13, Spring WebFlux, R2DBC, H2 Database, Jackson, Playwright --- ## Phase 1: 短期优化(1-2周) ### Task 1: 修复H2数据库初始化问题 **问题分析**: - H2测试环境启动时报错:`bad SQL grammar [SELECT sys_user.username, sys_user.password, sys_user.email, sys_user.phone, sys_user.nickname, sys_user.role_id, sys_user.status...]` - 原因:实体类字段映射与H2 schema不匹配 **文件:** - 检查: `novalon-manage-api/manage-db/src/main/java/cn/novalon/manage/db/entity/SysUserEntity.java` - 检查: `novalon-manage-api/manage-app/src/main/resources/schema-h2.sql` - 检查: `novalon-manage-api/manage-app/src/main/resources/data-h2.sql` **Step 1: 分析实体类字段映射** 运行: ```bash cd novalon-manage-api grep -n "@Column" manage-db/src/main/java/cn/novalon/manage/db/entity/SysUserEntity.java ``` 预期: 查看所有字段映射 **Step 2: 对比H2 schema定义** 运行: ```bash cd novalon-manage-api/manage-app/src/main/resources head -30 schema-h2.sql ``` 预期: 查看H2表结构定义 **Step 3: 检查data-h2.sql中的测试数据** 运行: ```bash cd novalon-manage-api/manage-app/src/main/resources grep -A5 "INSERT INTO sys_user" data-h2.sql | head -20 ``` 预期: 查看测试数据插入语句 **Step 4: 分析问题根源** 根据错误信息和代码检查,确定以下可能的问题: 1. 实体类字段名与数据库列名不匹配 2. H2 schema中缺少某些字段 3. R2DBC映射配置问题 **Step 5: 修复方案选择** 根据分析结果,选择合适的修复方案: - 方案A: 修改实体类的@Column注解,使其与H2 schema匹配 - 方案B: 修改H2 schema,使其与实体类字段匹配 - 方案C: 添加R2DBC自定义映射配置 **Step 6: 实施修复** 根据选择的方案,修改相应文件。 **Step 7: 验证修复** 运行: ```bash cd novalon-manage-api ./mvnw spring-boot:run -pl manage-app -Dspring-boot.run.profiles=test ``` 等待服务启动,检查是否还有SQL错误。 **Step 8: 提交修复** ```bash git add <修改的文件> git commit -m "fix: resolve H2 database initialization issue - Fix entity field mapping mismatch - Update H2 schema to match entity definitions - Ensure test environment works correctly" ``` --- ### Task 2: 添加操作日志集成测试 **文件:** - 创建: `novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/audit/OperationLogIntegrationTest.java` **Step 1: 创建集成测试类** ```java package cn.novalon.manage.sys.audit; import cn.novalon.manage.sys.core.domain.OperationLog; import cn.novalon.manage.sys.core.service.IOperationLogService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.reactive.server.WebTestClient; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import java.time.Duration; import static org.junit.jupiter.api.Assertions.*; /** * 操作日志集成测试 * * @author 张翔 * @date 2026-04-03 */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("test") class OperationLogIntegrationTest { @Autowired private WebTestClient webTestClient; @Autowired private IOperationLogService logService; @Test @WithMockUser(username = "test_user", roles = {"admin"}) void testCreateUserOperation_ShouldLogOperation() { long initialCount = logService.count().block(Duration.ofSeconds(5)); String userJson = """ { "username": "test_integration_user", "password": "Test123!@#", "email": "test@example.com", "phone": "13900139000", "nickname": "集成测试用户" } """; webTestClient.post() .uri("/api/users") .contentType(MediaType.APPLICATION_JSON) .bodyValue(userJson) .exchange() .expectStatus().isCreated(); // 等待异步日志保存 StepVerifier.create(logService.count()) .expectNext(initialCount + 1) .verify(Duration.ofSeconds(5)); // 验证日志内容 StepVerifier.create(logService.findAll().last()) .assertNext(log -> { assertEquals("test_user", log.getUsername()); assertTrue(log.getOperation().contains("创建用户")); assertEquals("0", log.getStatus()); assertNotNull(log.getParams()); assertNotNull(log.getDuration()); }) .verify(Duration.ofSeconds(5)); } @Test @WithMockUser(username = "test_user", roles = {"admin"}) void testDeleteUserOperation_ShouldLogOperation() { // 先创建一个用户 String userJson = """ { "username": "test_delete_user", "password": "Test123!@#", "email": "delete@example.com", "phone": "13900139001", "nickname": "待删除用户" } """; Long userId = webTestClient.post() .uri("/api/users") .contentType(MediaType.APPLICATION_JSON) .bodyValue(userJson) .exchange() .expectStatus().isCreated() .expectBody(Long.class) .returnResult() .getResponseBody(); long initialCount = logService.count().block(Duration.ofSeconds(5)); // 删除用户 webTestClient.delete() .uri("/api/users/{id}", userId) .exchange() .expectStatus().isOk(); // 验证日志记录 StepVerifier.create(logService.count()) .expectNext(initialCount + 1) .verify(Duration.ofSeconds(5)); } @Test @WithMockUser(username = "test_user", roles = {"admin"}) void testFailedOperation_ShouldLogError() { // 尝试创建重复用户名(应该失败) String userJson = """ { "username": "admin", "password": "Test123!@#", "email": "duplicate@example.com", "phone": "13900139002", "nickname": "重复用户" } """; webTestClient.post() .uri("/api/users") .contentType(MediaType.APPLICATION_JSON) .bodyValue(userJson) .exchange() .expectStatus().is4xxClientError(); // 验证错误日志记录 StepVerifier.create(logService.findAll().last()) .assertNext(log -> { assertEquals("1", log.getStatus()); assertNotNull(log.getErrorMsg()); }) .verify(Duration.ofSeconds(5)); } } ``` **Step 2: 运行集成测试** 运行: ```bash cd novalon-manage-api ./mvnw test -Dtest=OperationLogIntegrationTest -pl manage-sys ``` 预期: 所有测试通过 **Step 3: 提交测试代码** ```bash git add novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/audit/OperationLogIntegrationTest.java git commit -m "test: add integration tests for operation log - Test successful operation logging - Test failed operation error logging - Verify log content and status" ``` --- ### Task 3: 添加操作日志E2E测试 **文件:** - 创建: `novalon-manage-web/e2e/operation-log.spec.ts` **Step 1: 创建E2E测试文件** ```typescript import { test, expect } from '@playwright/test'; import { LoginPage } from './pages/LoginPage'; import { DashboardPage } from './pages/DashboardPage'; import { UserManagementPage } from './pages/UserManagementPage'; test.describe('操作日志E2E测试', () => { let loginPage: LoginPage; let dashboardPage: DashboardPage; let userManagementPage: UserManagementPage; test.beforeEach(async ({ page }) => { loginPage = new LoginPage(page); dashboardPage = new DashboardPage(page); userManagementPage = new UserManagementPage(page); // 清理localStorage await page.goto('/'); await page.evaluate(() => localStorage.clear()); // 登录 await loginPage.goto(); await loginPage.login('e2e_test_user', 'admin123'); }); test('创建用户后Dashboard操作日志数量应增加', async ({ page }) => { // 获取初始操作日志数量 await dashboardPage.goto(); const initialCount = await dashboardPage.getOperationLogCount(); console.log('初始操作日志数量:', initialCount); // 创建用户 await dashboardPage.navigateToUserManagement(); await userManagementPage.clickCreateUser(); const timestamp = Date.now(); const userData = { username: `oplog_test_${timestamp}`, nickname: `操作日志测试${timestamp}`, email: `oplog_${timestamp}@example.com`, phone: '13800138000', password: 'Test123!@#', confirmPassword: 'Test123!@#', }; await userManagementPage.fillUserForm(userData); await userManagementPage.submitForm(); await expect(userManagementPage.successMessage).toBeVisible(); // 返回Dashboard,验证操作日志数量 await dashboardPage.goto(); await page.waitForTimeout(2000); // 等待异步日志保存 const newCount = await dashboardPage.getOperationLogCount(); console.log('新操作日志数量:', newCount); expect(newCount).toBeGreaterThan(initialCount); }); test('删除用户后Dashboard操作日志数量应增加', async ({ page }) => { // 先创建一个用户 await dashboardPage.navigateToUserManagement(); await userManagementPage.clickCreateUser(); const timestamp = Date.now(); const userData = { username: `delete_oplog_${timestamp}`, nickname: `待删除用户${timestamp}`, email: `delete_${timestamp}@example.com`, phone: '13800138001', password: 'Test123!@#', confirmPassword: 'Test123!@#', }; await userManagementPage.fillUserForm(userData); await userManagementPage.submitForm(); await expect(userManagementPage.successMessage).toBeVisible(); // 获取初始操作日志数量 await dashboardPage.goto(); const initialCount = await dashboardPage.getOperationLogCount(); // 删除用户 await dashboardPage.navigateToUserManagement(); await userManagementPage.search(userData.username); await page.waitForTimeout(1000); await userManagementPage.deleteUser(1); await userManagementPage.confirmDelete(); await expect(userManagementPage.successMessage).toBeVisible(); // 验证操作日志数量 await dashboardPage.goto(); await page.waitForTimeout(2000); const newCount = await dashboardPage.getOperationLogCount(); expect(newCount).toBeGreaterThan(initialCount); }); test('操作失败后应记录错误日志', async ({ page }) => { // 获取初始操作日志数量 await dashboardPage.goto(); const initialCount = await dashboardPage.getOperationLogCount(); // 尝试创建重复用户(应该失败) await dashboardPage.navigateToUserManagement(); await userManagementPage.clickCreateUser(); const userData = { username: 'admin', // 已存在的用户名 nickname: '重复用户', email: 'duplicate@example.com', phone: '13800138002', password: 'Test123!@#', confirmPassword: 'Test123!@#', }; await userManagementPage.fillUserForm(userData); await userManagementPage.submitForm(); // 应该看到错误消息 await expect(userManagementPage.errorMessage).toBeVisible(); // 验证操作日志数量(失败操作也应该记录) await dashboardPage.goto(); await page.waitForTimeout(2000); const newCount = await dashboardPage.getOperationLogCount(); expect(newCount).toBeGreaterThan(initialCount); }); }); ``` **Step 2: 更新DashboardPage添加getOperationLogCount方法** 在 `novalon-manage-web/e2e/pages/DashboardPage.ts` 中添加: ```typescript async getOperationLogCount(): Promise { const logCard = this.page.locator('.log-card .el-statistic__content'); const text = await logCard.textContent(); return parseInt(text || '0', 10); } ``` **Step 3: 运行E2E测试** 运行: ```bash cd novalon-manage-web npx playwright test e2e/operation-log.spec.ts --project=chromium ``` 预期: 所有测试通过 **Step 4: 提交测试代码** ```bash git add novalon-manage-web/e2e/operation-log.spec.ts novalon-manage-web/e2e/pages/DashboardPage.ts git commit -m "test: add E2E tests for operation log - Verify operation log count increases after operations - Test successful and failed operation logging - Add getOperationLogCount method to DashboardPage" ``` --- ### Task 4: 验证Dashboard操作日志显示 **文件:** - 检查: `novalon-manage-web/src/views/system/Dashboard.vue` **Step 1: 启动完整系统** 运行: ```bash # 终端1: 启动后端 cd novalon-manage-api && ./mvnw spring-boot:run -pl manage-app -Dspring-boot.run.profiles=test # 终端2: 启动前端 cd novalon-manage-web && pnpm dev ``` **Step 2: 手动测试Dashboard显示** 1. 打开浏览器访问 http://localhost:3002 2. 登录系统(用户名: e2e_test_user, 密码: admin123) 3. 查看Dashboard操作日志数量(初始值) 4. 执行用户管理操作(创建、更新、删除用户) 5. 返回Dashboard,查看操作日志数量是否增加 6. 检查操作日志显示是否正确 **Step 3: 检查API响应** 运行: ```bash TOKEN=$(curl -s -X POST http://localhost:8084/api/auth/login -H "Content-Type: application/json" -d '{"username":"e2e_test_user","password":"admin123"}' | grep -o '"token":"[^"]*' | cut -d'"' -f4) curl -X GET "http://localhost:8084/api/logs/operation/count" -H "Authorization: Bearer $TOKEN" ``` 预期: 返回大于0的数字 **Step 4: 检查操作日志列表** 运行: ```bash curl -X GET "http://localhost:8084/api/logs/operation" -H "Authorization: Bearer $TOKEN" | jq '.[0]' ``` 预期: 返回最新的操作日志记录 **Step 5: 记录测试结果** 创建测试报告文档,记录: - Dashboard显示是否正常 - 操作日志数量是否正确 - 操作日志内容是否完整 - 发现的问题和解决方案 **Step 6: 提交验证报告** ```bash git add test-suite/reports/dashboard_operation_log_verification.md git commit -m "docs: add Dashboard operation log verification report - Verify Dashboard displays operation log count correctly - Confirm operation log content is complete - Document test results and findings" ``` --- ## Phase 2: 中期优化(1-2个月) ### Task 5: 添加操作日志查询功能 **文件:** - 修改: `novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/handler/log/OperationLogHandler.java` - 创建: `novalon-manage-web/src/views/system/OperationLog.vue` **Step 1: 扩展后端查询接口** 在 `OperationLogHandler.java` 中添加: ```java @Operation(summary = "根据条件查询操作日志", description = "支持按用户名、操作类型、时间范围等条件查询") public Mono searchOperationLogs(ServerRequest request) { Optional username = request.queryParam("username"); Optional operation = request.queryParam("operation"); Optional startTime = request.queryParam("startTime"); Optional endTime = request.queryParam("endTime"); Optional status = request.queryParam("status"); // 构建查询条件 OperationLogQuery query = new OperationLogQuery(); username.ifPresent(query::setUsername); operation.ifPresent(query::setOperation); startTime.ifPresent(query::setStartTime); endTime.ifPresent(query::setEndTime); status.ifPresent(query::setStatus); return logService.search(query) .collectList() .flatMap(logs -> ServerResponse.ok().bodyValue(logs)); } ``` **Step 2: 在SystemRouter中添加路由** 在 `SystemRouter.java` 中添加: ```java .GET("/api/logs/operation/search", operationLogHandler::searchOperationLogs) ``` **Step 3: 创建前端查询页面** 创建 `OperationLog.vue`: ```vue ``` **Step 4: 添加路由配置** 在 `router/index.ts` 中添加: ```typescript { path: '/system/operation-log', name: 'OperationLog', component: () => import('@/views/system/OperationLog.vue'), meta: { title: '操作日志', icon: 'document' } } ``` **Step 5: 测试查询功能** 运行: ```bash # 启动服务 cd novalon-manage-api && ./mvnw spring-boot:run -pl manage-app -Dspring-boot.run.profiles=test cd novalon-manage-web && pnpm dev # 浏览器访问 http://localhost:3002/system/operation-log ``` **Step 6: 提交代码** ```bash git add novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/handler/log/OperationLogHandler.java git add novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/SystemRouter.java git add novalon-manage-web/src/views/system/OperationLog.vue git add novalon-manage-web/src/router/index.ts git commit -m "feat: add operation log search functionality - Add search API with multiple filter conditions - Create operation log query page - Support pagination and detail view" ``` --- ### Task 6: 添加操作日志导出功能 **文件:** - 修改: `novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/handler/log/OperationLogHandler.java` - 修改: `novalon-manage-web/src/views/system/OperationLog.vue` **Step 1: 添加后端导出接口** 在 `OperationLogHandler.java` 中添加: ```java @Operation(summary = "导出操作日志", description = "导出操作日志为Excel文件") public Mono exportOperationLogs(ServerRequest request) { // 获取查询条件 Optional username = request.queryParam("username"); Optional operation = request.queryParam("operation"); Optional startTime = request.queryParam("startTime"); Optional endTime = request.queryParam("endTime"); // 构建查询条件 OperationLogQuery query = new OperationLogQuery(); username.ifPresent(query::setUsername); operation.ifPresent(query::setOperation); startTime.ifPresent(query::setStartTime); endTime.ifPresent(query::setEndTime); return logService.search(query) .collectList() .flatMap(logs -> { // 生成Excel文件 byte[] excelData = generateExcel(logs); return ServerResponse.ok() .header("Content-Disposition", "attachment; filename=operation_logs.xlsx") .contentType(MediaType.APPLICATION_OCTET_STREAM) .bodyValue(excelData); }); } private byte[] generateExcel(List logs) { // 使用Apache POI或EasyExcel生成Excel // 实现细节省略 return new byte[0]; } ``` **Step 2: 在SystemRouter中添加路由** ```java .GET("/api/logs/operation/export", operationLogHandler::exportOperationLogs) ``` **Step 3: 在前端添加导出按钮** 在 `OperationLog.vue` 中添加: ```vue 查询 重置 导出 ``` ```typescript const handleExport = async () => { try { const params: any = { ...searchForm } if (searchForm.timeRange && searchForm.timeRange.length === 2) { params.startTime = searchForm.timeRange[0] params.endTime = searchForm.timeRange[1] } const response = await request.get('/logs/operation/export', { params, responseType: 'blob' }) const url = window.URL.createObjectURL(new Blob([response])) const link = document.createElement('a') link.href = url link.setAttribute('download', 'operation_logs.xlsx') document.body.appendChild(link) link.click() document.body.removeChild(link) } catch (error) { console.error('导出失败:', error) } } ``` **Step 4: 测试导出功能** 在浏览器中点击"导出"按钮,验证Excel文件是否正确下载。 **Step 5: 提交代码** ```bash git add novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/handler/log/OperationLogHandler.java git add novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/SystemRouter.java git add novalon-manage-web/src/views/system/OperationLog.vue git commit -m "feat: add operation log export functionality - Add export API endpoint - Generate Excel file with operation logs - Add export button in frontend" ``` --- ### Task 7: 实现操作日志统计分析 **文件:** - 创建: `novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/handler/stats/OperationLogStatsHandler.java` - 创建: `novalon-manage-web/src/views/system/OperationLogStats.vue` **Step 1: 创建统计分析接口** ```java package cn.novalon.manage.sys.handler.stats; import cn.novalon.manage.sys.core.service.IOperationLogService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; @Component @Tag(name = "操作日志统计", description = "操作日志统计分析") public class OperationLogStatsHandler { private final IOperationLogService logService; public OperationLogStatsHandler(IOperationLogService logService) { this.logService = logService; } @Operation(summary = "获取操作日志统计概览", description = "获取操作日志的统计数据") public Mono getStatsOverview(ServerRequest request) { Mono totalCount = logService.count(); Mono todayCount = logService.countToday(); Mono successCount = logService.countByStatus("0"); Mono failCount = logService.countByStatus("1"); return Mono.zip(totalCount, todayCount, successCount, failCount) .flatMap(tuple -> { Map stats = new HashMap<>(); stats.put("totalCount", tuple.getT1()); stats.put("todayCount", tuple.getT2()); stats.put("successCount", tuple.getT3()); stats.put("failCount", tuple.getT4()); stats.put("successRate", tuple.getT1() > 0 ? (double) tuple.getT3() / tuple.getT1() * 100 : 0); return ServerResponse.ok().bodyValue(stats); }); } @Operation(summary = "按操作类型统计", description = "统计各操作类型的数量") public Mono getStatsByOperation(ServerRequest request) { return logService.countByOperation() .collectList() .flatMap(stats -> ServerResponse.ok().bodyValue(stats)); } @Operation(summary = "按用户统计", description = "统计各用户的操作数量") public Mono getStatsByUser(ServerRequest request) { return logService.countByUsername() .collectList() .flatMap(stats -> ServerResponse.ok().bodyValue(stats)); } @Operation(summary = "按时间统计", description = "统计每日操作数量趋势") public Mono getStatsByTime(ServerRequest request) { Optional days = request.queryParam("days"); int dayCount = days.map(Integer::parseInt).orElse(7); LocalDateTime startTime = LocalDateTime.now().minusDays(dayCount); return logService.countByDate(startTime) .collectList() .flatMap(stats -> ServerResponse.ok().bodyValue(stats)); } } ``` **Step 2: 创建前端统计页面** 创建 `OperationLogStats.vue`: ```vue ``` **Step 3: 添加路由** 在 `SystemRouter.java` 中添加: ```java .GET("/api/logs/operation/stats/overview", operationLogStatsHandler::getStatsOverview) .GET("/api/logs/operation/stats/by-operation", operationLogStatsHandler::getStatsByOperation) .GET("/api/logs/operation/stats/by-user", operationLogStatsHandler::getStatsByUser) .GET("/api/logs/operation/stats/by-time", operationLogStatsHandler::getStatsByTime) ``` **Step 4: 测试统计功能** 访问 http://localhost:3002/system/operation-log-stats 查看统计图表。 **Step 5: 提交代码** ```bash git add novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/handler/stats/OperationLogStatsHandler.java git add novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/SystemRouter.java git add novalon-manage-web/src/views/system/OperationLogStats.vue git commit -m "feat: add operation log statistics and analysis - Add stats API endpoints - Create statistics dashboard with charts - Support operation type, user, and time analysis" ``` --- ### Task 8: 添加操作日志定时清理任务 **文件:** - 创建: `novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/scheduler/OperationLogCleanupScheduler.java` **Step 1: 创建定时清理任务** ```java package cn.novalon.manage.sys.scheduler; import cn.novalon.manage.sys.core.service.IOperationLogService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.time.LocalDateTime; /** * 操作日志定时清理任务 * * @author 张翔 * @date 2026-04-03 */ @Component public class OperationLogCleanupScheduler { private static final Logger logger = LoggerFactory.getLogger(OperationLogCleanupScheduler.class); private final IOperationLogService logService; public OperationLogCleanupScheduler(IOperationLogService logService) { this.logService = logService; } /** * 每天凌晨2点清理3个月前的操作日志 */ @Scheduled(cron = "0 0 2 * * ?") public void cleanupOldLogs() { logger.info("开始清理操作日志..."); LocalDateTime cutoffDate = LocalDateTime.now().minusMonths(3); logService.deleteByCreatedAtBefore(cutoffDate) .doOnSuccess(count -> logger.info("操作日志清理完成,删除 {} 条记录", count)) .doOnError(error -> logger.error("操作日志清理失败: {}", error.getMessage(), error)) .subscribe(); } } ``` **Step 2: 在IOperationLogService中添加删除方法** ```java Mono deleteByCreatedAtBefore(LocalDateTime cutoffDate); ``` **Step 3: 在OperationLogService中实现删除方法** ```java @Override public Mono deleteByCreatedAtBefore(LocalDateTime cutoffDate) { return logRepository.deleteByCreatedAtBefore(cutoffDate); } ``` **Step 4: 在IOperationLogRepository中添加删除方法** ```java Mono deleteByCreatedAtBefore(LocalDateTime cutoffDate); ``` **Step 5: 启用定时任务** 在 `ManageApplication.java` 中添加: ```java @EnableScheduling @SpringBootApplication public class ManageApplication { public static void main(String[] args) { SpringApplication.run(ManageApplication.class, args); } } ``` **Step 6: 测试定时任务** 可以手动触发测试: ```java @Test void testCleanupScheduler() { cleanupScheduler.cleanupOldLogs(); Thread.sleep(5000); // 等待异步操作完成 } ``` **Step 7: 提交代码** ```bash git add novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/scheduler/OperationLogCleanupScheduler.java git add novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/IOperationLogService.java git add novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/service/impl/OperationLogService.java git add novalon-manage-api/manage-db/src/main/java/cn/novalon/manage/db/repository/IOperationLogRepository.java git add novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/ManageApplication.java git commit -m "feat: add operation log cleanup scheduler - Add scheduled task to clean up old logs - Keep logs for last 3 months - Run cleanup at 2 AM daily" ``` --- ## 完成标准 ### Phase 1 完成标准 - ✅ H2数据库初始化问题已修复 - ✅ 集成测试全部通过 - ✅ E2E测试全部通过 - ✅ Dashboard操作日志显示正常 - ✅ 所有代码已提交到Git ### Phase 2 完成标准 - ✅ 操作日志查询功能可用 - ✅ 操作日志导出功能可用 - ✅ 操作日志统计分析功能可用 - ✅ 定时清理任务正常运行 - ✅ 所有测试通过 - ✅ 文档更新完成 - ✅ 所有代码已提交到Git --- ## 预估时间 ### Phase 1: 短期优化 - Task 1: 修复H2数据库问题 - 2小时 - Task 2: 添加集成测试 - 3小时 - Task 3: 添加E2E测试 - 2小时 - Task 4: 验证Dashboard显示 - 1小时 - **总计**: 约8小时(1-2个工作日) ### Phase 2: 中期优化 - Task 5: 添加查询功能 - 4小时 - Task 6: 添加导出功能 - 3小时 - Task 7: 实现统计分析 - 5小时 - Task 8: 添加定时清理 - 2小时 - **总计**: 约14小时(2-3个工作日) **总预估时间**: 约22小时(3-5个工作日)