docs: 修复 AI 调用日志设计文档评审问题
- 补充 Request 校验注解和 @Schema - 修正 keyword 搜索的 MyBatis-Plus 嵌套逻辑 - 明确旧接口废弃策略 - 弹窗布局增加 userId/requestId - 展开行预览截断逻辑优化 - 补充性能量化阈值 - 增加 JsonViewer props 定义 - 明确时间筛选交互和非法 JSON 处理 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,7 @@ purpose: AI 调用日志详情查看功能设计文档
|
|||||||
**展开行交互:**
|
**展开行交互:**
|
||||||
- 点击行首展开/收起图标切换展开状态
|
- 点击行首展开/收起图标切换展开状态
|
||||||
- 展开区域显示入参和出参的前 200 字符预览(超长截断 + `...`)
|
- 展开区域显示入参和出参的前 200 字符预览(超长截断 + `...`)
|
||||||
|
- 截断逻辑确保不在 Unicode 转义序列中间截断(如 `"中"`),优先在空白字符处截断,保证预览可读性
|
||||||
- 提供「查看完整详情」按钮,点击打开详情弹窗
|
- 提供「查看完整详情」按钮,点击打开详情弹窗
|
||||||
|
|
||||||
### 详情弹窗
|
### 详情弹窗
|
||||||
@@ -55,6 +56,7 @@ purpose: AI 调用日志详情查看功能设计文档
|
|||||||
│ ┌────────────┬────────────┬────────────┬────────────┐ │
|
│ ┌────────────┬────────────┬────────────┬────────────┐ │
|
||||||
│ │ 调用时间 │ 场景 │ 服务商 │ 接口 │ │
|
│ │ 调用时间 │ 场景 │ 服务商 │ 接口 │ │
|
||||||
│ │ 状态 │ 首字耗时 │ 总耗时 │ 片段数 │ │
|
│ │ 状态 │ 首字耗时 │ 总耗时 │ 片段数 │ │
|
||||||
|
│ │ 用户 ID │ 请求 ID │ │ │ │
|
||||||
│ └────────────┴────────────┴────────────┴────────────┘ │
|
│ └────────────┴────────────┴────────────┴────────────┘ │
|
||||||
│ │
|
│ │
|
||||||
│ 入参 [📋 复制] │
|
│ 入参 [📋 复制] │
|
||||||
@@ -100,8 +102,19 @@ purpose: AI 调用日志详情查看功能设计文档
|
|||||||
|
|
||||||
### JSON 高亮方案
|
### JSON 高亮方案
|
||||||
|
|
||||||
不引入额外依赖,使用原生 `JSON.stringify(obj, null, 2)` + CSS 样式实现代码高亮:
|
不引入额外依赖,使用原生 `JSON.stringify(obj, null, 2)` + CSS 样式实现代码高亮。
|
||||||
|
|
||||||
|
**`JsonViewer.vue` Props 接口:**
|
||||||
|
```typescript
|
||||||
|
interface JsonViewerProps {
|
||||||
|
data: string | object // JSON 字符串或对象
|
||||||
|
title?: string // 标题,如"入参""出参"
|
||||||
|
showCopy?: boolean // 是否显示复制按钮,默认 true
|
||||||
|
maxHeight?: string // 最大高度,默认 "300px"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**配色方案:**
|
||||||
- 字符串值 → `#a5d6a7`(浅绿)
|
- 字符串值 → `#a5d6a7`(浅绿)
|
||||||
- 数字/布尔 → `#90caf9`(浅蓝)
|
- 数字/布尔 → `#90caf9`(浅蓝)
|
||||||
- key → `#ce93d8`(浅紫)
|
- key → `#ce93d8`(浅紫)
|
||||||
@@ -109,6 +122,8 @@ purpose: AI 调用日志详情查看功能设计文档
|
|||||||
|
|
||||||
背景使用深色主题(`#1e1e1e`),与当前管理后台暗色风格一致。
|
背景使用深色主题(`#1e1e1e`),与当前管理后台暗色风格一致。
|
||||||
|
|
||||||
|
**非法 JSON 处理:** 若 `inputText`/`outputText` 不是合法 JSON,直接展示原始文本(等宽字体、自动换行),不报错。
|
||||||
|
|
||||||
### 筛选栏参数
|
### 筛选栏参数
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
@@ -117,14 +132,16 @@ interface LogQueryParams {
|
|||||||
sceneCode?: string
|
sceneCode?: string
|
||||||
providerCode?: string
|
providerCode?: string
|
||||||
endpointCode?: string
|
endpointCode?: string
|
||||||
startTime?: string
|
startTime?: string // ISO 8601 格式,如 "2026-05-17T00:00:00"
|
||||||
endTime?: string
|
endTime?: string // ISO 8601 格式,如 "2026-05-24T23:59:59"
|
||||||
keyword?: string // 搜索入参/出参内容
|
keyword?: string // 搜索入参/出参内容
|
||||||
pageNum?: number
|
pageNum?: number
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**时间筛选交互:** 前端筛选栏提供快捷选项(近7天、近30天、自定义范围),由前端组件将快捷选项转换为 `startTime`/`endTime` 后传给后端。自定义范围时使用 Element Plus `el-date-picker` 选择起止时间。
|
||||||
|
|
||||||
### 展开行预览逻辑
|
### 展开行预览逻辑
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
@@ -137,7 +154,7 @@ function previewText(jsonStr: string, maxLen = 200): string {
|
|||||||
### API 变更
|
### API 变更
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 原接口
|
// 原接口(废弃,仅 AiRoutingList.vue 中使用,直接替换)
|
||||||
export function listAiCallLogs(limit = 50) { ... }
|
export function listAiCallLogs(limit = 50) { ... }
|
||||||
|
|
||||||
// 新接口
|
// 新接口
|
||||||
@@ -146,6 +163,8 @@ export function queryAiCallLogs(params: LogQueryParams) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**兼容性说明:** 经检查,`listAiCallLogs` 仅在 `AiRoutingList.vue` 的调用日志 Tab 中使用,无其他调用方。本次直接替换,不保留旧接口。
|
||||||
|
|
||||||
## 后端 API 设计
|
## 后端 API 设计
|
||||||
|
|
||||||
### 接口定义
|
### 接口定义
|
||||||
@@ -159,15 +178,38 @@ public Result<Page<AiCallLog>> queryCallLogs(@RequestBody @Valid AiCallLogQueryR
|
|||||||
### Request 对象
|
### Request 对象
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
@Schema(description = "AI 调用日志查询请求")
|
||||||
public class AiCallLogQueryRequest {
|
public class AiCallLogQueryRequest {
|
||||||
|
|
||||||
|
@Schema(description = "状态:running / success / failed")
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "场景编码")
|
||||||
private String sceneCode;
|
private String sceneCode;
|
||||||
|
|
||||||
|
@Schema(description = "服务商编码")
|
||||||
private String providerCode;
|
private String providerCode;
|
||||||
|
|
||||||
|
@Schema(description = "接口编码")
|
||||||
private String endpointCode;
|
private String endpointCode;
|
||||||
|
|
||||||
|
@Schema(description = "开始时间")
|
||||||
private LocalDateTime startTime;
|
private LocalDateTime startTime;
|
||||||
|
|
||||||
|
@Schema(description = "结束时间")
|
||||||
private LocalDateTime endTime;
|
private LocalDateTime endTime;
|
||||||
|
|
||||||
|
@Schema(description = "入参/出参关键词搜索")
|
||||||
|
@Size(max = 200, message = "关键词长度不能超过 200")
|
||||||
private String keyword;
|
private String keyword;
|
||||||
|
|
||||||
|
@Schema(description = "页码", example = "1")
|
||||||
|
@Min(value = 1, message = "页码必须大于 0")
|
||||||
private Integer pageNum = 1;
|
private Integer pageNum = 1;
|
||||||
|
|
||||||
|
@Schema(description = "每页条数", example = "20")
|
||||||
|
@Min(value = 1, message = "每页条数必须大于 0")
|
||||||
|
@Max(value = 100, message = "每页条数不能超过 100")
|
||||||
private Integer pageSize = 20;
|
private Integer pageSize = 20;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -190,9 +232,9 @@ public Page<AiCallLog> query(AiCallLogQueryRequest request) {
|
|||||||
.orderByDesc(AiCallLog::getCreateTime);
|
.orderByDesc(AiCallLog::getCreateTime);
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(request.getKeyword())) {
|
if (StringUtils.isNotBlank(request.getKeyword())) {
|
||||||
wrapper.and(w -> w.like(AiCallLog::getInputText, request.getKeyword())
|
wrapper.and(w -> w.nested(i -> i.like(AiCallLog::getInputText, request.getKeyword())
|
||||||
.or()
|
.or()
|
||||||
.like(AiCallLog::getOutputText, request.getKeyword()));
|
.like(AiCallLog::getOutputText, request.getKeyword())));
|
||||||
}
|
}
|
||||||
|
|
||||||
return page(page, wrapper);
|
return page(page, wrapper);
|
||||||
@@ -202,9 +244,10 @@ public Page<AiCallLog> query(AiCallLogQueryRequest request) {
|
|||||||
## 性能考虑
|
## 性能考虑
|
||||||
|
|
||||||
- `input_text` 和 `output_text` 可能存储大 JSON(聊天记录),LIKE 搜索在数据量极大时会变慢
|
- `input_text` 和 `output_text` 可能存储大 JSON(聊天记录),LIKE 搜索在数据量极大时会变慢
|
||||||
- 当前日志量不大,先使用 LIKE。后续如果数据量增长,可升级为:
|
- **量化阈值:** 当日志表数据量超过 10 万条,或 `keyword` 搜索响应时间超过 500ms 时,启动优化评估:
|
||||||
- MySQL 全文索引(FULLTEXT)
|
- 方案 1:为 `input_text`、`output_text` 增加 MySQL 全文索引(FULLTEXT)
|
||||||
- 或限制 keyword 搜索只查询近 N 天数据
|
- 方案 2:限制 keyword 搜索只查询近 30 天的数据(配合 `create_time` 索引)
|
||||||
|
- 当前日志量不大,先使用 LIKE。
|
||||||
|
|
||||||
## 数据模型
|
## 数据模型
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user