Commit Graph

1320 Commits

Author SHA1 Message Date
huangzd1997 188aedec84 fix(任务收尾): 打破「数据永久缺失 → 卡死恢复无限重建 job」的死循环
线上任务 28459(appearance_patent)卡在 RUNNING:chunk 462/473/484 的 payload
对象已不存在,组装失败 → stale recovery 每 30 秒重建一次 assemble job 再失败,
而恢复过程又会刷新任务心跳、任务永远判不出「卡死」,48 分钟空转近百轮。

- appearance-patent:读 chunk 时若 payload 对象已不存在(RustFS NoSuchKey),
  跳过该分片让任务按已有分片出部分结果,不再抛异常把组装永久拖死;
  similar-asin 在原有 typeMismatch 跳过分支旁补同款处理
- TaskFileJobService.hasExhaustedAssembleJob + stale recovery:已有「重试耗尽且
  已终态收尾」的 assemble job 时放弃恢复,交给 finalizeStaleTask 按失败收尾
  —— 用户看到明确失败,而不是无限 RUNNING

实测:部署后两节点 28459 相关日志、rustfs 确定性错误、stale recovery 重建全部归零。
2026-09-17 01:19:14 +08:00
huangzd1997 d5952945dd fix(rustfs): 确定性错误不再重试;回退两处经实测无效的连接池实验
- 新增 isNonRetryable:NoSuchKey / AccessDenied / 签名错误等确定性错误立即失败,
  不再白打两次请求(线上 read 一个已清理的 chunk 会连打 3 次并留下一条 ERROR)
- connectionPoolMaxIdle 0→5、keepAlive 30000→300000 回退:
  实测证明 unexpected end of stream 与连接复用无关——禁用复用后新容器起来的
  第一次请求照样中招。至此已排除公网链路、keepAlive 过长、连接复用三项;
  另用 mc 并发压 200 个小对象全部成功,说明 RustFS 服务端无问题。
  剩余方向指向 MinIO SDK(8.5.17,已是最新)/OkHttp 与 RustFS 的协议细节,
  需抓包对比 mc 与 Java 的请求才能定位。数据不丢(重试都能成功),影响是每次 +600ms。
2026-09-17 00:58:01 +08:00
huangzd1997 6e1689dfe5 docs(更新日志): 追加客户端 4.0.27 条目 2026-09-17 00:33:29 +08:00
huangzd1997 4f16a02658 fix(稳定性): 修任务锁超时/实例转发/扫描降噪,补 rustfs 结论日志
线上日志扫描发现的缺陷批量修复:
- TaskFileJobService.resetStuckRunningJobsDetailed 去掉跨行长事务:两节点
  @Scheduled 各取 200 行在同一事务里逐行 UPDATE,互等行锁导致 biz_task_file_job
  每天上百次 Lock wait timeout。改逐行独立提交(本就有 CAS 条件,幂等)
- TaskOwnerForwardService 连接类失败重试 3 次:两节点滚动重启窗口内的
  Connection refused 会直接丢掉用户提交的结果(读超时不重放,避免重复提交)
- AdminApiGuardFilter 把 401/被顶下线降为 debug(线上单节点一天近 3000 条噪声)
- NoResourceFoundException 单独处理:如实返回 404,不再刷 ERROR 堆栈、不再伪装
  200(每天 580 条,绝大多数是外部扫描 /.env、/credentials、aliyun.json 等)
- RustfsObjectStorageService 补「重试后成功 / 重试耗尽」结论日志:一天上千条
  retrying 却无结论,无法判断数据是否落盘
- 连接池 keep-alive 300s→30s,减少复用已被对端关闭的空闲连接
- SimilarAsin 提交结果遇到已删除任务时带 TASK_NOT_FOUND(40401),客户端据此
  停止每 30 秒一轮的无谓重试
- 修 SimilarAsinResultRowDto 的 GBK 乱码 JsonAlias("浠锋牸" → "价格")
2026-09-17 00:32:48 +08:00
huangzd1997 5e9a59b327 test(鉴权): 兜底过滤器用例改用不存在的样例豁免前缀
exemptPrefixes 是配置化操作出口(aiimage.security.admin-guard-exempt-prefixes,
默认空、生产未配置任何值),原用例拿已下线的 /api/admin/shop-credential-checks
当样例路径,容易被误读成「该端点已配好豁免」——那条路径是 2026-09-04 引入
豁免机制时随手取的,并非真的配过。换成显然不存在的 /api/admin/example-exempt,
并注明不要改成真实业务端点。
2026-09-17 00:13:27 +08:00
huangzd1997 ab09cff427 docs(更新日志): 追加客户端 4.0.26 条目 2026-09-16 22:55:31 +08:00
huangzd1997 47a9520a82 fix(跟价): 失败任务也产出并保留部分结果文件,不再只剩一条失败记录
会话掉线这类「跑到第 N 页才断」的场景,任务本就该是 FAILED,但已经跑出来的行
(哪些 ASIN 真实改价过)必须随任务一起交付,否则用户无从对账、也无法只重跑漏掉的。

- submitResult 的 error / success=false 分支改走 finalizeFailedShop:先按失败落库
  (success=0 + 原因),若合并后仍有可用行就排队组装部分结果文件;一行可用数据都没有时
  才退化成原来的纯失败(并清掉合并缓存)
- markResultFilePending / assembleShopResult 增加 preserveFailure 语义:失败行只挂文件,
  不把 success 洗成 1、也不清 errorMessage
