# GoalfyMax Admin 项目架构探索指南 ## 1. 项目整体架构 ### 1.1 目录结构 ``` /Users/youziba/goalfyagent/goalfymax-admin/ ├── cmd/ │ └── server/ │ └── main.go # 应用入口点 ├── internal/ │ ├── api/ │ │ ├── handlers/ # HTTP 请求处理层 │ │ │ ├── user_level_config_handler.go │ │ │ ├── page_handler.go │ │ │ ├── role_handler.go │ │ │ └── ... │ │ ├── middlewares/ # 中间件 │ │ └── routes/ │ │ └── routes.go # 路由配置 │ ├── config/ │ │ └── config.go # 配置管理 │ ├── models/ # 数据模型和请求/响应 │ │ ├── user_level_config.go │ │ ├── rbac.go # 包含 Page, RolePagePermission 等 │ │ ├── common.go # BaseModel, User, Role 等 │ │ ├── request.go # 所有请求模型 │ │ └── response.go │ ├── services/ # 业务逻辑层 │ │ ├── user_level_config_service.go │ │ ├── page_service.go │ │ └── ... │ └── storage/ # 数据访问层 │ ├── user_level_config_storage.go │ ├── page_storage.go │ ├── database.go # 数据库初始化和迁移 │ └── ... ├── pkg/ │ ├── middleware/ # 通用中间件 (RBAC, SSO等) │ └── utils/ # 工具函数 ├── etc/ │ ├── config.yaml # 开发配置 │ └── config-prod.yaml # 生产配置 └── go.mod / go.sum ``` ### 1.2 技术栈 - **框架**: Gin (Web 框架) - **数据库**: MySQL + GORM (ORM) - **日志**: Zap (结构化日志) - **配置**: Viper (YAML 配置) - **认证**: SSO + JWT - **架构模式**: 三层架构 (Handler -> Service -> Storage) --- ## 2. 菜单系统实现 (页面管理) ### 2.1 菜单系统术语澄清 在此项目中,**"菜单"** 通过 **"页面"(Page)** 概念实现。每个页面对应前端的一个菜单项。 ### 2.2 核心模型 **文件**: `/Users/youziba/goalfyagent/goalfymax-admin/internal/models/rbac.go` ```go // Page 页面模型(对应菜单项) type Page struct { BaseModel Name string `gorm:"size:50;not null" json:"name"` // 菜单名称 Path string `gorm:"uniqueIndex;size:100;not null" json:"path"` // 菜单路径 Icon string `gorm:"size:50" json:"icon"` // 菜单图标 SortOrder int `gorm:"default:0" json:"sortOrder"` // 排序顺序 IsActive bool `gorm:"default:true" json:"isActive"` // 是否激活 } // RolePagePermission 角色-页面权限关联 type RolePagePermission struct { BaseModel RoleID uint `gorm:"not null" json:"roleId"` PageID uint `gorm:"not null" json:"pageId"` Role Role `gorm:"foreignKey:RoleID" json:"role,omitempty"` Page Page `gorm:"foreignKey:PageID" json:"page,omitempty"` } ``` **表名映射**: - `admin_pages` - 页面/菜单表 - `admin_role_page_permissions` - 角色-页面权限关联表 ### 2.3 页面管理 API 路由 **文件**: `/Users/youziba/goalfyagent/goalfymax-admin/internal/api/routes/routes.go` (第174-181行) ```go // 页面管理 - 所有路由通过页面权限检查 pages := admin.Group("/pages") { pages.GET("", pageHandler.List) // 获取页面列表 pages.POST("", pageHandler.Create) // 创建页面 pages.GET("/:id", pageHandler.GetByID) // 获取页面详情 pages.PUT("/:id", pageHandler.Update) // 更新页面 pages.DELETE("/:id", pageHandler.Delete) // 删除页面 } ``` ### 2.4 页面管理完整流程 #### a) 创建页面 (菜单项) **Handler** (`/internal/api/handlers/page_handler.go`): ```go func (h *PageHandler) Create(c *gin.Context) { var req models.PageCreateRequest if err := c.ShouldBindJSON(&req); err != nil { h.response.ValidateError(c, err) return } page, err := h.pageService.Create(&req) if err != nil { h.response.InternalServerError(c, err.Error()) return } h.response.Success(c, page) } ``` **Request Model** (`/internal/models/request.go`): ```go type PageCreateRequest struct { Name string `json:"name" binding:"required,min=2,max=50"` Path string `json:"path" binding:"required"` Icon string `json:"icon"` SortOrder int `json:"sortOrder"` IsActive bool `json:"isActive"` } ``` **Service** (`/internal/services/page_service.go`): ```go func (s *pageService) Create(req *models.PageCreateRequest) (*models.Page, error) { // 检查页面路径是否已存在 _, err := s.pageStorage.GetByPath(req.Path) if err == nil { return nil, errors.New("页面路径已存在") } // 创建页面 page := &models.Page{ Name: req.Name, Path: req.Path, Icon: req.Icon, SortOrder: req.SortOrder, IsActive: req.IsActive, } err = s.pageStorage.Create(page) if err != nil { s.logger.Error("创建页面失败", zap.Error(err)) return nil, errors.New("创建页面失败") } s.logger.Info("页面创建成功", zap.String("name", page.Name)) return page, nil } ``` **Storage** (`/internal/storage/page_storage.go`): ```go func (s *pageStorage) Create(page *models.Page) error { return s.db.Create(page).Error } ``` #### b) 分配角色的页面权限 **Handler** (`/internal/api/handlers/rbac_handler.go`): ```go func (h *RBACHandler) AssignRolePagePermissions(c *gin.Context) { var req models.RolePagePermissionAssignRequest if err := c.ShouldBindJSON(&req); err != nil { h.response.ValidateError(c, err) return } err := h.rbacService.AssignRolePagePermissions(req.RoleID, req.PageIDs) if err != nil { h.response.InternalServerError(c, err.Error()) return } h.response.Success(c, nil) } ``` ### 2.5 权限检查流程 通过中间件动态检查用户对页面的访问权限,见 `pkg/middleware/rbac.go` --- ## 3. 现有配置模块实现 (用户等级配置) ### 3.1 模块文件清单 | 层级 | 文件位置 | 文件名 | |------|---------|--------| | **Models** | `internal/models/` | `user_level_config.go` | | **Storage** | `internal/storage/` | `user_level_config_storage.go` | | **Service** | `internal/services/` | `user_level_config_service.go` | | **Handler** | `internal/api/handlers/` | `user_level_config_handler.go` | | **Routes** | `internal/api/routes/` | `routes.go` (203-213行) | ### 3.2 完整代码示例 #### a) 模型定义 (`user_level_config.go`) ```go package models import "time" // UserLevelConfig 用户等级配置表 type UserLevelConfig struct { ID uint `json:"id" gorm:"primaryKey;autoIncrement;comment:主键ID"` LevelName string `json:"level_name" gorm:"not null;uniqueIndex:uk_level_name;type:varchar(50);comment:等级名称"` LevelCode string `json:"level_code" gorm:"not null;uniqueIndex:uk_level_code;type:varchar(50);comment:等级代码"` ProjectLimit int `json:"project_limit" gorm:"not null;default:0;comment:项目数限制,0表示不限"` Description string `json:"description" gorm:"type:varchar(255);comment:等级描述"` SortOrder int `json:"sort_order" gorm:"not null;default:0;comment:排序顺序"` Status int `json:"status" gorm:"not null;default:1;comment:状态 1-启用 0-禁用"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } func (UserLevelConfig) TableName() string { return "admin_user_level_configs" } // 请求模型 type UserLevelConfigCreateRequest struct { LevelName string `json:"level_name" binding:"required,min=1,max=50"` LevelCode string `json:"level_code" binding:"required,min=1,max=50"` ProjectLimit int `json:"project_limit" binding:"min=0"` Description string `json:"description" binding:"max=255"` SortOrder int `json:"sort_order"` } type UserLevelConfigUpdateRequest struct { LevelName string `json:"level_name" binding:"required,min=1,max=50"` ProjectLimit int `json:"project_limit" binding:"min=0"` Description string `json:"description" binding:"max=255"` SortOrder int `json:"sort_order"` } type UserLevelConfigListRequest struct { LevelName string `form:"level_name"` Status *int `form:"status"` Page int `form:"page,default=1"` Size int `form:"size,default=20"` } type UserLevelConfigUpdateStatusRequest struct { Status int `json:"status" binding:"required,oneof=0 1"` } ``` #### b) 存储层接口 (`user_level_config_storage.go`) ```go package storage import ( "goalfymax-admin/internal/models" "gorm.io/gorm" ) // 接口定义 type UserLevelConfigStorage interface { Create(config *models.UserLevelConfig) error GetByID(id uint) (*models.UserLevelConfig, error) GetByLevelCode(levelCode string) (*models.UserLevelConfig, error) Update(config *models.UserLevelConfig) error Delete(id uint) error List(req *models.UserLevelConfigListRequest) ([]models.UserLevelConfig, int64, error) UpdateStatus(id uint, status int) error GetAll() ([]models.UserLevelConfig, error) } // 实现 type userLevelConfigStorage struct { db *gorm.DB } func NewUserLevelConfigStorage() UserLevelConfigStorage { return &userLevelConfigStorage{db: DB} } func (s *userLevelConfigStorage) Create(config *models.UserLevelConfig) error { return s.db.Create(config).Error } func (s *userLevelConfigStorage) List(req *models.UserLevelConfigListRequest) ([]models.UserLevelConfig, int64, error) { var configs []models.UserLevelConfig var total int64 query := s.db.Model(&models.UserLevelConfig{}) if req.LevelName != "" { query = query.Where("level_name LIKE ?", "%"+req.LevelName+"%") } if req.Status != nil { query = query.Where("status = ?", *req.Status) } if err := query.Count(&total).Error; err != nil { return nil, 0, err } offset := (req.Page - 1) * req.Size if err := query.Order("sort_order ASC, id DESC").Offset(offset).Limit(req.Size).Find(&configs).Error; err != nil { return nil, 0, err } return configs, total, nil } ``` #### c) 服务层 (`user_level_config_service.go`) ```go package services import ( "errors" "goalfymax-admin/internal/models" "goalfymax-admin/internal/storage" "goalfymax-admin/pkg/utils" "go.uber.org/zap" ) type UserLevelConfigService interface { Create(req *models.UserLevelConfigCreateRequest) (*models.UserLevelConfig, error) GetByID(id uint) (*models.UserLevelConfig, error) Update(id uint, req *models.UserLevelConfigUpdateRequest) (*models.UserLevelConfig, error) Delete(id uint) error List(req *models.UserLevelConfigListRequest) ([]models.UserLevelConfig, int64, error) UpdateStatus(id uint, status int) error GetAll() ([]models.UserLevelConfig, error) } type userLevelConfigService struct { storage storage.UserLevelConfigStorage logger *utils.Logger } func NewUserLevelConfigService(storage storage.UserLevelConfigStorage, logger *utils.Logger) UserLevelConfigService { return &userLevelConfigService{ storage: storage, logger: logger, } } func (s *userLevelConfigService) Create(req *models.UserLevelConfigCreateRequest) (*models.UserLevelConfig, error) { // 检查等级代码是否已存在 _, err := s.storage.GetByLevelCode(req.LevelCode) if err == nil { return nil, errors.New("等级代码已存在") } config := &models.UserLevelConfig{ LevelName: req.LevelName, LevelCode: req.LevelCode, ProjectLimit: req.ProjectLimit, Description: req.Description, SortOrder: req.SortOrder, Status: 1, // 默认启用 } err = s.storage.Create(config) if err != nil { s.logger.Error("创建用户等级配置失败", zap.Error(err)) return nil, errors.New("创建用户等级配置失败") } s.logger.Info("用户等级配置创建成功", zap.String("level_name", config.LevelName)) return config, nil } // ... 其他方法 Update, Delete, List 等 ``` #### d) Handler 处理器 (`user_level_config_handler.go`) ```go package handlers import ( "goalfymax-admin/internal/models" "goalfymax-admin/internal/services" "goalfymax-admin/pkg/utils" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" ) type UserLevelConfigHandler struct { service services.UserLevelConfigService response *utils.Response logger *utils.Logger } func NewUserLevelConfigHandler(service services.UserLevelConfigService, logger *utils.Logger) *UserLevelConfigHandler { return &UserLevelConfigHandler{ service: service, response: utils.NewResponse(), logger: logger, } } func (h *UserLevelConfigHandler) Create(c *gin.Context) { var req models.UserLevelConfigCreateRequest if err := c.ShouldBindJSON(&req); err != nil { h.response.ValidateError(c, err) return } config, err := h.service.Create(&req) if err != nil { h.logger.Error("创建用户等级配置失败", zap.Error(err)) h.response.InternalServerError(c, err.Error()) return } h.response.Success(c, config) } func (h *UserLevelConfigHandler) List(c *gin.Context) { var req models.UserLevelConfigListRequest if err := c.ShouldBindQuery(&req); err != nil { h.response.ValidateError(c, err) return } if req.Page <= 0 { req.Page = 1 } if req.Size <= 0 { req.Size = 20 } configs, total, err := h.service.List(&req) if err != nil { h.logger.Error("获取用户等级配置列表失败", zap.Error(err)) h.response.InternalServerError(c, "获取列表失败") return } h.response.Page(c, configs, total, req.Page, req.Size) } // ... 其他方法 Update, Delete, GetByID 等 ``` --- ## 4. Handler/Controller 层实现模式 ### 4.1 Handler 结构 每个 Handler 都遵循相同的模式: ```go type [Resource]Handler struct { service services.[Resource]Service // 业务服务 response *utils.Response // 响应工具 logger *utils.Logger // 日志 } // 构造函数 func New[Resource]Handler(service services.[Resource]Service, logger *utils.Logger) *[Resource]Handler { return &[Resource]Handler{ service: service, response: utils.NewResponse(), logger: logger, } } // 处理方法 func (h *[Resource]Handler) Create(c *gin.Context) { ... } func (h *[Resource]Handler) GetByID(c *gin.Context) { ... } func (h *[Resource]Handler) Update(c *gin.Context) { ... } func (h *[Resource]Handler) Delete(c *gin.Context) { ... } func (h *[Resource]Handler) List(c *gin.Context) { ... } ``` ### 4.2 标准响应处理 Handler 使用 `utils.Response` 提供统一的响应: ```go h.response.Success(c, data) // 成功响应 h.response.BadRequest(c, "错误信息") // 请求错误 (400) h.response.NotFound(c, "资源不存在") // 资源不存在 (404) h.response.InternalServerError(c, "错误信息") // 服务器错误 (500) h.response.ValidateError(c, err) // 验证错误 h.response.Page(c, data, total, page, size) // 分页响应 ``` ### 4.3 错误处理 ```go if err := c.ShouldBindJSON(&req); err != nil { h.response.ValidateError(c, err) return } if err != nil { h.logger.Error("操作失败", zap.Error(err)) h.response.InternalServerError(c, "错误消息") return } ``` --- ## 5. 路由配置 ### 5.1 路由组织结构 **文件**: `/Users/youziba/goalfyagent/goalfymax-admin/internal/api/routes/routes.go` ```go func SetupRoutes( userService services.UserService, roleService services.RoleService, pageService services.PageService, quotaService services.QuotaService, ssoService services.SSOService, rbacService services.RBACService, userLevelConfigService services.UserLevelConfigService, logger *utils.Logger, appConfig *config.Config, ) *gin.Engine { r := gin.New() // 中间件设置 r.Use(cors.New(cors.Config{...})) r.Use(middlewares.RequestLogMiddleware(logger)) r.Use(gin.Recovery()) // SSO 路由 (不需要认证) sso := api.Group("/sso") { sso.POST("/login", ssoHandler.HandleSSOLogin) // ... } // 管理员路由 (需要认证) admin := api.Group("/admin") admin.Use(authMiddleware.RequireAuth()) { // 用户管理 users := admin.Group("/users") { users.GET("", userHandler.List) users.POST("", userHandler.Create) users.GET("/:id", userHandler.GetByID) users.PUT("/:id", userHandler.Update) users.DELETE("/:id", userHandler.Delete) } // 页面管理 pages := admin.Group("/pages") { pages.GET("", pageHandler.List) pages.POST("", pageHandler.Create) pages.GET("/:id", pageHandler.GetByID) pages.PUT("/:id", pageHandler.Update) pages.DELETE("/:id", pageHandler.Delete) } // 用户等级配置管理 userLevelConfigs := admin.Group("/user-level-configs") { userLevelConfigs.GET("", userLevelConfigHandler.List) userLevelConfigs.GET("/all", userLevelConfigHandler.GetAll) userLevelConfigs.POST("", userLevelConfigHandler.Create) userLevelConfigs.GET("/:id", userLevelConfigHandler.GetByID) userLevelConfigs.PUT("/:id", userLevelConfigHandler.Update) userLevelConfigs.DELETE("/:id", userLevelConfigHandler.Delete) userLevelConfigs.PUT("/:id/status", userLevelConfigHandler.UpdateStatus) } } return r } ``` ### 5.2 路由注册步骤 (在 main.go 中) ```go // 创建服务实例 userLevelConfigService := services.NewUserLevelConfigService( storage.NewUserLevelConfigStorage(), logger, ) // 设置路由并传入服务 router := routes.SetupRoutes( userService, roleService, pageService, quotaService, ssoService, rbacService, userLevelConfigService, // 新增 logger, cfg, ) ``` --- ## 6. 数据库迁移 ### 6.1 迁移方式 项目使用 **GORM 的 AutoMigrate** 自动迁移,无需手写 SQL 脚本。 **文件**: `/Users/youziba/goalfyagent/goalfymax-admin/internal/storage/database.go` ```go // AutoMigrate 自动迁移数据库表 func AutoMigrate() error { if DB == nil { return fmt.Errorf("数据库未初始化") } // 迁移所有模型 err := DB.AutoMigrate( &models.UserLevelConfig{}, // 添加其他模型... ) if err != nil { return fmt.Errorf("数据库迁移失败: %w", err) } // 初始化默认数据 if err := initDefaultUserLevelConfigs(); err != nil { return fmt.Errorf("初始化默认用户等级配置失败: %w", err) } return nil } // 初始化默认数据示例 func initDefaultUserLevelConfigs() error { var count int64 if err := DB.Model(&models.UserLevelConfig{}).Count(&count).Error; err != nil { return err } if count > 0 { return nil } defaultConfigs := []models.UserLevelConfig{ { LevelName: "普通", LevelCode: "normal", ProjectLimit: 2, Description: "普通用户等级,可创建2个项目", SortOrder: 1, Status: 1, }, { LevelName: "VIP", LevelCode: "vip", ProjectLimit: 10, Description: "VIP用户等级,可创建10个项目", SortOrder: 2, Status: 1, }, } return DB.Create(&defaultConfigs).Error } ``` ### 6.2 表结构生成规则 GORM 根据模型中的 tag 自动生成表: ```go type UserLevelConfig struct { ID uint `gorm:"primaryKey;autoIncrement;comment:主键ID"` // 自增主键 LevelName string `gorm:"uniqueIndex:uk_level_name;type:varchar(50);comment:等级名称"` // 唯一索引 LevelCode string `gorm:"not null;uniqueIndex:uk_level_code"` // 非空 + 唯一索引 ProjectLimit int `gorm:"default:0"` // 默认值 Status int `gorm:"default:1"` // 默认值 CreatedAt time.Time // 自动时间戳 UpdatedAt time.Time // 自动时间戳 } // 生成的表名 func (UserLevelConfig) TableName() string { return "admin_user_level_configs" } ``` **生成的表**: `admin_user_level_configs` ### 6.3 数据库初始化流程 **在 main.go 中**: ```go // 初始化数据库 if err := storage.InitDatabase(logger); err != nil { logger.Fatal("初始化数据库失败", zap.Error(err)) } // 自动迁移 if err := storage.AutoMigrate(); err != nil { logger.Fatal("数据库迁移失败", zap.Error(err)) } ``` --- ## 7. 配置管理 ### 7.1 配置文件结构 **文件**: `/Users/youziba/goalfyagent/goalfymax-admin/etc/config.yaml` ```yaml server: addr: "0.0.0.0" port: 8087 database: dsn: "user:password@tcp(host:3306)/db?charset=utf8mb4&parseTime=True&loc=Local" maxIdleConns: 10 maxOpenConns: 100 logLevel: "info" gateway: base_url: "http://gateway:8080" timeout: 30 auth: login_url: "http://gateway:8080/api/login" key: "api-key" sso: sso_server_url: "https://sso.example.com" client_id: "your-client-id" client_secret: "your-client-secret" redirect_uri: "http://localhost:3003" scope: "openid profile email" resource_aud: "api://admin" timeout: 30s log: level: "debug" format: "json" output: "stdout" message_push: goalfymax_base_url: "https://goalfymax.example.com" timeout: 30 retry_count: 3 retry_interval: 1000 ``` ### 7.2 配置结构定义 **文件**: `/Users/youziba/goalfyagent/goalfymax-admin/internal/config/config.go` ```go type Config struct { Server ServerConfig `mapstructure:"server"` Database DatabaseConfig `mapstructure:"database"` Gateway GatewayConfig `mapstructure:"gateway"` SSO SSOConfig `mapstructure:"sso"` MessagePush MessagePushConfig `mapstructure:"message_push"` Log LogConfig `mapstructure:"log"` } type ServerConfig struct { Addr string `mapstructure:"addr"` Port int `mapstructure:"port"` } type DatabaseConfig struct { DSN string `mapstructure:"dsn"` MaxIdleConns int `mapstructure:"maxIdleConns"` MaxOpenConns int `mapstructure:"maxOpenConns"` LogLevel string `mapstructure:"logLevel"` } ``` --- ## 8. 实现新功能的完整步骤 ### 8.1 添加"通用配置"功能模块 假设要添加 `GeneralConfig` (通用配置) 模块: #### 步骤 1: 创建模型 **文件**: `/internal/models/general_config.go` ```go package models import "time" type GeneralConfig struct { ID uint `json:"id" gorm:"primaryKey;autoIncrement"` Key string `json:"key" gorm:"uniqueIndex;type:varchar(100);not null"` Value string `json:"value" gorm:"type:longtext"` Type string `json:"type" gorm:"type:varchar(50)"` // string, int, bool, json Desc string `json:"desc" gorm:"type:varchar(255)"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } func (GeneralConfig) TableName() string { return "admin_general_configs" } // 请求模型 (在 request.go 中添加) type GeneralConfigCreateRequest struct { Key string `json:"key" binding:"required,min=1,max=100"` Value string `json:"value" binding:"required"` Type string `json:"type"` Desc string `json:"desc" binding:"max=255"` } type GeneralConfigUpdateRequest struct { Value string `json:"value" binding:"required"` Desc string `json:"desc" binding:"max=255"` } type GeneralConfigListRequest struct { Key string `form:"key"` Type string `form:"type"` Page int `form:"page,default=1"` Size int `form:"size,default=20"` } ``` #### 步骤 2: 创建存储层 **文件**: `/internal/storage/general_config_storage.go` ```go package storage import ( "goalfymax-admin/internal/models" "gorm.io/gorm" ) type GeneralConfigStorage interface { Create(config *models.GeneralConfig) error GetByID(id uint) (*models.GeneralConfig, error) GetByKey(key string) (*models.GeneralConfig, error) Update(config *models.GeneralConfig) error Delete(id uint) error List(req *models.GeneralConfigListRequest) ([]models.GeneralConfig, int64, error) GetAll() ([]models.GeneralConfig, error) } type generalConfigStorage struct { db *gorm.DB } func NewGeneralConfigStorage() GeneralConfigStorage { return &generalConfigStorage{db: DB} } func (s *generalConfigStorage) Create(config *models.GeneralConfig) error { return s.db.Create(config).Error } func (s *generalConfigStorage) GetByKey(key string) (*models.GeneralConfig, error) { var config models.GeneralConfig err := s.db.Where("key = ?", key).First(&config).Error return &config, err } func (s *generalConfigStorage) List(req *models.GeneralConfigListRequest) ([]models.GeneralConfig, int64, error) { var configs []models.GeneralConfig var total int64 query := s.db.Model(&models.GeneralConfig{}) if req.Key != "" { query = query.Where("key LIKE ?", "%"+req.Key+"%") } if req.Type != "" { query = query.Where("type = ?", req.Type) } query.Count(&total) offset := (req.Page - 1) * req.Size err := query.Order("id DESC").Offset(offset).Limit(req.Size).Find(&configs).Error return configs, total, err } ``` #### 步骤 3: 创建服务层 **文件**: `/internal/services/general_config_service.go` ```go package services import ( "errors" "goalfymax-admin/internal/models" "goalfymax-admin/internal/storage" "goalfymax-admin/pkg/utils" "go.uber.org/zap" ) type GeneralConfigService interface { Create(req *models.GeneralConfigCreateRequest) (*models.GeneralConfig, error) GetByID(id uint) (*models.GeneralConfig, error) GetByKey(key string) (*models.GeneralConfig, error) Update(id uint, req *models.GeneralConfigUpdateRequest) (*models.GeneralConfig, error) Delete(id uint) error List(req *models.GeneralConfigListRequest) ([]models.GeneralConfig, int64, error) GetAll() ([]models.GeneralConfig, error) } type generalConfigService struct { storage storage.GeneralConfigStorage logger *utils.Logger } func NewGeneralConfigService(storage storage.GeneralConfigStorage, logger *utils.Logger) GeneralConfigService { return &generalConfigService{ storage: storage, logger: logger, } } func (s *generalConfigService) Create(req *models.GeneralConfigCreateRequest) (*models.GeneralConfig, error) { // 检查 Key 是否已存在 _, err := s.storage.GetByKey(req.Key) if err == nil { return nil, errors.New("配置 Key 已存在") } config := &models.GeneralConfig{ Key: req.Key, Value: req.Value, Type: req.Type, Desc: req.Desc, } if err := s.storage.Create(config); err != nil { s.logger.Error("创建通用配置失败", zap.Error(err)) return nil, errors.New("创建配置失败") } s.logger.Info("通用配置创建成功", zap.String("key", config.Key)) return config, nil } // ... 其他方法 ``` #### 步骤 4: 创建 Handler **文件**: `/internal/api/handlers/general_config_handler.go` ```go package handlers import ( "goalfymax-admin/internal/models" "goalfymax-admin/internal/services" "goalfymax-admin/pkg/utils" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" ) type GeneralConfigHandler struct { service services.GeneralConfigService response *utils.Response logger *utils.Logger } func NewGeneralConfigHandler(service services.GeneralConfigService, logger *utils.Logger) *GeneralConfigHandler { return &GeneralConfigHandler{ service: service, response: utils.NewResponse(), logger: logger, } } func (h *GeneralConfigHandler) Create(c *gin.Context) { var req models.GeneralConfigCreateRequest if err := c.ShouldBindJSON(&req); err != nil { h.response.ValidateError(c, err) return } config, err := h.service.Create(&req) if err != nil { h.logger.Error("创建通用配置失败", zap.Error(err)) h.response.InternalServerError(c, err.Error()) return } h.response.Success(c, config) } func (h *GeneralConfigHandler) List(c *gin.Context) { var req models.GeneralConfigListRequest if err := c.ShouldBindQuery(&req); err != nil { h.response.ValidateError(c, err) return } if req.Page <= 0 { req.Page = 1 } if req.Size <= 0 { req.Size = 20 } configs, total, err := h.service.List(&req) if err != nil { h.response.InternalServerError(c, "获取列表失败") return } h.response.Page(c, configs, total, req.Page, req.Size) } // ... 其他方法 GetByID, Update, Delete ``` #### 步骤 5: 注册路由 **在** `/internal/api/routes/routes.go` 的 `SetupRoutes` 函数中: ```go func SetupRoutes( // ... 其他参数 generalConfigService services.GeneralConfigService, // ... 其他参数 ) *gin.Engine { // ... 其他代码 admin := api.Group("/admin") admin.Use(authMiddleware.RequireAuth()) { // ... 其他路由组 // 通用配置管理 generalConfigs := admin.Group("/general-configs") { generalConfigs.GET("", generalConfigHandler.List) generalConfigs.POST("", generalConfigHandler.Create) generalConfigs.GET("/:id", generalConfigHandler.GetByID) generalConfigs.PUT("/:id", generalConfigHandler.Update) generalConfigs.DELETE("/:id", generalConfigHandler.Delete) } } return r } ``` #### 步骤 6: 在 main.go 中注册服务 **文件**: `/cmd/server/main.go` ```go // 创建通用配置服务 generalConfigService := services.NewGeneralConfigService( storage.NewGeneralConfigStorage(), logger, ) // 设置路由 router := routes.SetupRoutes( userService, roleService, pageService, quotaService, ssoService, rbacService, userLevelConfigService, generalConfigService, // 新增 logger, cfg, ) ``` #### 步骤 7: 数据库迁移 **在** `/internal/storage/database.go` 的 `AutoMigrate` 函数中: ```go func AutoMigrate() error { err := DB.AutoMigrate( &models.UserLevelConfig{}, &models.GeneralConfig{}, // 新增 ) if err != nil { return fmt.Errorf("数据库迁移失败: %w", err) } // ... 初始化默认数据 return nil } ``` --- ## 9. 最佳实践总结 ### 9.1 代码组织 - 遵循三层架构: Handler (API) -> Service (业务逻辑) -> Storage (数据访问) - 每个资源类型对应独立的文件组 - 使用接口定义 Service 和 Storage,便于测试和扩展 ### 9.2 错误处理 - 在 Service 层捕获错误,返回有意义的错误信息 - 在 Handler 层根据错误类型返回适当的 HTTP 状态码 - 使用结构化日志记录所有错误 ### 9.3 验证 - 在请求模型中使用 Gin 的 binding tag 进行字段验证 - 在 Service 层进行业务逻辑验证(如唯一性检查) - 统一使用 `ValidateError` 返回验证错误 ### 9.4 数据库 - 使用 GORM tag 定义表结构和约束 - 为经常查询的字段添加索引 - 在初始化时创建默认数据 - 遵循命名约定: 表名为 `admin_[resource]s` ### 9.5 路由设计 - RESTful 设计: GET/POST/PUT/DELETE - 使用路由组织逻辑相关的端点 - 在路由级别应用认证中间件 - 提供 `.../all` 端点用于不分页的列表查询 ### 9.6 日志记录 - 在每个操作前后记录日志 - 使用结构化日志的字段记录上下文信息 - 记录所有错误及其堆栈信息 --- ## 10. 相关文件快速查找表 | 功能 | 文件路径 | |------|---------| | 模型定义 | `/internal/models/` | | 请求/响应 | `/internal/models/request.go`, `response.go` | | 存储接口 | `/internal/storage/` | | 数据库迁移 | `/internal/storage/database.go` | | 业务逻辑 | `/internal/services/` | | HTTP 处理 | `/internal/api/handlers/` | | 路由配置 | `/internal/api/routes/routes.go` | | 应用入口 | `/cmd/server/main.go` | | 配置管理 | `/internal/config/config.go` | | YAML 配置 | `/etc/config.yaml` | | 中间件 | `/pkg/middleware/`, `/internal/api/middlewares/` | | 工具函数 | `/pkg/utils/` |