Commit Graph

100 Commits

Author SHA1 Message Date
agent 66cd596a02 审核管理:新增当前用户待办统计接口(按模块聚合)
- GET /api/gxmu/review-list/pending-summary:status=0 且 user_id=当前登录人,
  按 module 聚合并回填注册表标题,供「待办审核」左侧模块导航显示待办数
- 不拿分页结果当计数:避免 limit 上限后数字失真
- 附 sql/20261111_spec58_review_menu.sql:三个子菜单收敛为
  待办审核(14359)/ 审批规则(14355),删除「审核流分配管理」(14356)
2026-11-04 00:53:13 +08:00
weicw ce8dd3e08a fix(sql): 注册表种子 11 行 tenant_id 统一为 10049 2026-11-04 00:09:06 +08:00
weicw a57a824004 fix(sql): 注册表种子 tenant_id 对齐本环境租户(10049) 2026-11-04 00:08:47 +08:00
weicw 3c2e8b082c fix(sql): 回填脚本 LIKE 显式 COLLATE(MySQL 8 上业务表 general_ci 与组织表 0900_ai_ci 混用会报错) 2026-11-04 00:04:26 +08:00
weicw 7696a56740 fix(sql): 注册表不登记 gxmu_tygl(只是前端别名,无业务表,会让启动期一致性校验失败) 2026-11-04 00:01:24 +08:00
weicw 9b6c9fb90c merge: 审查修复(college 写入、逐级解析、文件默认收紧、注册表 all/has_college、层级校验下沉) 2026-11-03 23:16:21 +08:00
weicw 70213ab783 refactor(gxmu): 审批链层级校验下沉到 Service,删除路径也校验剩余链
- ReviewFlowController 里约 115 行的 validateLevels / validateUpdate /
  validateBatchUpdate / validateRemaining / merge 下沉为
  ReviewFlowService.validateLevelsOnSave / OnUpdate / OnBatchUpdate / OnRemove,
  controller 只在写操作前调用一次
- remove / removeBatch 新增删除后剩余链连续性校验;删除前就已不连续的链跳过校验,
  避免存量脏数据变成「永远删不掉」
- 顺手修掉一个真缺陷:merge 会就地改 existing,导致「改名后原链剩余校验」永远不触发
  (批量路径同样先取旧标题再合并)
- 模块标题回退三份收敛成 ModuleRegistryService.titleOf(Map, module) 一处,
  ReviewFlowConfigServiceImpl / ReviewListServiceImpl 直接调用
- ReviewFlowLevelValidator 的手写 join 换成 Collectors.joining(", ")
- 回填脚本头部补「表名 → 学院文本列」对照表,注明新增表按此表同步 4 处

refs #58
2026-11-03 23:14:24 +08:00
weicw 6bfdae5787 fix(security): 文件内联预览默认需登录,公开目录改为显式白名单
- file.public-preview 默认值由 true 改为 false(application.yml 与 @Value 同步),
  上传一律落在 /yyyyMMdd/ 日期目录,默认公开等于把所有上传文件(含盖章证明材料)
  变成匿名可读;公开页面引用的文件必须把所在目录列进 file.public-dirs
- 注释写清「公开页面引用的文件必须列进白名单,否则匿名访问会被拒」,同步更新
  FileAccessRules 类注释
- 删掉只被测试调用的 FileAccessRules.isPublicFileRequest(测试钉住的是没上线的
  平行判定);测试改为断言 downloadPatterns / permitAllPatterns /
  authenticatedPatterns 的内容与顺序
- 顺带把 gxmu.module-registry.strict 改成 ${GXMU_MODULE_REGISTRY_STRICT:true},
  与相邻配置一致

refs #58
2026-11-03 23:14:13 +08:00
weicw 43f0119b25 fix(gxmu): 审批人解析覆盖第 2..N 级,已处理节点再提交直接拒绝
- ReviewFlowStarterService 新增 advanceReview(module, pk, currentSortNumber):
  通过后推进下一级也走 ReviewerResolver,不再 setUserId(nextOne.getReviewUserId());
  解析为空抛可读 BusinessException 且不写节点,最后一级则不生成节点
- ReviewListController 的推进改为调用 advanceReview;ManuscriptController 的首级
  改为 startReview("cms_manuscript", id),cms 稿件不再复制 review_user_id