- updateTaskStatusFromLatestRows:失败行正在组装文件时不让任务提前终态,
  否则前端一停轮询、下载按钮永不出现(失败任务的结果文件同样要能下载)

PriceTrackTaskServiceTest 新增 2 条契约测试(部分结果排队 + 组装完成后仍失败且可下载),
跟价模块测试 20/20 通过。
2026-09-16 20:33:31 +08:00
huangzd1997 7a6c3c3fa3 fix(菜单权限): 授权树按操作者置灰不可授予项 + 放行保留既有授权
普通管理员勾到无权授予的菜单后,ensureGrantable 抛 403,而 createUser/updateUser
都是 @Transactional,整笔回滚——线上表现就是「后台不能保存权限,也不能创建用户」
(2026-09-16 周丽娥账号创建用户与保存 uid=45 权限双双失败,库里查无新用户)。
根因是校验 2026-07-28 就加了,但授权树一直拿全量菜单,两边规则不一致。

- permission-menus 按操作者标记 grantable;授权树据此置灰(不隐藏:授权是整树替换,
  隐藏会把超管授予过、操作者自己没有的菜单当取消勾选删掉,与 09-13「权限自己没掉」同类)
- ensureGrantable 放行目标已持有的授权,只拦新增,不构成提权
- GlobalExceptionHandler 补业务异常日志:此前普通业务异常一行都不记,本次排查只能靠
  反推响应体字节数(nginx body_bytes_sent 含 chunked 开销)才定位到根因
2026-09-16 18:46:21 +08:00
huangzd1997 803b5d583d docs(更新日志): 追加客户端 4.0.25 条目
跟价「指定ASIN模式」搜索打到屏外的导航搜索框,客户端 4.0.25 已修(app_client 29c12f2)。
2026-09-16 17:15:06 +08:00
huangzd1997 1c52cd529b fix(去重): 历史任务弹层补下载/删除按钮(漏了 history-item-actions 插槽)
任务面板的 #item-actions 只作用于「当前任务」列表,历史结果全部只在
#history-item-actions 弹层里渲染。16 个工具页里唯独去重页只给了前者,
表现为用户反馈「历史任务里压根没下载按钮」(既无下载也无删除)。
接口数据(success/resultId/downloadUrl)完整、类型检查与单测均不报错,
只能靠点开历史弹层才能发现。
2026-09-16 17:05:11 +08:00
huangzd1997 ab28b168ec fix(去重): 主链接行位于其子链接行之后时未被丢弃 + 导出表头与数据行错列
线上任务 28422(文件 新数据变体完.xlsx)结果中 16 个主ID同时保留了主链接与
子链接,违反所选规则(keepIntegerIds=false / keepUnderscoreIds=true /
keepIntegerMainIdsWhenNoSubIds=true)。

根因:旧实现把整数主链接行暂存,依赖「后到的同主ID子链接行」把它丢弃。
当顺序为「子链接在前、主链接在后」时,子行到来时暂存区尚空,无人记录该主ID
已有子链接,主链接一路存活到 flush。改用 IdRuleRowPicker 先收集、收尾统一按
「该主ID是否出现过子链接行」判定,与源文件顺序无关;补 subRows/mainRows/
droppedMainRows 排查日志。

同时修一处潜在错列:表头按 selectedColumns 原顺序写、数据行按
orderedSelectedColumns(id/ASIN/国家/价格/品牌 提前)写,两者不同序时整体
错列;本任务所选列恰为导出优先级顺序故未暴露。

实测:用线上源文件重算,输出 7273 → 7257 行(正好少掉那 16 个主/子并存的主ID)。
2026-09-16 16:33:04 +08:00
huangzd1997 367b4b7553 chore(更新日志): 补 4.0.24 条目(跟价改版为行内输入+底部「全部保存」提交) 2026-09-16 16:03:42 +08:00
huangzd1997 25c7323c47 chore(更新日志): 补 4.0.23 条目(跟价适配亚马逊新版商品页) 2026-09-16 13:40:59 +08:00
huangzd1997 aea0e16279 feat(站内通知): 铃铛支持按类型大类筛选(系统异常/任务异常/配置异常/系统通知)
原来铃铛只能按关键字和日期筛,任务失败、密钥欠费、麦象异常全混在一起,
用户没法只看自己关心的那类。

后端 NotificationService 增加 scene→大类映射(未知 scene 归「系统通知」兜底),
列表接口新增 category 参数,落到 SQL 是 scene IN (...);「系统通知」作兜底类
还要纳入未登记的 scene,故表达为「= system 或 不在其它三类里」。列表项返回
category,前端不必各自维护一份 scene 映射。前后端两个通知接口同步加参数。

前端铃铛筛选区新增一行类型 chip(全部 + 四类),与关键字、日期一起参与
hasFilter 与重置;空分类不下发,避免后端查询落空。
2026-09-16 10:32:11 +08:00
huangzd1997 f566573fce fix(菜单权限): 后台侧边栏为部分授权用户补全祖先分组(二级菜单不再平铺)
只勾选分组内子页面、未勾选分组本身时,getUserColumnPermissions 只返回
直接授权+展开后代,分组容器节点缺失,AdminMenuTreeBuilder 把子页面当根节点,
前端侧边栏渲染成一列平铺;且子页面 sort_order 是全局值、跨分组穿插显示,
与超管的「分组+组内顺序」视图完全对不上(线上 31 个账号如此,含 uid=972 阿武)。

