Files
wx_service/docs/common/backend_convention.md
T
nepiedg 1eab1b99c1 feat: rename qiniu to oss, add admin upload proxy with thumbnail, add dev-login
- Rename all QINIU_* config/code/docs to OSS_* to match actual Alibaba Cloud OSS
- Refactor upload module from internal/common/qiniu to internal/common/upload
- Add backend proxy upload endpoint (POST /api/admin/marketing/upload) to avoid CORS
- Auto-generate compressed thumbnail (800px, JPEG 80%) on admin image upload
- Add dev-login endpoint (POST /api/v1/auth/dev-login) for H5 debugging
- Add imageutil package for server-side image resizing

Made-with: Cursor
2026-04-04 02:52:16 +08:00

12 KiB
Raw Blame History

后端开发规范 (wx_service)

本文档定义 wx_service 后端项目的编码规范、架构约定和开发流程,适用于所有后端贡献者。


1. 技术栈与环境

分类 技术 最低版本
语言 Go 1.23
Web 框架 Gin v1.11
ORM GORM v1.31
数据库 MySQL 8.x
缓存 Redis (go-redis v9) 可选
鉴权 JWT (golang-jwt v5)
配置 godotenv

开发工具要求

  • 使用 gofmt / goimports 格式化代码
  • 推荐 golangci-lint 进行静态检查
  • 提交前确保 go vet ./... 无警告

2. 项目结构

wx_service/
├── cmd/api/main.go          # 程序入口(配置加载 → DB 初始化 → 依赖注入 → 路由注册 → 启动)
├── config/                  # 配置结构体与加载逻辑
├── internal/                # 业务代码(不可被外部包导入)
│   ├── admin/               # 管理后台模块
│   ├── common/              # 公共能力
│   │   ├── auth/            #   登录认证
│   │   ├── upload/          #   OSS 上传
│   │   ├── redis/           #   Redis 缓存
│   │   └── wechat_official/ #   公众号 OAuth
│   ├── database/            # 数据库连接与迁移
│   ├── middleware/           # HTTP 中间件
│   ├── model/               # 公共数据模型(User、MiniProgram、Response
│   ├── observability/       # 指标采集与日志
│   ├── routes/              # 路由注册
│   └── <module>/            # 业务模块(按域拆分)
├── docs/                    # 文档
├── deploy/                  # 部署配置
├── scripts/                 # 运维脚本
├── .env.example             # 环境变量模板
├── Dockerfile               # 容器镜像构建
├── docker-compose.yml       # 开发环境依赖
└── docker-compose.prod.yml  # 生产环境编排

3. 模块分层架构

每个业务模块位于 internal/<module>/,采用以下分层结构:

internal/<module>/
├── handler/       # HTTP 层
├── service/       # 业务逻辑层
├── model/         # 数据模型
└── repository/    # 数据访问层(可选)

3.1 各层职责

职责 禁止事项
handler 解析请求参数、调用 service、组装 HTTP 响应 不包含业务逻辑、不直接操作数据库
service 实现核心业务规则、编排多表操作、调用外部 API 不感知 HTTP 层(不依赖 gin.Context 进行响应)
model 定义 GORM struct、表名、数据校验 不包含业务逻辑
repository 封装数据库查询(可选,简单模块可由 service 直接操作 DB) 不包含业务逻辑

3.2 依赖方向

handler → service → repository → model
                  → model

handler 依赖 serviceservice 依赖 repository(或直接使用 *gorm.DB),所有层共享 model。禁止反向依赖。

3.3 依赖注入

采用构造函数注入,在 cmd/api/main.go 中完成装配:

// service 接收 *gorm.DB 和配置
svc := someservice.NewSomeService(database.DB, config.AppConfig.SomeConfig)

// handler 接收 service
handler := somehandler.NewSomeHandler(svc)

// 路由注册接收 handler
routes.Register(router, handler, ...)

4. 命名规范

4.1 文件命名

  • 使用 snake_casesmoke_log_service.goauth_handler.go
  • 测试文件以 _test.go 结尾:smoke_log_service_test.go
  • 每个文件聚焦单一职责,避免大文件

