- 迁移 v10 新增 nav_links 与 nav_link_translations 两张表,文案按语言存储 - 公开 GET /api/nav-links 返回启用链接(按 sort/id 排序);管理员可增删改查与启停 - URL 仅允许站内路径、http(s) 与 mailto,拒绝 javascript: 等危险协议;译文替换与校验 - 前端删除写死的“主页”按钮,头部按当前语言渲染动态链接,新窗口加 rel=noopener noreferrer - 后台管理页新增“头部导航链接”卡片(三语文案、URL、打开方式、排序、状态),改动即时生效 - 补充 nav 接口与迁移测试、三语文案,重新生成 Swagger 文档
119 lines
5.6 KiB
Markdown
119 lines
5.6 KiB
Markdown
# 后端开发规范
|
||
|
||
本文约定后端代码的组织方式与开发流程,重点说明通用工具模块 `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. 提交规范
|
||
|
||
- 提交信息使用简短中文标题,正文用要点列出改动
|
||
- 一个提交只做一件事;重构与功能尽量分开
|