feat(登录安全): 实现 RSA 密码加密传输
后端: - 新增 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 <noreply@anthropic.com>
This commit is contained in:
parent
1038a70d92
commit
c935988188
@ -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` | 详情展示新增字段 |
|
||||
|
||||
6
reading-platform-frontend/package-lock.json
generated
6
reading-platform-frontend/package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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<PublicKeyResponse> {
|
||||
return http.get('/v1/auth/public-key');
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA 加密登录
|
||||
* @param params 加密后的登录参数
|
||||
*/
|
||||
export function loginEncrypted(params: EncryptedLoginParams): Promise<LoginResponse> {
|
||||
return http.post('/v1/auth/login/encrypted', {
|
||||
username: params.username,
|
||||
encryptedPassword: params.encryptedPassword,
|
||||
role: params.role,
|
||||
keyVersion: params.keyVersion,
|
||||
});
|
||||
}
|
||||
|
||||
@ -860,6 +860,8 @@ export interface OperationLog {
|
||||
oldValue: string | null;
|
||||
newValue: string | null;
|
||||
ipAddress: string | null;
|
||||
requestParams?: string; // 请求参数 JSON
|
||||
requestUri?: string; // 请求接口路径
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
37
reading-platform-frontend/src/utils/encryption.ts
Normal file
37
reading-platform-frontend/src/utils/encryption.ts
Normal file
@ -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;
|
||||
}
|
||||
@ -118,6 +118,12 @@
|
||||
<a-descriptions-item label="操作描述" :span="2">
|
||||
{{ selectedLog.description }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="请求接口" :span="2">
|
||||
{{ selectedLog.requestUri || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="请求参数" :span="2">
|
||||
<pre class="json-data">{{ formatJson(selectedLog.requestParams) }}</pre>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="变更前数据" :span="2">
|
||||
<pre class="json-data">{{ formatJson(selectedLog.oldValue) }}</pre>
|
||||
</a-descriptions-item>
|
||||
|
||||
@ -111,6 +111,7 @@ public class LogAspect {
|
||||
// 记录请求信息
|
||||
operationLog.setIpAddress(getIpAddress(request));
|
||||
operationLog.setUserAgent(request.getHeader("User-Agent"));
|
||||
operationLog.setRequestUri(request.getRequestURI());
|
||||
|
||||
// 记录请求参数
|
||||
if (logAnnotation.recordParams()) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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<PublicKeyResponse> 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<LoginResponse> 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 = "用户登录")
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
|
||||
@ -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 注解标记的操作会自动保存
|
||||
-- -----------------------------------------------------
|
||||
Loading…
Reference in New Issue
Block a user