修复:getUserColumnPermissions 增加 includeAncestorGroups 重载(默认 false),
true 时沿 parentId 链把有可见后代的祖先并入集合——分组无页面路由,仅还原
展示层级,不构成授权扩展;只有 current-user/menus(后台侧边栏)启用。

刻意保持原语义的出口:/permission-users/{id}/column-permissions 与登录响应
(桌面端 tool-catalog「组键命中即整组放行」,补组键会误放行整组工具)、
dedupe/invalidasin 的精确 key 校验。

新增 3 个测试:祖先补全含多层链、树组装还原分组与组内顺序、默认出口不含祖先。
2026-09-16 09:58:33 +08:00
huangzd1997 8fcceb3226 fix(撞款扫描): 保留期 90→7 天,并单独兜住最新 SUCCESS 行
该表每行含整份聚合 payload(线上实测约 2.3MB/行,24 行占 55MB),
而读取侧只认最新一行(selectLatestFullRow/LightRow 都是 ORDER BY id DESC LIMIT 1),
历史行纯占磁盘。保留期降到 7 天,行数下限仍由 PROTECTED_ROWS=10 兜底。

同时补一条保护线:连续失败多日时,最新 SUCCESS 行会滑出「最新 N 行」保护窗口,
再按时间线被删掉,界面就空白了——现在取「第 N 新行」与「最新 SUCCESS 行」
两者更靠前的那个 id 作为保护线,确保它永不被删。
2026-09-16 09:25:30 +08:00
huangzd1997 ed0d6575c8 feat(前端): 追加客户端 4.0.22 更新日志条目
包体积 -38%、启动时清理本地垃圾、启动与连接优化三条面向用户的说明。
version 与 web_config 发布记录一致(4.0.22),否则更新面板按本机版本筛选匹配不到。
2026-09-16 09:09:05 +08:00
huangzd1997 f2ada02383 feat(保留期清理): 补齐 7 处只增不删的数据;修结果对象孤儿
审核发现一批表/元数据只增不删,且对象存储存在活跃泄漏:

- module-cleanup 漏收 task_result_item / task_result_payload 的 payload 指针,
  每晚删完行就在 json-server 桶留下孤儿对象;指针收集触顶从"截断后照样删整组"
  改为任务组二分拆分,拆到单任务仍触顶则整组不删并记 error(截断会让指针随行消失)
- 结果文件对象此前从不回收(只删 DB 行、对象留在桶里),现改为事务提交后
  按 file_result / task_file_job 的 result_file_url 逐个回收
- 新增 7 个保留期清理:device_log_file 元数据、price_track_loop_run、撞款扫描行、
  密钥用量日统计、紫鸟记忆过期行、PUBLISH 任务、BRAND 任务。后两者刻意不复用
  module-cleanup(BRAND 不写 biz_file_task;PUBLISH 单任务可达数万行),
  改为调各自既有的业务删除入口,保证结果对象与子表一起回收
- 未读通知补保留期(180 天,已读仍 90 天);ZiniaoMemoryStoreService.deleteExpired
  此前全库无调用方(实现了没接线),现已挂定时任务

全部沿用既有范式:@Scheduled + 分布式锁单实例 + 分批 + 单轮批次数上限 + 失败只记日志。
2026-09-16 01:59:28 +08:00
huangzd1997 5e5816cd74 fix(站内通知): 麦象异常扫描滞留检查改用 update_time 过滤(18960 console 新增 update_time_end)——原按 create_time 过滤 + desc(id) 分页只能看到最新批次,积压深处的老任务永远看不到(2026-09-15 滞留在队 9 小时未被告警指出的漏报根因) 2026-09-16 01:05:26 +08:00
huangzd1997 d189d94c3b fix(更新日志): 面板随指定版本下拉联动;本机已是最新时展示本机条目
- 「指定版本更新」下拉选中版本时日志切到该版本自身的条目(升级/回退统一语义),
  未收录显示"暂无更新说明"(区块不消失),选回空项回默认区间;
  列表加载后自动默认选中第一条不触发切换
- 本机版本==线上最新时回退显示本机版本条目(原来整块消失),灰度/回滚仍隐藏
- 精简 4.0.20/4.0.21 超长文案,修复既有红测试(更新说明限 40 字)
2026-09-16 01:02:13 +08:00
huangzd1997 79b5d40327 feat(设备日志/后台配置/密钥检测): 补齐已上线未提交的设备日志与app_config,并合入检测模型解耦
三部分均已部署到双节点(当前线上 JAR 009efc3e),本次补齐仓库状态,避免"已上线未提交"
在后续最小构建比对里被误判。

- 设备日志管理:modules/devicelog + DeviceLogOssProperties(主机B 独立 MinIO,仅内网)
  + V127 device_log_file/device_log_config + admin-vue 日志管理页;客户端/麦象按 offset
  增量上报(X-Internal-Token),查询仅超管
- 后台通用配置:modules/appconfig + V128 app_config 键值表;工作台「开店流程」密码改服务端
  校验(POST /api/kd-flow/verify),改密码只需 UPDATE 该行、客户端无需重新发版
- 密钥检测模型解耦:新增 aiimage.user-secret.check-model(env AIIMAGE_USER_SECRET_CHECK_MODEL),
  默认 doubao-seed-2-0-lite-260215 —— 同系列 mini 在中继分组下无可用渠道(503 model_not_found),
  实测 lite 可路由;UserSecretModule 去掉 LlmTarget 改 resolveLlmHost,探测日志带 model=
