Files
jiu/docs/context/project.md
T
wangjia 480ce836bb
Deploy / build-linux-web (push) Successful in 53s
Deploy / build-windows (push) Successful in 1m48s
Deploy / build-macos (push) Successful in 1m17s
Deploy / build-android (push) Successful in 4m13s
Deploy / build-ios (push) Successful in 9s
Deploy / release-deploy (push) Successful in 1m37s
chore: release v1.0.18
移动端响应式适配(抽屉导航/列表卡片/弹窗自适应)、Android 正式签名与 APK 发布、
iOS(TestFlight) 工程与 CI、多平台构建流水线、相关文档同步。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 07:55:33 +08:00

19 KiB
Raw Permalink Blame History

酒库管理系统 — 项目上下文

所有 Agent 在开始工作前必须读取此文件,了解项目全貌。

项目简介

面向酒水门店的仓库管理系统。核心特点:

  • 多租户:每个账号对应一个门店,数据通过 shop_id 完全隔离
  • 付费授权:许可证绑定设备 ID,支持试用/年付/买断
  • 跨端客户端:Flutter 单一代码库,支持 Windows / Web / macOS / iOS / Android

技术栈

后端:
  语言: Go 1.26
  框架: GinHTTP+ GORMORM
  数据库: MySQL 8.0Docker 容器 jiu_mysql
  认证: JWTHS256),Access Token 60分钟,Refresh Token 7天
  模块路径: github.com/wangjia/jiu/backend

前端:
  框架: Flutter 3.x
  状态管理: Riverpod
  HTTP客户端: Dio(含 401 自动刷新 token 拦截器)
  路由: go_router(含登录重定向)
  目录: client/

数据库管理:
  Schema: backend/schema/schema.sql(完整建表 SQL,容器启动时自动执行)
  ORM 迁移: GORM AutoMigrate(后端启动时自动同步)
  种子数据: backend/seeds/<shop>.sql(通过 dev.sh seed 命令执行)

项目目录结构

jiu/
├── backend/
│   ├── main.go                      # 入口,启动时执行 AutoMigrate
│   ├── config/
│   │   ├── config.go                # 配置结构体(含 mapstructure 标签)
│   │   ├── config.yaml              # 本地开发配置(不提交 git)
│   │   └── version.yaml             # 版本清单(version + download_urls,发版时由 release.sh 更新)
│   ├── internal/
│   │   ├── handler/                 # HTTP 处理器(每模块一文件)
│   │   │   └── *_test.go            # Handler 集成测试(SQLite in-memory
│   │   ├── service/                 # 业务逻辑层
│   │   │   └── *_test.go            # Service 单元测试
│   │   ├── model/                   # GORM 数据模型
│   │   │   ├── base.go              # Base/TenantBase/Date/JSON 公共类型
│   │   │   └── *.go                 # 各业务模型
│   │   ├── middleware/auth.go       # JWT 验证 + shop_id 注入(GetShopID/GetUserID
│   │   │                            #   ReadOnly():只读用户禁止写操作(全局挂载)
│   │   │                            #   AdminOnly():仅管理员可访问(挂载在 /users 路由组)
│   │   └── router/router.go         # 路由注册
│   ├── testutil/setup.go            # 测试工具包(SQLite DB、CreateTestXxx、GetAuthToken
│   ├── cmd/seed/main.go             # 数据库工具(--reset 删表重建,--clear 清空数据)
│   ├── seeds/
│   │   └── S001.sql                 # 门店 S001 测试种子数据(SQL 命令形式)
│   └── schema/schema.sql            # 完整建表 SQLMySQL
├── client/                          # Flutter 跨端客户端
│   └── android/ ios/ macos/ web/ windows/   # 五个平台目录(均已初始化)
├── deploy/
│   └── docker-compose.yml           # 本地开发:MySQL(3306) + Adminer(8888)
├── .gitea/workflows/                # Forgejo Actionsdeploy.yml=tag 发版,及备份/重置等)
├── scripts/
│   ├── dev.sh                       # 一站式开发脚本(见下方用法)
│   ├── ci/                          # CI 编译/发布脚本(compile-*.sh / release.sh / deploy.sh / provision-*.sh
│   └── .logs/                       # 脚本运行日志(已加入 .gitignore,不提交)
└── docs/
    ├── context/project.md           # 本文件(项目全貌)
    ├── dev-setup.md                 # 环境搭建指南
    ├── requirements/                # 需求文档
    ├── architecture/                # 架构设计
    ├── api/                         # API 接口规范
    ├── review/                      # 代码审查报告 + bug 报告
    ├── security/                    # 安全审计报告
    └── runbooks/                    # 运维操作手册

