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 |
|
weicw-dshsa
|
21e49c6a1a
|
fix(ai): WebSocket 推送静默化,并补重试与静默推送用例(issue 11 / 12)
- WebSocketServer.sendMessage 里两行 System.out.println 删除。该方法被**每个 SSE
事件**调用一次(大纲单次生成实测 3062 个事件,即一次请求刷 6000+ 行),而且绕过
日志框架——既不受 logging.level 控制(生产把 com.gxwebsoft 压到 ERROR 也拦不住),
也不进日志文件,真故障时需要的几行会被顶掉。日志改到低频的 onOpen / onClose;
同时把 onOpen 里静默吞掉的握手回应异常改成 WARN(那是「连上了却收不到内容」的
第一现场)。
- 新增用例:AiCreativeRetryTest、AiCreativeRetryIntegrationTest(issue 11,
后者用本地 HTTP server 扮演 Dify,断言瞬时故障确实重跑一次、凭证失效只请求一次)、
WebSocketServerSilentPushTest(issue 12,捕获 System.out 断言推送路径零输出)。
|
2026-10-13 20:00:01 +08:00 |
|
weicw-dshsa
|
8406c90cf3
|
fix(ai): 文件上传按应用选择凭证;并入创意助手重试与失败日志(issue 11 / 14)
同一文件内包含两块内容,无法拆分提交:
一、文件上传按应用选择凭证(issue 14,本次)
Dify 的 /files/upload 是按应用鉴权的:实测同一张图、同一个 end user 下,chat Key
上传得到的 file id 喂给创意助手工作流的 file-list 变量,0.2 秒即报
Invalid upload file;用 creativeAssistant Key 上传的同一文件则 succeeded。
而原实现只有两个分支(type 为空/0 用 chat,否则用 manuscript),从不使用创意助手
的 Key,因此任何喂给创意助手的附件都必然失败 —— views/gxmu/creativeAssistant 表单页
用的是同一个接口,它的附件功能同样是坏的(既有缺陷)。
新增 app 参数按应用代号取凭证(复用 DifyProperties.AppKeys#byCode),不传时保持既有
行为;未知代号回退旧逻辑并打 WARN。新增 resolveUploadToken。
二、创意助手出图失败的重试与可观测(issue 11,前次会话已完成验收、此前未提交)
executeCreativeAssistantWithRetry / runCreativeAssistantOnce / CreativeRun /
CreativeApiFailureException / creativeFailureReason / creativeFailureAnswer /
isUpstreamCapacityFailure / isRetryableCreativeFailure / briefUpstreamError:
上游 50500 算力不足时自动重跑一次,凭证失效、模型停用等不重试;每次失败按 ERROR
打结构化日志(含失败节点名与上游原文截断)。
|
2026-10-13 19:57:41 +08:00 |
|
weicw-dshsa
|
f54e28cffc
|
fix(ai): 失败态在返回体里带 status=failed,前端才能区分失败文案与正文(issue 10)
上游工作流 failed 时,后端把 reply 置为失败文案("生成失败,请稍后重试")
并照常走成功路径返回(HTTP 200 / code=0),而返回体里**没有 status**:
data = {"answer":"生成失败,请稍后重试", "conversationId":"…", "messageId":"…"}
两端前端的 isFailedPayload(读 payload.status / payload.data.status)因此
永远落空,只能把失败文案当正文渲染(实测三个 admin 页面都会这样)。
修 issue 09 时上游每次都 failed,这条是必现的。
改动:
- buildManuscriptResult 增加 failed 参数(保留三参重载,等价 failed=false,
以免破坏 issue 08 既有测试的调用形态),失败时 put("status","failed")。
- 新增 buildActivityOutlineResult(同构),activityOutlineGen 收尾改用之。
- 两个方法都在外层作用域记录 workflowFailed:失败分支里有 continue,
不能用分支内的局部布尔。
刻意不改的东西:
- 失败仍走 HTTP 200 / code=0。改成 code!=0 会与既有约定冲突、影响所有调用方
的 catch 分支,本条只加一个字段即可让两端现有判定生效。
- answer 键在失败态下依然存在(issue 08 契约),只是现在有了 status 可区分。
测试:新增 AiWorkflowFailureHttpStatusTest 8/8,含反向用例——
成功态**不得**带 status(否则 isFailedPayload 会误伤正常结果),
以及「成功态正文恰好是失败文案」时唯一判据是 status。
全量 210 跑 0 失败(2 个 ServerContainer not available 为预存问题)。
验证:用桩上游(probe/stub-dify-failure.py,回 workflow_finished{status:failed})
起第二个后端实例,实测两条链路的返回体都带 status='failed' 且
answer 仍为可读文案,前端 isFailedPayload 判定为 true —— 全链路闭合。
|
2026-10-13 02:12:46 +08:00 |
|
weicw-dshsa
|
fbc9774e19
|
fix(ai): 大纲链路补齐上游必填变量,资金归一化为数值(issue 09)
活动策划大纲当时必然失败,界面只显示「生成失败,请稍后重试」。
两条根因(单变量对照实测确证):
1. Dify 开始节点 event_funding 是 number,而实体与两端前端都是字符串,
小程序问答助手页默认值还是中文「待定」→ must be a valid number,0.2s 即 failed。
2. event_member / tel_people 是上游必填变量,实体里根本没有字段,从不发送
→ event_member is required in input form。
实施中发现的关键事实,推翻了 issue 初稿的一条建议:
「is required in input form」检查的是键是否存在,不是值是否非空 ——
缺 event_member 键 / null → failed: is required in input form
"event_member": "" → succeeded(4752 条 message)
"event_funding": "" → succeeded(7268 条 message)
"event_funding": "待定" → failed: must be a valid number
所以「干脆不发该字段」行不通(不发 = 报 required),正确做法是键始终下发、
值空串兜底。
改动:
- ActivityOutlineInputs 补 eventMember / peopleManage / telPeople 三字段
(snake + 驼峰别名);eventFunding 保持 String 承载用户原文。
- buildActivityOutlineInputs:6 个上游必填变量无条件占位(空值走空白转空串,
不能给 null —— null 会被上游当缺失);event_type / event_location /
people_manage / event_meme 实测非必填,保持「为空不发」。
- 新增 normalizeEventFunding:抽出首个金额并归一化为 BigDecimal。
两个必须先处理的坑,否则静默发出错误金额(比留空更糟):
· 日期:'2026年4月办活动,预算5000元' 的 2026 会被当金额 → 先剔除日期
· 单位前空格:'预算 1 万元' 只认紧邻的「万」会抽成 1 元 → \s* 容忍
另支持 万/亿/千/百 倍率与 '3万5千' 连续降序单位相加;抽不出即降级空串。
- 前置校验抽成 validateActivityOutline,只校验主题/受众/时间三项。
刻意不校验另外三个上游必填项 —— 上游必填不等于用户必填,加校验会把两端
默认入参挡在门外(改前端默认值为空串后,旧校验会直接返回
'event_funding不能为空',比原缺陷更糟)。
- buildLoadingText 加 120 字上限:agent_thought 的思维链实测可达数 KB,
整段塞进 loading 气泡等于把思维链推给端上。
- 去掉 isDisplayableText 之后的恒假条件(空串已被排除),并注释说明
reply.indexOf("") 恒为 0 会误判已存在,故两道判断都不能少。
配套前端:
- uniapp 问答助手页资金默认值 '待定' → ''(未提及 != 待定)
- admin 入参类型补注释与 3 个可选键
测试:新增 AiActivityOutlineContractTest 23/23,含「凡校验放行的入参必带上游
全部必填键」的不变量用例;AiManuscriptUuidLeakTest 增至 21 条,补 loading
长度上限与真实 message_end 形状。全量 202 跑 0 失败
(2 个 ServerContainer not available 为预存问题)。
验收:probe/outline-e2e.py 走真实后端,用两端前端默认入参与 issue 原文要求的
'待定' 一档,复刻 apiRequest 回落 + formatOutlineResult + parseAssistantContent
判定,29/29 PASS。
|
2026-10-13 00:37:15 +08:00 |
|
weicw-dshsa
|
a8c8dd8643
|
feat(dify): 全部迁移到内网实例,统一为单份配置
VPN 打通后开发机可直连内网,原先「默认公网 + prod 覆盖内网」的双份配置
已无必要,且两套 Key/知识库 ID 完全不通用(实测互打一律 401),
留着只会造成误用。公网那套整体移除,回退到 git 历史取。
- application.yml:base-url 与 8 个应用 Key、全部知识库密钥/ID 改指内网;
原默认值来自公网,其注释同步改写
- application-dev.yml / application-prod.yml:删除 dify 覆盖段。
YAML map 是按键合并而非整体替换,保留一份与默认值相同的覆盖
只会在将来漏改时混出「一半内网一半公网」的状态
- DifyProperties:默认 baseUrl 改内网,类注释同步
修复一个上线起就坏的接口:
- admin 端「竞赛历史」文档列表按 challenge-cup 取库,而该 ID
b342f096-… 在**公网实例上也返回 404**(公网真实的挑战杯库是
7c812029-…,从未登记),故该接口一直不可用。改指内网「竞赛案例」
库(181d58e3-…,145 篇),实测已能列出文档。对外路径保持
/challenge-cup/documents 不变,以免影响 admin 端调用
清理失效的工作流变量(内网实测已从开始节点移除):
- document-process 的 query / pdf_url,declaration 的 parse_value。
三者配置为空串以显式丢弃 —— 直接删行会退回「按原名透传」,
反而把 pdfUrl / parseValue 这类驼峰名发给 Dify。实体字段保留,
不破坏 /documentProcess/upload 的入参契约(pdfUrl 仍参与
「必须提供文件或 pdfUrl」的校验,只是不再转发)
- 注:Dify 对多传变量静默忽略,故此前不报错、功能未受影响,
属「看着对其实无效」的配置
测试:改断言为内网值,并新增「不残留任何公网配置」「失效变量必须
配成空串」等防回归用例;变异验证(base-url 改回公网 / 空串改回生效名)
确认用例有真实判别力。Dify 相关 44 例全绿。
Refs: .scratch/dify-probe/README.md, .scratch/ai-assistant/map.md
|
2026-10-12 12:37:33 +08:00 |
|
weicw-dshsa
|
a2a83f7d9e
|
fix(ai): 稿件/大纲链路失败态不再把标识符当正文推给端上
这三条路径在 workflow_finished 事件只 extractAnswer、从不读 status:
工作流失败时 outputs 为空、answer 可能被填成 workflow_run_id(UUID),
原样下发等于把内部标识符当正文推给端上(issue 03)。
- activityOutlineGen:失败先判定,推「生成失败,请稍后重试」(reset=true)
再正常收尾
- executeWorkflowForResult:同上;最终结果失败时只回 status + 可读文案,
不带 outputs / raw
- manuscriptGen:自查发现该方法不走通用执行、自带一份缺口,一并补齐
- socket 发送器增加 reset 字段:端上收到后丢弃失败前已累积的半截内容
- 新增 AiWorkflowFailureStatusTest 10 例:锁定「失败判定先于取 answer」
的顺序契约与顶层/嵌套 status、大小写空白、成功不误判等边界
Refs: .scratch/ai-assistant/issues/03-other-links-raw-payload-fallback.md
|
2026-09-24 04:22:29 +08:00 |
|
weicw-dshsa
|
c598d88506
|
test(ai): 补端到端形状契约用例
把 buildCreativeAssistantResult 在失败态下的**真实产出**打成一行 JSON,
供 .scratch/ai-assistant/verify-frontend-e2e.js 当输入喂给前端真实渲染函数,
验证的是「后端真实产出 → 前端真实渲染」整条链路,而非两侧各自自测。
实测产出为 {"status":"failed","answer":"生成失败,请稍后重试"},
不含 raw / workflow_id / req_id / 上游错误栈。
Refs: .scratch/ai-assistant/issues/01-creative-assistant-raw-json-bubble.md
|
2026-09-24 03:51:49 +08:00 |
|