后端 Go 落地参考(server_api)
目录结构与归属
cmd/ Cobra 子命令:service admin / service api / version(-c 指定配置)
config/ 配置结构体、加载与默认值
router/ admin.go / api.go,两端路由分文件
internal/
common/
app/ 配置 + GORM + Redis 生命周期、健康检查、必需表检查
auth/ 登录流水、JWT 签发解析、踢人下线
enums/ 业务枚举(按功能分文件)
middleware/ 两端共用:CORS、Auth
model/ GORM 模型 sys_*
upload/ 上传业务、默认存储加载、本地文件访问
admin/
controller/ logic/ param/ resp/ middleware/ permission/
api/
controller/ logic/ param/ resp/
pkg/ response pagination password tree dberror oss mask sms sn
sql/ 建表与升级脚本
docs/ 接口文档与规范归属判断顺序:两端共用 → internal/common/<功能>;仅一端使用 → internal/<端>/<功能>;无业务语义 → pkg/<功能>。
分层职责
- controller:只做
requestvalidate.Bind→ 调 logic →response.OK/Fail,禁止直接调用 GinShouldBind*。 - logic:业务规则、事务、缓存维护;方法签名统一
func (l *XxxLogic) Action(c *gin.Context, req *param.XxxReq) (*resp.XxxRes, error)。 - param / resp:结构体定义,按功能拆分文件(
user.go、menu.go…),禁止param.go聚合。 - model:仅放 GORM 模型与表名,不放响应结构。
代码模板
param
go
package param
type UserSaveReq struct {
ID uint `json:"id" validate:"主键ID" comment:"主键ID"`
Username string `json:"username" binding:"required" validate:"登录账号" comment:"登录账号"`
Status int `json:"status" binding:"omitempty,oneof=1 2" validate:"状态" comment:"状态"`
}查询类(GET)字段补 form:"...":
go
type UserListReq struct {
Keyword string `form:"keyword" validate:"搜索关键字" comment:"搜索关键字"`
Page int `form:"page" validate:"页码" comment:"页码"`
PageSize int `form:"page_size" validate:"每页数量" comment:"每页数量"`
}resp
go
package resp
type UserListRes struct {
List []UserItem `json:"list" comment:"用户列表"`
Total int64 `json:"total" comment:"数据总数"`
}实体统一内嵌 BaseItem(id / created_at / updated_at),树形接口由 pkg/tree.BuildTree 生成 data + children。
logic
go
// Create 新增用户:写主表、维护角色关联并清理权限缓存。
func (l *UserLogic) Create(c *gin.Context, req *param.UserSaveReq) (*resp.UserItem, error) {
// 1. 校验业务规则
// 2. 事务内写库
// 3. 清理权限缓存
// 4. 返回 resp.Item
}controller
go
func (h *UserController) Create(c *gin.Context) {
var req param.UserSaveReq
if err := requestvalidate.Bind(c, &req); err != nil {
response.Fail(c, response.CodeErrParams, err.Error())
return
}
res, err := h.Logic.Create(c, &req)
if err != nil {
response.Fail(c, response.CodeErrBusiness, err.Error())
return
}
response.OK(c, res)
}路由
go
perm.POST("/user/add", userC.Create) // 登录 + 接口级权限 + 操作日志
auth.POST("/logout", authC.Logout) // 仅登录
pub.POST("/login", authC.Login) // 公开强制约定
- controller 必须向 logic 传
*gin.Context;claims / IP / UA 在 logic 内获取。 - 统一
response.OK/Fail;参数错误用CodeErrParams,业务失败用CodeErrBusiness。 - 数据库错误用
pkg/dberror识别(重复键 → 友好提示)。 - 禁止
AutoMigrate,表变更提供sql/脚本。 - 并发写入优先
clause.OnConflictupsert,并在数据库层加唯一约束。 - 权限相关变更(菜单、角色、用户角色)后清理权限缓存。
- 密钥类字段响应不返回;写入留空表示不修改。
- 上传只存相对路径,地址动态解析;远程抓取复用
safeRemoteHTTPClient。 - 数值枚举从 1 开始,
0表示未设置/不过滤。 - 新增配置同步
config/config.go、config.example.yaml、README;新增接口同步go_basic_frame_wiki/public/openapi/admin_openapi.yaml。
提交前
bash
gofmt -l .
go vet ./...
go build ./...
go test ./...