diff --git a/README.md.orig b/README.md.orig deleted file mode 100644 index e775cc8..0000000 --- a/README.md.orig +++ /dev/null @@ -1,949 +0,0 @@ -# 君鸿卡管系统 - Fiber 中间件集成 - -基于 Go + Fiber 框架的 HTTP 服务,集成了认证、限流、结构化日志和嵌入式配置功能。 - -## 系统简介 - -物联网卡 + 号卡全生命周期管理平台,支持代理商体系和分佣结算。 - -**技术栈**:Fiber + GORM + Viper + Zap + Lumberjack.v2 + Validator + sonic JSON + Asynq + PostgreSQL - ---- - -## 核心业务说明 - -### 业务模式概览 - -君鸿卡管系统是一个物联网卡和号卡的全生命周期管理平台,支持三种客户类型和两种组织实体的多租户管理。 - -### 三种客户类型 - -| 客户类型 | 业务特点 | 典型场景 | 钱包归属 | -|---------|---------|---------|---------| -| **企业客户** | B端大客户,公对公支付 | 企业购买大量卡/设备用于业务运营 | ❌ 无钱包(后台直接分配套餐) | -| **个人客户** | C端用户,微信登录 | 个人购买单卡或设备(含1-4张卡) | ✅ 钱包归属**卡/设备**(支持转手) | -| **代理商** | 渠道分销商,层级管理 | 预存款采购套餐,按成本价+加价销售 | ✅ 钱包归属**店铺**(多账号共享) | - -### 个人客户业务流程 - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ 个人客户使用流程 │ -└──────────────────────────┬──────────────────────────────────────┘ - │ - ┌──────────▼──────────┐ - │ 1. 获得卡/设备 │ - │ - 单卡:ICCID │ - │ - 设备:设备号/IMEI │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 2. 微信扫码登录 │ - │ - 输入ICCID/IMEI │ - │ - 首次需绑定手机号 │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 3. 查看卡/设备信息 │ - │ - 流量使用情况 │ - │ - 套餐有效期 │ - │ - 钱包余额 │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 4. 钱包充值 │ - │ - 微信支付 │ - │ - 支付宝支付 │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 5. 购买套餐 │ - │ - 单卡套餐 │ - │ - 设备套餐(共享) │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 6. 卡/设备转手 │ - │ - 新用户扫码登录 │ - │ - 钱包余额跟着走 │ - └─────────────────────┘ -``` - -### 钱包归属设计 - -#### 为什么钱包绑定资源(卡/设备)而非用户? - -**问题场景**: -``` -个人客户 A 购买单卡 → 充值 100 元 → 使用 50 元 → 转手给个人客户 B -``` - -**如果钱包绑定用户**: -- ❌ 个人客户 B 登录后看不到余额(钱包还在 A 账号下) -- ❌ 需要手动转账或退款,体验极差 - -**钱包绑定资源(当前设计)**: -- ✅ 个人客户 B 登录后看到剩余 50 元(钱包跟着卡走) -- ✅ 无需任何额外操作,自然流转 - -#### 钱包归属规则 - -```go -// 钱包模型 -type Wallet struct { - ResourceType string // iot_card | device | shop - ResourceID uint // 资源ID - Balance int64 // 余额(分) - // ... -} - -// 场景1:个人客户的单卡钱包 -resource_type = "iot_card" -resource_id = 101 // 卡ID - -// 场景2:个人客户的设备钱包(3张卡共享) -resource_type = "device" -resource_id = 1001 // 设备ID - -// 场景3:代理商店铺钱包(多账号共享) -resource_type = "shop" -resource_id = 10 // 店铺ID -``` - -### 设备套餐业务规则 - -#### 设备级套餐购买 - -``` -设备绑定 3 张 IoT 卡 -├── 卡1:ICCID-001 -├── 卡2:ICCID-002 -└── 卡3:ICCID-003 - -用户购买套餐:399 元/年,每月 3000G 流量 -├── 套餐分配:3 张卡都获得该套餐 -├── 流量共享:3000G/月 在 3 张卡之间共享(总共 3000G) -├── 用户支付:399 元(一次性) -└── 代理分佣:100 元(只分一次,不按卡数倍增) -``` - -**关键点**: -- ✅ 套餐自动分配到设备的所有卡 -- ✅ 流量是**设备级别共享**(非每卡独立) -- ✅ 分佣**只计算一次**(防止重复分佣) - -### 标签系统多租户隔离 - -#### 三级隔离模型 - -| 标签类型 | 创建者 | 可见范围 | 名称唯一性 | 示例 | -|---------|-------|---------|-----------|------| -| 平台全局标签 | 平台管理员 | 所有用户 | 全局唯一 | "VIP"、"重要客户" | -| 企业标签 | 企业用户 | 仅该企业 | 企业内唯一 | 企业A的"测试标签" | -| 店铺标签 | 代理商 | 该店铺及下级 | 店铺内唯一 | 店铺10的"华东区" | - -#### 隔离规则 - -``` -企业 A 创建标签 "测试标签" -├── enterprise_id = 5, shop_id = NULL -├── 企业 A 的用户可见 -└── 企业 B 的用户不可见 - -企业 B 创建标签 "测试标签"(允许) -├── enterprise_id = 8, shop_id = NULL -├── 企业 B 的用户可见 -└── 与企业 A 的 "测试标签" 相互隔离 - -平台创建标签 "VIP" -├── enterprise_id = NULL, shop_id = NULL -└── 所有用户可见 -``` - -#### 数据权限自动过滤 - -```go -// GORM Callback 自动注入过滤条件 -switch userType { -case UserTypeAgent: - // 代理用户:只看到自己店铺及下级店铺的标签 - db.Where("shop_id IN (?) OR (enterprise_id IS NULL AND shop_id IS NULL)", subordinateShopIDs) - -case UserTypeEnterprise: - // 企业用户:只看到自己企业的标签 - db.Where("enterprise_id = ? OR (enterprise_id IS NULL AND shop_id IS NULL)", enterpriseID) - -default: - // 个人客户:只看到全局标签 - db.Where("enterprise_id IS NULL AND shop_id IS NULL") -} -``` - ---- - -## 核心功能 - -- **认证中间件**:基于 Redis 的 Token 认证 -- **限流中间件**:基于 IP 的限流,支持可配置的限制和存储后端 -- **结构化日志**:使用 Zap 的 JSON 日志和自动日志轮转 -- **嵌入式配置**:配置嵌入二进制文件,通过环境变量覆盖,简化 Docker 部署 -- **请求 ID 追踪**:UUID 跨日志的请求追踪 -- **Panic 恢复**:优雅的 panic 处理和堆栈跟踪日志 -- **统一错误处理**:全局 ErrorHandler 统一处理所有 API 错误,返回一致的 JSON 格式(包含错误码、消息、时间戳);Panic 自动恢复防止服务崩溃;错误分类处理(客户端 4xx、服务端 5xx)和日志级别控制;敏感信息自动脱敏保护 -- **数据持久化**:GORM + PostgreSQL 集成,提供完整的 CRUD 操作、事务支持和数据库迁移能力 -- **异步任务处理**:Asynq 任务队列集成,支持任务提交、后台执行、自动重试和幂等性保障,实现邮件发送、数据同步等异步任务 -- **RBAC 权限系统**:完整的基于角色的访问控制,支持账号、角色、权限的多对多关联和层级关系;基于店铺层级的自动数据权限过滤,实现多租户数据隔离;使用 PostgreSQL WITH RECURSIVE 查询下级店铺并通过 Redis 缓存优化性能;完整的权限检查功能支持路由级别的细粒度权限控制,支持平台过滤(web/h5/all)和超级管理员自动跳过(详见 [功能总结](docs/004-rbac-data-permission/功能总结.md)、[使用指南](docs/004-rbac-data-permission/使用指南.md) 和 [权限检查使用指南](docs/permission-check-usage.md)) -- **商户管理**:完整的商户(Shop)和商户账号管理功能,支持商户创建时自动创建初始坐席账号、删除商户时批量禁用关联账号、账号密码重置等功能(详见 [使用指南](docs/shop-management/使用指南.md) 和 [API 文档](docs/shop-management/API文档.md)) -- **B 端认证系统**:完整的后台和 H5 认证功能,支持基于 Redis 的 Token 管理和双令牌机制(Access Token 24h + Refresh Token 7天);包含登录、登出、Token 刷新、用户信息查询和密码修改功能;通过用户类型隔离确保后台(SuperAdmin、Platform、Agent)和 H5(Agent、Enterprise)的访问控制;**登录响应包含菜单树和按钮权限**(menus/buttons),前端无需二次处理直接渲染侧边栏和控制按钮显示;详见 [API 文档](docs/api/auth.md)、[使用指南](docs/auth-usage-guide.md)、[架构说明](docs/auth-architecture.md) 和 [菜单权限使用指南](docs/login-menu-button-response/使用指南.md) -- **生命周期管理**:物联网卡/号卡的开卡、激活、停机、复机、销户 -- **代理商体系**:层级管理和分佣结算 -- **批量同步**:卡状态、实名状态、流量使用情况 -- **分佣验证指引**:对代理分佣的冻结、解冻、提现校验流程进行了结构化说明与流程图,详见 [分佣逻辑正确与否验证](docs/优化说明/分佣逻辑正确与否验证.md) -- **对象存储**:S3 兼容的对象存储服务集成(联通云 OSS),支持预签名 URL 上传、文件下载、临时文件处理;用于 ICCID 批量导入、数据导出等场景;详见 [使用指南](docs/object-storage/使用指南.md) 和 [前端接入指南](docs/object-storage/前端接入指南.md) -<<<<<<< .merge_file_MlwfUH -- **Gateway 客户端**:第三方 Gateway API 的 Go 封装,提供流量卡和设备管理的统一接口;内置 AES-128-ECB 加密、MD5 签名验证、HTTP 连接池管理;支持流量卡状态查询、停复机、实名认证、流量查询等 7 个流量卡接口和设备信息查询、卡槽管理、限速设置、WiFi 配置、切卡、重启、恢复出厂等 7 个设备管理接口;测试覆盖率 88.8%;详见 [使用指南](docs/gateway-client-usage.md) 和 [API 参考](docs/gateway-api-reference.md) -======= -- **微信集成**:完整的微信公众号 OAuth 认证和微信支付功能(JSAPI + H5),使用 PowerWeChat v3 SDK;支持个人客户微信授权登录、账号绑定、微信内支付和浏览器 H5 支付;支付回调自动验证签名和幂等性处理;详见 [使用指南](docs/wechat-integration/使用指南.md) 和 [API 文档](docs/wechat-integration/API文档.md) ->>>>>>> .merge_file_NfqRhJ - -## 用户体系设计 - -系统支持四种用户类型和两种组织实体,实现分层级的多租户管理: - -### 用户类型 - -1. **平台用户**:平台管理员,具有最高权限,可分配多个角色 -2. **代理账号**:店铺(代理商)员工账号,归属于特定店铺,权限相同 -3. **企业账号**:企业客户账号,归属于特定企业,一企业一账号 -4. **个人客户**:个人用户,独立表存储,支持微信绑定,不参与 RBAC 体系 - -### 组织实体 - -1. **店铺(Shop)**:代理商组织实体,支持最多 7 级层级关系 - - 一级代理直接归属于平台 - - 下级代理归属于上级店铺(通过 `parent_id` 字段) - - 一个店铺可以有多个账号(代理员工) - -2. **企业(Enterprise)**:企业客户组织实体 - - 可归属于店铺(通过 `owner_shop_id` 字段) - - 可归属于平台(`owner_shop_id = NULL`) - - 一个企业目前只有一个账号 - -### 核心设计决策 - -- **层级关系在店铺之间维护**:代理上下级关系通过 `Shop.parent_id` 维护,而非账号之间 -- **数据权限基于店铺归属**:数据过滤使用 `shop_id IN (当前店铺及下级店铺)` -- **递归查询+Redis缓存**:使用 `GetSubordinateShopIDs()` 递归查询下级店铺ID,结果缓存30分钟 -- **GORM 自动过滤**:通过 GORM Callback 自动应用数据权限过滤,无需在每个查询手动添加条件 -- **禁止外键约束**:遵循项目原则,表之间通过ID字段关联,关联查询在代码层显式执行 -- **GORM字段显式命名**:所有模型字段必须显式指定 `gorm:"column:field_name"` 标签 - -### 表结构 - -``` -tb_shop (店铺表) -├── id, created_at, updated_at, deleted_at -├── creator, updater -├── shop_name, shop_code -├── parent_id (上级店铺ID) -├── level (层级 1-7) -├── contact_name, contact_phone -├── province, city, district, address -└── status - -tb_enterprise (企业表) -├── id, created_at, updated_at, deleted_at -├── creator, updater -├── enterprise_name, enterprise_code -├── owner_shop_id (归属店铺ID) -├── legal_person, contact_name, contact_phone -├── business_license -├── province, city, district, address -└── status - -tb_personal_customer (个人客户表) -├── id, created_at, updated_at, deleted_at -├── phone (唯一标识) -├── nickname, avatar_url -├── wx_open_id, wx_union_id -└── status - -tb_account (账号表 - 已修改) -├── id, created_at, updated_at, deleted_at -├── creator, updater -├── username, phone, password -├── user_type (1=超级管理员 2=平台用户 3=代理账号 4=企业账号) -├── shop_id (代理账号必填) -├── enterprise_id (企业账号必填) ← 新增 -└── status -``` - -详细设计文档参见: -- [设计文档](openspec/changes/add-user-organization-model/design.md) -- [提案文档](openspec/changes/add-user-organization-model/proposal.md) - -## 数据权限模型 - -系统采用基于用户类型的自动数据权限过滤策略,通过 GORM Callback 自动应用,无需在每个查询中手动添加过滤条件。 - -### 过滤规则 - -| 用户类型 | 过滤策略 | 示例 | -|---------|---------|------| -| 超级管理员(Super Admin) | 跳过过滤,查看所有数据 | - | -| 平台用户(Platform) | 跳过过滤,查看所有数据 | - | -| 代理账号(Agent) | 基于店铺层级过滤 | `WHERE shop_id IN (当前店铺及下级店铺)` | -| 企业账号(Enterprise) | 基于企业归属过滤 | `WHERE enterprise_id = 当前企业ID` | -| 个人客户(Personal Customer) | 基于创建者过滤 | `WHERE creator = 当前用户ID` | - -### 工作机制 - -1. **认证中间件**设置完整用户上下文(`UserContextInfo`)到 `context` 中 -2. **GORM Callback**在每次查询前自动注入过滤条件 -3. **递归查询 + 缓存**:代理用户的下级店铺 ID 通过 `GetSubordinateShopIDs()` 递归查询,结果缓存 30 分钟 -4. **跳过过滤**:特殊场景(如统计、后台任务)可使用 `SkipDataPermission(ctx)` 绕过过滤 - -### 使用示例 - -```go -// 1. 认证后 context 已自动包含用户信息 -ctx := c.UserContext() - -// 2. 所有 Store 层查询自动应用数据权限过滤 -orders, err := orderStore.List(ctx) // 自动过滤为当前用户可见的订单 - -// 3. 需要查询所有数据时,显式跳过过滤 -ctx = gorm.SkipDataPermission(ctx) -allOrders, err := orderStore.List(ctx) // 查询所有订单(仅限特殊场景) -``` - -详细说明参见: -- [数据权限清理总结](docs/remove-legacy-rbac-cleanup/清理总结.md) -- [RBAC 权限使用指南](docs/004-rbac-data-permission/使用指南.md) - -## 快速开始 - -```bash -# 安装依赖 -go mod tidy - -# 启动 Redis(认证功能必需) -redis-server - -# 运行 API 服务 -go run cmd/api/main.go - -# 运行 Worker 服务(可选) -go run cmd/worker/main.go -``` - -### 默认超级管理员账号 - -系统首次启动时会自动创建默认超级管理员账号,无需手动执行 SQL 或脚本。 - -**默认账号信息**: -- 用户名:`admin` -- 密码:`Admin@123456` -- 手机号:`13800000000` - -**自定义配置**: - -通过环境变量自定义默认管理员信息: - -```bash -export JUNHONG_DEFAULT_ADMIN_USERNAME="自定义用户名" -export JUNHONG_DEFAULT_ADMIN_PASSWORD="自定义密码" -export JUNHONG_DEFAULT_ADMIN_PHONE="自定义手机号" -``` - -**注意事项**: -- 系统只在数据库无超级管理员账号时才创建 -- 如果已存在超级管理员,启动时会跳过创建 -- 建议首次登录后立即修改默认密码 -- 初始化日志记录在 `logs/app.log` 中 - -详细设置和测试说明请参阅 [快速开始指南](specs/001-fiber-middleware-integration/quickstart.md)。 - -## 项目结构 - -``` -junhong_cmp_fiber/ -│ -├── cmd/ # 应用程序入口 -│ ├── api/ # HTTP API 服务 -│ │ └── main.go # API 服务主入口 -│ └── worker/ # Asynq 异步任务 Worker -│ └── main.go # Worker 服务主入口 -│ -├── internal/ # 私有业务代码 -│ ├── handler/ # HTTP 处理层 -│ │ ├── user.go # 用户处理器 -│ │ └── health.go # 健康检查处理器 -│ ├── middleware/ # Fiber 中间件实现 -│ │ ├── auth.go # 认证中间件(keyauth) -│ │ ├── ratelimit.go # 限流中间件 -│ │ └── recover.go # Panic 恢复中间件 -│ ├── service/ # 业务逻辑层(核心业务) -│ ├── store/ # 数据访问层 -│ │ └── postgres/ # PostgreSQL 实现 -│ ├── model/ # 数据模型(实体、DTO) -│ ├── task/ # Asynq 任务定义和处理 -│ ├── gateway/ # Gateway 服务 HTTP 客户端 -│ └── router/ # 路由注册 -│ -├── pkg/ # 公共工具库 -│ ├── config/ # 配置管理 -│ │ ├── config.go # 配置结构定义 -│ │ ├── loader.go # 配置加载(嵌入配置 + 环境变量覆盖) -│ │ ├── embedded.go # go:embed 嵌入配置加载 -│ │ └── defaults/config.yaml # 默认配置(嵌入二进制) -│ ├── logger/ # 日志基础设施 -│ │ ├── logger.go # Zap 日志初始化 -│ │ └── middleware.go # Fiber 日志中间件适配器 -│ ├── response/ # 统一响应处理 -│ │ └── response.go # 响应结构和辅助函数 -│ ├── errors/ # 错误码和类型 -│ │ ├── codes.go # 错误码常量 -│ │ └── errors.go # 自定义错误类型 -│ ├── constants/ # 业务常量 -│ │ ├── constants.go # 上下文键、请求头名称 -│ │ └── redis.go # Redis Key 生成器 -│ ├── validator/ # 验证服务 -│ │ └── token.go # Token 验证(Redis) -│ ├── database/ # 数据库初始化 -│ │ └── redis.go # Redis 客户端初始化 -│ └── queue/ # 队列封装(Asynq) -│ -├── tests/ -│ └── integration/ # 集成测试 -│ ├── auth_test.go # 认证测试 -│ └── ratelimit_test.go # 限流测试 -│ -├── migrations/ # 数据库迁移文件 -├── scripts/ # 脚本工具 -├── docs/ # 文档 -│ └── rate-limiting.md # 限流指南 -└── logs/ # 应用日志(自动创建) - ├── app.log # 应用日志(JSON) - └── access.log # 访问日志(JSON) -``` - -## 中间件执行顺序 - -中间件按注册顺序执行。请求按顺序流经每个中间件: - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ HTTP 请求 │ -└────────────────────────────────┬────────────────────────────────┘ - │ - ┌────────────▼────────────┐ - │ 1. Recover 中间件 │ - │ (panic 恢复) │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 2. RequestID 中间件 │ - │ (生成 UUID) │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 3. Logger 中间件 │ - │ (访问日志) │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 4. 认证中间件 │ - │ (按路由组配置) │ ─── 模块化路由注册 - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 5. RateLimiter 中间件 │ - │ (限流) │ ─── 可选 (config: enable_rate_limiter) - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 6. 路由处理器 │ - │ (业务逻辑) │ - └────────────┬────────────┘ - │ -┌────────────────────────────────▼────────────────────────────────┐ -│ HTTP 响应 │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### 中间件详情 - -#### 1. Recover 中间件(fiber.Recover) -- **用途**:捕获 panic 并防止服务器崩溃 -- **行为**: - - 捕获下游中间件/处理器中的任何 panic - - 将 panic 及堆栈跟踪记录到 `logs/app.log` - - 返回 HTTP 500 统一错误响应 - - 服务器继续处理后续请求 -- **始终激活**:是 - -#### 2. RequestID 中间件(自定义) -- **用途**:生成请求追踪的唯一标识符 -- **行为**: - - 为每个请求生成 UUID v4 - - 存储在上下文中:`c.Locals(constants.ContextKeyRequestID)` - - 添加 `X-Request-ID` 响应头 - - 用于所有日志条目以进行关联 -- **始终激活**:是 - -#### 3. Logger 中间件(自定义 Fiber 适配器) -- **用途**:记录所有 HTTP 请求和响应 -- **行为**: - - 记录请求:方法、路径、IP、User-Agent、请求 ID - - 记录响应:状态码、耗时、用户 ID(如果已认证) - - 写入 `logs/access.log`(JSON 格式) - - 结构化字段便于解析和分析 -- **始终激活**:是 -- **日志格式**:包含字段的 JSON:timestamp、level、method、path、status、duration_ms、request_id、ip、user_agent、user_id - -#### 4. 认证中间件(pkg/middleware/auth.go 和 internal/middleware/) -- **用途**:使用 Token 验证对请求进行认证 -- **行为**: - - 从 `Authorization: Bearer {token}` 请求头提取 token - - 通过 TokenValidator 函数验证 token(支持 JWT 和 Redis Token) - - 如果缺失/无效 token 返回 401 - - 成功时将用户信息存储在上下文中(UserID、UserType、ShopID、EnterpriseID) -- **实现方式**:模块化路由注册(无全局配置) - - `/api/admin/*`:后台认证(SuperAdmin、Platform、Agent) - - `/api/h5/*`:H5 认证(Agent、Enterprise) - - `/api/personal/*`:个人客户认证(JWT) -- **跳过路由**:各路由组可自行配置跳过路径(如 `/api/admin/login`) -- **错误码**: - - 1001:缺失 token - - 1002:无效或过期 token - - 1003:权限不足 - -#### 5. RateLimiter 中间件(internal/middleware/ratelimit.go) -- **用途**:通过限制请求速率保护 API 免受滥用 -- **行为**: - - 按客户端 IP 地址追踪请求 - - 执行限制:`expiration` 时间窗口内 `max` 个请求 - - 如果超过限制返回 429 - - 每个 IP 地址独立计数器 -- **配置**:`middleware.enable_rate_limiter`(默认:false) -- **存储选项**: - - `memory`:内存存储(单服务器,重启后重置) - - `redis`:基于 Redis(分布式,持久化) -- **错误码**:1003(请求过于频繁) - -#### 6. 路由处理器 -- **用途**:执行端点的业务逻辑 -- **可用上下文数据**: - - 请求 ID:`c.Locals(constants.ContextKeyRequestID)` - - 用户 ID:`c.Locals(constants.ContextKeyUserID)`(如果已认证) - - 标准 Fiber 上下文方法:`c.Params()`、`c.Query()`、`c.Body()` 等 - -### 中间件注册(cmd/api/main.go) - -```go -// 核心中间件(始终激活) -app.Use(recover.New()) -app.Use(addRequestID()) -app.Use(loggerMiddleware()) - -// 模块化路由注册(认证中间件按路由组配置) -routes.RegisterRoutes(app, handlers, middlewares) - -// 可选:限流中间件 -if config.GetConfig().Middleware.EnableRateLimiter { - var storage fiber.Storage = nil - if config.GetConfig().Middleware.RateLimiter.Storage == "redis" { - storage = redisStorage // 使用 Redis 存储 - } - v1 := app.Group("/api/v1") - v1.Use(middleware.RateLimiter( - config.GetConfig().Middleware.RateLimiter.Max, - config.GetConfig().Middleware.RateLimiter.Expiration, - storage, - )) -} -``` - -### 请求流程示例 - -**场景**:已启用所有中间件的 `/api/v1/users` 认证请求 - -``` -1. 请求到达:GET /api/v1/users - 请求头:token: abc123 - -2. Recover 中间件:准备捕获 panic - → 传递到下一个中间件 - -3. RequestID 中间件:生成 UUID - → 设置上下文:request_id = "550e8400-e29b-41d4-a716-446655440000" - → 传递到下一个中间件 - -4. Logger 中间件:记录请求开始 - → 日志:{"method":"GET", "path":"/api/v1/users", "request_id":"550e8400-..."} - → 传递到下一个中间件 - -5. KeyAuth 中间件:验证 token - → 检查 Redis:GET "auth:token:abc123" → "user-789" - → 设置上下文:user_id = "user-789" - → 传递到下一个中间件 - -6. RateLimiter 中间件:检查限流 - → 检查计数器:GET "rate_limit:127.0.0.1" → "5"(低于限制 100) - → 增加计数器:INCR "rate_limit:127.0.0.1" → "6" - → 传递到下一个中间件 - -7. 处理器执行:listUsersHandler() - → 从上下文获取 user_id:"user-789" - → 从数据库获取用户 - → 返回响应:{"code":0, "data":[...], "msg":"success"} - -8. Logger 中间件:记录响应 - → 日志:{"status":200, "duration_ms":23.45, "user_id":"user-789"} - -9. RequestID 中间件:添加响应头 - → 响应头:X-Request-ID: 550e8400-e29b-41d4-a716-446655440000 - -10. 响应发送给客户端 -``` - -### 中间件中的错误处理 - -如果任何中间件返回错误,链停止并发送错误响应: - -``` -请求 → Recover → RequestID → Logger → [KeyAuth 失败] ✗ - ↓ - 返回 401 - (不执行 RateLimiter 和 Handler) -``` - -示例:缺失 token -``` -KeyAuth:Token 缺失 -→ 返回 response.Error(c, 401, 1001, "缺失认证令牌") -→ Logger 记录:{"status":401, "duration_ms":1.23} -→ RequestID 添加响应头 -→ 发送响应 -``` - -## 配置 - -### 嵌入式配置机制 - -系统使用 go:embed 将默认配置嵌入二进制文件,通过环境变量进行覆盖: - -- **默认配置**:`pkg/config/defaults/config.yaml`(编译时嵌入) -- **环境变量前缀**:`JUNHONG_` -- **格式转换**:配置路径中的 `.` 替换为 `_` - -**环境变量覆盖示例**: - -| 配置项 | 环境变量 | -|-------|---------| -| `database.host` | `JUNHONG_DATABASE_HOST` | -| `redis.address` | `JUNHONG_REDIS_ADDRESS` | -| `jwt.secret_key` | `JUNHONG_JWT_SECRET_KEY` | -| `logging.level` | `JUNHONG_LOGGING_LEVEL` | - -### 必填配置 - -以下配置项必须通过环境变量设置(无默认值或需要覆盖): - -```bash -# 数据库配置(必填) -export JUNHONG_DATABASE_HOST=localhost -export JUNHONG_DATABASE_PORT=5432 -export JUNHONG_DATABASE_USER=postgres -export JUNHONG_DATABASE_PASSWORD=your_password -export JUNHONG_DATABASE_DBNAME=junhong_cmp - -# Redis 配置(必填) -export JUNHONG_REDIS_ADDRESS=localhost - -# JWT 密钥(必填,生产环境必须修改) -export JUNHONG_JWT_SECRET_KEY=your-secret-key-change-in-production -``` - -### Docker 部署 - -Docker 部署使用纯环境变量配置,无需挂载配置文件: - -```yaml -# docker-compose.prod.yml 示例 -services: - api: - image: registry.boss160.cn/junhong/cmp-fiber-api:latest - environment: - - JUNHONG_DATABASE_HOST=db-host - - JUNHONG_DATABASE_PORT=5432 - - JUNHONG_DATABASE_USER=postgres - - JUNHONG_DATABASE_PASSWORD=secret - - JUNHONG_DATABASE_DBNAME=junhong_cmp - - JUNHONG_REDIS_ADDRESS=redis - - JUNHONG_JWT_SECRET_KEY=production-secret - volumes: - - ./logs:/app/logs # 仅挂载日志目录 -``` - -### 完整环境变量列表 - -详见 [环境变量配置文档](docs/environment-variables.md) - -## 测试 - -### 运行所有测试 - -```bash -# 运行所有单元和集成测试 -go test ./... - -# 带覆盖率运行 -go test -cover ./... - -# 详细输出运行 -go test -v ./... -``` - -### 运行特定测试套件 - -```bash -# 仅单元测试 -go test ./pkg/... - -# 仅集成测试 -go test ./tests/integration/... - -# 特定测试 -go test -v ./internal/middleware -run TestKeyAuth -``` - -### 集成测试 - -集成测试需要 Redis 运行: - -```bash -# 启动 Redis -redis-server - -# 运行集成测试 -go test -v ./tests/integration/... -``` - -如果 Redis 不可用,测试自动跳过。 - -### 测试连接管理 - -测试使用全局单例连接池,性能提升 6-7 倍。详见 [测试连接管理规范](docs/testing/test-connection-guide.md)。 - -**标准写法**: -```go -func TestXxx(t *testing.T) { - tx := testutils.NewTestTransaction(t) // 自动回滚的事务 - rdb := testutils.GetTestRedis(t) // 全局 Redis 连接 - testutils.CleanTestRedisKeys(t, rdb) // 自动清理 Redis 键 - - store := postgres.NewXxxStore(tx, rdb) - // 测试代码... -} -``` - -## 架构设计 - -### 分层架构 -``` -Handler (HTTP) → Service (业务逻辑) → Store (数据访问) → Model (数据模型) -``` - -### 双服务架构 -- **API 服务**:处理 HTTP 请求,快速响应 -- **Worker 服务**:处理异步任务(批量同步、分佣计算等),独立部署 - -### 核心模块 -- **Service 层**:统一管理所有业务逻辑,支持跨模块调用 -- **Store 层**:统一管理所有数据访问,支持事务 -- **Task 层**:Asynq 任务处理器,支持定时任务和事件触发 - -## 框架优化历史 - -### 005-framework-cleanup-refactor(2025-11) - -**背景**:清理技术债务,统一框架设计 - -**主要变更**: -1. **清理示例代码**:删除所有 user/order 示例业务代码,保持代码库整洁 -2. **统一认证中间件**:合并两套 Auth 实现到 `pkg/middleware/auth.go`,统一错误处理格式 -3. **简化错误结构**:删除 AppError 的 HTTPStatus 字段,避免字段冗余 -4. **组件注册解耦**:创建 `internal/bootstrap/` 包实现自动化组件初始化 - - 按模块拆分:`stores.go`、`services.go`、`handlers.go` - - main.go 简化为一行:`handlers, err := bootstrap.Bootstrap(deps)` -5. **数据权限自动化**:实现 GORM Callback 自动注入数据权限过滤 - - 基于 creator 字段自动过滤(普通用户只能看到自己和下级的数据) - - root 用户自动跳过过滤 - - 支持通过 `gorm.SkipDataPermission(ctx)` 手动绕过 - - 删除未使用的 `scopes.go` 手动 Scope 函数 - -**设计原则**: -- 保持 Go 惯用模式,避免 Java 风格过度抽象 -- 使用显式依赖注入,不引入复杂的 DI 框架 -- 每个文件保持 < 100 行,职责单一 -- 在关键扩展点添加 TODO 标记 - -**详细文档**: -- [变更提案](openspec/changes/refactor-framework-cleanup/proposal.md) -- [设计文档](openspec/changes/refactor-framework-cleanup/design.md) -- [任务清单](openspec/changes/refactor-framework-cleanup/tasks.md) - -## 开发规范 - -### 依赖注入 -通过 `Service` 和 `Store` 结构体统一管理依赖: -```go -// 初始化 -st := store.New(db) -svc := service.New(st, queueClient, logger) - -// 使用 -svc.SIM.Activate(...) -svc.Commission.Calculate(...) -``` - -### 事务处理 -```go -store.Transaction(ctx, func(tx *store.Store) error { - tx.SIM.UpdateStatus(...) - tx.Commission.Create(...) - return nil -}) -``` - -### 异步任务 -- 高频任务:批量状态同步、流量同步、实名检查 -- 业务任务:分佣计算、生命周期变更通知 -- 任务优先级:critical > default > low - -### 常量和 Redis Key 管理 -所有常量统一在 `pkg/constants/` 目录管理: -```go -// 业务常量 -constants.SIMStatusActive -constants.SIMStatusInactive - -// Redis Key 管理(统一使用 Key 生成函数) -constants.RedisSIMStatusKey(iccid) // sim:status:{iccid} -constants.RedisAgentCommissionKey(agentID) // agent:commission:{agentID} -constants.RedisTaskLockKey(taskName) // task:lock:{taskName} -constants.RedisAuthTokenKey(token) // auth:token:{token} - -// 使用示例 -key := constants.RedisSIMStatusKey("898600...") -rdb.Set(ctx, key, status, time.Hour) -``` - -## 文档 - -### 开发规范 - -- **[API 文档生成规范](docs/api-documentation-guide.md)**:路由注册规范、DTO 规范、OpenAPI 文档生成流程 -- **[数据库验证规范](AGENTS.md#数据库验证规范)**:使用 PostgreSQL MCP 验证接口逻辑和业务数据的正确性 -- **[开发规范总览](AGENTS.md)**:完整的项目开发规范(必读) - -### 功能指南 - -- **[快速开始指南](specs/001-fiber-middleware-integration/quickstart.md)**:详细设置和测试说明 -- **[限流指南](docs/rate-limiting.md)**:全面的限流配置和使用 -- **[错误处理使用指南](docs/003-error-handling/使用指南.md)**:错误码参考、Handler 使用、客户端处理、最佳实践 -- **[错误处理架构说明](docs/003-error-handling/架构说明.md)**:架构设计、性能优化、扩展性说明 - -### 架构设计 - -- **[实现计划](specs/001-fiber-middleware-integration/plan.md)**:设计决策和架构 -- **[数据模型](specs/001-fiber-middleware-integration/data-model.md)**:配置结构和 Redis 架构 - -## 技术栈 - -- **Go**:1.25.1 -- **Fiber**:v2.52.9(HTTP 框架) -- **Zap**:v1.27.0(结构化日志) -- **Lumberjack**:v2.2.1(日志轮转) -- **Viper**:v1.19.0(配置管理) -- **go-redis**:v9.7.0(Redis 客户端) -- **fsnotify**:v1.8.0(文件系统通知) -- **GORM**:(数据库 ORM) -- **sonic**:(高性能 JSON) -- **Asynq**:(异步任务队列) -- **Validator**:(参数验证) -- **PowerWeChat**:v3.4.38(微信SDK - 公众号 & 支付) - -## 开发流程(Speckit) - -本项目使用 Speckit 规范化功能开发流程,确保代码质量、测试覆盖和架构一致性。 - -### 项目宪章 - -项目遵循 `.specify/memory/constitution.md` 定义的核心原则: - -1. **技术栈遵守**:严格使用 Fiber + GORM + Viper + Zap + Asynq,禁止原生调用快捷方式 -2. **代码质量标准**:遵循 Handler → Service → Store → Model 分层架构 -3. **测试标准**:70%+ 测试覆盖率,核心业务 90%+ -4. **用户体验一致性**:统一 JSON 响应格式、RESTful API、双语错误消息 -5. **性能要求**:API P95 < 200ms,P99 < 500ms,合理使用批量操作和异步任务 - -详细原则和规则请参阅宪章文档。 - -### Speckit 命令 - -```bash -# 创建功能规范 -/speckit.specify "功能描述" - -# 明确规范细节 -/speckit.clarify - -# 生成实现计划 -/speckit.plan - -# 生成任务列表 -/speckit.tasks - -# 执行实现 -/speckit.implement - -# 一致性分析 -/speckit.analyze - -# 生成自定义检查清单 -/speckit.checklist "检查项要求" - -# 更新项目宪章 -/speckit.constitution "宪章更新说明" -``` - -## 代码规范检查 - -运行代码规范检查: - -```bash -# 检查 Service 层错误处理 -bash scripts/check-service-errors.sh - -# 检查注释路径一致性 -bash scripts/check-comment-paths.sh - -# 运行所有检查 -bash scripts/check-all.sh -``` - -这些检查会在 CI/CD 流程中自动执行。 - -## 设计原则 - -- **简单实用**:不过度设计,够用就好 -- **直接实现**:避免不必要的接口抽象 -- **统一管理**:依赖集中初始化,避免参数传递 -- **职责分离**:API 和 Worker 独立部署,便于扩展 - -## 许可证 - -MIT License diff --git a/README_BACKUP_57152.md b/README_BACKUP_57152.md deleted file mode 100644 index a629154..0000000 --- a/README_BACKUP_57152.md +++ /dev/null @@ -1,949 +0,0 @@ -# 君鸿卡管系统 - Fiber 中间件集成 - -基于 Go + Fiber 框架的 HTTP 服务,集成了认证、限流、结构化日志和嵌入式配置功能。 - -## 系统简介 - -物联网卡 + 号卡全生命周期管理平台,支持代理商体系和分佣结算。 - -**技术栈**:Fiber + GORM + Viper + Zap + Lumberjack.v2 + Validator + sonic JSON + Asynq + PostgreSQL - ---- - -## 核心业务说明 - -### 业务模式概览 - -君鸿卡管系统是一个物联网卡和号卡的全生命周期管理平台,支持三种客户类型和两种组织实体的多租户管理。 - -### 三种客户类型 - -| 客户类型 | 业务特点 | 典型场景 | 钱包归属 | -|---------|---------|---------|---------| -| **企业客户** | B端大客户,公对公支付 | 企业购买大量卡/设备用于业务运营 | ❌ 无钱包(后台直接分配套餐) | -| **个人客户** | C端用户,微信登录 | 个人购买单卡或设备(含1-4张卡) | ✅ 钱包归属**卡/设备**(支持转手) | -| **代理商** | 渠道分销商,层级管理 | 预存款采购套餐,按成本价+加价销售 | ✅ 钱包归属**店铺**(多账号共享) | - -### 个人客户业务流程 - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ 个人客户使用流程 │ -└──────────────────────────┬──────────────────────────────────────┘ - │ - ┌──────────▼──────────┐ - │ 1. 获得卡/设备 │ - │ - 单卡:ICCID │ - │ - 设备:设备号/IMEI │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 2. 微信扫码登录 │ - │ - 输入ICCID/IMEI │ - │ - 首次需绑定手机号 │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 3. 查看卡/设备信息 │ - │ - 流量使用情况 │ - │ - 套餐有效期 │ - │ - 钱包余额 │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 4. 钱包充值 │ - │ - 微信支付 │ - │ - 支付宝支付 │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 5. 购买套餐 │ - │ - 单卡套餐 │ - │ - 设备套餐(共享) │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 6. 卡/设备转手 │ - │ - 新用户扫码登录 │ - │ - 钱包余额跟着走 │ - └─────────────────────┘ -``` - -### 钱包归属设计 - -#### 为什么钱包绑定资源(卡/设备)而非用户? - -**问题场景**: -``` -个人客户 A 购买单卡 → 充值 100 元 → 使用 50 元 → 转手给个人客户 B -``` - -**如果钱包绑定用户**: -- ❌ 个人客户 B 登录后看不到余额(钱包还在 A 账号下) -- ❌ 需要手动转账或退款,体验极差 - -**钱包绑定资源(当前设计)**: -- ✅ 个人客户 B 登录后看到剩余 50 元(钱包跟着卡走) -- ✅ 无需任何额外操作,自然流转 - -#### 钱包归属规则 - -```go -// 钱包模型 -type Wallet struct { - ResourceType string // iot_card | device | shop - ResourceID uint // 资源ID - Balance int64 // 余额(分) - // ... -} - -// 场景1:个人客户的单卡钱包 -resource_type = "iot_card" -resource_id = 101 // 卡ID - -// 场景2:个人客户的设备钱包(3张卡共享) -resource_type = "device" -resource_id = 1001 // 设备ID - -// 场景3:代理商店铺钱包(多账号共享) -resource_type = "shop" -resource_id = 10 // 店铺ID -``` - -### 设备套餐业务规则 - -#### 设备级套餐购买 - -``` -设备绑定 3 张 IoT 卡 -├── 卡1:ICCID-001 -├── 卡2:ICCID-002 -└── 卡3:ICCID-003 - -用户购买套餐:399 元/年,每月 3000G 流量 -├── 套餐分配:3 张卡都获得该套餐 -├── 流量共享:3000G/月 在 3 张卡之间共享(总共 3000G) -├── 用户支付:399 元(一次性) -└── 代理分佣:100 元(只分一次,不按卡数倍增) -``` - -**关键点**: -- ✅ 套餐自动分配到设备的所有卡 -- ✅ 流量是**设备级别共享**(非每卡独立) -- ✅ 分佣**只计算一次**(防止重复分佣) - -### 标签系统多租户隔离 - -#### 三级隔离模型 - -| 标签类型 | 创建者 | 可见范围 | 名称唯一性 | 示例 | -|---------|-------|---------|-----------|------| -| 平台全局标签 | 平台管理员 | 所有用户 | 全局唯一 | "VIP"、"重要客户" | -| 企业标签 | 企业用户 | 仅该企业 | 企业内唯一 | 企业A的"测试标签" | -| 店铺标签 | 代理商 | 该店铺及下级 | 店铺内唯一 | 店铺10的"华东区" | - -#### 隔离规则 - -``` -企业 A 创建标签 "测试标签" -├── enterprise_id = 5, shop_id = NULL -├── 企业 A 的用户可见 -└── 企业 B 的用户不可见 - -企业 B 创建标签 "测试标签"(允许) -├── enterprise_id = 8, shop_id = NULL -├── 企业 B 的用户可见 -└── 与企业 A 的 "测试标签" 相互隔离 - -平台创建标签 "VIP" -├── enterprise_id = NULL, shop_id = NULL -└── 所有用户可见 -``` - -#### 数据权限自动过滤 - -```go -// GORM Callback 自动注入过滤条件 -switch userType { -case UserTypeAgent: - // 代理用户:只看到自己店铺及下级店铺的标签 - db.Where("shop_id IN (?) OR (enterprise_id IS NULL AND shop_id IS NULL)", subordinateShopIDs) - -case UserTypeEnterprise: - // 企业用户:只看到自己企业的标签 - db.Where("enterprise_id = ? OR (enterprise_id IS NULL AND shop_id IS NULL)", enterpriseID) - -default: - // 个人客户:只看到全局标签 - db.Where("enterprise_id IS NULL AND shop_id IS NULL") -} -``` - ---- - -## 核心功能 - -- **认证中间件**:基于 Redis 的 Token 认证 -- **限流中间件**:基于 IP 的限流,支持可配置的限制和存储后端 -- **结构化日志**:使用 Zap 的 JSON 日志和自动日志轮转 -- **嵌入式配置**:配置嵌入二进制文件,通过环境变量覆盖,简化 Docker 部署 -- **请求 ID 追踪**:UUID 跨日志的请求追踪 -- **Panic 恢复**:优雅的 panic 处理和堆栈跟踪日志 -- **统一错误处理**:全局 ErrorHandler 统一处理所有 API 错误,返回一致的 JSON 格式(包含错误码、消息、时间戳);Panic 自动恢复防止服务崩溃;错误分类处理(客户端 4xx、服务端 5xx)和日志级别控制;敏感信息自动脱敏保护 -- **数据持久化**:GORM + PostgreSQL 集成,提供完整的 CRUD 操作、事务支持和数据库迁移能力 -- **异步任务处理**:Asynq 任务队列集成,支持任务提交、后台执行、自动重试和幂等性保障,实现邮件发送、数据同步等异步任务 -- **RBAC 权限系统**:完整的基于角色的访问控制,支持账号、角色、权限的多对多关联和层级关系;基于店铺层级的自动数据权限过滤,实现多租户数据隔离;使用 PostgreSQL WITH RECURSIVE 查询下级店铺并通过 Redis 缓存优化性能;完整的权限检查功能支持路由级别的细粒度权限控制,支持平台过滤(web/h5/all)和超级管理员自动跳过(详见 [功能总结](docs/004-rbac-data-permission/功能总结.md)、[使用指南](docs/004-rbac-data-permission/使用指南.md) 和 [权限检查使用指南](docs/permission-check-usage.md)) -- **商户管理**:完整的商户(Shop)和商户账号管理功能,支持商户创建时自动创建初始坐席账号、删除商户时批量禁用关联账号、账号密码重置等功能(详见 [使用指南](docs/shop-management/使用指南.md) 和 [API 文档](docs/shop-management/API文档.md)) -- **B 端认证系统**:完整的后台和 H5 认证功能,支持基于 Redis 的 Token 管理和双令牌机制(Access Token 24h + Refresh Token 7天);包含登录、登出、Token 刷新、用户信息查询和密码修改功能;通过用户类型隔离确保后台(SuperAdmin、Platform、Agent)和 H5(Agent、Enterprise)的访问控制;**登录响应包含菜单树和按钮权限**(menus/buttons),前端无需二次处理直接渲染侧边栏和控制按钮显示;详见 [API 文档](docs/api/auth.md)、[使用指南](docs/auth-usage-guide.md)、[架构说明](docs/auth-architecture.md) 和 [菜单权限使用指南](docs/login-menu-button-response/使用指南.md) -- **生命周期管理**:物联网卡/号卡的开卡、激活、停机、复机、销户 -- **代理商体系**:层级管理和分佣结算 -- **批量同步**:卡状态、实名状态、流量使用情况 -- **分佣验证指引**:对代理分佣的冻结、解冻、提现校验流程进行了结构化说明与流程图,详见 [分佣逻辑正确与否验证](docs/优化说明/分佣逻辑正确与否验证.md) -- **对象存储**:S3 兼容的对象存储服务集成(联通云 OSS),支持预签名 URL 上传、文件下载、临时文件处理;用于 ICCID 批量导入、数据导出等场景;详见 [使用指南](docs/object-storage/使用指南.md) 和 [前端接入指南](docs/object-storage/前端接入指南.md) -<<<<<<< HEAD -- **Gateway 客户端**:第三方 Gateway API 的 Go 封装,提供流量卡和设备管理的统一接口;内置 AES-128-ECB 加密、MD5 签名验证、HTTP 连接池管理;支持流量卡状态查询、停复机、实名认证、流量查询等 7 个流量卡接口和设备信息查询、卡槽管理、限速设置、WiFi 配置、切卡、重启、恢复出厂等 7 个设备管理接口;测试覆盖率 88.8%;详见 [使用指南](docs/gateway-client-usage.md) 和 [API 参考](docs/gateway-api-reference.md) -======= -- **微信集成**:完整的微信公众号 OAuth 认证和微信支付功能(JSAPI + H5),使用 PowerWeChat v3 SDK;支持个人客户微信授权登录、账号绑定、微信内支付和浏览器 H5 支付;支付回调自动验证签名和幂等性处理;详见 [使用指南](docs/wechat-integration/使用指南.md) 和 [API 文档](docs/wechat-integration/API文档.md) ->>>>>>> emdash/wechat-official-account-payment-integration-30g - -## 用户体系设计 - -系统支持四种用户类型和两种组织实体,实现分层级的多租户管理: - -### 用户类型 - -1. **平台用户**:平台管理员,具有最高权限,可分配多个角色 -2. **代理账号**:店铺(代理商)员工账号,归属于特定店铺,权限相同 -3. **企业账号**:企业客户账号,归属于特定企业,一企业一账号 -4. **个人客户**:个人用户,独立表存储,支持微信绑定,不参与 RBAC 体系 - -### 组织实体 - -1. **店铺(Shop)**:代理商组织实体,支持最多 7 级层级关系 - - 一级代理直接归属于平台 - - 下级代理归属于上级店铺(通过 `parent_id` 字段) - - 一个店铺可以有多个账号(代理员工) - -2. **企业(Enterprise)**:企业客户组织实体 - - 可归属于店铺(通过 `owner_shop_id` 字段) - - 可归属于平台(`owner_shop_id = NULL`) - - 一个企业目前只有一个账号 - -### 核心设计决策 - -- **层级关系在店铺之间维护**:代理上下级关系通过 `Shop.parent_id` 维护,而非账号之间 -- **数据权限基于店铺归属**:数据过滤使用 `shop_id IN (当前店铺及下级店铺)` -- **递归查询+Redis缓存**:使用 `GetSubordinateShopIDs()` 递归查询下级店铺ID,结果缓存30分钟 -- **GORM 自动过滤**:通过 GORM Callback 自动应用数据权限过滤,无需在每个查询手动添加条件 -- **禁止外键约束**:遵循项目原则,表之间通过ID字段关联,关联查询在代码层显式执行 -- **GORM字段显式命名**:所有模型字段必须显式指定 `gorm:"column:field_name"` 标签 - -### 表结构 - -``` -tb_shop (店铺表) -├── id, created_at, updated_at, deleted_at -├── creator, updater -├── shop_name, shop_code -├── parent_id (上级店铺ID) -├── level (层级 1-7) -├── contact_name, contact_phone -├── province, city, district, address -└── status - -tb_enterprise (企业表) -├── id, created_at, updated_at, deleted_at -├── creator, updater -├── enterprise_name, enterprise_code -├── owner_shop_id (归属店铺ID) -├── legal_person, contact_name, contact_phone -├── business_license -├── province, city, district, address -└── status - -tb_personal_customer (个人客户表) -├── id, created_at, updated_at, deleted_at -├── phone (唯一标识) -├── nickname, avatar_url -├── wx_open_id, wx_union_id -└── status - -tb_account (账号表 - 已修改) -├── id, created_at, updated_at, deleted_at -├── creator, updater -├── username, phone, password -├── user_type (1=超级管理员 2=平台用户 3=代理账号 4=企业账号) -├── shop_id (代理账号必填) -├── enterprise_id (企业账号必填) ← 新增 -└── status -``` - -详细设计文档参见: -- [设计文档](openspec/changes/add-user-organization-model/design.md) -- [提案文档](openspec/changes/add-user-organization-model/proposal.md) - -## 数据权限模型 - -系统采用基于用户类型的自动数据权限过滤策略,通过 GORM Callback 自动应用,无需在每个查询中手动添加过滤条件。 - -### 过滤规则 - -| 用户类型 | 过滤策略 | 示例 | -|---------|---------|------| -| 超级管理员(Super Admin) | 跳过过滤,查看所有数据 | - | -| 平台用户(Platform) | 跳过过滤,查看所有数据 | - | -| 代理账号(Agent) | 基于店铺层级过滤 | `WHERE shop_id IN (当前店铺及下级店铺)` | -| 企业账号(Enterprise) | 基于企业归属过滤 | `WHERE enterprise_id = 当前企业ID` | -| 个人客户(Personal Customer) | 基于创建者过滤 | `WHERE creator = 当前用户ID` | - -### 工作机制 - -1. **认证中间件**设置完整用户上下文(`UserContextInfo`)到 `context` 中 -2. **GORM Callback**在每次查询前自动注入过滤条件 -3. **递归查询 + 缓存**:代理用户的下级店铺 ID 通过 `GetSubordinateShopIDs()` 递归查询,结果缓存 30 分钟 -4. **跳过过滤**:特殊场景(如统计、后台任务)可使用 `SkipDataPermission(ctx)` 绕过过滤 - -### 使用示例 - -```go -// 1. 认证后 context 已自动包含用户信息 -ctx := c.UserContext() - -// 2. 所有 Store 层查询自动应用数据权限过滤 -orders, err := orderStore.List(ctx) // 自动过滤为当前用户可见的订单 - -// 3. 需要查询所有数据时,显式跳过过滤 -ctx = gorm.SkipDataPermission(ctx) -allOrders, err := orderStore.List(ctx) // 查询所有订单(仅限特殊场景) -``` - -详细说明参见: -- [数据权限清理总结](docs/remove-legacy-rbac-cleanup/清理总结.md) -- [RBAC 权限使用指南](docs/004-rbac-data-permission/使用指南.md) - -## 快速开始 - -```bash -# 安装依赖 -go mod tidy - -# 启动 Redis(认证功能必需) -redis-server - -# 运行 API 服务 -go run cmd/api/main.go - -# 运行 Worker 服务(可选) -go run cmd/worker/main.go -``` - -### 默认超级管理员账号 - -系统首次启动时会自动创建默认超级管理员账号,无需手动执行 SQL 或脚本。 - -**默认账号信息**: -- 用户名:`admin` -- 密码:`Admin@123456` -- 手机号:`13800000000` - -**自定义配置**: - -通过环境变量自定义默认管理员信息: - -```bash -export JUNHONG_DEFAULT_ADMIN_USERNAME="自定义用户名" -export JUNHONG_DEFAULT_ADMIN_PASSWORD="自定义密码" -export JUNHONG_DEFAULT_ADMIN_PHONE="自定义手机号" -``` - -**注意事项**: -- 系统只在数据库无超级管理员账号时才创建 -- 如果已存在超级管理员,启动时会跳过创建 -- 建议首次登录后立即修改默认密码 -- 初始化日志记录在 `logs/app.log` 中 - -详细设置和测试说明请参阅 [快速开始指南](specs/001-fiber-middleware-integration/quickstart.md)。 - -## 项目结构 - -``` -junhong_cmp_fiber/ -│ -├── cmd/ # 应用程序入口 -│ ├── api/ # HTTP API 服务 -│ │ └── main.go # API 服务主入口 -│ └── worker/ # Asynq 异步任务 Worker -│ └── main.go # Worker 服务主入口 -│ -├── internal/ # 私有业务代码 -│ ├── handler/ # HTTP 处理层 -│ │ ├── user.go # 用户处理器 -│ │ └── health.go # 健康检查处理器 -│ ├── middleware/ # Fiber 中间件实现 -│ │ ├── auth.go # 认证中间件(keyauth) -│ │ ├── ratelimit.go # 限流中间件 -│ │ └── recover.go # Panic 恢复中间件 -│ ├── service/ # 业务逻辑层(核心业务) -│ ├── store/ # 数据访问层 -│ │ └── postgres/ # PostgreSQL 实现 -│ ├── model/ # 数据模型(实体、DTO) -│ ├── task/ # Asynq 任务定义和处理 -│ ├── gateway/ # Gateway 服务 HTTP 客户端 -│ └── router/ # 路由注册 -│ -├── pkg/ # 公共工具库 -│ ├── config/ # 配置管理 -│ │ ├── config.go # 配置结构定义 -│ │ ├── loader.go # 配置加载(嵌入配置 + 环境变量覆盖) -│ │ ├── embedded.go # go:embed 嵌入配置加载 -│ │ └── defaults/config.yaml # 默认配置(嵌入二进制) -│ ├── logger/ # 日志基础设施 -│ │ ├── logger.go # Zap 日志初始化 -│ │ └── middleware.go # Fiber 日志中间件适配器 -│ ├── response/ # 统一响应处理 -│ │ └── response.go # 响应结构和辅助函数 -│ ├── errors/ # 错误码和类型 -│ │ ├── codes.go # 错误码常量 -│ │ └── errors.go # 自定义错误类型 -│ ├── constants/ # 业务常量 -│ │ ├── constants.go # 上下文键、请求头名称 -│ │ └── redis.go # Redis Key 生成器 -│ ├── validator/ # 验证服务 -│ │ └── token.go # Token 验证(Redis) -│ ├── database/ # 数据库初始化 -│ │ └── redis.go # Redis 客户端初始化 -│ └── queue/ # 队列封装(Asynq) -│ -├── tests/ -│ └── integration/ # 集成测试 -│ ├── auth_test.go # 认证测试 -│ └── ratelimit_test.go # 限流测试 -│ -├── migrations/ # 数据库迁移文件 -├── scripts/ # 脚本工具 -├── docs/ # 文档 -│ └── rate-limiting.md # 限流指南 -└── logs/ # 应用日志(自动创建) - ├── app.log # 应用日志(JSON) - └── access.log # 访问日志(JSON) -``` - -## 中间件执行顺序 - -中间件按注册顺序执行。请求按顺序流经每个中间件: - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ HTTP 请求 │ -└────────────────────────────────┬────────────────────────────────┘ - │ - ┌────────────▼────────────┐ - │ 1. Recover 中间件 │ - │ (panic 恢复) │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 2. RequestID 中间件 │ - │ (生成 UUID) │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 3. Logger 中间件 │ - │ (访问日志) │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 4. 认证中间件 │ - │ (按路由组配置) │ ─── 模块化路由注册 - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 5. RateLimiter 中间件 │ - │ (限流) │ ─── 可选 (config: enable_rate_limiter) - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 6. 路由处理器 │ - │ (业务逻辑) │ - └────────────┬────────────┘ - │ -┌────────────────────────────────▼────────────────────────────────┐ -│ HTTP 响应 │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### 中间件详情 - -#### 1. Recover 中间件(fiber.Recover) -- **用途**:捕获 panic 并防止服务器崩溃 -- **行为**: - - 捕获下游中间件/处理器中的任何 panic - - 将 panic 及堆栈跟踪记录到 `logs/app.log` - - 返回 HTTP 500 统一错误响应 - - 服务器继续处理后续请求 -- **始终激活**:是 - -#### 2. RequestID 中间件(自定义) -- **用途**:生成请求追踪的唯一标识符 -- **行为**: - - 为每个请求生成 UUID v4 - - 存储在上下文中:`c.Locals(constants.ContextKeyRequestID)` - - 添加 `X-Request-ID` 响应头 - - 用于所有日志条目以进行关联 -- **始终激活**:是 - -#### 3. Logger 中间件(自定义 Fiber 适配器) -- **用途**:记录所有 HTTP 请求和响应 -- **行为**: - - 记录请求:方法、路径、IP、User-Agent、请求 ID - - 记录响应:状态码、耗时、用户 ID(如果已认证) - - 写入 `logs/access.log`(JSON 格式) - - 结构化字段便于解析和分析 -- **始终激活**:是 -- **日志格式**:包含字段的 JSON:timestamp、level、method、path、status、duration_ms、request_id、ip、user_agent、user_id - -#### 4. 认证中间件(pkg/middleware/auth.go 和 internal/middleware/) -- **用途**:使用 Token 验证对请求进行认证 -- **行为**: - - 从 `Authorization: Bearer {token}` 请求头提取 token - - 通过 TokenValidator 函数验证 token(支持 JWT 和 Redis Token) - - 如果缺失/无效 token 返回 401 - - 成功时将用户信息存储在上下文中(UserID、UserType、ShopID、EnterpriseID) -- **实现方式**:模块化路由注册(无全局配置) - - `/api/admin/*`:后台认证(SuperAdmin、Platform、Agent) - - `/api/h5/*`:H5 认证(Agent、Enterprise) - - `/api/personal/*`:个人客户认证(JWT) -- **跳过路由**:各路由组可自行配置跳过路径(如 `/api/admin/login`) -- **错误码**: - - 1001:缺失 token - - 1002:无效或过期 token - - 1003:权限不足 - -#### 5. RateLimiter 中间件(internal/middleware/ratelimit.go) -- **用途**:通过限制请求速率保护 API 免受滥用 -- **行为**: - - 按客户端 IP 地址追踪请求 - - 执行限制:`expiration` 时间窗口内 `max` 个请求 - - 如果超过限制返回 429 - - 每个 IP 地址独立计数器 -- **配置**:`middleware.enable_rate_limiter`(默认:false) -- **存储选项**: - - `memory`:内存存储(单服务器,重启后重置) - - `redis`:基于 Redis(分布式,持久化) -- **错误码**:1003(请求过于频繁) - -#### 6. 路由处理器 -- **用途**:执行端点的业务逻辑 -- **可用上下文数据**: - - 请求 ID:`c.Locals(constants.ContextKeyRequestID)` - - 用户 ID:`c.Locals(constants.ContextKeyUserID)`(如果已认证) - - 标准 Fiber 上下文方法:`c.Params()`、`c.Query()`、`c.Body()` 等 - -### 中间件注册(cmd/api/main.go) - -```go -// 核心中间件(始终激活) -app.Use(recover.New()) -app.Use(addRequestID()) -app.Use(loggerMiddleware()) - -// 模块化路由注册(认证中间件按路由组配置) -routes.RegisterRoutes(app, handlers, middlewares) - -// 可选:限流中间件 -if config.GetConfig().Middleware.EnableRateLimiter { - var storage fiber.Storage = nil - if config.GetConfig().Middleware.RateLimiter.Storage == "redis" { - storage = redisStorage // 使用 Redis 存储 - } - v1 := app.Group("/api/v1") - v1.Use(middleware.RateLimiter( - config.GetConfig().Middleware.RateLimiter.Max, - config.GetConfig().Middleware.RateLimiter.Expiration, - storage, - )) -} -``` - -### 请求流程示例 - -**场景**:已启用所有中间件的 `/api/v1/users` 认证请求 - -``` -1. 请求到达:GET /api/v1/users - 请求头:token: abc123 - -2. Recover 中间件:准备捕获 panic - → 传递到下一个中间件 - -3. RequestID 中间件:生成 UUID - → 设置上下文:request_id = "550e8400-e29b-41d4-a716-446655440000" - → 传递到下一个中间件 - -4. Logger 中间件:记录请求开始 - → 日志:{"method":"GET", "path":"/api/v1/users", "request_id":"550e8400-..."} - → 传递到下一个中间件 - -5. KeyAuth 中间件:验证 token - → 检查 Redis:GET "auth:token:abc123" → "user-789" - → 设置上下文:user_id = "user-789" - → 传递到下一个中间件 - -6. RateLimiter 中间件:检查限流 - → 检查计数器:GET "rate_limit:127.0.0.1" → "5"(低于限制 100) - → 增加计数器:INCR "rate_limit:127.0.0.1" → "6" - → 传递到下一个中间件 - -7. 处理器执行:listUsersHandler() - → 从上下文获取 user_id:"user-789" - → 从数据库获取用户 - → 返回响应:{"code":0, "data":[...], "msg":"success"} - -8. Logger 中间件:记录响应 - → 日志:{"status":200, "duration_ms":23.45, "user_id":"user-789"} - -9. RequestID 中间件:添加响应头 - → 响应头:X-Request-ID: 550e8400-e29b-41d4-a716-446655440000 - -10. 响应发送给客户端 -``` - -### 中间件中的错误处理 - -如果任何中间件返回错误,链停止并发送错误响应: - -``` -请求 → Recover → RequestID → Logger → [KeyAuth 失败] ✗ - ↓ - 返回 401 - (不执行 RateLimiter 和 Handler) -``` - -示例:缺失 token -``` -KeyAuth:Token 缺失 -→ 返回 response.Error(c, 401, 1001, "缺失认证令牌") -→ Logger 记录:{"status":401, "duration_ms":1.23} -→ RequestID 添加响应头 -→ 发送响应 -``` - -## 配置 - -### 嵌入式配置机制 - -系统使用 go:embed 将默认配置嵌入二进制文件,通过环境变量进行覆盖: - -- **默认配置**:`pkg/config/defaults/config.yaml`(编译时嵌入) -- **环境变量前缀**:`JUNHONG_` -- **格式转换**:配置路径中的 `.` 替换为 `_` - -**环境变量覆盖示例**: - -| 配置项 | 环境变量 | -|-------|---------| -| `database.host` | `JUNHONG_DATABASE_HOST` | -| `redis.address` | `JUNHONG_REDIS_ADDRESS` | -| `jwt.secret_key` | `JUNHONG_JWT_SECRET_KEY` | -| `logging.level` | `JUNHONG_LOGGING_LEVEL` | - -### 必填配置 - -以下配置项必须通过环境变量设置(无默认值或需要覆盖): - -```bash -# 数据库配置(必填) -export JUNHONG_DATABASE_HOST=localhost -export JUNHONG_DATABASE_PORT=5432 -export JUNHONG_DATABASE_USER=postgres -export JUNHONG_DATABASE_PASSWORD=your_password -export JUNHONG_DATABASE_DBNAME=junhong_cmp - -# Redis 配置(必填) -export JUNHONG_REDIS_ADDRESS=localhost - -# JWT 密钥(必填,生产环境必须修改) -export JUNHONG_JWT_SECRET_KEY=your-secret-key-change-in-production -``` - -### Docker 部署 - -Docker 部署使用纯环境变量配置,无需挂载配置文件: - -```yaml -# docker-compose.prod.yml 示例 -services: - api: - image: registry.boss160.cn/junhong/cmp-fiber-api:latest - environment: - - JUNHONG_DATABASE_HOST=db-host - - JUNHONG_DATABASE_PORT=5432 - - JUNHONG_DATABASE_USER=postgres - - JUNHONG_DATABASE_PASSWORD=secret - - JUNHONG_DATABASE_DBNAME=junhong_cmp - - JUNHONG_REDIS_ADDRESS=redis - - JUNHONG_JWT_SECRET_KEY=production-secret - volumes: - - ./logs:/app/logs # 仅挂载日志目录 -``` - -### 完整环境变量列表 - -详见 [环境变量配置文档](docs/environment-variables.md) - -## 测试 - -### 运行所有测试 - -```bash -# 运行所有单元和集成测试 -go test ./... - -# 带覆盖率运行 -go test -cover ./... - -# 详细输出运行 -go test -v ./... -``` - -### 运行特定测试套件 - -```bash -# 仅单元测试 -go test ./pkg/... - -# 仅集成测试 -go test ./tests/integration/... - -# 特定测试 -go test -v ./internal/middleware -run TestKeyAuth -``` - -### 集成测试 - -集成测试需要 Redis 运行: - -```bash -# 启动 Redis -redis-server - -# 运行集成测试 -go test -v ./tests/integration/... -``` - -如果 Redis 不可用,测试自动跳过。 - -### 测试连接管理 - -测试使用全局单例连接池,性能提升 6-7 倍。详见 [测试连接管理规范](docs/testing/test-connection-guide.md)。 - -**标准写法**: -```go -func TestXxx(t *testing.T) { - tx := testutils.NewTestTransaction(t) // 自动回滚的事务 - rdb := testutils.GetTestRedis(t) // 全局 Redis 连接 - testutils.CleanTestRedisKeys(t, rdb) // 自动清理 Redis 键 - - store := postgres.NewXxxStore(tx, rdb) - // 测试代码... -} -``` - -## 架构设计 - -### 分层架构 -``` -Handler (HTTP) → Service (业务逻辑) → Store (数据访问) → Model (数据模型) -``` - -### 双服务架构 -- **API 服务**:处理 HTTP 请求,快速响应 -- **Worker 服务**:处理异步任务(批量同步、分佣计算等),独立部署 - -### 核心模块 -- **Service 层**:统一管理所有业务逻辑,支持跨模块调用 -- **Store 层**:统一管理所有数据访问,支持事务 -- **Task 层**:Asynq 任务处理器,支持定时任务和事件触发 - -## 框架优化历史 - -### 005-framework-cleanup-refactor(2025-11) - -**背景**:清理技术债务,统一框架设计 - -**主要变更**: -1. **清理示例代码**:删除所有 user/order 示例业务代码,保持代码库整洁 -2. **统一认证中间件**:合并两套 Auth 实现到 `pkg/middleware/auth.go`,统一错误处理格式 -3. **简化错误结构**:删除 AppError 的 HTTPStatus 字段,避免字段冗余 -4. **组件注册解耦**:创建 `internal/bootstrap/` 包实现自动化组件初始化 - - 按模块拆分:`stores.go`、`services.go`、`handlers.go` - - main.go 简化为一行:`handlers, err := bootstrap.Bootstrap(deps)` -5. **数据权限自动化**:实现 GORM Callback 自动注入数据权限过滤 - - 基于 creator 字段自动过滤(普通用户只能看到自己和下级的数据) - - root 用户自动跳过过滤 - - 支持通过 `gorm.SkipDataPermission(ctx)` 手动绕过 - - 删除未使用的 `scopes.go` 手动 Scope 函数 - -**设计原则**: -- 保持 Go 惯用模式,避免 Java 风格过度抽象 -- 使用显式依赖注入,不引入复杂的 DI 框架 -- 每个文件保持 < 100 行,职责单一 -- 在关键扩展点添加 TODO 标记 - -**详细文档**: -- [变更提案](openspec/changes/refactor-framework-cleanup/proposal.md) -- [设计文档](openspec/changes/refactor-framework-cleanup/design.md) -- [任务清单](openspec/changes/refactor-framework-cleanup/tasks.md) - -## 开发规范 - -### 依赖注入 -通过 `Service` 和 `Store` 结构体统一管理依赖: -```go -// 初始化 -st := store.New(db) -svc := service.New(st, queueClient, logger) - -// 使用 -svc.SIM.Activate(...) -svc.Commission.Calculate(...) -``` - -### 事务处理 -```go -store.Transaction(ctx, func(tx *store.Store) error { - tx.SIM.UpdateStatus(...) - tx.Commission.Create(...) - return nil -}) -``` - -### 异步任务 -- 高频任务:批量状态同步、流量同步、实名检查 -- 业务任务:分佣计算、生命周期变更通知 -- 任务优先级:critical > default > low - -### 常量和 Redis Key 管理 -所有常量统一在 `pkg/constants/` 目录管理: -```go -// 业务常量 -constants.SIMStatusActive -constants.SIMStatusInactive - -// Redis Key 管理(统一使用 Key 生成函数) -constants.RedisSIMStatusKey(iccid) // sim:status:{iccid} -constants.RedisAgentCommissionKey(agentID) // agent:commission:{agentID} -constants.RedisTaskLockKey(taskName) // task:lock:{taskName} -constants.RedisAuthTokenKey(token) // auth:token:{token} - -// 使用示例 -key := constants.RedisSIMStatusKey("898600...") -rdb.Set(ctx, key, status, time.Hour) -``` - -## 文档 - -### 开发规范 - -- **[API 文档生成规范](docs/api-documentation-guide.md)**:路由注册规范、DTO 规范、OpenAPI 文档生成流程 -- **[数据库验证规范](AGENTS.md#数据库验证规范)**:使用 PostgreSQL MCP 验证接口逻辑和业务数据的正确性 -- **[开发规范总览](AGENTS.md)**:完整的项目开发规范(必读) - -### 功能指南 - -- **[快速开始指南](specs/001-fiber-middleware-integration/quickstart.md)**:详细设置和测试说明 -- **[限流指南](docs/rate-limiting.md)**:全面的限流配置和使用 -- **[错误处理使用指南](docs/003-error-handling/使用指南.md)**:错误码参考、Handler 使用、客户端处理、最佳实践 -- **[错误处理架构说明](docs/003-error-handling/架构说明.md)**:架构设计、性能优化、扩展性说明 - -### 架构设计 - -- **[实现计划](specs/001-fiber-middleware-integration/plan.md)**:设计决策和架构 -- **[数据模型](specs/001-fiber-middleware-integration/data-model.md)**:配置结构和 Redis 架构 - -## 技术栈 - -- **Go**:1.25.1 -- **Fiber**:v2.52.9(HTTP 框架) -- **Zap**:v1.27.0(结构化日志) -- **Lumberjack**:v2.2.1(日志轮转) -- **Viper**:v1.19.0(配置管理) -- **go-redis**:v9.7.0(Redis 客户端) -- **fsnotify**:v1.8.0(文件系统通知) -- **GORM**:(数据库 ORM) -- **sonic**:(高性能 JSON) -- **Asynq**:(异步任务队列) -- **Validator**:(参数验证) -- **PowerWeChat**:v3.4.38(微信SDK - 公众号 & 支付) - -## 开发流程(Speckit) - -本项目使用 Speckit 规范化功能开发流程,确保代码质量、测试覆盖和架构一致性。 - -### 项目宪章 - -项目遵循 `.specify/memory/constitution.md` 定义的核心原则: - -1. **技术栈遵守**:严格使用 Fiber + GORM + Viper + Zap + Asynq,禁止原生调用快捷方式 -2. **代码质量标准**:遵循 Handler → Service → Store → Model 分层架构 -3. **测试标准**:70%+ 测试覆盖率,核心业务 90%+ -4. **用户体验一致性**:统一 JSON 响应格式、RESTful API、双语错误消息 -5. **性能要求**:API P95 < 200ms,P99 < 500ms,合理使用批量操作和异步任务 - -详细原则和规则请参阅宪章文档。 - -### Speckit 命令 - -```bash -# 创建功能规范 -/speckit.specify "功能描述" - -# 明确规范细节 -/speckit.clarify - -# 生成实现计划 -/speckit.plan - -# 生成任务列表 -/speckit.tasks - -# 执行实现 -/speckit.implement - -# 一致性分析 -/speckit.analyze - -# 生成自定义检查清单 -/speckit.checklist "检查项要求" - -# 更新项目宪章 -/speckit.constitution "宪章更新说明" -``` - -## 代码规范检查 - -运行代码规范检查: - -```bash -# 检查 Service 层错误处理 -bash scripts/check-service-errors.sh - -# 检查注释路径一致性 -bash scripts/check-comment-paths.sh - -# 运行所有检查 -bash scripts/check-all.sh -``` - -这些检查会在 CI/CD 流程中自动执行。 - -## 设计原则 - -- **简单实用**:不过度设计,够用就好 -- **直接实现**:避免不必要的接口抽象 -- **统一管理**:依赖集中初始化,避免参数传递 -- **职责分离**:API 和 Worker 独立部署,便于扩展 - -## 许可证 - -MIT License diff --git a/README_BASE_57152.md b/README_BASE_57152.md deleted file mode 100644 index 7ec0eae..0000000 --- a/README_BASE_57152.md +++ /dev/null @@ -1,943 +0,0 @@ -# 君鸿卡管系统 - Fiber 中间件集成 - -基于 Go + Fiber 框架的 HTTP 服务,集成了认证、限流、结构化日志和嵌入式配置功能。 - -## 系统简介 - -物联网卡 + 号卡全生命周期管理平台,支持代理商体系和分佣结算。 - -**技术栈**:Fiber + GORM + Viper + Zap + Lumberjack.v2 + Validator + sonic JSON + Asynq + PostgreSQL - ---- - -## 核心业务说明 - -### 业务模式概览 - -君鸿卡管系统是一个物联网卡和号卡的全生命周期管理平台,支持三种客户类型和两种组织实体的多租户管理。 - -### 三种客户类型 - -| 客户类型 | 业务特点 | 典型场景 | 钱包归属 | -|---------|---------|---------|---------| -| **企业客户** | B端大客户,公对公支付 | 企业购买大量卡/设备用于业务运营 | ❌ 无钱包(后台直接分配套餐) | -| **个人客户** | C端用户,微信登录 | 个人购买单卡或设备(含1-4张卡) | ✅ 钱包归属**卡/设备**(支持转手) | -| **代理商** | 渠道分销商,层级管理 | 预存款采购套餐,按成本价+加价销售 | ✅ 钱包归属**店铺**(多账号共享) | - -### 个人客户业务流程 - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ 个人客户使用流程 │ -└──────────────────────────┬──────────────────────────────────────┘ - │ - ┌──────────▼──────────┐ - │ 1. 获得卡/设备 │ - │ - 单卡:ICCID │ - │ - 设备:设备号/IMEI │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 2. 微信扫码登录 │ - │ - 输入ICCID/IMEI │ - │ - 首次需绑定手机号 │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 3. 查看卡/设备信息 │ - │ - 流量使用情况 │ - │ - 套餐有效期 │ - │ - 钱包余额 │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 4. 钱包充值 │ - │ - 微信支付 │ - │ - 支付宝支付 │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 5. 购买套餐 │ - │ - 单卡套餐 │ - │ - 设备套餐(共享) │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 6. 卡/设备转手 │ - │ - 新用户扫码登录 │ - │ - 钱包余额跟着走 │ - └─────────────────────┘ -``` - -### 钱包归属设计 - -#### 为什么钱包绑定资源(卡/设备)而非用户? - -**问题场景**: -``` -个人客户 A 购买单卡 → 充值 100 元 → 使用 50 元 → 转手给个人客户 B -``` - -**如果钱包绑定用户**: -- ❌ 个人客户 B 登录后看不到余额(钱包还在 A 账号下) -- ❌ 需要手动转账或退款,体验极差 - -**钱包绑定资源(当前设计)**: -- ✅ 个人客户 B 登录后看到剩余 50 元(钱包跟着卡走) -- ✅ 无需任何额外操作,自然流转 - -#### 钱包归属规则 - -```go -// 钱包模型 -type Wallet struct { - ResourceType string // iot_card | device | shop - ResourceID uint // 资源ID - Balance int64 // 余额(分) - // ... -} - -// 场景1:个人客户的单卡钱包 -resource_type = "iot_card" -resource_id = 101 // 卡ID - -// 场景2:个人客户的设备钱包(3张卡共享) -resource_type = "device" -resource_id = 1001 // 设备ID - -// 场景3:代理商店铺钱包(多账号共享) -resource_type = "shop" -resource_id = 10 // 店铺ID -``` - -### 设备套餐业务规则 - -#### 设备级套餐购买 - -``` -设备绑定 3 张 IoT 卡 -├── 卡1:ICCID-001 -├── 卡2:ICCID-002 -└── 卡3:ICCID-003 - -用户购买套餐:399 元/年,每月 3000G 流量 -├── 套餐分配:3 张卡都获得该套餐 -├── 流量共享:3000G/月 在 3 张卡之间共享(总共 3000G) -├── 用户支付:399 元(一次性) -└── 代理分佣:100 元(只分一次,不按卡数倍增) -``` - -**关键点**: -- ✅ 套餐自动分配到设备的所有卡 -- ✅ 流量是**设备级别共享**(非每卡独立) -- ✅ 分佣**只计算一次**(防止重复分佣) - -### 标签系统多租户隔离 - -#### 三级隔离模型 - -| 标签类型 | 创建者 | 可见范围 | 名称唯一性 | 示例 | -|---------|-------|---------|-----------|------| -| 平台全局标签 | 平台管理员 | 所有用户 | 全局唯一 | "VIP"、"重要客户" | -| 企业标签 | 企业用户 | 仅该企业 | 企业内唯一 | 企业A的"测试标签" | -| 店铺标签 | 代理商 | 该店铺及下级 | 店铺内唯一 | 店铺10的"华东区" | - -#### 隔离规则 - -``` -企业 A 创建标签 "测试标签" -├── enterprise_id = 5, shop_id = NULL -├── 企业 A 的用户可见 -└── 企业 B 的用户不可见 - -企业 B 创建标签 "测试标签"(允许) -├── enterprise_id = 8, shop_id = NULL -├── 企业 B 的用户可见 -└── 与企业 A 的 "测试标签" 相互隔离 - -平台创建标签 "VIP" -├── enterprise_id = NULL, shop_id = NULL -└── 所有用户可见 -``` - -#### 数据权限自动过滤 - -```go -// GORM Callback 自动注入过滤条件 -switch userType { -case UserTypeAgent: - // 代理用户:只看到自己店铺及下级店铺的标签 - db.Where("shop_id IN (?) OR (enterprise_id IS NULL AND shop_id IS NULL)", subordinateShopIDs) - -case UserTypeEnterprise: - // 企业用户:只看到自己企业的标签 - db.Where("enterprise_id = ? OR (enterprise_id IS NULL AND shop_id IS NULL)", enterpriseID) - -default: - // 个人客户:只看到全局标签 - db.Where("enterprise_id IS NULL AND shop_id IS NULL") -} -``` - ---- - -## 核心功能 - -- **认证中间件**:基于 Redis 的 Token 认证 -- **限流中间件**:基于 IP 的限流,支持可配置的限制和存储后端 -- **结构化日志**:使用 Zap 的 JSON 日志和自动日志轮转 -- **嵌入式配置**:配置嵌入二进制文件,通过环境变量覆盖,简化 Docker 部署 -- **请求 ID 追踪**:UUID 跨日志的请求追踪 -- **Panic 恢复**:优雅的 panic 处理和堆栈跟踪日志 -- **统一错误处理**:全局 ErrorHandler 统一处理所有 API 错误,返回一致的 JSON 格式(包含错误码、消息、时间戳);Panic 自动恢复防止服务崩溃;错误分类处理(客户端 4xx、服务端 5xx)和日志级别控制;敏感信息自动脱敏保护 -- **数据持久化**:GORM + PostgreSQL 集成,提供完整的 CRUD 操作、事务支持和数据库迁移能力 -- **异步任务处理**:Asynq 任务队列集成,支持任务提交、后台执行、自动重试和幂等性保障,实现邮件发送、数据同步等异步任务 -- **RBAC 权限系统**:完整的基于角色的访问控制,支持账号、角色、权限的多对多关联和层级关系;基于店铺层级的自动数据权限过滤,实现多租户数据隔离;使用 PostgreSQL WITH RECURSIVE 查询下级店铺并通过 Redis 缓存优化性能;完整的权限检查功能支持路由级别的细粒度权限控制,支持平台过滤(web/h5/all)和超级管理员自动跳过(详见 [功能总结](docs/004-rbac-data-permission/功能总结.md)、[使用指南](docs/004-rbac-data-permission/使用指南.md) 和 [权限检查使用指南](docs/permission-check-usage.md)) -- **商户管理**:完整的商户(Shop)和商户账号管理功能,支持商户创建时自动创建初始坐席账号、删除商户时批量禁用关联账号、账号密码重置等功能(详见 [使用指南](docs/shop-management/使用指南.md) 和 [API 文档](docs/shop-management/API文档.md)) -- **B 端认证系统**:完整的后台和 H5 认证功能,支持基于 Redis 的 Token 管理和双令牌机制(Access Token 24h + Refresh Token 7天);包含登录、登出、Token 刷新、用户信息查询和密码修改功能;通过用户类型隔离确保后台(SuperAdmin、Platform、Agent)和 H5(Agent、Enterprise)的访问控制;详见 [API 文档](docs/api/auth.md)、[使用指南](docs/auth-usage-guide.md) 和 [架构说明](docs/auth-architecture.md) -- **生命周期管理**:物联网卡/号卡的开卡、激活、停机、复机、销户 -- **代理商体系**:层级管理和分佣结算 -- **批量同步**:卡状态、实名状态、流量使用情况 -- **分佣验证指引**:对代理分佣的冻结、解冻、提现校验流程进行了结构化说明与流程图,详见 [分佣逻辑正确与否验证](docs/优化说明/分佣逻辑正确与否验证.md) -- **对象存储**:S3 兼容的对象存储服务集成(联通云 OSS),支持预签名 URL 上传、文件下载、临时文件处理;用于 ICCID 批量导入、数据导出等场景;详见 [使用指南](docs/object-storage/使用指南.md) 和 [前端接入指南](docs/object-storage/前端接入指南.md) - -## 用户体系设计 - -系统支持四种用户类型和两种组织实体,实现分层级的多租户管理: - -### 用户类型 - -1. **平台用户**:平台管理员,具有最高权限,可分配多个角色 -2. **代理账号**:店铺(代理商)员工账号,归属于特定店铺,权限相同 -3. **企业账号**:企业客户账号,归属于特定企业,一企业一账号 -4. **个人客户**:个人用户,独立表存储,支持微信绑定,不参与 RBAC 体系 - -### 组织实体 - -1. **店铺(Shop)**:代理商组织实体,支持最多 7 级层级关系 - - 一级代理直接归属于平台 - - 下级代理归属于上级店铺(通过 `parent_id` 字段) - - 一个店铺可以有多个账号(代理员工) - -2. **企业(Enterprise)**:企业客户组织实体 - - 可归属于店铺(通过 `owner_shop_id` 字段) - - 可归属于平台(`owner_shop_id = NULL`) - - 一个企业目前只有一个账号 - -### 核心设计决策 - -- **层级关系在店铺之间维护**:代理上下级关系通过 `Shop.parent_id` 维护,而非账号之间 -- **数据权限基于店铺归属**:数据过滤使用 `shop_id IN (当前店铺及下级店铺)` -- **递归查询+Redis缓存**:使用 `GetSubordinateShopIDs()` 递归查询下级店铺ID,结果缓存30分钟 -- **GORM 自动过滤**:通过 GORM Callback 自动应用数据权限过滤,无需在每个查询手动添加条件 -- **禁止外键约束**:遵循项目原则,表之间通过ID字段关联,关联查询在代码层显式执行 -- **GORM字段显式命名**:所有模型字段必须显式指定 `gorm:"column:field_name"` 标签 - -### 表结构 - -``` -tb_shop (店铺表) -├── id, created_at, updated_at, deleted_at -├── creator, updater -├── shop_name, shop_code -├── parent_id (上级店铺ID) -├── level (层级 1-7) -├── contact_name, contact_phone -├── province, city, district, address -└── status - -tb_enterprise (企业表) -├── id, created_at, updated_at, deleted_at -├── creator, updater -├── enterprise_name, enterprise_code -├── owner_shop_id (归属店铺ID) -├── legal_person, contact_name, contact_phone -├── business_license -├── province, city, district, address -└── status - -tb_personal_customer (个人客户表) -├── id, created_at, updated_at, deleted_at -├── phone (唯一标识) -├── nickname, avatar_url -├── wx_open_id, wx_union_id -└── status - -tb_account (账号表 - 已修改) -├── id, created_at, updated_at, deleted_at -├── creator, updater -├── username, phone, password -├── user_type (1=超级管理员 2=平台用户 3=代理账号 4=企业账号) -├── shop_id (代理账号必填) -├── enterprise_id (企业账号必填) ← 新增 -└── status -``` - -详细设计文档参见: -- [设计文档](openspec/changes/add-user-organization-model/design.md) -- [提案文档](openspec/changes/add-user-organization-model/proposal.md) - -## 数据权限模型 - -系统采用基于用户类型的自动数据权限过滤策略,通过 GORM Callback 自动应用,无需在每个查询中手动添加过滤条件。 - -### 过滤规则 - -| 用户类型 | 过滤策略 | 示例 | -|---------|---------|------| -| 超级管理员(Super Admin) | 跳过过滤,查看所有数据 | - | -| 平台用户(Platform) | 跳过过滤,查看所有数据 | - | -| 代理账号(Agent) | 基于店铺层级过滤 | `WHERE shop_id IN (当前店铺及下级店铺)` | -| 企业账号(Enterprise) | 基于企业归属过滤 | `WHERE enterprise_id = 当前企业ID` | -| 个人客户(Personal Customer) | 基于创建者过滤 | `WHERE creator = 当前用户ID` | - -### 工作机制 - -1. **认证中间件**设置完整用户上下文(`UserContextInfo`)到 `context` 中 -2. **GORM Callback**在每次查询前自动注入过滤条件 -3. **递归查询 + 缓存**:代理用户的下级店铺 ID 通过 `GetSubordinateShopIDs()` 递归查询,结果缓存 30 分钟 -4. **跳过过滤**:特殊场景(如统计、后台任务)可使用 `SkipDataPermission(ctx)` 绕过过滤 - -### 使用示例 - -```go -// 1. 认证后 context 已自动包含用户信息 -ctx := c.UserContext() - -// 2. 所有 Store 层查询自动应用数据权限过滤 -orders, err := orderStore.List(ctx) // 自动过滤为当前用户可见的订单 - -// 3. 需要查询所有数据时,显式跳过过滤 -ctx = gorm.SkipDataPermission(ctx) -allOrders, err := orderStore.List(ctx) // 查询所有订单(仅限特殊场景) -``` - -详细说明参见: -- [数据权限清理总结](docs/remove-legacy-rbac-cleanup/清理总结.md) -- [RBAC 权限使用指南](docs/004-rbac-data-permission/使用指南.md) - -## 快速开始 - -```bash -# 安装依赖 -go mod tidy - -# 启动 Redis(认证功能必需) -redis-server - -# 运行 API 服务 -go run cmd/api/main.go - -# 运行 Worker 服务(可选) -go run cmd/worker/main.go -``` - -### 默认超级管理员账号 - -系统首次启动时会自动创建默认超级管理员账号,无需手动执行 SQL 或脚本。 - -**默认账号信息**: -- 用户名:`admin` -- 密码:`Admin@123456` -- 手机号:`13800000000` - -**自定义配置**: - -通过环境变量自定义默认管理员信息: - -```bash -export JUNHONG_DEFAULT_ADMIN_USERNAME="自定义用户名" -export JUNHONG_DEFAULT_ADMIN_PASSWORD="自定义密码" -export JUNHONG_DEFAULT_ADMIN_PHONE="自定义手机号" -``` - -**注意事项**: -- 系统只在数据库无超级管理员账号时才创建 -- 如果已存在超级管理员,启动时会跳过创建 -- 建议首次登录后立即修改默认密码 -- 初始化日志记录在 `logs/app.log` 中 - -详细设置和测试说明请参阅 [快速开始指南](specs/001-fiber-middleware-integration/quickstart.md)。 - -## 项目结构 - -``` -junhong_cmp_fiber/ -│ -├── cmd/ # 应用程序入口 -│ ├── api/ # HTTP API 服务 -│ │ └── main.go # API 服务主入口 -│ └── worker/ # Asynq 异步任务 Worker -│ └── main.go # Worker 服务主入口 -│ -├── internal/ # 私有业务代码 -│ ├── handler/ # HTTP 处理层 -│ │ ├── user.go # 用户处理器 -│ │ └── health.go # 健康检查处理器 -│ ├── middleware/ # Fiber 中间件实现 -│ │ ├── auth.go # 认证中间件(keyauth) -│ │ ├── ratelimit.go # 限流中间件 -│ │ └── recover.go # Panic 恢复中间件 -│ ├── service/ # 业务逻辑层(核心业务) -│ ├── store/ # 数据访问层 -│ │ └── postgres/ # PostgreSQL 实现 -│ ├── model/ # 数据模型(实体、DTO) -│ ├── task/ # Asynq 任务定义和处理 -│ ├── gateway/ # Gateway 服务 HTTP 客户端 -│ └── router/ # 路由注册 -│ -├── pkg/ # 公共工具库 -│ ├── config/ # 配置管理 -│ │ ├── config.go # 配置结构定义 -│ │ ├── loader.go # 配置加载(嵌入配置 + 环境变量覆盖) -│ │ ├── embedded.go # go:embed 嵌入配置加载 -│ │ └── defaults/config.yaml # 默认配置(嵌入二进制) -│ ├── logger/ # 日志基础设施 -│ │ ├── logger.go # Zap 日志初始化 -│ │ └── middleware.go # Fiber 日志中间件适配器 -│ ├── response/ # 统一响应处理 -│ │ └── response.go # 响应结构和辅助函数 -│ ├── errors/ # 错误码和类型 -│ │ ├── codes.go # 错误码常量 -│ │ └── errors.go # 自定义错误类型 -│ ├── constants/ # 业务常量 -│ │ ├── constants.go # 上下文键、请求头名称 -│ │ └── redis.go # Redis Key 生成器 -│ ├── validator/ # 验证服务 -│ │ └── token.go # Token 验证(Redis) -│ ├── database/ # 数据库初始化 -│ │ └── redis.go # Redis 客户端初始化 -│ └── queue/ # 队列封装(Asynq) -│ -├── tests/ -│ └── integration/ # 集成测试 -│ ├── auth_test.go # 认证测试 -│ └── ratelimit_test.go # 限流测试 -│ -├── migrations/ # 数据库迁移文件 -├── scripts/ # 脚本工具 -├── docs/ # 文档 -│ └── rate-limiting.md # 限流指南 -└── logs/ # 应用日志(自动创建) - ├── app.log # 应用日志(JSON) - └── access.log # 访问日志(JSON) -``` - -## 中间件执行顺序 - -中间件按注册顺序执行。请求按顺序流经每个中间件: - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ HTTP 请求 │ -└────────────────────────────────┬────────────────────────────────┘ - │ - ┌────────────▼────────────┐ - │ 1. Recover 中间件 │ - │ (panic 恢复) │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 2. RequestID 中间件 │ - │ (生成 UUID) │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 3. Logger 中间件 │ - │ (访问日志) │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 4. 认证中间件 │ - │ (按路由组配置) │ ─── 模块化路由注册 - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 5. RateLimiter 中间件 │ - │ (限流) │ ─── 可选 (config: enable_rate_limiter) - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 6. 路由处理器 │ - │ (业务逻辑) │ - └────────────┬────────────┘ - │ -┌────────────────────────────────▼────────────────────────────────┐ -│ HTTP 响应 │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### 中间件详情 - -#### 1. Recover 中间件(fiber.Recover) -- **用途**:捕获 panic 并防止服务器崩溃 -- **行为**: - - 捕获下游中间件/处理器中的任何 panic - - 将 panic 及堆栈跟踪记录到 `logs/app.log` - - 返回 HTTP 500 统一错误响应 - - 服务器继续处理后续请求 -- **始终激活**:是 - -#### 2. RequestID 中间件(自定义) -- **用途**:生成请求追踪的唯一标识符 -- **行为**: - - 为每个请求生成 UUID v4 - - 存储在上下文中:`c.Locals(constants.ContextKeyRequestID)` - - 添加 `X-Request-ID` 响应头 - - 用于所有日志条目以进行关联 -- **始终激活**:是 - -#### 3. Logger 中间件(自定义 Fiber 适配器) -- **用途**:记录所有 HTTP 请求和响应 -- **行为**: - - 记录请求:方法、路径、IP、User-Agent、请求 ID - - 记录响应:状态码、耗时、用户 ID(如果已认证) - - 写入 `logs/access.log`(JSON 格式) - - 结构化字段便于解析和分析 -- **始终激活**:是 -- **日志格式**:包含字段的 JSON:timestamp、level、method、path、status、duration_ms、request_id、ip、user_agent、user_id - -#### 4. 认证中间件(pkg/middleware/auth.go 和 internal/middleware/) -- **用途**:使用 Token 验证对请求进行认证 -- **行为**: - - 从 `Authorization: Bearer {token}` 请求头提取 token - - 通过 TokenValidator 函数验证 token(支持 JWT 和 Redis Token) - - 如果缺失/无效 token 返回 401 - - 成功时将用户信息存储在上下文中(UserID、UserType、ShopID、EnterpriseID) -- **实现方式**:模块化路由注册(无全局配置) - - `/api/admin/*`:后台认证(SuperAdmin、Platform、Agent) - - `/api/h5/*`:H5 认证(Agent、Enterprise) - - `/api/personal/*`:个人客户认证(JWT) -- **跳过路由**:各路由组可自行配置跳过路径(如 `/api/admin/login`) -- **错误码**: - - 1001:缺失 token - - 1002:无效或过期 token - - 1003:权限不足 - -#### 5. RateLimiter 中间件(internal/middleware/ratelimit.go) -- **用途**:通过限制请求速率保护 API 免受滥用 -- **行为**: - - 按客户端 IP 地址追踪请求 - - 执行限制:`expiration` 时间窗口内 `max` 个请求 - - 如果超过限制返回 429 - - 每个 IP 地址独立计数器 -- **配置**:`middleware.enable_rate_limiter`(默认:false) -- **存储选项**: - - `memory`:内存存储(单服务器,重启后重置) - - `redis`:基于 Redis(分布式,持久化) -- **错误码**:1003(请求过于频繁) - -#### 6. 路由处理器 -- **用途**:执行端点的业务逻辑 -- **可用上下文数据**: - - 请求 ID:`c.Locals(constants.ContextKeyRequestID)` - - 用户 ID:`c.Locals(constants.ContextKeyUserID)`(如果已认证) - - 标准 Fiber 上下文方法:`c.Params()`、`c.Query()`、`c.Body()` 等 - -### 中间件注册(cmd/api/main.go) - -```go -// 核心中间件(始终激活) -app.Use(recover.New()) -app.Use(addRequestID()) -app.Use(loggerMiddleware()) - -// 模块化路由注册(认证中间件按路由组配置) -routes.RegisterRoutes(app, handlers, middlewares) - -// 可选:限流中间件 -if config.GetConfig().Middleware.EnableRateLimiter { - var storage fiber.Storage = nil - if config.GetConfig().Middleware.RateLimiter.Storage == "redis" { - storage = redisStorage // 使用 Redis 存储 - } - v1 := app.Group("/api/v1") - v1.Use(middleware.RateLimiter( - config.GetConfig().Middleware.RateLimiter.Max, - config.GetConfig().Middleware.RateLimiter.Expiration, - storage, - )) -} -``` - -### 请求流程示例 - -**场景**:已启用所有中间件的 `/api/v1/users` 认证请求 - -``` -1. 请求到达:GET /api/v1/users - 请求头:token: abc123 - -2. Recover 中间件:准备捕获 panic - → 传递到下一个中间件 - -3. RequestID 中间件:生成 UUID - → 设置上下文:request_id = "550e8400-e29b-41d4-a716-446655440000" - → 传递到下一个中间件 - -4. Logger 中间件:记录请求开始 - → 日志:{"method":"GET", "path":"/api/v1/users", "request_id":"550e8400-..."} - → 传递到下一个中间件 - -5. KeyAuth 中间件:验证 token - → 检查 Redis:GET "auth:token:abc123" → "user-789" - → 设置上下文:user_id = "user-789" - → 传递到下一个中间件 - -6. RateLimiter 中间件:检查限流 - → 检查计数器:GET "rate_limit:127.0.0.1" → "5"(低于限制 100) - → 增加计数器:INCR "rate_limit:127.0.0.1" → "6" - → 传递到下一个中间件 - -7. 处理器执行:listUsersHandler() - → 从上下文获取 user_id:"user-789" - → 从数据库获取用户 - → 返回响应:{"code":0, "data":[...], "msg":"success"} - -8. Logger 中间件:记录响应 - → 日志:{"status":200, "duration_ms":23.45, "user_id":"user-789"} - -9. RequestID 中间件:添加响应头 - → 响应头:X-Request-ID: 550e8400-e29b-41d4-a716-446655440000 - -10. 响应发送给客户端 -``` - -### 中间件中的错误处理 - -如果任何中间件返回错误,链停止并发送错误响应: - -``` -请求 → Recover → RequestID → Logger → [KeyAuth 失败] ✗ - ↓ - 返回 401 - (不执行 RateLimiter 和 Handler) -``` - -示例:缺失 token -``` -KeyAuth:Token 缺失 -→ 返回 response.Error(c, 401, 1001, "缺失认证令牌") -→ Logger 记录:{"status":401, "duration_ms":1.23} -→ RequestID 添加响应头 -→ 发送响应 -``` - -## 配置 - -### 嵌入式配置机制 - -系统使用 go:embed 将默认配置嵌入二进制文件,通过环境变量进行覆盖: - -- **默认配置**:`pkg/config/defaults/config.yaml`(编译时嵌入) -- **环境变量前缀**:`JUNHONG_` -- **格式转换**:配置路径中的 `.` 替换为 `_` - -**环境变量覆盖示例**: - -| 配置项 | 环境变量 | -|-------|---------| -| `database.host` | `JUNHONG_DATABASE_HOST` | -| `redis.address` | `JUNHONG_REDIS_ADDRESS` | -| `jwt.secret_key` | `JUNHONG_JWT_SECRET_KEY` | -| `logging.level` | `JUNHONG_LOGGING_LEVEL` | - -### 必填配置 - -以下配置项必须通过环境变量设置(无默认值或需要覆盖): - -```bash -# 数据库配置(必填) -export JUNHONG_DATABASE_HOST=localhost -export JUNHONG_DATABASE_PORT=5432 -export JUNHONG_DATABASE_USER=postgres -export JUNHONG_DATABASE_PASSWORD=your_password -export JUNHONG_DATABASE_DBNAME=junhong_cmp - -# Redis 配置(必填) -export JUNHONG_REDIS_ADDRESS=localhost - -# JWT 密钥(必填,生产环境必须修改) -export JUNHONG_JWT_SECRET_KEY=your-secret-key-change-in-production -``` - -### Docker 部署 - -Docker 部署使用纯环境变量配置,无需挂载配置文件: - -```yaml -# docker-compose.prod.yml 示例 -services: - api: - image: registry.boss160.cn/junhong/cmp-fiber-api:latest - environment: - - JUNHONG_DATABASE_HOST=db-host - - JUNHONG_DATABASE_PORT=5432 - - JUNHONG_DATABASE_USER=postgres - - JUNHONG_DATABASE_PASSWORD=secret - - JUNHONG_DATABASE_DBNAME=junhong_cmp - - JUNHONG_REDIS_ADDRESS=redis - - JUNHONG_JWT_SECRET_KEY=production-secret - volumes: - - ./logs:/app/logs # 仅挂载日志目录 -``` - -### 完整环境变量列表 - -详见 [环境变量配置文档](docs/environment-variables.md) - -## 测试 - -### 运行所有测试 - -```bash -# 运行所有单元和集成测试 -go test ./... - -# 带覆盖率运行 -go test -cover ./... - -# 详细输出运行 -go test -v ./... -``` - -### 运行特定测试套件 - -```bash -# 仅单元测试 -go test ./pkg/... - -# 仅集成测试 -go test ./tests/integration/... - -# 特定测试 -go test -v ./internal/middleware -run TestKeyAuth -``` - -### 集成测试 - -集成测试需要 Redis 运行: - -```bash -# 启动 Redis -redis-server - -# 运行集成测试 -go test -v ./tests/integration/... -``` - -如果 Redis 不可用,测试自动跳过。 - -### 测试连接管理 - -测试使用全局单例连接池,性能提升 6-7 倍。详见 [测试连接管理规范](docs/testing/test-connection-guide.md)。 - -**标准写法**: -```go -func TestXxx(t *testing.T) { - tx := testutils.NewTestTransaction(t) // 自动回滚的事务 - rdb := testutils.GetTestRedis(t) // 全局 Redis 连接 - testutils.CleanTestRedisKeys(t, rdb) // 自动清理 Redis 键 - - store := postgres.NewXxxStore(tx, rdb) - // 测试代码... -} -``` - -## 架构设计 - -### 分层架构 -``` -Handler (HTTP) → Service (业务逻辑) → Store (数据访问) → Model (数据模型) -``` - -### 双服务架构 -- **API 服务**:处理 HTTP 请求,快速响应 -- **Worker 服务**:处理异步任务(批量同步、分佣计算等),独立部署 - -### 核心模块 -- **Service 层**:统一管理所有业务逻辑,支持跨模块调用 -- **Store 层**:统一管理所有数据访问,支持事务 -- **Task 层**:Asynq 任务处理器,支持定时任务和事件触发 - -## 框架优化历史 - -### 005-framework-cleanup-refactor(2025-11) - -**背景**:清理技术债务,统一框架设计 - -**主要变更**: -1. **清理示例代码**:删除所有 user/order 示例业务代码,保持代码库整洁 -2. **统一认证中间件**:合并两套 Auth 实现到 `pkg/middleware/auth.go`,统一错误处理格式 -3. **简化错误结构**:删除 AppError 的 HTTPStatus 字段,避免字段冗余 -4. **组件注册解耦**:创建 `internal/bootstrap/` 包实现自动化组件初始化 - - 按模块拆分:`stores.go`、`services.go`、`handlers.go` - - main.go 简化为一行:`handlers, err := bootstrap.Bootstrap(deps)` -5. **数据权限自动化**:实现 GORM Callback 自动注入数据权限过滤 - - 基于 creator 字段自动过滤(普通用户只能看到自己和下级的数据) - - root 用户自动跳过过滤 - - 支持通过 `gorm.SkipDataPermission(ctx)` 手动绕过 - - 删除未使用的 `scopes.go` 手动 Scope 函数 - -**设计原则**: -- 保持 Go 惯用模式,避免 Java 风格过度抽象 -- 使用显式依赖注入,不引入复杂的 DI 框架 -- 每个文件保持 < 100 行,职责单一 -- 在关键扩展点添加 TODO 标记 - -**详细文档**: -- [变更提案](openspec/changes/refactor-framework-cleanup/proposal.md) -- [设计文档](openspec/changes/refactor-framework-cleanup/design.md) -- [任务清单](openspec/changes/refactor-framework-cleanup/tasks.md) - -## 开发规范 - -### 依赖注入 -通过 `Service` 和 `Store` 结构体统一管理依赖: -```go -// 初始化 -st := store.New(db) -svc := service.New(st, queueClient, logger) - -// 使用 -svc.SIM.Activate(...) -svc.Commission.Calculate(...) -``` - -### 事务处理 -```go -store.Transaction(ctx, func(tx *store.Store) error { - tx.SIM.UpdateStatus(...) - tx.Commission.Create(...) - return nil -}) -``` - -### 异步任务 -- 高频任务:批量状态同步、流量同步、实名检查 -- 业务任务:分佣计算、生命周期变更通知 -- 任务优先级:critical > default > low - -### 常量和 Redis Key 管理 -所有常量统一在 `pkg/constants/` 目录管理: -```go -// 业务常量 -constants.SIMStatusActive -constants.SIMStatusInactive - -// Redis Key 管理(统一使用 Key 生成函数) -constants.RedisSIMStatusKey(iccid) // sim:status:{iccid} -constants.RedisAgentCommissionKey(agentID) // agent:commission:{agentID} -constants.RedisTaskLockKey(taskName) // task:lock:{taskName} -constants.RedisAuthTokenKey(token) // auth:token:{token} - -// 使用示例 -key := constants.RedisSIMStatusKey("898600...") -rdb.Set(ctx, key, status, time.Hour) -``` - -## 文档 - -### 开发规范 - -- **[API 文档生成规范](docs/api-documentation-guide.md)**:路由注册规范、DTO 规范、OpenAPI 文档生成流程 -- **[数据库验证规范](AGENTS.md#数据库验证规范)**:使用 PostgreSQL MCP 验证接口逻辑和业务数据的正确性 -- **[开发规范总览](AGENTS.md)**:完整的项目开发规范(必读) - -### 功能指南 - -- **[快速开始指南](specs/001-fiber-middleware-integration/quickstart.md)**:详细设置和测试说明 -- **[限流指南](docs/rate-limiting.md)**:全面的限流配置和使用 -- **[错误处理使用指南](docs/003-error-handling/使用指南.md)**:错误码参考、Handler 使用、客户端处理、最佳实践 -- **[错误处理架构说明](docs/003-error-handling/架构说明.md)**:架构设计、性能优化、扩展性说明 - -### 架构设计 - -- **[实现计划](specs/001-fiber-middleware-integration/plan.md)**:设计决策和架构 -- **[数据模型](specs/001-fiber-middleware-integration/data-model.md)**:配置结构和 Redis 架构 - -## 技术栈 - -- **Go**:1.25.1 -- **Fiber**:v2.52.9(HTTP 框架) -- **Zap**:v1.27.0(结构化日志) -- **Lumberjack**:v2.2.1(日志轮转) -- **Viper**:v1.19.0(配置管理) -- **go-redis**:v9.7.0(Redis 客户端) -- **fsnotify**:v1.8.0(文件系统通知) -- **GORM**:(数据库 ORM) -- **sonic**:(高性能 JSON) -- **Asynq**:(异步任务队列) -- **Validator**:(参数验证) - -## 开发流程(Speckit) - -本项目使用 Speckit 规范化功能开发流程,确保代码质量、测试覆盖和架构一致性。 - -### 项目宪章 - -项目遵循 `.specify/memory/constitution.md` 定义的核心原则: - -1. **技术栈遵守**:严格使用 Fiber + GORM + Viper + Zap + Asynq,禁止原生调用快捷方式 -2. **代码质量标准**:遵循 Handler → Service → Store → Model 分层架构 -3. **测试标准**:70%+ 测试覆盖率,核心业务 90%+ -4. **用户体验一致性**:统一 JSON 响应格式、RESTful API、双语错误消息 -5. **性能要求**:API P95 < 200ms,P99 < 500ms,合理使用批量操作和异步任务 - -详细原则和规则请参阅宪章文档。 - -### Speckit 命令 - -```bash -# 创建功能规范 -/speckit.specify "功能描述" - -# 明确规范细节 -/speckit.clarify - -# 生成实现计划 -/speckit.plan - -# 生成任务列表 -/speckit.tasks - -# 执行实现 -/speckit.implement - -# 一致性分析 -/speckit.analyze - -# 生成自定义检查清单 -/speckit.checklist "检查项要求" - -# 更新项目宪章 -/speckit.constitution "宪章更新说明" -``` - -## 代码规范检查 - -运行代码规范检查: - -```bash -# 检查 Service 层错误处理 -bash scripts/check-service-errors.sh - -# 检查注释路径一致性 -bash scripts/check-comment-paths.sh - -# 运行所有检查 -bash scripts/check-all.sh -``` - -这些检查会在 CI/CD 流程中自动执行。 - -## 设计原则 - -- **简单实用**:不过度设计,够用就好 -- **直接实现**:避免不必要的接口抽象 -- **统一管理**:依赖集中初始化,避免参数传递 -- **职责分离**:API 和 Worker 独立部署,便于扩展 - -## 许可证 - -MIT License diff --git a/README_LOCAL_57152.md b/README_LOCAL_57152.md deleted file mode 100644 index adc535a..0000000 --- a/README_LOCAL_57152.md +++ /dev/null @@ -1,944 +0,0 @@ -# 君鸿卡管系统 - Fiber 中间件集成 - -基于 Go + Fiber 框架的 HTTP 服务,集成了认证、限流、结构化日志和嵌入式配置功能。 - -## 系统简介 - -物联网卡 + 号卡全生命周期管理平台,支持代理商体系和分佣结算。 - -**技术栈**:Fiber + GORM + Viper + Zap + Lumberjack.v2 + Validator + sonic JSON + Asynq + PostgreSQL - ---- - -## 核心业务说明 - -### 业务模式概览 - -君鸿卡管系统是一个物联网卡和号卡的全生命周期管理平台,支持三种客户类型和两种组织实体的多租户管理。 - -### 三种客户类型 - -| 客户类型 | 业务特点 | 典型场景 | 钱包归属 | -|---------|---------|---------|---------| -| **企业客户** | B端大客户,公对公支付 | 企业购买大量卡/设备用于业务运营 | ❌ 无钱包(后台直接分配套餐) | -| **个人客户** | C端用户,微信登录 | 个人购买单卡或设备(含1-4张卡) | ✅ 钱包归属**卡/设备**(支持转手) | -| **代理商** | 渠道分销商,层级管理 | 预存款采购套餐,按成本价+加价销售 | ✅ 钱包归属**店铺**(多账号共享) | - -### 个人客户业务流程 - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ 个人客户使用流程 │ -└──────────────────────────┬──────────────────────────────────────┘ - │ - ┌──────────▼──────────┐ - │ 1. 获得卡/设备 │ - │ - 单卡:ICCID │ - │ - 设备:设备号/IMEI │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 2. 微信扫码登录 │ - │ - 输入ICCID/IMEI │ - │ - 首次需绑定手机号 │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 3. 查看卡/设备信息 │ - │ - 流量使用情况 │ - │ - 套餐有效期 │ - │ - 钱包余额 │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 4. 钱包充值 │ - │ - 微信支付 │ - │ - 支付宝支付 │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 5. 购买套餐 │ - │ - 单卡套餐 │ - │ - 设备套餐(共享) │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 6. 卡/设备转手 │ - │ - 新用户扫码登录 │ - │ - 钱包余额跟着走 │ - └─────────────────────┘ -``` - -### 钱包归属设计 - -#### 为什么钱包绑定资源(卡/设备)而非用户? - -**问题场景**: -``` -个人客户 A 购买单卡 → 充值 100 元 → 使用 50 元 → 转手给个人客户 B -``` - -**如果钱包绑定用户**: -- ❌ 个人客户 B 登录后看不到余额(钱包还在 A 账号下) -- ❌ 需要手动转账或退款,体验极差 - -**钱包绑定资源(当前设计)**: -- ✅ 个人客户 B 登录后看到剩余 50 元(钱包跟着卡走) -- ✅ 无需任何额外操作,自然流转 - -#### 钱包归属规则 - -```go -// 钱包模型 -type Wallet struct { - ResourceType string // iot_card | device | shop - ResourceID uint // 资源ID - Balance int64 // 余额(分) - // ... -} - -// 场景1:个人客户的单卡钱包 -resource_type = "iot_card" -resource_id = 101 // 卡ID - -// 场景2:个人客户的设备钱包(3张卡共享) -resource_type = "device" -resource_id = 1001 // 设备ID - -// 场景3:代理商店铺钱包(多账号共享) -resource_type = "shop" -resource_id = 10 // 店铺ID -``` - -### 设备套餐业务规则 - -#### 设备级套餐购买 - -``` -设备绑定 3 张 IoT 卡 -├── 卡1:ICCID-001 -├── 卡2:ICCID-002 -└── 卡3:ICCID-003 - -用户购买套餐:399 元/年,每月 3000G 流量 -├── 套餐分配:3 张卡都获得该套餐 -├── 流量共享:3000G/月 在 3 张卡之间共享(总共 3000G) -├── 用户支付:399 元(一次性) -└── 代理分佣:100 元(只分一次,不按卡数倍增) -``` - -**关键点**: -- ✅ 套餐自动分配到设备的所有卡 -- ✅ 流量是**设备级别共享**(非每卡独立) -- ✅ 分佣**只计算一次**(防止重复分佣) - -### 标签系统多租户隔离 - -#### 三级隔离模型 - -| 标签类型 | 创建者 | 可见范围 | 名称唯一性 | 示例 | -|---------|-------|---------|-----------|------| -| 平台全局标签 | 平台管理员 | 所有用户 | 全局唯一 | "VIP"、"重要客户" | -| 企业标签 | 企业用户 | 仅该企业 | 企业内唯一 | 企业A的"测试标签" | -| 店铺标签 | 代理商 | 该店铺及下级 | 店铺内唯一 | 店铺10的"华东区" | - -#### 隔离规则 - -``` -企业 A 创建标签 "测试标签" -├── enterprise_id = 5, shop_id = NULL -├── 企业 A 的用户可见 -└── 企业 B 的用户不可见 - -企业 B 创建标签 "测试标签"(允许) -├── enterprise_id = 8, shop_id = NULL -├── 企业 B 的用户可见 -└── 与企业 A 的 "测试标签" 相互隔离 - -平台创建标签 "VIP" -├── enterprise_id = NULL, shop_id = NULL -└── 所有用户可见 -``` - -#### 数据权限自动过滤 - -```go -// GORM Callback 自动注入过滤条件 -switch userType { -case UserTypeAgent: - // 代理用户:只看到自己店铺及下级店铺的标签 - db.Where("shop_id IN (?) OR (enterprise_id IS NULL AND shop_id IS NULL)", subordinateShopIDs) - -case UserTypeEnterprise: - // 企业用户:只看到自己企业的标签 - db.Where("enterprise_id = ? OR (enterprise_id IS NULL AND shop_id IS NULL)", enterpriseID) - -default: - // 个人客户:只看到全局标签 - db.Where("enterprise_id IS NULL AND shop_id IS NULL") -} -``` - ---- - -## 核心功能 - -- **认证中间件**:基于 Redis 的 Token 认证 -- **限流中间件**:基于 IP 的限流,支持可配置的限制和存储后端 -- **结构化日志**:使用 Zap 的 JSON 日志和自动日志轮转 -- **嵌入式配置**:配置嵌入二进制文件,通过环境变量覆盖,简化 Docker 部署 -- **请求 ID 追踪**:UUID 跨日志的请求追踪 -- **Panic 恢复**:优雅的 panic 处理和堆栈跟踪日志 -- **统一错误处理**:全局 ErrorHandler 统一处理所有 API 错误,返回一致的 JSON 格式(包含错误码、消息、时间戳);Panic 自动恢复防止服务崩溃;错误分类处理(客户端 4xx、服务端 5xx)和日志级别控制;敏感信息自动脱敏保护 -- **数据持久化**:GORM + PostgreSQL 集成,提供完整的 CRUD 操作、事务支持和数据库迁移能力 -- **异步任务处理**:Asynq 任务队列集成,支持任务提交、后台执行、自动重试和幂等性保障,实现邮件发送、数据同步等异步任务 -- **RBAC 权限系统**:完整的基于角色的访问控制,支持账号、角色、权限的多对多关联和层级关系;基于店铺层级的自动数据权限过滤,实现多租户数据隔离;使用 PostgreSQL WITH RECURSIVE 查询下级店铺并通过 Redis 缓存优化性能;完整的权限检查功能支持路由级别的细粒度权限控制,支持平台过滤(web/h5/all)和超级管理员自动跳过(详见 [功能总结](docs/004-rbac-data-permission/功能总结.md)、[使用指南](docs/004-rbac-data-permission/使用指南.md) 和 [权限检查使用指南](docs/permission-check-usage.md)) -- **商户管理**:完整的商户(Shop)和商户账号管理功能,支持商户创建时自动创建初始坐席账号、删除商户时批量禁用关联账号、账号密码重置等功能(详见 [使用指南](docs/shop-management/使用指南.md) 和 [API 文档](docs/shop-management/API文档.md)) -- **B 端认证系统**:完整的后台和 H5 认证功能,支持基于 Redis 的 Token 管理和双令牌机制(Access Token 24h + Refresh Token 7天);包含登录、登出、Token 刷新、用户信息查询和密码修改功能;通过用户类型隔离确保后台(SuperAdmin、Platform、Agent)和 H5(Agent、Enterprise)的访问控制;**登录响应包含菜单树和按钮权限**(menus/buttons),前端无需二次处理直接渲染侧边栏和控制按钮显示;详见 [API 文档](docs/api/auth.md)、[使用指南](docs/auth-usage-guide.md)、[架构说明](docs/auth-architecture.md) 和 [菜单权限使用指南](docs/login-menu-button-response/使用指南.md) -- **生命周期管理**:物联网卡/号卡的开卡、激活、停机、复机、销户 -- **代理商体系**:层级管理和分佣结算 -- **批量同步**:卡状态、实名状态、流量使用情况 -- **分佣验证指引**:对代理分佣的冻结、解冻、提现校验流程进行了结构化说明与流程图,详见 [分佣逻辑正确与否验证](docs/优化说明/分佣逻辑正确与否验证.md) -- **对象存储**:S3 兼容的对象存储服务集成(联通云 OSS),支持预签名 URL 上传、文件下载、临时文件处理;用于 ICCID 批量导入、数据导出等场景;详见 [使用指南](docs/object-storage/使用指南.md) 和 [前端接入指南](docs/object-storage/前端接入指南.md) -- **Gateway 客户端**:第三方 Gateway API 的 Go 封装,提供流量卡和设备管理的统一接口;内置 AES-128-ECB 加密、MD5 签名验证、HTTP 连接池管理;支持流量卡状态查询、停复机、实名认证、流量查询等 7 个流量卡接口和设备信息查询、卡槽管理、限速设置、WiFi 配置、切卡、重启、恢复出厂等 7 个设备管理接口;测试覆盖率 88.8%;详见 [使用指南](docs/gateway-client-usage.md) 和 [API 参考](docs/gateway-api-reference.md) - -## 用户体系设计 - -系统支持四种用户类型和两种组织实体,实现分层级的多租户管理: - -### 用户类型 - -1. **平台用户**:平台管理员,具有最高权限,可分配多个角色 -2. **代理账号**:店铺(代理商)员工账号,归属于特定店铺,权限相同 -3. **企业账号**:企业客户账号,归属于特定企业,一企业一账号 -4. **个人客户**:个人用户,独立表存储,支持微信绑定,不参与 RBAC 体系 - -### 组织实体 - -1. **店铺(Shop)**:代理商组织实体,支持最多 7 级层级关系 - - 一级代理直接归属于平台 - - 下级代理归属于上级店铺(通过 `parent_id` 字段) - - 一个店铺可以有多个账号(代理员工) - -2. **企业(Enterprise)**:企业客户组织实体 - - 可归属于店铺(通过 `owner_shop_id` 字段) - - 可归属于平台(`owner_shop_id = NULL`) - - 一个企业目前只有一个账号 - -### 核心设计决策 - -- **层级关系在店铺之间维护**:代理上下级关系通过 `Shop.parent_id` 维护,而非账号之间 -- **数据权限基于店铺归属**:数据过滤使用 `shop_id IN (当前店铺及下级店铺)` -- **递归查询+Redis缓存**:使用 `GetSubordinateShopIDs()` 递归查询下级店铺ID,结果缓存30分钟 -- **GORM 自动过滤**:通过 GORM Callback 自动应用数据权限过滤,无需在每个查询手动添加条件 -- **禁止外键约束**:遵循项目原则,表之间通过ID字段关联,关联查询在代码层显式执行 -- **GORM字段显式命名**:所有模型字段必须显式指定 `gorm:"column:field_name"` 标签 - -### 表结构 - -``` -tb_shop (店铺表) -├── id, created_at, updated_at, deleted_at -├── creator, updater -├── shop_name, shop_code -├── parent_id (上级店铺ID) -├── level (层级 1-7) -├── contact_name, contact_phone -├── province, city, district, address -└── status - -tb_enterprise (企业表) -├── id, created_at, updated_at, deleted_at -├── creator, updater -├── enterprise_name, enterprise_code -├── owner_shop_id (归属店铺ID) -├── legal_person, contact_name, contact_phone -├── business_license -├── province, city, district, address -└── status - -tb_personal_customer (个人客户表) -├── id, created_at, updated_at, deleted_at -├── phone (唯一标识) -├── nickname, avatar_url -├── wx_open_id, wx_union_id -└── status - -tb_account (账号表 - 已修改) -├── id, created_at, updated_at, deleted_at -├── creator, updater -├── username, phone, password -├── user_type (1=超级管理员 2=平台用户 3=代理账号 4=企业账号) -├── shop_id (代理账号必填) -├── enterprise_id (企业账号必填) ← 新增 -└── status -``` - -详细设计文档参见: -- [设计文档](openspec/changes/add-user-organization-model/design.md) -- [提案文档](openspec/changes/add-user-organization-model/proposal.md) - -## 数据权限模型 - -系统采用基于用户类型的自动数据权限过滤策略,通过 GORM Callback 自动应用,无需在每个查询中手动添加过滤条件。 - -### 过滤规则 - -| 用户类型 | 过滤策略 | 示例 | -|---------|---------|------| -| 超级管理员(Super Admin) | 跳过过滤,查看所有数据 | - | -| 平台用户(Platform) | 跳过过滤,查看所有数据 | - | -| 代理账号(Agent) | 基于店铺层级过滤 | `WHERE shop_id IN (当前店铺及下级店铺)` | -| 企业账号(Enterprise) | 基于企业归属过滤 | `WHERE enterprise_id = 当前企业ID` | -| 个人客户(Personal Customer) | 基于创建者过滤 | `WHERE creator = 当前用户ID` | - -### 工作机制 - -1. **认证中间件**设置完整用户上下文(`UserContextInfo`)到 `context` 中 -2. **GORM Callback**在每次查询前自动注入过滤条件 -3. **递归查询 + 缓存**:代理用户的下级店铺 ID 通过 `GetSubordinateShopIDs()` 递归查询,结果缓存 30 分钟 -4. **跳过过滤**:特殊场景(如统计、后台任务)可使用 `SkipDataPermission(ctx)` 绕过过滤 - -### 使用示例 - -```go -// 1. 认证后 context 已自动包含用户信息 -ctx := c.UserContext() - -// 2. 所有 Store 层查询自动应用数据权限过滤 -orders, err := orderStore.List(ctx) // 自动过滤为当前用户可见的订单 - -// 3. 需要查询所有数据时,显式跳过过滤 -ctx = gorm.SkipDataPermission(ctx) -allOrders, err := orderStore.List(ctx) // 查询所有订单(仅限特殊场景) -``` - -详细说明参见: -- [数据权限清理总结](docs/remove-legacy-rbac-cleanup/清理总结.md) -- [RBAC 权限使用指南](docs/004-rbac-data-permission/使用指南.md) - -## 快速开始 - -```bash -# 安装依赖 -go mod tidy - -# 启动 Redis(认证功能必需) -redis-server - -# 运行 API 服务 -go run cmd/api/main.go - -# 运行 Worker 服务(可选) -go run cmd/worker/main.go -``` - -### 默认超级管理员账号 - -系统首次启动时会自动创建默认超级管理员账号,无需手动执行 SQL 或脚本。 - -**默认账号信息**: -- 用户名:`admin` -- 密码:`Admin@123456` -- 手机号:`13800000000` - -**自定义配置**: - -通过环境变量自定义默认管理员信息: - -```bash -export JUNHONG_DEFAULT_ADMIN_USERNAME="自定义用户名" -export JUNHONG_DEFAULT_ADMIN_PASSWORD="自定义密码" -export JUNHONG_DEFAULT_ADMIN_PHONE="自定义手机号" -``` - -**注意事项**: -- 系统只在数据库无超级管理员账号时才创建 -- 如果已存在超级管理员,启动时会跳过创建 -- 建议首次登录后立即修改默认密码 -- 初始化日志记录在 `logs/app.log` 中 - -详细设置和测试说明请参阅 [快速开始指南](specs/001-fiber-middleware-integration/quickstart.md)。 - -## 项目结构 - -``` -junhong_cmp_fiber/ -│ -├── cmd/ # 应用程序入口 -│ ├── api/ # HTTP API 服务 -│ │ └── main.go # API 服务主入口 -│ └── worker/ # Asynq 异步任务 Worker -│ └── main.go # Worker 服务主入口 -│ -├── internal/ # 私有业务代码 -│ ├── handler/ # HTTP 处理层 -│ │ ├── user.go # 用户处理器 -│ │ └── health.go # 健康检查处理器 -│ ├── middleware/ # Fiber 中间件实现 -│ │ ├── auth.go # 认证中间件(keyauth) -│ │ ├── ratelimit.go # 限流中间件 -│ │ └── recover.go # Panic 恢复中间件 -│ ├── service/ # 业务逻辑层(核心业务) -│ ├── store/ # 数据访问层 -│ │ └── postgres/ # PostgreSQL 实现 -│ ├── model/ # 数据模型(实体、DTO) -│ ├── task/ # Asynq 任务定义和处理 -│ ├── gateway/ # Gateway 服务 HTTP 客户端 -│ └── router/ # 路由注册 -│ -├── pkg/ # 公共工具库 -│ ├── config/ # 配置管理 -│ │ ├── config.go # 配置结构定义 -│ │ ├── loader.go # 配置加载(嵌入配置 + 环境变量覆盖) -│ │ ├── embedded.go # go:embed 嵌入配置加载 -│ │ └── defaults/config.yaml # 默认配置(嵌入二进制) -│ ├── logger/ # 日志基础设施 -│ │ ├── logger.go # Zap 日志初始化 -│ │ └── middleware.go # Fiber 日志中间件适配器 -│ ├── response/ # 统一响应处理 -│ │ └── response.go # 响应结构和辅助函数 -│ ├── errors/ # 错误码和类型 -│ │ ├── codes.go # 错误码常量 -│ │ └── errors.go # 自定义错误类型 -│ ├── constants/ # 业务常量 -│ │ ├── constants.go # 上下文键、请求头名称 -│ │ └── redis.go # Redis Key 生成器 -│ ├── validator/ # 验证服务 -│ │ └── token.go # Token 验证(Redis) -│ ├── database/ # 数据库初始化 -│ │ └── redis.go # Redis 客户端初始化 -│ └── queue/ # 队列封装(Asynq) -│ -├── tests/ -│ └── integration/ # 集成测试 -│ ├── auth_test.go # 认证测试 -│ └── ratelimit_test.go # 限流测试 -│ -├── migrations/ # 数据库迁移文件 -├── scripts/ # 脚本工具 -├── docs/ # 文档 -│ └── rate-limiting.md # 限流指南 -└── logs/ # 应用日志(自动创建) - ├── app.log # 应用日志(JSON) - └── access.log # 访问日志(JSON) -``` - -## 中间件执行顺序 - -中间件按注册顺序执行。请求按顺序流经每个中间件: - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ HTTP 请求 │ -└────────────────────────────────┬────────────────────────────────┘ - │ - ┌────────────▼────────────┐ - │ 1. Recover 中间件 │ - │ (panic 恢复) │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 2. RequestID 中间件 │ - │ (生成 UUID) │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 3. Logger 中间件 │ - │ (访问日志) │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 4. 认证中间件 │ - │ (按路由组配置) │ ─── 模块化路由注册 - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 5. RateLimiter 中间件 │ - │ (限流) │ ─── 可选 (config: enable_rate_limiter) - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 6. 路由处理器 │ - │ (业务逻辑) │ - └────────────┬────────────┘ - │ -┌────────────────────────────────▼────────────────────────────────┐ -│ HTTP 响应 │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### 中间件详情 - -#### 1. Recover 中间件(fiber.Recover) -- **用途**:捕获 panic 并防止服务器崩溃 -- **行为**: - - 捕获下游中间件/处理器中的任何 panic - - 将 panic 及堆栈跟踪记录到 `logs/app.log` - - 返回 HTTP 500 统一错误响应 - - 服务器继续处理后续请求 -- **始终激活**:是 - -#### 2. RequestID 中间件(自定义) -- **用途**:生成请求追踪的唯一标识符 -- **行为**: - - 为每个请求生成 UUID v4 - - 存储在上下文中:`c.Locals(constants.ContextKeyRequestID)` - - 添加 `X-Request-ID` 响应头 - - 用于所有日志条目以进行关联 -- **始终激活**:是 - -#### 3. Logger 中间件(自定义 Fiber 适配器) -- **用途**:记录所有 HTTP 请求和响应 -- **行为**: - - 记录请求:方法、路径、IP、User-Agent、请求 ID - - 记录响应:状态码、耗时、用户 ID(如果已认证) - - 写入 `logs/access.log`(JSON 格式) - - 结构化字段便于解析和分析 -- **始终激活**:是 -- **日志格式**:包含字段的 JSON:timestamp、level、method、path、status、duration_ms、request_id、ip、user_agent、user_id - -#### 4. 认证中间件(pkg/middleware/auth.go 和 internal/middleware/) -- **用途**:使用 Token 验证对请求进行认证 -- **行为**: - - 从 `Authorization: Bearer {token}` 请求头提取 token - - 通过 TokenValidator 函数验证 token(支持 JWT 和 Redis Token) - - 如果缺失/无效 token 返回 401 - - 成功时将用户信息存储在上下文中(UserID、UserType、ShopID、EnterpriseID) -- **实现方式**:模块化路由注册(无全局配置) - - `/api/admin/*`:后台认证(SuperAdmin、Platform、Agent) - - `/api/h5/*`:H5 认证(Agent、Enterprise) - - `/api/personal/*`:个人客户认证(JWT) -- **跳过路由**:各路由组可自行配置跳过路径(如 `/api/admin/login`) -- **错误码**: - - 1001:缺失 token - - 1002:无效或过期 token - - 1003:权限不足 - -#### 5. RateLimiter 中间件(internal/middleware/ratelimit.go) -- **用途**:通过限制请求速率保护 API 免受滥用 -- **行为**: - - 按客户端 IP 地址追踪请求 - - 执行限制:`expiration` 时间窗口内 `max` 个请求 - - 如果超过限制返回 429 - - 每个 IP 地址独立计数器 -- **配置**:`middleware.enable_rate_limiter`(默认:false) -- **存储选项**: - - `memory`:内存存储(单服务器,重启后重置) - - `redis`:基于 Redis(分布式,持久化) -- **错误码**:1003(请求过于频繁) - -#### 6. 路由处理器 -- **用途**:执行端点的业务逻辑 -- **可用上下文数据**: - - 请求 ID:`c.Locals(constants.ContextKeyRequestID)` - - 用户 ID:`c.Locals(constants.ContextKeyUserID)`(如果已认证) - - 标准 Fiber 上下文方法:`c.Params()`、`c.Query()`、`c.Body()` 等 - -### 中间件注册(cmd/api/main.go) - -```go -// 核心中间件(始终激活) -app.Use(recover.New()) -app.Use(addRequestID()) -app.Use(loggerMiddleware()) - -// 模块化路由注册(认证中间件按路由组配置) -routes.RegisterRoutes(app, handlers, middlewares) - -// 可选:限流中间件 -if config.GetConfig().Middleware.EnableRateLimiter { - var storage fiber.Storage = nil - if config.GetConfig().Middleware.RateLimiter.Storage == "redis" { - storage = redisStorage // 使用 Redis 存储 - } - v1 := app.Group("/api/v1") - v1.Use(middleware.RateLimiter( - config.GetConfig().Middleware.RateLimiter.Max, - config.GetConfig().Middleware.RateLimiter.Expiration, - storage, - )) -} -``` - -### 请求流程示例 - -**场景**:已启用所有中间件的 `/api/v1/users` 认证请求 - -``` -1. 请求到达:GET /api/v1/users - 请求头:token: abc123 - -2. Recover 中间件:准备捕获 panic - → 传递到下一个中间件 - -3. RequestID 中间件:生成 UUID - → 设置上下文:request_id = "550e8400-e29b-41d4-a716-446655440000" - → 传递到下一个中间件 - -4. Logger 中间件:记录请求开始 - → 日志:{"method":"GET", "path":"/api/v1/users", "request_id":"550e8400-..."} - → 传递到下一个中间件 - -5. KeyAuth 中间件:验证 token - → 检查 Redis:GET "auth:token:abc123" → "user-789" - → 设置上下文:user_id = "user-789" - → 传递到下一个中间件 - -6. RateLimiter 中间件:检查限流 - → 检查计数器:GET "rate_limit:127.0.0.1" → "5"(低于限制 100) - → 增加计数器:INCR "rate_limit:127.0.0.1" → "6" - → 传递到下一个中间件 - -7. 处理器执行:listUsersHandler() - → 从上下文获取 user_id:"user-789" - → 从数据库获取用户 - → 返回响应:{"code":0, "data":[...], "msg":"success"} - -8. Logger 中间件:记录响应 - → 日志:{"status":200, "duration_ms":23.45, "user_id":"user-789"} - -9. RequestID 中间件:添加响应头 - → 响应头:X-Request-ID: 550e8400-e29b-41d4-a716-446655440000 - -10. 响应发送给客户端 -``` - -### 中间件中的错误处理 - -如果任何中间件返回错误,链停止并发送错误响应: - -``` -请求 → Recover → RequestID → Logger → [KeyAuth 失败] ✗ - ↓ - 返回 401 - (不执行 RateLimiter 和 Handler) -``` - -示例:缺失 token -``` -KeyAuth:Token 缺失 -→ 返回 response.Error(c, 401, 1001, "缺失认证令牌") -→ Logger 记录:{"status":401, "duration_ms":1.23} -→ RequestID 添加响应头 -→ 发送响应 -``` - -## 配置 - -### 嵌入式配置机制 - -系统使用 go:embed 将默认配置嵌入二进制文件,通过环境变量进行覆盖: - -- **默认配置**:`pkg/config/defaults/config.yaml`(编译时嵌入) -- **环境变量前缀**:`JUNHONG_` -- **格式转换**:配置路径中的 `.` 替换为 `_` - -**环境变量覆盖示例**: - -| 配置项 | 环境变量 | -|-------|---------| -| `database.host` | `JUNHONG_DATABASE_HOST` | -| `redis.address` | `JUNHONG_REDIS_ADDRESS` | -| `jwt.secret_key` | `JUNHONG_JWT_SECRET_KEY` | -| `logging.level` | `JUNHONG_LOGGING_LEVEL` | - -### 必填配置 - -以下配置项必须通过环境变量设置(无默认值或需要覆盖): - -```bash -# 数据库配置(必填) -export JUNHONG_DATABASE_HOST=localhost -export JUNHONG_DATABASE_PORT=5432 -export JUNHONG_DATABASE_USER=postgres -export JUNHONG_DATABASE_PASSWORD=your_password -export JUNHONG_DATABASE_DBNAME=junhong_cmp - -# Redis 配置(必填) -export JUNHONG_REDIS_ADDRESS=localhost - -# JWT 密钥(必填,生产环境必须修改) -export JUNHONG_JWT_SECRET_KEY=your-secret-key-change-in-production -``` - -### Docker 部署 - -Docker 部署使用纯环境变量配置,无需挂载配置文件: - -```yaml -# docker-compose.prod.yml 示例 -services: - api: - image: registry.boss160.cn/junhong/cmp-fiber-api:latest - environment: - - JUNHONG_DATABASE_HOST=db-host - - JUNHONG_DATABASE_PORT=5432 - - JUNHONG_DATABASE_USER=postgres - - JUNHONG_DATABASE_PASSWORD=secret - - JUNHONG_DATABASE_DBNAME=junhong_cmp - - JUNHONG_REDIS_ADDRESS=redis - - JUNHONG_JWT_SECRET_KEY=production-secret - volumes: - - ./logs:/app/logs # 仅挂载日志目录 -``` - -### 完整环境变量列表 - -详见 [环境变量配置文档](docs/environment-variables.md) - -## 测试 - -### 运行所有测试 - -```bash -# 运行所有单元和集成测试 -go test ./... - -# 带覆盖率运行 -go test -cover ./... - -# 详细输出运行 -go test -v ./... -``` - -### 运行特定测试套件 - -```bash -# 仅单元测试 -go test ./pkg/... - -# 仅集成测试 -go test ./tests/integration/... - -# 特定测试 -go test -v ./internal/middleware -run TestKeyAuth -``` - -### 集成测试 - -集成测试需要 Redis 运行: - -```bash -# 启动 Redis -redis-server - -# 运行集成测试 -go test -v ./tests/integration/... -``` - -如果 Redis 不可用,测试自动跳过。 - -### 测试连接管理 - -测试使用全局单例连接池,性能提升 6-7 倍。详见 [测试连接管理规范](docs/testing/test-connection-guide.md)。 - -**标准写法**: -```go -func TestXxx(t *testing.T) { - tx := testutils.NewTestTransaction(t) // 自动回滚的事务 - rdb := testutils.GetTestRedis(t) // 全局 Redis 连接 - testutils.CleanTestRedisKeys(t, rdb) // 自动清理 Redis 键 - - store := postgres.NewXxxStore(tx, rdb) - // 测试代码... -} -``` - -## 架构设计 - -### 分层架构 -``` -Handler (HTTP) → Service (业务逻辑) → Store (数据访问) → Model (数据模型) -``` - -### 双服务架构 -- **API 服务**:处理 HTTP 请求,快速响应 -- **Worker 服务**:处理异步任务(批量同步、分佣计算等),独立部署 - -### 核心模块 -- **Service 层**:统一管理所有业务逻辑,支持跨模块调用 -- **Store 层**:统一管理所有数据访问,支持事务 -- **Task 层**:Asynq 任务处理器,支持定时任务和事件触发 - -## 框架优化历史 - -### 005-framework-cleanup-refactor(2025-11) - -**背景**:清理技术债务,统一框架设计 - -**主要变更**: -1. **清理示例代码**:删除所有 user/order 示例业务代码,保持代码库整洁 -2. **统一认证中间件**:合并两套 Auth 实现到 `pkg/middleware/auth.go`,统一错误处理格式 -3. **简化错误结构**:删除 AppError 的 HTTPStatus 字段,避免字段冗余 -4. **组件注册解耦**:创建 `internal/bootstrap/` 包实现自动化组件初始化 - - 按模块拆分:`stores.go`、`services.go`、`handlers.go` - - main.go 简化为一行:`handlers, err := bootstrap.Bootstrap(deps)` -5. **数据权限自动化**:实现 GORM Callback 自动注入数据权限过滤 - - 基于 creator 字段自动过滤(普通用户只能看到自己和下级的数据) - - root 用户自动跳过过滤 - - 支持通过 `gorm.SkipDataPermission(ctx)` 手动绕过 - - 删除未使用的 `scopes.go` 手动 Scope 函数 - -**设计原则**: -- 保持 Go 惯用模式,避免 Java 风格过度抽象 -- 使用显式依赖注入,不引入复杂的 DI 框架 -- 每个文件保持 < 100 行,职责单一 -- 在关键扩展点添加 TODO 标记 - -**详细文档**: -- [变更提案](openspec/changes/refactor-framework-cleanup/proposal.md) -- [设计文档](openspec/changes/refactor-framework-cleanup/design.md) -- [任务清单](openspec/changes/refactor-framework-cleanup/tasks.md) - -## 开发规范 - -### 依赖注入 -通过 `Service` 和 `Store` 结构体统一管理依赖: -```go -// 初始化 -st := store.New(db) -svc := service.New(st, queueClient, logger) - -// 使用 -svc.SIM.Activate(...) -svc.Commission.Calculate(...) -``` - -### 事务处理 -```go -store.Transaction(ctx, func(tx *store.Store) error { - tx.SIM.UpdateStatus(...) - tx.Commission.Create(...) - return nil -}) -``` - -### 异步任务 -- 高频任务:批量状态同步、流量同步、实名检查 -- 业务任务:分佣计算、生命周期变更通知 -- 任务优先级:critical > default > low - -### 常量和 Redis Key 管理 -所有常量统一在 `pkg/constants/` 目录管理: -```go -// 业务常量 -constants.SIMStatusActive -constants.SIMStatusInactive - -// Redis Key 管理(统一使用 Key 生成函数) -constants.RedisSIMStatusKey(iccid) // sim:status:{iccid} -constants.RedisAgentCommissionKey(agentID) // agent:commission:{agentID} -constants.RedisTaskLockKey(taskName) // task:lock:{taskName} -constants.RedisAuthTokenKey(token) // auth:token:{token} - -// 使用示例 -key := constants.RedisSIMStatusKey("898600...") -rdb.Set(ctx, key, status, time.Hour) -``` - -## 文档 - -### 开发规范 - -- **[API 文档生成规范](docs/api-documentation-guide.md)**:路由注册规范、DTO 规范、OpenAPI 文档生成流程 -- **[数据库验证规范](AGENTS.md#数据库验证规范)**:使用 PostgreSQL MCP 验证接口逻辑和业务数据的正确性 -- **[开发规范总览](AGENTS.md)**:完整的项目开发规范(必读) - -### 功能指南 - -- **[快速开始指南](specs/001-fiber-middleware-integration/quickstart.md)**:详细设置和测试说明 -- **[限流指南](docs/rate-limiting.md)**:全面的限流配置和使用 -- **[错误处理使用指南](docs/003-error-handling/使用指南.md)**:错误码参考、Handler 使用、客户端处理、最佳实践 -- **[错误处理架构说明](docs/003-error-handling/架构说明.md)**:架构设计、性能优化、扩展性说明 - -### 架构设计 - -- **[实现计划](specs/001-fiber-middleware-integration/plan.md)**:设计决策和架构 -- **[数据模型](specs/001-fiber-middleware-integration/data-model.md)**:配置结构和 Redis 架构 - -## 技术栈 - -- **Go**:1.25.1 -- **Fiber**:v2.52.9(HTTP 框架) -- **Zap**:v1.27.0(结构化日志) -- **Lumberjack**:v2.2.1(日志轮转) -- **Viper**:v1.19.0(配置管理) -- **go-redis**:v9.7.0(Redis 客户端) -- **fsnotify**:v1.8.0(文件系统通知) -- **GORM**:(数据库 ORM) -- **sonic**:(高性能 JSON) -- **Asynq**:(异步任务队列) -- **Validator**:(参数验证) - -## 开发流程(Speckit) - -本项目使用 Speckit 规范化功能开发流程,确保代码质量、测试覆盖和架构一致性。 - -### 项目宪章 - -项目遵循 `.specify/memory/constitution.md` 定义的核心原则: - -1. **技术栈遵守**:严格使用 Fiber + GORM + Viper + Zap + Asynq,禁止原生调用快捷方式 -2. **代码质量标准**:遵循 Handler → Service → Store → Model 分层架构 -3. **测试标准**:70%+ 测试覆盖率,核心业务 90%+ -4. **用户体验一致性**:统一 JSON 响应格式、RESTful API、双语错误消息 -5. **性能要求**:API P95 < 200ms,P99 < 500ms,合理使用批量操作和异步任务 - -详细原则和规则请参阅宪章文档。 - -### Speckit 命令 - -```bash -# 创建功能规范 -/speckit.specify "功能描述" - -# 明确规范细节 -/speckit.clarify - -# 生成实现计划 -/speckit.plan - -# 生成任务列表 -/speckit.tasks - -# 执行实现 -/speckit.implement - -# 一致性分析 -/speckit.analyze - -# 生成自定义检查清单 -/speckit.checklist "检查项要求" - -# 更新项目宪章 -/speckit.constitution "宪章更新说明" -``` - -## 代码规范检查 - -运行代码规范检查: - -```bash -# 检查 Service 层错误处理 -bash scripts/check-service-errors.sh - -# 检查注释路径一致性 -bash scripts/check-comment-paths.sh - -# 运行所有检查 -bash scripts/check-all.sh -``` - -这些检查会在 CI/CD 流程中自动执行。 - -## 设计原则 - -- **简单实用**:不过度设计,够用就好 -- **直接实现**:避免不必要的接口抽象 -- **统一管理**:依赖集中初始化,避免参数传递 -- **职责分离**:API 和 Worker 独立部署,便于扩展 - -## 许可证 - -MIT License diff --git a/README_REMOTE_57152.md b/README_REMOTE_57152.md deleted file mode 100644 index 73fc2b6..0000000 --- a/README_REMOTE_57152.md +++ /dev/null @@ -1,945 +0,0 @@ -# 君鸿卡管系统 - Fiber 中间件集成 - -基于 Go + Fiber 框架的 HTTP 服务,集成了认证、限流、结构化日志和嵌入式配置功能。 - -## 系统简介 - -物联网卡 + 号卡全生命周期管理平台,支持代理商体系和分佣结算。 - -**技术栈**:Fiber + GORM + Viper + Zap + Lumberjack.v2 + Validator + sonic JSON + Asynq + PostgreSQL - ---- - -## 核心业务说明 - -### 业务模式概览 - -君鸿卡管系统是一个物联网卡和号卡的全生命周期管理平台,支持三种客户类型和两种组织实体的多租户管理。 - -### 三种客户类型 - -| 客户类型 | 业务特点 | 典型场景 | 钱包归属 | -|---------|---------|---------|---------| -| **企业客户** | B端大客户,公对公支付 | 企业购买大量卡/设备用于业务运营 | ❌ 无钱包(后台直接分配套餐) | -| **个人客户** | C端用户,微信登录 | 个人购买单卡或设备(含1-4张卡) | ✅ 钱包归属**卡/设备**(支持转手) | -| **代理商** | 渠道分销商,层级管理 | 预存款采购套餐,按成本价+加价销售 | ✅ 钱包归属**店铺**(多账号共享) | - -### 个人客户业务流程 - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ 个人客户使用流程 │ -└──────────────────────────┬──────────────────────────────────────┘ - │ - ┌──────────▼──────────┐ - │ 1. 获得卡/设备 │ - │ - 单卡:ICCID │ - │ - 设备:设备号/IMEI │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 2. 微信扫码登录 │ - │ - 输入ICCID/IMEI │ - │ - 首次需绑定手机号 │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 3. 查看卡/设备信息 │ - │ - 流量使用情况 │ - │ - 套餐有效期 │ - │ - 钱包余额 │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 4. 钱包充值 │ - │ - 微信支付 │ - │ - 支付宝支付 │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 5. 购买套餐 │ - │ - 单卡套餐 │ - │ - 设备套餐(共享) │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ 6. 卡/设备转手 │ - │ - 新用户扫码登录 │ - │ - 钱包余额跟着走 │ - └─────────────────────┘ -``` - -### 钱包归属设计 - -#### 为什么钱包绑定资源(卡/设备)而非用户? - -**问题场景**: -``` -个人客户 A 购买单卡 → 充值 100 元 → 使用 50 元 → 转手给个人客户 B -``` - -**如果钱包绑定用户**: -- ❌ 个人客户 B 登录后看不到余额(钱包还在 A 账号下) -- ❌ 需要手动转账或退款,体验极差 - -**钱包绑定资源(当前设计)**: -- ✅ 个人客户 B 登录后看到剩余 50 元(钱包跟着卡走) -- ✅ 无需任何额外操作,自然流转 - -#### 钱包归属规则 - -```go -// 钱包模型 -type Wallet struct { - ResourceType string // iot_card | device | shop - ResourceID uint // 资源ID - Balance int64 // 余额(分) - // ... -} - -// 场景1:个人客户的单卡钱包 -resource_type = "iot_card" -resource_id = 101 // 卡ID - -// 场景2:个人客户的设备钱包(3张卡共享) -resource_type = "device" -resource_id = 1001 // 设备ID - -// 场景3:代理商店铺钱包(多账号共享) -resource_type = "shop" -resource_id = 10 // 店铺ID -``` - -### 设备套餐业务规则 - -#### 设备级套餐购买 - -``` -设备绑定 3 张 IoT 卡 -├── 卡1:ICCID-001 -├── 卡2:ICCID-002 -└── 卡3:ICCID-003 - -用户购买套餐:399 元/年,每月 3000G 流量 -├── 套餐分配:3 张卡都获得该套餐 -├── 流量共享:3000G/月 在 3 张卡之间共享(总共 3000G) -├── 用户支付:399 元(一次性) -└── 代理分佣:100 元(只分一次,不按卡数倍增) -``` - -**关键点**: -- ✅ 套餐自动分配到设备的所有卡 -- ✅ 流量是**设备级别共享**(非每卡独立) -- ✅ 分佣**只计算一次**(防止重复分佣) - -### 标签系统多租户隔离 - -#### 三级隔离模型 - -| 标签类型 | 创建者 | 可见范围 | 名称唯一性 | 示例 | -|---------|-------|---------|-----------|------| -| 平台全局标签 | 平台管理员 | 所有用户 | 全局唯一 | "VIP"、"重要客户" | -| 企业标签 | 企业用户 | 仅该企业 | 企业内唯一 | 企业A的"测试标签" | -| 店铺标签 | 代理商 | 该店铺及下级 | 店铺内唯一 | 店铺10的"华东区" | - -#### 隔离规则 - -``` -企业 A 创建标签 "测试标签" -├── enterprise_id = 5, shop_id = NULL -├── 企业 A 的用户可见 -└── 企业 B 的用户不可见 - -企业 B 创建标签 "测试标签"(允许) -├── enterprise_id = 8, shop_id = NULL -├── 企业 B 的用户可见 -└── 与企业 A 的 "测试标签" 相互隔离 - -平台创建标签 "VIP" -├── enterprise_id = NULL, shop_id = NULL -└── 所有用户可见 -``` - -#### 数据权限自动过滤 - -```go -// GORM Callback 自动注入过滤条件 -switch userType { -case UserTypeAgent: - // 代理用户:只看到自己店铺及下级店铺的标签 - db.Where("shop_id IN (?) OR (enterprise_id IS NULL AND shop_id IS NULL)", subordinateShopIDs) - -case UserTypeEnterprise: - // 企业用户:只看到自己企业的标签 - db.Where("enterprise_id = ? OR (enterprise_id IS NULL AND shop_id IS NULL)", enterpriseID) - -default: - // 个人客户:只看到全局标签 - db.Where("enterprise_id IS NULL AND shop_id IS NULL") -} -``` - ---- - -## 核心功能 - -- **认证中间件**:基于 Redis 的 Token 认证 -- **限流中间件**:基于 IP 的限流,支持可配置的限制和存储后端 -- **结构化日志**:使用 Zap 的 JSON 日志和自动日志轮转 -- **嵌入式配置**:配置嵌入二进制文件,通过环境变量覆盖,简化 Docker 部署 -- **请求 ID 追踪**:UUID 跨日志的请求追踪 -- **Panic 恢复**:优雅的 panic 处理和堆栈跟踪日志 -- **统一错误处理**:全局 ErrorHandler 统一处理所有 API 错误,返回一致的 JSON 格式(包含错误码、消息、时间戳);Panic 自动恢复防止服务崩溃;错误分类处理(客户端 4xx、服务端 5xx)和日志级别控制;敏感信息自动脱敏保护 -- **数据持久化**:GORM + PostgreSQL 集成,提供完整的 CRUD 操作、事务支持和数据库迁移能力 -- **异步任务处理**:Asynq 任务队列集成,支持任务提交、后台执行、自动重试和幂等性保障,实现邮件发送、数据同步等异步任务 -- **RBAC 权限系统**:完整的基于角色的访问控制,支持账号、角色、权限的多对多关联和层级关系;基于店铺层级的自动数据权限过滤,实现多租户数据隔离;使用 PostgreSQL WITH RECURSIVE 查询下级店铺并通过 Redis 缓存优化性能;完整的权限检查功能支持路由级别的细粒度权限控制,支持平台过滤(web/h5/all)和超级管理员自动跳过(详见 [功能总结](docs/004-rbac-data-permission/功能总结.md)、[使用指南](docs/004-rbac-data-permission/使用指南.md) 和 [权限检查使用指南](docs/permission-check-usage.md)) -- **商户管理**:完整的商户(Shop)和商户账号管理功能,支持商户创建时自动创建初始坐席账号、删除商户时批量禁用关联账号、账号密码重置等功能(详见 [使用指南](docs/shop-management/使用指南.md) 和 [API 文档](docs/shop-management/API文档.md)) -- **B 端认证系统**:完整的后台和 H5 认证功能,支持基于 Redis 的 Token 管理和双令牌机制(Access Token 24h + Refresh Token 7天);包含登录、登出、Token 刷新、用户信息查询和密码修改功能;通过用户类型隔离确保后台(SuperAdmin、Platform、Agent)和 H5(Agent、Enterprise)的访问控制;详见 [API 文档](docs/api/auth.md)、[使用指南](docs/auth-usage-guide.md) 和 [架构说明](docs/auth-architecture.md) -- **生命周期管理**:物联网卡/号卡的开卡、激活、停机、复机、销户 -- **代理商体系**:层级管理和分佣结算 -- **批量同步**:卡状态、实名状态、流量使用情况 -- **分佣验证指引**:对代理分佣的冻结、解冻、提现校验流程进行了结构化说明与流程图,详见 [分佣逻辑正确与否验证](docs/优化说明/分佣逻辑正确与否验证.md) -- **对象存储**:S3 兼容的对象存储服务集成(联通云 OSS),支持预签名 URL 上传、文件下载、临时文件处理;用于 ICCID 批量导入、数据导出等场景;详见 [使用指南](docs/object-storage/使用指南.md) 和 [前端接入指南](docs/object-storage/前端接入指南.md) -- **微信集成**:完整的微信公众号 OAuth 认证和微信支付功能(JSAPI + H5),使用 PowerWeChat v3 SDK;支持个人客户微信授权登录、账号绑定、微信内支付和浏览器 H5 支付;支付回调自动验证签名和幂等性处理;详见 [使用指南](docs/wechat-integration/使用指南.md) 和 [API 文档](docs/wechat-integration/API文档.md) - -## 用户体系设计 - -系统支持四种用户类型和两种组织实体,实现分层级的多租户管理: - -### 用户类型 - -1. **平台用户**:平台管理员,具有最高权限,可分配多个角色 -2. **代理账号**:店铺(代理商)员工账号,归属于特定店铺,权限相同 -3. **企业账号**:企业客户账号,归属于特定企业,一企业一账号 -4. **个人客户**:个人用户,独立表存储,支持微信绑定,不参与 RBAC 体系 - -### 组织实体 - -1. **店铺(Shop)**:代理商组织实体,支持最多 7 级层级关系 - - 一级代理直接归属于平台 - - 下级代理归属于上级店铺(通过 `parent_id` 字段) - - 一个店铺可以有多个账号(代理员工) - -2. **企业(Enterprise)**:企业客户组织实体 - - 可归属于店铺(通过 `owner_shop_id` 字段) - - 可归属于平台(`owner_shop_id = NULL`) - - 一个企业目前只有一个账号 - -### 核心设计决策 - -- **层级关系在店铺之间维护**:代理上下级关系通过 `Shop.parent_id` 维护,而非账号之间 -- **数据权限基于店铺归属**:数据过滤使用 `shop_id IN (当前店铺及下级店铺)` -- **递归查询+Redis缓存**:使用 `GetSubordinateShopIDs()` 递归查询下级店铺ID,结果缓存30分钟 -- **GORM 自动过滤**:通过 GORM Callback 自动应用数据权限过滤,无需在每个查询手动添加条件 -- **禁止外键约束**:遵循项目原则,表之间通过ID字段关联,关联查询在代码层显式执行 -- **GORM字段显式命名**:所有模型字段必须显式指定 `gorm:"column:field_name"` 标签 - -### 表结构 - -``` -tb_shop (店铺表) -├── id, created_at, updated_at, deleted_at -├── creator, updater -├── shop_name, shop_code -├── parent_id (上级店铺ID) -├── level (层级 1-7) -├── contact_name, contact_phone -├── province, city, district, address -└── status - -tb_enterprise (企业表) -├── id, created_at, updated_at, deleted_at -├── creator, updater -├── enterprise_name, enterprise_code -├── owner_shop_id (归属店铺ID) -├── legal_person, contact_name, contact_phone -├── business_license -├── province, city, district, address -└── status - -tb_personal_customer (个人客户表) -├── id, created_at, updated_at, deleted_at -├── phone (唯一标识) -├── nickname, avatar_url -├── wx_open_id, wx_union_id -└── status - -tb_account (账号表 - 已修改) -├── id, created_at, updated_at, deleted_at -├── creator, updater -├── username, phone, password -├── user_type (1=超级管理员 2=平台用户 3=代理账号 4=企业账号) -├── shop_id (代理账号必填) -├── enterprise_id (企业账号必填) ← 新增 -└── status -``` - -详细设计文档参见: -- [设计文档](openspec/changes/add-user-organization-model/design.md) -- [提案文档](openspec/changes/add-user-organization-model/proposal.md) - -## 数据权限模型 - -系统采用基于用户类型的自动数据权限过滤策略,通过 GORM Callback 自动应用,无需在每个查询中手动添加过滤条件。 - -### 过滤规则 - -| 用户类型 | 过滤策略 | 示例 | -|---------|---------|------| -| 超级管理员(Super Admin) | 跳过过滤,查看所有数据 | - | -| 平台用户(Platform) | 跳过过滤,查看所有数据 | - | -| 代理账号(Agent) | 基于店铺层级过滤 | `WHERE shop_id IN (当前店铺及下级店铺)` | -| 企业账号(Enterprise) | 基于企业归属过滤 | `WHERE enterprise_id = 当前企业ID` | -| 个人客户(Personal Customer) | 基于创建者过滤 | `WHERE creator = 当前用户ID` | - -### 工作机制 - -1. **认证中间件**设置完整用户上下文(`UserContextInfo`)到 `context` 中 -2. **GORM Callback**在每次查询前自动注入过滤条件 -3. **递归查询 + 缓存**:代理用户的下级店铺 ID 通过 `GetSubordinateShopIDs()` 递归查询,结果缓存 30 分钟 -4. **跳过过滤**:特殊场景(如统计、后台任务)可使用 `SkipDataPermission(ctx)` 绕过过滤 - -### 使用示例 - -```go -// 1. 认证后 context 已自动包含用户信息 -ctx := c.UserContext() - -// 2. 所有 Store 层查询自动应用数据权限过滤 -orders, err := orderStore.List(ctx) // 自动过滤为当前用户可见的订单 - -// 3. 需要查询所有数据时,显式跳过过滤 -ctx = gorm.SkipDataPermission(ctx) -allOrders, err := orderStore.List(ctx) // 查询所有订单(仅限特殊场景) -``` - -详细说明参见: -- [数据权限清理总结](docs/remove-legacy-rbac-cleanup/清理总结.md) -- [RBAC 权限使用指南](docs/004-rbac-data-permission/使用指南.md) - -## 快速开始 - -```bash -# 安装依赖 -go mod tidy - -# 启动 Redis(认证功能必需) -redis-server - -# 运行 API 服务 -go run cmd/api/main.go - -# 运行 Worker 服务(可选) -go run cmd/worker/main.go -``` - -### 默认超级管理员账号 - -系统首次启动时会自动创建默认超级管理员账号,无需手动执行 SQL 或脚本。 - -**默认账号信息**: -- 用户名:`admin` -- 密码:`Admin@123456` -- 手机号:`13800000000` - -**自定义配置**: - -通过环境变量自定义默认管理员信息: - -```bash -export JUNHONG_DEFAULT_ADMIN_USERNAME="自定义用户名" -export JUNHONG_DEFAULT_ADMIN_PASSWORD="自定义密码" -export JUNHONG_DEFAULT_ADMIN_PHONE="自定义手机号" -``` - -**注意事项**: -- 系统只在数据库无超级管理员账号时才创建 -- 如果已存在超级管理员,启动时会跳过创建 -- 建议首次登录后立即修改默认密码 -- 初始化日志记录在 `logs/app.log` 中 - -详细设置和测试说明请参阅 [快速开始指南](specs/001-fiber-middleware-integration/quickstart.md)。 - -## 项目结构 - -``` -junhong_cmp_fiber/ -│ -├── cmd/ # 应用程序入口 -│ ├── api/ # HTTP API 服务 -│ │ └── main.go # API 服务主入口 -│ └── worker/ # Asynq 异步任务 Worker -│ └── main.go # Worker 服务主入口 -│ -├── internal/ # 私有业务代码 -│ ├── handler/ # HTTP 处理层 -│ │ ├── user.go # 用户处理器 -│ │ └── health.go # 健康检查处理器 -│ ├── middleware/ # Fiber 中间件实现 -│ │ ├── auth.go # 认证中间件(keyauth) -│ │ ├── ratelimit.go # 限流中间件 -│ │ └── recover.go # Panic 恢复中间件 -│ ├── service/ # 业务逻辑层(核心业务) -│ ├── store/ # 数据访问层 -│ │ └── postgres/ # PostgreSQL 实现 -│ ├── model/ # 数据模型(实体、DTO) -│ ├── task/ # Asynq 任务定义和处理 -│ ├── gateway/ # Gateway 服务 HTTP 客户端 -│ └── router/ # 路由注册 -│ -├── pkg/ # 公共工具库 -│ ├── config/ # 配置管理 -│ │ ├── config.go # 配置结构定义 -│ │ ├── loader.go # 配置加载(嵌入配置 + 环境变量覆盖) -│ │ ├── embedded.go # go:embed 嵌入配置加载 -│ │ └── defaults/config.yaml # 默认配置(嵌入二进制) -│ ├── logger/ # 日志基础设施 -│ │ ├── logger.go # Zap 日志初始化 -│ │ └── middleware.go # Fiber 日志中间件适配器 -│ ├── response/ # 统一响应处理 -│ │ └── response.go # 响应结构和辅助函数 -│ ├── errors/ # 错误码和类型 -│ │ ├── codes.go # 错误码常量 -│ │ └── errors.go # 自定义错误类型 -│ ├── constants/ # 业务常量 -│ │ ├── constants.go # 上下文键、请求头名称 -│ │ └── redis.go # Redis Key 生成器 -│ ├── validator/ # 验证服务 -│ │ └── token.go # Token 验证(Redis) -│ ├── database/ # 数据库初始化 -│ │ └── redis.go # Redis 客户端初始化 -│ └── queue/ # 队列封装(Asynq) -│ -├── tests/ -│ └── integration/ # 集成测试 -│ ├── auth_test.go # 认证测试 -│ └── ratelimit_test.go # 限流测试 -│ -├── migrations/ # 数据库迁移文件 -├── scripts/ # 脚本工具 -├── docs/ # 文档 -│ └── rate-limiting.md # 限流指南 -└── logs/ # 应用日志(自动创建) - ├── app.log # 应用日志(JSON) - └── access.log # 访问日志(JSON) -``` - -## 中间件执行顺序 - -中间件按注册顺序执行。请求按顺序流经每个中间件: - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ HTTP 请求 │ -└────────────────────────────────┬────────────────────────────────┘ - │ - ┌────────────▼────────────┐ - │ 1. Recover 中间件 │ - │ (panic 恢复) │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 2. RequestID 中间件 │ - │ (生成 UUID) │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 3. Logger 中间件 │ - │ (访问日志) │ - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 4. 认证中间件 │ - │ (按路由组配置) │ ─── 模块化路由注册 - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 5. RateLimiter 中间件 │ - │ (限流) │ ─── 可选 (config: enable_rate_limiter) - └────────────┬────────────┘ - │ - ┌────────────▼────────────┐ - │ 6. 路由处理器 │ - │ (业务逻辑) │ - └────────────┬────────────┘ - │ -┌────────────────────────────────▼────────────────────────────────┐ -│ HTTP 响应 │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### 中间件详情 - -#### 1. Recover 中间件(fiber.Recover) -- **用途**:捕获 panic 并防止服务器崩溃 -- **行为**: - - 捕获下游中间件/处理器中的任何 panic - - 将 panic 及堆栈跟踪记录到 `logs/app.log` - - 返回 HTTP 500 统一错误响应 - - 服务器继续处理后续请求 -- **始终激活**:是 - -#### 2. RequestID 中间件(自定义) -- **用途**:生成请求追踪的唯一标识符 -- **行为**: - - 为每个请求生成 UUID v4 - - 存储在上下文中:`c.Locals(constants.ContextKeyRequestID)` - - 添加 `X-Request-ID` 响应头 - - 用于所有日志条目以进行关联 -- **始终激活**:是 - -#### 3. Logger 中间件(自定义 Fiber 适配器) -- **用途**:记录所有 HTTP 请求和响应 -- **行为**: - - 记录请求:方法、路径、IP、User-Agent、请求 ID - - 记录响应:状态码、耗时、用户 ID(如果已认证) - - 写入 `logs/access.log`(JSON 格式) - - 结构化字段便于解析和分析 -- **始终激活**:是 -- **日志格式**:包含字段的 JSON:timestamp、level、method、path、status、duration_ms、request_id、ip、user_agent、user_id - -#### 4. 认证中间件(pkg/middleware/auth.go 和 internal/middleware/) -- **用途**:使用 Token 验证对请求进行认证 -- **行为**: - - 从 `Authorization: Bearer {token}` 请求头提取 token - - 通过 TokenValidator 函数验证 token(支持 JWT 和 Redis Token) - - 如果缺失/无效 token 返回 401 - - 成功时将用户信息存储在上下文中(UserID、UserType、ShopID、EnterpriseID) -- **实现方式**:模块化路由注册(无全局配置) - - `/api/admin/*`:后台认证(SuperAdmin、Platform、Agent) - - `/api/h5/*`:H5 认证(Agent、Enterprise) - - `/api/personal/*`:个人客户认证(JWT) -- **跳过路由**:各路由组可自行配置跳过路径(如 `/api/admin/login`) -- **错误码**: - - 1001:缺失 token - - 1002:无效或过期 token - - 1003:权限不足 - -#### 5. RateLimiter 中间件(internal/middleware/ratelimit.go) -- **用途**:通过限制请求速率保护 API 免受滥用 -- **行为**: - - 按客户端 IP 地址追踪请求 - - 执行限制:`expiration` 时间窗口内 `max` 个请求 - - 如果超过限制返回 429 - - 每个 IP 地址独立计数器 -- **配置**:`middleware.enable_rate_limiter`(默认:false) -- **存储选项**: - - `memory`:内存存储(单服务器,重启后重置) - - `redis`:基于 Redis(分布式,持久化) -- **错误码**:1003(请求过于频繁) - -#### 6. 路由处理器 -- **用途**:执行端点的业务逻辑 -- **可用上下文数据**: - - 请求 ID:`c.Locals(constants.ContextKeyRequestID)` - - 用户 ID:`c.Locals(constants.ContextKeyUserID)`(如果已认证) - - 标准 Fiber 上下文方法:`c.Params()`、`c.Query()`、`c.Body()` 等 - -### 中间件注册(cmd/api/main.go) - -```go -// 核心中间件(始终激活) -app.Use(recover.New()) -app.Use(addRequestID()) -app.Use(loggerMiddleware()) - -// 模块化路由注册(认证中间件按路由组配置) -routes.RegisterRoutes(app, handlers, middlewares) - -// 可选:限流中间件 -if config.GetConfig().Middleware.EnableRateLimiter { - var storage fiber.Storage = nil - if config.GetConfig().Middleware.RateLimiter.Storage == "redis" { - storage = redisStorage // 使用 Redis 存储 - } - v1 := app.Group("/api/v1") - v1.Use(middleware.RateLimiter( - config.GetConfig().Middleware.RateLimiter.Max, - config.GetConfig().Middleware.RateLimiter.Expiration, - storage, - )) -} -``` - -### 请求流程示例 - -**场景**:已启用所有中间件的 `/api/v1/users` 认证请求 - -``` -1. 请求到达:GET /api/v1/users - 请求头:token: abc123 - -2. Recover 中间件:准备捕获 panic - → 传递到下一个中间件 - -3. RequestID 中间件:生成 UUID - → 设置上下文:request_id = "550e8400-e29b-41d4-a716-446655440000" - → 传递到下一个中间件 - -4. Logger 中间件:记录请求开始 - → 日志:{"method":"GET", "path":"/api/v1/users", "request_id":"550e8400-..."} - → 传递到下一个中间件 - -5. KeyAuth 中间件:验证 token - → 检查 Redis:GET "auth:token:abc123" → "user-789" - → 设置上下文:user_id = "user-789" - → 传递到下一个中间件 - -6. RateLimiter 中间件:检查限流 - → 检查计数器:GET "rate_limit:127.0.0.1" → "5"(低于限制 100) - → 增加计数器:INCR "rate_limit:127.0.0.1" → "6" - → 传递到下一个中间件 - -7. 处理器执行:listUsersHandler() - → 从上下文获取 user_id:"user-789" - → 从数据库获取用户 - → 返回响应:{"code":0, "data":[...], "msg":"success"} - -8. Logger 中间件:记录响应 - → 日志:{"status":200, "duration_ms":23.45, "user_id":"user-789"} - -9. RequestID 中间件:添加响应头 - → 响应头:X-Request-ID: 550e8400-e29b-41d4-a716-446655440000 - -10. 响应发送给客户端 -``` - -### 中间件中的错误处理 - -如果任何中间件返回错误,链停止并发送错误响应: - -``` -请求 → Recover → RequestID → Logger → [KeyAuth 失败] ✗ - ↓ - 返回 401 - (不执行 RateLimiter 和 Handler) -``` - -示例:缺失 token -``` -KeyAuth:Token 缺失 -→ 返回 response.Error(c, 401, 1001, "缺失认证令牌") -→ Logger 记录:{"status":401, "duration_ms":1.23} -→ RequestID 添加响应头 -→ 发送响应 -``` - -## 配置 - -### 嵌入式配置机制 - -系统使用 go:embed 将默认配置嵌入二进制文件,通过环境变量进行覆盖: - -- **默认配置**:`pkg/config/defaults/config.yaml`(编译时嵌入) -- **环境变量前缀**:`JUNHONG_` -- **格式转换**:配置路径中的 `.` 替换为 `_` - -**环境变量覆盖示例**: - -| 配置项 | 环境变量 | -|-------|---------| -| `database.host` | `JUNHONG_DATABASE_HOST` | -| `redis.address` | `JUNHONG_REDIS_ADDRESS` | -| `jwt.secret_key` | `JUNHONG_JWT_SECRET_KEY` | -| `logging.level` | `JUNHONG_LOGGING_LEVEL` | - -### 必填配置 - -以下配置项必须通过环境变量设置(无默认值或需要覆盖): - -```bash -# 数据库配置(必填) -export JUNHONG_DATABASE_HOST=localhost -export JUNHONG_DATABASE_PORT=5432 -export JUNHONG_DATABASE_USER=postgres -export JUNHONG_DATABASE_PASSWORD=your_password -export JUNHONG_DATABASE_DBNAME=junhong_cmp - -# Redis 配置(必填) -export JUNHONG_REDIS_ADDRESS=localhost - -# JWT 密钥(必填,生产环境必须修改) -export JUNHONG_JWT_SECRET_KEY=your-secret-key-change-in-production -``` - -### Docker 部署 - -Docker 部署使用纯环境变量配置,无需挂载配置文件: - -```yaml -# docker-compose.prod.yml 示例 -services: - api: - image: registry.boss160.cn/junhong/cmp-fiber-api:latest - environment: - - JUNHONG_DATABASE_HOST=db-host - - JUNHONG_DATABASE_PORT=5432 - - JUNHONG_DATABASE_USER=postgres - - JUNHONG_DATABASE_PASSWORD=secret - - JUNHONG_DATABASE_DBNAME=junhong_cmp - - JUNHONG_REDIS_ADDRESS=redis - - JUNHONG_JWT_SECRET_KEY=production-secret - volumes: - - ./logs:/app/logs # 仅挂载日志目录 -``` - -### 完整环境变量列表 - -详见 [环境变量配置文档](docs/environment-variables.md) - -## 测试 - -### 运行所有测试 - -```bash -# 运行所有单元和集成测试 -go test ./... - -# 带覆盖率运行 -go test -cover ./... - -# 详细输出运行 -go test -v ./... -``` - -### 运行特定测试套件 - -```bash -# 仅单元测试 -go test ./pkg/... - -# 仅集成测试 -go test ./tests/integration/... - -# 特定测试 -go test -v ./internal/middleware -run TestKeyAuth -``` - -### 集成测试 - -集成测试需要 Redis 运行: - -```bash -# 启动 Redis -redis-server - -# 运行集成测试 -go test -v ./tests/integration/... -``` - -如果 Redis 不可用,测试自动跳过。 - -### 测试连接管理 - -测试使用全局单例连接池,性能提升 6-7 倍。详见 [测试连接管理规范](docs/testing/test-connection-guide.md)。 - -**标准写法**: -```go -func TestXxx(t *testing.T) { - tx := testutils.NewTestTransaction(t) // 自动回滚的事务 - rdb := testutils.GetTestRedis(t) // 全局 Redis 连接 - testutils.CleanTestRedisKeys(t, rdb) // 自动清理 Redis 键 - - store := postgres.NewXxxStore(tx, rdb) - // 测试代码... -} -``` - -## 架构设计 - -### 分层架构 -``` -Handler (HTTP) → Service (业务逻辑) → Store (数据访问) → Model (数据模型) -``` - -### 双服务架构 -- **API 服务**:处理 HTTP 请求,快速响应 -- **Worker 服务**:处理异步任务(批量同步、分佣计算等),独立部署 - -### 核心模块 -- **Service 层**:统一管理所有业务逻辑,支持跨模块调用 -- **Store 层**:统一管理所有数据访问,支持事务 -- **Task 层**:Asynq 任务处理器,支持定时任务和事件触发 - -## 框架优化历史 - -### 005-framework-cleanup-refactor(2025-11) - -**背景**:清理技术债务,统一框架设计 - -**主要变更**: -1. **清理示例代码**:删除所有 user/order 示例业务代码,保持代码库整洁 -2. **统一认证中间件**:合并两套 Auth 实现到 `pkg/middleware/auth.go`,统一错误处理格式 -3. **简化错误结构**:删除 AppError 的 HTTPStatus 字段,避免字段冗余 -4. **组件注册解耦**:创建 `internal/bootstrap/` 包实现自动化组件初始化 - - 按模块拆分:`stores.go`、`services.go`、`handlers.go` - - main.go 简化为一行:`handlers, err := bootstrap.Bootstrap(deps)` -5. **数据权限自动化**:实现 GORM Callback 自动注入数据权限过滤 - - 基于 creator 字段自动过滤(普通用户只能看到自己和下级的数据) - - root 用户自动跳过过滤 - - 支持通过 `gorm.SkipDataPermission(ctx)` 手动绕过 - - 删除未使用的 `scopes.go` 手动 Scope 函数 - -**设计原则**: -- 保持 Go 惯用模式,避免 Java 风格过度抽象 -- 使用显式依赖注入,不引入复杂的 DI 框架 -- 每个文件保持 < 100 行,职责单一 -- 在关键扩展点添加 TODO 标记 - -**详细文档**: -- [变更提案](openspec/changes/refactor-framework-cleanup/proposal.md) -- [设计文档](openspec/changes/refactor-framework-cleanup/design.md) -- [任务清单](openspec/changes/refactor-framework-cleanup/tasks.md) - -## 开发规范 - -### 依赖注入 -通过 `Service` 和 `Store` 结构体统一管理依赖: -```go -// 初始化 -st := store.New(db) -svc := service.New(st, queueClient, logger) - -// 使用 -svc.SIM.Activate(...) -svc.Commission.Calculate(...) -``` - -### 事务处理 -```go -store.Transaction(ctx, func(tx *store.Store) error { - tx.SIM.UpdateStatus(...) - tx.Commission.Create(...) - return nil -}) -``` - -### 异步任务 -- 高频任务:批量状态同步、流量同步、实名检查 -- 业务任务:分佣计算、生命周期变更通知 -- 任务优先级:critical > default > low - -### 常量和 Redis Key 管理 -所有常量统一在 `pkg/constants/` 目录管理: -```go -// 业务常量 -constants.SIMStatusActive -constants.SIMStatusInactive - -// Redis Key 管理(统一使用 Key 生成函数) -constants.RedisSIMStatusKey(iccid) // sim:status:{iccid} -constants.RedisAgentCommissionKey(agentID) // agent:commission:{agentID} -constants.RedisTaskLockKey(taskName) // task:lock:{taskName} -constants.RedisAuthTokenKey(token) // auth:token:{token} - -// 使用示例 -key := constants.RedisSIMStatusKey("898600...") -rdb.Set(ctx, key, status, time.Hour) -``` - -## 文档 - -### 开发规范 - -- **[API 文档生成规范](docs/api-documentation-guide.md)**:路由注册规范、DTO 规范、OpenAPI 文档生成流程 -- **[数据库验证规范](AGENTS.md#数据库验证规范)**:使用 PostgreSQL MCP 验证接口逻辑和业务数据的正确性 -- **[开发规范总览](AGENTS.md)**:完整的项目开发规范(必读) - -### 功能指南 - -- **[快速开始指南](specs/001-fiber-middleware-integration/quickstart.md)**:详细设置和测试说明 -- **[限流指南](docs/rate-limiting.md)**:全面的限流配置和使用 -- **[错误处理使用指南](docs/003-error-handling/使用指南.md)**:错误码参考、Handler 使用、客户端处理、最佳实践 -- **[错误处理架构说明](docs/003-error-handling/架构说明.md)**:架构设计、性能优化、扩展性说明 - -### 架构设计 - -- **[实现计划](specs/001-fiber-middleware-integration/plan.md)**:设计决策和架构 -- **[数据模型](specs/001-fiber-middleware-integration/data-model.md)**:配置结构和 Redis 架构 - -## 技术栈 - -- **Go**:1.25.1 -- **Fiber**:v2.52.9(HTTP 框架) -- **Zap**:v1.27.0(结构化日志) -- **Lumberjack**:v2.2.1(日志轮转) -- **Viper**:v1.19.0(配置管理) -- **go-redis**:v9.7.0(Redis 客户端) -- **fsnotify**:v1.8.0(文件系统通知) -- **GORM**:(数据库 ORM) -- **sonic**:(高性能 JSON) -- **Asynq**:(异步任务队列) -- **Validator**:(参数验证) -- **PowerWeChat**:v3.4.38(微信SDK - 公众号 & 支付) - -## 开发流程(Speckit) - -本项目使用 Speckit 规范化功能开发流程,确保代码质量、测试覆盖和架构一致性。 - -### 项目宪章 - -项目遵循 `.specify/memory/constitution.md` 定义的核心原则: - -1. **技术栈遵守**:严格使用 Fiber + GORM + Viper + Zap + Asynq,禁止原生调用快捷方式 -2. **代码质量标准**:遵循 Handler → Service → Store → Model 分层架构 -3. **测试标准**:70%+ 测试覆盖率,核心业务 90%+ -4. **用户体验一致性**:统一 JSON 响应格式、RESTful API、双语错误消息 -5. **性能要求**:API P95 < 200ms,P99 < 500ms,合理使用批量操作和异步任务 - -详细原则和规则请参阅宪章文档。 - -### Speckit 命令 - -```bash -# 创建功能规范 -/speckit.specify "功能描述" - -# 明确规范细节 -/speckit.clarify - -# 生成实现计划 -/speckit.plan - -# 生成任务列表 -/speckit.tasks - -# 执行实现 -/speckit.implement - -# 一致性分析 -/speckit.analyze - -# 生成自定义检查清单 -/speckit.checklist "检查项要求" - -# 更新项目宪章 -/speckit.constitution "宪章更新说明" -``` - -## 代码规范检查 - -运行代码规范检查: - -```bash -# 检查 Service 层错误处理 -bash scripts/check-service-errors.sh - -# 检查注释路径一致性 -bash scripts/check-comment-paths.sh - -# 运行所有检查 -bash scripts/check-all.sh -``` - -这些检查会在 CI/CD 流程中自动执行。 - -## 设计原则 - -- **简单实用**:不过度设计,够用就好 -- **直接实现**:避免不必要的接口抽象 -- **统一管理**:依赖集中初始化,避免参数传递 -- **职责分离**:API 和 Worker 独立部署,便于扩展 - -## 许可证 - -MIT License diff --git a/cmd/api/main.go b/cmd/api/main.go index 00a2d9e..93a3097 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -66,12 +66,9 @@ func main() { // 9. 初始化对象存储服务(可选) storageSvc := initStorage(cfg, appLogger) -<<<<<<< HEAD // 9. 初始化 Gateway 客户端(可选) gatewayClient := initGateway(cfg, appLogger) -======= ->>>>>>> emdash/wechat-official-account-payment-integration-30g // 10. 初始化所有业务组件(通过 Bootstrap) result, err := bootstrap.Bootstrap(&bootstrap.Dependencies{ DB: db, @@ -339,7 +336,6 @@ func initStorage(cfg *config.Config, appLogger *zap.Logger) *storage.Service { return storage.NewService(provider, &cfg.Storage) } -<<<<<<< HEAD func initGateway(cfg *config.Config, appLogger *zap.Logger) *gateway.Client { if cfg.Gateway.BaseURL == "" { appLogger.Info("Gateway 未配置,跳过初始化") @@ -357,7 +353,8 @@ func initGateway(cfg *config.Config, appLogger *zap.Logger) *gateway.Client { zap.String("app_id", cfg.Gateway.AppID)) return client -======= +} + func validateWechatConfig(cfg *config.Config, appLogger *zap.Logger) { wechatCfg := cfg.Wechat @@ -417,5 +414,4 @@ func validateWechatConfig(cfg *config.Config, appLogger *zap.Logger) { zap.String("app_id", wechatCfg.Payment.AppID), zap.String("mch_id", wechatCfg.Payment.MchID)) } ->>>>>>> emdash/wechat-official-account-payment-integration-30g } diff --git a/internal/bootstrap/dependencies.go b/internal/bootstrap/dependencies.go index c25f482..669f5f4 100644 --- a/internal/bootstrap/dependencies.go +++ b/internal/bootstrap/dependencies.go @@ -15,17 +15,6 @@ import ( // Dependencies 封装所有基础依赖 // 这些是应用启动时初始化的核心组件 type Dependencies struct { -<<<<<<< HEAD - DB *gorm.DB // PostgreSQL 数据库连接 - Redis *redis.Client // Redis 客户端 - Logger *zap.Logger // 应用日志器 - JWTManager *auth.JWTManager // JWT 管理器(个人客户认证) - TokenManager *auth.TokenManager // Token 管理器(后台和H5认证) - VerificationService *verification.Service // 验证码服务 - QueueClient *queue.Client // Asynq 任务队列客户端 - StorageService *storage.Service // 对象存储服务(可选,配置缺失时为 nil) - GatewayClient *gateway.Client // Gateway API 客户端(可选,配置缺失时为 nil) -======= DB *gorm.DB // PostgreSQL 数据库连接 Redis *redis.Client // Redis 客户端 Logger *zap.Logger // 应用日志器 @@ -34,7 +23,7 @@ type Dependencies struct { VerificationService *verification.Service // 验证码服务 QueueClient *queue.Client // Asynq 任务队列客户端 StorageService *storage.Service // 对象存储服务(可选,配置缺失时为 nil) + GatewayClient *gateway.Client // Gateway API 客户端(可选,配置缺失时为 nil) WechatOfficialAccount wechat.OfficialAccountServiceInterface // 微信公众号服务(可选) WechatPayment wechat.PaymentServiceInterface // 微信支付服务(可选) ->>>>>>> emdash/wechat-official-account-payment-integration-30g } diff --git a/pkg/config/config.go b/pkg/config/config.go index 26d940b..e05b02f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -23,11 +23,8 @@ type Config struct { JWT JWTConfig `mapstructure:"jwt"` DefaultAdmin DefaultAdminConfig `mapstructure:"default_admin"` Storage StorageConfig `mapstructure:"storage"` -<<<<<<< HEAD Gateway GatewayConfig `mapstructure:"gateway"` -======= Wechat WechatConfig `mapstructure:"wechat"` ->>>>>>> emdash/wechat-official-account-payment-integration-30g } // ServerConfig HTTP 服务器配置 diff --git a/worker b/worker deleted file mode 100755 index ae50785..0000000 Binary files a/worker and /dev/null differ