Files
junhong_cmp_fiber/README.md
huang 028cfaa7aa feat: 实现权限检查功能并添加Redis缓存优化
- 完成 CheckPermission 方法的完整实现(账号→角色→权限查询链)
- 实现 Redis 缓存机制,大幅提升权限查询性能(~12倍提升)
- 自动缓存失效:角色/权限变更时清除相关用户缓存
- 新增完整的单元测试和集成测试(10个测试用例全部通过)
- 添加权限检查使用文档和缓存机制说明
- 归档 implement-permission-check OpenSpec 提案

性能优化:
- 首次查询: ~18ms(3次DB查询 + 1次Redis写入)
- 缓存命中: ~1.5ms(1次Redis查询)
- TTL: 30分钟,自动失效机制保证数据一致性
2026-01-16 18:15:32 +08:00

890 lines
35 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 君鸿卡管系统 - 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 卡
├── 卡1ICCID-001
├── 卡2ICCID-002
└── 卡3ICCID-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 缓存优化性能完整的权限检查功能支持路由级别的细粒度权限控制支持平台过滤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和 H5Agent、Enterprise的访问控制详见 [API 文档](docs/api/auth.md)、[使用指南](docs/auth-usage-guide.md) 和 [架构说明](docs/auth-architecture.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 格式)
- 结构化字段便于解析和分析
- **始终激活**:是
- **日志格式**:包含字段的 JSONtimestamp、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 不可用返回 503fail-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
→ 检查 RedisGET "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
```
KeyAuthToken 缺失
→ 返回 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-refactor2025-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.9HTTP 框架)
- **Zap**v1.27.0(结构化日志)
- **Lumberjack**v2.2.1(日志轮转)
- **Viper**v1.19.0(配置管理)
- **go-redis**v9.7.0Redis 客户端)
- **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 < 200msP99 < 500ms合理使用批量操作和异步任务
详细原则和规则请参阅宪章文档。
### Speckit 命令
```bash
# 创建功能规范
/speckit.specify "功能描述"
# 明确规范细节
/speckit.clarify
# 生成实现计划
/speckit.plan
# 生成任务列表
/speckit.tasks
# 执行实现
/speckit.implement
# 一致性分析
/speckit.analyze
# 生成自定义检查清单
/speckit.checklist "检查项要求"
# 更新项目宪章
/speckit.constitution "宪章更新说明"
```
## 设计原则
- **简单实用**:不过度设计,够用就好
- **直接实现**:避免不必要的接口抽象
- **统一管理**:依赖集中初始化,避免参数传递
- **职责分离**API 和 Worker 独立部署,便于扩展
## 许可证
MIT License