# 君鸿卡管系统 - 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 日志和自动日志轮转 - **配置热重载**:运行时配置更新,无需重启服务 - **请求 ID 追踪**:UUID 跨日志的请求追踪 - **Panic 恢复**:优雅的 panic 处理和堆栈跟踪日志 - **统一错误处理**:全局 ErrorHandler 统一处理所有 API 错误,返回一致的 JSON 格式(包含错误码、消息、时间戳);Panic 自动恢复防止服务崩溃;错误分类处理(客户端 4xx、服务端 5xx)和日志级别控制;敏感信息自动脱敏保护 - **数据持久化**:GORM + PostgreSQL 集成,提供完整的 CRUD 操作、事务支持和数据库迁移能力 - **异步任务处理**:Asynq 任务队列集成,支持任务提交、后台执行、自动重试和幂等性保障,实现邮件发送、数据同步等异步任务 - **RBAC 权限系统**:完整的基于角色的访问控制,支持账号、角色、权限的多对多关联和层级关系;基于店铺层级的自动数据权限过滤,实现多租户数据隔离;使用 PostgreSQL WITH RECURSIVE 查询下级店铺并通过 Redis 缓存优化性能(详见 [功能总结](docs/004-rbac-data-permission/功能总结.md) 和 [使用指南](docs/004-rbac-data-permission/使用指南.md)) - **生命周期管理**:物联网卡/号卡的开卡、激活、停机、复机、销户 - **代理商体系**:层级管理和分佣结算 - **批量同步**:卡状态、实名状态、流量使用情况 - **分佣验证指引**:对代理分佣的冻结、解冻、提现校验流程进行了结构化说明与流程图,详见 [分佣逻辑正确与否验证](docs/优化说明/分佣逻辑正确与否验证.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` **自定义配置**: 可在 `configs/config.yaml` 中自定义默认管理员信息: ```yaml default_admin: username: "自定义用户名" password: "自定义密码" 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 # 配置加载与验证 │ │ └── watcher.go # 配置热重载(fsnotify) │ ├── 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) │ ├── configs/ # 配置文件 │ ├── config.yaml # 默认配置 │ ├── config.dev.yaml # 开发环境 │ ├── config.staging.yaml # 预发布环境 │ └── config.prod.yaml # 生产环境 │ ├── 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. KeyAuth 中间件 │ │ (认证) │ ─── 可选 (config: enable_auth) └────────────┬────────────┘ │ ┌────────────▼────────────┐ │ 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. KeyAuth 中间件(internal/middleware/auth.go) - **用途**:使用 Token 验证对请求进行认证 - **行为**: - 从 `token` 请求头提取 token - 通过 Redis 验证 token(`auth:token:{token}`) - 如果缺失/无效 token 返回 401 - 如果 Redis 不可用返回 503(fail-closed 策略) - 成功时将用户 ID 存储在上下文中:`c.Locals(constants.ContextKeyUserID)` - **配置**:`middleware.enable_auth`(默认:true) - **跳过路由**:`/health`(健康检查绕过认证) - **错误码**: - 1001:缺失 token - 1002:无效或过期 token - 1004:认证服务不可用 #### 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()) // 可选:认证中间件 if config.GetConfig().Middleware.EnableAuth { tokenValidator := validator.NewTokenValidator(rdb, logger.GetAppLogger()) app.Use(middleware.KeyAuth(tokenValidator, logger.GetAppLogger())) } // 可选:限流中间件 if config.GetConfig().Middleware.EnableRateLimiter { var storage fiber.Storage = nil if config.GetConfig().Middleware.RateLimiter.Storage == "redis" { storage = redisStorage // 使用 Redis 存储 } app.Use(middleware.RateLimiter( config.GetConfig().Middleware.RateLimiter.Max, config.GetConfig().Middleware.RateLimiter.Expiration, storage, )) } // 路由 app.Get("/health", healthHandler) app.Get("/api/v1/users", listUsersHandler) ``` ### 请求流程示例 **场景**:已启用所有中间件的 `/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 添加响应头 → 发送响应 ``` ## 配置 ### 环境特定配置 设置 `CONFIG_ENV` 环境变量以加载特定配置: ```bash # 开发环境(config.dev.yaml) export CONFIG_ENV=dev # 预发布环境(config.staging.yaml) export CONFIG_ENV=staging # 生产环境(config.prod.yaml) export CONFIG_ENV=prod # 默认配置(config.yaml) # 不设置 CONFIG_ENV ``` ### 配置热重载 配置更改在 5 秒内自动检测并应用,无需重启服务器: - **监控文件**:所有 `configs/*.yaml` 文件 - **检测**:使用 fsnotify 监视文件更改 - **验证**:应用前验证新配置 - **行为**: - 有效更改:立即应用,记录到 `logs/app.log` - 无效更改:拒绝,服务器继续使用先前配置 - **原子性**:使用 `sync/atomic` 进行线程安全的配置更新 **示例**: ```bash # 在服务器运行时编辑配置 vim configs/config.yaml # 将 logging.level 从 "info" 改为 "debug" # 检查日志(5 秒内) tail -f logs/app.log | jq . # {"level":"info","message":"配置文件已更改","file":"configs/config.yaml"} # {"level":"info","message":"配置重新加载成功"} ``` ## 测试 ### 运行所有测试 ```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 不可用,测试自动跳过。 ## 架构设计 ### 分层架构 ``` 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) ``` ## 文档 - **[快速开始指南](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 "宪章更新说明" ``` ## 设计原则 - **简单实用**:不过度设计,够用就好 - **直接实现**:避免不必要的接口抽象 - **统一管理**:依赖集中初始化,避免参数传递 - **职责分离**:API 和 Worker 独立部署,便于扩展 ## 许可证 MIT License