docs: 调用日志列表增加用户字段实现计划
This commit is contained in:
@@ -0,0 +1,330 @@
|
|||||||
|
# 调用日志列表增加用户字段实现计划
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** 在管理后台调用日志列表中增加"调用用户"列,显示格式为 `昵称(ID)`,方便排查用户反馈问题。
|
||||||
|
|
||||||
|
**Architecture:** 后端 `AiCallLog` 实体新增 `userName` 字段,两处日志保存逻辑写入该字段;前端列表和详情弹窗统一展示 `昵称(ID)` 格式。
|
||||||
|
|
||||||
|
**Tech Stack:** Java (Spring Boot 2.7 + MyBatis-Plus), Vue 3 + TypeScript + Element Plus
|
||||||
|
|
||||||
|
**Spec:** `docs/superpowers/specs/2026-05-25-call-log-add-user-design.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文件清单
|
||||||
|
|
||||||
|
| 操作 | 文件 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| Modify | `backend-single/src/main/java/com/emotion/entity/AiCallLog.java` | 实体新增 `userName` 字段 |
|
||||||
|
| Create | `backend-single/src/main/resources/db/migration/V20260525__add_user_name_to_ai_call_log.sql` | 数据库迁移脚本 |
|
||||||
|
| Modify | `backend-single/src/main/java/com/emotion/service/impl/AiRuntimeServiceImpl.java:68-73` | invokeStream 写入 userName |
|
||||||
|
| Modify | `backend-single/src/main/java/com/emotion/service/impl/AiRuntimeServiceImpl.java:219-225` | invokeEndpointStream 写入 userName |
|
||||||
|
| Modify | `web-admin/src/types/aiconfig.ts` | AiCallLog 接口新增 userName |
|
||||||
|
| Modify | `web-admin/src/views/aiconfig/AiRoutingList.vue` | 列表新增"调用用户"列 + userDisplay 函数 |
|
||||||
|
| Modify | `web-admin/src/views/aiconfig/components/AiCallLogDetailDialog.vue` | 详情弹窗"用户 ID"改为"调用用户" |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: 后端实体 + 数据库迁移
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `backend-single/src/main/java/com/emotion/entity/AiCallLog.java:29-31`
|
||||||
|
- Create: `backend-single/src/main/resources/db/migration/V20260525__add_user_name_to_ai_call_log.sql`
|
||||||
|
- Test: `backend-single/` 目录下执行编译验证
|
||||||
|
|
||||||
|
- [ ] **Step 1: 在 AiCallLog 实体中新增 userName 字段**
|
||||||
|
|
||||||
|
在 `userId` 字段(第 29-30 行)下方新增:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// AiCallLog.java — 在 @TableField("user_id") private String userId; 之后新增
|
||||||
|
|
||||||
|
@TableField("user_name")
|
||||||
|
private String userName;
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 创建数据库迁移脚本**
|
||||||
|
|
||||||
|
创建 `backend-single/src/main/resources/db/migration/V20260525__add_user_name_to_ai_call_log.sql`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 调用日志表新增 user_name 字段
|
||||||
|
ALTER TABLE t_ai_call_log ADD COLUMN user_name VARCHAR(100) DEFAULT NULL COMMENT '用户昵称' AFTER user_id;
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 编译验证后端代码**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend-single
|
||||||
|
mvn clean compile -DskipTests
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:BUILD SUCCESS
|
||||||
|
|
||||||
|
- [ ] **Step 4: 提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add backend-single/src/main/java/com/emotion/entity/AiCallLog.java
|
||||||
|
git add backend-single/src/main/resources/db/migration/V20260525__add_user_name_to_ai_call_log.sql
|
||||||
|
git commit -m "feat: 调用日志实体新增 userName 字段及数据库迁移脚本"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: 后端日志保存逻辑写入 userName
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `backend-single/src/main/java/com/emotion/service/impl/AiRuntimeServiceImpl.java`
|
||||||
|
- Test: `backend-single/src/test/java/com/emotion/service/AiRuntimeServiceImplTest.java`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 在 invokeStream 方法中写入 userName**
|
||||||
|
|
||||||
|
找到第 71 行 `callLog.setUserId(resolveUserId(request));` 之后,第 72 行 `callLog.setInputText(...)` 之前,新增:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 在 callLog.setUserId(resolveUserId(request)); 之后新增
|
||||||
|
callLog.setUserName(request.getUserName());
|
||||||
|
```
|
||||||
|
|
||||||
|
修改后的上下文(第 68-74 行)变为:
|
||||||
|
|
||||||
|
```java
|
||||||
|
AiCallLog callLog = new AiCallLog();
|
||||||
|
callLog.setRequestId(requestId);
|
||||||
|
callLog.setSceneCode(request.getSceneCode());
|
||||||
|
callLog.setUserId(resolveUserId(request));
|
||||||
|
callLog.setUserName(request.getUserName());
|
||||||
|
callLog.setInputText(JSON.toJSONString(request.getInputs()));
|
||||||
|
callLog.setStatus("running");
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 在 invokeEndpointStream 方法中写入 userName**
|
||||||
|
|
||||||
|
找到第 223 行 `callLog.setUserId(request.getUserId());` 之后,第 224 行 `callLog.setInputText(...)` 之前,新增:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 在 callLog.setUserId(request.getUserId()); 之后新增
|
||||||
|
callLog.setUserName(request.getUserName());
|
||||||
|
```
|
||||||
|
|
||||||
|
修改后的上下文(第 219-226 行)变为:
|
||||||
|
|
||||||
|
```java
|
||||||
|
AiCallLog callLog = new AiCallLog();
|
||||||
|
callLog.setRequestId(requestId);
|
||||||
|
callLog.setEndpointCode(endpoint.getEndpointCode());
|
||||||
|
callLog.setProviderCode(provider.getProviderCode());
|
||||||
|
callLog.setUserId(request.getUserId());
|
||||||
|
callLog.setUserName(request.getUserName());
|
||||||
|
callLog.setInputText(JSON.toJSONString(request.getInputs()));
|
||||||
|
callLog.setStatus("running");
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 更新单元测试,验证 userName 被正确写入**
|
||||||
|
|
||||||
|
修改 `backend-single/src/test/java/com/emotion/service/AiRuntimeServiceImplTest.java`,在 `invokeStreamRecoversWhenOutputExists` 测试中:
|
||||||
|
|
||||||
|
在已有的 `request.setRequestId("client-request-1");` 之后添加 `request.setUserName("测试用户");`,然后在断言部分新增 userName 的验证:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 在 request.setRequestId("client-request-1"); 之后添加
|
||||||
|
request.setUserName("测试用户");
|
||||||
|
|
||||||
|
// 在已有的 assertEquals("完整输出", savedLog.getOutputText()); 之后添加
|
||||||
|
assertEquals("测试用户", savedLog.getUserName());
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 运行测试验证**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend-single
|
||||||
|
mvn test -Dtest=AiRuntimeServiceImplTest
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:Tests run: 1, Failures: 0, Errors: 0
|
||||||
|
|
||||||
|
- [ ] **Step 5: 编译验证 + 提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend-single
|
||||||
|
mvn clean compile -DskipTests
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add backend-single/src/main/java/com/emotion/service/impl/AiRuntimeServiceImpl.java
|
||||||
|
git add backend-single/src/test/java/com/emotion/service/AiRuntimeServiceImplTest.java
|
||||||
|
git commit -m "feat: 日志保存时写入 userName 字段"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: 前端类型定义 + 列表新增列
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `web-admin/src/types/aiconfig.ts:249-250` (AiCallLog 接口)
|
||||||
|
- Modify: `web-admin/src/views/aiconfig/AiRoutingList.vue` (调用日志表格区域)
|
||||||
|
|
||||||
|
- [ ] **Step 1: AiCallLog 接口新增 userName**
|
||||||
|
|
||||||
|
在 `web-admin/src/types/aiconfig.ts` 的 `AiCallLog` 接口中,找到 `userId?: string` 行(约第 249 行),在其下方新增:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 在 userId?: string 之后新增
|
||||||
|
userName?: string
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: AiRoutingList.vue 新增"调用用户"列**
|
||||||
|
|
||||||
|
在调用日志表格中,找到 `调用时间` 列(`<el-table-column prop="createTime" label="调用时间" width="175" />`)之后,`场景` 列之前,新增:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<el-table-column label="调用用户" width="180" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ userDisplay(row) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: AiRoutingList.vue 新增 userDisplay 辅助函数**
|
||||||
|
|
||||||
|
在 `<script setup>` 中,找到 `function formatMs(ms?: number)` 函数之后(约第 640 行),新增:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function userDisplay(row: AiCallLog): string {
|
||||||
|
const name = row.userName
|
||||||
|
const id = row.userId
|
||||||
|
if (name && id) return `${name}(${id})`
|
||||||
|
if (name) return name
|
||||||
|
if (id) return `-(ID: ${id})`
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 前端类型检查**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd web-admin
|
||||||
|
npm run type-check
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:无错误
|
||||||
|
|
||||||
|
- [ ] **Step 5: 提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add web-admin/src/types/aiconfig.ts
|
||||||
|
git add web-admin/src/views/aiconfig/AiRoutingList.vue
|
||||||
|
git commit -m "feat: 调用日志列表新增调用用户列"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: 前端详情弹窗格式统一
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `web-admin/src/views/aiconfig/components/AiCallLogDetailDialog.vue`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 替换"用户 ID"行为"调用用户"**
|
||||||
|
|
||||||
|
在 `AiCallLogDetailDialog.vue` 中,找到基本信息区域中的"用户 ID"行(约第 50-53 行):
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">用户 ID</span>
|
||||||
|
<span class="info-value">{{ log.userId || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
替换为:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">调用用户</span>
|
||||||
|
<span class="info-value">{{ userDisplay(log) }}</span>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 添加 userDisplay 辅助函数**
|
||||||
|
|
||||||
|
在 `<script setup>` 中,找到 `function formatMs(ms?: number)` 函数(约第 111 行)之后,新增:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function userDisplay(log: AiCallLog): string {
|
||||||
|
const name = log.userName
|
||||||
|
const id = log.userId
|
||||||
|
if (name && id) return `${name}(${id})`
|
||||||
|
if (name) return name
|
||||||
|
if (id) return `-(ID: ${id})`
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 前端类型检查**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd web-admin
|
||||||
|
npm run type-check
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:无错误
|
||||||
|
|
||||||
|
- [ ] **Step 4: 提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add web-admin/src/views/aiconfig/components/AiCallLogDetailDialog.vue
|
||||||
|
git commit -m "feat: 详情弹窗用户信息改为昵称+ID格式"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: 浏览器验证
|
||||||
|
|
||||||
|
- [ ] **Step 1: 启动管理后台**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd web-admin
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 浏览器验证**
|
||||||
|
|
||||||
|
1. 打开管理后台页面(通常 `http://localhost:5174`)
|
||||||
|
2. 导航至 AI 配置管理页面 (`/aiconfig/list`)
|
||||||
|
3. 切换到"调用日志" tab
|
||||||
|
4. 验证:列表中出现"调用用户"列,显示格式为 `昵称(ID)` 或 `-(ID: xxx)` 或 `-`
|
||||||
|
5. 点击任意日志的"详情"按钮,验证弹窗中"调用用户"行显示格式与列表一致
|
||||||
|
6. 打开浏览器 DevTools Console,确认无报错
|
||||||
|
|
||||||
|
- [ ] **Step 3: 最终提交(如有必要的小修复)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add -A
|
||||||
|
git commit -m "fix: 调用日志用户列浏览器验证修复"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Self-Review: Spec Coverage Check
|
||||||
|
|
||||||
|
| Spec 要求 | 对应 Task |
|
||||||
|
|-----------|-----------|
|
||||||
|
| AiCallLog 实体新增 userName | Task 1 Step 1 |
|
||||||
|
| 数据库新增 user_name 列 | Task 1 Step 2 |
|
||||||
|
| invokeStream 写入 userName | Task 2 Step 1 |
|
||||||
|
| invokeEndpointStream 写入 userName | Task 2 Step 2 |
|
||||||
|
| 前端类型定义新增 userName | Task 3 Step 1 |
|
||||||
|
| 列表新增"调用用户"列 | Task 3 Step 2-3 |
|
||||||
|
| 详情弹窗改为调用用户格式 | Task 4 Step 1-2 |
|
||||||
|
| 兜底策略(历史数据 NULL) | userDisplay 函数已处理 |
|
||||||
|
| 不改 AiCallLogQueryRequest | 确认未改 |
|
||||||
|
|
||||||
|
## Placeholder Scan
|
||||||
|
|
||||||
|
计划中无 "TBD"、"TODO"、"fill in"、"add appropriate error handling" 等占位符。所有步骤包含完整代码和命令。
|
||||||
|
|
||||||
|
## Type Consistency Check
|
||||||
|
|
||||||
|
- `AiCallLog.userName` → Java `String`,前端 `userName?: string` ✅ 一致
|
||||||
|
- `userDisplay` 函数在 AiRoutingList.vue 和 AiCallLogDetailDialog.vue 中实现相同逻辑 ✅ 一致
|
||||||
|
- 显示格式统一为 `昵称(ID)` / `-(ID: xxx)` / `-` ✅ 一致
|
||||||
Reference in New Issue
Block a user