Files
junhong_cmp_fiber/README.md
huang eaa70ac255 feat: 实现 RBAC 权限系统和数据权限控制 (004-rbac-data-permission)
主要功能:
- 实现完整的 RBAC 权限系统(账号、角色、权限的多对多关联)
- 基于 owner_id + shop_id 的自动数据权限过滤
- 使用 PostgreSQL WITH RECURSIVE 查询下级账号
- Redis 缓存优化下级账号查询性能(30分钟过期)
- 支持多租户数据隔离和层级权限管理

技术实现:
- 新增 Account、Role、Permission 模型及关联关系表
- 实现 GORM Scopes 自动应用数据权限过滤
- 添加数据库迁移脚本(000002_rbac_data_permission、000003_add_owner_id_shop_id)
- 完善错误码定义(1010-1027 为 RBAC 相关错误)
- 重构 main.go 采用函数拆分提高可读性

测试覆盖:
- 添加 Account、Role、Permission 的集成测试
- 添加数据权限过滤的单元测试和集成测试
- 添加下级账号查询和缓存的单元测试
- 添加 API 回归测试确保向后兼容

文档更新:
- 更新 README.md 添加 RBAC 功能说明
- 更新 CLAUDE.md 添加技术栈和开发原则
- 添加 docs/004-rbac-data-permission/ 功能总结和使用指南

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 16:44:06 +08:00

543 lines
20 KiB
Markdown
Raw 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
## 核心功能
- **认证中间件**:基于 Redis 的 Token 认证
- **限流中间件**:基于 IP 的限流,支持可配置的限制和存储后端
- **结构化日志**:使用 Zap 的 JSON 日志和自动日志轮转
- **配置热重载**:运行时配置更新,无需重启服务
- **请求 ID 追踪**UUID 跨日志的请求追踪
- **Panic 恢复**:优雅的 panic 处理和堆栈跟踪日志
- **统一错误处理**:全局 ErrorHandler 统一处理所有 API 错误,返回一致的 JSON 格式包含错误码、消息、时间戳Panic 自动恢复防止服务崩溃;错误分类处理(客户端 4xx、服务端 5xx和日志级别控制敏感信息自动脱敏保护
- **数据持久化**GORM + PostgreSQL 集成,提供完整的 CRUD 操作、事务支持和数据库迁移能力
- **异步任务处理**Asynq 任务队列集成,支持任务提交、后台执行、自动重试和幂等性保障,实现邮件发送、数据同步等异步任务
- **RBAC 权限系统**:完整的基于角色的访问控制,支持账号、角色、权限的多对多关联和层级关系;基于 owner_id + shop_id 的自动数据权限过滤,实现多租户数据隔离;使用 PostgreSQL WITH RECURSIVE 查询下级账号并通过 Redis 缓存优化性能(详见 [功能总结](docs/004-rbac-data-permission/功能总结.md) 和 [使用指南](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 格式)
- 结构化字段便于解析和分析
- **始终激活**:是
- **日志格式**:包含字段的 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 任务处理器,支持定时任务和事件触发
## 开发规范
### 依赖注入
通过 `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