From c9359881881c23f26b09d238223205140153b7e8 Mon Sep 17 00:00:00 2001 From: En Date: Tue, 24 Mar 2026 18:06:19 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=99=BB=E5=BD=95=E5=AE=89=E5=85=A8):=20?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20RSA=20=E5=AF=86=E7=A0=81=E5=8A=A0=E5=AF=86?= =?UTF-8?q?=E4=BC=A0=E8=BE=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端: - 新增 RsaEncryptionUtil 工具类,支持 RSA 2048 位加解密 - 新增 RsaKeyRotationTask 定时任务,每月 1 日凌晨 2 点自动更换密钥 - 新增 EncryptedLoginRequest 和 PublicKeyResponse DTO - AuthController 添加 /public-key 和 /login/encrypted 接口 前端: - 添加 jsencrypt 依赖用于 RSA 加密 - 新增 encryption.ts 工具函数 - auth.ts 添加 getPublicKey 和 loginEncrypted API - user.ts 修改 login 函数使用 RSA 加密流程 feat(操作日志): 添加请求参数和请求接口字段 - 数据库迁移 V50 添加 request_uri 字段 - LogAspect 记录请求 URI - OperationLogResponse 新增 requestParams 和 requestUri 字段 - 前端 OperationLogView 详情弹窗展示新字段 Co-Authored-By: Claude Opus 4.6 --- docs/dev-logs/2026-03-24.md | 60 ++++++++ reading-platform-frontend/package-lock.json | 6 + reading-platform-frontend/package.json | 1 + reading-platform-frontend/src/api/auth.ts | 23 +++ reading-platform-frontend/src/api/school.ts | 2 + reading-platform-frontend/src/stores/user.ts | 20 ++- .../src/utils/encryption.ts | 37 +++++ .../school/settings/OperationLogView.vue | 6 + .../platform/common/aspect/LogAspect.java | 1 + .../common/task/RsaKeyRotationTask.java | 36 +++++ .../common/util/RsaEncryptionUtil.java | 132 ++++++++++++++++++ .../platform/controller/AuthController.java | 34 +++++ .../school/SchoolOperationLogController.java | 2 + .../dto/request/EncryptedLoginRequest.java | 29 ++++ .../dto/response/OperationLogResponse.java | 6 + .../dto/response/PublicKeyResponse.java | 18 +++ .../reading/platform/entity/OperationLog.java | 5 +- .../V50__add_request_uri_to_operation_log.sql | 19 +++ 18 files changed, 434 insertions(+), 3 deletions(-) create mode 100644 reading-platform-frontend/src/utils/encryption.ts create mode 100644 reading-platform-java/src/main/java/com/reading/platform/common/task/RsaKeyRotationTask.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/common/util/RsaEncryptionUtil.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/request/EncryptedLoginRequest.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/response/PublicKeyResponse.java create mode 100644 reading-platform-java/src/main/resources/db/migration/V50__add_request_uri_to_operation_log.sql diff --git a/docs/dev-logs/2026-03-24.md b/docs/dev-logs/2026-03-24.md index fc3f8ae..18bd150 100644 --- a/docs/dev-logs/2026-03-24.md +++ b/docs/dev-logs/2026-03-24.md @@ -61,3 +61,63 @@ | `AdminTenantController.java` | 修改详情查询方法 | | `admin.ts` | 扩展类型定义 | | `TenantListView.vue` | 更新详情展示 | + +--- + +## 工作内容 - 学校端操作日志请求参数与接口展示 + +### 背景 +在学校端操作日志页面,需要记录并返回以下三个信息: +1. **操作人** - 谁执行的操作(已有 `userId`、`userRole`) +2. **请求参数** - 操作时传入的参数(`requestParams`) +3. **请求接口** - 执行的具体 API 接口路径(`requestUri`) + +当前后端已通过 `LogAspect` 自动记录这些信息到数据库,但未返回给前端展示。 + +### 修改内容 + +#### 1. 数据库迁移 +**文件**: `V50__add_request_uri_to_operation_log.sql` +- 添加 `request_uri` 字段 - VARCHAR(500),记录请求接口路径 + +#### 2. 后端实体修改 +**文件**: `OperationLog.java` +- 新增 `requestUri` 字段 + +#### 3. 后端切面修改 +**文件**: `LogAspect.java` +- 在 `before()` 方法中记录 `requestURI` + +#### 4. 后端 Response DTO 修改 +**文件**: `OperationLogResponse.java` +- 新增 `requestParams` 字段 - 请求参数 JSON +- 新增 `requestUri` 字段 - 请求接口路径 + +#### 5. 后端 Controller 修改 +**文件**: `SchoolOperationLogController.java` +- 在 `convertToResponse()` 方法中添加字段映射 + +#### 6. 前端类型定义更新 +**文件**: `school.ts` +- `OperationLog` 接口添加 `requestParams` 和 `requestUri` 字段 + +#### 7. 前端页面修改 +**文件**: `OperationLogView.vue` +- 详情弹窗新增"请求接口"展示项 +- 详情弹窗新增"请求参数"展示项(JSON 格式化) + +### 验证步骤 +1. ✅ 后端编译成功 +2. ⏳ 数据库迁移待执行 +3. ⏳ 启动服务测试 + +### 文件清单 +| 文件 | 修改内容 | +|------|---------| +| `V50__add_request_uri_to_operation_log.sql` | 新增数据库迁移 | +| `OperationLog.java` | 新增 `requestUri` 字段 | +| `LogAspect.java` | 记录 `requestURI` | +| `OperationLogResponse.java` | 新增返回字段 | +| `SchoolOperationLogController.java` | 字段映射 | +| `school.ts` | 类型定义更新 | +| `OperationLogView.vue` | 详情展示新增字段 | diff --git a/reading-platform-frontend/package-lock.json b/reading-platform-frontend/package-lock.json index f72cf12..151ed5f 100644 --- a/reading-platform-frontend/package-lock.json +++ b/reading-platform-frontend/package-lock.json @@ -18,6 +18,7 @@ "axios": "^1.6.7", "dayjs": "^1.11.10", "echarts": "^6.0.0", + "jsencrypt": "^3.3.2", "lodash-es": "^4.17.21", "lucide-vue-next": "^0.575.0", "pdfjs-dist": "^3.11.174", @@ -4463,6 +4464,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsencrypt": { + "version": "3.5.4", + "resolved": "https://registry.npmmirror.com/jsencrypt/-/jsencrypt-3.5.4.tgz", + "integrity": "sha512-kNjfYEMNASxrDGsmcSQh/rUTmcoRfSUkxnAz+MMywM8jtGu+fFEZ3nJjHM58zscVnwR0fYmG9sGkTDjqUdpiwA==" + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", diff --git a/reading-platform-frontend/package.json b/reading-platform-frontend/package.json index be33c2e..47a466f 100644 --- a/reading-platform-frontend/package.json +++ b/reading-platform-frontend/package.json @@ -26,6 +26,7 @@ "axios": "^1.6.7", "dayjs": "^1.11.10", "echarts": "^6.0.0", + "jsencrypt": "^3.3.2", "lodash-es": "^4.17.21", "lucide-vue-next": "^0.575.0", "pdfjs-dist": "^3.11.174", diff --git a/reading-platform-frontend/src/api/auth.ts b/reading-platform-frontend/src/api/auth.ts index 6dd37f8..dd5858a 100644 --- a/reading-platform-frontend/src/api/auth.ts +++ b/reading-platform-frontend/src/api/auth.ts @@ -1,4 +1,5 @@ import { http } from './index'; +import type { EncryptedLoginParams, PublicKeyResponse } from '@/utils/encryption'; export interface LoginParams { account: string; @@ -78,3 +79,25 @@ export function changePassword(oldPassword: string, newPassword: string): Promis params: { oldPassword, newPassword }, }); } + +// ========== RSA 加密登录相关 API ========== + +/** + * 获取 RSA 公钥 + */ +export function getPublicKey(): Promise { + return http.get('/v1/auth/public-key'); +} + +/** + * RSA 加密登录 + * @param params 加密后的登录参数 + */ +export function loginEncrypted(params: EncryptedLoginParams): Promise { + return http.post('/v1/auth/login/encrypted', { + username: params.username, + encryptedPassword: params.encryptedPassword, + role: params.role, + keyVersion: params.keyVersion, + }); +} diff --git a/reading-platform-frontend/src/api/school.ts b/reading-platform-frontend/src/api/school.ts index 04bc856..9e86da4 100644 --- a/reading-platform-frontend/src/api/school.ts +++ b/reading-platform-frontend/src/api/school.ts @@ -860,6 +860,8 @@ export interface OperationLog { oldValue: string | null; newValue: string | null; ipAddress: string | null; + requestParams?: string; // 请求参数 JSON + requestUri?: string; // 请求接口路径 createdAt: string; } diff --git a/reading-platform-frontend/src/stores/user.ts b/reading-platform-frontend/src/stores/user.ts index 0935868..783d941 100644 --- a/reading-platform-frontend/src/stores/user.ts +++ b/reading-platform-frontend/src/stores/user.ts @@ -3,6 +3,7 @@ import { ref, computed } from 'vue'; import { message } from 'ant-design-vue'; import { router } from '@/router'; import * as authApi from '@/api/auth'; +import { rsaEncrypt } from '@/utils/encryption'; export interface User { id: number; @@ -26,10 +27,25 @@ export const useUserStore = defineStore('user', () => { const isLoggedIn = computed(() => !!token.value); const userRole = computed(() => user.value?.role || null); - // 登录 + // 登录(使用 RSA 加密) async function login(account: string, password: string, role: string) { try { - const data = await authApi.login({ account, password, role }); + // 1. 获取 RSA 公钥 + const { publicKey, keyVersion } = await authApi.getPublicKey(); + + // 2. 使用 RSA 公钥加密密码 + const encryptedPassword = rsaEncrypt(password, publicKey); + if (!encryptedPassword) { + throw new Error('密码加密失败'); + } + + // 3. 使用加密后的密码登录 + const data = await authApi.loginEncrypted({ + username: account, + encryptedPassword, + role, + keyVersion, + }); // 后端返回格式: { token, userId, username, name, role, tenantId } token.value = data.token; diff --git a/reading-platform-frontend/src/utils/encryption.ts b/reading-platform-frontend/src/utils/encryption.ts new file mode 100644 index 0000000..8155701 --- /dev/null +++ b/reading-platform-frontend/src/utils/encryption.ts @@ -0,0 +1,37 @@ +import { JSEncrypt } from 'jsencrypt'; + +/** + * RSA 加密工具函数 + * 使用 jsencrypt 库进行 RSA 加密 + */ + +/** + * 使用 RSA 公钥加密数据 + * @param data 要加密的明文数据 + * @param publicKey RSA 公钥(Base64 编码的 PEM 格式) + * @returns 加密后的密文(Base64 编码) + */ +export function rsaEncrypt(data: string, publicKey: string): string { + const encrypt = new JSEncrypt(); + encrypt.setPublicKey(publicKey); + const result = encrypt.encrypt(data); + return result || ''; +} + +/** + * RSA 公钥响应 + */ +export interface PublicKeyResponse { + publicKey: string; + keyVersion: number; +} + +/** + * 加密登录参数 + */ +export interface EncryptedLoginParams { + username: string; + encryptedPassword: string; + role: string; + keyVersion?: number; +} diff --git a/reading-platform-frontend/src/views/school/settings/OperationLogView.vue b/reading-platform-frontend/src/views/school/settings/OperationLogView.vue index d6fae76..a69a840 100644 --- a/reading-platform-frontend/src/views/school/settings/OperationLogView.vue +++ b/reading-platform-frontend/src/views/school/settings/OperationLogView.vue @@ -118,6 +118,12 @@ {{ selectedLog.description }} + + {{ selectedLog.requestUri || '-' }} + + +
{{ formatJson(selectedLog.requestParams) }}
+
{{ formatJson(selectedLog.oldValue) }}
diff --git a/reading-platform-java/src/main/java/com/reading/platform/common/aspect/LogAspect.java b/reading-platform-java/src/main/java/com/reading/platform/common/aspect/LogAspect.java index ee55e39..b5f9665 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/common/aspect/LogAspect.java +++ b/reading-platform-java/src/main/java/com/reading/platform/common/aspect/LogAspect.java @@ -111,6 +111,7 @@ public class LogAspect { // 记录请求信息 operationLog.setIpAddress(getIpAddress(request)); operationLog.setUserAgent(request.getHeader("User-Agent")); + operationLog.setRequestUri(request.getRequestURI()); // 记录请求参数 if (logAnnotation.recordParams()) { diff --git a/reading-platform-java/src/main/java/com/reading/platform/common/task/RsaKeyRotationTask.java b/reading-platform-java/src/main/java/com/reading/platform/common/task/RsaKeyRotationTask.java new file mode 100644 index 0000000..90d43bb --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/common/task/RsaKeyRotationTask.java @@ -0,0 +1,36 @@ +package com.reading.platform.common.task; + +import com.reading.platform.common.util.RsaEncryptionUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + * RSA 密钥定时轮换任务 + * 每月自动更换 RSA 密钥对,增强安全性 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class RsaKeyRotationTask { + + private final RsaEncryptionUtil rsaEncryptionUtil; + + /** + * 每月 1 日凌晨 2 点自动更换 RSA 密钥 + * cron 表达式: 秒 分 时 日 月 周 + */ + @Scheduled(cron = "0 0 2 1 * ?") + public void rotateRsaKey() { + try { + log.info("开始执行 RSA 密钥定时更换任务"); + long oldVersion = rsaEncryptionUtil.getKeyVersion(); + rsaEncryptionUtil.generateNewKeyPair(); + long newVersion = rsaEncryptionUtil.getKeyVersion(); + log.info("RSA 密钥定时更换任务执行完成,版本: {} -> {}", oldVersion, newVersion); + } catch (Exception e) { + log.error("RSA 密钥定时更换任务执行失败", e); + } + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/common/util/RsaEncryptionUtil.java b/reading-platform-java/src/main/java/com/reading/platform/common/util/RsaEncryptionUtil.java new file mode 100644 index 0000000..4821eb0 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/common/util/RsaEncryptionUtil.java @@ -0,0 +1,132 @@ +package com.reading.platform.common.util; + +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.crypto.Cipher; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Base64; +import java.util.concurrent.atomic.AtomicLong; + +/** + * RSA 加密工具类 + * 支持密钥对生成、加密解密、密钥版本管理 + */ +@Slf4j +@Component +public class RsaEncryptionUtil { + + private static final String ALGORITHM = "RSA"; + private static final int KEY_SIZE = 2048; + + private volatile KeyPair keyPair; + private volatile String currentPublicKey; + private final AtomicLong keyVersion = new AtomicLong(0); + + /** + * 初始化时生成密钥对 + */ + @PostConstruct + public void init() throws Exception { + generateNewKeyPair(); + log.info("RSA 加密工具初始化完成,当前密钥版本: {}", keyVersion.get()); + } + + /** + * 生成新的 RSA 密钥对 + * 每月定时执行,也可手动调用 + */ + public synchronized void generateNewKeyPair() throws Exception { + try { + KeyPairGenerator generator = KeyPairGenerator.getInstance(ALGORITHM); + generator.initialize(KEY_SIZE); + this.keyPair = generator.generateKeyPair(); + this.currentPublicKey = Base64.getEncoder().encodeToString( + keyPair.getPublic().getEncoded() + ); + keyVersion.incrementAndGet(); + log.info("RSA 密钥对已更换,新版本号: {},密钥长度: {} 位", keyVersion.get(), KEY_SIZE); + } catch (Exception e) { + log.error("生成 RSA 密钥对失败", e); + throw e; + } + } + + /** + * 获取当前公钥(Base64 编码) + */ + public String getPublicKey() { + return currentPublicKey; + } + + /** + * 获取当前密钥版本号 + */ + public long getKeyVersion() { + return keyVersion.get(); + } + + /** + * 使用私钥解密数据 + * + * @param encryptedData Base64 编码的加密数据 + * @return 解密后的明文 + */ + public String decrypt(String encryptedData) throws Exception { + if (encryptedData == null || encryptedData.isEmpty()) { + throw new IllegalArgumentException("加密数据不能为空"); + } + + try { + Cipher cipher = Cipher.getInstance(ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); + byte[] decrypted = cipher.doFinal( + Base64.getDecoder().decode(encryptedData) + ); + return new String(decrypted, StandardCharsets.UTF_8); + } catch (Exception e) { + log.error("RSA 解密失败", e); + throw new Exception("密码解密失败,请重试", e); + } + } + + /** + * 使用公钥加密数据(主要用于测试) + * + * @param plainText 明文 + * @param publicKeyBase64 Base64 编码的公钥 + * @return Base64 编码的密文 + */ + public String encrypt(String plainText, String publicKeyBase64) throws Exception { + if (plainText == null || plainText.isEmpty()) { + throw new IllegalArgumentException("明文不能为空"); + } + + try { + byte[] keyBytes = Base64.getDecoder().decode(publicKeyBase64); + java.security.spec.X509EncodedKeySpec spec = new java.security.spec.X509EncodedKeySpec(keyBytes); + java.security.KeyFactory keyFactory = java.security.KeyFactory.getInstance(ALGORITHM); + PublicKey publicKey = keyFactory.generatePublic(spec); + + Cipher cipher = Cipher.getInstance(ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(encrypted); + } catch (Exception e) { + log.error("RSA 加密失败", e); + throw new Exception("密码加密失败", e); + } + } + + /** + * 使用当前公钥加密数据(用于测试) + */ + public String encryptWithCurrentKey(String plainText) throws Exception { + return encrypt(plainText, currentPublicKey); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/AuthController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/AuthController.java index e3c7df1..b113f8a 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/controller/AuthController.java +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/AuthController.java @@ -8,9 +8,12 @@ import com.reading.platform.common.mapper.ParentMapper; import com.reading.platform.common.mapper.TenantMapper; import com.reading.platform.common.mapper.TeacherMapper; import com.reading.platform.common.response.Result; +import com.reading.platform.common.util.RsaEncryptionUtil; +import com.reading.platform.dto.request.EncryptedLoginRequest; import com.reading.platform.dto.request.LoginRequest; import com.reading.platform.dto.request.UpdateProfileRequest; import com.reading.platform.dto.response.LoginResponse; +import com.reading.platform.dto.response.PublicKeyResponse; import com.reading.platform.dto.response.TokenResponse; import com.reading.platform.dto.response.UpdateProfileResponse; import com.reading.platform.dto.response.UserInfoResponse; @@ -38,6 +41,37 @@ public class AuthController { private final TeacherMapper teacherMapper; private final ParentMapper parentMapper; private final AdminUserMapper adminUserMapper; + private final RsaEncryptionUtil rsaEncryptionUtil; + + @Operation(summary = "获取 RSA 公钥") + @GetMapping("/public-key") + public Result getPublicKey() { + PublicKeyResponse response = new PublicKeyResponse(); + response.setPublicKey(rsaEncryptionUtil.getPublicKey()); + response.setKeyVersion(rsaEncryptionUtil.getKeyVersion()); + return Result.success(response); + } + + @Operation(summary = "加密登录(RSA)") + @Log(module = LogModule.AUTH, type = LogOperationType.OTHER, description = "用户加密登录") + @PostMapping("/login/encrypted") + public Result loginEncrypted(@Valid @RequestBody EncryptedLoginRequest request) { + // 解密密码 + String decryptedPassword; + try { + decryptedPassword = rsaEncryptionUtil.decrypt(request.getEncryptedPassword()); + } catch (Exception e) { + return Result.error("密码解密失败,请重试"); + } + + // 构造普通登录请求 + LoginRequest loginRequest = new LoginRequest(); + loginRequest.setUsername(request.getUsername()); + loginRequest.setPassword(decryptedPassword); + loginRequest.setRole(request.getRole()); + + return Result.success(authService.login(loginRequest)); + } @Operation(summary = "用户登录") @Log(module = LogModule.AUTH, type = LogOperationType.OTHER, description = "用户登录") diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolOperationLogController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolOperationLogController.java index ad0eaed..051cf62 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolOperationLogController.java +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolOperationLogController.java @@ -108,6 +108,8 @@ public class SchoolOperationLogController { .details(log.getDetails()) .ipAddress(log.getIpAddress()) .userAgent(log.getUserAgent()) + .requestParams(log.getRequestParams()) + .requestUri(log.getRequestUri()) .createdAt(log.getCreatedAt()) .build(); } diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/request/EncryptedLoginRequest.java b/reading-platform-java/src/main/java/com/reading/platform/dto/request/EncryptedLoginRequest.java new file mode 100644 index 0000000..656d7e1 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/request/EncryptedLoginRequest.java @@ -0,0 +1,29 @@ +package com.reading.platform.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 加密登录请求 DTO + * 用于 RSA 加密传输的登录请求 + */ +@Data +@Schema(description = "加密登录请求") +public class EncryptedLoginRequest { + + @NotBlank(message = "账号不能为空") + @Schema(description = "登录账号(明文)") + private String username; + + @NotBlank(message = "密码不能为空") + @Schema(description = "RSA 加密后的密码(Base64 编码)") + private String encryptedPassword; + + @NotBlank(message = "角色不能为空") + @Schema(description = "登录角色") + private String role; + + @Schema(description = "密钥版本号(可选,用于兼容性)") + private Long keyVersion; +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/response/OperationLogResponse.java b/reading-platform-java/src/main/java/com/reading/platform/dto/response/OperationLogResponse.java index c007eab..e994fbb 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/dto/response/OperationLogResponse.java +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/response/OperationLogResponse.java @@ -48,6 +48,12 @@ public class OperationLogResponse { @Schema(description = "用户代理") private String userAgent; + @Schema(description = "请求参数(JSON 格式)") + private String requestParams; + + @Schema(description = "请求接口路径") + private String requestUri; + @Schema(description = "创建时间") private LocalDateTime createdAt; } diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/response/PublicKeyResponse.java b/reading-platform-java/src/main/java/com/reading/platform/dto/response/PublicKeyResponse.java new file mode 100644 index 0000000..113bf15 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/response/PublicKeyResponse.java @@ -0,0 +1,18 @@ +package com.reading.platform.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * RSA 公钥响应 + */ +@Data +@Schema(description = "RSA 公钥响应") +public class PublicKeyResponse { + + @Schema(description = "RSA 公钥(Base64 编码)") + private String publicKey; + + @Schema(description = "密钥版本号") + private Long keyVersion; +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/entity/OperationLog.java b/reading-platform-java/src/main/java/com/reading/platform/entity/OperationLog.java index c01dc74..0be319a 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/entity/OperationLog.java +++ b/reading-platform-java/src/main/java/com/reading/platform/entity/OperationLog.java @@ -44,7 +44,10 @@ public class OperationLog extends BaseEntity { @Schema(description = "用户代理") private String userAgent; - @Schema(description = "请求参数(JSON 格式,不返回给前端)") + @Schema(description = "请求参数(JSON 格式)") private String requestParams; + @Schema(description = "请求接口路径") + private String requestUri; + } diff --git a/reading-platform-java/src/main/resources/db/migration/V50__add_request_uri_to_operation_log.sql b/reading-platform-java/src/main/resources/db/migration/V50__add_request_uri_to_operation_log.sql new file mode 100644 index 0000000..214feff --- /dev/null +++ b/reading-platform-java/src/main/resources/db/migration/V50__add_request_uri_to_operation_log.sql @@ -0,0 +1,19 @@ +-- ===================================================== +-- 操作日志表添加请求接口字段 +-- 版本:V49 +-- 创建时间:2026-03-24 +-- 描述:在 operation_log 表中添加 request_uri 字段,用于记录请求接口路径 +-- ===================================================== + +-- ----------------------------------------------------- +-- 1. 添加 request_uri 字段 +-- ----------------------------------------------------- +ALTER TABLE `operation_log` + ADD COLUMN `request_uri` VARCHAR(500) COMMENT '请求接口路径' AFTER `request_params`; + +-- ----------------------------------------------------- +-- 说明: +-- - request_uri 字段用于存储请求的接口路径(如:/api/v1/school/teachers) +-- - 该字段与 request_params 配合使用,完整记录操作请求信息 +-- - 由 LogAspect 自动记录,通过 @Log 注解标记的操作会自动保存 +-- -----------------------------------------------------