核心变更: - 数据权限过滤从基于账号层级改为基于用户类型的多策略过滤 - 移除 AccountStore 中的 GetSubordinateIDs 等旧方法 - 重构认证中间件,支持 enterprise_id 和 customer_id - 更新 GORM Callback,根据用户类型自动选择过滤策略(代理/企业/个人客户) - 更新所有集成测试以适配新的 API 签名 - 添加功能总结文档和 OpenSpec 归档 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
689 lines
27 KiB
Markdown
689 lines
27 KiB
Markdown
# 君鸿卡管系统 - Fiber 中间件集成
|
||
|
||
基于 Go + Fiber 框架的 HTTP 服务,集成了认证、限流、结构化日志和配置热重载功能。
|
||
|
||
## 系统简介
|
||
|
||
物联网卡 + 号卡全生命周期管理平台,支持代理商体系和分佣结算。
|
||
|
||
**技术栈**:Fiber + GORM + Viper + Zap + Lumberjack.v2 + Validator + sonic JSON + Asynq + PostgreSQL
|
||
|
||
## 核心功能
|
||
|
||
- **认证中间件**:基于 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
|
||
```
|
||
|
||
详细设置和测试说明请参阅 [快速开始指南](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
|