- 推进前校验当前节点 status == 0(ReviewStatus.PENDING),已处理节点再 PUT 直接拒绝,
  避免重复通过造出第二组活跃节点;批量审批同样逐行校验
- 新增 ReviewStatus 常量(gxmu_review_list.status:0待审 1通过 2驳回),
  把 controller / starter / DeclareGuard / TzbcyFormServiceImpl 里的裸 1、2 换成常量
- 解析失败的兜底消息补上学院与层级,与 ReviewerResolver 口径一致
- 审批动作注释里的「撤销」改成实际语义(通过 / 驳回)

refs #58
2026-11-03 23:14:00 +08:00
weicw be011dd4f1 fix(gxmu): 新申报按学籍补齐 college_id,不再只有存量回填
4 张在跑表(gxmu_sjqn / gxmu_tzbcy_form / gxmu_qmgc_form / gxmu_wxxzx_project)
的实体补 collegeId,save 路径在为空时按「登录用户 → gxmu_student.college_id」
补齐;解析不到(无学籍、名册未同步、学院不唯一)一律留空不报错。
更新路径不调用解析器,collegeId 为 null 时 MyBatis-Plus 忽略该字段,库里已有值不变。

解析逻辑收在一个共享组件 CollegeResolver 里,4 个 controller 只各调一行;
口径与一次性回填脚本一致(优先在校那条学籍,学院不唯一就留空)。

单测:有学籍时写入、无学籍时留空、学院多义时留空、更新不覆盖已有值。

refs #58
2026-11-03 23:13:41 +08:00
weicw 37cc8ee0d5 fix(gxmu): 注册表补 has_college 与团员管理种子,接口返回 DTO 并支持 all=true
- gxmu_module_registry 增加 has_college(4 张在跑表置 1,含已有库补列与补数据语句)
- 种子补 gxmu_tygl_form / gxmu_tygl(enabled=0,团员管理,sort 11/12),
  同步修正末尾「执行后校验」:12 行未删除、5 行启用、has_college 4 行
- GET /api/gxmu/module-registry/list 支持 all=true(默认只返回启用),
  改为返回 ModuleRegistryDTO,不再外泄 tenantId / userId / 时间戳
- ModuleRegistryService:删掉无生产调用的 titleOf(String)/allModules,
  标题回退收敛成唯一的静态 titleOf(Map, module);getByModule 改由 hasCollege 使用
- listAll 只保留 SQL 排序(sort_number 升序,NULL 排最前,与 MySQL ASC 口径一致),
  删掉内存二次排序
- ModuleRegistryIntegrityChecker 增加反向告警:review_flow_config 里有、注册表里没有的
  module 打 warn,不阻断启动

