- 完成 CheckPermission 方法的完整实现(账号→角色→权限查询链) - 实现 Redis 缓存机制,大幅提升权限查询性能(~12倍提升) - 自动缓存失效:角色/权限变更时清除相关用户缓存 - 新增完整的单元测试和集成测试(10个测试用例全部通过) - 添加权限检查使用文档和缓存机制说明 - 归档 implement-permission-check OpenSpec 提案 性能优化: - 首次查询: ~18ms(3次DB查询 + 1次Redis写入) - 缓存命中: ~1.5ms(1次Redis查询) - TTL: 30分钟,自动失效机制保证数据一致性
君鸿卡管系统 - 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 元(钱包跟着卡走)
- ✅ 无需任何额外操作,自然流转
钱包归属规则
// 钱包模型
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
└── 所有用户可见
数据权限自动过滤
// 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)和超级管理员自动跳过(详见 功能总结、使用指南 和 权限检查使用指南)
- 商户管理:完整的商户(Shop)和商户账号管理功能,支持商户创建时自动创建初始坐席账号、删除商户时批量禁用关联账号、账号密码重置等功能(详见 使用指南 和 API 文档)
- B 端认证系统:完整的后台和 H5 认证功能,支持基于 Redis 的 Token 管理和双令牌机制(Access Token 24h + Refresh Token 7天);包含登录、登出、Token 刷新、用户信息查询和密码修改功能;通过用户类型隔离确保后台(SuperAdmin、Platform、Agent)和 H5(Agent、Enterprise)的访问控制;详见 API 文档、使用指南 和 架构说明
- 生命周期管理:物联网卡/号卡的开卡、激活、停机、复机、销户
- 代理商体系:层级管理和分佣结算
- 批量同步:卡状态、实名状态、流量使用情况
- 分佣验证指引:对代理分佣的冻结、解冻、提现校验流程进行了结构化说明与流程图,详见 分佣逻辑正确与否验证
用户体系设计
系统支持四种用户类型和两种组织实体,实现分层级的多租户管理:
用户类型
- 平台用户:平台管理员,具有最高权限,可分配多个角色
- 代理账号:店铺(代理商)员工账号,归属于特定店铺,权限相同
- 企业账号:企业客户账号,归属于特定企业,一企业一账号
- 个人客户:个人用户,独立表存储,支持微信绑定,不参与 RBAC 体系
组织实体
-
店铺(Shop):代理商组织实体,支持最多 7 级层级关系
- 一级代理直接归属于平台
- 下级代理归属于上级店铺(通过
parent_id字段) - 一个店铺可以有多个账号(代理员工)
-
企业(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
详细设计文档参见:
数据权限模型
系统采用基于用户类型的自动数据权限过滤策略,通过 GORM Callback 自动应用,无需在每个查询中手动添加过滤条件。
过滤规则
| 用户类型 | 过滤策略 | 示例 |
|---|---|---|
| 超级管理员(Super Admin) | 跳过过滤,查看所有数据 | - |
| 平台用户(Platform) | 跳过过滤,查看所有数据 | - |
| 代理账号(Agent) | 基于店铺层级过滤 | WHERE shop_id IN (当前店铺及下级店铺) |
| 企业账号(Enterprise) | 基于企业归属过滤 | WHERE enterprise_id = 当前企业ID |
| 个人客户(Personal Customer) | 基于创建者过滤 | WHERE creator = 当前用户ID |
工作机制
- 认证中间件设置完整用户上下文(
UserContextInfo)到context中 - GORM Callback在每次查询前自动注入过滤条件
- 递归查询 + 缓存:代理用户的下级店铺 ID 通过
GetSubordinateShopIDs()递归查询,结果缓存 30 分钟 - 跳过过滤:特殊场景(如统计、后台任务)可使用
SkipDataPermission(ctx)绕过过滤
使用示例
// 1. 认证后 context 已自动包含用户信息
ctx := c.UserContext()
// 2. 所有 Store 层查询自动应用数据权限过滤
orders, err := orderStore.List(ctx) // 自动过滤为当前用户可见的订单
// 3. 需要查询所有数据时,显式跳过过滤
ctx = gorm.SkipDataPermission(ctx)
allOrders, err := orderStore.List(ctx) // 查询所有订单(仅限特殊场景)
详细说明参见:
快速开始
# 安装依赖
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 中自定义默认管理员信息:
default_admin:
username: "自定义用户名"
password: "自定义密码"
phone: "自定义手机号"
注意事项:
- 系统只在数据库无超级管理员账号时才创建
- 如果已存在超级管理员,启动时会跳过创建
- 建议首次登录后立即修改默认密码
- 初始化日志记录在
logs/app.log中
详细设置和测试说明请参阅 快速开始指南。
项目结构
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()等
- 请求 ID:
中间件注册(cmd/api/main.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 环境变量以加载特定配置:
# 开发环境(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进行线程安全的配置更新
示例:
# 在服务器运行时编辑配置
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":"配置重新加载成功"}
测试
运行所有测试
# 运行所有单元和集成测试
go test ./...
# 带覆盖率运行
go test -cover ./...
# 详细输出运行
go test -v ./...
运行特定测试套件
# 仅单元测试
go test ./pkg/...
# 仅集成测试
go test ./tests/integration/...
# 特定测试
go test -v ./internal/middleware -run TestKeyAuth
集成测试
集成测试需要 Redis 运行:
# 启动 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)
背景:清理技术债务,统一框架设计
主要变更:
- 清理示例代码:删除所有 user/order 示例业务代码,保持代码库整洁
- 统一认证中间件:合并两套 Auth 实现到
pkg/middleware/auth.go,统一错误处理格式 - 简化错误结构:删除 AppError 的 HTTPStatus 字段,避免字段冗余
- 组件注册解耦:创建
internal/bootstrap/包实现自动化组件初始化- 按模块拆分:
stores.go、services.go、handlers.go - main.go 简化为一行:
handlers, err := bootstrap.Bootstrap(deps)
- 按模块拆分:
- 数据权限自动化:实现 GORM Callback 自动注入数据权限过滤
- 基于 creator 字段自动过滤(普通用户只能看到自己和下级的数据)
- root 用户自动跳过过滤
- 支持通过
gorm.SkipDataPermission(ctx)手动绕过 - 删除未使用的
scopes.go手动 Scope 函数
设计原则:
- 保持 Go 惯用模式,避免 Java 风格过度抽象
- 使用显式依赖注入,不引入复杂的 DI 框架
- 每个文件保持 < 100 行,职责单一
- 在关键扩展点添加 TODO 标记
详细文档:
开发规范
依赖注入
通过 Service 和 Store 结构体统一管理依赖:
// 初始化
st := store.New(db)
svc := service.New(st, queueClient, logger)
// 使用
svc.SIM.Activate(...)
svc.Commission.Calculate(...)
事务处理
store.Transaction(ctx, func(tx *store.Store) error {
tx.SIM.UpdateStatus(...)
tx.Commission.Create(...)
return nil
})
异步任务
- 高频任务:批量状态同步、流量同步、实名检查
- 业务任务:分佣计算、生命周期变更通知
- 任务优先级:critical > default > low
常量和 Redis Key 管理
所有常量统一在 pkg/constants/ 目录管理:
// 业务常量
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)
文档
- 快速开始指南:详细设置和测试说明
- 限流指南:全面的限流配置和使用
- 错误处理使用指南:错误码参考、Handler 使用、客户端处理、最佳实践
- 错误处理架构说明:架构设计、性能优化、扩展性说明
- 实现计划:设计决策和架构
- 数据模型:配置结构和 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 定义的核心原则:
- 技术栈遵守:严格使用 Fiber + GORM + Viper + Zap + Asynq,禁止原生调用快捷方式
- 代码质量标准:遵循 Handler → Service → Store → Model 分层架构
- 测试标准:70%+ 测试覆盖率,核心业务 90%+
- 用户体验一致性:统一 JSON 响应格式、RESTful API、双语错误消息
- 性能要求:API P95 < 200ms,P99 < 500ms,合理使用批量操作和异步任务
详细原则和规则请参阅宪章文档。
Speckit 命令
# 创建功能规范
/speckit.specify "功能描述"
# 明确规范细节
/speckit.clarify
# 生成实现计划
/speckit.plan
# 生成任务列表
/speckit.tasks
# 执行实现
/speckit.implement
# 一致性分析
/speckit.analyze
# 生成自定义检查清单
/speckit.checklist "检查项要求"
# 更新项目宪章
/speckit.constitution "宪章更新说明"
设计原则
- 简单实用:不过度设计,够用就好
- 直接实现:避免不必要的接口抽象
- 统一管理:依赖集中初始化,避免参数传递
- 职责分离:API 和 Worker 独立部署,便于扩展
许可证
MIT License