FSD M12 · 管理后台
← 总则 | 附录 → | 上游:PRD §4 M12 实现落点:
backend/internal/domain/admin/(model 276/repo 476/service 332/service_billing 416/service_prompts 250 单包多文件+三测试件)+cmd/server/admin_handlers.go(562 行 39 handler)+cmd/server/routes.go:268-318(挂载)。事实基准=Go 代码;冻结版qmtv/server/index.js(admin 段 app.* 直挂非 router,19−withdrawals 2=17)+llmroutes.js(18)+routes2.js(admin 4)仅差异对拍参照。 域内要点:39 条全部 amw+adminMw 双挂(403forbidden,gateway/auth.go:133-141=冻结 adminRequired 逐字);前端消费面≠端点族——AdminView 14 tab(AdminView.vue:667-682)中 submissions/withdrawals/scripts 三 tab 消费 M9/M7 域端点,本册 39 条按后端功能族 AH 组聚合;404 八态八哨兵刻意不同源(model.go:16-36:用户调整英文 “not found”/密码重置中文「用户不存在」/org approve 英文/指令 DELETE 英文/提示词删「版本不存在」/接入「接入不存在」/release「任务不存在或不是失败态」/重拆「任务不存在」);bind 失败统一 400「请求体格式错误」(与 m08 裸传 return err 两域不同制);⑨(prd-qmtv-v1.md:222)引用不重立。api_keyjson:"-"masked-only(二十一期②收敛,PRD ④);GetActivePrompt(整行+split 三级)↔activePromptContent(content-only 单级)两份同语义并存(二十二期 C1 裁决 a,task_assembly.go:188-189 注释「两处改动须同步对拍」)。PRD §4 M12 已登记差异 ①
M12 管理后台
39 端点全量(冻结三文件 41 物理 − withdrawals 2 归 M7;对账脚注见册尾)。静态段优先三处:credit-records/export、instruction-versions/active、prompts/upload 与 :id 参数段并存——echo radix 静态子节点恒先匹配(routes.go:270-272 注)。
F12.01 概览与运行时观测(summary/runtime)
对应 PRD:§4 M12 · GET /api/admin/summary、GET /api/admin/runtime/tasks | 状态:实现 | 实现落点:admin_handlers.go:19-35/:127-135、service.go:63-65/:236-246、repo.go:91-109/:215-229 | UI:AdminView.vue(概览 tab+运行时面板 30s 轮询)| 场景挂钩:E2E-ADM-01(✅ 只读巡检断言 .stat≥5);AdminChain 集成锚
1 功能定义
全站六计数概览+运行中视频/拆解双列表观测(前端 30s 轮询面)。
2 触发条件与前置状态
双挂+requireDB;零参数零 body(无 bind 路径)。
3 输入与校验
无输入。
4 处理流程
summary:六条独立 SELECT 顺序执行无事务(repo.go:91-109=冻结同形)。runtime:ListRuntimeVideos(status IN ('processing') LIMIT 50)+ListRuntimeSplits(split_status='processing' LIMIT 50)。
5 输出与结果状态
summary 顶层键序 users,tasks,images,videos,credits_granted,credits_consumed(model.go:63-70=冻结 :641-648 展开序);runtime 键序 videos,splits,videos 行键 id,status,seconds,resolution,created_at,email,title(title *string 可空)、splits 行键 id,title,created_at,email。
6 状态流转
只读;不订阅 SSE(M12 全程无 bus.emit——repo.go:252-253 注,与 m08 recharge SSE 对照面)。
7 边界与异常
无哨兵路径;DB 错误 → errRespond default 500。
8 权限与数据规则
quirk:六计数非事务一致性(计数间可撕裂,冻结同形照抄);credits_granted/consumed 以 amount>0/amount<0 符号二分而非 type 字段。
F12.02 用户列表与合并 mutation(users GET/PUT)
对应 PRD:§4 M12 · GET、PUT /api/admin/users(/:id) | 状态:实现 | 实现落点:admin_handlers.go:30-58、service.go:67-98、repo.go:113-153 | UI:AdminView.vue(用户管理 tab——adjustCredits/toggleUserStatus/setAdmin 合并 mutation)| 场景挂钩:AdminChain;E2E-ADM-01(用户行≥3)
1 功能定义
8 列投影全量用户列表+credits 差额调整/status/role 更新的合并 mutation。
2 触发条件与前置状态
双挂+requireDB;PUT 经 GetUserCredits 无行 → 404 英文 “not found”(ErrAdjustNotFound——与 F12.03 中文 404 刻意不同源)。
3 输入与校验
GET 零参数。PUT body credits *int64(指针透传=冻结 credits != null 判空)+status/role string(空串跳过 truthy 才更新);bind 失败 → 400「请求体格式错误」。
4 处理流程
AdjustUser(service.go:74-98):①GetUserCredits ErrNoRows→404;②delta != nil → credits.Add(u.ID, float64(*delta)-u.Credits, admin_adjust, "管理员调整积分", "")——Δ=req.credits−u.credits 现值差;③status/role 各自独立 UPDATE 无事务(users 无 updated_at 列不写=冻结同形)。ErrNoUser 吞掉(PRD ③——冻结 addCredits 静默 no-op)。
5 输出与结果状态
GET 行键序 id,email,nickname,role,credits,status,referral_code,created_at(referral_code *string NULL→null);PUT data:true。无分页无搜索(前端 filteredUsers 客户端过滤,repo.go:113)。
6 状态流转
users.status/role 直改;credits 经 credits.Add 唯一口落 credit_records(type=admin_adjust)。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 用户不存在 | 404 | “not found”(英文——八态之一) |
| ErrNoUser(并发删除窗口) | 200 | 吞掉静默(PRD ③) |
| bind 失败 | 400 | 「请求体格式错误」 |
8 权限与数据规则
quirk:差额调整语义(前端传绝对值后端算 Δ);跨域共表 users 本域持有写。
F12.03 密码重置(users/:id/password POST)
对应 PRD:§4 M12 · POST /api/admin/users/:id/password | 状态:实现 | 实现落点:admin_handlers.go:60-76、service.go:100-119、repo.go:131-159 | UI:AdminView.vue(用户管理 tab resetPassword)| 场景挂钩:AdminChain
1 功能定义
管理员重置用户密码(校验序 404→400 十六续 A1 修序)。
2 触发条件与前置状态
双挂+requireDB;UserExists 不存在 → 中文 404「用户不存在」(ErrUserNotFoundCN——八态之二)。
3 输入与校验
body password string;bind 失败 → 400「请求体格式错误」;len(password) < 8 → 400「密码至少 8 位」(冻结 index.js:143 逐字)。先存在后长度(顺序对齐冻结 :140-142,service.go:100-102 注「十六续 A1 修序」)。
4 处理流程
UserExists → 长度校验 → crypto.HashPassword → UpdateUserPassword 单列覆盖(repo.go:156-159)。
5 输出与结果状态
data:true。
6 状态流转
users.password_hash 覆写;refresh_tokens 不动(不撤销 refreshToken=冻结语义 quirk,repo.go:155-156)。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 用户不存在 | 404 | 「用户不存在」(中文——与 F12.02 英文 404 分叉) |
| 密码 <8 位 | 400 | 「密码至少 8 位」 |
8 权限与数据规则
quirk:len() 字节序无 trim(冻结 String(req.body.password||'') 空串→len 0→400 同效);旧 refreshToken 仍有效。
F12.04 发放流水与发放(credit-grant-records GET/POST)
对应 PRD:§4 M12 · GET、POST /api/admin/credit-grant-records | 状态:实现 | 实现落点:admin_handlers.go:79-109、service.go:126-141、repo.go:162-175 | UI:AdminView.vue(积分 tab doGrant——全页唯一跨分区联动 grantRecords+users 双刷)| 场景挂钩:AdminChain
1 功能定义
发放流水列表(amount>0 跨分区联动)+向用户发放积分+通知。
2 触发条件与前置状态
双挂+requireDB;POST userID=="" || amount==0 → 400「参数错误」(冻结 :667 !user_id || !amount)。
3 输入与校验
POST body user_id/amount int64/description(缺省「管理员发放积分」);bind 失败 → 400「请求体格式错误」。
4 处理流程
credits.Add(userID, amount, admin_grant, desc, "")——task_id 空(冻结 4 参调用 :668),ErrNoUser 吞掉(冻结静默但通知照发 service.go:126-127 注)→ InsertNotification:title「积分到账」+content 管理员为您发放 %d 积分:%s(冻结 :669 逐字)。
5 输出与结果状态
GET 行键序 id,user_id,task_id,amount,balance_after,type,description,created_at,email,nickname(task_id *string NULL 透传、amount float64——0004 DECIMAL);LIMIT 200;无 type 过滤仅 amount>0(quirk:充值 credit/admin_grant/admin_adjust/release 退款全部入「发放流水」列表——跨分区联动面)。POST data:true。
6 状态流转
credit_records 新行 type=admin_grant + notifications 新行。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| user_id 空或 amount=0 | 400 | 「参数错误」 |
| 用户不存在 | 200 | ok:true 且通知照插(notifications 无 FK 阻断——PRD ③ 吞形) |
8 权限与数据规则
quirk:用户不存在仍 ok:true 通知照发(通知 userId 为不存在 id=冻结 :669 无条件执行同形)。
F12.05 流水导出 CSV(credit-records/export GET)
对应 PRD:§4 M12 · GET /api/admin/credit-records/export | 状态:实现 | 实现落点:admin_handlers.go:111-124、service.go:202-233、repo.go:178-182 | UI:AdminView.vue(积分 tab 导出按钮 fetch+blob)| 场景挂钩:无专项 e2e(AdminChain);静态段优先第一处(routes.go:270-272 注)
1 功能定义
全量流水 CSV 直出(Excel 可开)——非 JSON 信封端点。
2 触发条件与前置状态
双挂+requireDB;零参数零 body。
3 输入与校验
无输入。
4 处理流程
ListExportRows(7 列 JOIN DESC LIMIT 10000)→ "" BOM → 头行 时间,用户邮箱,昵称,类型,事项,积分,余额 → 每行 7 字段全量引号包裹("→"" 转义)+CRLF 分隔 → 时间 2006-01-02 15:04:05、数值 FormatFloat。handler:Content-Type text/csv; charset=utf-8 + Content-Disposition attachment + WriteHeader(200) 直写。
5 输出与结果状态
CSV 文本流(列序 created_at,email,nickname,type,description,amount,balance_after);LIMIT 10000 静默截断无溢出标记。
6 状态流转
只读。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| DB 错误 | 500 | JSON 信封(错误路径与成功路径信封分裂——admin_handlers.go:116-118) |
8 权限与数据规则
quirk:全字段强制双引号包裹(冻结 :196-197 同形);BOM+CRLF;10000 截断静默。
F12.06 生成审计与人工释放(failures/release)
对应 PRD:§4 M12 · GET /api/admin/generation-audit/failures、POST …/videos/:id/release | 状态:实现 | 实现落点:admin_handlers.go:140-159、service.go:146-184、repo.go:187-212+generation/repo.go:214-222 | UI:AdminView.vue(任务审计 tab)| 场景挂钩:AdminChain(47=7+5×8 videoCost 联动断言)
1 功能定义
失败视频+失败拆解双列表观测+失败视频人工退款释放(CAS 认领制)。
2 触发条件与前置状态
双挂+requireDB;release 仅路径 :id 零 body;FailedVideoByID(窄投影 5 列——error TEXT NULL 不进投影防 scan 坑,repo.go:203-204 注)ErrNoRows → 404。
3 输入与校验
GET 零参数;release 无 body 无 bind。
4 处理流程
failures:ListFailedVideos(status='failed' LIMIT 100,LEFT JOIN tasks)+ListFailedSplits(status='failed' OR split_status='failed' LIMIT 100)。release:①查行 404 → ②ClaimManualRelease(UPDATE videos SET refunded=1, error=CONCAT(COALESCE(error,''),'(已人工退款)') WHERE id=? AND refunded=0——CAS 认领+error 追加一步)affected=0 → 400「该任务已自动退款」 → ③cost=billing.VideoCost(secs)(secs<=0 归 5=冻结 seconds||5) → ④credits.Refund type=release desc「人工释放:失败视频退款(+%d)」。冻结序「查→检查 refunded→加币→置位」有并发双退窗,Go 认领前置收口(service.go:158-160 注,PRD ⑥)。
5 输出与结果状态
failures 键序 videos,splits;videos 行键 id,error,seconds,resolution,attempts,refunded,created_at,email,task_title,user_id(error/task_title *string NULL→null);splits 行键 id,title,error,created_at,email(tasks.error 已 0002 收紧 NOT NULL)。release {"refunded": cost}。
6 状态流转
videos:failed → refunded=1 + error 尾缀追加;credit_records type=release 行;无通知。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 非 failed 态/不存在 | 404 | 「任务不存在或不是失败态」(八态之三) |
| CAS affected=0(已退) | 400 | 「该任务已自动退款,无需重复处理」(冻结 :173 逐字——Go 由 CAS 同义认领) |
8 权限与数据规则
跨域共表——videos 写归 generation(ClaimManualRelease 窄接口 service.go:40-42);退款额随 credit-settings 写侧联动(改 video_pull 后生效——集成断言 47=7+5×8)。
F12.07 重拆(retry-split POST)
对应 PRD:§4 M12 · POST /api/admin/generation-audit/tasks/:id/retry-split | 状态:实现 | 实现落点:admin_handlers.go:162-169、service.go:188-197、task/service.go:469-485 | UI:AdminView.vue(任务审计 tab)| 场景挂钩:AdminChain
1 功能定义
任务清分镜后 Mock 重拆(跨域共表写归 task 域 AdminRetrySplit)。
2 触发条件与前置状态
双挂+requireDB;仅路径 :id 零 body;AdminTaskByID ErrNoRows → 404。
3 输入与校验
无 body 无 bind。
4 处理流程
AdminRetrySplit(task/service.go:469-485):①AdminTaskByID → ②ClearShotImagesByTask(不删根资产=冻结无此步,重复重拆根资产重复系冻结原样,PRD ⑥)→ ③DeleteShotsByTask → ④fillWithMock 直插 → ⑤RecoverSplitDone(status='complete', split_status='done'=冻结 :187)。
5 输出与结果状态
data:true。
6 状态流转
tasks 任意态 → complete/done;shots+shot_images 销毁重建;assets 根资产不动。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 任务不存在 | 404 | 「任务不存在」(八态之四,ErrTaskNoExist——冻结 index.js:182) |
| handler 层 | 200 | 恒 ok(service 返 nil 即 OK,admin_handlers.go:168) |
8 权限与数据规则
tasks/shots/shot_images 共表写归 task 域(SplitRefiller 窄接口,TECH.md §2.1 共表登记);无用户属主校验(admin 全权=冻结)。
F12.08 公告管理(announcements 三件)
对应 PRD:§4 M12 · GET、POST /api/admin/announcements、PUT /api/admin/announcements/:id | 状态:实现 | 实现落点:admin_handlers.go:174-220、service.go:257-281+325-332、repo.go:234-257 | UI:AdminView.vue(公告 tab createAnn——busy quirk 同步复位照抄勿修)| 场景挂钩:AdminChain(扇出区间断言抗并发)
1 功能定义
公告 CRUD 面(读/建+全量扇出/toggle 置位——无 title/content 编辑端点,冻结同构)。
2 触发条件与前置状态
三处双挂+requireDB;POST title=="" || content=="" → 400「标题和内容必填」;PUT 无存在性校验(不存在 id 也 ok:true)。
3 输入与校验
POST body title/content(bind 失败 → 400「请求体格式错误」);PUT body active bool。
4 处理流程
CreateAnnouncement:an_ id → InsertAnnouncement 三列(active 落 DDL 默认 1)→ ListUserIDs 全量 → 逐用户 INSERT notification 扇出:title「新公告:」+title、content truncateRunes(content, 100)(rune 截断=冻结 content.slice(0,100) 等价)——循环内任一失败即中断返回 err(部分扇出无回滚=冻结 forEach 同形)。ToggleAnnouncement:单列 UPDATE 恒 ok(repo.go:246 注)。
5 输出与结果状态
GET 行键序 id,title,content,active,created_at;POST {"id"};PUT true。无 LIMIT。
6 状态流转
announcements.active 布尔位;notifications 全量扇出行。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| title/content 空 | 400 | 「标题和内容必填」 |
| PUT 不存在 id | 200 | 恒 true 静默 0 行 |
8 权限与数据规则
quirk:content 截 100 rune 只作用于通知、公告正文存全量;active wire bool(vs 冻结 1/0——DIF-31-3);公开读镜像归 M5(routes.go:102 pub 无 amw)。
F12.09 组织审批(organization-applications 三件)
对应 PRD:§4 M12 · GET、POST /api/admin/organization-applications(/:id/approve|/:id/reject) | 状态:实现 | 实现落点:admin_handlers.go:222-254、service.go:292-323、repo.go:262-301 | UI:AdminView.vue(orgAppReview tab——approve/reject 合并 mutation)| 场景挂钩:AdminChain(reject 幽灵 id 恒 ok 断言)
1 功能定义
组织入驻申请列表+审批(approve 建 org 四写+通知)+驳回(无条件恒 ok)。
2 触发条件与前置状态
三处双挂+requireDB;三端点均仅路径 :id 零 body 无 bind;approve 仅 pending 命中否则英文 404(WHERE id=? AND status='pending' 窄投影 3 列——reason TEXT NULL 不进投影)。
3 输入与校验
无输入。
4 处理流程
approve 四写+通知零事务(service.go:292-318=冻结 :692-702 逐条直译):UpdateOrgAppStatus ‘approved’ → InsertOrganization(org_,credits=0)→ InsertOrgMember(om_ role=‘owner’)→ SetUserOrg(users.org_id 回填)→ 通知「企业入驻审核通过」+组织「%s」已创建,您是管理员。(冻结 :700 逐字)。reject:无条件 UPDATE ‘rejected’,不存在也 ok:true(service.go:321-323——与 approve 严格不对称)。
5 输出与结果状态
GET 行键序 id,user_id,company,contact,reason,status,created_at,email——version int64 json:"-" 不进 JSON(model.go:177-187);reason *string NULL→null。approve {"org_id"}(冻结 :701);reject true。无 LIMIT。
6 状态流转
org_applications:pending→approved(仅此路径有守卫);pending/任意态→rejected(无守卫)。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| approve 非 pending/不存在 | 404 | “not found”(英文——八态之五;approve 幂等性缺失二跑 404) |
| reject 不存在 | 200 | 恒 true(幽灵 id 静默——集成 oa_ghost2 断言) |
8 权限与数据规则
quirk:四写零事务(中途失败留半态=冻结逐条 run 同形);organizations/org_members/org_applications/users.org_id 跨 M6 共表但本域直写(model.go:3-5 自持表登记)。
F12.10 计费配置(credit-settings/model-settings 四件)
对应 PRD:§4 M12 · GET、PUT /api/admin/credit-settings、GET、PUT /api/admin/model-settings | 状态:实现 | 实现落点:admin_handlers.go:256-287、service_billing.go:3-109、repo.go:307-326 | UI:AdminView.vue(计费 tab saveBilling——只写局部零刷新三零刷新面之一)| 场景挂钩:无专项 e2e(AdminChain)
1 功能定义
system_settings KV 配置读写(五键积分+四键模型名;GET/PUT 两面共用全量十键白名单)。
2 触发条件与前置状态
四处双挂+requireDB;PUT 任意 JSON 对象——白名单外键静默忽略恒 ok(service_billing.go:80-90)。
3 输入与校验
PUT body map[string]any(bind 失败 → 400「请求体格式错误」);无键校验。
4 处理流程
GET:逐键 settingOrDefault(无行回退 defaultSettings 十键=冻结 routes2.js:26-37 DEFAULT_SETTINGS)。PUT:遍历 body → 白名单键 UpsertSetting(k, jsString(v))(ON DUPLICATE KEY upsert;值恒字符串 jsString:null→“null”/bool→“true”)。读侧零失效:genBilling/modelCfg 每请求现查 DB,UPSERT 后下一请求即生效(service_billing.go:3-5 注)。
5 输出与结果状态
credit-settings 键序 trial_credits,text_tokens_per_credit,image_generation,video_pull,video_per_second;model-settings 键序 text_model,image_model,image_model_fallback,video_model——值恒字符串;冻结键名 image_model_fallback(PRD 曾误写 image_fallback_model 勿跟)。PUT data:true。
6 状态流转
system_settings KV upsert;provider_routing 在白名单内但两 GET 均不回显(冻结 :36 同形)。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| PUT 恒 | 200 | ok:true(除 bind 外永不 400) |
| 值非数字(读侧消费) | — | Go 回退默认 vs 冻结 NaN→JSON null(PRD M8 ④ 同制——getSettingNum 0 值回退 quirk) |
8 权限与数据规则
quirk:PUT /credit-settings 可写 text_model 等、PUT /model-settings 可写 trial_credits 等(同一全量十键白名单——冻结 :74-79/:84-88 同一循环同形照抄勿"修复");死端点:model-settings GET/PUT 前端零调用(PRD ②)。
F12.11 模型接入(model-endpoints 六件)
对应 PRD:§4 M12 · GET、POST /api/admin/model-endpoints、PUT、DELETE …/:id、POST …/:id/activate、POST …/:id/test | 状态:实现 | 实现落点:admin_handlers.go:289-370、service_billing.go:114-162/:182-300/:306-416、repo.go:331-386 | UI:AdminView.vue(模型接入 tab+meTest
.catch(()=>{})防御死代码注)| 场景挂钩:无专项 e2e(AdminChain;api_key 不回显断言)
1 功能定义
模型接入注册表全生命周期(列表+建+改+删+激活+三分支连通测试)。
2 触发条件与前置状态
六处双挂+requireDB;PUT/DELETE/activate/test 经 GetEndpointByID 404 前置 → 「接入不存在」(八态之六,四路同源)。
3 输入与校验
POST body purpose/name/base_url/api_key/model(purpose ∉ {text,image,video} → 400「未知模型用途」;name/base_url/model 任一空 → 400「显示名、接口地址、模型名必填」;name 截 60 rune、base_url/api_key/model TrimSpace);PUT body 五指针(nil 即未传)。bind 失败 → 400「请求体格式错误」。
4 处理流程
List:ensureEndpointSeed(表空且三 KV 全非空 → 插「文本接入(迁移)」+置 active=冻结 models.js:12-24)→ 全量列表 → maskEndpoint(api_key 非空 → 前 6 rune+******;has_key=api_key!="")→ active 三键(KV active_model_<p> → 行按 id+purpose 不查 enabled)。Create:me_ 行 enabled=1 → 该用途首个接入自动置 active(冻结 models.js:64)。Update:api_key 空串=保留旧值 SQL CASE(repo.go:374-380 CASE WHEN ? IS NULL OR ?='' THEN api_key ELSE ? END;其余 COALESCE)。Remove:删行 → 删的是 active 则 DeleteSetting(active_model_
5 输出与结果状态
GET 键序 purposes,endpoints,active(purposes 各 {name,usage} 文案=冻结 models.js:6-10 逐字);endpoints 行键序 id,purpose,name,base_url,model,enabled,created_at,api_key_masked,has_key——api_key json:"-" 不回显(二十一期②收敛,PRD ④——wire 与冻结分叉一处);active 三 *string(id‖null)。POST {"id"};PUT/DELETE true;activate {"purpose"};test {ok, reply? omitempty, note}。
6 状态流转
model_endpoints 行 + system_settings active_model_<purpose> KV;enabled 位由 PUT 任意翻转不联动 active KV(展示面 getActiveRaw 不查 enabled vs 启用面 getActive 才查——冻结两函数分工同构)。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| id 不存在(四路) | 404 | 「接入不存在」 |
| purpose 非法 | 400 | 「未知模型用途」 |
| 必填空 | 400 | 「显示名、接口地址、模型名必填」 |
| test 超时/失败 | 504/502 | 「连接超时」/HTTP %d:%s(TestError 携带 Status 直出) |
8 权限与数据规则
quirk:ensureSeed 每次 GET 触发(表非空即短路);api_key 明文仍入库仅 wire 隐藏(AdminView 只消费 masked 零消费实证);测试直连用户提供的 base_url(SSRF 面与冻结同构);video 探测无鉴权头纯 GET 可达性。
F12.12 提示词中心(prompts 六件)
对应 PRD:§4 M12 · GET、POST /api/admin/prompts、POST /api/admin/prompts/upload、PUT、POST …/:id/activate、DELETE …/:id | 状态:实现 | 实现落点:admin_handlers.go:372-537、service_prompts.go:29-44/:82-175/:244-250、repo.go:393-473 | UI:AdminView.vue(prompts tab——草稿保护 dataUpdatedAt 守卫面)| 场景挂钩:无专项 e2e(集成第十五链 PromptTemplateWiring 覆盖配置即生效)
1 功能定义
八分类提示词版本管理(分类元数据+建/传/改/激活/删;POST 缺省激活、upload 恒激活)。
2 触发条件与前置状态
六处双挂(:309 upload 额外挂 up2 2mb——全域唯一三中间件端点,对齐冻结 multer fileSize);PUT/activate/DELETE 经 GetPromptByID → 404「版本不存在」(八态之七,三路共用)。
3 输入与校验
POST body category/name/script_type/content/activate *bool(activate !== false 缺省激活);upload multipart 单文件 file(FormFile 失败 → 400「缺少文件」;超 2MB → 500「File too large」multer LIMIT_FILE_SIZE 信封——状态码 500 非 400;扩展名 ∈ {.txt,.md,.markdown} 否则 400「仅支持 .txt / .md 文件」);PUT body 三 *string COALESCE;service 校验 category 八键白名单 → 400「未知的提示词分类」、content trim 空 → 400「提示词内容不能为空」。
4 处理流程
GET:ListPromptsAll(ORDER BY category, is_active DESC, created_at DESC)→ 遍历八分类(CATEGORIES=冻结 promptCenter.js:7-16 逐字)→ GetActivePrompt(key, "")(scriptType 空直落单级——三级逻辑只在 split+scriptType!="" 触发)→ count 循环。createPromptVersion 共核:name 空 → <分类名> 新版本、截 60 rune、script_type 白名单 {all,domestic,overseas} 仅 split 生效否则 ‘all’ → InsertPrompt is_active=0 → activate 则 ActivatePrompt(先全类清 0 再置 1 互斥两步,repo.go:461-467)。upload:name 缺省 regexpStripExt(originalName)(=冻结 replace(/.[^.]+$/,''))→ 恒激活。DELETE:row.IsActive → 400「生效中的版本不可删除,请先切换其他版本」→ 删。
5 输出与结果状态
GET 键序 categories,versions;categories 元素键 key,name,desc,placeholders,scriptScoped,active,count——scriptScoped 驼峰 *bool omitempty 仅 split 有键(=JS {...c} 展开只带既有键);versions 行键 id,name,category,script_type,content,is_active,created_at。POST {"id","activated"};upload {"id","name"}(fixFilename 后全名含扩展名);PUT/activate/DELETE true。无 LIMIT。
6 状态流转
prompt_templates.is_active 互斥位(每分类至多一行 1);激活即切、旧版静默下线无确认。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 分类非法 | 400 | 「未知的提示词分类」 |
| content 空 | 400 | 「提示词内容不能为空」 |
| 扩展名非法 | 400 | 「仅支持 .txt / .md 文件」 |
| 删 active 版本 | 400 | 「生效中的版本不可删除,请先切换其他版本」 |
| 无文件(upload) | 400 | 「缺少文件」 |
| 超 2MB(upload) | 500 | 「File too large」(multer LIMIT_FILE_SIZE 信封) |
| id 不存在 | 404 | 「版本不存在」 |
8 权限与数据规则
quirk:POST 缺省激活 vs instruction POST 不激活相反默认(F12.13 交叉);upload 恒激活(不同入口同效);不做 in-band ensureSeed(cmd/seed 表级哨兵补种 5 行——PRD ⑧);upload 文件全量进内存(2MB 双保险);C1 裁决注记:本域 GetActivePrompt(整行+split 三级 repo.go:413-434)↔ 生成链 activePromptContent(content 单级+content 空视同缺失 task_assembly.go:185-200)两份刻意并存不收敛(返回形态不同+domain 不可导入 package main),两处改动须同步对拍。
F12.13 指令版本(instruction-versions 六件)
对应 PRD:§4 M12 · GET、GET /api/admin/instruction-versions/active、POST、PUT、POST …/:id/activate、DELETE …/:id | 状态:实现 | 实现落点:admin_handlers.go:375-451、service_prompts.go:4-77/:216-231 | UI:AdminView.vue(指令版本 tab——十一续④拆解模板级联消费面)| 场景挂钩:无专项 e2e(集成第十五链覆盖)
1 功能定义
split 分类指令版本管理(prompt_templates category=‘split’ 薄委托——instruction_versions 死表不接线)+当前生效指令读取(三级+内置兜底)。
2 触发条件与前置状态
六处双挂+requireDB(:314 active 为静态段优先第三处);PUT/activate 404「版本不存在」(与 prompts 同源);DELETE 404 英文 “not found”(ErrVersionNotFoundEN——八态之八,与 prompts DELETE 的「版本不存在」刻意不同源照抄)。
3 输入与校验
GET active query script_type(缺省归 ‘all’=冻结 ||'all');POST body name/script_type/content(content trim 空 → 400「指令内容不能为空」);PUT body 三 *string(无 script_type 白名单直写任意值=冻结 promptCenter.js:101 原样——与 POST 白名单不对称)。
4 处理流程
versions=ListPromptsByCategory(‘split’);active=GetActivePrompt('split', scriptType) 三级(split+script_type → split+‘all’ → category 单级)→ ErrNoRows 兜底 {name:"内置默认", content: DefaultInstruction}(两形态响应:命中行=PromptRow 全键 / 未命中=两键);CreateInstruction → createPromptVersion(…, false) 不激活(与 prompts POST 相反默认);Update/Activate 与 F12.12 完全共用;DeleteInstruction 按修后语义打 prompt_templates(冻结查删 instruction_versions 死表恒 404——Go 修后:404 → IsActive 400 同文案 → 删,PRD ⑤)。
5 输出与结果状态
versions=PromptRow 数组(同 F12.12 键序);active 两形态(行全键 ‖ {name,content} 两键);POST {"id"};PUT/activate/DELETE true。
6 状态流转
prompt_templates(category=‘split’)is_active 互斥位;instruction_versions 死表(service_prompts.go:4-5 注)。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| content 空(POST) | 400 | 「指令内容不能为空」 |
| PUT/activate id 不存在 | 404 | 「版本不存在」(中文——与 prompts 同源) |
| DELETE id 不存在 | 404 | “not found”(英文——八态之八刻意分叉) |
| DELETE active 版本 | 400 | 「生效中的版本不可删除,请先切换其他版本」 |
8 权限与数据规则
quirk 汇总:①POST 不激活 vs prompts POST 缺省激活相反默认;②DELETE 双 404 文案分叉(EN)+400 中文保持(PRD ⑤);③active 死端点照抄(PRD ② 前端零调用);④PUT 无 script_type 白名单与 POST 不对称;⑤CRUD 纯存储面不接生成链(生成链内置读侧照旧——改动即生效经 GetActivePrompt 查库,service_prompts.go:6 注)。
册尾端点覆盖对账(39/39)
对账 backend/cmd/server/routes.go:275-318 亲数 39 条 ↔ 本册 F12.01~F12.13。口径脚注:M12 组注释「41 物理 admin 路由 − withdrawals 2 条归 M7」(routes.go:268-270)——冻结三文件口径 index.js admin 段 17(app.* 直挂非 router)+ llmroutes.js 18 + routes2.js 4 = 39,冻结-Go 计数自洽无 m10 式错位;withdrawals 2 条(routes.go:150-151)与 submissions 2 条(:199-200)物理不在本段,归 M7/M9。
| # | 端点(routes.go:line) | 节 | # | 端点(routes.go:line) | 节 |
|---|---|---|---|---|---|
| 1 | GET /admin/summary(:275) | F12.01 | 21 | PUT /admin/model-settings(:298) | F12.10 |
| 2 | GET /admin/users(:276) | F12.02 | 22 | GET /admin/model-endpoints(:300) | F12.11 |
| 3 | PUT /admin/users/:id(:277) | F12.02 | 23 | POST /admin/model-endpoints(:301) | F12.11 |
| 4 | POST /admin/users/:id/password(:278) | F12.03 | 24 | PUT /admin/model-endpoints/:id(:302) | F12.11 |
| 5 | GET /admin/credit-grant-records(:279) | F12.04 | 25 | DELETE /admin/model-endpoints/:id(:303) | F12.11 |
| 6 | POST /admin/credit-grant-records(:280) | F12.04 | 26 | POST /admin/model-endpoints/:id/activate(:304) | F12.11 |
| 7 | GET /admin/credit-records/export(:281,静态段优先) | F12.05 | 27 | POST /admin/model-endpoints/:id/test(:305) | F12.11 |
| 8 | GET /admin/runtime/tasks(:282) | F12.01 | 28 | GET /admin/prompts(:307) | F12.12 |
| 9 | GET /admin/generation-audit/failures(:284) | F12.06 | 29 | POST /admin/prompts(:308) | F12.12 |
| 10 | POST /admin/generation-audit/videos/:id/release(:285) | F12.06 | 30 | POST /admin/prompts/upload(:309,+up2 三中间件) | F12.12 |
| 11 | POST /admin/generation-audit/tasks/:id/retry-split(:286) | F12.07 | 31 | PUT /admin/prompts/:id(:310) | F12.12 |
| 12 | GET /admin/announcements(:288) | F12.08 | 32 | POST /admin/prompts/:id/activate(:311) | F12.12 |
| 13 | POST /admin/announcements(:289) | F12.08 | 33 | DELETE /admin/prompts/:id(:312) | F12.12 |
| 14 | PUT /admin/announcements/:id(:290) | F12.08 | 34 | GET /admin/instruction-versions(:313) | F12.13 |
| 15 | GET /admin/organization-applications(:291) | F12.09 | 35 | GET /admin/instruction-versions/active(:314,静态段优先) | F12.13 |
| 16 | POST /admin/organization-applications/:id/approve(:292) | F12.09 | 36 | POST /admin/instruction-versions(:315) | F12.13 |
| 17 | POST /admin/organization-applications/:id/reject(:293) | F12.09 | 37 | PUT /admin/instruction-versions/:id(:316) | F12.13 |
| 18 | GET /admin/credit-settings(:295) | F12.10 | 38 | POST /admin/instruction-versions/:id/activate(:317) | F12.13 |
| 19 | PUT /admin/credit-settings(:296) | F12.10 | 39 | DELETE /admin/instruction-versions/:id(:318) | F12.13 |
| 20 | GET /admin/model-settings(:297,死端点②) | F12.10 |
差异纪律:PRD §4 M12 ①~⑨ 全引用不重立(含二十一期② api_key 收敛/⑥ release 认领前置+retry-split 修后/⑤ DELETE 修后语义/③ ErrNoUser 吞形/② 死端点/⑧ ensureSeed 缺位);本册新立 DIF-31-3(布尔 wire 型 enabled/active/is_active/refunded Go true/false vs 冻结 SQLite 0/1——方言级六节波及)/DIF-31-4(bind 严格性:冻结 +credits/+amount/truthy 宽收数值字符串 vs Go int64 bind 400「请求体格式错误」——F12.02/04,m08 下单 amount_cny 同型扩域)/DIF-31-5(PUT settings 非标量值字符串化 fmt.Sprint(map) vs JS "[object Object]")/DIF-31-6(upload 点文件名 .md 形态 regexpStripExt 保留 vs JS 正则清空走缺省名)入 99-附录 B。