refs #58
2026-11-03 23:13:29 +08:00
weicw 9c4476a395 fix(sql): 注册表种子启用稿件模块(cms_manuscript 有在跑的审核链) 2026-11-03 22:36:52 +08:00
weicw 4c2cf5542e merge: #60 模块注册表后端与启动期一致性校验 2026-11-03 22:31:28 +08:00
weicw 2838bc0d9e merge: #61 审批人规则解析并接入发起与重提 2026-11-03 22:31:19 +08:00
weicw 5c998f39d0 merge: #62 驳回清理旧节点与审批链层级校验
冲突解决:ReviewFlowStarterServiceImpl 保留 #62 的驳回清理与事务注解,
发起/重提仍由 ReviewerResolver 解析取人;合并两份 ReviewFlowStarterServiceImplTest,
置于同一文件(驳回清理 4 个用例 + 审批人解析 4 个用例)。
2026-11-03 22:30:39 +08:00
weicw 51b8dddd22 merge: #64 学院标识一次性回填脚本 2026-11-03 22:29:48 +08:00
weicw 02b2e4b56b merge: spec58/integration(带进 #59 schema 与已合并的集成顶部) 2026-11-03 22:29:27 +08:00
weicw 11f0f90f7a feat(spec58): 模块注册表后端与模块硬编码收敛(#60)
- 新增 gxmu/module 包:ModuleRegistry 实体/Mapper/Service,listEnabled 按 sort_number 升序,titleOf 未注册回退 module 原串
- 新增 GET /api/gxmu/module-registry/list(需登录),返回启用模块元数据
- ReviewFlowConfigServiceImpl / ReviewListServiceImpl 的模块名映射改读注册表,查不到回退原串
- DeclareGuardServiceImpl 的模块集合改为注册表驱动,含 enabled=0(停用模块存量仍可撤回/作废)
- 启动期一致性校验:table_name≠module 或业务表缺失即启动失败,gxmu.module-registry.strict=false 可关闭
- 单测 13 例:注册表加载与排序、titleOf 回退、启动校验、module 名回退
2026-11-03 22:29:27 +08:00
weicw 446a56db72 Merge branch 'spec58/integration' into spec58/t6 2026-11-03 22:28:55 +08:00
weicw 5b7ddd48c7 feat(spec58): 4 张业务表 college_id 一次性回填脚本
以学生档案 gxmu_student.college_id 为准,按 user_id 回填 sjqn / tzbcy_form /
qmgc_form / wxxzx_project 的 college_id;仍为空的行再用表单里的学院文本
按组织名 LIKE 兜底,只有唯一命中才写入,无法匹配留空。

- A 段学生档案主路径,优先取在校记录的学院,档案学院不唯一时留空不猜
- B 段文本兜底,候选学院 = 有平台单位号的节点 ∪ 学生档案引用过的节点
- 只填空不覆盖,可重复执行;不接定时任务
- 附回填率、空值清单、未匹配文本一览与一致性抽查(3a-3d),供人工核对
2026-11-03 22:28:49 +08:00
weicw 58b09b029a merge: #63 审批动作鉴权与文件访问白名单 2026-11-03 22:28:29 +08:00
weicw f96613a0ee feat(spec58): 审批人解析组件 ReviewerResolver 与发起接线
- 新增 ReviewerResolver:USER:<userId> / COLLEGE_ROLE:<roleCode>,; 分隔多段取并集升序
- reviewer_rule 为空回退 review_user_id;按学院解析空集合抛 BusinessException,消息含学院名/层级/缺什么
- ReviewFlowStarterService 发起与驳回重提改由解析取人,解析为空不写任何节点
- ReviewFlow 增加 reviewerRule 字段;DeclareRecordMapper 增加 selectReviewContext(仅 4 张在跑表)
2026-11-03 22:28:02 +08:00
weicw c732cdb037 Merge branch 'spec58/integration' into spec58/t5 2026-11-03 22:26:58 +08:00
Codex d65e8453f5 fix(file): 下载需登录,内联预览与缩略图默认公开
- /api/file/download/** 恒需登录,注册顺序排在公开模式之前
- 新增 file.public-preview(默认 true):内联预览 /api/file/*/* 与缩略图默认匿名
- file.public-dirs 保留为额外公开目录,语义限定在原图与缩略图
- 测试补三条:下载无白名单也需登录、内联默认公开、public-preview=false 时内联需登录
2026-11-03 22:26:52 +08:00
weicw 4b50891fd4 merge: #62 驳回清理旧节点与审批链层级校验 2026-11-03 22:25:44 +08:00
weicw 91b3007939 Merge branch 'spec58/integration' into spec58/t4 2026-11-03 22:25:01 +08:00
weicw 17499cb526 fix(review): 驳回重走时清理旧节点,审批链层级连续性保存校验
- restartReviewWhenLastRejected 先删同一申报记录下旧节点再建第一级,同一事务
- 新增 ReviewFlowLevelValidator,按 title 校验层级从 1 连续升序
- ReviewFlowController 的 save/update/saveBatch/updateBatch 接入校验
- 单测覆盖驳回清理、层级缺级/重复/多链独立校验
2026-11-03 22:24:56 +08:00
weicw b8c9c6c853 Merge branch 'spec58/integration' into spec58/t5 2026-11-03 22:23:42 +08:00
Codex 94071092e3 fix(review): 审批动作校验归属、驳回必须写意见、文件下载需登录
- PUT /api/gxmu/review-list 与 /batch:操作者必须是待办行 user_id,否则 code 403
- status=2 且意见为空时拒绝并提示「驳回必须填写审核意见」
- GET /api/file/** 由匿名改为需登录,公开目录走 file.public-dirs 白名单
- 新增 FileAccessRules 纯规则组件,SecurityConfig 按其产出注册 antMatchers
- 新增 ReviewListControllerAuthTest(8 例)与 FileAccessRulesTest(7 例)
2026-11-03 22:23:35 +08:00
weicw 1f2a837609 merge: #59 schema 脚本(审批人规则列、学院标识列、模块注册表) 2026-11-03 22:21:56 +08:00
weicw fa7633b35e feat(spec58): 审批人规则列、学院标识列与模块注册表
- gxmu_review_flow 增加 reviewer_rule(为空回退 review_user_id)
- 4 张在跑业务表增加可空 college_id,供按学院解析审批人
- 新建 gxmu_module_registry 并写入 10 个模块,仅 4 个在跑模块启用
- information_schema 判存 + INSERT IGNORE,脚本可重复执行
2026-11-03 22:21:22 +08:00
weicw 0e4986330a feat(declare): 同一申报项目进行中只允许一条申报
- 新增 DeclareGuardService:按已通过/未处理驳回/待审核数判重,申报项目行加锁防连点
- 9 张申报表补 declare_id / declare_status / declare_remark,提交时写入并随审核结果回写
- 新增撤回、作废(强制放行)接口;挑战杯团队成员按手机号判重
- 申报项目支持配置允许重复提交与最多同时条数
- 迁移脚本 sql/20261102_add_declare_uniqueness.sql(含存量回填与重复清单)
2026-11-02 19:28:56 +08:00
weicw 76c9edd4be feat(file): 上传准入白名单 + 素材媒体大类筛选
素材库要从「只传图片」放开到文档与音视频,后端必须先有一道类型防线:
/api/file/upload 此前对文件类型零校验,任意文件都能落到公开可读目录。

- UploadRules:image/* 按探测 MIME 放行(历史头像/图标格式多,不做后缀白名单);
  其余按后缀白名单(pdf/doc/docx/xls/xlsx/ppt/pptx/txt/md/csv、
  mp3/wav/m4a/aac、mp4/mov/webm),再用危险 MIME 黑名单兜住 exe 改名。
  不用「扩展名必须等于探测类型」的严格比对:docx/xlsx/pptx 经 Tika 常得到
  application/zip,md/csv 得到 text/plain,严格比对会误杀正常办公文档。
- FileStorageService.store 落盘后校验,本地分支失败删除刚写的文件,不留孤儿。
- FileController 单独捕获 UploadRejectedException,把原因原样回给前端。
- FileRecordParam.mediaCategory + Mapper 展开成 image/video/audio/doc 四类条件:
  「文档」在 MIME 里没有统一前缀,只能落到 application/% | text/%。

设计取舍见 docs/adr/0004(本仓库外,tuanwei-home)。
2026-11-01 21:03:56 +08:00
weicw 9e88c53052 fix: 数据源会话时区强制 +08:00,修复创建时间早 8 小时
生产数据库容器(1Panel-mariadb-ZOpD)的 system_time_zone 是 UTC,而数据源 URL 写着

serverTimezone=Asia/Shanghai:JDBC 会话时区是 UTC、驱动却按 +08 解析服务端返回的字面量,

于是 timestamp 列(分类管理、文章、系统日志、审核流、AI 对话等)读出来整体早 8 小时。

数据源 URL 增加 connectionTimeZone=%2B08:00&forceConnectionTimeZoneToSession=true,

连接建立时把会话时区设为 +08:00,与 JVM 默认时区、spring.jackson.time-zone 保持一致。

用偏移量而非命名时区,避免 MariaDB 容器未加载时区表时报 Unknown or incorrect time zone。

附 sql/20261026_timezone_8h_check.sql:部署后的时区自检、分类管理核对,

以及 datetime 列历史 UTC 字面量与教师建号行的时间回填模板(默认注释未执行)。
2026-10-26 11:09:15 +08:00
weicw 0252672997 fix(ai): 短视频/图片产物归一并转存为对外地址(禅道 #1)
- 归一按内容识别 video_result(此前只认图片):地址字符串、markdown 链接、
  文件对象都统一成 {url,name,type:video},端上四个页面才取得到播放地址,
  修复「上游接口已通、我方未输出视频、复制链接为空」;
- answer 不再残留裸地址:媒体地址命中时改写成「文件名 + 链接」,
  生成记录也补上媒体直链,纯视频分支不再因正文为空而整条不入库;
- 新增 CreativeMediaRelay:上游产物在内网,端上打不开,由后端下载、
  存入本项目文件存储,下发 /api/file 对外地址(Range 续传现成);
  失败只降级保留原地址,不影响本次生成;
- FileStorageService.storeFile 支持把磁盘文件落入本地/对象存储;
- 开发环境关闭转存(公网 demo 地址本就可直接访问),生产默认开启。
2026-10-25 15:55:49 +08:00
b2894lxlx 5626cdd679 fix: 申报可用时间按东八区判断,写入时显式使用北京时间
避免容器 JVM/库会话仍为 UTC 时 createTime 偏 8 小时,以及 usable 查询把刚开放的申报滤掉。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-25 12:20:04 +08:00
weicw 0a2801850d fix(sync): 班级人数与辅导员不再被空值覆盖,人数按名册兜底
开放平台对 2025 级起的班级不返回 BJRS,对 2026 级连 FDYH 都不返回,
而 batchUpsert 用 VALUES() 直写这两列,每晚同步都会把人工补录的辅导员和
学生人数刷成 NULL;新生班级的人数因此一直空着,班级管理页首屏(2026 级)
两列全空。

- ClassInfoMapper.xml: counselor_name / student_count 改为
  COALESCE(VALUES(x), x),平台有值仍以平台为准,给空时保留存量
- 新增 refreshStudentCountFromRoster:学生同步结束时按 gxmu_student
  全量在册统计(不过滤 status,与平台 BJRS 口径一致)回填人数,
  只补「为空或 0」的班级,名册里没学生的班级不动
- StudentSyncHandler: 学生段写完后调用兜底

开发库已用同一语句回填 728 个班级。
2026-10-24 20:41:35 +08:00
weicw 4c2a787d81 feat(ai): 写作/生图/创意/活动策划也保存历史记录
- ai_chat_list 新增 chat_type / params,旧数据默认 chat
- 新增 AiGenerationRecorder:成功后同事务写 list + history,末帧回传 chatId
- 聊天改为成功收尾才建行,失败/中止不再留下空记录
- 写作(含自由创作)、生图、创意助手、活动策划四处接入,软失败与断流不入库
- 历史列表只取最近 30 条(SQL LIMIT),并强制按登录用户过滤
- 修复历史排序用了不存在的 sort_number 列(多行会 NPE)
- 新增 4 个测试类:记录器规则、排序回归、创意成功/软失败、30 条上限
2026-10-24 17:37:39 +08:00
b2894lxlx d54f559d34 fix: 审核列表与申报可见性,并统一 Docker 北京时区
修复团支部等角色待审漏数、PageParam 对 Collection 的 IN 条件失效,以及容器默认 UTC 导致时间偏移 8 小时的问题。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-24 14:55:11 +08:00
weicw-dshsa da5c5c1e09 fix(ai): SSE 帧优先投给在途流式请求,修「生成后按钮永久不可点」
WebSocketServer.send 原本 WS 优先:只要同一账号还有 WS 会话(问答助手页会建
连接),文稿工作台那条 /stream 的帧就全被投到 WS 上,SSE 一帧收不到——正文只能
等末帧,收尾的 __END__ 永远不来,前端 loading 不复位,生成按钮卡死。

改为 SSE 优先:ChatStreamRegistry 是按请求登记的,登记期间发出的帧都属于那条
请求;没有在途请求时 emit 返回 false,照旧走 WS。握手回执「连接成功」改由
register() 直接写给该会话,避免被派发到别的请求。

测试:补两条回归(有在途 SSE 时即使 WS 在线也投 SSE;没有在途 SSE 时仍走 WS),
ai 包 158 条全绿。
2026-10-23 12:57:03 +08:00
weicw-dshsa 206adbff6c chore(build): 项目 JDK 固定为 17,修正 .sdkmanrc 的过期结论
原先钉的是 JDK 8,注释里写「lombok 1.18.20 不支持 JDK 17」。实测不成立:
JDK 17 下 `mvn -o clean compile` 与 158 条 ai 测试全绿;真正炸的是 JDK 21
(NoSuchFieldError: JCTree$JCImport does not have member field 'qualid')。

本机 sdkman 全局默认已切到 21,所以按项目固定 17,不动 sdk default。
2026-10-23 12:57:03 +08:00
weicw-dshsa 1ae2ab580d feat(manuscript): 稿件正文增加 content_format 格式标记
- cms_manuscript 新增 content_format(html/markdown/text),默认 html
- Manuscript 实体加 contentFormat;保存与修改时缺省按 html 落库,下游不再靠猜
- 迁移脚本幂等,并把明显是 markdown 的历史行标出来
2026-10-22 14:37:34 +08:00
weicw-dshsa f5a89634c4 feat(file): 文件存储接入 RustFS(S3),修正文件外链域名与路径
文件上传从本地磁盘改为可切换的对象存储(默认 s3,type 改回 local 即退回原行为):

- 新增 common.core.storage 包:ObjectStorage 接口 + S3ObjectStorage(minio 8.5.17)
  + NoopObjectStorage + FileStorageService + StorageConfig + StoredFile;
  key 规则沿用 file/yyyyMMdd/uuid.ext、缩略图 file/thumbnail/...,桶内结构对齐旧磁盘目录
- 读取仍走后端代理 GET /api/file/**:s3 模式下首次读取物化到本地 cache-path 再返回,
  对外 URL 不变;历史本地文件一律作废,不做回退兼容
- ConfigProperties 增加 storage 配置块,endpoint/access-key/secret-key 支持环境变量覆盖
  (RUSTFS_ENDPOINT / RUSTFS_ACCESS_KEY / RUSTFS_SECRET_KEY),配置非法时启动即失败
- FileRecordService.deleteFileAsync 改为按 path 删除,本地与对象存储同时清理
- pom 固定 okhttp3.version=4.12.0、kotlin.version=1.9.25:Spring Boot 2.5.4 默认的
  okhttp 3.14.9 与 minio 所需的 okhttp 4.12.0 冲突,会导致启动即
  NoClassDefFoundError: okhttp3/ConnectionPool

同时修正文件外链拼接(以下均为改造前就存在的问题):

- application-prod.yml:server-url / file-server 由失效的 tuanwei-api.gxwebsoft.com
  改为生产实际域名 https://twai-api.gxmu.edu.cn(与 admin .env.production、
  uniapp request.js 一致),此前上传返回的 url 指向旧域名导致访问不到
- FileController 抽出 fileBaseUrl(),分页/列表原来漏了 /api/file 前缀
- OpenFileController 同样补 /api/open/file 前缀
- TzbcyMaterialServiceImpl 下载链接补 /api/file/download
2026-10-21 23:11:45 +08:00
weicw-dshsa bf6813e71d feat(ai): 聊天 WebSocket 加 /api 端点与心跳,WS 不可用时退到 HTTP SSE
生产网关在 /api 前丢掉 Upgrade 头,wss://…/chat/{userId} 到不了后端,
端上一直卡在「正在建立 WebSocket 连接…」。

- 新增 ApiChatWebSocketServer(/api/chat/{userId}),与老端点共用会话表;
  会话表改存 Session 而不是端点实例,两条路径都能收到推送
- 登记会话时关闭空闲超时(Tomcat 默认 20s,AI 首字慢时会被掐断),
  并补 @OnMessage 丢弃心跳(缺了它容器会因不支持的文本帧直接断连)
- send() 找不到 WS 会话时改投 ChatStreamRegistry,把帧送到 HTTP SSE 连接,
  推送侧仍只有 sendMessage 一个入口,三千多次调用点不用改
- prod 放开 com.gxwebsoft.ai.config 到 INFO:只保留连接/断开,推送路径仍静默
- 补 ChatStreamRegistryTest / WebSocketEndpointPathTest / AiControllerSseFrameTest

顺带补上 31eb75c 漏加的 ChatStreamRegistry.java:AiController 已经 import 它,
缺文件时 HEAD 编译不过。
2026-10-21 19:18:02 +08:00
weicw-dshsa 31eb75cdad fix(ai): 创意助手繁忙归类补 503/50508 与流式中断,失败日志保留尾部
线上日志(2026-10-21 18:10/18:50)显示失败节点已从出图节点移到最前面的
「问题分类器」(SiliconFlow):503 code 50508 System is too busy,以及
requests/urllib3 read_chunked 流式读取中断。旧归类只认 50500,用户看到的是
「生成失败」而不是「生成服务繁忙」;且 briefUpstreamError 只留头部,
Python traceback 的异常类型在尾部被截掉。

- isUpstreamCapacityFailure 增补 50508 / 503 / 流式中断等模式
- briefUpstreamError 改为头 2/3 + 标记 + 尾 1/3(仍单行、仍 <900 字符)
- buildCreativeAssistantResult 在成功但无可展示内容时记 WARN(含 outputKeys
  与 photo_files 条数),让解析兜底路径下一次可诊断
- 新增 AiCreativeBusyClassificationTest(6 条)

issue 19;不恢复自动重试。
2026-10-21 19:09:03 +08:00
weicw-dshsa f4590001d8 feat(ai): 创意助手出图链路收口——去重试、图片模式包装提示词、输出形状兼容(issue 19)
- 删除 CREATIVE_ASSISTANT_MAX_RETRY / executeCreativeAssistantWithRetry /
  isRetryableCreativeFailure,失败即失败;日志仍带 failedNode 与上游 error。
- 图片模式(mode=image)把 user_content 包装成「【任务类型】生成图片…」意图声明,
  避免上游「问题分类器」把画面描述判成文案/脚本分支;不带 mode 的入口保持原话直送。
- outputs 形状兼容:image7 这类数组里的 markdown 图片按内容归一到 photo_files
  ({url,name,type}),findTextValue 支持 List 中的图片字符串;raw 保持上游原样。
- 测试:新增 AiCreativeNoRetryIntegrationTest(2)、AiCreativeImagePromptTest(4)、
  AiCreativeImageOutputTest(5),删除两个重试用例;全量 148 跑 0 failures。
2026-10-21 15:42:54 +08:00
weicw-dshsa 42655ecd83 chore(build): 用 .sdkmanrc 固定 JDK 8 与 Maven 3.9.16
父 POM 管的 lombok 1.18.20 不支持 JDK 17,而本机 sdkman 全局默认是 java 17.0.20-tem,编译必然失败。按项目固定工具链,不改 sdk default。
2026-10-21 12:40:13 +08:00
weicw-dshsa aed2870b27 chore(dev): 开发环境 Dify 指向公网演示实例
只覆盖 dify.base-url,dify.apps / dataset / workflows 仍取 application.yml 默认值,避免两份配置漂移。base-url 必须带 /v1(/v1/parameters 返回缺 Bearer 的 401 JSON,/parameters 返回控制台 404)。演示实例与内网实例共用同一批应用 Key 与知识库。
2026-10-21 12:40:13 +08:00
weicw-dshsa 1b61fe5586 feat(ai): 自由模式转创意助手,失败日志补失败节点与上游 error(issue 17 / 18)
自由模式(issue 18):
- docType 为空时不调「内容创作与宣传助手」——它开始节点的 doc_type 是必填 select,
  空值直接被上游判 failed(error="doc_type is required in input form")。改为转
  「AI 创意助手」,只发它唯一的必填入参 user_content。
- ManuscriptGenInputs 增加 userContent;doc_type / Number 仍无条件带键(空串能过
  Dify 的必填校验,缺键不行)。
- 用例:AiManuscriptFreeModeRouteTest、AiManuscriptInputsContractTest。

失败日志(issue 17):
- 稿件流程、大纲流程、executeWorkflowForResult 三处失败日志补 status + failedNode +
  upstreamError + elapsed。失败信息拆在 node_finished 与 workflow_finished 两类事件,
  入参校验类失败只有后者,只取节点事件等于没取。
- 补 event=error 与「静默断流」(没有 workflow_finished / message_end)的日志,
  两者都带 taskId,可与请求对上。
- 日志级用 error 而非 warn:生产 com.gxwebsoft 只开 ERROR。
- 用例:AiWorkflowFailureDetailTest。
2026-10-21 12:40:05 +08:00
weicw-dshsa 9e20014cd6 fix(cms): 分类写入前校验上级分类,拦截自引用与成环
分类是自关联结构,一旦出现「自己指向自己」或「祖先指向后代」的环,前端构建分类树时
会直接抛错,导致整个页面无法渲染(报 data error: {categoryId: x, parentId: x})。

- ArticleCategoryService 新增 checkParentValid:顶级分类放行;修改时沿父级链向上回溯,
  父级链中不允许出现自己;带回溯深度上限兜底,避免历史脏数据造成死循环。
- Controller 的保存与更新入口都调用该校验,不通过时抛 BusinessException(中文提示)。

注:本提交同时包含这三个文件的整体重新格式化(忽略空白后实际逻辑改动为 52 行插入)。
2026-10-13 20:00:25 +08:00