Files
jiu/docs/review/2026-07-03-uncommitted-review.md
T

10 KiB
Raw Blame History

代码审查报告 — 未提交变更集(出入库 rolling30 + DS 重构 + CI ali 单轨化)

审查时间2026-07-03 变更文件200 个文件,+10283 行,−9665 行(含 staged 删除 client/lib/widgets/data_table_card.dart 验证结果go build ./... / go vet ./... / go test ./... 全部通过;flutter analyze 0 error3 warning 均在 test/96 info

总体评价

变更集质量整体很高。后端 rolling30 窗口边界数学正确、窗口相接不重叠、有测试覆盖,新增查询多租户 shop_id 条件齐全;客户端大规模 DS 重构后,我逐一核对了全部 40+ 处确认弹窗的「按钮标签 ↔ onPressed ↔ pop 值」语义,未发现批量正则转换导致的错位;CI ali 单轨化的 DEPLOY_* 变量在 workflows / lib-forgejo.sh / deploy-*.sh / backup-db.sh 之间自洽。无阻塞合并的问题;「裸 IP 明文 HTTP」是用户拍板的备案过渡方案,但有两条具体的破坏路径需要确认(见 SHOULD-001/002)。


必须修复(blocking

无。


建议改进(should-fixnon-blocking 但建议尽快处理/确认)

[SHOULD-001] Web 版 mixed contenthttps 域名入口下 API 全被浏览器拦截

文件scripts/ci/compile-client-web.sh:20-21(同模式见 compile-android.sh:44compile-ios.sh:106compile-macos.sh:33compile-windows.sh:43scripts/local_test.sh:33 问题Web 构建的 BASE_URL 硬编码为 http://182.92.213.171:8443deploy-client.yml:183-184 注释说明 EC2 仍保留 jiu.51yanmei.com 反代桥接到 ali——若任何用户仍从 https://jiu.51yanmei.com/app 打开 Web 版,页面是 https 源、XHR 目标是 http,浏览器会以 active mixed content 直接拦截所有 API 请求,登录静默失败(native 桌面/移动端无此策略,不受影响)。 建议:三选一——① Web 构建 BASE_URL 留空走同源相对路径(AppConfig 里 Web 分支取 Uri.base),天然兼容 http/https 两个入口;② 确认 EC2 桥已把 /app 路径 301 到 http://182.92.213.171:8443/app;③ 明确弃用域名 Web 入口并在 baize 台账登记。

[SHOULD-002] 明文 HTTP 承载登录口令与 JWT(过渡期已知风险,建议登记回切项)

文件scripts/ci/compile-*.sh(同上)、client/android/app/src/main/AndroidManifest.xml:11client/ios/Runner/Info.plist:11client/macos/Runner/Info.plist:37 问题:备案过渡期所有客户端流量(含 /auth/login 的明文密码、后续每个请求的 JWT)走公网明文 HTTP;三端又同时开了全局明文豁免(Android usesCleartextTraffic="true"、iOS/macOS NSAllowsArbitraryLoads=true)。注释里已写「备案后应移除」,但散落在 6 个 compile 脚本 + 3 个平台清单共 9 处。另注意:iOS 带 NSAllowsArbitraryLoads=true 提交 App Store/TestFlight 可能被要求填写豁免理由。 建议:用 todo skill 登记一条「备案通过后回切 https」任务,列全 9 处文件清单,避免遗漏;iOS 可考虑收窄为 NSExceptionDomains 只豁免该 IP。

[SHOULD-003] manual.yml 回滚到割接前的旧 tag 会失败

文件.gitea/workflows/manual.yml:22-24, 31-35 问题manual 回滚 checkout ref: inputs.version 后执行旧 tag 里的 deploy 脚本。割接前的 tagdeploy 脚本尚未支持 DEPLOY_* 回退写法、直接引用 ${EC2_HOST})在 set -u 下会因 unbound variable 直接失败——而回滚恰恰最可能选旧版本。 建议:要么在 manual.yml env 里同时兼容传一份 EC2_* = ALI_*(让旧脚本也指向 ali),要么在 workflow 注释里写明「最早可回滚 tag」的下限。

[SHOULD-004] DsButton 无 disabled 视觉态,加载中按钮看起来仍可点

文件client/lib/widgets/ds/ds_atoms.dart:47InkWell(onTap: onPressed) 问题onPressed: null 时(如 about_screen.dart:758 提交反馈、stock_in_form_screen.dart 暂存/提交的 _submitting ? null : ...)按钮仅失去点击响应,颜色/边框与可用态完全一致,用户无从感知「提交中」。旧 Material 按钮自带 disabled 置灰,本次批量替换后此反馈丢失。 建议:在 DsButton.build 里对 onPressed == nullOpacity(0.5) 或 fg/bg 换 t.muted/t.bg(对齐原型 .btn:disabled 若有定义;没有则先在原型 atoms.css 登记再实现,遵守设计系统单一真源)。

[SHOULD-005] 结清成功后不刷新列表/财务数据

文件client/lib/screens/stock_in/stock_in_list_screen.dart:1101-1135client/lib/screens/stock_out/stock_out_list_screen.dart(同构 _confirmSettle 问题closeByRef 成功后只弹 snackbar,既不 reload() 列表也不 invalidate 财务相关 provider。若详情抽屉/行内展示了应收应付状态徽章,会显示过期状态;财务屏的 financePartnerRowsProvider 非 autoDispose 时也会陈旧(finance_screen.dart:105 有手动 invalidate,但只在财务屏自己的刷新路径里)。 建议:结清成功后补 ref.read(stockXxListProvider.notifier).reload()ref.invalidate(financePartnerRowsProvider)

[SHOULD-006] finance Trend 循环发 2N 条 SQLmonths=24 时 48 条)

文件backend/internal/handler/finance.go:198-215 问题:每个月对 stock_in_orders/stock_out_orders 各发一条 SUM 查询,months 上限 24 → 单请求最多 48 次往返。数据量小时无碍,但属于典型可合并聚合。 建议:各表一条 GROUP BY strftime/DATE_FORMAT(order_date, '%Y-%m')(或按窗口一条 SQL 取回后内存分桶),把 2N 降到 2。注意保持 MySQL/SQLite 双方言兼容(现有代码用日期串比较正是为此,分桶在 Go 内存里做最稳)。


细节(nit

[NIT-001] 出库列表头部计数回退值口径混用

文件client/lib/screens/stock_out/stock_out_list_screen.dart:466 '近30天 ${...summary30...monthCount ?? total}'——summary 未加载时回退到 total(当前筛选下的分页总数,非 30 天口径),文案与数值短暂不符。入库侧 stock_in_list_screen.dart:426 同构,建议未加载时显示

[NIT-002] DsSelect 按对象同一性判等

文件client/lib/screens/inventory/inventory_check_screen.dart:227 o.$1 == value 依赖实例同一性(Warehouse 未重写 ==)。仓库列表 provider 因换店/网络恢复重建后,_selectedWarehouse 指向旧实例,选中标签会回落成「请选择仓库」。建议 DsSelect<int>warehouse.id 做 value,或给模型加 ==/hashCode

[NIT-003] _resetPassword 的 TextEditingController 未 dispose

文件client/lib/screens/settings/users_screen.dart:461 对话框关闭后 ctrl 泄漏(对照 stock_in_list_screen.dart:1084_confirmCost 是有 dispose 的)。低危但建议补齐。

[NIT-004] finance Summary 的 GROUP BY 依赖 MySQL 函数依赖推导

文件backend/internal/handler/finance.go:233-247 SELECT COALESCE(p.name,'')GROUP BY f.partner_id, f.type。MySQL 5.7+ 的 ONLY_FULL_GROUP_BY 能识别 p.id = f.partner_id 的函数依赖所以现在能跑,但把 p.name 显式加进 GROUP BY 更防御(不同版本/方言行为差异)。

[NIT-005] deploy 脚本头注释仍写 EC2 为默认目标

文件scripts/ci/deploy-client.sh:5deploy-site.sh:4deploy-server.sh:21DEPLOY_TARGET:-ec2 默认值) 割接后 EC2 轨已从 workflows 移除,但脚本注释与 DEPLOY_TARGET 默认值仍是 ec2。行为不受影响(workflow 显式传 ali),属文档漂移;下次动这些脚本时顺手更正即可。

[NIT-006] 确认进价对话框:全部留空点「确认」无任何反馈

文件client/lib/screens/stock_in/stock_in_list_screen.dart:1087 ok == trueitems 为空时静默 return,用户不知道为什么没生效。建议 snackbar 提示「未填写任何进价」。


值得肯定的地方

  • rolling30 窗口边界正确summaryBoundsbackend/internal/handler/stock_in.go:170-180)当前窗 [今-29d, 明) 与对照窗 [今-59d, 今-29d) 各 30 天、首尾相接不重叠;默认分支与原 monthBounds 行为完全等价;stock_in_test.go:167-179 补了 -40 天单据落对照窗的边界测试
  • 多租户无缺口:本次触及的全部查询(stock_in/out Summary 的 agg 与 pending 计数、finance Trend 的 sum、finance Summary 的 Raw SQL、inventory summary 月初快照)均带 shop_id = ?shopID 一律取自 middleware.GetShopID(c),无一处从请求参数读
  • 批量转换零错位:逐一核对 40+ 处 DsButton 弹窗动作——取消一律 pop(false/null/void)、确认语义键一律 pop(true)_detailActionGroups 中标签与 _confirmDelete/Submit/Approve/Reject/Withdraw/Return/Settle 的映射全部对应正确
  • 除零防御到位_momDeltainventory_list_screen.dart:474)、StockSummary.countDeltaPct/amountDeltaPctstock_summary.dart:27-32)、DsBarChartmax<=0→1ds_bar_chart.dart:41)均有保护
  • 口径拆分清晰stockXxSummaryProvider(自然月,财务屏用)与 stockXxSummary30Providerrolling30,列表 KPI 用)并存,避免财务屏被列表口径变更波及,注释写明了用户拍板缘由
  • CI ali 单轨化自洽DEPLOY_SSH_KEY/HOST/USERlib-forgejo.sh:114-115、三个 deploy 脚本、backup-db.sh:20${DEPLOY_*:-$EC2_*} 回退写法一致;backup/manual/deploy-client/site/server 五个 workflow 均已切 ALI_* secrets
  • reload 保留旧数据inventory_provider.dart:105 copyWithPrevious)消除筛选切换白屏;WheelDatePanel 起止倒置时自动交换(wheel_date_picker.dart:285-287);stock_out 表单空明细在提交与渲染两处都有守卫

结论

  • 0 处 blocking 问题,可以合并
  • 建议改进 6 处(SHOULD-001/002 建议在下次发版前确认/登记回切项;003-006 可随后续迭代处理),nit 6 处不阻塞