- 前端:密钥面板「检测配置密钥」按钮不再折行;client-changelog 补 4.0.19/4.0.20/4.0.21 条目

注:application.yml 与 PropertiesConfig 同时承载上述多个部分,故未按功能拆分提交
2026-09-15 23:53:05 +08:00
huangzd1997 228d481211 perf(全链路): 连接池/事务边界/轮询与 IO 效率收口
Java:
- LLM 180s 读超时不再被全局 call-timeout 静默截断成 90s(长思考请求被掐断→重试→付费网关二次计费)
- 代理 HttpClient 缓存改有界 LRU(jikip 每次提取新 IP,无界缓存持续泄漏 selector 线程与连接池)
- 12 个 service 的 Redis 任务锁移出 @Transactional(自旋最坏 10s 白占 DB 连接,池仅 30),远端对象删/传改 afterCommit
- 结果文件 Job 闸门拒绝时不再回退内联执行(改重新入队,避免把背压转嫁给 MQ 消费线程)
- imagevideo 每秒扫描加列投影、过期清理加 LIMIT;权限页整表查询改列投影(不再拉回密码哈希)
- 哈希改 HexFormat;补 5 处"不能改"的技术依据注释(批量插入会丢回填主键、流式丢模板与图片等)

前端:4 个工具页轮询改轻量端点(带 fallback);PriceTrack 快照节流写盘;候选店铺表分页;页面隐藏时停表

客户端:HTTP 连接池按出口复用(Session 仍每请求新建,保持无跨请求状态);品牌检测 WIPO 逐请求握手;
代理配置按 mtime 缓存;串行任务改专属池;异常降级为标签页重连;紫鸟启动改端口轮询;模板编译缓存;
日志上报连接与落盘收口;Flask 版本 API 改按请求复用连接
2026-09-15 23:01:59 +08:00
huangzd1997 d6f8368493 feat(站内通知): 麦象异常扫描上线——任务停滞/失败/队列积压/批量空结果推管理员
用户要求「maixiang异常也要发通知」,确认范围(卡住/失败/队列堆积/服务不可用)与
受众(只发管理员)后实现:
- MaixiangConsoleClient:18960 后台只读接口客户端(batch/tasks、tasks、queue/status、
  batch/detail),token 鉴权、失败只记日志。URL 必须用 URI 对象提交——字符串形态会被
  RestClient 二次编码(%20→%2520),end_time 参数实测报 Incorrect DATETIME value,
  检查会静默失效;已加回归单测 buildUrlEncodesTimeParameterExactlyOnce
- MaixiangAnomalyScanner 五类检查:①批量任务停滞(status 0/1 超时无更新,默认 60 分钟)
  ②单任务滞留(创建超 30 分钟未完成)③近 30 分钟失败达最小条数 ④批量任务空结果
  (worker 报错时批次仍被标"已完成",是唯一可抓的批量失败信号)⑤队列积压(pending≥300
  / processing≥100);去重按天/小时/任务,全部为管理员全局事件(subjectUserId=null)
- 接入 NotificationScanScheduler(分布式锁内同跑);scene=maixiang_anomaly;
  AIIMAGE_NOTIFICATION_MAIXIANG_* 环境变量可调,令牌留空=跳过
- 前端 NotificationScene 类型补 maixiang_anomaly(铃铛不按 scene 渲染,无运行时改动)
- 测试:客户端解析(真实抓包样例)+ 扫描器 14 例 + 手工联调探针 MaixiangLiveProbeTest
  (-Dmaixiang.live=true 开启,只读不写通知)

已部署双节点(.env 加 console token、JAR cd3e06c4)并线上验证:首扫推 2×15 条管理员通知
(3 个变体任务停滞 / 39 个跟价任务滞留),二次扫描落库=0 去重生效。
2026-09-15 12:20:15 +08:00
huangzd1997 b0f764b6b6 feat(前端): 追加客户端 4.0.18 更新日志条目 2026-09-15 11:33:53 +08:00
huangzd1997 1403fec5fe feat(前端): 追加客户端 4.0.17 更新日志条目
跟价购物车归属未知跳过改价、店铺名回落、失败原因如实带出三条面向用户的说明。
2026-09-15 01:27:57 +08:00
huangzd1997 07b4ebe983 feat(成本): 密钥检测防抖 + 巡检降频隔日 + 客户端 4.0.16 更新日志
- 用户密钥「检测」90 秒新鲜期:同值重复检测复用上次通过结果,
  代理提取不再因连点/手滑重复扣费(只缓存 passed,失败允许立即重试)
- 连通性巡检由每日降为隔日(cron 0 30 4 */2 * *,双实例锁不变)
- changelog 追加 4.0.16 条目
2026-09-14 19:43:22 +08:00
huangzd1997 0b2b9303d0 chore(前端): 追加客户端 4.0.15 更新日志条目 2026-09-14 18:10:48 +08:00
huangzd1997 05a2c479a5 fix(测试): CollectDataNoUploadStaleTest 的 updateById 重载歧义(补编译验证) 2026-09-14 18:00:07 +08:00
huangzd1997 52b55df7b2 feat(任务判死): 心跳正常但 180 分钟无结果上报的二次判死线(13 模块)+ 同期待发改动
判死线(治 28131 型「主线程卡死、心跳线程照发」):
- 判据改看 biz_task_scope_state.last_chunk_at(HTTP 心跳不刷新它);从未上报跳过不判
- 中央线覆盖 DELETE_BRAND/PRODUCT_RISK_RESOLVE/PRICE_TRACK/SHOP_MATCH/PATROL_DELETE/QUERY_ASIN/WITHDRAW
- 自带线接入 COLLECT_DATA/SIMILAR_ASIN/APPEARANCE_PATENT/SHOP_DATA_CRAWL/PUBLISH/BRAND
- 客户端心跳带处理位置 progressText,判死文案含最后位置;no-result-upload-timeout-minutes 默认 180(0 关闭)

