gin-vue-admin 后端分层约束全指南:Router → API → Service → Model 依赖方向与规范化开发实践
2026/9/20 5:14:26 网站建设 项目流程
  • 后端
  • 前端
  • 认证鉴权
  • 低代码
  • 企业应用

【免费下载链接】gin-vue-admin

🚀Vite+Vue3+Gin的开发基础平台,支持TS和JS混用。它集成了JWT鉴权、权限管理、动态路由、显隐可控组件、分页封装、多点登录拦截、资源权限、上传下载、代码生成器【可AI辅助】、表单生成器和可配置的导入导出等开发必备功能。

项目地址:https://gitcode.com/flipped-aurora/gin-vue-admin
点击查看免费下载

导读

本指南以 aiDoc/modules/backend-layer-rules.md 为骨架,系统讲解 gin-vue-admin 后端的分层架构约束:Model(模型)、Service(业务)、API(接口)、Router(路由)四层的职责边界、依赖方向与enter.go组装机制,并结合作品源码(server/api、server/service、server/model、server/router)逐层佐证。读完本文,你将掌握该框架下"如何新建一个后端模块、如何放置模型与请求结构、如何绑定参数、如何写出规范 Swagger 注释",从而写出可维护、可协作、不越层的代码。

总原则:单向依赖,禁止跨层

Router -> API -> Service -> Model
  • 严格遵守Router -> API -> Service -> Model依赖方向:Router 只调用 API,API 只调用 Service,Service 只操作 Model(经 GORM 与数据库交互),依赖箭头永远自上而下,不允许出现反向引用。
  • 禁止跨层直接调用:例如 Router 直接操作数据库、API 直接使用global.GVA_DB、Service 里出现 HTTP 语义,都属于越层。
  • enter.go作为组装与暴露入口,避免循环引用:Go 的包级循环依赖会直接编译失败。gin-vue-admin 通过service/enter.goapi/v1/xxx/enter.gorouter/xxx/enter.go三处聚合注册,让各层只依赖上层的聚合对象,从而切断循环引用。这一机制贯穿 server/service/system/enter.go(ServiceGroup聚合所有 Service)、server/api/v1/system/enter.go(ApiGroup聚合所有 API 并通过service.ServiceGroupApp.SystemServiceGroup注入服务)、server/router/system/enter.go(RouterGroup聚合所有路由)。

Model 层:数据模型的规范

继承 GVA_MODEL 与字段标签

数据模型优先继承global.GVA_MODEL。该基础结构定义在 server/global/model.go:

