docs: 修复第三轮 spec review 问题
- Service 返回 PageResult,内部做 Page 转换 - 修正展开行预览截断逻辑(lastIndexOf) - PageResult 类型移至 types/common.ts - 明确旧 GET 接口保留策略 - 弹窗增加响应式、ESC/遮罩关闭、错误信息格式 - 补充 keyword SQL 注入安全和大小写说明 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -87,8 +87,9 @@ purpose: AI 调用日志详情查看功能设计文档
|
|||||||
**弹窗特性:**
|
**弹窗特性:**
|
||||||
- 入参/出参使用代码高亮 + 等宽字体,支持纵向滚动
|
- 入参/出参使用代码高亮 + 等宽字体,支持纵向滚动
|
||||||
- 每个代码块右上角有「复制」按钮,点击复制原始 JSON 到剪贴板
|
- 每个代码块右上角有「复制」按钮,点击复制原始 JSON 到剪贴板
|
||||||
- 错误信息区域仅在 `status === 'failed'` 时显示
|
- 错误信息区域仅在 `status === 'failed'` 时显示,格式为 `{errorCode}: {errorMessage}`(如 `AI_STREAM_INTERRUPTED: 连接超时`)
|
||||||
- 弹窗宽度 800px
|
- 弹窗宽度 800px,响应式限制 `max-width: 90vw`,防止窄屏溢出
|
||||||
|
- 支持 ESC 关闭和点击遮罩关闭(Element Plus Dialog 默认行为)
|
||||||
|
|
||||||
## 前端组件设计
|
## 前端组件设计
|
||||||
|
|
||||||
@@ -153,16 +154,17 @@ interface LogQueryParams {
|
|||||||
function previewText(jsonStr: string, maxLen = 200): string {
|
function previewText(jsonStr: string, maxLen = 200): string {
|
||||||
if (!jsonStr) return '-'
|
if (!jsonStr) return '-'
|
||||||
if (jsonStr.length <= maxLen) return jsonStr
|
if (jsonStr.length <= maxLen) return jsonStr
|
||||||
// 优先在空白字符处截断,避免截断在转义序列中间
|
|
||||||
const truncated = jsonStr.slice(0, maxLen)
|
const truncated = jsonStr.slice(0, maxLen)
|
||||||
const lastSpace = truncated.search(/\s+(?!.*\s)/)
|
// 优先在空白字符处截断;无空白字符时直接截断到 maxLen
|
||||||
return lastSpace > maxLen * 0.5 ? truncated.slice(0, lastSpace) + '...' : truncated + '...'
|
const lastSpace = truncated.lastIndexOf(' ')
|
||||||
|
const cutAt = lastSpace > maxLen * 0.5 ? lastSpace : maxLen
|
||||||
|
return truncated.slice(0, cutAt) + '...'
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 类型定义
|
### 类型定义
|
||||||
|
|
||||||
在 `web-admin/src/types/aiconfig.ts` 中新增:
|
在 `web-admin/src/types/common.ts` 中新增通用分页类型(如文件不存在则创建):
|
||||||
```typescript
|
```typescript
|
||||||
export interface LogQueryParams {
|
export interface LogQueryParams {
|
||||||
status?: string
|
status?: string
|
||||||
@@ -198,7 +200,7 @@ export function queryAiCallLogs(params: LogQueryParams) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**兼容性说明:** 经检查,`listAiCallLogs` 仅在 `AiRoutingList.vue` 的调用日志 Tab 中使用,无其他调用方。新接口使用 POST,旧接口使用 GET,路径相同方法不同,技术上可共存。实现时直接替换前端调用即可,旧 GET 接口可保留或后续清理。
|
**兼容性说明:** 经检查,`listAiCallLogs` 仅在 `AiRoutingList.vue` 的调用日志 Tab 中使用,无其他调用方。新接口使用 POST,旧接口使用 GET,路径相同方法不同,技术上可共存。实现时直接替换前端调用为 POST 新接口,旧 GET 接口保留不删除(避免影响其他潜在调用方)。
|
||||||
|
|
||||||
## 后端 API 设计
|
## 后端 API 设计
|
||||||
|
|
||||||
@@ -264,8 +266,8 @@ PageResult<AiCallLog> query(AiCallLogQueryRequest request);
|
|||||||
|
|
||||||
```java
|
```java
|
||||||
@Override
|
@Override
|
||||||
public Page<AiCallLog> query(AiCallLogQueryRequest request) {
|
public PageResult<AiCallLog> query(AiCallLogQueryRequest request) {
|
||||||
Page<AiCallLog> page = new Page<>(request.getPageNum(), request.getPageSize());
|
Page<AiCallLog> pageParam = new Page<>(request.getPageNum(), request.getPageSize());
|
||||||
LambdaQueryWrapper<AiCallLog> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<AiCallLog> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
wrapper.eq(AiCallLog::getIsDeleted, 0)
|
wrapper.eq(AiCallLog::getIsDeleted, 0)
|
||||||
@@ -278,13 +280,22 @@ public Page<AiCallLog> query(AiCallLogQueryRequest request) {
|
|||||||
.orderByDesc(AiCallLog::getCreateTime);
|
.orderByDesc(AiCallLog::getCreateTime);
|
||||||
|
|
||||||
// keyword 为空字符串或纯空格时不执行 LIKE 搜索(isNotBlank 已处理)
|
// keyword 为空字符串或纯空格时不执行 LIKE 搜索(isNotBlank 已处理)
|
||||||
|
// MyBatis-Plus like 默认使用预编译语句,无 SQL 注入风险
|
||||||
|
// LIKE 是否区分大小写取决于数据库字符集(项目使用 utf8mb4,默认不区分大小写)
|
||||||
if (StringUtils.isNotBlank(request.getKeyword())) {
|
if (StringUtils.isNotBlank(request.getKeyword())) {
|
||||||
wrapper.and(w -> w.like(AiCallLog::getInputText, request.getKeyword())
|
wrapper.and(w -> w.like(AiCallLog::getInputText, request.getKeyword())
|
||||||
.or()
|
.or()
|
||||||
.like(AiCallLog::getOutputText, request.getKeyword()));
|
.like(AiCallLog::getOutputText, request.getKeyword()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return page(page, wrapper);
|
Page<AiCallLog> page = page(pageParam, wrapper);
|
||||||
|
// 转换为 PageResult,字段名与前端对齐
|
||||||
|
PageResult<AiCallLog> result = new PageResult<>();
|
||||||
|
result.setRecords(page.getRecords());
|
||||||
|
result.setTotal(page.getTotal());
|
||||||
|
result.setPageNum((int) page.getCurrent());
|
||||||
|
result.setPageSize((int) page.getSize());
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user