同期带上另一工作流的待发改动:跟价换 IP 重试、品牌检测重试上限与 LLM 并发下调、
教程包后台管理页与 V126 迁移、admin-vue 教程记录页。
2026-09-14 17:56:20 +08:00
huangzd1997 b54f72d3f6 fix(品牌检测): 熔断中止落真实原因+部分结果可下载,熔断加持续时长门槛
- 熔断改为「连续失败持续 6 分钟未恢复」才中止:原 8 次即中止,5 线程一轮
  就能凑满,重跑机制没机会生效导致任务失败率过高(任务 2309 复盘)
- 期间每 30s 冷却重试,重跑轮次 10→30;限流窗口恢复后任务自动跑完
- 失败终态上报内部接口 /api/internal/brand/tasks/{id}/abort:落真实原因 +
  用已收分片部分组装结果(未检测品牌单独成 sheet),不再悬挂到心跳超时被
  判「前端长时间无响应」且已跑数据无法下载
- 组装前从分片重建聚合(缓存快照可能缺后加字段如 keptBrands)
- 前端:失败任务有结果文件即显示「下载结果」
2026-09-14 17:04:19 +08:00
huangzd1997 24c5a09c7f feat(任务派发): 客户端兜底拉取页面未推送成功的任务 + 修店铺匹配定时任务误杀
问题:任务派发链路的"推送"只存在于页面里(Java 解析落库 PENDING → activate →
pywebview 桥 enqueue_json 推本机队列)。只解析没点启动、推送前关页面、在纯浏览器
打开,任务都会停在 PENDING,2 小时后被 StaleTaskRepairService 标失败
(「任务长期未被领取,已自动失败」,09-11 生产清理过 263 条同画像)。

- 服务端新增 GET /api/tasks/pull-pending(TaskClientPullController,身份从 JWT 取,
  不接受 user_id 参数):只挑创建超 5 分钟仍 PENDING 的本用户任务,逐条条件更新认领
  (PENDING→RUNNING + 接管 owner_instance_id)——与页面 activate 同一谓词,天然互斥,
  不会重复执行;认领后组装不出载荷则标 FAILED,不留 RUNNING 孤儿
- payload 由各业务模块实现 ClientTaskPullSpi 组装(task 侧不 import 业务模块,同 G5):
  首批 SIMILAR_ASIN / COLLECT_DATA / APPEARANCE_PATENT——这三个 Python 消费端会自行
  回拉明细,故载荷极简、客户端零模块知识;开关 aiimage.client-task-pull.enabled 默认 false
- 防双执行:三处 activate 由「非终态即可」收紧为只认 PENDING,未命中抛
  「任务已在执行中(可能已由客户端自动接管),无需重复启动」(顺带堵住整行 updateById
  把认领写入的 owner 覆盖回去的竞态);两个前端页 activate 失败即提示并停止入队
- 客户端:amazon/main.py 新增 pending_task_pull_worker,启动点挂在 app_client/main.py
  的 start_task_monitor(独立入口的 worker 线上并不生效);开关 pending_pull_enabled 用
  getattr 读取,避免 test/ 下的旧 config 缺键导致整包导入失败
- 同批修:StaleTaskRepairService 的 SCHEDULED 分支改按 scheduled_at + 120min 判死
  (原按 updated_at 会必杀排期 >2h 的店铺匹配定时任务,而 activate 又被 scheduledAt-90s 挡住)
- 测试:TaskClientPullServiceTest / StaleTaskRepairServiceTest / CollectDataTaskPullSpiImplTest /
  CollectDataActivateGuardTest 共 20 例;客户端 pending_task_pull_worker 7 例并更新启动顺序契约测试
2026-09-14 16:23:34 +08:00
huangzd1997 8cab9d4bad fix(密钥配置): 保存沿用刚检测过的输入值结果,消除「三项检测通过却提示未检测」死循环
- 服务端:对「输入值(未保存)」的检测结果按 uid+模块+值指纹暂存 Redis(TTL 30min,
  Redis 异常降级为需重新检测,不阻断保存);保存同一个值时落库该结果
  (passed/failed/error 一并沿用),改过值或从未检测则维持未检测
- 前端:保存后清理本地「输入值(未保存)」绿字,展示统一走服务端快照,避免展示与门禁矛盾;
  门禁提示改列「模块名(掩码):未检测 / 检测失败:原因 / 未配置」,同名掩码也能分辨模块
- 测试:UserApiSecretServiceTest 补沿用 / 值不一致 / Redis 降级用例;
  新增前后端一致性守卫测试(保存后必须清检测结果、提示必须带模块名)
2026-09-14 13:49:25 +08:00
huangzd1997 5ea52e5291 perf(F5+): 行数据按需拉取(结果行版本信号)+ 修复 progress/light 恒判 missing
行数据按需拉取(审查 F5 后续):
- V125 给 biz_file_result 补 updated_at(DEFAULT/ON UPDATE 由数据库维护,
  实体标注 insertStrategy/updateStrategy=NEVER —— 否则 selectById→updateById 的
  写回会把旧值写回去、ON UPDATE 不触发,版本信号静默冻结)
