- Flyway V6:模型表、默认种子、ai_chat_session.model_id 回填 - 管理端 /api/admin/ai/models;用户端选模与新会话绑定 - 禁用模型后会话只读(5009);chat 按会话 modelCode 调 DashScope - 前端 AiModels 页、助手模型选择器与 localStorage - 单测、AiModelManagementApiIT、Playwright E2E;e2e Profile 关闭注解限流 - 技术实施文档与验收报告;移除过期冲突分析文档 Co-authored-by: Cursor <cursoragent@cursor.com>
24 KiB
AI 助手模型管理功能技术实施文档
| 文档版本 | 修订日期 | 修订人 | 修订内容 |
|---|---|---|---|
| V1.0 | 2026-05-23 | — | 基于《AI助手模型管理功能需求》V1.1 初稿 |
| V1.1 | 2026-05-23 | — | 补充与润色、标签推荐等功能的实施边界说明 |
| V1.2 | 2026-05-24 | — | 同步 §6 待办进度;§5.3 联调清单标注自动化已覆盖项 |
| V1.3 | 2026-05-24 | — | §6 待办 18~19 标为已完成(API IT + Playwright E2E) |
| V1.4 | 2026-05-24 | — | §6 待办 20 完成;新增《AI助手模型管理功能验收报告》 |
关联需求:AI助手模型管理功能需求.md(V1.2)
依赖能力:AI助手功能技术实施文档.md(AI 助手 V1.1 已落地)
与其它 AI 功能(实施边界):本期 不修改 TextPolishServiceImpl、TagRecommendServiceImpl、AiSummaryServiceImpl 的 properties.getModel() 逻辑;润色、标签推荐、摘要仍走 dashscope.model。仅改造 AiChatServiceImpl 及新增 AiAssistantModel* 相关类。
前提条件
数据安全规则(与项目一致):
- 联调产生的测试模型、会话,完成后可清理;勿动名为 「hello」 的文章
环境依赖:
- AI 助手相关表已存在(Flyway
V5__ai_chat_tables.sql) DASHSCOPE_API_KEY已配置- MySQL、Redis 可用
产品约定(实施须遵守):
- D-08:会话创建时绑定
model_id,同会话不换模 - D-09:模型
enabled=0后,绑定会话 仅可查看,POST /chat拒绝 - D-10:摘要 / 润色 / 标签推荐 不改,仍用
DashScopeProperties.model - D-13:
allow_image/allow_voice仅管理端配置 + API 返回;用户端 无 上传 UI
1. 功能概述
在现有 AI 助手基础上增加:
| 端 | 能力 |
|---|---|
| 管理后台 | /admin/ai-models 模型 CRUD(逻辑删除)、默认模型、启用/禁用、多模态开关 |
| 用户端 | 助手页模型下拉;localStorage 记住选择;新会话绑定模型 |
| 后端 | 表 ai_assistant_model;ai_chat_session.model_id;按 model_code 调 DashScope |
与现网差异:AiChatServiceImpl.buildRequestBody 中 "model", properties.getModel() 改为会话绑定模型的 model_code。
2. 核心实现思路
2.1 复用项
| 复用项 | 说明 |
|---|---|
AiSummaryServiceImpl.callModel |
仍负责 HTTP 调用 DashScope,仅请求体 model 字段来源变化 |
DashScopeProperties |
api-key、base-url、timeout、max-input;model 作种子与异常兜底 |
AiChatServiceImpl |
在现有 chat / session 流程上扩展,不重写 |
| 管理后台模式 | 参考 AdminCategoryController + Categories.vue 表格 + 弹窗 |
@TableLogic |
ai_assistant_model.deleted 与 ai_chat_session.deleted 一致 |
2.2 新增组件关系
flowchart LR
subgraph admin [管理端]
AiModelsVue --> aiModelApi
aiModelApi --> AdminAiModelController
end
subgraph app [用户端]
AiAssistantVue --> aiModelApi
AiAssistantVue --> aiChatApi
aiChatApi --> AiChatController
end
AdminAiModelController --> AiAssistantModelService
AiChatController --> AiChatService
AiChatService --> AiAssistantModelService
AiAssistantModelService --> AiAssistantModelMapper
AiChatService --> AiChatSessionMapper
2.3 模型解析与只读(核心)
createSession(userId, modelId?)
→ resolveModelForNewSession(modelId) // 启用且未删;否则默认模型
→ insert session.model_id
chat(userId, dto)
→ session = getSessionForUser(...)
→ model = getModelById(session.model_id) // 含已禁用、已删记录(历史需要)
→ if model == null || model.deleted → 400
→ if !model.enabled → throw AI_MODEL_DISABLED
→ buildRequestBody(..., model.modelCode)
只读原因(readOnlyReason):
| 值 | 条件 |
|---|---|
null |
可继续对话 |
ROUND_FULL |
round_count >= 5 |
MODEL_DISABLED |
绑定模型 enabled = 0(优先于 ROUND_FULL 展示文案时,前端 modelEnabled === false 时用停用提示) |
3. 改动项清单
3.1 数据库(Flyway)
文件:jog-admin/src/main/resources/db/migration/V6__ai_assistant_model.sql
(当前仓库最大版本为 V5,故使用 V6。)
-- V6__ai_assistant_model.sql
-- AI 助手:可配置模型 + 会话绑定
CREATE TABLE ai_assistant_model (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
model_code VARCHAR(64) NOT NULL COMMENT 'DashScope model 参数',
display_name VARCHAR(32) NOT NULL,
description VARCHAR(200) NULL,
enabled TINYINT NOT NULL DEFAULT 1,
allow_image TINYINT NOT NULL DEFAULT 0,
allow_voice TINYINT NOT NULL DEFAULT 0,
is_default TINYINT NOT NULL DEFAULT 0,
sort INT NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted TINYINT NOT NULL DEFAULT 0,
INDEX idx_enabled_sort (enabled, sort, id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 种子:与 application.yml 默认 qwen-turbo 对齐;部署若 DASHSCOPE_MODEL 不同,上线后可在后台改 model_code
INSERT INTO ai_assistant_model (
model_code, display_name, description, enabled, allow_image, allow_voice, is_default, sort
) VALUES (
'qwen-turbo', '默认模型', '系统迁移种子模型', 1, 0, 0, 1, 0
);
ALTER TABLE ai_chat_session
ADD COLUMN model_id BIGINT NULL COMMENT '绑定 ai_assistant_model.id' AFTER user_id;
UPDATE ai_chat_session SET model_id = (SELECT id FROM ai_assistant_model WHERE is_default = 1 AND deleted = 0 LIMIT 1)
WHERE model_id IS NULL;
注意:
model_code唯一性由应用层在deleted=0范围内校验,不在 DB 建唯一索引(避免软删后无法复用编码)。
3.2 后端实体与 Mapper
| 文件 | 说明 |
|---|---|
entity/AiAssistantModel.java |
@TableName("ai_assistant_model"),@TableLogic on deleted |
mapper/AiAssistantModelMapper.java |
BaseMapper<AiAssistantModel> |
实体字段映射(Java 侧建议布尔包装,MyBatis 映射 TINYINT):
@Data
@TableName("ai_assistant_model")
public class AiAssistantModel {
@TableId(type = IdType.AUTO)
private Long id;
private String modelCode;
private String displayName;
private String description;
private Boolean enabled;
private Boolean allowImage;
private Boolean allowVoice;
private Boolean isDefault;
private Integer sort;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdAt;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedAt;
@TableLogic
private Integer deleted;
}
修改 entity/AiChatSession.java:增加 private Long modelId;
3.3 后端 DTO / VO
| 文件 | 说明 |
|---|---|
dto/AiAssistantModelSaveDTO.java |
管理端新增/修改:modelCode, displayName, description, enabled, allowImageUpload, allowVoiceUpload, isDefault, sort + Jakarta 校验 |
dto/AiAssistantModelVO.java |
管理端列表/详情(含 modelCode) |
dto/AiAssistantModelAppVO.java |
用户端列表(无 modelCode) |
dto/CreateAiChatSessionDTO.java |
可选 modelId |
dto/AiChatSessionMessagesVO.java |
消息列表包装:messages, modelEnabled, readOnlyReason, roundCount, maxRounds |
dto/CreateAiChatSessionDTO.java |
@RequestBody(required = false) 用于 POST sessions |
扩展已有 VO:
// AiChatSessionVO 增加
private Long modelId;
private String modelDisplayName;
private Boolean modelEnabled;
// AiChatResultVO 增加(可选)
private String modelDisplayName;
// AiChatSendDTO 增加(可选,会话级绑定后 chat 内忽略)
private Long modelId;
枚举 enums/AiChatReadOnlyReason.java:
public enum AiChatReadOnlyReason {
ROUND_FULL,
MODEL_DISABLED
}
3.4 后端 Service
3.4.1 AiAssistantModelService / AiAssistantModelServiceImpl
| 方法 | 说明 |
|---|---|
List<AiAssistantModelVO> listAllForAdmin() |
deleted=0,ORDER BY sort ASC, id ASC |
AiAssistantModelVO getByIdForAdmin(Long id) |
不存在 → NOT_FOUND |
AiAssistantModelVO create(AiAssistantModelSaveDTO dto) |
校验编码唯一、默认模型互斥 |
void update(Long id, AiAssistantModelSaveDTO dto) |
禁用前 assertAtLeastOneEnabled;设默认时清除其它 is_default |
void deleteLogical(Long id) |
禁止删当前默认;删后仍须至少一条 enabled(若删的是启用项) |
List<AiAssistantModelAppVO> listEnabledForApp() |
enabled=1 且 deleted=0 |
AiAssistantModel getById(Long id) |
含已删(@TableLogic 需用自定义查询或 selectById 带逻辑) |
AiAssistantModel requireEnabledById(Long id) |
创建会话用 |
AiAssistantModel getDefaultModel() |
is_default=1 AND deleted=0;无则 warn + 合成 fallback(dashscope.model) |
默认模型切换(事务内):
@Transactional
public void applyDefaultFlag(Long modelId, boolean isDefault) {
if (isDefault) {
modelMapper.update(null, Wrappers.<AiAssistantModel>lambdaUpdate()
.set(AiAssistantModel::getIsDefault, false)
.eq(AiAssistantModel::getDeleted, 0));
}
// 再 update 目标行 is_default
}
编码唯一:
long count = modelMapper.selectCount(Wrappers.<AiAssistantModel>lambdaQuery()
.eq(AiAssistantModel::getModelCode, code)
.ne(excludeId != null, AiAssistantModel::getId, excludeId));
if (count > 0) throw new BusinessException(ErrorCode.AI_MODEL_CODE_DUPLICATE);
3.4.2 修改 AiChatService / AiChatServiceImpl
| 变更点 | 说明 |
|---|---|
createSession(Long userId, Long modelId) |
解析模型 → session.setModelId(...) |
listSessions |
toSessionVO 联查或批量查模型,填充 modelDisplayName、modelEnabled |
listMessages |
返回类型改为 AiChatSessionMessagesVO;计算 readOnlyReason |
chat |
开头校验绑定模型 enabled;buildRequestBody(history, modelCode) |
| 日志 | log.info("AI 助手成功 sessionId={}, modelId={}, modelCode={}, ...") |
禁用校验(chat 内,在轮次校验之后):
AiAssistantModel bound = aiAssistantModelService.getById(session.getModelId());
if (bound == null || Boolean.FALSE.equals(bound.getEnabled())) {
throw new BusinessException(ErrorCode.AI_MODEL_DISABLED);
}
buildRequestBody 签名:
private Map<String, Object> buildRequestBody(List<AiChatMessage> history, String modelCode) {
// ...
return Map.of(
"model", modelCode,
"messages", messages,
"temperature", 0.7,
"top_p", 0.9
);
}
resolveReadOnlyReason:
private AiChatReadOnlyReason resolveReadOnlyReason(AiChatSession session, AiAssistantModel model) {
if (model != null && Boolean.FALSE.equals(model.getEnabled())) {
return AiChatReadOnlyReason.MODEL_DISABLED;
}
if (session.getRoundCount() != null && session.getRoundCount() >= MAX_ROUNDS) {
return AiChatReadOnlyReason.ROUND_FULL;
}
return null;
}
已删除模型:
getById若被@TableLogic过滤为 null,展示modelDisplayName = "已下架模型",modelEnabled = false,readOnlyReason = MODEL_DISABLED,且chat拒绝。
3.5 后端 Controller
3.5.1 AdminAiModelController.java
@Tag(name = "管理员-AI 助手模型")
@RestController
@RequestMapping("/api/admin/ai/models")
@RequiredArgsConstructor
public class AdminAiModelController {
private final AiAssistantModelService modelService;
@GetMapping
public ApiResult<List<AiAssistantModelVO>> list() { ... }
@GetMapping("/{id}")
public ApiResult<AiAssistantModelVO> get(@PathVariable Long id) { ... }
@PostMapping
public ApiResult<AiAssistantModelVO> create(@Valid @RequestBody AiAssistantModelSaveDTO dto) { ... }
@PutMapping("/{id}")
public ApiResult<Void> update(@PathVariable Long id, @Valid @RequestBody AiAssistantModelSaveDTO dto) { ... }
@DeleteMapping("/{id}")
public ApiResult<Void> delete(@PathVariable Long id) { ... }
}
权限:沿用 Spring Security 对 /api/admin/** 的 ADMIN 规则(与 CategoryController 一致)。
3.5.2 扩展 AiChatController.java
@GetMapping("/models")
public ApiResult<List<AiAssistantModelAppVO>> listModels(@AuthenticationPrincipal Long userId) {
return ApiResult.success(aiAssistantModelService.listEnabledForApp());
}
@PostMapping("/sessions")
public ApiResult<CreateAiChatSessionVO> createSession(
@AuthenticationPrincipal Long userId,
@RequestBody(required = false) CreateAiChatSessionDTO body) {
Long modelId = body == null ? null : body.getModelId();
return ApiResult.success(aiChatService.createSession(userId, modelId));
}
@GetMapping("/sessions/{sessionId}/messages")
public ApiResult<AiChatSessionMessagesVO> listMessages(...) { ... }
3.6 错误码
在 jog-common/.../ErrorCode.java 新增(code 段延续 500x):
| 枚举 | code | message(默认) |
|---|---|---|
AI_MODEL_CODE_DUPLICATE |
5007 | 模型编码已存在 |
AI_MODEL_NOT_AVAILABLE |
5008 | 模型不可用 |
AI_MODEL_DISABLED |
5009 | 该会话所用模型已停用,仅可查看历史 |
AI_MODEL_MUST_KEEP_ENABLED |
5010 | 须至少保留一个启用的模型 |
AI_MODEL_DEFAULT_REQUIRED |
5011 | 请先指定其它模型为默认后再删除 |
BusinessException 可覆盖 message 以贴合 FR-AI-MODEL-ADM-07/08 文案。
3.7 前端改动
3.7.1 API
新建 jog-frontend/src/api/aiModel.js:
import request from '@/utils/request'
// 管理端
export function listAdminAiModels() {
return request.get('/api/admin/ai/models')
}
export function createAdminAiModel(data) {
return request.post('/api/admin/ai/models', data)
}
export function updateAdminAiModel(id, data) {
return request.put(`/api/admin/ai/models/${id}`, data)
}
export function deleteAdminAiModel(id) {
return request.delete(`/api/admin/ai/models/${id}`)
}
// 用户端
export function listAppAiModels() {
return request.get('/api/app/ai/models')
}
修改 jog-frontend/src/api/aiChat.js:
export function createAiChatSession(data) {
return request.post('/api/app/ai/sessions', data ?? {})
}
export function listAiChatMessages(sessionId) {
// 响应 data 结构变为 { messages, modelEnabled, readOnlyReason, roundCount, maxRounds }
return request.get(`/api/app/ai/sessions/${sessionId}/messages`)
}
3.7.2 管理后台 views/admin/AiModels.vue
- 参考
Categories.vue:el-table+el-dialog表单 - 表单字段:展示名称、模型编码、描述、启用、允许上传图片、允许上传语音、默认、排序
- 列表列:ID、展示名称、编码、状态、图片、语音、默认、排序、操作
- 删除:
ElMessageBox.confirm→deleteAdminAiModel - 不实现 任何上传控件(D-13)
3.7.3 路由与菜单
| 文件 | 变更 |
|---|---|
router/index.js |
children 增加 { path: 'ai-models', name: 'AdminAiModels', component: ..., meta: { title: 'AI 模型', requiresAdmin: true } } |
layouts/AdminLayout.vue |
el-menu-item index="/admin/ai-models" 文案「AI 模型」 |
3.7.4 用户端 AiAssistant.vue + AiChatPanel.vue
状态新增:
const MODEL_STORAGE_KEY = 'jog_ai_assistant_model_id'
const availableModels = ref([])
const selectedModelId = ref(null)
const modelEnabled = ref(true)
const readOnlyReason = ref(null) // 'ROUND_FULL' | 'MODEL_DISABLED' | null
onMounted:
await loadAppModels()
restoreModelIdFromStorage()
// 原有 loadSessions + selectSession
readOnly 计算:
const readOnly = computed(() =>
roundCount.value >= maxRounds || modelEnabled.value === false
)
selectSession:解析 listAiChatMessages 新结构:
const res = await listAiChatMessages(item.id)
const payload = res.data
messages.value = payload.messages ?? []
modelEnabled.value = payload.modelEnabled !== false
readOnlyReason.value = payload.readOnlyReason ?? null
roundCount.value = payload.roundCount ?? item.roundCount ?? 0
handleNewSession:
const res = await createAiChatSession({ modelId: selectedModelId.value })
AiChatPanel 扩展:
- Props:
selectedModelId、availableModels、readOnlyReason - 标题栏:
el-select绑定selectedModelId,@change写localStorage(不 改当前 session) - 提示条:
readOnlyReason === 'MODEL_DISABLED'时显示停用文案;ROUND_FULL沿用现有满 5 轮条(可合并为两个v-if)
不实现:图片/语音上传按钮(即使 allowImageUpload=true 也不展示)。
4. 核心逻辑说明
4.1 创建会话时模型绑定
sequenceDiagram
participant FE as 前端
participant BE as AiChatService
participant MS as AiAssistantModelService
FE->>BE: POST /sessions { modelId: 2 }
BE->>MS: requireEnabledById(2) 或 getDefaultModel()
MS-->>BE: model(id=2, modelCode=qwen-plus)
BE->>BE: insert session(model_id=2)
BE-->>FE: { sessionId }
4.2 禁用后只读
sequenceDiagram
participant U as 用户
participant FE as 前端
participant BE as 后端
U->>FE: 点击绑定已禁用模型的会话
FE->>BE: GET /sessions/{id}/messages
BE-->>FE: modelEnabled=false, readOnlyReason=MODEL_DISABLED
FE->>FE: 禁用输入框 + 提示条
U->>FE: 尝试发送
FE->>FE: readOnly 拦截(可选)
FE->>BE: POST /chat
BE-->>FE: 400 AI_MODEL_DISABLED
4.3 与多模态字段的关系(本期)
- 管理端保存
allowImage/allowVoice至 DB GET /api/app/ai/models返回两字段,前端 不消费POST /chat无attachments字段;后续迭代在 Service 内校验allowImage等
5. 测试要点
5.1 后端单元测试
新建 AiAssistantModelServiceImplTest(Mockito):
| 用例 | 预期 |
|---|---|
| 创建重复 modelCode | AI_MODEL_CODE_DUPLICATE |
| 设 B 为默认 | 仅 B isDefault=true |
| 禁用最后一个启用模型 | AI_MODEL_MUST_KEEP_ENABLED |
| 删除默认模型 | AI_MODEL_DEFAULT_REQUIRED |
扩展 AiChatServiceImplTest:
| 用例 | 预期 |
|---|---|
| createSession 带 modelId | session.modelId 正确 |
| chat 使用绑定 modelCode | callModel 参数含对应 model |
| 绑定模型 enabled=false | AI_MODEL_DISABLED |
| listMessages | readOnlyReason=MODEL_DISABLED |
扩展 AiChatServiceIntegrationTest:覆盖消息包装 VO。
5.2 前端手工验收
对照需求 AC-AI-MODEL-ADM-、AC-AI-MODEL-FE-、AC-AI-MODEL-BE-*。
5.3 联调检查清单
- Flyway V6 执行成功;
ai_chat_session.model_id回填(线上库已 V6) - 管理端 CRUD、默认互斥、禁止删唯一启用(单测 + API IT + E2E)
- 用户下拉仅启用模型;
localStorage恢复(代码 + E2E 选模) - 新会话绑定所选模型;同会话不改绑(单测 + 设计)
- 禁用模型后旧会话只读、
POST /chat5009(IT + E2E-19) - 助手页 无 图片/语音上传入口
- 摘要/润色仍走
dashscope.model
6. 待办事项
状态说明:⬜ 未开始 🔄 进行中 ✅ 已完成
| 序号 | 阶段 | 待办项 | 状态 |
|---|---|---|---|
| 1 | 数据库 | 新增 V6__ai_assistant_model.sql(建表、种子、session.model_id 回填) |
✅ 已完成 |
| 2 | 后端 | 实体 AiAssistantModel、AiChatSession.modelId |
✅ 已完成 |
| 3 | 后端 | AiAssistantModelMapper |
✅ 已完成 |
| 4 | 后端 | DTO/VO:AiAssistantModelSaveDTO、AiAssistantModelVO、AiAssistantModelAppVO、AiChatSessionMessagesVO、CreateAiChatSessionDTO |
✅ 已完成 |
| 5 | 后端 | AiChatReadOnlyReason 枚举 |
✅ 已完成 |
| 6 | 后端 | ErrorCode 新增 5007~5011 |
✅ 已完成 |
| 7 | 后端 | AiAssistantModelService + Impl(CRUD、默认、校验) |
✅ 已完成 |
| 8 | 后端 | AdminAiModelController |
✅ 已完成 |
| 9 | 后端 | AiChatController 增加 GET /models;sessions 支持 body |
✅ 已完成 |
| 10 | 后端 | AiChatServiceImpl 改造(绑定、禁用校验、modelCode、消息包装) |
✅ 已完成 |
| 11 | 后端 | 单元/集成测试(AiAssistantModelServiceImplTest;AiChatServiceImplTest / AiChatServiceIntegrationTest 已适配并通过) |
✅ 已完成 |
| 12 | 前端 | api/aiModel.js |
✅ 已完成 |
| 13 | 前端 | views/admin/AiModels.vue |
✅ 已完成 |
| 14 | 前端 | AdminLayout + router 注册 /admin/ai-models |
✅ 已完成 |
| 15 | 前端 | api/aiChat.js 适配新 sessions/messages 结构 |
✅ 已完成 |
| 16 | 前端 | AiAssistant.vue 模型列表、localStorage、readOnly 逻辑 |
✅ 已完成 |
| 17 | 前端 | AiChatPanel.vue 模型选择器 + 停用提示条 |
✅ 已完成 |
| 18 | 联调 | 管理端上架第二模型 → 用户新会话切换 → 对话成功 | ✅ 已完成(AiModelManagementApiIT + e2e/tests/ai-model-management.spec.js TC-E2E-18) |
| 19 | 联调 | 禁用模型 → 旧会话只读 + chat 5009 | ✅ 已完成(AiModelManagementApiIT + e2e/tests/ai-model-management.spec.js TC-E2E-19) |
| 20 | 验收 | 按需求 V1.1 验收标准逐项确认 | ✅ 已完成(见 AI助手模型管理功能验收报告.md) |
7. 实施顺序建议
- Flyway V6 → 本地启动验证表与种子、session 回填
- 模型 Service + Admin Controller → Postman 测通 CRUD
- AiChatService 改造 → 单元测试 → 与前端联调 sessions/chat/messages
- 管理后台页面 → 运营可配模型
- 助手页选择器 + 只读 → 禁用场景联调
- 回归 原 AI 助手 5 轮、重试、限流不受影响
8. 风险与注意事项
| 风险 | 说明 | 缓解 |
|---|---|---|
| 种子 model_code 与环境不一致 | SQL 写死 qwen-turbo |
上线后管理端改编码或重建默认行 |
listMessages 破坏性变更 |
由 List 改为包装对象 |
前后端同 PR 发布;仅 AI 助手页消费 |
| 逻辑删模型后 session.model_id 悬空 | 展示「已下架」 | getById 自定义查询含 deleted 或冗余 displayName 快照(首版联查+兜底文案) |
| 禁用与满 5 轮双重只读 | 文案冲突 | 前端优先展示 MODEL_DISABLED |
| 误开上传 UI | 违背 D-13 | Code Review 确认 Panel 无 upload 组件 |
9. 需求追溯矩阵(摘要)
| 需求编号 | 实施落点 |
|---|---|
| FR-AI-MODEL-ADM-* | 待办 7、8、13、14 |
| FR-AI-MODEL-FE-* | 待办 15~17 |
| FR-AI-MODEL-BE-* | 待办 1~11 |
| D-08 ~ D-13 | 待办 10、16~19 |
| NFR-02 ~ NFR-05 | 待办 6、8、10、11 |
编写人: AI 助手
评审人: 待评审
生效日期: 2026-05-23
进度摘要(V1.4): 待办 1~20 已完成;核心 AC 自动化通过,FE-09 列为非阻塞缺口(见验收报告 §4)。