核心数据模型

shops:               # 门店(租户根节点)
users:               # 用户(含 shop_idrole: admin/operator/readonly
licenses:            # 许可证(含 device_id 绑定,type: trial/monthly/annual/lifetime
product_categories:  # 商品分类
products:            # 商品(含 custom_fields JSON 动态扩展)
                     #   UNIQUE KEY uk_product_code (shop_id, code) — 防并发重复
warehouses:          # 仓库
partners:            # 往来单位(type: supplier/customer
stock_in_orders:     # 入库单(status: draft→pending→approved/rejected
stock_in_items:      # 入库单明细
stock_out_orders:    # 出库单
stock_out_items:     # 出库单明细
inventories:         # 实时库存(唯一键: shop_id+warehouse_id+product_id
inventory_logs:      # 库存流水(每次变动自动记录)
inventory_checks:    # 盘点单
inventory_check_items: # 盘点明细
finance_records:     # 财务流水(receivable/payable/receipt/payment
number_rules:        # 单号生成规则(前缀+日期+6位序号)

关键业务规则

  1. 多租户隔离shop_id 只从 JWT 提取(middleware.GetShopID(c)),绝不从请求参数读取
  2. 库存变更事务:入库/出库审核时,同一事务内更新 inventories + 写 inventory_logs
  3. 出库前校验:审核出库时校验库存充足,不足返回错误并回滚
  4. 单号生成:通过 number_rules 表事务安全生成,格式 {前缀}{YYYYMMDD}{6位序号}
  5. 扩展字段:所有业务主表含 custom_fields JSON,用于存储动态业务字段,避免频繁改表
  6. 并发安全
    • GenerateOrderNo 在事务内用 FOR UPDATE 锁住 number_rules 行,防止并发生成重复单号
    • ApproveStockOut/updateInventory 在事务内用 FOR UPDATE 锁住库存行,防止超卖竞态
    • 商品编码 products.code 有 UNIQUE 约束,Create 时重试最多 5 次