- 装配器回传 rowsVersion=「最后变更时间毫秒#行数」,5 个品牌工具页版本未变即跳过
  带行明细的重型 batch;前端变更信号为 rowsVersion + status/fileStatus/fileReady 复合
  (任务收尾常见「行早写完、之后才置成功」,只看行版本会把界面卡在旧状态)

修复线上缺陷(同一功能验证时暴露):
- TaskProgressLightAssembler 列裁剪漏选 module_type 却用它做模块过滤 →
  getModuleType() 恒为 null → light 恒把任务判成 missing;第七批把 light 接进
  跟价/定时匹配/商品风险的轮询后,消费方会把运行中任务判为 FAILED
- 补选中列 + 守卫用例 taskQueryMustSelectModuleType(已反向验证:去掉修复即红)
- 前端 lightClaimsAllTasksMissing:整体性 missing 结论用重型端点复核后再采信

契约与文档:light 白名单补 rowsVersion(Java 契约测试 / spec 06 §2 / 12 个端点描述)
测试:mvn test 2901 全绿;前端 npm test 765 全绿
2026-09-14 12:08:25 +08:00
huangzd1997 9166656673 perf(C6): 去重总数据列表顺序翻页改 keyset(前后端契约一起改)
背景:列表页 `ORDER BY id DESC LIMIT offset,size`,带筛选且选择性低时每页都要
对命中集做一次 filesort;深翻页 offset 也白扫索引。

改法(保持跳页/回退/改每页的原有行为):
- 后端:page 接口新增可选 `last_id` 游标 —— 传了就 `id < lastId` + `LIMIT size`(无 offset),
  响应新增 `nextLastId`(本页最后一行 id);不传仍是原 OFFSET 分页
- 管理前端:只有"下一页"用游标(上一页响应带回),跳页/改每页/筛选清空游标走 OFFSET
- 测试:新增 2 个后端契约测试(keyset 无 offset + nextLastId;无游标保持 LIMIT 30,15)

验证:mvn test 2897 全绿;admin-frontend-vue vue-tsc 通过 + 1619 测试全绿;
已部署 JAR 2b9ab774c60c3f5d802c9a2cee549008(双节点 health=200)与 admin-vue-20260914-112055。
2026-09-14 11:21:40 +08:00
huangzd1997 1360a44e01 fix(web): 货源查询/外观专利入队不再把 api_key 当必填
密钥已服务端化,本机无明文属正常状态,Java 侧 readApiKey 会按 uid 从用户密钥表兜底;
列成必填会把未在本机手输密钥的用户全部挡在启动之前。
2026-09-14 11:21:09 +08:00
huangzd1997 86c05e71a2 fix(web): 补齐命令式调用的 EP 按需样式,修拦截弹窗贴到文档左上角
Element Plus 走按需引入,只有模板里用到的组件才会注入样式;ElMessage /
ElMessageBox 都是 import 后函数式调用,构建产物里一条 .el-message-box 规则都没有,
弹窗因此没有定位与遮罩,渲染成普通块元素压在页头文字上(ElMessage 提示条同样受影响)。
入口显式引入两个样式模块,并加测试钉住「显式 import 的 EP 组件必须有样式引入」。
2026-09-14 11:21:08 +08:00
huangzd1997 fae26aa460 style(version): 指定版本下拉只显示版本号,去掉发布日期 2026-09-14 11:08:07 +08:00
huangzd1997 8803e22f39 feat(version): 桌面端更新面板支持指定版本安装
- 新增公开接口 GET /api/version/list(最近 50 条,字段口径同管理端列表),
  桌面端下拉不再拼 OSS 地址(版本包被删后拼地址会 404)
- 更新面板(登录页 + 首页)加「指定版本更新」选择器:默认选中最新版,
  可选全部已发布版本(含回退),回退/同版给出明确文案与二次确认
- 客户端无需发版:do_update_app(file_url) 本就接受任意版本包直链
2026-09-14 11:05:30 +08:00
huangzd1997 375b89154b perf(C5): 外观专利服务解析改走流式解析器(最后一条活路径)
AppearancePatentTaskService.parseWorkbook 原为 POI 全量 DOM(用户源文件整表入堆);
改为复用已有的流式孪生实现 AppearancePatentExcelParser(EasyExcel SAX,语义一致且自带单测),
服务侧只保留原有业务处理(分组键/状态过滤/字段补齐/hydrate)。外观专利模块测试全绿。

至此:用户源文件的解析已无 DOM 路径(剩余 DOM 仅用于报表模板写入,样式/图片/公式必须 DOM)。
2026-09-14 10:58:59 +08:00
huangzd1997 6a90adc765 feat(A1/A3): 三个客户端零调用前缀立即无条件收紧
实测桌面端 Python 侧调用面:/api/image-video、/api/task-file-jobs、/api/ziniao 为 0 次调用
(只有带 JWT 的网页端在用),因此从 user-tool-guard-enabled 开关名单移入无条件守卫名单,
不必等客户端铺开即完成收紧;其余 14 个前缀的客户端调用面与令牌携带情况已逐一实测,
仍在开关后面(老客户端不带身份,提前打开会 401)。

