ai_chat_session 和 ai_chat_record 两张表,以及面向当前用户的会话 API(/ai/session/list、/ai/session/{id}/messages、DELETE /ai/session/{id})views/ai/ 下仅有 provider.vue 和 model.vue,无会话管理页面采用单页面集成方案,一个页面包含统计区 + 左右分栏会话管理:
┌──────────────────────────────────────────────────┐
│ 统计卡片区(4个 NStatistic) │
│ [会话总数] [消息总数] [今日会话] [Token消耗] │
├──────────────────────────────────────────────────┤
│ 趋势图(echarts 双Y轴:近30天每日会话数+消息数) │
├────────────────┬─────────────────────────────────┤
│ 会话列表(左) │ 聊天记录(右) │
│ 🔍 搜索/筛选 │ │
│ ┌──────────┐ │ 用户头像 昵称 14:30 │
│ │ 张三 会话 │ │ ┌───────────────────┐ │
│ │ 3条消息 │ │ │ 用户消息(蓝色气泡) │ │
│ │ 10分钟前 │ │ └───────────────────┘ │
│ ├──────────┤ │ │
│ │ 李四 会话 │ │ AI助手 14:31 │
│ │ 8条消息 │ │ ┌───────────────┐ │
│ │ 1小时前 │ │ │ AI回复(灰色气泡)│ │
│ └──────────┘ │ └───────────────┘ │
│ │ │
└────────────────┴─────────────────────────────────┘
echarts-theme.js 主题适配新增管理端 Controller,路径前缀 /ai/admin/session,不修改现有 AiChatController。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /ai/admin/session/page |
分页查询所有会话 |
| GET | /ai/admin/session/{sessionId}/messages |
查看会话消息记录 |
| DELETE | /ai/admin/session/{sessionId} |
删除会话(软删除) |
| GET | /ai/admin/session/statistics |
统计数据 |
GET /ai/admin/session/page请求参数(Query): | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | pageNum | Integer | 否 | 页码,默认1 | | pageSize | Integer | 否 | 每页条数,默认10 | | keyword | String | 否 | 搜索关键词(匹配用户昵称或会话标题) | | startTime | String | 否 | 开始时间(yyyy-MM-dd) | | endTime | String | 否 | 结束时间(yyyy-MM-dd) | | status | String | 否 | 状态(0正常,1已删除) |
响应数据:
{
"code": 200,
"data": {
"records": [
{
"id": "session-uuid",
"userId": 1,
"nickName": "张三",
"avatar": "file-id-or-url",
"sessionName": "帮我写一个用户管理模块",
"agentCode": "code-assistant",
"status": "0",
"messageCount": 8,
"tokenUsage": 1234,
"createTime": "2026-04-19 14:30:00",
"updateTime": "2026-04-19 14:35:00"
}
],
"total": 100,
"pageNum": 1,
"pageSize": 10
}
}
SQL 实现:在 Mapper XML 中写自定义分页 SQL:
SELECT s.*, u.real_name AS nickName, u.avatar,
(SELECT COUNT(*) FROM ai_chat_record r WHERE r.session_id = s.id) AS messageCount,
(SELECT COALESCE(SUM(r.token_usage), 0) FROM ai_chat_record r WHERE r.session_id = s.id) AS tokenUsage
FROM ai_chat_session s
LEFT JOIN sys_user u ON s.user_id = u.id
WHERE s.tenant_id = #{tenantId}
AND (#{keyword} IS NULL OR u.real_name LIKE CONCAT('%',#{keyword},'%') OR s.session_name LIKE CONCAT('%',#{keyword},'%'))
AND (#{startTime} IS NULL OR s.create_time >= #{startTime})
AND (#{endTime} IS NULL OR s.create_time <= #{endTime})
AND (#{status} IS NULL OR s.status = #{status})
ORDER BY s.update_time DESC
GET /ai/admin/session/{sessionId}/messages复用现有 AiChatRecordService.listBySession(sessionId) 逻辑,但在管理端 Controller 中暴露。
响应数据:
{
"code": 200,
"data": [
{
"id": 123,
"sessionId": "session-uuid",
"role": "user",
"content": "帮我写一个用户管理模块",
"tokenUsage": 50,
"createTime": "2026-04-19 14:30:00"
},
{
"id": 124,
"sessionId": "session-uuid",
"role": "assistant",
"content": "好的,我来帮你写...",
"tokenUsage": 200,
"createTime": "2026-04-19 14:30:05"
}
]
}
DELETE /ai/admin/session/{sessionId}复用现有 AiChatSessionService.deleteSession(sessionId),软删除(status=1)。
GET /ai/admin/session/statistics响应数据:
{
"code": 200,
"data": {
"totalSessions": 1234,
"totalMessages": 5678,
"todaySessions": 15,
"totalTokenUsage": 98765,
"dailyTrend": [
{ "date": "2026-03-20", "sessionCount": 10, "messageCount": 45 },
{ "date": "2026-03-21", "sessionCount": 12, "messageCount": 50 }
]
}
}
SQL 实现:
-- 汇总统计
SELECT COUNT(*) AS totalSessions FROM ai_chat_session WHERE tenant_id = #{tenantId} AND status = '0';
SELECT COUNT(*) AS totalMessages FROM ai_chat_record WHERE tenant_id = #{tenantId};
SELECT COUNT(*) AS todaySessions FROM ai_chat_session WHERE tenant_id = #{tenantId} AND status = '0' AND DATE(create_time) = CURDATE();
SELECT COALESCE(SUM(token_usage), 0) AS totalTokenUsage FROM ai_chat_record WHERE tenant_id = #{tenantId};
-- 30天趋势
SELECT DATE(s.create_time) AS date,
COUNT(DISTINCT s.id) AS sessionCount,
(SELECT COUNT(*) FROM ai_chat_record r WHERE r.tenant_id = #{tenantId} AND DATE(r.create_time) = DATE(s.create_time)) AS messageCount
FROM ai_chat_session s
WHERE s.tenant_id = #{tenantId} AND s.create_time >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
GROUP BY DATE(s.create_time)
ORDER BY date;
AiSessionPageQuery(分页查询条件): | 字段 | 类型 | 说明 | |------|------|------| | pageNum | Integer | 页码 | | pageSize | Integer | 每页条数 | | keyword | String | 搜索关键词 | | startTime | String | 开始时间 | | endTime | String | 结束时间 | | status | String | 状态 |
AiSessionVO(会话列表返回对象): | 字段 | 类型 | 说明 | |------|------|------| | id | String | 会话ID | | userId | Long | 用户ID | | nickName | String | 用户昵称(来自 sys_user.real_name) | | avatar | String | 用户头像(来自 sys_user.avatar) | | sessionName | String | 会话标题 | | agentCode | String | Agent编码 | | status | String | 状态 | | messageCount | Integer | 消息条数 | | tokenUsage | Integer | Token消耗 | | createTime | LocalDateTime | 创建时间 | | updateTime | LocalDateTime | 更新时间 |
AiSessionStatisticsVO(统计数据返回对象): | 字段 | 类型 | 说明 | |------|------|------| | totalSessions | Long | 会话总数 | | totalMessages | Long | 消息总数 | | todaySessions | Long | 今日会话数 | | totalTokenUsage | Long | Token消耗总量 | | dailyTrend | List | 30天趋势 |
DailyTrendItem: | 字段 | 类型 | 说明 | |------|------|------| | date | String | 日期 | | sessionCount | Long | 会话数 | | messageCount | Long | 消息数 |
使用现有的 ai_chat_session 和 ai_chat_record 表,无需新增或修改表。
forge-plugin-ai/src/main/java/com/mdframe/forge/plugin/ai/
├── admin/
│ └── controller/
│ └── AiSessionAdminController.java # 管理端会话Controller
├── session/
│ ├── dto/
│ │ ├── AiSessionPageQuery.java # 分页查询条件
│ │ └── AiSessionVO.java # 会话列表VO
│ ├── vo/
│ │ ├── AiSessionStatisticsVO.java # 统计VO
│ │ └── DailyTrendItem.java # 趋势项
│ ├── mapper/
│ │ └── AiChatSessionMapper.java # 新增自定义方法
│ └── service/
│ └── AiChatSessionService.java # 新增管理端方法
└── resources/mapper/
└── AiChatSessionMapper.xml # 新增自定义SQL
forge-admin-ui/src/
├── api/ai.js # 新增会话管理API
└── views/ai/
└── session.vue # AI会话管理页面
@/utils/echarts-theme.js 主题AuthImage 组件(鉴权图片需带 token)tenant_id 条件,项目 TenantLineInnerInterceptor 会自动处理ai_chat_record.token_usage 字段未被实际写入,统计值可能为 0,需确认后续是否补全@ApiPermissionIgnore,走正常权限体系,在 sys_resource 中配置菜单权限,仅管理员可访问