4.2 包命名

  • 全小写,不使用下划线:handlerservicemodel
  • 导入别名使用有意义的缩写:smokeservicermhandler

4.3 结构体与函数

  • 导出类型使用 PascalCaseSmokeHandlerSmokeLogService
  • 构造函数统一使用 New 前缀:NewSmokeHandler
  • 请求结构体使用小写开头(非导出):createSmokeLogRequest
  • 常量使用 camelCase 或 PascalCase(视作用域定)

4.4 数据库相关

  • 表名使用 snake_casefa_smoke_logmarketing_categories
  • 模型通过 TableName() 方法显式指定表名
  • 字段标签使用 gorm:"column:xxx" 明确列名

5. HTTP 接口规范

5.1 响应格式

所有接口统一使用 model.Response 结构:

// 成功
c.JSON(http.StatusOK, model.Success(data))

// 失败
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "参数错误描述"))

响应 JSON 结构:

{
  "code": 200,
  "message": "success",
  "data": {}
}

5.2 HTTP 状态码使用

状态码 场景
200 成功(包括空结果集)
400 请求参数校验失败
401 未登录或 Token 过期
403 无权限或业务限制(如额度用尽)
404 资源不存在
500 服务端内部错误
502 第三方服务调用失败
503 关键配置缺失导致服务不可用

5.3 路由规范

  • RESTful 风格:GET 获取、POST 创建、PUT 更新、DELETE 删除
  • 路由前缀:
    • /api/v1 — 主版本 API
    • /api/v2 — 新版本模块
    • /api/expiry — 保质期独立域
    • /api/admin — 管理后台
  • 路由注册集中在 internal/routes/ 目录下
  • 每个模块创建独立的 register<Module>Routes 函数

5.4 认证

  • 用户认证:Bearer <session_key>,通过 AuthMiddleware + RequireUserMiddleware 处理
  • 管理后台:X-Admin-Token 头或 JWT
  • 公开接口不挂载鉴权中间件
  • handler 中通过 middleware.MustCurrentUser(c) 获取当前用户

5.5 分页

列表接口统一使用以下参数和返回结构:

// 请求参数
?page=1&page_size=20

// 响应
{
  "code": 200,
  "message": "success",
  "data": {
    "items": [...],
    "total": 100,
    "page": 1,
    "page_size": 20
  }
}

6. 数据库规范

6.1 GORM 模型

type SomeModel struct {
    ID        uint           `gorm:"primaryKey" json:"id"`
    UserID    uint           `gorm:"index;not null" json:"user_id"`
    Name      string         `gorm:"size:100;not null" json:"name"`
    CreatedAt time.Time      `json:"created_at"`
    UpdatedAt time.Time      `json:"updated_at"`
    DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}

func (SomeModel) TableName() string {
    return "some_models"
}

6.2 迁移

  • 开发阶段使用 AutoMigrate,在 cmd/api/main.go 中注册新模型
  • 生产环境建议手动迁移,SQL 参考放在 docs/sql/ 目录

6.3 查询

  • 优先使用 GORM 链式查询,避免裸 SQL
  • 复杂报表查询可使用 db.Raw(),但需添加注释说明
  • 列表查询必须加分页限制(Limit + Offset
  • 敏感查询加 context.Context 传递超时控制

6.4 事务

涉及多表写入的操作使用事务:

err := db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
    // 多步操作
    return nil
})

7. 错误处理

7.1 Service 层

  • 定义领域错误变量:var ErrSmokeLogNotFound = errors.New("smoke log not found")
  • Service 返回具体的错误类型,handler 据此决定 HTTP 状态码
  • 内部错误使用 fmt.Errorf("xxx: %w", err) 包装

7.2 Handler 层

  • errors.Is() 判断 service 返回的错误类型
  • 用户可见的错误信息使用中文
  • 内部错误记录日志后返回通用提示:"xxx失败,请稍后重试"
