- 后端
- 前端
- 认证鉴权
- 低代码
- 企业应用
【免费下载链接】gin-vue-admin
🚀Vite+Vue3+Gin的开发基础平台,支持TS和JS混用。它集成了JWT鉴权、权限管理、动态路由、显隐可控组件、分页封装、多点登录拦截、资源权限、上传下载、代码生成器【可AI辅助】、表单生成器和可配置的导入导出等开发必备功能。
导读
本指南以 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.go、api/v1/xxx/enter.go、router/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:"-"避免暴露给前端。
在此基础上,业务字段应补全清晰的json与gorm标签。以 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()转换)、IdsReq、GetAuthorityId等常用请求结构,可直接复用。
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.Status、ParentID均为指针,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 body | ShouldBindJSON |
| Query string | ShouldBindQuery、c.Query(...)、c.DefaultQuery(...) |
| Path params | c.Param(...) |
| multipart/form-data | c.FormFile(...)、c.DefaultPostForm(...)、c.Request.FormValue(...) |
| Header | c.GetHeader(...)、c.Request.Header.Get(...) |
| Cookie | c.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")的用法)。
仓库示例:FindSysDictionary用ShouldBindQuery绑定查询参数,CreateSysDictionary用ShouldBindJSON绑定 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,提供Ok、OkWithMessage、OkWithData、OkWithDetailed、Fail、FailWithMessage、NoAuth等函数,统一{code, data, msg}结构(SUCCESS = 0,ERROR = 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.go | API 注册 |
以公告插件为例,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 中新增一个后端模块的标准动作:
- Model:在 server/model 下建结构体,继承
global.GVA_MODEL,补全json/form/gorm标签;请求结构放model/request/(列表查询内嵌request.PageInfo定义XxxSearch); - Service:在 server/service 下新建
xxx.go,只写业务逻辑,返回(result, err),不依赖gin.Context;在service/enter.go注册进ServiceGroup; - API:在 server/api/v1 下新建 API 文件,按真实参数来源绑定(JSON/Query/Path/form-data/Header/Cookie),通过
service.ServiceGroupApp调用服务,用response包统一输出,并写全 Swagger 注释;在api/.../enter.go的ApiGroup中注册; - Router:在 server/router 下新建路由文件,分组挂载中间件、绑定 API 处理函数;在
router/.../enter.go注册; - 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辅助】、表单生成器和可配置的导入导出等开发必备功能。
相关推荐
gin-vue-admin 后端分层约束实战指南:Router → API → Service → Model 依赖方向、数据权限引擎与 Swagger 规范
gin vue admin 后端分层约束实战指南:Router → API → Service → Model 依赖方向、数据权限引擎与 Swagger 规范
后端前端认证鉴权低代码任务调度gin-vue-admin 模块化开发规范:后端分层约束与插件开发实战指南
gin vue admin 模块化开发规范:后端分层约束与插件开发实战指南 本文是 gin vue admin 项目 aiDoc/modules 目录下模块级与
后端前端认证鉴权低代码企业应用gin-vue-admin 模块化开发规范全景:模块索引体系、后端分层约束与插件开发指南
gin vue admin 模块化开发规范全景:模块索引体系、后端分层约束与插件开发指南 导读 本文以 gin vue admin 仓库内 aiDoc/modu
后端前端认证鉴权低代码任务调度
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考