Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YZ4DskSRKsSiheQonFtQvx
15 KiB
酒库管理系统 — 项目规则与 Orchestrator
必读
开始任何任务前,先读 docs/context/project.md 了解项目全貌。
Orchestrator:如何自动选择 Agent
根据用户请求的关键词和意图,按以下规则判断调用哪些 Agent:
新功能开发(完整流水线)
触发词:「新增」「开发」「实现」「做一个」+ 功能描述
串行:requirements-analyst → architect
并行:api-designer + db-designer(同时进行,都依赖架构但互不依赖)
→ ui-designer 判断复杂度:
简单变更:直接输出规范 → 继续下一步
复杂设计:输出 Figma 需求文档 → 暂停等用户确认设计稿
并行:backend-coder + flutter-coder(都依赖 API 设计,互不依赖)
串行:test-engineer → linter → code-reviewer → security-auditor
仅后端修改
触发词:「修复后端」「后端接口」「API」+ 具体描述
backend-coder → test-engineer → linter → code-reviewer
仅前端修改
触发词:「UI」「界面」「Flutter」「页面」
flutter-coder → linter
Bug 修复
触发词:「bug」「报错」「修复」「问题」
1. 先判断 bug 来源(后端/前端/数据库)
2. 调用对应的 coder agent 修复
3. test-engineer 验证修复
安全/代码质量检查
触发词:「安全」「漏洞」「review」「审查」「代码质量」
并行:security-auditor + code-reviewer + linter
线上问题
触发词:「线上」「故障」「宕机」「慢」「报错」
sre(优先级最高,立即响应)
→ 根据 sre 诊断结果,再决定是否调用 backend-coder 修复
文档需求
触发词:「文档」「说明」「手册」「README」
doc-writer
Agent 边界规则(强制执行)
| Agent | 可以写 | 不可以写 |
|---|---|---|
| requirements-analyst | docs/requirements/ | 任何代码 |
| architect | docs/architecture/ | 任何代码 |
| api-designer | docs/api/ | 任何代码 |
| db-designer | backend/schema/ | 业务代码、测试代码 |
| backend-coder | backend/internal/, backend/main.go | *_test.go, schema/ |
| ui-designer | docs/design/ | 任何代码 |
| flutter-coder | client/ | backend/ |
| test-engineer | _test.go, docs/review/-bugs.md | 业务代码 |
| linter | 格式自动修复(gofmt/dart format),docs/review/lint-report.md | 逻辑代码 |
| code-reviewer | docs/review/*-review.md | 任何代码 |
| security-auditor | docs/security/ | 任何代码 |
| devops | deploy/, .gitea/workflows/, scripts/ci/ | 业务代码 |
| sre | docs/runbooks/ | 任何代码 |
| doc-writer | docs/(除 context/ 外) | 任何代码 |
Agent 间通信协议
文件传递(主要方式):
- 每个 Agent 完成后将结果写入约定文件
- 下游 Agent 开始前读取上游产出文件
- 文件格式:混合模式(中文叙述 + YAML/代码块)
短链式调用(反馈循环):
- test-engineer 发现 bug → 直接将错误信息和
docs/review/{功能}-bugs.md路径传给 backend-coder - security-auditor 发现 Critical 问题 → 直接告知 backend-coder 修复,不等下次迭代
并行 Agent 调度规则
当多个 Agent 可以并行时,在同一条消息中发起多个 Agent 调用:
# 可以并行的情况(互不依赖):
- api-designer + db-designer
- backend-coder + flutter-coder
- security-auditor + code-reviewer + linter
# 必须串行的情况(有依赖):
- requirements-analyst 完成后 → architect 才能开始
- architect 完成后 → api-designer 和 db-designer 才能并行开始
- backend-coder 完成后 → test-engineer 才能开始
Git 提交规范
每个 Agent 完成工作后提交,commit message 格式:
{类型}({模块}): {简短说明}
类型:feat | fix | test | docs | chore | security | refactor
模块:backend | client | db | deploy | docs
示例:
feat(backend): 新增财务报表接口
test(backend): 财务报表模块测试用例
docs(api): 财务报表 API 接口文档
security: 修复入库单多租户隔离漏洞
开发完成标准(Definition of Done)
任何功能或修复,必须满足以下全部条件才算完成:
后端变更
# 1. 编译通过
export PATH="/opt/homebrew/bin:$PATH"
cd backend && go build ./...
# 2. 所有测试通过(不得有 FAIL)
cd backend && go test ./...
# 3. 无编译警告(vet 检查)
cd backend && go vet ./...
前端变更
# 1. 静态分析无 error(warning/info 允许存在)
cd client && flutter analyze --no-fatal-infos --no-fatal-warnings
# 2. 所有测试通过
cd client && flutter test
通用要求
- 代码已按 Git 提交规范提交(feat/fix/test/...)
- 涉及新接口:已在对应测试文件中覆盖核心路径
- 涉及数据库变更:schema.sql 和 model 同步更新
- 多租户隔离未被破坏(所有查询含 shop_id 条件)
测试未通过前,禁止提交代码、禁止打 tag 发版。
代码质量门禁
以下情况不得提交:
go build ./...失败go test ./...有失败用例- 存在 security-auditor 标记的 Critical 级别问题
- 存在 code-reviewer 标记的 blocking 级别问题
项目强制规则
多租户隔离
shop_id永远从 JWT token 中提取,使用middleware.GetShopID(c)- 绝不从请求参数、URL 或请求体中读取
shop_id - 所有数据库查询必须带
WHERE shop_id = ?条件
数据模型:product = 特有产品/序列号(核心铁律)
products每行 = 一个特有产品/序列号(code=商品编号,同店唯一uk_shop_code),不是 SKU;语义:name=品牌、series=型号、spec=版本。product 是商品信息的单一来源(序列号/品牌型号版本/生产日期production_date/批次batch_no/进价/图片/public_id)。- 入库每录一行 = 新建一个独立 product(
createIndependentProduct+nextProductCodemax+1),绝不按名称/系列/规格复用(findOrCreate复用已废弃)。同理任何"导入/建库存"逻辑:有商品编号时按编号匹配/建 product,禁止按名称合并(历史 bug 根因)。 - 基础数据字典(品牌/型号/版本 + 产地/保质期/储存/介绍)在「基础数据」页管理,入库选文本后据此建 product。
- 库存(
inventories)/出入库明细 = 指向 product 的引用 + 快照列(product_code/name/series/spec/...,导入/审核时拷贝,作历史保真+显示兜底)。显示优先 product、回退快照(COALESCE(p.*, 快照)/ 前端lineOrProduct)。快照列保留,勿擅自删。
库存变更
- 入库/出库审核通过时,必须在同一事务中同时完成:
- 更新
inventories表数量 - 写入
inventory_logs流水记录
- 更新
- 出库前必须校验库存充足,不足时返回错误并回滚
- 审核中(pending)单据支持撤回(
withdraw)回 draft:管理员/超管任意单、操作员限本人单(OperatorID==本人,handler 内判权,非 AdminOnly 中间件)
Schema 管理
- 表结构变更:修改
backend/schema/schema.sql+backend/internal/model/对应 model - 启动时自动 AutoMigrate,无单独迁移文件
- 禁止为每个新业务字段修改表结构,使用
custom_fields JSON动态扩展
种子数据
- 测试数据以 SQL 文件形式维护:
backend/seeds/<shop_code>.sql - 通过
sh scripts/dev.sh seed <shop_code>执行(内部用docker exec注入 MySQL 容器) - 每个门店一个文件,SQL 文件顶部包含 TRUNCATE,每次执行完整重建
Go 命令
- 所有 Go 命令前须确保 PATH:
export PATH="/opt/homebrew/bin:$PATH" - 或直接使用
sh scripts/dev.sh脚本(已内置 PATH)
前端 URL 配置
- 所有后端 URL 必须通过
AppConfig读取(client/lib/core/config/app_config.dart) - 禁止在任何 provider / repository / widget 中硬编码
localhost:8080或任何 IP/域名 - 使用:
AppConfig.apiBaseUrl、AppConfig.healthUrl、AppConfig.versionUrl
前端平台判断
- 使用
dart:io的Platform类前,必须先检查kIsWeb(import 'package:flutter/foundation.dart') - Web 平台不支持
dart:io,直接使用会在 Web 构建时崩溃
表格列头筛选
- 可筛选列使用
FilterableColumnHeader(来自widgets/multi_select_dropdown.dart) - 不要在 toolbar 放独立的筛选按钮,筛选入口应内嵌在列头
- 列定义使用
ColDef,可隐藏列用ColumnToggleButton控制
响应式 / 移动端适配
- 断点判定统一用
context.isMobile(client/lib/core/responsive/responsive.dart,宽度 < 600 为窄屏/手机),禁止在各处散落MediaQuery.width < 600这类魔法数 - 列表屏:用
DataTableCard时必须同时传mobileCards(窄屏渲染卡片流),卡片复用MobileListCard/MobileCardField(widgets/mobile_list_card.dart);宽屏仍走原表格,行为不变 - 弹窗固定宽度:一律用
context.dialogWidth(X)(≤ 屏宽 92%),禁止裸写width: <固定值>导致窄屏溢出 - 导航:窄屏(手机)用 Drawer 抽屉(
app_shell.dart),宽屏保持侧边栏;新增页面无需单独处理,挂在 shell 下即可 - 平台判断仍遵守上面「前端平台判断」规则(
kIsWeb先于dart:io)
权限中间件(已全局挂载)
middleware.ReadOnly():已挂载到主api路由组,只读用户(role=readonly)所有写操作自动返回 403middleware.AdminOnly():挂载在/users路由组,仅管理员可管理用户- 新增写操作路由时,默认受
ReadOnly()保护;若仅管理员可用,需加入AdminOnly()子组
并发安全(数据库操作)
- 在同一事务内读后写的操作,必须用
FOR UPDATE锁行:tx.Set("gorm:query_option", "FOR UPDATE").Where(...).First(&model) - 适用场景:单号生成(
number_rules)、库存扣减(inventories)、任何 check-then-act 模式
发版流程
发版拆成三条互不影响的流水线,各有 tag 前缀、独立版本序列、独立 CHANGELOG:
| part | 范围 | tag 前缀 | CHANGELOG | workflow |
|---|---|---|---|---|
| client | client/ Flutter 全平台(Web→/app、macOS、Windows、Android、iOS)+ 应用自更新清单 version.yaml |
client-v* |
CHANGELOG-client.md |
deploy-client.yml |
| site | web/ Eleventy 营销宣传站(→/opt/jiu/marketing,nginx /)。不含 Web 版 app |
site-v* |
CHANGELOG-site.md |
deploy-site.yml |
| server | backend/ Go 服务(jiu-server)+ 共享基建 nginx-jiu.conf / jiu.service |
server-v* |
CHANGELOG-server.md |
deploy-server.yml |
使用 /release <part> [version] slash command:
/release client 1.0.55 # 指定版本
/release server # 省略则自增 server-v* 最新 tag 的 patch
执行顺序:本地 build → test(按 part)→ 更新对应 CHANGELOG → git commit → tag <part>-v<ver> → push main+tag。
CI/CD(Forgejo)按 tag 前缀触发对应 workflow,自动:编译 → 测试 → 创建 Release → 部署 EC2 → Telegram 通知。
归属与解耦(关键):
version.yaml归 client(写 version/build_number/release_notes/下载链接/changelog);nginx/systemd 归 server。- 后端
/version与/api/v1/public/release每请求实时读version.yaml,无缓存——client 部署 version.yaml 后立即生效,不重启后端、不触发 server 流水线。 - 官网下载页运行时
fetch('/api/v1/public/release'):版本徽章、各平台下载链接、更新日志时间线全部动态刷新——client 发版无需重建官网。接口不可达时回退到构建时静态内容(web/_data/changelog.js读CHANGELOG-client.md)。
CI 脚本(scripts/ci/):lib-forgejo.sh(公共函数)+ compile-{client-web,site,backend,macos,android,ios,windows}.sh + release-{client,site,server}.sh + deploy-{client,site,server}.sh。compile-{macos,android,ios,windows}.sh 去 client-v 前缀取版本。
client 多平台构建矩阵:mac runner(容量 1)串行链 build-client-web → build-macos → build-android → build-ios;windows runner 并行 build-windows(Inno Setup);release-deploy-client 收齐后发布。Android 需 ANDROID_*、iOS 走 TestFlight 需 IOS_*/APPSTORE_* secrets,未配置时该 job 优雅跳过(exit 0,不阻塞)。
手动回滚:manual.yml 输入带前缀 tag(如 client-v1.0.54),按前缀路由到对应 deploy-<part>.sh,从 Forgejo Release 下载产物。
CHANGELOG 格式(Keep a Changelog,三个文件同构):
## [1.0.2] - YYYY-MM-DD
### 新功能
- ...
### 改进
- ...
### 修复
- ...
只保留有内容的分类。/release 会检查对应 CHANGELOG 是否已有该版本节,没有则从该 part 上一个 tag 的 git log 自动生成。
异常上报
客户端异常已在两处统一捕获,无需在每个业务层重复处理:
main.dart:FlutterError.onError+runZonedGuarded捕获所有未处理异常api_client.dart:Dio 拦截器自动上报所有 HTTP 5xx 错误
需要手动调用 reportError(e, st) 的场景:
- 技术性异常(JSON 解析失败、第三方 SDK 崩溃等)
- 不需要上报:
AppException及其子类(已知业务错误)
} catch (e, st) {
reportError(e, st); // 一行,fire-and-forget
rethrow;
}
项目 TODO 管理
统一使用全局 todo 工具(~/.claude/skills/todo/,遵循全局 ~/.claude/CLAUDE.md 的规则)。本项目不再维护自己的 /todo slash command(已删除),一律调用全局 todo skill。
- 数据落地:全局 skill 把待办存到当前项目的
todo/目录(todo.json+ 看板todo/todo.html,首次使用自动创建);命令须在项目根目录运行,例如node ~/.claude/skills/todo/todo.mjs list。 - 禁止使用 TaskCreate / TodoWrite 等任何内置 todo 工具,一律走全局 todo skill。
- 改动等级把控:接口/schema/跨 2+ 模块的「大改」先进 plan 模式经用户批准,完成后同步更新设计文档;小 bugfix 直接做。
搜索实现(拼音)
商品名搜索同时支持汉字、全拼、首字母:
products表有name_pinyin(全拼)和name_initials(首字母)两列- Create / Update / FindOrCreate 写入时自动调用
util.ToPinyin()生成,无需手动维护 - 启动时对
name_pinyin = ''的存量数据自动回填(backfillPinyin) - 库存搜索 SQL 同时 LIKE 匹配 name、code、name_pinyin、name_initials
- 新增其他需要拼音搜索的实体时,复用
backend/internal/util/pinyin.go的ToPinyin()