record, err := h.service.GetByID(ctx, userID, id)
if err != nil {
    if errors.Is(err, service.ErrNotFound) {
        c.JSON(http.StatusNotFound, model.Error(http.StatusNotFound, "记录不存在"))
        return
    }
    log.Printf("GetByID failed: %v", err)
    c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "查询失败,请稍后重试"))
    return
}

8. 配置管理

8.1 环境变量

  • 所有配置通过 .env 文件加载,使用 godotenv
  • 新增配置项必须同步更新 .env.example
  • 敏感信息(密钥、密码)不可提交到代码仓库

8.2 配置结构

配置统一定义在 config/config.go 的结构体中:

type Config struct {
    Server    ServerConfig
    Database  DatabaseConfig
    JWT       JWTConfig
    // 按需扩展...
}

8.3 可选功能降级

部分功能(如 Redis、额外数据库)可选启用。启动时检测配置是否存在,缺失则打印日志并禁用相关接口,不阻塞服务启动:

if redisClient == nil {
    log.Println("redis disabled: ...")
}

9. 测试规范

9.1 文件与组织

  • 测试文件与被测文件同目录:smoke_log_service.gosmoke_log_service_test.go
  • 使用 Go 标准测试框架 testing
  • 表驱动测试(table-driven tests)优先

9.2 命名

  • 测试函数:TestFunctionName_Scenario
  • 子测试:t.Run("scenario description", ...)

9.3 运行

# 运行全部测试
go test ./...

# 运行指定模块测试
go test ./internal/smoke/service/...

# 带覆盖率
go test -cover ./...

10. 新增业务模块流程

以新增"xx功能"模块为例:

第一步:创建模块目录

internal/xx/
├── handler/
│   └── xx_handler.go
├── service/
│   └── xx_service.go
└── model/
    └── xx.go

第二步:定义数据模型

// internal/xx/model/xx.go
package model

type XX struct {
    ID     uint   `gorm:"primaryKey" json:"id"`
    UserID uint   `gorm:"index;not null" json:"user_id"`
    // ...
}

func (XX) TableName() string { return "xx_records" }

第三步:实现 Service

// internal/xx/service/xx_service.go
package service

type XXService struct {
    db *gorm.DB
}

func NewXXService(db *gorm.DB) *XXService {
    return &XXService{db: db}
}

第四步:实现 Handler

// internal/xx/handler/xx_handler.go
package handler

type XXHandler struct {
    service *xxservice.XXService
}

func NewXXHandler(svc *xxservice.XXService) *XXHandler {
    return &XXHandler{service: svc}
}

第五步:注册依赖和路由

  1. cmd/api/main.go 中:
    • 导入新模块
    • 初始化 service → handler
    • 将新模型加入 AutoMigrate
  2. internal/routes/ 中新建 xx_routes.go,实现 registerXXRoutes
  3. routes.goRegister 函数中调用

第六步:编写文档

docs/xx/ 下创建:

  • README.md:模块需求说明
  • API.md:接口文档(方法、路径、参数、返回值)

11. Git 与提交规范

11.1 分支策略

  • main / master:生产分支,推送自动触发部署
  • 功能分支:feature/<name>fix/<name>

11.2 提交信息格式

<type>: <简要描述>

<可选的详细说明>

类型约定:

类型 说明
feat 新功能
fix 修复 Bug
refactor 重构(不影响功能)
docs 文档变更
test 测试相关
chore 构建/运维/依赖等杂项

示例:feat: add quit plan CRUD endpoints

11.3 提交检查清单

  • go vet ./... 无警告
  • gofmt / goimports 已格式化
  • 相关测试通过
  • 新增接口已更新文档
  • .env.example 已同步更新(如有新配置项)

12. 部署

Docker 方式

docker compose -f docker-compose.prod.yml up -d

非 DockerGitHub Actions

推送 main 分支自动触发 .github/workflows/deploy-prod.yml

健康检查

  • GET /healthz{"status": "ok"}
  • Docker 健康检查间隔 30s
  • Nginx 反代配置在 deploy/nginx/

详细部署文档:docs/common/deploy_ci.md