新增 2 个守卫测试:开关关闭时这三组前缀对匿名同样 401、带用户令牌仍放行。
2026-09-14 10:46:05 +08:00
huangzd1997 bcf66dc1d7 fix(startup): 两个 GroupDeletionGuard 显式命名 + Bean 名唯一性守卫测试
事故:2026-09-14 边界收敛新增 invalidasin/dedupe 两个同名 GroupDeletionGuard,
Spring 默认用简单类名做 Bean 名 → 启动抛 ConflictingBeanDefinitionException,
主机 A 的 java-server 连续重启失败(health 不通),**单测全绿也发现不了**
(不起完整 Spring 上下文),部署后才知道。

- 两个守卫分别显式命名 @Service("invalidAsinGroupDeletionGuard") / ("dedupeGroupDeletionGuard")
- 新增 SpringComponentBeanNameUniquenessTest:扫描 main 源码,同简单名的组件必须显式命名,
  否则红测试(把这次事故固化成可回归的守卫)
- 已重新打包部署:JAR 1f1e82bda227f2b0768574f8fd7c32c9,双节点 health=200
2026-09-14 10:37:44 +08:00
huangzd1997 c55c4a140b feat(A1/A3+C8): 客户端令牌链路打通(按人鉴权就绪)+ 快照 JSON 写入节流
A1/A3 客户端令牌链路(服务端守卫已能按 JWT 鉴权,缺口在客户端不带身份)
- 前端:新增 user-token-bridge(纯逻辑,值变化才推送)+ user-token-sync(启动安装,
  pywebviewready/storage/60s 轮询补推),main.ts 接入;桥接口补 set_user_token
- 客户端:新增 crawler_core/user_token.py(持有 + 对自家 Java 端点注入
  Authorization: Bearer <jwt>,Session.request 包装,第三方域名不注入、已有头不覆盖、
  登出清空、SHUFUAI_DISABLE_USER_TOKEN_HOOK 可关);main.py 暴露桥方法并在启动安装钩子
- 服务端:守卫契约测试补 5 个用户态前缀用例(开关关闭放行 / 打开后匿名 401 /
  用户令牌通过 / 内部令牌仍放行 / /api/ziniao 第二层)
- 翻开关的前置条件(客户端铺开后置 aiimage.security.user-tool-guard-enabled=true)写入报告

C8 快照 JSON 写入节流
- 读端改以 biz_task_result_item 行为准、JSON 仅兜底(历史任务 JSON 仍是唯一副本时可用)
- 整档 JSON 改 30s 节流写,终态路径 force 立即写;新增 2 个契约测试钉住语义

验证:mvn test 2888 全绿;npm test 741 全绿;user_token 钩子自测(注入/归一/第三方跳过/不覆盖/登出)通过
2026-09-14 10:20:15 +08:00
huangzd1997 7643094f1d refactor(boundary): 跨模块循环依赖清零(8 对 → 0),task→业务 依赖归零
共享内核下沉(跨模块共享的"身份/组织/安全"类型进 common)
- AdminUserEntity/AdminUserMapper、ShopManageGroupEntity/ShopManageGroupMapper → common
- AdminAuthSupport(33 文件 17 模块引用)、JwtService、AuthProperties、DeviceSessionPolicy
  → common/security(原先放在 admin/auth 里,任何模块用一次就多一条跨模块边)

端口化(消费方声明接口、数据方实现)
- task/spi:ResultFileJobHandler 新增 resolveResultFileUrl 钩子(BRAND 特例从 Worker 收回);
  Worker 改为调用 handler.onSuccess(该钩子历史上从未被调用,withdraw 的收尾靠 Worker 里的
  WITHDRAW 特判硬编码——现两者都归位,task 侧不再 import withdraw/brand)
- admin/spi/UserSecretCleanupPort(删除用户级联清理密钥)、notification/spi/ProxyBalancePort
  (代理余额探测)、shopdatacrawl/spi/ManagedShopNamesPort(可管店铺名)
- shopkey/spi/GroupDeletionGuardPort:分组删除守卫改由各业务模块实现(invalidasin/dedupe 两个实现),
  shopkey 不再直连它们的 Mapper

棘轮与量化(2026-09-14 实测)
- ArchitectureBoundaryTest:task→业务 119 → **0**(基线钉死为 0,新增跨模块动作必须走 task/spi)
- 跨模块 import 行数 650 → 605;双向依赖对 8 → **0**(usersecret/permission/shopkey 三向环、
  task↔withdraw、task↔brand、admin↔permission、admin↔usersecret、dedup↔shopkey、
  invalidasin↔shopkey、notification↔usersecret、shopdatacrawl↔shopduplicatecheck 全部拆解)

测试同步:接口契约 8→9、品牌 Handler 钩子契约、worker 的 10 个测试构造实参、通知/管理测试端口化。
mvn test 2881 全绿。
2026-09-14 10:05:04 +08:00
huangzd1997 1b480f915f test(G4): 定时匹配/格式转换/数据拆分 补 27 个契约测试(三个模块此前均零测试文件)
- ShopMatchResolveService 15:候选越权与幂等、匹配去重保序、国家偏好默认顺序与坏 JSON 兜底
- ConvertTemplateService 8:内置模板禁删(软禁用)、导入命名/后缀补全、设为默认时清掉其它默认位
- SplitRunService 4:下载/删除历史必须属于本人且模块匹配(含无结果文件不可下载)

