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

478 lines
12 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.
# 后端开发规范 (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` 中完成装配:
```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_case**`smoke_log_service.go``auth_handler.go`
- 测试文件以 `_test.go` 结尾:`smoke_log_service_test.go`
- 每个文件聚焦单一职责,避免大文件
### 4.2 包命名
- 全小写,不使用下划线:`handler``service``model`
- 导入别名使用有意义的缩写:`smokeservice``rmhandler`
### 4.3 结构体与函数
- 导出类型使用 PascalCase`SmokeHandler``SmokeLogService`
- 构造函数统一使用 `New` 前缀:`NewSmokeHandler`
- 请求结构体使用小写开头(非导出):`createSmokeLogRequest`
- 常量使用 camelCase 或 PascalCase(视作用域定)
### 4.4 数据库相关
- 表名使用 **snake_case**`fa_smoke_log``marketing_categories`
- 模型通过 `TableName()` 方法显式指定表名
- 字段标签使用 `gorm:"column:xxx"` 明确列名
---
## 5. HTTP 接口规范
### 5.1 响应格式
所有接口统一使用 `model.Response` 结构:
```go
// 成功
c.JSON(http.StatusOK, model.Success(data))
// 失败
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "参数错误描述"))
```
响应 JSON 结构:
```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 模型
```go
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 事务
涉及多表写入的操作使用事务:
```go
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失败,请稍后重试"
```go
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` 的结构体中:
```go
type Config struct {
Server ServerConfig
Database DatabaseConfig
JWT JWTConfig
// 按需扩展...
}
```
### 8.3 可选功能降级
部分功能(如 Redis、额外数据库)可选启用。启动时检测配置是否存在,缺失则打印日志并禁用相关接口,不阻塞服务启动:
```go
if redisClient == nil {
log.Println("redis disabled: ...")
}
```
---
## 9. 测试规范
### 9.1 文件与组织
- 测试文件与被测文件同目录:`smoke_log_service.go``smoke_log_service_test.go`
- 使用 Go 标准测试框架 `testing`
- 表驱动测试(table-driven tests)优先
### 9.2 命名
- 测试函数:`TestFunctionName_Scenario`
- 子测试:`t.Run("scenario description", ...)`
### 9.3 运行
```bash
# 运行全部测试
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
```
### 第二步:定义数据模型
```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
```go
// 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
```go
// 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.go``Register` 函数中调用
### 第六步:编写文档
`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 方式
```bash
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`