# 君鸿卡管系统 Constitution ## Core Principles ### I. Tech Stack Adherence (技术栈遵守) **规则 (RULES):** - 开发时 **MUST** 严格遵守项目定义的技术栈:Fiber + GORM + Viper + Zap + Lumberjack.v2 + Validator + sonic JSON + Asynq + PostgreSQL - **MUST NOT** 使用原生调用或绕过框架的快捷方式(禁止 `database/sql` 直接调用、禁止 `net/http` 替代 Fiber、禁止 `encoding/json` 替代 sonic) - 所有 HTTP 路由和中间件 **MUST** 使用 Fiber 框架 - 所有数据库操作 **MUST** 通过 GORM 进行 - 所有配置管理 **MUST** 使用 Viper - 所有日志记录 **MUST** 使用 Zap + Lumberjack.v2 - 所有 JSON 序列化 **MUST** 使用 sonic - 所有异步任务 **MUST** 使用 Asynq - **MUST** 使用 Go 官方工具链:`go fmt`、`go vet`、`golangci-lint` - **MUST** 使用 Go Modules 进行依赖管理 **理由 (RATIONALE):** 一致的技术栈使用确保代码可维护性、团队协作效率和长期技术债务可控。绕过框架的"快捷方式"会导致代码碎片化、难以调试、性能不一致和安全漏洞。框架选择已经过深思熟虑,必须信任并充分利用其生态系统。Go 官方工具链确保代码风格一致性和质量。 --- ### II. Code Quality Standards (代码质量标准) **规则 (RULES):** - 代码 **MUST** 遵循项目分层架构:`Handler → Service → Store → Model` - Handler 层 **MUST ONLY** 处理 HTTP 请求/响应,不得包含业务逻辑 - Service 层 **MUST** 包含所有业务逻辑,支持跨模块调用 - Store 层 **MUST** 统一管理所有数据访问,支持事务处理 - Model 层 **MUST** 定义清晰的数据结构和 DTO - 所有依赖 **MUST** 通过结构体字段进行依赖注入(不使用构造函数模式) - 所有公共错误 **MUST** 在 `pkg/errors/` 中定义,使用统一错误码 - 所有 API 响应 **MUST** 使用 `pkg/response/` 的统一格式 - 所有常量 **MUST** 在 `pkg/constants/` 中定义和管理 - 所有 Redis key **MUST** 通过 `pkg/constants/` 中的 Key 生成函数统一管理 - **MUST** 为所有导出的函数、类型和常量编写 Go 风格的文档注释(`// FunctionName does something...`) - **MUST** 避免 magic numbers 和 magic strings,使用常量定义 **Go 代码风格要求:** - **MUST** 使用 `gofmt` 格式化所有代码 - **MUST** 遵循 [Effective Go](https://go.dev/doc/effective_go) 和 [Go Code Review Comments](https://go.dev/wiki/CodeReviewComments) - 变量命名 **MUST** 使用 Go 风格:`userID`(不是 `userId`)、`HTTPServer`(不是 `HttpServer`) - 缩写词 **MUST** 全部大写或全部小写:`URL`、`ID`、`HTTP`(导出)或 `url`、`id`、`http`(未导出) - 包名 **MUST** 简短、小写、单数、无下划线:`user`、`order`、`pkg`(不是 `users`、`userService`、`user_service`) - 接口命名 **SHOULD** 使用 `-er` 后缀:`Reader`、`Writer`、`Logger`(不是 `ILogger`、`LoggerInterface`) **常量管理规范 (Constants Management):** - 业务常量(状态码、类型枚举等)**MUST** 定义在 `pkg/constants/constants.go` 或按模块分文件 - Redis key **MUST** 使用函数生成,不允许硬编码字符串拼接 - Redis key 生成函数 **MUST** 遵循命名规范:`Redis{Module}{Purpose}Key(params...)` - Redis key 格式 **MUST** 使用冒号分隔:`{module}:{purpose}:{identifier}` - 示例: ```go // 正确:使用常量和生成函数 constants.SIMStatusActive constants.RedisSIMStatusKey(iccid) // 生成 "sim:status:{iccid}" // 错误:硬编码和拼接 status := "active" key := "sim:status:" + iccid ``` **理由 (RATIONALE):** 清晰的分层架构和代码组织使代码易于理解、测试和维护。统一的错误处理和响应格式提升 API 一致性和客户端集成体验。依赖注入模式便于单元测试和模块替换。集中管理常量和 Redis key 避免拼写错误、重复定义和命名不一致,提升代码可维护性和重构安全性。Redis key 统一管理便于监控、调试和缓存策略调整。遵循 Go 官方代码风格确保代码一致性和可读性。 --- ### III. Testing Standards (测试标准) **规则 (RULES):** - 所有核心业务逻辑(Service 层)**MUST** 有单元测试覆盖 - 所有 API 端点 **MUST** 有集成测试覆盖 - 所有数据库操作 **SHOULD** 有事务回滚测试 - 测试 **MUST** 使用 Go 标准测试框架(`testing` 包) - 测试文件 **MUST** 与源文件同目录,命名为 `*_test.go` - 测试函数 **MUST** 使用 `Test` 前缀:`func TestUserCreate(t *testing.T)` - 基准测试 **MUST** 使用 `Benchmark` 前缀:`func BenchmarkUserCreate(b *testing.B)` - 测试 **MUST** 可独立运行,不依赖外部服务(使用 mock 或 testcontainers) - 单元测试 **MUST** 在 100ms 内完成 - 集成测试 **SHOULD** 在 1s 内完成 - 测试覆盖率 **SHOULD** 达到 70% 以上(核心业务代码必须 90% 以上) - 测试 **MUST** 使用 table-driven tests 处理多个测试用例 - 测试 **MUST** 使用 `t.Helper()` 标记辅助函数 **Table-Driven Test 示例:** ```go func TestUserValidate(t *testing.T) { tests := []struct { name string user User wantErr bool }{ {"valid user", User{Name: "John", Email: "john@example.com"}, false}, {"empty name", User{Name: "", Email: "john@example.com"}, true}, {"invalid email", User{Name: "John", Email: "invalid"}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.user.Validate() if (err != nil) != tt.wantErr { t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) } }) } } ``` **理由 (RATIONALE):** 高质量的测试是代码质量的基石。单元测试确保业务逻辑正确性,集成测试确保模块间协作正常。快速的测试执行时间保证开发效率。测试独立性避免环境依赖导致的 flaky tests。Table-driven tests 使测试更简洁、易于扩展和维护,这是 Go 社区的最佳实践。 --- ### IV. User Experience Consistency (用户体验一致性) **规则 (RULES):** - 所有 API 响应 **MUST** 使用统一的 JSON 格式: ```json { "code": 0, "message": "success", "data": {}, "timestamp": "2025-11-10T15:30:00Z" } ``` - 所有错误响应 **MUST** 包含明确的错误码和错误消息(中英文双语) - 所有 API 端点 **MUST** 遵循 RESTful 设计原则 - 所有分页 API **MUST** 使用统一的分页参数:`page`、`page_size`、`total` - 所有时间字段 **MUST** 使用 ISO 8601 格式(RFC3339) - 所有货币金额 **MUST** 使用整数表示(分为单位),避免浮点精度问题 - 所有布尔字段 **MUST** 使用 `true`/`false`,不使用 `0`/`1` - API 版本 **MUST** 通过 URL 路径管理(如 `/api/v1/...`) **理由 (RATIONALE):** 一致的 API 设计降低客户端开发成本,减少集成错误。统一的数据格式和错误处理提升系统可预测性。清晰的时间和金额表示避免常见的数据处理错误。 --- ### V. Performance Requirements (性能要求) **规则 (RULES):** - API 响应时间(P95)**MUST** < 200ms(数据库查询 < 50ms) - API 响应时间(P99)**MUST** < 500ms - 批量操作 **MUST** 使用批量查询/插入,避免 N+1 查询问题 - 所有数据库查询 **MUST** 有适当的索引支持 - 列表查询 **MUST** 实现分页,默认 `page_size=20`,最大 `page_size=100` - 异步任务 **MUST** 用于非实时操作(批量同步、分佣计算等) - 内存使用(API 服务)**SHOULD** < 500MB(正常负载) - 内存使用(Worker 服务)**SHOULD** < 1GB(正常负载) - 数据库连接池 **MUST** 配置合理(`MaxOpenConns=25`, `MaxIdleConns=10`, `ConnMaxLifetime=5m`) - Redis 连接池 **MUST** 配置合理(`PoolSize=10`, `MinIdleConns=5`) - 并发操作 **SHOULD** 使用 goroutines 和 channels(不是线程池模式) - **MUST** 使用 `context.Context` 进行超时和取消控制 - **MUST** 使用 `sync.Pool` 复用频繁分配的对象(如缓冲区) **理由 (RATIONALE):** 性能要求确保系统在生产环境下的稳定性和用户体验。批量操作和异步任务避免阻塞主流程。合理的连接池配置平衡性能和资源消耗。明确的性能指标便于监控和优化。使用 Go 的并发原语(goroutines、channels)而不是传统的线程池模式,充分发挥 Go 的并发优势。 --- ### VI. Go Idiomatic Design Principles (Go 语言惯用设计原则) **核心理念:写 Go 味道的代码,不要写 Java 味道的代码** #### 包组织 (Package Organization) **MUST 遵循的原则:** - 包结构 **MUST** 扁平化,避免深层嵌套(最多 2-3 层) - 包 **MUST** 按功能组织,不是按层次组织 - 包名 **MUST** 描述功能,不是类型(`http` 不是 `httputils`、`handlers`) **正确的 Go 风格:** ``` internal/ ├── user/ # user 功能的所有代码 │ ├── handler.go # HTTP handlers │ ├── service.go # 业务逻辑 │ ├── store.go # 数据访问 │ └── model.go # 数据模型 ├── order/ │ ├── handler.go │ ├── service.go │ └── store.go └── sim/ ├── handler.go ├── service.go └── store.go ``` **错误的 Java 风格(禁止):** ``` internal/ ├── handlers/ │ ├── user/ │ │ └── UserHandler.go # ❌ 类名式命名 │ └── order/ │ └── OrderHandler.go ├── services/ │ ├── user/ │ │ ├── IUserService.go # ❌ 接口前缀 I │ │ └── UserServiceImpl.go # ❌ Impl 后缀 │ └── impls/ # ❌ 过度抽象 ├── repositories/ # ❌ Repository 模式过度使用 │ └── interfaces/ └── models/ └── entities/ └── dto/ # ❌ 过度分层 ``` #### 接口设计 (Interface Design) **MUST 遵循的原则:** - 接口 **MUST** 小而专注(1-3 个方法),不是大而全 - 接口 **SHOULD** 在使用方定义,不是实现方(依赖倒置) - 接口命名 **SHOULD** 使用 `-er` 后缀:`Reader`、`Writer`、`Storer` - **MUST NOT** 使用 `I` 前缀或 `Interface` 后缀 - **MUST NOT** 创建只有一个实现的接口(除非明确需要抽象) **正确的 Go 风格:** ```go // 小接口,在使用方定义 type UserStorer interface { Create(ctx context.Context, user *User) error GetByID(ctx context.Context, id string) (*User, error) } // 具体实现 type PostgresStore struct { db *gorm.DB } func (s *PostgresStore) Create(ctx context.Context, user *User) error { return s.db.WithContext(ctx).Create(user).Error } ``` **错误的 Java 风格(禁止):** ```go // ❌ 大接口,方法过多 type IUserRepository interface { Create(user *User) error Update(user *User) error Delete(id string) error FindByID(id string) (*User, error) FindByEmail(email string) (*User, error) FindAll() ([]*User, error) FindByStatus(status string) ([]*User, error) Count() (int, error) // ... 更多方法 } // ❌ I 前缀 type IUserService interface {} // ❌ 不必要的抽象层 type UserRepositoryImpl struct {} // 只有一个实现 ``` #### 错误处理 (Error Handling) **MUST 遵循的原则:** - 错误 **MUST** 显式返回和检查,不使用异常(panic/recover) - 错误处理 **MUST** 紧跟错误产生的代码 - **MUST** 使用 `errors.Is()` 和 `errors.As()` 检查错误类型 - **MUST** 使用 `fmt.Errorf()` 包装错误,保留错误链 - 自定义错误 **SHOULD** 实现 `error` 接口 - panic **MUST ONLY** 用于不可恢复的程序错误 **正确的 Go 风格:** ```go func (s *Service) GetUser(ctx context.Context, id string) (*User, error) { user, err := s.store.GetByID(ctx, id) if err != nil { // 包装错误,添加上下文 return nil, fmt.Errorf("get user by id %s: %w", id, err) } return user, nil } // 自定义错误类型 type NotFoundError struct { Resource string ID string } func (e *NotFoundError) Error() string { return fmt.Sprintf("%s not found: %s", e.Resource, e.ID) } ``` **错误的 Java 风格(禁止):** ```go // ❌ 使用 panic 代替错误返回 func GetUser(id string) *User { user, err := db.Find(id) if err != nil { panic(err) // ❌ 不要这样做 } return user } // ❌ try-catch 风格 func DoSomething() { defer func() { if r := recover(); r != nil { // ❌ 滥用 recover log.Println("recovered:", r) } }() // ... } // ❌ 异常类层次结构 type Exception interface { // ❌ 不需要 GetMessage() string GetStackTrace() []string } type BusinessException struct {} // ❌ 过度设计 type ValidationException struct {} // ❌ 过度设计 ``` #### 结构体和方法 (Structs and Methods) **MUST 遵循的原则:** - 结构体 **MUST** 简单直接,不是类(class)的替代品 - **MUST NOT** 为每个字段创建 getter/setter 方法 - **MUST** 直接访问导出的字段(大写开头) - **MUST** 使用组合(composition)而不是继承(inheritance) - 构造函数 **SHOULD** 命名为 `New` 或 `NewXxx`,返回具体类型 - **MUST NOT** 使用构造器模式(Builder Pattern)除非真正需要 **正确的 Go 风格:** ```go // 简单结构体,导出字段直接访问 type User struct { ID string Name string Email string } // 简单构造函数 func NewUser(name, email string) *User { return &User{ ID: generateID(), Name: name, Email: email, } } // 使用组合 type Service struct { store UserStorer logger *zap.Logger } func NewService(store UserStorer, logger *zap.Logger) *Service { return &Service{ store: store, logger: logger, } } ``` **错误的 Java 风格(禁止):** ```go // ❌ 私有字段 + getter/setter type User struct { id string // ❌ 不需要私有 name string email string } func (u *User) GetID() string { return u.id } // ❌ 不需要 func (u *User) SetID(id string) { u.id = id } // ❌ 不需要 func (u *User) GetName() string { return u.name } // ❌ 不需要 func (u *User) SetName(name string) { u.name = name } // ❌ 不需要 // ❌ 构造器模式(不必要) type UserBuilder struct { user *User } func NewUserBuilder() *UserBuilder { // ❌ 过度设计 return &UserBuilder{user: &User{}} } func (b *UserBuilder) WithName(name string) *UserBuilder { b.user.name = name return b } func (b *UserBuilder) Build() *User { return b.user } // ❌ 工厂模式(过度抽象) type UserFactory interface { // ❌ 不需要 CreateUser(name string) *User } ``` #### 并发模式 (Concurrency Patterns) **MUST 遵循的原则:** - **MUST** 使用 goroutines 和 channels,不是线程和锁(大多数情况) - **MUST** 使用 `context.Context` 传递取消信号 - **MUST** 遵循"通过通信共享内存,不要通过共享内存通信" - **SHOULD** 使用 `sync.WaitGroup` 等待 goroutines 完成 - **SHOULD** 使用 `sync.Once` 确保只执行一次 - **MUST NOT** 创建线程池类(Go 运行时已处理) **正确的 Go 风格:** ```go // 使用 goroutines 和 channels func ProcessBatch(ctx context.Context, items []Item) error { results := make(chan Result, len(items)) errors := make(chan error, len(items)) for _, item := range items { go func(item Item) { result, err := process(ctx, item) if err != nil { errors <- err return } results <- result }(item) } // 收集结果 for i := 0; i < len(items); i++ { select { case <-ctx.Done(): return ctx.Err() case err := <-errors: return err case <-results: // 处理结果 } } return nil } // 使用 sync.WaitGroup func ProcessConcurrently(items []Item) { var wg sync.WaitGroup for _, item := range items { wg.Add(1) go func(item Item) { defer wg.Done() process(item) }(item) } wg.Wait() } ``` **错误的 Java 风格(禁止):** ```go // ❌ 线程池模式 type ThreadPool struct { // ❌ 不需要 workers int taskQueue chan Task wg sync.WaitGroup } func NewThreadPool(workers int) *ThreadPool { // ❌ Go 运行时已处理 return &ThreadPool{ workers: workers, taskQueue: make(chan Task, 100), } } // ❌ Future/Promise 模式(不需要单独实现) type Future struct { // ❌ 直接使用 channel result chan interface{} err chan error } // ❌ 过度使用 mutex(应该用 channel) type SafeMap struct { // ❌ 考虑使用 sync.Map 或 channel mu sync.Mutex data map[string]interface{} } func (m *SafeMap) Get(key string) interface{} { m.mu.Lock() defer m.mu.Unlock() return m.data[key] } ``` #### 命名约定 (Naming Conventions) **MUST 遵循的原则:** - 变量名 **MUST** 简短且符合上下文: - 短作用域用短名字:`i`, `j`, `k` 用于循环 - 长作用域用描述性名字:`userRepository`, `configManager` - 缩写词 **MUST** 保持一致的大小写:`URL`, `HTTP`, `ID`(不是 `Url`, `Http`, `Id`) - **MUST NOT** 使用匈牙利命名法或类型前缀:`strName`, `arrUsers`(禁止) - **MUST NOT** 使用下划线连接(除了测试和包名):`user_service`(禁止,应该是 `userservice` 或 `UserService`) - 方法接收者名称 **SHOULD** 使用 1-2 个字母的缩写,全文件保持一致 **正确的 Go 风格:** ```go // 短作用域,短名字 for i, user := range users { fmt.Println(i, user.Name) } // 长作用域,描述性名字 func ProcessUserRegistration(ctx context.Context, req *RegistrationRequest) error { // ... } // 缩写词大小写一致 type UserAPI struct { BaseURL string APIKey string UserID int64 } // 方法接收者简短一致 type UserService struct {} func (s *UserService) Create(user *User) error { // s 用于 service return s.store.Create(user) } func (s *UserService) Update(user *User) error { // 保持一致使用 s return s.store.Update(user) } ``` **错误的 Java 风格(禁止):** ```go // ❌ 过长的变量名 func ProcessRegistration() { userRegistrationRequest := &Request{} // ❌ 太长 userRegistrationValidator := NewValidator() // ❌ 太长 userRegistrationService := NewService() // ❌ 太长 } // ❌ 匈牙利命名 strUserName := "John" // ❌ intUserAge := 25 // ❌ arrUserList := []User{} // ❌ // ❌ 下划线命名 type User_Service struct {} // ❌ 应该是 UserService func Get_User_By_Id() {} // ❌ 应该是 GetUserByID // ❌ 缩写词大小写不一致 type UserApi struct { // ❌ 应该是 UserAPI BaseUrl string // ❌ 应该是 BaseURL ApiKey string // ❌ 应该是 APIKey UserId int64 // ❌ 应该是 UserID } // ❌ 方法接收者不一致或过长 func (userService *UserService) Create() {} // ❌ 太长 func (us *UserService) Update() {} // ❌ 与上面不一致 func (this *UserService) Delete() {} // ❌ 不要使用 this/self ``` #### 反模式清单 (Anti-Patterns to Avoid) **严格禁止的 Java 风格模式:** 1. ❌ **过度抽象**:不需要的接口、工厂、构造器 2. ❌ **Getter/Setter**:直接访问导出字段 3. ❌ **继承层次**:使用组合,不是嵌入 4. ❌ **异常处理**:使用错误返回,不是 panic/recover 5. ❌ **单例模式**:使用包级别变量或 `sync.Once` 6. ❌ **线程池**:直接使用 goroutines 7. ❌ **深层包嵌套**:保持扁平结构 8. ❌ **类型前缀**:`IService`, `AbstractBase`, `ServiceImpl` 9. ❌ **Bean 风格**:不需要 POJO/JavaBean 模式 10. ❌ **过度 DI 框架**:简单直接的依赖注入 **理由 (RATIONALE):** Go 语言的设计哲学强调简单性、直接性和实用性。Java 风格的模式(深层继承、过度抽象、复杂的设计模式)违背了 Go 的核心理念。Go 提供了更简单、更高效的方式来解决相同的问题:接口的隐式实现、结构体组合、显式错误处理、轻量级并发。遵循 Go 惯用法不仅使代码更地道,还能充分发挥 Go 的性能优势和简洁性。 --- ## Development Workflow (开发工作流程) ### 分支管理 - **MUST** 从 `main` 分支创建功能分支 - 功能分支命名格式:`feature/###-brief-description` 或 `fix/###-brief-description` - **MUST** 在合并前保持分支与 `main` 同步(rebase 或 merge) - 合并到 `main` **MUST** 通过 Pull Request 并经过代码审查 ### 提交规范 - 提交信息 **MUST** 遵循 Conventional Commits 格式: - `feat: 新功能描述` - `fix: 修复问题描述` - `refactor: 重构描述` - `test: 测试相关` - `docs: 文档更新` - `chore: 构建/工具相关` - 提交信息 **SHOULD** 简洁明了(中文或英文) ### 代码审查 - 所有 PR **MUST** 至少有一人审查通过 - 审查者 **MUST** 验证: - 代码符合本宪章所有原则(特别是 Go 惯用法原则) - 无 Java 风格的反模式(getter/setter、过度抽象等) - 测试覆盖充分且通过 - 无安全漏洞(SQL 注入、XSS、命令注入等) - 性能影响可接受 - 代码通过 `gofmt`、`go vet`、`golangci-lint` 检查 --- ## Quality Gates (质量关卡) ### 代码合并前检查 - [ ] 所有测试通过(`go test ./...`) - [ ] 代码格式化(`gofmt -l .` 无输出) - [ ] 代码静态检查通过(`go vet ./...`) - [ ] 代码质量检查通过(`golangci-lint run`) - [ ] 测试覆盖率符合标准(`go test -cover ./...`) - [ ] 无 TODO/FIXME 遗留(或已记录 Issue) - [ ] API 文档已更新(如有 API 变更) - [ ] 数据库迁移文件已创建(如有 schema 变更) - [ ] 常量和 Redis key 使用符合规范(无硬编码字符串) - [ ] **无 Java 风格反模式**(无 getter/setter、无不必要接口、无过度抽象) - [ ] **遵循 Go 命名约定**(缩写大小写一致、包名简短、无下划线) - [ ] **使用 Go 惯用并发模式**(goroutines/channels,无线程池类) ### 上线前检查 - [ ] 所有功能在 staging 环境测试通过 - [ ] 性能测试通过(响应时间、内存使用、并发能力) - [ ] 数据库迁移在 staging 环境验证通过 - [ ] 监控和告警配置完成 - [ ] 回滚方案已准备 --- ## Governance 本宪章是所有开发实践的最高指导原则,优先级高于个人偏好或临时便利。 ### 修订流程 - 宪章修订 **MUST** 经过团队讨论并达成共识 - 修订 **MUST** 包含明确的理由和影响分析 - 修订 **MUST** 更新版本号(遵循语义化版本) - 修订 **MUST** 同步更新相关模板(plan-template.md、spec-template.md、tasks-template.md) ### 版本策略 - **MAJOR**: 移除或重新定义核心原则(不兼容变更) - **MINOR**: 新增原则或显著扩展指导内容 - **PATCH**: 澄清说明、措辞优化、错误修正 ### 合规审查 - 所有 PR 审查 **MUST** 验证是否符合本宪章 - 违反宪章的代码 **MUST** 在合并前修正 - 特别关注 Go 惯用法原则,拒绝 Java 风格的代码 - 任何复杂性增加(新依赖、新架构层)**MUST** 在设计文档中明确说明必要性 ### 运行时开发指导 开发时参考本宪章确保一致性。如有疑问,优先遵守原则,再讨论例外情况。记住:**写 Go 代码,不是用 Go 语法写 Java**。 --- **Version**: 2.0.0 | **Ratified**: 2025-11-10 | **Last Amended**: 2025-11-10