至此审查点名的 6 个零测试模块全部有测试文件。
2026-09-14 09:40:56 +08:00
huangzd1997 ac36c08460 test(G4): 取款/查询ASIN ResolveService 各补 13 个契约测试
取款:候选增删幂等与越权保护、按店铺名批量删的归一化去重、匹配去重保序。
查询ASIN:额外覆盖「国家 ASIN 清单」契约(国家顺序固定、空国家不出现、同国家去重),
该清单会整体推给 Python,格式错会直接导致采集错列。
2026-09-14 07:11:59 +08:00
huangzd1997 ef3a2c9bd6 docs(更新日志): 追加客户端 4.0.13 条目 2026-09-14 07:08:39 +08:00
huangzd1997 da0f10f1cc test(G4): 巡店删除 ResolveService 补 13 个契约测试(该模块此前零测试文件)
覆盖:用户校验、索引未命中/同名冲突拒绝、重复添加幂等、他人记录不可删(同文案防探测)、
匹配结果去重保序、count 空值兜底。
2026-09-14 07:05:04 +08:00
huangzd1997 9ba231dc4c feat(D2): 导入进度跨节点可见(NodeSharedStore:本地快路径 + Redis 真源)
- 新增 common/service/NodeSharedStore:本地 Map 快路径 + Redis 跨节点真源 + 写节流
  (默认 500ms,逐行刷新进度不会打爆 Redis)+ 本节点条目快照(维护用)+ TTL 兜底过期
- 接入 DedupeTotalDataService(8 个进度/归属/分组/完成时间映射)、QueryAsinService、
  SkipPriceAsinService(各 3 个):轮询落到另一节点不再报"任务不存在"
- 保留期清理改为遍历本节点快照(跨节点过期由 Redis TTL 兜底),不再依赖全量遍历
- 测试同步:去重服务测试的反射注入改用 NodeSharedStore(未注入 Redis 时等价纯本地)

mvn test 2815 全绿
2026-09-14 06:54:48 +08:00
huangzd1997 9a6b57db58 refactor+perf+fix: G5 模块边界 SPI 化、C5 流式解析、C6/C7 查询优化、D13 队列持久化、A3/A4/A7 鉴权
模块边界(G5 / G7)
- 新增 task/spi/TaskModuleHeartbeatSpi + 13 个模块实现:TaskHeartbeatService 不再 import 任何
  业务模块(原先注入 12 个 CacheService 并用 switch 分发);启动校验重复注册
- 新增 task/spi/BrandTaskHeartbeatSpi(品牌任务心跳/中断)、BrandTaskStaleRepairSpi(陈旧修复)、
  CollectDataItemCleanupSpi(历史清理):跨模块 Mapper 操作收回业务模块
- G7:10 个被跨模块借用的 productrisk VO 迁至 common/model/vo
- 架构棘轮收紧:TASK_TO_BUSINESS_BASELINE 119 → 6(实测)
- 新增 TaskModuleHeartbeatSpiCoverageTest(moduleType 覆盖与拼写)

性能与容量(C5/C6/C7/D13)
- C5 流式解析:SkipPriceAsinService(含两行表头语义)、AppearancePatentExcelParser、
  BrandTaskService、DeleteBrandRunService、LocalFileStorageService.getExcelInfo 改 ExcelStreamReader;
  行数上限改为迭代中生效
- C6 去重总数据列表:关键字改前缀匹配(命中 uk_data_value);V124 删除永不生效的 idx_country
- C7 撞款扫描:按店逐批取数(索引前缀),不再全表 GROUP BY + JOIN + 全量拉内存
- D13 待删对象本地日志 PendingDeleteJournal(启动回放 + 收敛重写),RustfsDeleteRetryService 与
  TransientPayloadDeleteOrchestrator 接入;异步删除失败对象写回日志
- V123 删除 biz_file_result 两个被复合索引覆盖的单列索引

安全(A3/A4/A7 + 守卫名单)
- A4 数字人版本写操作要求管理员;A7 视频密钥按登录身份(超管例外)
- A3 上传接口加危险扩展名黑名单(可配置)
- AdminApiGuardFilter 用户态名单补 /api/ziniao(controller 已 requireAdmin,此处为开关打开后的第二层)

容量(明细表保留期)
- 新增 ShopDataCrawlItemRetentionService:biz_shop_data_crawl_item 按保留期(默认 30 天)分批清理
  (该表此前无任何清理策略,是增长最快的表),job 锁 + 单轮批次上限
2026-09-14 06:46:27 +08:00
huangzd1997 67223f8950 perf(frontend): 工具页分页/历史截断/轻量轮询/共享纯逻辑(审查 F5/F6/F9/F12/G3)
- F9 新增 useTablePaging composable 并接入 7 个工具页"匹配结果"表(只切渲染窗口,
  不加选择语义;每页 100 条)+ 深色分页样式
- F6 历史任务抽屉渲染截断(默认 50 条 + "显示全部/收起"),全选口径改为当前可见条目
- F5 新增 task-progress-polling 适配器:等待任务终态的紧循环走 /tasks/progress/light,
  异常或空响应回退重型 batch;已接入跟价/定时匹配的 waitForTaskTerminal
- F12 背景图 bg.jpg 251KB → 166KB(1920 宽 + quality 80,image-set 仍优先 webp)
- G3 抽出共享纯逻辑 task-queue-state(开始时间表序列化校验 + 记录缺失错误判定),
  5 个工具页删除逐字重复实现
- 新增 4 个单测文件(分页切片/历史截断/轻量轮询归一/任务队列纯逻辑)
2026-09-14 06:23:46 +08:00