Files
rill/docs/backend-development.md
kevin 4449250e97 新增头部导航链接配置与多语言支持
- 迁移 v10 新增 nav_links 与 nav_link_translations 两张表,文案按语言存储
- 公开 GET /api/nav-links 返回启用链接(按 sort/id 排序);管理员可增删改查与启停
- URL 仅允许站内路径、http(s) 与 mailto,拒绝 javascript: 等危险协议;译文替换与校验
- 前端删除写死的“主页”按钮,头部按当前语言渲染动态链接,新窗口加 rel=noopener noreferrer
- 后台管理页新增“头部导航链接”卡片(三语文案、URL、打开方式、排序、状态),改动即时生效
- 补充 nav 接口与迁移测试、三语文案,重新生成 Swagger 文档
2026-09-21 21:26:30 +08:00

5.6 KiB
Raw Permalink Blame History

后端开发规范

本文约定后端代码的组织方式与开发流程,重点说明通用工具模块 internal/utils 的使用。

1. 目录结构

internal/ 按功能拆分模块,职责单一:

internal/
├── api/          仅路由装配(公开/登录/管理员三组)
├── auth/         注册、登录、JWT、鉴权中间件、个人资料
├── user/         用户管理
├── usergroup/    用户组管理
├── note/         便签
├── site/         站点信息
├── nav/          头部导航链接
├── file/         文件上传、删除、查看与本地存储
├── avatar/       当前用户头像
├── httpx/        HTTP 公共能力:ErrorResponse、分页、ID 解析
├── utils/        通用工具(与业务无关的公共函数)
├── model/        数据库模型
├── database/     连接、迁移、种子数据
├── config/       配置加载与自动补全
└── testutil/     测试公共环境(仅测试引用)

依赖方向:业务模块 → httpx / utils / model,禁止反向依赖;api 只做路由装配,不写业务逻辑。

2. 通用工具模块 internal/utils

使用原则

  • 只放与具体业务无关的公共函数;有业务语义的逻辑放在对应业务模块
  • 一个主题一个文件(如 ip.gorandom.go),函数命名语义化
  • 新增函数必须附带同包单元测试(*_test.go
  • 尽量无全局可变状态;无法避免时(如可信代理配置)提供 SetXxx 初始化函数、内部加锁保证并发安全,并在 main.go 启动时接线
  • utils 只允许依赖标准库与 gin,不得依赖 model 等业务包

2.1 获取客户端 IP

统一使用 utils.ClientIP(c)不要直接使用 c.ClientIP()c.Request.RemoteAddr——前者无法兼容多 CDN 且 gin 默认信任所有代理,后者拿到的是代理节点地址。

ip := utils.ClientIP(c) // 业务取 IP 的唯一入口,如操作日志

取值规则:

  1. 先取直连地址;只有直连地址属于可信代理时才会读取转发头,否则直接返回直连地址(防止伪造)
  2. 按优先级依次尝试:CF-Connecting-IPTrue-Client-IPAli-CDN-Real-IPX-Real-IPX-Client-IPFastly-Client-IPForwarded(RFC 7239) → X-Forwarded-For
  3. 链式头(ForwardedX-Forwarded-For)从右往左取第一个非可信代理 IP,全部可信时取最左
  4. 每个候选值自动去引号、去端口并校验合法性,非法值跳过继续尝试下一个
  5. unix socket 连接视为可信(本地进程)

可信代理通过配置项 server.trusted_proxies 设置(IP 或 CIDR 列表):

server:
  # 留空表示不信任任何代理头;部署 CDN / 自建反代时填其回源网段
  trusted_proxies:
    - "173.245.48.0/20"   # 示例:Cloudflare 回源网段
    - "127.0.0.1"

注意:main.go 启动时已同时调用 utils.SetTrustedProxiesr.SetTrustedProxies,业务代码不要重复调用或修改该配置。

需要直连地址(审计、排障)时使用 utils.RemoteIP(c),它不读取任何请求头。

2.2 随机字符串

安全敏感场景(初始密码、令牌等)统一使用 utils.RandomString(n),内部使用 crypto/rand 且字符集去掉了易混淆字符;禁止用 math/rand 生成此类字符串。

password, err := utils.RandomString(16)

2.3 新增工具函数的流程

  1. 判断是否属于与业务无关的公共逻辑
  2. 放入对应主题文件,或新建 xxx.go / xxx_test.go
  3. 涉及全局初始化时提供 SetXxx,并在 main.go 中调用(配置类需同时加入 config 校验与 ConfigVersion 升级)
  4. 运行 gofmt -l . && go build ./... && go vet ./... && go test ./...

3. 接口规范

  • 响应文案一律英文,统一错误响应 httpx.ErrorResponse,如 invalid requestrecord not foundinternal server errorunauthorized or session expired
  • 分页参数用 httpx.ParsePagination(c);路径 ID 用 httpx.ParseID(c)(用户组用 usergroup 内部的 parseGroupID,允许 id 0
  • Swagger 注解:@Summary / @Description / @Tags(按权限取 public / user / admin/ @Security BearerAuth / 401、403、500 失败响应;注解修改后执行 go generate ./... 重新生成 docs/
  • 路由按权限挂载:公开(/health/swagger/auth/*GET /siteGET /files/:id)、需登录(个人资料、头像、文件上传删除、notes)、仅管理员

4. 数据库与迁移

  • 模型放在 internal/model/,表名遵循 GORM 默认复数
  • 迁移只能追加Version 递增),禁止修改已发布的迁移;补数据、字段翻译等一次性操作同样通过新迁移完成
  • SQLite 为单连接串行写且不建外键;多对多关联表手动维护(参考 user_group_members
  • 仅日期字段使用 model.DateJSON 为 YYYY-MM-DD,未设置输出 null
  • 状态字段沿用 int8status)或字符串枚举(genderfile operation),取值定义在 model

5. 测试与验证

  • 接口层测试使用 internal/testutil(临时库、鉴权路由、请求辅助),业务/工具测试放在对应包
  • 表驱动优先;涉及全局状态的测试用 t.Cleanup 复位
  • 提交前必须通过:
gofmt -l .
go build ./...
go vet ./...
go test ./...
  • 修改 Swagger 注解后额外执行:go generate ./...

6. 提交规范

  • 提交信息使用简短中文标题,正文用要点列出改动
  • 一个提交只做一件事;重构与功能尽量分开