开发脚本(scripts/dev.sh

所有常用操作都通过 sh scripts/dev.sh <命令> 执行:

# 启动
sh scripts/dev.sh run                # 启动前后端(后端代码未变则跳过重启)
sh scripts/dev.sh run --force        # 强制重启后端
sh scripts/dev.sh --backend-only     # 仅启动后端
sh scripts/dev.sh --frontend-only    # 仅启动前端
sh scripts/dev.sh stop               # 停止所有服务

# 数据库
sh scripts/dev.sh seed S001          # 清空并写入 S001 测试数据(执行 seeds/S001.sql
sh scripts/dev.sh reset              # 删表重建(AutoMigrate
sh scripts/dev.sh clear              # 清空所有业务数据(保留表结构)

本地开发快速启动

# 1. 启动 MySQL 容器(首次或容器未运行时)
docker compose -f deploy/docker-compose.yml up -d

# 2. 一键启动前后端
sh scripts/dev.sh run

# 3. 写入测试数据(另开终端)
sh scripts/dev.sh seed S001
# 写入后可用以下账号登录(门店编号 S001):
#   admin    / password123   管理员
#   operator / password123   操作员
#   test     / password123   只读

# 4. 数据库管理界面(可选)
# 浏览器打开 http://localhost:8888
# 服务器: mysql,用户: root,密码: password,数据库: jiu_db

已实现的 API 接口

认证(无需 JWT:
  POST /api/v1/auth/login
  POST /api/v1/auth/refresh

许可证:
  POST /api/v1/license/activate
  GET  /api/v1/license/verify
  POST /api/v1/license/deactivate
  GET  /api/v1/license/info           # 返回当前许可证详情(含到期日、类型)

版本(无需 JWT:
  GET  /version                       # 返回最新版本信息(读取 version.yaml,缺失返回 500
  GET  /api/v1/public/release         # 版本 + download_urls(驱动客户端更新提示与下载页)

商品:
  GET/POST   /api/v1/products
  PUT/DELETE /api/v1/products/:id

仓库:
  GET/POST   /api/v1/warehouses
  PUT/DELETE /api/v1/warehouses/:id

往来单位:
  GET/POST   /api/v1/partners
  PUT/DELETE /api/v1/partners/:id

入库:
  GET/POST /api/v1/stock-in/orders
  GET      /api/v1/stock-in/orders/:id
  PUT      /api/v1/stock-in/orders/:id/submit
  PUT      /api/v1/stock-in/orders/:id/approve
  PUT      /api/v1/stock-in/orders/:id/reject

出库:
  GET/POST /api/v1/stock-out/orders
  GET      /api/v1/stock-out/orders/:id
  PUT      /api/v1/stock-out/orders/:id/submit
  PUT      /api/v1/stock-out/orders/:id/approve
  PUT      /api/v1/stock-out/orders/:id/reject

库存:
  GET      /api/v1/inventory
  GET      /api/v1/inventory/logs
  GET      /api/v1/inventory/batch-tracking  # 批次追踪(已审核入库商品含销售状态)
  POST/GET /api/v1/inventory/checks

用户管理(仅 admin 角色):
  GET/POST   /api/v1/users
  PUT/DELETE /api/v1/users/:id

数据导入:
  POST /api/v1/import/products   (Excel/CSV)
  POST /api/v1/import/partners   (Excel/CSV)

意见反馈:
  POST /api/v1/feedback                  # 提交反馈(bug/suggestion,文字+图片,认证)
  POST /api/v1/feedback/images           # 上传反馈附图(认证)
  GET   /api/v1/admin/feedback           # 反馈列表(仅 superadmin
  PATCH /api/v1/admin/feedback/:id       # 标记处理状态(仅 superadmin

Flutter 客户端结构

client/lib/
├── main.dart                        # 入口:ProviderScope + _AppBootstrapauth restore
├── core/
│   ├── theme/app_theme.dart         # 颜色常量 + ThemeData
│   ├── auth/auth_state.dart         # AuthUser, AuthState, AuthNotifiershared_preferences 持久化)
│   ├── api/api_client.dart          # Dio 封装(401 自动刷新,_disposed 防悬空回调)
│   ├── config/app_config.dart       # 全局 URL 配置(通过 --dart-define=BASE_URL=... 注入)
│   ├── router/app_router.dart       # go_router_RouterNotifier + refreshListenable
│   ├── storage/login_history.dart   # 登录历史(shared_preferences,支持候选词自动填充)
│   ├── responsive/responsive.dart   # 响应式:context.isMobile<600)、context.dialogWidth(X)
│   ├── update/app_updater*.dart     # 应用内更新(_io: Win/macOS 装;_web: 刷新;移动端/兜底开下载链接)
│   ├── errors/error_reporter.dart   # 异常上报(reportError
│   ├── models/page_result.dart      # 通用分页结果模型
│   └── exceptions.dart              # 自定义异常类型
├── models/                          # 数据模型(与后端 JSON 对应)
├── repositories/                    # 数据访问层(封装 API 调用)
│   └── auth_repository.dart         # 登录/刷新 token(含历史记录保存)
├── providers/                       # Riverpod 状态管理
│   ├── license_provider.dart        # 许可证状态(licenseInfoProvider
│   ├── connectivity_provider.dart   # 网络连通性探测(connectivityProvider
│   ├── update_provider.dart         # 版本更新检查(updateCheckProvider
│   │                                #   平台判断用 kIsWeb,避免 Web 上调用 dart:io
│   ├── product_provider.dart        # 商品列表(AsyncNotifierProvider,含缓存)
│   ├── stock_in_provider.dart       # 入库单列表
│   ├── stock_out_provider.dart      # 出库单列表
│   ├── inventory_provider.dart      # 库存列表
│   ├── finance_provider.dart        # 财务记录
│   ├── partner_provider.dart        # 往来单位
│   ├── warehouse_provider.dart      # 仓库列表
│   ├── user_provider.dart           # 用户管理
│   └── number_rule_provider.dart    # 编号规则
├── screens/
│   ├── auth/login_screen.dart       # 登录页(含候选词下拉,150ms 延迟防止覆盖 onTap
│   ├── shell/app_shell.dart         # 主框架(顶栏 + 侧边栏/窄屏 Drawer 抽屉 + 状态栏 + 更新 banner
│   ├── stock_in/                    # 入库管理
│   ├── stock_out/                   # 出库管理
│   ├── inventory/                   # 库存管理(含批次追踪 batch_tracking_screen.dart
│   ├── partners/                    # 往来单位
│   ├── finance/                     # 财务管理
│   ├── products/                    # 商品管理(含分类)
│   ├── about/                       # 关于我们(含意见反馈:文字+附图直接提交后台)
│   ├── public/                      # 商品扫码公开展示页(无需登录)
│   └── settings/                    # 系统设置(用户/仓库/编号规则)
└── widgets/
    ├── page_scaffold.dart           # Tab 页面封装
    ├── data_table_card.dart         # 含工具栏+分页的数据表格
    │                                #   StatefulWidget,用 Table + IntrinsicColumnWidth 实现
    │                                #   ValueNotifier<int> _hoveredRow 驱动行 hover 高亮
    │                                #   mobileCards 入参:窄屏渲染卡片流,宽屏仍表格
    ├── mobile_list_card.dart        # 移动端列表卡片:MobileListCard / MobileCardField
    │                                #   标题 + 右上角徽章 + 字段竖排 + 底部操作,替代窄屏表格行
    ├── multi_select_dropdown.dart   # 多选筛选组件(多个导出):
    │                                #   MultiSelectDropdown — 独立多选按钮(toolbar 使用)
    │                                #   FilterableColumnHeader — 列头内嵌筛选图标
    │                                #     hover 显示,active 常显;图标 16px 靠右对齐
    │                                #   ColDef — 列定义(key, label, required, minWidth
    │                                #   ColumnToggleButton — 列显示/隐藏切换按钮
    ├── status_badge.dart            # 状态标签(草稿/待审/已审/拒绝)
    └── form_dialog.dart             # 弹窗表单封装

前端配置管理

后端 URL 通过 AppConfig 统一管理,支持构建时注入:

// client/lib/core/config/app_config.dart
class AppConfig {
  static const _baseUrl = String.fromEnvironment(
    'BASE_URL',
    defaultValue: 'http://localhost:8080',
  );
  static String get baseUrl    => _baseUrl;
  static String get apiBaseUrl => '$_baseUrl/api/v1';
  static String get healthUrl  => '$_baseUrl/health';
  static String get versionUrl => '$_baseUrl/version';
}

构建/运行时注入

flutter run --dart-define=BASE_URL=http://192.168.1.100:8080
flutter build web --dart-define=BASE_URL=https://api.example.com

规则:所有硬编码 localhost:8080 的 URL 必须改用 AppConfig 对应属性,不得直接拼接字符串。

前端表格 UI 规范

所有数据表格使用 DataTableCard widget,遵循以下规范:

行 Hover 效果

data_table_card.dartTable + MouseRegion + ValueNotifier<int> 实现行级 hover

  • hover 时行背景色:Color(0xFFF5F7FF)
  • 列头背景色:Color(0xFFF0F4FF)

列头内嵌筛选(FilterableColumnHeader

可筛选的列头使用 FilterableColumnHeader,作为 DataColumn.label 传入:

DataColumn(
  label: FilterableColumnHeader(
    text: '仓库',
    options: warehouseOptions,
    selected: _filterWarehouse,
    onChanged: (v) => setState(() => _filterWarehouse = v),
  ),
)
  • 鼠标 hover 时显示筛选图标(filter_alt_outlined16px,右对齐)
  • 已激活筛选时图标常驻(filter_alt,蓝色)+ 取消 icon
  • 点击图标弹出多选对话框

客户端筛选模式

部分表格(库存、商品、批次追踪、财务)采用客户端筛选:加载全量数据,在内存中过滤,派生 options:

final warehouseOptions = _records
    .map((r) => r.warehouseName ?? '')
    .where((s) => s.isNotEmpty)
    .toSet().toList()..sort();

列显示/隐藏(ColDef + ColumnToggleButton

使用 ColDef 定义列,支持:

  • required: true — 不可隐藏
  • minWidth: 1000 — 屏幕宽度不足时自动隐藏

移动端 / 响应式 UI 规范

客户端同一套代码跑桌面/Web/平板/手机。窄屏(手机)自动切换为移动布局,宽屏保持桌面布局不变

断点判定

统一用 client/lib/core/responsive/responsive.dart 的扩展,不要散落魔法数:

context.isMobile          // 宽度 < 600kMobileBreakpoint
context.dialogWidth(720)  // 固定宽度弹窗的安全宽度,≤ 屏宽 92%

列表 → 卡片

列表屏用 DataTableCard 时同时传 mobileCards,窄屏渲染卡片流、宽屏仍表格:

DataTableCard(
  columns: [...], rows: [...],          // 宽屏表格
  mobileCards: items.map(_buildCard).toList(),  // 窄屏卡片(MobileListCard
)

卡片复用 MobileListCard(标题/徽章/字段/操作)+ MobileCardFieldwidgets/mobile_list_card.dart)。 顶部并排汇总卡片在窄屏改为横向滚动。

弹窗宽度

固定宽度弹窗一律 width: context.dialogWidth(X)禁止裸写 width: <固定值>(窄屏会溢出)。 表单字段(_FormField)窄屏占满整行。

导航

窄屏用 Drawer 抽屉(app_shell.dart_buildDrawer,汉堡按钮打开),隐藏底部状态栏;宽屏保持侧边栏。 新增页面挂在 shell 下即可,自动适配,无需单独处理。

跨端分发

平台 构建产物 分发方式
Web flutter build web 部署到 jiu.51yanmei.com/app
Windows Inno Setup setup.exe 下载页 /downloads/ + 应用内更新
macOS .app zip 下载页 /downloads/ + 应用内更新
Android 签名 jiu-android.apk 下载页 /downloads/(见 docs/android-signing.md
iOS 签名 IPA TestFlight(见 docs/ios-signing.md

版本清单:backend/config/version.yamldownload_urls(含各平台下载地址);客户端 GET /api/v1/public/release 读取,驱动更新提示与下载页。

文档索引

文档 路径 说明
项目上下文 docs/context/project.md 本文件,所有 Agent 必读
环境搭建 docs/dev-setup.md 工具安装 + 开发脚本详解 + 测试架构
Schema backend/schema/schema.sql 完整数据库建表 SQL
S001 种子 backend/seeds/S001.sql 门店 S001 测试数据(含完整入库/出库/库存历史)
S002 种子 backend/seeds/S002.sql 门店 S002 测试数据(基础数据相同,无库存/单据,模拟新门店)
用户手册 docs/user-manual.md 酒行员工操作手册(登录/入库/出库/库存/财务/设置)
Android 签名 docs/android-signing.md 生成 keystore + 配置 Forgejo secretsCI 给 APK 正式签名
iOS 分发 docs/ios-signing.md 证书/Profile/API Key + secretsCI 构建并上传 TestFlight
部署(NAS/Gitea docs/deployment-nas-gitea.md 自建 Forgejo + runner 部署说明
待办 docs/TODO.md 后续迭代事项(iOS 上架、应用内 APK 安装、平板布局等)