Files
junhong_cmp_fiber/.specify/memory/constitution.md

42 KiB
Raw Blame History

君鸿卡管系统 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 fmtgo vetgolangci-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 通过结构体字段进行依赖注入(不使用构造函数模式)
  • 所有公共错误 MUSTpkg/errors/ 中定义,使用统一错误码
  • 所有 API 响应 MUST 使用 pkg/response/ 的统一格式
  • 所有常量 MUSTpkg/constants/ 中定义和管理
  • 所有 Redis key MUST 通过 pkg/constants/ 中的 Key 生成函数统一管理
  • MUST 为所有导出的函数、类型和常量编写 Go 风格的文档注释(// FunctionName does something...
  • MUST 避免 magic numbers 和 magic strings使用常量定义

Go 代码风格要求:

  • MUST 使用 gofmt 格式化所有代码
  • MUST 遵循 Effective GoGo Code Review Comments
  • 变量命名 MUST 使用 Go 风格:userID(不是 userId)、HTTPServer(不是 HttpServer
  • 缩写词 MUST 全部大写或全部小写:URLIDHTTP(导出)或 urlidhttp(未导出)
  • 包名 MUST 简短、小写、单数、无下划线:userorderpkg(不是 usersuserServiceuser_service
  • 接口命名 SHOULD 使用 -er 后缀:ReaderWriterLogger(不是 ILoggerLoggerInterface

常量管理规范 (Constants Management):

  • 业务常量(状态码、类型枚举等)MUST 定义在 pkg/constants/constants.go 或按模块分文件
  • Redis key MUST 使用函数生成,不允许硬编码字符串拼接
  • Redis key 生成函数 MUST 遵循命名规范:Redis{Module}{Purpose}Key(params...)
  • Redis key 格式 MUST 使用冒号分隔:{module}:{purpose}:{identifier}
  • 示例:
    // 正确:使用常量和生成函数
    constants.SIMStatusActive
    constants.RedisSIMStatusKey(iccid)  // 生成 "sim:status:{iccid}"
    
    // 错误:硬编码和拼接
    status := "active"
    key := "sim:status:" + iccid
    

Magic Numbers 和硬编码规则 (Magic Numbers and Hardcoding Rules):

  • MUST NOT 在代码中直接使用 magic numbers未定义含义的数字字面量
  • MUST NOT 在代码中硬编码字符串字面量URL、状态码、配置值、业务规则等
  • 当相同的字面量值在 3 个或以上位置使用时,MUST 提取为常量
  • 已定义的常量 MUST 被使用,MUST NOT 重复硬编码相同的值
  • 只允许在以下情况使用字面量:
    • 语言特性:niltruefalse
    • 数学常量:01-1(用于循环、索引、比较等明确的上下文)
    • 一次性使用的临时值(测试数据、日志消息等)

正确的常量使用:

// pkg/constants/constants.go
const (
    DefaultPageSize = 20
    MaxPageSize     = 100
    MinPageSize     = 1
    
    SIMStatusActive   = "active"
    SIMStatusInactive = "inactive"
    SIMStatusSuspended = "suspended"
    
    OrderTypeRecharge = "recharge"
    OrderTypeTransfer = "transfer"
)

// internal/handler/user.go
func (h *Handler) ListUsers(c *fiber.Ctx) error {
    page := c.QueryInt("page", 1)
    pageSize := c.QueryInt("page_size", constants.DefaultPageSize) // ✅ 使用常量
    
    if pageSize > constants.MaxPageSize {  // ✅ 使用常量
        pageSize = constants.MaxPageSize
    }
    
    users, err := h.service.List(page, pageSize)
    // ...
}

// internal/service/sim.go
func (s *Service) Activate(iccid string) error {
    return s.store.UpdateStatus(iccid, constants.SIMStatusActive) // ✅ 使用常量
}

错误的硬编码模式:

// ❌ 硬编码 magic number在多处使用
func ListUsers(c *fiber.Ctx) error {
    pageSize := c.QueryInt("page_size", 20)  // ❌ 硬编码 20
    if pageSize > 100 {                       // ❌ 硬编码 100
        pageSize = 100
    }
    // ...
}

func ListOrders(c *fiber.Ctx) error {
    pageSize := c.QueryInt("page_size", 20)  // ❌ 重复硬编码 20
    // ...
}

// ❌ 硬编码字符串(在多处使用)
func ActivateSIM(iccid string) error {
    return UpdateStatus(iccid, "active")      // ❌ 硬编码 "active"
}

func IsSIMActive(sim *SIM) bool {
    return sim.Status == "active"             // ❌ 重复硬编码 "active"
}

// ❌ 已定义常量但不使用
// 常量已定义在 pkg/response/codes.go
func Success(c *fiber.Ctx, data any) error {
    return c.JSON(Response{
        Code: 0,  // ❌ 应该使用 errors.CodeSuccess
        Data: data,
    })
}

可接受的字面量使用:

// ✅ 语言特性和明确上下文的数字
if user == nil {                              // ✅ nil
    return errors.New("user not found")
}

for i := 0; i < len(items); i++ {            // ✅ 0, 1 用于循环
    // ...
}

if count == 1 {                               // ✅ 1 用于比较
    // 特殊处理单个元素
}

// ✅ 测试数据和日志消息
func TestUserCreate(t *testing.T) {
    user := &User{
        Name:  "Test User",                   // ✅ 测试数据
        Email: "test@example.com",
    }
    // ...
}

logger.Info("processing request",            // ✅ 日志消息
    zap.String("path", c.Path()),
    zap.Int("status", 200))

中文注释和输出规范 (Chinese Comments and Output Standards):

本项目面向中文开发者,为提高代码可读性和维护效率,SHOULD 优先使用中文进行注释和输出。

  • 代码注释implementation commentsSHOULD 使用中文
  • 日志消息log messagesSHOULD 使用中文
  • 用户可见的错误消息 MUST 使用中文(通过 pkg/errors/ 的双语消息支持)
  • 内部错误消息和调试日志 SHOULD 使用中文
  • Go 文档注释doc comments for exported APIsMAY 使用英文以保持生态兼容性,但中文注释更佳
  • 变量名、函数名、类型名 MUST 使用英文(遵循 Go 命名规范)

正确的中文注释使用:

// GetUserByID 根据用户 ID 获取用户信息
// 如果用户不存在,返回 NotFoundError
func (s *Service) GetUserByID(ctx context.Context, id string) (*User, error) {
    // 参数验证
    if id == "" {
        return nil, errors.New(errors.CodeInvalidParam, "用户 ID 不能为空")
    }
    
    // 从存储层获取用户
    user, err := s.store.GetByID(ctx, id)
    if err != nil {
        s.logger.Error("获取用户失败",
            zap.String("user_id", id),
            zap.Error(err))
        return nil, fmt.Errorf("获取用户失败: %w", err)
    }
    
    // 检查用户状态
    if user.Status != constants.UserStatusActive {
        s.logger.Warn("用户状态异常",
            zap.String("user_id", id),
            zap.String("status", user.Status))
    }
    
    return user, nil
}

正确的中文日志使用:

// 信息日志
logger.Info("服务启动成功",
    zap.String("host", cfg.Server.Host),
    zap.Int("port", cfg.Server.Port))

// 警告日志
logger.Warn("Redis 连接延迟较高",
    zap.Duration("latency", latency),
    zap.String("threshold", "50ms"))

// 错误日志
logger.Error("数据库连接失败",
    zap.String("host", cfg.DB.Host),
    zap.Int("port", cfg.DB.Port),
    zap.Error(err))

理由 (RATIONALE):

清晰的分层架构和代码组织使代码易于理解、测试和维护。统一的错误处理和响应格式提升 API 一致性和客户端集成体验。依赖注入模式便于单元测试和模块替换。集中管理常量和 Redis key 避免拼写错误、重复定义和命名不一致提升代码可维护性和重构安全性。Redis key 统一管理便于监控、调试和缓存策略调整。遵循 Go 官方代码风格确保代码一致性和可读性。

避免硬编码和强制使用常量的规则能够:

  1. 提高可维护性:修改常量值只需改一处,不需要搜索所有硬编码位置
  2. 减少错误:避免手动输入错误(拼写错误、大小写错误)
  3. 增强可读性constants.MaxPageSize100 更能表达意图
  4. 便于重构IDE 可以追踪常量使用,重命名时不会遗漏
  5. 统一业务规则:确保所有地方使用相同的业务规则值
  6. "3 次规则"提供明确的阈值,避免过早优化,同时确保重复值被及时抽取

使用中文注释和日志的理由:

  1. 提高团队效率:中文开发团队阅读中文注释更快速准确
  2. 降低理解成本:减少翻译和理解偏差
  3. 改善调试体验:中文日志更易于排查问题
  4. 保持生态兼容:导出 API 的文档注释可保留英文

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 示例:

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 格式:
    {
      "code": 0,
      "message": "success",
      "data": {},
      "timestamp": "2025-11-10T15:30:00Z"
    }
    
  • 所有错误响应 MUST 包含明确的错误码和错误消息(中英文双语)
  • 所有 API 端点 MUST 遵循 RESTful 设计原则
  • 所有分页 API MUST 使用统一的分页参数:pagepage_sizetotal
  • 所有时间字段 MUST 使用 ISO 8601 格式RFC3339
  • 所有货币金额 MUST 使用整数表示(分为单位),避免浮点精度问题
  • 所有布尔字段 MUST 使用 true/false,不使用 0/1
  • API 版本 MUST 通过 URL 路径管理(如 /api/v1/...

理由 (RATIONALE):

一致的 API 设计降低客户端开发成本,减少集成错误。统一的数据格式和错误处理提升系统可预测性。清晰的时间和金额表示避免常见的数据处理错误。


V. Performance Requirements (性能要求)

规则 (RULES):

  • API 响应时间P95MUST < 200ms数据库查询 < 50ms
  • API 响应时间P99MUST < 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 不是 httputilshandlers

正确的 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 后缀:ReaderWriterStorer
  • MUST NOT 使用 I 前缀或 Interface 后缀
  • MUST NOT 创建只有一个实现的接口(除非明确需要抽象)

正确的 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 风格(禁止):

// ❌ 大接口,方法过多
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 风格:

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 风格(禁止):

// ❌ 使用 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 命名为 NewNewXxx,返回具体类型
  • MUST NOT 使用构造器模式Builder Pattern除非真正需要

正确的 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 风格(禁止):

// ❌ 私有字段 + 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 风格:

// 使用 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 风格(禁止):

// ❌ 线程池模式
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(禁止,应该是 userserviceUserService
  • 方法接收者名称 SHOULD 使用 1-2 个字母的缩写,全文件保持一致

正确的 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 风格(禁止):

// ❌ 过长的变量名
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 的性能优势和简洁性。


VII. Documentation Standards (文档规范)

规则 (RULES):

  • 每个功能完成后 MUSTdocs/ 目录创建总结文档
  • 总结文档路径 MUST 遵循规范:docs/{feature-id}/ 对应 specs/{feature-id}/
  • 总结文档文件名 MUST 使用中文命名(例如:功能总结.md使用指南.md架构说明.md
  • 总结文档内容 MUST 使用中文编写
  • 每次添加新功能总结文档时 MUST 同步更新 README.md
  • README.md 中的功能描述 MUST 简短精炼,让首次接触项目的开发者能快速了解
  • README.md 的功能描述 SHOULD 控制在 2-3 句话以内

文档结构示例:

specs/001-fiber-middleware-integration/  # 功能规划文档(设计阶段)
├── spec.md
├── plan.md
├── tasks.md
└── quickstart.md

docs/001-fiber-middleware-integration/   # 功能总结文档(完成阶段)
├── 功能总结.md                          # 功能概述和实现要点
├── 使用指南.md                          # 如何使用该功能
└── 架构说明.md                          # 架构设计和技术决策(可选)

README.md                                 # 项目主文档
## 核心功能
- **认证中间件**:基于 Redis 的 Token 认证
- **限流中间件**:基于 IP 的限流,支持可配置的限制和存储后端

正确的文档组织:

<!-- docs/002-user-management/功能总结.md -->
# 用户管理功能总结

## 功能概述

实现了完整的用户 CRUD 操作,包括用户创建、查询、更新、删除和列表分页。

## 核心实现

- Handler: `internal/handler/user.go`
- Service: `internal/service/user.go`
- Store: `internal/store/postgres/user.go`
- Model: `internal/model/user.go`

## API 端点

- `POST /api/v1/users` - 创建用户
- `GET /api/v1/users/:id` - 获取用户
- `PUT /api/v1/users/:id` - 更新用户
- `DELETE /api/v1/users/:id` - 删除用户
- `GET /api/v1/users` - 用户列表(分页)

## 技术要点

- 使用 GORM 进行数据访问
- 使用 Validator 进行参数验证
- 支持事务处理
- 统一错误处理和响应格式

详细使用说明请参阅 [使用指南.md](./使用指南.md)
<!-- README.md 更新示例 -->
## 核心功能

- **认证中间件**:基于 Redis 的 Token 认证
- **限流中间件**IP 限流,支持 Memory 和 Redis 存储
- **用户管理**:完整的用户 CRUD 和分页列表功能
- **日志系统**Zap 结构化日志,自动轮转和请求追踪

详细文档:
- [认证中间件](docs/001-fiber-middleware-integration/功能总结.md)
- [用户管理](docs/002-user-management/功能总结.md)

错误的文档组织(禁止):

❌ docs/feature-summary.md               # 所有功能混在一个文件
❌ docs/001-summary.md                   # 英文命名
❌ docs/001/summary.md                   # 英文内容
❌ specs/001.../implementation.md        # 总结文档放在 specs/ 下
❌ README.md 中没有更新新功能             # 遗漏 README 更新
❌ README.md 功能描述过长(超过 5 句话)  # 描述冗长

README.md 更新要求:

  • 简洁性:每个功能 2-3 句话描述核心价值
  • 可读性:使用中文,便于中文开发者快速理解
  • 链接性:提供到详细文档的链接
  • 分类性:按功能模块分组(如"核心功能"、"中间件"、"业务模块"等)

理由 (RATIONALE):

统一的文档结构使新成员快速了解项目功能和架构。中文文档降低中文团队的阅读门槛,提高协作效率。将总结文档与设计文档分离(docs/ vs specs/),清晰区分"计划做什么"和"做了什么"。强制更新 README.md 确保项目主文档始终反映最新功能。简短的 README 描述让首次接触项目的开发者能在 5 分钟内了解项目全貌。


VIII. Access Logging Standards (访问日志规范)

规则 (RULES):

  • 所有 HTTP 请求 MUST 被记录到 access.log,无例外
  • 访问日志 MUST 记录完整的请求参数query 参数 + request body
  • 访问日志 MUST 记录完整的响应参数response body
  • 请求/响应 body MUST 限制大小为 50KB超过部分截断并标注 ... (truncated)
  • 访问日志 MUST 通过统一的 Logger 中间件(pkg/logger/Middleware())记录
  • 任何中间件 的短路返回(认证失败、限流拒绝、参数验证失败等)MUST NOT 绕过访问日志
  • 访问日志 MUST 包含以下字段(最低要求):
    • method: HTTP 方法
    • path: 请求路径
    • query: Query 参数字符串
    • status: HTTP 状态码
    • duration_ms: 请求耗时(毫秒)
    • request_id: 请求唯一 ID
    • ip: 客户端 IP
    • user_agent: 用户代理
    • user_id: 用户 ID认证后有值否则为空
    • request_body: 请求体(限制 50KB
    • response_body: 响应体(限制 50KB
  • 访问日志 SHOULD 使用 JSON 格式,便于日志分析和监控
  • 访问日志文件 MUST 配置自动轮转(基于大小或时间)

正确的访问日志示例:

{
  "level": "info",
  "timestamp": "2025-11-11T17:45:03.186+0800",
  "message": "",
  "method": "POST",
  "path": "/api/v1/users",
  "query": "page=1&size=10",
  "status": 400,
  "duration_ms": 0.035,
  "request_id": "f1d8b767-dfb3-4588-9fa0-8a97e5337184",
  "ip": "127.0.0.1",
  "user_agent": "curl/8.7.1",
  "user_id": "",
  "request_body": "{\"username\":\"testuser\",\"email\":\"test@example.com\"}",
  "response_body": "{\"code\":1001,\"data\":null,\"msg\":\"缺失认证令牌\",\"timestamp\":\"2025-11-11T17:45:03+08:00\"}"
}

Logger 中间件实现要求:

// pkg/logger/middleware.go
func Middleware() fiber.Handler {
    return func(c *fiber.Ctx) error {
        startTime := time.Now()
        
        // 在 c.Next() 之前读取请求 body
        requestBody := truncateBody(c.Body(), MaxBodyLogSize)
        queryParams := string(c.Request().URI().QueryString())
        
        // 处理请求(可能被中间件短路返回)
        err := c.Next()
        
        // 在 c.Next() 之后读取响应 body无论是否短路
        responseBody := truncateBody(c.Response().Body(), MaxBodyLogSize)
        
        // 记录完整的访问日志(包含请求和响应参数)
        accessLogger.Info("",
            zap.String("method", c.Method()),
            zap.String("path", c.Path()),
            zap.String("query", queryParams),
            zap.Int("status", c.Response().StatusCode()),
            zap.Float64("duration_ms", time.Since(startTime).Seconds()*1000),
            zap.String("request_id", getRequestID(c)),
            zap.String("ip", c.IP()),
            zap.String("user_agent", c.Get("User-Agent")),
            zap.String("user_id", getUserID(c)),
            zap.String("request_body", requestBody),
            zap.String("response_body", responseBody),
        )
        
        return err
    }
}

禁止的做法:

// ❌ 在中间件中跳过日志记录
func AuthMiddleware() fiber.Handler {
    return func(c *fiber.Ctx) error {
        if !isAuthenticated(c) {
            // ❌ 直接返回,没有记录到 access.log
            return c.Status(401).JSON(fiber.Map{"error": "unauthorized"})
        }
        return c.Next()
    }
}

// ❌ 只在部分路由记录日志
app.Use("/api/v1/users", logger.Middleware())  // ❌ 其他路由没有日志

// ❌ 不记录请求/响应 body
accessLogger.Info("",
    zap.String("method", c.Method()),
    zap.String("path", c.Path()),
    zap.Int("status", c.Response().StatusCode()),
    // ❌ 缺少 request_body 和 response_body
)

理由 (RATIONALE):

完整的访问日志是系统可观测性的基础,对于以下场景至关重要:

  1. 问题排查:当用户报告错误时,通过 request_id 可以追溯完整的请求/响应数据,快速定位问题
  2. 安全审计:记录所有请求(包括认证失败、参数验证失败等)可以追踪潜在的安全攻击
  3. 性能分析:通过 duration_ms 和请求参数可以分析慢查询和性能瓶颈
  4. 合规要求:某些行业(金融、医疗等)要求完整的操作审计日志
  5. 用户行为分析:通过 user_id 和请求参数可以分析用户行为模式
  6. 无例外原则:确保没有请求"逃脱"日志记录,避免日志盲点

通过强制在 Logger 中间件中统一记录,确保:

  • 中间件的短路返回auth 失败、rate limit 等)不会绕过日志
  • 所有请求的日志格式统一,便于日志分析工具处理
  • 50KB 限制平衡了日志完整性和存储成本

Development Workflow (开发工作流程)

分支管理

  • MUSTmain 分支创建功能分支
  • 功能分支命名格式:feature/###-brief-descriptionfix/###-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、命令注入等
    • 性能影响可接受
    • 代码通过 gofmtgo vetgolangci-lint 检查
    • 文档已按规范更新docs/ 和 README.md
    • 访问日志记录符合规范(所有请求都被记录,包含请求/响应 body

Quality Gates (质量关卡)

代码合并前检查

  • 所有测试通过(go test ./...
  • 代码格式化(gofmt -l . 无输出)
  • 代码静态检查通过(go vet ./...
  • 代码质量检查通过(golangci-lint run
  • 测试覆盖率符合标准(go test -cover ./...
  • 无 TODO/FIXME 遗留(或已记录 Issue
  • API 文档已更新(如有 API 变更)
  • 数据库迁移文件已创建(如有 schema 变更)
  • 常量和 Redis key 使用符合规范(无硬编码字符串)
  • 无重复硬编码值3 次以上相同字面量已提取为常量)
  • 已定义的常量被正确使用(无重复硬编码已有常量的值)
  • 代码注释优先使用中文(实现注释使用中文,提高团队可读性)
  • 日志消息使用中文Info/Warn/Error/Debug 日志使用中文描述)
  • 错误消息支持中文(用户可见错误有中文消息)
  • 无 Java 风格反模式(无 getter/setter、无不必要接口、无过度抽象
  • 遵循 Go 命名约定(缩写大小写一致、包名简短、无下划线)
  • 使用 Go 惯用并发模式goroutines/channels无线程池类
  • 功能总结文档已创建(在 docs/{feature-id}/ 目录下,中文命名和内容)
  • README.md 已更新添加功能简短描述2-3 句话)
  • 访问日志验证通过(所有请求被记录到 access.log包含完整请求/响应参数)
  • 访问日志格式正确包含所有必需字段method, path, query, status, duration_ms, request_id, ip, user_agent, user_id, request_body, response_body
  • 中间件不绕过日志(认证失败、限流等短路返回也被记录)

上线前检查

  • 所有功能在 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.3.0 | Ratified: 2025-11-10 | Last Amended: 2025-11-11