岩美酒库 · 开发手册

面向后续开发者/维护者 · server v1.1.1 / client v1.1.1 · 更新于 2026-07-03 · 来源以仓库代码为准(backend/ client/ deploy/ scripts/
目录 1. 系统架构总览 2. 后端(Go / Gin) 3. 前端(Flutter) 4. 数据库 5. API 参考 6. 数据操作与运维 7. 开发环境与工作流

1. 系统架构总览

五端 Flutter 客户端统一访问 https://jiu.51yanmei.com。阿里云主机上 nginx 1.24 终结 TLS(443 ssl+http2,certbot webroot 自动续期,80 → 443 跳转),按 location 分发:API/健康检查/版本反代到本机 8081 的 Go 后端(systemd jiu.service),静态资源(Flutter Web /app、商品图 /images、安装包 /downloads、营销站根路径)直接由 nginx 服务;/product/:id/app/product/:id(扫码分享页)回代后端注入 OG 标签。后端连本机 Docker 容器 jiu_mysql(MySQL 8,127.0.0.1:3306)。未鉴权的 /api/v1/(public|auth)/ 在 nginx 层再加一道 per-IP 限流(10r/s burst 20)。源:deploy/nginx-jiu-ali.confdeploy/jiu.service

https://jiu.51yanmei.com proxy_pass 127.0.0.1:8081 127.0.0.1:3306 客户端 · FLUTTER 五端 Windows macOS Web(/app) Android iOS ali · nginx 1.24 — 443 ssl+http2(certbot webroot 自动续期)· 80 → 443 静态直出 /app · /images · /downloads · 官网 反代 API /api · /health · /version → :8081 限流反代(per-IP) /api/v1/(public|auth) 10r/s·burst20 公开页 SSR /product · /shop → 回代后端 证书 /etc/letsencrypt/live/jiu.51yanmei.com/ · client_max_body_size 20m · /import 超时 300s · 安全头 HSTS / XFO / nosniff jiu-server(Go / Gin)— systemd jiu.service · :8081 /opt/jiu/backend/jiu-server · EnvironmentFile=/opt/jiu/config/production.env MySQL 8 — docker jiu_mysql 127.0.0.1:3306 · 库 jiu_db · 每日备份见 §6 Redis 7 — jiu_redis 限流/登录锁 · :6379
阿里机 8080 已被 pay 项目 payd 占用,后端端口固定 8081;本地开发默认仍是 8080。改 nginx 时只动 jiu 的 server block(/etc/nginx/conf.d/jiu.conf),别碰同机其他站点。

2. 后端(Go / Gin)

2.1 目录分层

目录 / 文件职责
main.go入口:配置加载、生产前置检查(CORS/JWT 密钥)、连库、AutoMigrate、启动回填、SetTrustedProxies、挂路由
cmd/一次性/运维工具(seed、gencode、import-history、fix-inventory-products、backfill-pinyin,详见 §6.4)
config/Viper 配置结构体 + config.yaml(本地,不入 git)+ version.yaml(发版清单,/version 实时读)
internal/handler/HTTP 处理器,每业务模块一个文件;参数校验 + 组织响应,*_test.go 为 SQLite in-memory 集成测试
internal/service/业务逻辑层:auth(登录/失败锁定)、license(兑换券)、stock(审核/库存事务)、migrate(启动回填)、session_cleanup
internal/model/GORM 模型;base.go 提供 Base/TenantBase/Date/JSON 公共类型
internal/middleware/JWT 鉴权 + 会话校验、角色拦截(ReadOnly/AdminOnly/SuperAdminOnly)、LicenseGuard、限流
internal/util/通用工具(pinyin.go 拼音搜索列生成等)
internal/router/router.go全部路由注册与中间件编排(§5 的唯一真相源)
schema/schema.sql完整建表 DDL(新装用;线上结构演进靠 AutoMigrate)
seeds/门店种子数据 SQL(S001 全量测试数据、S002 空门店)
testutil/setup.go测试工具:SQLite in-memory DB、CreateTestXxx 工厂、GetAuthToken

2.2 JWT 与多租户隔离

源:backend/internal/middleware/auth.go。JWT(HS256,Access 60 分钟 / Refresh 7 天)Claims 携带 user_id / shop_id / role / sid / lic_exp。带 sid 的 token 每请求校验 user_sessions(撤销/禁用即时下线,last_seen_at 30s 节流刷新)。

铁律:shop_id 永远用 middleware.GetShopID(c) 从 JWT 取,绝不从请求参数/URL/请求体读;所有查询必须带 WHERE shop_id = ?。违反即多租户越权。

2.3 角色中间件

中间件挂载位置行为
ReadOnly()业务路由组全局 + /licenserole=readonly 的非 GET 请求 → 403(code: READONLY_USER
AdminOnly()/users 组、/shop 写、/sessions/:id 删admin / superadmin 放行
SuperAdminOnly()/admin 组superadmin 放行(清数据/对账/错误报告/反馈后台)

角色顺序:superadmin > admin > operator > readonly。新增写路由默认已受 ReadOnly 保护;仅管理员可用的要再包 AdminOnly 子组。

2.4 LicenseGuard(授权过期降级)

源:middleware/license_guard.go。按当前 DB 的有效授权实时算 phase(每店 30s 缓存,激活/续费调用 InvalidateLicensePhase 即时生效),不信任登录时 token 快照:

phase条件效果
normal未过期 / 永久授权正常读写
grace过期 0–7 天可写,前端展示提醒横幅
readonly过期 7–15 天非 GET → 403(phase: readonly
locked过期 15+ 天,或授权全部被停用(吊销)所有业务请求 403(phase: locked

/license/* 与 登出/心跳/在线会话 挂在 LicenseGuard 之外——锁定时仍能查看授权状态并激活新码。

2.5 限流

源:middleware/ratelimit.go(进程内令牌桶 + janitor 清理,config.C.RateLimit.Enabled=false 可整体关闭)。两个维度:

超限返回 429 + Retry-After: 60。nginx 层对 /api/v1/(public|auth)/ 还有最外层 10r/s 兜底(§1)。

2.6 单据审核流状态机

入库单 / 出库单同一状态机(service/stock.go + handler/stock_in.gostock_out.go)。审核通过时同一事务内:更新 inventories(FOR UPDATE 锁行)+ 写 inventory_logs + 生成财务应收/应付;出库先校验库存充足,不足回滚。

draft 草稿 pending 审核中 approved 已审核 rejected 已拒绝 PUT /:id/submit withdraw 撤回(管理员任意单 / 操作员限本人单) approve 审核通过 reject 拒绝 approved 后续动作(POST,service 内判权): return 退单(冲库存+冲应收/应付) · confirm-cost 确认进价(入库) confirm-sale 确认售价(出库,重算应收/利润) draft 可改/可删

2.7 AutoMigrate 与启动回填

2.8 拼音搜索

源:internal/util/pinyin.go(go-pinyin)。ToPinyin(name) 返回全拼 + 首字母,Create/Update 时自动写入 name_pinyin / name_initials 两列;搜索 SQL 同时 LIKE 匹配 name、code、name_pinyin、name_initials(汉字/全拼/首字母三种输入都命中)。新增需要拼音搜索的实体直接复用 ToPinyin()

2.9 客户端异常上报

客户端 POST /api/v1/public/errors(per-IP 限流)→ error_reports 表;超管在 GET /api/v1/admin/errors 查看。前端两处统一捕获(main.dart 的 runZonedGuarded + Dio 拦截器上报 5xx),业务代码只在技术性异常处手动 reportError(e, st),已知业务错误(AppException)不上报。

3. 前端(Flutter)

3.1 目录分层

目录职责
lib/core/横切基建:api(Dio 封装,401 自动刷新)、auth(AuthNotifier + 持久化)、config(AppConfig,BASE_URL 注入)、router(go_router 登录重定向)、theme(token 三主题)、responsive、update(应用内更新)、errors(reportError)
lib/models/与后端 JSON 对应的数据模型
lib/providers/Riverpod 状态管理,每业务模块一个 provider(列表缓存/刷新/心跳等)
lib/repositories/数据访问层:封装 API 调用,供 provider 消费
lib/screens/页面(auth/shell/stock_in/stock_out/inventory/partners/finance/products/devices/settings/about/public/shared)
lib/widgets/共享组件;widgets/ds/ 为设计系统组件库(唯一样式来源,见 3.3)

3.2 数据流

单向:Screen 只组合 ds 组件并 watch provider;provider 调 repository;repository 走 Dio(ApiClient 统一注入 token、401 自动刷新、5xx 自动上报)。

Screenscreens/ + ds 组件 ProviderRiverpod 状态/缓存 RepositoryAPI 调用封装 Dio401 刷新 · 5xx 上报 API

3.3 ds 设计系统(单一真相源)

UI 的真相源是原型 design/prototype/(tokens + atoms.css);Flutter 侧一对一镜像,屏只组合 ds 组件、不自己堆样式。令牌层由 lib/core/theme/token_source/tokens.css codegen 生成 app_tokens.g.dart / app_dims.g.dart / app_chrome.g.dart——改样式改源后重跑脚本,禁止手改 .g 文件、禁止硬编码色值

widgets/ds/ 文件镜像对象
ds_atoms.dartatoms.css 原子组件全集(按钮/输入/徽章/卡片…),尺寸引 AppDims、颜色引 context.tokens
ds_table.dart原型 .table + .toolbar + .pager(toolbar 与表连成一卡,pager 卡外透明)
ds_kpi.dart原型 .kpi 汇总卡(图标块 .ic 五种 tone)
ds_menu.dart原型 .menu 下拉(定位规则照抄 shell.js openMenu)
ds_toast.dart原型 .toast 1:1(单例顶替、2.2s 自动消失)
ds_bar_chart.dart原型 finance.html 收支趋势分组柱状图(度量逐像素照抄)
grid_combo_cell.dart表格内联可搜索下拉单元格(入库/出库编辑网格用)

闸机 1 — 代码端client/tool/check_ds_code.mjs 扫描 lib/ 禁 Color(0x..)Colors.x 硬编码(合理特例需 // ds-ignore: 理由),--changed --strict 供 pre-commit。

3.4 golden × 3 主题 + fidelity 像素闸

cd client && flutter test --update-goldens test/golden/inventory_list_golden_test.dart
node tools/fidelity.mjs inventory          # 仓库根运行;--themes a,b,c;--update 刷新原型基准

3.5 响应式

断点统一 context.isMobile(宽 < 600,core/responsive/responsive.dart),弹窗宽度 context.dialogWidth(X)(≤ 屏宽 92%)。列表屏 DataTableCard 必传 mobileCards(窄屏卡片流),窄屏导航为 Drawer 抽屉。平台判断先 kIsWebdart:io Platform。后端 URL 一律 AppConfig--dart-define=BASE_URL=... 注入),禁止硬编码。

4. 数据库

MySQL 8 · 所有业务表含 shop_id 租户隔离列。schema.sql 定义 26 张表;另有 5 张仅由 AutoMigrate 创建(4 张商品属性字典 + error_reports),共 31 张。完整列级文档见 db-schema.html

4.1 分组速览

分组要点
租户与账号shops
users
user_sessions
login_attempts
shops=租户根(code 唯一);users 角色 ENUM 四档、uk_shop_username;user_sessions 支撑 sid 会话校验/踢人(revoked_at、last_seen_at);login_attempts 记失败登录供锁定
授权licenses
license_devices
license_codes
时长兑换券模型:license_codes 为平台码池(短码),激活兑入 licenses(expires_at 驱动 LicenseGuard),license_devices 记设备绑定
商品与字典products
product_categories
product_images
product_*_options ×6
product_description_docs
product = 特有产品/序列号(非 SKU):code 同店唯一(uk_shop_code),name/series/spec=品牌/型号/版本,含 name_pinyin/name_initials 搜索列、public_id 公开页;字典表(名称/系列/规格/产地/保质期/储存/介绍)供入库选取后建 product
仓库与往来warehouses
partners
partners.type=supplier/customer,含拼音搜索列
入库stock_in_orders
stock_in_items
单据状态机(§2.6)、uk_order_no(shop_id,order_no);明细含快照列 + cost_price/cost_amount + 批次/生产日期/有效期
出库stock_out_orders
stock_out_items
同状态机;单头 sale_total/profit_total;明细 cost_price(成本快照)+ sale_price/sale_amount;idx_so_items_shop_code 支撑按编码反查
库存inventories
inventory_logs
inventory_checks
inventory_check_items
inventories=批次行(每行一个入库批,stock_in_item_id 溯源,idx_fifo 支撑先进先出);logs 记每次变动 qty_before/after + ref;盘点单 draft→completed
财务finance_recordstype=receivable/payable/receipt/payment,balance=操作后余额,status=open/closed,ref_type/ref_id 关联单据
其他number_rules
feedbacks
error_reports
number_rules 每店每类型一行(uk_shop_type),单号=前缀+日期+6 位序号;feedbacks 意见反馈;error_reports 客户端异常

4.2 定价字段口径(2026-07 消歧)

新列旧列(弃用)口径
stock_in_items.cost_priceunit_price进价 · 单瓶
stock_in_items.cost_amounttotal_price总进价 = quantity × cost_price
stock_in_orders.cost_totaltotal_amount应付合计 = Σ 明细 cost_amount
stock_out_items.cost_priceunit_price成本单价(入库成本快照)
stock_out_items.cost_amounttotal_price成本小计 = quantity × cost_price
stock_out_items.sale_price—(新增)实际销售单价,0 = 待定价
stock_out_items.sale_amount—(新增)售价小计 = quantity × sale_price(待定价 = 0)
stock_out_orders.sale_totaltotal_amount应收合计 = Σ 明细 sale_amount
stock_out_orders.profit_total—(新增)总利润 = Σ(sale_price>0 ? (sale_price−cost_price)×qty : 0),建单落库,确认售价/进价联动重算
行利润只统计已定价行(sale_price>0);待定价行不计利润也不计应收。旧列已停止读写,由启动回填拷值(§2.7),观察一版后手动 DROP——期间新代码引用旧列。

4.3 关键机制

5. API 参考

逐条核对自 backend/internal/router/router.go。权限图例:公开 无需登录(多带 per-IP 限流);JWT 登录即可(readonly 角色写操作被 ReadOnly 拦);Admin = AdminOnly;Super = SuperAdminOnly;限流 = 独立 IP 限流。所有 JWT 路由另受 per-Shop 限流;业务组(商品及以下)还受 LicenseGuard(§2.4)。

5.1 根路由 / 认证 / 公开接口

MethodPath说明权限
GET/health健康检查(连通性探测)公开
GET/version版本清单(实时读 version.yaml)公开
GET/product/:public_id扫码分享页(OG 标签注入的 index.html)公开 限流
POST/api/v1/auth/login登录(失败锁定 + IP 限流双保险)公开 限流
POST/api/v1/auth/refresh刷新 token公开 限流
GET/api/v1/public/products/:public_id公开商品详情(扫码页数据)公开 限流
GET/api/v1/public/shops/:shop_code/products店铺公开商品列表(限流最紧)公开 限流
GET/api/v1/public/release版本 + 下载链接 + changelog(官网/更新用)公开 限流
POST/api/v1/public/errors客户端异常上报 → error_reports公开 限流
POST/api/v1/public/register门店注册公开 限流

5.2 会话 / 授权(豁免 LicenseGuard)

MethodPath说明权限
POST/api/v1/auth/logout登出(撤销会话)JWT
POST/api/v1/auth/ping在线心跳JWT
GET/api/v1/sessions在线会话列表JWT
DELETE/api/v1/sessions/:id强制下线Admin
GET/api/v1/license/info授权状态(与 LicenseGuard 同一取数口径)JWT
POST/api/v1/license/activate兑换/激活授权码(锁定期仍可用)JWT
GET/api/v1/license/verify校验当前授权JWT
POST/api/v1/license/deactivate解绑设备JWT
GET/api/v1/license/devices已绑定设备列表JWT
POST/api/v1/license/purchase在线购买/续费下单,返回 pay 收银台 pay_url(契约 pay-contract v1.0.0)Admin
GET/api/v1/license/purchase/:out_trade_no购买单状态(支付结果页轮询,本店可见)JWT
POST/api/v1/pay/callbackpay 支付成功 webhook(HMAC 验签+幂等+金额核对后续期)公开

5.3 商品 / 仓库 / 往来单位

MethodPath说明权限
GET/api/v1/products商品列表(汉字/全拼/首字母/编码搜索)JWT
POST/api/v1/products新建商品(独立序列号,code 自增 max+1)JWT
POST/api/v1/products/find-or-create按编号找/建 product(导入用;禁止按名称合并)JWT
GET/api/v1/products/:id/detail商品详情(含图片/字典属性)JWT
GET/api/v1/products/:id/price-history进价/售价历史JWT
GET/api/v1/products/:id/qrcode公开页二维码JWT
PUT/api/v1/products/:id更新商品JWT
DELETE/api/v1/products/:id删除商品(软删)JWT
POST/api/v1/products/:id/images上传商品图片JWT
DELETE/api/v1/products/:id/images/:image_id删除商品图片JWT
GET/api/v1/warehouses仓库列表JWT
POST/api/v1/warehouses新建仓库JWT
PUT/api/v1/warehouses/:id更新仓库JWT
DELETE/api/v1/warehouses/:id删除仓库JWT
GET/api/v1/partners往来单位列表(?type&keyword,拼音搜索)JWT
POST/api/v1/partners新建往来单位JWT
PUT/api/v1/partners/:id更新往来单位JWT
DELETE/api/v1/partners/:id删除往来单位JWT

5.4 入库 / 出库

MethodPath说明权限
GET/api/v1/stock-in/orders入库单列表(?status&keyword,编码双路匹配)JWT
GET/api/v1/stock-in/summary入库汇总(KPI 卡)JWT
GET/api/v1/stock-in/orders/:id入库单详情JWT
POST/api/v1/stock-in/orders创建入库单(每明细行新建独立 product)JWT
PUT/api/v1/stock-in/orders/:id更新(仅 draft)JWT
DELETE/api/v1/stock-in/orders/:id删除(仅 draft)JWT
PUT/api/v1/stock-in/orders/:id/submit提交审核 draft→pendingJWT
PUT/api/v1/stock-in/orders/:id/approve审核通过(事务:+库存 +流水 +应付)JWT
PUT/api/v1/stock-in/orders/:id/reject拒绝JWT
PUT/api/v1/stock-in/orders/:id/withdraw撤回 pending→draft(管理员任意/操作员本人)JWT
POST/api/v1/stock-in/orders/:id/return退单(库存删除 + 冲应付;service 内判管理员)Admin
POST/api/v1/stock-in/orders/:id/confirm-cost确认进价(暂估 0 价→真实价,前向补偿)Admin
GET/api/v1/stock-out/orders出库单列表(?status&keyword)JWT
GET/api/v1/stock-out/summary出库汇总JWT
GET/api/v1/stock-out/orders/:id出库单详情JWT
POST/api/v1/stock-out/orders创建出库单JWT
PUT/api/v1/stock-out/orders/:id更新(仅 draft)JWT
DELETE/api/v1/stock-out/orders/:id删除(仅 draft)JWT
PUT/api/v1/stock-out/orders/:id/submit提交审核JWT
PUT/api/v1/stock-out/orders/:id/approve审核通过(校验库存充足,事务扣减 FIFO)JWT
PUT/api/v1/stock-out/orders/:id/reject拒绝JWT
PUT/api/v1/stock-out/orders/:id/withdraw撤回 pending→draftJWT
POST/api/v1/stock-out/orders/:id/return退单(库存加回 + 冲应收;管理员或本人)JWT
POST/api/v1/stock-out/orders/:id/confirm-sale确认售价(先出后定价,重算应收/利润)Admin

5.5 库存 / 财务 / 用户 / 店铺 / 反馈 / 编号规则

MethodPath说明权限
GET/api/v1/inventory库存列表(?warehouse_id&keyword&series&spec&in_stock)JWT
GET/api/v1/inventory/summary库存汇总JWT
GET/api/v1/inventory/logs库存流水JWT
PUT/api/v1/inventory/:id/remark更新库存行备注JWT
POST/api/v1/inventory/checks创建盘点单JWT
GET/api/v1/inventory/checks/:id盘点单详情JWT
PUT/api/v1/inventory/checks/:id/complete完成盘点(盘盈盘亏落库)JWT
GET/api/v1/finance/records财务流水列表(含日期区间)JWT
POST/api/v1/finance/records手工记账(收款/付款)JWT
PUT/api/v1/finance/records/:id/close结清单条应收/应付JWT
PUT/api/v1/finance/records/close-by-ref按关联单据结清JWT
GET/api/v1/finance/summary财务汇总JWT
GET/api/v1/finance/trend收支趋势(柱状图数据)JWT
GET/api/v1/users用户列表Admin
POST/api/v1/users新建用户Admin
PUT/api/v1/users/:id更新用户(角色/启用)Admin
PUT/api/v1/users/:id/reset-password重置密码Admin
GET/api/v1/shop/info店铺信息JWT
PUT/api/v1/shop/info更新店铺信息Admin
POST/api/v1/shop/logo上传店铺 logoAdmin
POST/api/v1/feedback提交意见反馈JWT
POST/api/v1/feedback/images上传反馈附图JWT
GET/api/v1/number-rules单号规则列表JWT
PUT/api/v1/number-rules/:id更新单号规则JWT

5.6 数据导入(Excel)

MethodPath说明权限
POST/api/v1/import/products导入商品JWT
POST/api/v1/import/partners导入往来单位JWT
POST/api/v1/import/product-names导入名称字典JWT
POST/api/v1/import/product-series导入系列字典JWT
POST/api/v1/import/product-specs导入规格字典JWT
POST/api/v1/import/product-codes导入商品编码JWT
POST/api/v1/import/stock-in导入历史入库单JWT
POST/api/v1/import/stock-out导入历史出库单JWT
POST/api/v1/import/inventory导入库存(按编号建 product,禁按名称合并)JWT

5.7 基础数据字典(/api/v1/product-options)

八组字典同构:每组 GET(支持 ?keyword 服务端搜索)/ POST / PUT /:id / DELETE /:id,共 32 条路由,权限均为 JWT

子路径字典
/names商品名称(品牌)
/series系列(型号)
/specs规格(版本)
/categories香型 / 分类
/origins产地
/shelf-lives保质期
/storages储存方式
/description-docs商品介绍文档

5.8 超级管理员(/api/v1/admin)

MethodPath说明权限
POST/api/v1/admin/clear-data清空本店业务数据Super
GET/api/v1/admin/reconcile库存对账(inventories vs logs)Super
GET/api/v1/admin/errors客户端异常报告列表Super
GET/api/v1/admin/feedback意见反馈列表Super
PATCH/api/v1/admin/feedback/:id更新反馈处理状态Super

6. 数据操作与运维

6.1 Schema 管理

backend/schema/schema.sql = 新装环境的完整 DDL(docker 初始化时执行);线上结构演进只靠启动时 AutoMigrate(只增不删),没有独立迁移文件。表结构变更 = 同步改 schema.sql + 对应 model。改生产库数据前先备份

6.2 种子数据

sh scripts/dev.sh seed S001    # 清空并写入 S001 全量测试数据(docker exec 注入 MySQL 容器)
sh scripts/dev.sh seed S002    # 空门店(基础数据相同,无单据/库存)

每店一个 backend/seeds/<shop_code>.sql,文件顶部含 TRUNCATE,每次完整重建。

6.3 每日备份

.gitea/workflows/backup.yml:每日北京时间 02:00(cron UTC 18:00)在 mac runner(开发者本机)跑 scripts/ci/backup-db.sh——SSH 到阿里主库机 dump MySQL,落到本机 ~/jiu-db-backups,Telegram 通知结果;也可 workflow_dispatch 手动触发(外网 nginx 挡 API 触发,需 Forgejo UI 操作)。

6.4 一次性工具(backend/cmd/)

工具用途
cmd/seed初始化/重置测试数据(--reset 删表重建、--clear 清数据)
cmd/gencode平台方批量生成授权兑换券写入码池:go run ./cmd/gencode -type annual -days 365 -count 100 -batch 2026-summer
cmd/import-history旧系统历史进销存一次性迁入(历史只读、4 列快照直存、不回放库存)
cmd/fix-inventory-products修复历史库存导入按「名称|系列|规格」错误合并 product 的数据(按快照编号重指)
cmd/backfill-pinyin为存量 products 补 name_pinyin/name_initials(现启动已自动兜底)

6.5 生产环境配置

生产 env 在 /opt/jiu/config/production.env(systemd EnvironmentFile 加载):SERVER_PORT=8081DATABASE_DSNJWT_SECRET(生产必须非默认值否则拒绝启动)、CORS origin(生产禁 *)。密钥不入 git,需要时从 Bitwarden 取。

7. 开发环境与工作流

7.1 本地开发

# 首次:起 MySQL 容器(127.0.0.1:3306 + Adminer :8888)
docker compose -f deploy/docker-compose.yml up -d

# 一键起前后端(后端代码未变则跳过重启;--force 强制)
sh scripts/dev.sh run
sh scripts/dev.sh stop

# 测试数据 + 账号(门店编号 S001,密码均 password123)
sh scripts/dev.sh seed S001    # admin=管理员 operator=操作员 test=只读

Go 命令前须 export PATH="/opt/homebrew/bin:$PATH"(dev.sh 已内置)。发版前自测「指向线上后端」的 macOS 包:sh scripts/local_test.sh(BASE_URL 固定生产域名,版本取最新 client tag patch+1)。

7.2 完成标准(DoD 门禁)

# 后端
cd backend && go build ./... && go vet ./... && go test ./...

# 前端
cd client && flutter analyze --no-fatal-infos --no-fatal-warnings && flutter test

# 设计系统闸(改了 UI 时)
cd client && node tool/check_ds_code.mjs --changed    # 禁硬编码色
node tools/fidelity.mjs                               # golden vs 原型像素闸(仓库根)
任一项失败禁止提交、禁止打 tag 发版。通用检查:多租户隔离未破坏(查询都带 shop_id)、schema.sql 与 model 同步、新接口有测试覆盖。

7.3 Git 提交规范

{类型}({模块}): {简短说明}     # 类型: feat|fix|test|docs|chore|security|refactor
                              # 模块: backend|client|db|deploy|docs
feat(backend): 新增财务报表接口
docs(api): 财务报表 API 接口文档

7.4 三条发版流水线

client / site / server 三条互不影响的流水线,各有 tag 前缀、独立版本序列、独立 CHANGELOG,由 Forgejo CI 按 tag 前缀路由(.gitea/workflows/deploy-*.yml + scripts/ci/)。用 /release <part> [version]:本地 build → test → 更新 CHANGELOG → commit → tag → push;省略版本则自增最新 tag 的 patch。回滚用 manual.yml 输入带前缀 tag,从 Forgejo Release 下载产物重部署。

client-v* CHANGELOG-client site-v* CHANGELOG-site server-v* CHANGELOG-server deploy-client.yml — 五端构建矩阵 mac runner(本机,容量 1,串行链): build-client-web build-macos build-android build-ios windows runner(并行): build-windows (Inno) release-deploy-client 收齐产物 → Release → 部署 Android 需 ANDROID_* / iOS TestFlight 需 IOS_*·APPSTORE_* secrets,未配置优雅跳过 deploy-site.yml — Eleventy 构建 → rsync /opt/jiu/marketing(nginx 根路径) deploy-server.yml — go build+test → Release → 部署 jiu-server + nginx/systemd 阿里云主机 /opt/jiu/* Forgejo Release Telegram 通知
part范围tag 前缀要点
clientclient/ 全平台 + version.yamlclient-v*version.yaml 归 client;后端 /version 与 /public/release 每请求实时读——client 发版立即生效,不重启后端、不重建官网
siteweb/ 营销站(不含 Web 版 app)site-v*官网下载页运行时 fetch /public/release 动态刷新版本徽章/链接/更新日志
serverbackend/ + nginx/systemd 基建server-v*nginx conf、jiu.service 归 server 流水线
/release client 1.1.1     # 指定版本
/release server           # 省略则自增最新 server-v* tag 的 patch

CI 脚本在 scripts/ci/lib-forgejo.sh(公共函数)+ compile-{client-web,site,backend,macos,android,ios,windows}.sh + release-*.sh + deploy-*.sh。runner:mac = 开发者本机(launchd 看门狗)、windows = 另一台机(nssm)、Forgejo 在 NAS(git.51yanmei.com)。


相关文档:数据库 Schema(列级) · 项目上下文 project.md · 文档索引