type GVA_MODEL struct { ID uint `gorm:"primarykey" json:"ID"` // 主键ID CreatedAt time.Time // 创建时间 UpdatedAt time.Time // 更新时间 DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` // 删除时间 }
  • ID:自增主键,json:"ID"
  • CreatedAt/UpdatedAt:GORM 自动维护的创建、更新时间;
  • DeletedAt:软删除字段,带索引,json:"-"避免暴露给前端。

在此基础上,业务字段应补全清晰的jsongorm标签。以 server/model/system/sys_dictionary.go 为例:

type SysDictionary struct { global.GVA_MODEL Name string `json:"name" form:"name" gorm:"column:name;comment:字典名(中)"` // 字典名(中) Type string `json:"type" form:"type" gorm:"column:type;comment:字典名(英)"` // 字典名(英) Status *bool `json:"status" form:"status" gorm:"column:status;comment:状态"` // 状态 Desc string `json:"desc" form:"desc" gorm:"column:desc;comment:描述"` // 描述 ParentID *uint `json:"parentID" form:"parentID" gorm:"column:parent_id;comment:父级字典ID"` // 父级字典ID Children []SysDictionary `json:"children" gorm:"foreignKey:ParentID"` // 子字典 SysDictionaryDetails []SysDictionaryDetail `json:"sysDictionaryDetails" form:"sysDictionaryDetails"` }

其中form标签用于 Query/表单绑定,gorm:"column:xxx;comment:xxx"用于列名与注释;Children通过foreignKey:ParentID声明自关联。模型还可通过实现TableName()显式指定表名(如return "sys_dictionaries")。

请求模型的放置与XxxSearch约定

  • 请求模型放在model/request/目录下(如 server/model/system/request),响应模型放在model/response/目录下;
  • 列表查询模型应定义XxxSearch,并内嵌通用的request.PageInfo

通用分页结构定义在 server/model/common/request/common.go:

type PageInfo struct { Page int `json:"page" form:"page"` // 页码 PageSize int `json:"pageSize" form:"pageSize"` // 每页大小 Keyword string `json:"keyword" form:"keyword"` // 关键字 }

Paginate()方法封装了分页兜底逻辑:Page <= 0时取 1;PageSize > 100时截断为 100,<= 0时取 10。同文件还提供了GetById(含Uint()转换)、IdsReqGetAuthorityId等常用请求结构,可直接复用。

XxxSearch的规范示例见 server/model/system/request/sys_dictionary.go:

type SysDictionarySearch struct { Name string `json:"name" form:"name" gorm:"column:name;comment:字典名(中)"` // 字典名(中) } type ImportSysDictionaryRequest struct { Json string `json:"json" binding:"required"` // JSON字符串 }

类型一致性:高风险字段必须重点检查

同一字段在模型、请求结构、响应结构、前端使用处必须保持一致。尤其关注四类高风险字段:

  • 状态字段(如Status *bool):指针与布尔值的序列化差异;
  • ID 字段(如ID uint与请求中的id int):请求层常通过GetById.Uint()显式转换;
  • 枚举字段:值与含义对应关系;
  • 时间字段time.Time的 JSON 序列化格式(RFC3339)在前端是否需要格式化。

若涉及指针类型与非指针类型互转,必须在 Service 层显式处理nil。例如SysDictionary.StatusParentID均为指针,Service 在写入Updates(map)时直接透传指针,并在读取时对status == nil做兜底(见 server/service/system/sys_dictionary.go)。

Service 层:纯业务逻辑,不碰 HTTP

约束要点:

  • 只承载业务逻辑,不处理 HTTP 语义;
  • 不要依赖gin.Context
  • 函数应返回业务结果和error
  • 每个模块在service/下建立独立文件,并在service/enter.go注册。

以 server/service/system/sys_dictionary.go 为范本,可以看到一个规范 Service 的结构:type DictionaryService struct{}+ 包级实例var DictionaryServiceApp = new(DictionaryService),方法签名统一返回(xxx, err error)

func (dictionaryService *DictionaryService) CreateSysDictionary(sysDictionary system.SysDictionary) (err error) { if (!errors.Is(global.GVA_DB.First(&system.SysDictionary{}, "type = ?", sysDictionary.Type).Error, gorm.ErrRecordNotFound)) { return errors.New("存在相同的type,不允许创建") } err = global.GVA_DB.Create(&sysDictionary).Error return err }

业务校验(type 唯一性)、GORM 查询、事务(global.GVA_DB.Transaction)、递归校验(checkCircularReference)等全部收敛在 Service 内,API 层只做编排。service/enter.go通过聚合结构体对外暴露服务(如ServiceGroup中的DictionaryService),供 API 层统一引用。

注意:仓库中个别历史代码在 Service 里携带c *gin.Context(如GetSysDictionaryInfoList为了透传上下文),属于历史包袱;新代码应严格遵循"Service 不依赖 gin.Context"的约束。

API 层:参数提取、校验与统一响应

职责边界

API 层负责:参数提取、参数校验、调用 Service、统一响应。参数从哪里取,取决于前端怎么传、协议怎么设计、当前逻辑需要什么,以及哪个位置更合理——不要把绑定方式写死成某一种固定模板。

常见参数来源与取法

参数来源常见取法
JSON bodyShouldBindJSON
Query stringShouldBindQueryc.Query(...)c.DefaultQuery(...)
Path paramsc.Param(...)
multipart/form-datac.FormFile(...)c.DefaultPostForm(...)c.Request.FormValue(...)
Headerc.GetHeader(...)c.Request.Header.Get(...)
Cookiec.Cookie(...)

使用原则

  • 绑定方式要与真实参数来源一致:body 数据用ShouldBindJSON,Query 数据用ShouldBindQuery,不要互换;
  • 不要为了套模板,把 Header / Cookie / Query / form-data 中的数据强行改成 body
  • 认证、追踪、网关透传等信息,很多时候本来就应该从 Header 或 Cookie 获取(如 JWT 用户信息经 server/middleware/jwt.go 解析后注入上下文);
  • 上传文件时,应按上传协议从multipart/form-data中取文件和附带字段(参考 server/api/v1/example/exa_file_upload_download.go 中c.FormFile("file")的用法)。

仓库示例:FindSysDictionaryShouldBindQuery绑定查询参数,CreateSysDictionaryShouldBindJSON绑定 body,两种取法在同一个 API 文件中共存(见 server/api/v1/system/sys_dictionary.go 与 同文件 L98-L112)。

强制约束

  • 必须通过service.ServiceGroupApp访问服务层:见 server/api/v1/system/enter.go,所有服务实例统一声明为包级变量(如dictionaryService = service.ServiceGroupApp.SystemServiceGroup.DictionaryService);
  • 必须使用项目统一的response包输出结果:封装在 server/model/common/response/response.go,提供OkOkWithMessageOkWithDataOkWithDetailedFailFailWithMessageNoAuth等函数,统一{code, data, msg}结构(SUCCESS = 0ERROR = 7);
  • 每个对外 API 都必须写完整且准确的 Swagger 注释(详见下文)。

Router 层:分组、中间件与绑定

Router 层负责路由分组、中间件挂载和处理函数绑定。约束:

  • 必须通过api.ApiGroupApp引用 API 层;
  • 每个模块在router/下建立独立文件,并在router/enter.go注册。

以 server/router/system/sys_dictionary.go 为例:

func (s *DictionaryRouter) InitSysDictionaryRouter(Router *gin.RouterGroup) { sysDictionaryRouter := Router.Group("sysDictionary").Use(middleware.OperationRecord()) sysDictionaryRouterWithoutRecord := Router.Group("sysDictionary") { sysDictionaryRouter.POST("createSysDictionary", dictionaryApi.CreateSysDictionary) // 新建SysDictionary sysDictionaryRouter.DELETE("deleteSysDictionary", dictionaryApi.DeleteSysDictionary) // 删除SysDictionary sysDictionaryRouter.PUT("updateSysDictionary", dictionaryApi.UpdateSysDictionary) // 更新SysDictionary sysDictionaryRouter.POST("importSysDictionary", dictionaryApi.ImportSysDictionary) // 导入SysDictionary sysDictionaryRouter.GET("exportSysDictionary", dictionaryApi.ExportSysDictionary) // 导出SysDictionary } { sysDictionaryRouterWithoutRecord.GET("findSysDictionary", dictionaryApi.FindSysDictionary) // 根据ID获取SysDictionary sysDictionaryRouterWithoutRecord.GET("getSysDictionaryList", dictionaryApi.GetSysDictionaryList) // 获取SysDictionary列表 } }

可以看到两种分组策略:写操作挂middleware.OperationRecord()(操作记录/审计),读操作独立分组不挂记录中间件。分组命名、Use中间件、处理函数引用均通过包级dictionaryApi完成,而dictionaryApi来自api.ApiGroupApp聚合(见 server/api/v1/system/enter.go 的ApiGroup结构体)。中间件生态位于 server/middleware,包括 JWT 鉴权、Casbin RBAC、CORS、限流、超时、操作日志等,按需挂载。

Initialize 层:模块初始化的标准职责

插件或模块若需要初始化入口,至少关注以下五个职责(参考 server/initialize 与 server/plugin/announcement/initialize):

文件职责
gorm.go表结构迁移(AutoMigrate模型)
router.go路由注册
menu.go菜单与权限初始化
viper.go配置加载
api.goAPI 注册

以公告插件为例,server/plugin/announcement/initialize 目录下即为这五类文件的完整实现,插件通过 server/plugin/announcement/plugin/plugin.go 声明初始化入口,由 server/initialize/plugin.go 统一调度。系统内置模块的初始化链则可参考 server/initialize/init.go 及 server/initialize/router.go。

Swagger 约束:对外 API 的注释规范

对外 API 的 Swagger 注释至少要准确说明:功能说明、请求参数、响应结构、路由路径、鉴权要求。以 server/api/v1/system/sys_dictionary.go 的创建接口为例:

// CreateSysDictionary // @Tags SysDictionary // @Summary 创建SysDictionary // @Security ApiKeyAuth // @accept application/json // @Produce application/json // @Param data body system.SysDictionary true "SysDictionary模型" // @Success 200 {object} response.Response{msg=string} "创建SysDictionary" // @Router /sysDictionary/createSysDictionary [post]

各注释标签的含义与规范:

  • @Tags:接口分组(前端可按 Tag 检索 API);
  • @Summary:一句话功能说明;
  • @Security ApiKeyAuth:声明该接口需要 JWT 鉴权(与 server/middleware/jwt.go 的鉴权中间件对应);
  • @accept/@Produce:请求/响应的 MIME 类型,通常为application/json
  • @Param:请求参数,包含位置(body/query/path/formData/header)类型是否必填说明
  • @Success:响应结构,统一使用response.Response{...}泛型描述;
  • @Router:路由路径与 HTTP 方法,必须与实际路由注册(见 Router 层)完全一致。

响应统一收口到response.Response(见 server/model/common/response/response.go),因此@Success中一律以response.Response{data=..., msg=string}形式声明。完整的 Swagger 文档由 server/docs/docs.go 生成并对外提供。

小结:一个模块从零到一的落地清单

结合全文约束,在 gin-vue-admin 中新增一个后端模块的标准动作:

  1. Model:在 server/model 下建结构体,继承global.GVA_MODEL,补全json/form/gorm标签;请求结构放model/request/(列表查询内嵌request.PageInfo定义XxxSearch);
  2. Service:在 server/service 下新建xxx.go,只写业务逻辑,返回(result, err),不依赖gin.Context;在service/enter.go注册进ServiceGroup
  3. API:在 server/api/v1 下新建 API 文件,按真实参数来源绑定(JSON/Query/Path/form-data/Header/Cookie),通过service.ServiceGroupApp调用服务,用response包统一输出,并写全 Swagger 注释;在api/.../enter.goApiGroup中注册;
  4. Router:在 server/router 下新建路由文件,分组挂载中间件、绑定 API 处理函数;在router/.../enter.go注册;
  5. Initialize(可选):若需初始化入口(建表、菜单、配置、API 注册),按gorm.go/router.go/menu.go/viper.go/api.go五件套组织,参考 server/plugin/announcement/initialize。

始终牢记依赖方向Router -> API -> Service -> Model、禁止跨层调用、enter.go聚合暴露,即可保证模块边界清晰、可测试、可协作、可被代码生成器与 AI 辅助工具稳定生成。

  • 后端
  • 前端
  • 认证鉴权
  • 低代码
  • 企业应用

【免费下载链接】gin-vue-admin

🚀Vite+Vue3+Gin的开发基础平台,支持TS和JS混用。它集成了JWT鉴权、权限管理、动态路由、显隐可控组件、分页封装、多点登录拦截、资源权限、上传下载、代码生成器【可AI辅助】、表单生成器和可配置的导入导出等开发必备功能。

项目地址:https://gitcode.com/flipped-aurora/gin-vue-admin
点击查看免费下载

相关推荐

上一篇:Gin-Gonic/Gin容器化部署终极指南:Docker与Kubernetes最佳实践
下一篇:Gin框架CI/CD完整指南:10步实现自动化测试与部署

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询