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

119 lines
5.6 KiB
Markdown
Raw Permalink 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.
# 后端开发规范
本文约定后端代码的组织方式与开发流程,重点说明通用工具模块 `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.go``random.go`),函数命名语义化
- 新增函数必须附带同包单元测试(`*_test.go`
- 尽量无全局可变状态;无法避免时(如可信代理配置)提供 `SetXxx` 初始化函数、内部加锁保证并发安全,并在 `main.go` 启动时接线
- `utils` 只允许依赖标准库与 gin,不得依赖 `model` 等业务包
### 2.1 获取客户端 IP
统一使用 `utils.ClientIP(c)`**不要**直接使用 `c.ClientIP()``c.Request.RemoteAddr`——前者无法兼容多 CDN 且 gin 默认信任所有代理,后者拿到的是代理节点地址。
```go
ip := utils.ClientIP(c) // 业务取 IP 的唯一入口,如操作日志
```
取值规则:
1. 先取直连地址;**只有直连地址属于可信代理时才会读取转发头**,否则直接返回直连地址(防止伪造)
2. 按优先级依次尝试:`CF-Connecting-IP``True-Client-IP``Ali-CDN-Real-IP``X-Real-IP``X-Client-IP``Fastly-Client-IP``Forwarded`(RFC 7239) → `X-Forwarded-For`
3. 链式头(`Forwarded``X-Forwarded-For`)从右往左取第一个非可信代理 IP,全部可信时取最左
4. 每个候选值自动去引号、去端口并校验合法性,非法值跳过继续尝试下一个
5. unix socket 连接视为可信(本地进程)
可信代理通过配置项 `server.trusted_proxies` 设置(IP 或 CIDR 列表):
```yaml
server:
# 留空表示不信任任何代理头;部署 CDN / 自建反代时填其回源网段
trusted_proxies:
- "173.245.48.0/20" # 示例:Cloudflare 回源网段
- "127.0.0.1"
```
注意:`main.go` 启动时已同时调用 `utils.SetTrustedProxies``r.SetTrustedProxies`,业务代码**不要**重复调用或修改该配置。
需要直连地址(审计、排障)时使用 `utils.RemoteIP(c)`,它不读取任何请求头。
### 2.2 随机字符串
安全敏感场景(初始密码、令牌等)统一使用 `utils.RandomString(n)`,内部使用 `crypto/rand` 且字符集去掉了易混淆字符;禁止用 `math/rand` 生成此类字符串。
```go
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 request``record not found``internal server error``unauthorized 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 /site``GET /files/:id`)、需登录(个人资料、头像、文件上传删除、notes)、仅管理员
## 4. 数据库与迁移
- 模型放在 `internal/model/`,表名遵循 GORM 默认复数
- 迁移**只能追加**`Version` 递增),禁止修改已发布的迁移;补数据、字段翻译等一次性操作同样通过新迁移完成
- SQLite 为单连接串行写且不建外键;多对多关联表手动维护(参考 `user_group_members`
- 仅日期字段使用 `model.Date`JSON 为 `YYYY-MM-DD`,未设置输出 `null`
- 状态字段沿用 `int8``status`)或字符串枚举(`gender``file operation`),取值定义在 `model`
## 5. 测试与验证
- 接口层测试使用 `internal/testutil`(临时库、鉴权路由、请求辅助),业务/工具测试放在对应包
- 表驱动优先;涉及全局状态的测试用 `t.Cleanup` 复位
- 提交前必须通过:
```sh
gofmt -l .
go build ./...
go vet ./...
go test ./...
```
- 修改 Swagger 注解后额外执行:`go generate ./...`
## 6. 提交规范
- 提交信息使用简短中文标题,正文用要点列出改动
- 一个提交只做一件事;重构与功能尽量分开