# GoalfyMax Admin 后端管理系统 - 架构文档 ## 目录 - [项目概述](#项目概述) - [技术栈](#技术栈) - [项目结构](#项目结构) - [架构设计](#架构设计) - [核心功能模块](#核心功能模块) - [数据库设计](#数据库设计) - [API接口说明](#api接口说明) - [认证与权限](#认证与权限) - [中间件系统](#中间件系统) - [服务层设计](#服务层设计) - [配置管理](#配置管理) - [部署方案](#部署方案) - [开发指南](#开发指南) --- ## 项目概述 GoalfyMax Admin 是一个企业级的后端管理系统,基于 Go 语言开发,采用分层架构设计。系统提供了完整的用户管理、权限控制、财务管理、邀请码系统等功能,支持 SSO 单点登录和 RBAC 权限控制。 go run cmd/server/main.go --env dev ### 主要特性 - **企业级架构**: 严格的三层架构,职责分离明确 - **SSO单点登录**: 基于 OAuth2/OIDC 协议的认证系统 - **RBAC权限控制**: 灵活的角色-页面权限管理 - **完整的审计日志**: 记录所有数据变更操作 - **多数据库支持**: MySQL + PostgreSQL + Redis - **容器化部署**: Docker + Kubernetes 生产就绪 - **国际化支持**: 中英文邮件模板 - **可扩展性**: 模块化设计,易于扩展 ### 代码规模 - **总代码量**: 约 1.5 万行 Go 代码 - **服务层**: 20 个 Service,共 7305 行代码 - **API层**: 18 个 Handler - **存储层**: 17 个 Storage - **数据模型**: 15 个 Model --- ## 技术栈 ### 核心框架 | 技术 | 版本 | 用途 | |------|------|------| | Go | 1.25+ | 编程语言 | | Gin | v1.11.0 | Web 框架 | | GORM | v1.30.1 | ORM 框架 | ### 数据库 | 数据库 | 用途 | |--------|------| | MySQL | 主数据库 | | PostgreSQL | MCP 配置存储 | | Redis v9.16.0 | 缓存和会话管理 | ### 认证与安全 | 技术 | 版本 | 用途 | |------|------|------| | JWT | v5.3.0 | Token 管理 | | OAuth2 | - | SSO 单点登录 | | PKCE | - | OAuth2 安全增强 | ### 基础设施 | 组件 | 版本 | 用途 | |------|------|------| | Viper | v1.20.1 | 配置管理 | | Zap | v1.27.0 | 结构化日志 | | CORS | v1.7.0 | 跨域支持 | | AWS SDK | v2 | S3 对象存储 | ### 其他依赖 - **UUID v1.6.0**: 唯一标识符生成 - **Decimal v1.3.1**: 精确数值计算 - **Email v4.0**: 邮件发送 --- ## 项目结构 ``` goalfylearning-admin/ ├── cmd/ │ └── server/ │ └── main.go # 应用程序入口 ├── internal/ # 内部私有代码 │ ├── api/ # API 层 │ │ ├── handlers/ # HTTP 处理器 (18个) │ │ │ ├── user_handler.go │ │ │ ├── goalfymax_user_handler.go │ │ │ ├── sso_handler.go │ │ │ ├── rbac_handler.go │ │ │ ├── role_handler.go │ │ │ ├── page_handler.go │ │ │ ├── invite_code_handler.go │ │ │ ├── invite_code_application_handler.go │ │ │ ├── user_level_config_handler.go │ │ │ ├── finance_handler.go │ │ │ ├── quota_handler.go │ │ │ ├── message_push_handler.go │ │ │ ├── user_feedback_handler.go │ │ │ ├── audit_log_handler.go │ │ │ ├── system_config_handler.go │ │ │ ├── user_project_quota_handler.go │ │ │ ├── vendor_model_pricing_handler.go │ │ │ ├── vm_pricing_handler.go │ │ │ └── mcp_provider_handler.go │ │ ├── middlewares/ # 中间件 │ │ │ ├── logging.go # 请求日志 │ │ │ └── api_log_middleware.go # API 调用日志 │ │ └── routes/ │ │ └── routes.go # 路由配置 │ ├── config/ │ │ └── config.go # 配置结构定义 │ ├── jobs/ # 定时任务 │ │ ├── mcp_usage_job.go # MCP 使用余额同步 │ │ └── model_token_job.go # 模型 Token 余额同步 │ ├── models/ # 数据模型 (15个) │ │ ├── common.go # 基础模型 │ │ ├── user.go │ │ ├── goalfymax_user.go │ │ ├── user_level_config.go │ │ ├── invite_code.go │ │ ├── invite_code_application.go │ │ ├── rbac.go │ │ ├── audit_log.go │ │ ├── balance_operation_log.go │ │ ├── message_push.go │ │ ├── quota_models.go │ │ ├── user_feedback.go │ │ ├── user_project_quota.go │ │ ├── sso.go │ │ ├── request.go │ │ └── response.go │ ├── notifier/ │ │ └── notifier.go # 钉钉通知服务 │ ├── oss/ │ │ └── s3.go # S3 对象存储 │ ├── services/ # 业务逻辑层 (20个,7305行) │ │ ├── user_service.go │ │ ├── goalfymax_user_service.go │ │ ├── sso_service.go │ │ ├── sso_admin_service.go │ │ ├── rbac_service.go │ │ ├── role_service.go │ │ ├── page_service.go │ │ ├── invite_code_service.go │ │ ├── invite_code_application_service.go │ │ ├── user_level_config_service.go │ │ ├── finance_service.go │ │ ├── quota_service.go │ │ ├── message_push_service.go │ │ ├── user_feedback_service.go │ │ ├── audit_log_service.go │ │ ├── system_config_service.go │ │ ├── user_project_quota_service.go │ │ ├── email_service.go │ │ ├── gateway_client.go │ │ └── log_service.go │ └── storage/ # 数据访问层 (17个) │ ├── database.go # 数据库初始化 │ ├── postgres.go # PostgreSQL 连接 │ ├── user_storage.go │ ├── goalfymax_user_storage.go │ ├── rbac_storage.go │ ├── role_storage.go │ ├── page_storage.go │ ├── invite_code.go │ ├── user_level_config_storage.go │ ├── audit_log_storage.go │ ├── balance_operation_log_storage.go │ ├── message_push_storage.go │ ├── user_feedback_storage.go │ ├── system_config_storage.go │ ├── user_project_quota_storage.go │ ├── sso_storage.go │ └── log_storage.go ├── pkg/ # 公共可复用包 │ ├── middleware/ │ │ ├── auth.go # 认证中间件 │ │ ├── rbac.go # 权限中间件 │ │ └── sso_client.go # SSO 客户端 │ ├── redis/ │ │ └── redis.go # Redis 客户端 │ └── utils/ │ ├── crypto.go # 加密工具 │ ├── jwt.go # JWT 工具 │ ├── logger.go # 日志工具 │ ├── response.go # 响应封装 │ └── validator.go # 验证工具 ├── etc/ # 配置文件 │ ├── config.yaml # 开发配置 │ └── config-prod.yaml # 生产配置 ├── k8s/ # Kubernetes 部署配置 │ ├── deployment.yaml │ ├── service.yaml │ └── configmap.yaml ├── docs/ # 文档 │ ├── deployment_and_testing.md │ ├── email_templates_preview.html │ └── *.html # 邮件模板 ├── migrations/ # 数据库迁移脚本 │ ├── 20250129_add_client_id_to_invite_codes.sql │ ├── 20250131_add_invite_code_applications_table.sql │ └── 20250204_add_language_to_invite_code_applications.sql ├── scripts/ # 脚本工具 │ ├── start.sh │ ├── stop.sh │ ├── status.sh │ ├── migrate.sh │ ├── invite_code_api.py │ └── test_*.sh ├── test/ # 测试文件 │ ├── test_email.go │ ├── send_test_email.go │ └── preview_email.go ├── Dockerfile # Docker 构建文件 ├── build.sh # 构建脚本 ├── build-and-push.sh # 构建并推送到 ECR ├── go.mod # Go 模块定义 └── go.sum # 依赖校验 ``` --- ## 架构设计 ### 整体架构 系统采用**分层架构**设计,从上到下分为: ``` ┌─────────────────────────────────────────────┐ │ HTTP Client (前端) │ └─────────────────┬───────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ Gin Router & Middlewares │ │ (认证、权限、日志、CORS、错误处理) │ └─────────────────┬───────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ Handler Layer (API层) │ │ (参数验证、请求解析、响应封装) │ └─────────────────┬───────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ Service Layer (业务逻辑层) │ │ (业务规则、事务处理、外部服务调用) │ └─────────────────┬───────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ Storage Layer (数据访问层) │ │ (数据库操作、缓存操作) │ └─────────────────┬───────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ Database (MySQL/PostgreSQL/Redis) │ └─────────────────────────────────────────────┘ ``` ### 架构特点 1. **严格的层次分离** - Handler 只负责 HTTP 请求处理 - Service 封装所有业务逻辑 - Storage 封装所有数据访问 2. **依赖注入** - Service 依赖 Storage 接口 - Handler 依赖 Service 接口 - 便于单元测试和模块替换 3. **统一错误处理** - 使用 `utils.ErrorResponse` 统一错误响应 - 使用 `utils.SuccessResponse` 统一成功响应 4. **中间件链** - 认证 -> 权限 -> 日志 -> 业务处理 ### 数据流向 ``` Request → Middleware Chain → Handler → Service → Storage → Database ↓ External API ↓ Redis Cache ``` ### 外部依赖 系统依赖以下外部服务: - **SSO Server**: passport.goalfy.ai (OAuth2/OIDC 认证) - **AI Gateway**: 44.247.156.94:8080 (配额查询、网关服务) - **SMTP Server**: smtp.mxhichina.com (邮件发送) - **DingTalk**: 钉钉机器人 (告警通知) - **AWS S3**: 对象存储 --- ## 核心功能模块 ### 1. 用户管理系统 **位置**: `internal/api/handlers/user_handler.go`, `internal/services/user_service.go` **功能**: - 用户 CRUD 操作 - 用户状态管理 (启用/禁用) - 角色分配 - 权限查询 - 系统角色变更 **API 路由**: `/api/admin/users/*` **主要接口**: - `GET /api/admin/users` - 用户列表 (支持分页、搜索) - `POST /api/admin/users` - 创建用户 - `PUT /api/admin/users/:id` - 更新用户 - `DELETE /api/admin/users/:id` - 删除用户 - `PUT /api/admin/users/:id/status` - 更新状态 - `PUT /api/admin/users/:id/roles` - 更新角色 - `POST /api/admin/users/change-system-role` - 变更系统角色 --- ### 2. GoalfyMax 用户管理 **位置**: `internal/models/goalfymax_user.go`, `internal/services/goalfymax_user_service.go` **核心字段**: ```go type GoalfyMaxUser struct { BaseModel UserID uint `json:"user_id" gorm:"uniqueIndex;not null"` // SSO 用户 ID Username string `json:"username"` Email string `json:"email"` Nickname string `json:"nickname"` Avatar string `json:"avatar"` UserLevelCode string `json:"user_level_code"` // 用户等级代码 IsBanned bool `json:"is_banned" gorm:"default:false"` // 是否封禁 BanReason string `json:"ban_reason"` // 封禁原因 BannedAt *time.Time `json:"banned_at"` // 封禁时间 BannedBy *uint `json:"banned_by"` // 封禁操作人 GoalfyHubPermission int `json:"goalfy_hub_permission" gorm:"default:0"` // Hub权限 (0/1) Version int `json:"version" gorm:"default:1"` // 版本 (1:用户版 2:观察版 3:双版) Balance float64 `json:"balance" gorm:"-"` // 余额(从Redis查询,不存数据库) } ``` **功能**: - 用户创建、编辑、删除 - 封禁/解封操作 - 余额增减操作 - 版本管理 (用户版/观察版/双版) - Hub 权限管理 **API 路由**: `/api/admin/goalfymax-users/*` **余额操作**: - 余额存储在 Redis: `goalfymax:user:balance:{user_id}` - 支持增加余额和扣减余额 - 自动记录余额操作日志 (balance_operation_logs 表) --- ### 3. SSO 单点登录系统 **位置**: `internal/services/sso_service.go`, `pkg/middleware/auth.go` **认证流程**: ``` 1. 前端请求登录 ↓ 2. 后端生成 PKCE state 和 code_verifier ↓ 3. 返回 SSO 授权 URL ↓ 4. 用户在 SSO 服务器登录 ↓ 5. SSO 重定向到回调 URL (带 code 和 state) ↓ 6. 后端验证 state,使用 code_verifier 交换 token ↓ 7. 获取 access_token, refresh_token, id_token ↓ 8. 调用 SSO userinfo 接口获取用户信息 ↓ 9. 自动创建/更新本地用户 ↓ 10. 返回 token 给前端 ``` **特性**: - **OAuth2/OpenID Connect** 协议 - **PKCE** 安全增强 (防止授权码拦截攻击) - **自动用户创建**: 首次登录自动创建本地用户记录 - **在线用户管理**: 内存存储在线用户会话 - **批量登出**: 支持批量踢出用户 - **Token 刷新**: 自动刷新过期的 access_token **API 路由**: `/api/sso/*` **配置** (etc/config.yaml): ```yaml sso: sso_server_url: "https://passport.goalfy.ai" client_id: "xv5Xesd4ry1_I3hP3xYXNw" redirect_uri: "http://localhost:3003" scope: "openid profile email" resource_aud: "api://admin" ``` **关键代码**: - 认证中间件: `pkg/middleware/auth.go:84-159` (RequireAuth) - 自动用户创建: `pkg/middleware/auth.go:230-290` (findOrCreateUser) --- ### 4. RBAC 权限系统 **位置**: `internal/models/rbac.go`, `pkg/middleware/rbac.go` **权限模型**: ``` User (用户) ↓ (role_id) Role (角色) ↓ (role_page_permissions) Page (页面/菜单) ``` **数据表**: - `admin_users`: 用户表 (包含 role_id 外键) - `admin_roles`: 角色表 (如: 超级管理员、普通管理员) - `admin_pages`: 页面/菜单表 (如: /users, /roles) - `admin_role_page_permissions`: 角色-页面关联表 **核心模型**: ```go type Role struct { BaseModel Name string `json:"name" gorm:"uniqueIndex;size:100;not null"` Level int `json:"level" gorm:"default:1"` // 角色级别 (1-5) Description string `json:"description" gorm:"size:255"` IsDefault bool `json:"is_default" gorm:"default:false"` // 是否默认角色 } type Page struct { BaseModel Name string `json:"name" gorm:"size:100;not null"` Path string `json:"path" gorm:"uniqueIndex;size:255;not null"` // 页面路径 Icon string `json:"icon" gorm:"size:100"` SortOrder int `json:"sort_order" gorm:"default:0"` IsActive bool `json:"is_active" gorm:"default:true"` } type RolePagePermission struct { BaseModel RoleID uint `json:"role_id" gorm:"not null;index:idx_role_page"` PageID uint `json:"page_id" gorm:"not null;index:idx_role_page"` } ``` **功能**: - 页面权限检查 - 角色权限管理 - 用户可访问页面查询 - 权限继承 (通过角色级别) **API 路由**: `/api/admin/rbac/*` **权限检查中间件**: ```go // 检查用户是否有访问某个页面的权限 rbacMiddleware.RequirePagePermission("/users") ``` **使用示例**: ```go // routes.go users := admin.Group("/users") users.Use(rbacMiddleware.RequirePagePermission("/users")) { users.GET("", userHandler.List) users.POST("", userHandler.Create) } ``` --- ### 5. 用户等级配置系统 **位置**: `internal/models/user_level_config.go`, `internal/services/user_level_config_service.go` **配置内容**: ```go type UserLevelConfig struct { BaseModel LevelName string `json:"level_name" gorm:"size:100;not null"` LevelCode string `json:"level_code" gorm:"uniqueIndex;size:50;not null"` // 等级代码 ProjectLimit int `json:"project_limit" gorm:"default:0"` // 项目数限制 CoderVMLimit int `json:"coder_vm_limit" gorm:"default:0"` // Coder VM上限 BrowserVMLimit int `json:"browser_vm_limit" gorm:"default:0"` // Browser VM上限 ProcessLimit int `json:"process_limit" gorm:"default:0"` // 进程上限 Description string `json:"description" gorm:"size:255"` SortOrder int `json:"sort_order" gorm:"default:0"` Status int `json:"status" gorm:"default:1"` // 1:启用 0:禁用 } ``` **默认等级** (storage/database.go:181-206): | 等级代码 | 等级名称 | 项目限制 | Coder VM | Browser VM | 进程上限 | |---------|---------|---------|----------|------------|---------| | normal | 普通用户 | 2 | 0 | 0 | 0 | | vip | VIP用户 | 10 | 0 | 0 | 0 | | internal| 内部用户 | 0 (无限) | 0 | 0 | 0 | **功能**: - 等级配置 CRUD - 等级启用/禁用 - 排序管理 - 与 GoalfyMax 用户关联 **API 路由**: `/api/admin/user-level-configs/*` --- ### 6. 邀请码系统 **位置**: `internal/models/invite_code.go`, `internal/services/invite_code_service.go` **核心模型**: ```go type InviteCode struct { BaseModel Code string `json:"code" gorm:"uniqueIndex;size:50;not null"` // 邀请码 IsUsed bool `json:"is_used" gorm:"default:false"` // 是否已使用 ClientID *string `json:"client_id" gorm:"size:255"` // 客户端ID Email string `json:"email" gorm:"size:255"` // 关联邮箱 ExpiresAt *time.Time `json:"expires_at"` // 过期时间 UserLevelID uint `json:"user_level_id"` // 用户等级ID UserLevel *UserLevelConfig `json:"user_level,omitempty" gorm:"foreignKey:UserLevelID"` } ``` **功能**: - 邀请码生成 (支持批量生成) - 过期时间管理 - 使用状态追踪 - 邮箱关联 - 用户等级绑定 - 邀请码验证 (检查是否过期、是否已使用) **申请审批流程**: ``` 1. 用户在官网提交申请 (公开接口) ↓ 2. 管理员查看待审批列表 ↓ 3. 审批通过 ├─ 自动生成邀请码 ├─ 关联申请邮箱 └─ 发送邮件通知 (中英文) 或 3. 审批拒绝 └─ 发送拒绝邮件 (中英文) ``` **API 路由**: - 管理端: `/api/admin/invite-codes/*` - 公开接口: `/api/public/invite-code/apply` **统计功能**: - 总邀请码数 - 已使用数量 - 未使用数量 - 过期数量 --- ### 7. 邀请码申请系统 **位置**: `internal/models/invite_code_application.go`, `internal/services/invite_code_application_service.go` **核心模型**: ```go type InviteCodeApplication struct { BaseModel Email string `json:"email" gorm:"size:255;not null;index"` // 申请人邮箱 Purpose string `json:"purpose" gorm:"type:text"` // 申请目的 Language string `json:"language" gorm:"size:10;default:'zh'"` // 语言 (zh/en) Status string `json:"status" gorm:"size:20;default:'pending'"` // pending/approved/rejected ReviewedBy *uint `json:"reviewed_by"` // 审核人ID ReviewedAt *time.Time `json:"reviewed_at"` // 审核时间 RejectReason string `json:"reject_reason" gorm:"type:text"` // 拒绝原因 InviteCodeID *uint `json:"invite_code_id"` // 关联的邀请码ID } ``` **功能**: - 公开申请接口 (官网接入) - 申请列表查询 (支持状态筛选) - 待处理数量统计 - 审批通过 (自动生成邀请码并发邮件) - 审批拒绝 (发送拒绝邮件) - 批量审批 (批量通过/拒绝) **API 路由**: `/api/admin/invite-applications/*` **邮件模板**: - 通过模板: `docs/email_preview_approval.html` (中文), `docs/email_preview_en.html` (英文) - 拒绝模板: `docs/email_preview_rejection.html` (中文) **审批逻辑** (invite_code_application_service.go): **通过审批**: ```go 1. 验证申请状态 (必须是 pending) 2. 生成邀请码 - 关联申请邮箱 - 设置默认用户等级 (normal) - 设置过期时间 (30天) 3. 更新申请状态为 approved 4. 记录审核人和审核时间 5. 发送邮件通知 (根据语言选择模板) 6. 在事务中完成所有操作 ``` **拒绝审批**: ```go 1. 验证申请状态 (必须是 pending) 2. 更新申请状态为 rejected 3. 记录拒绝原因、审核人和审核时间 4. 发送拒绝邮件 (根据语言选择模板) 5. 在事务中完成所有操作 ``` --- ### 8. 财务管理系统 **位置**: `internal/api/handlers/finance_handler.go`, `internal/services/finance_service.go` **功能模块**: #### 8.1 使用记录查询 - **Sandbox 使用记录**: 查询用户的 Sandbox 使用历史 - **Token 使用统计**: 模型 Token 消耗统计 - **MCP 使用记录**: MCP 服务使用记录 - **交易日志**: 所有交易流水 - **支付记录**: 支付订单和退款记录 #### 8.2 MCP 账户管理 - **余额管理**: 各 MCP Provider 的账户余额 - **充值记录**: MCP 账户充值历史 - **余额调整**: 增加或减少账户余额 #### 8.3 模型账户管理 - **余额查询**: 各模型供应商的账户余额 - **使用统计**: Token 消耗统计 **API 路由**: `/api/finance/*` **主要接口**: - `GET /api/finance/sandbox-records` - Sandbox 记录 - `GET /api/finance/token-usages` - Token 使用 - `GET /api/finance/mcp-usages` - MCP 使用 - `GET /api/finance/transaction-logs` - 交易日志 - `GET /api/finance/payment-records` - 支付记录 - `POST /api/finance/payment-records/refund` - 退款 - `GET /api/finance/mcp-account-balances` - MCP 余额 - `POST /api/finance/mcp-account-balances` - 创建余额 - `PUT /api/finance/mcp-account-balances/:provider_id` - 调整余额 --- ### 9. 配额管理系统 **位置**: `internal/services/quota_service.go`, `internal/api/handlers/quota_handler.go` **功能**: #### 9.1 配额历史查询 - 转发到 AI Gateway - 查询用户的配额使用历史 #### 9.2 配额规则管理 - 配额规则 CRUD - 规则启用/禁用 #### 9.3 用户项目配额 - 用户项目配额查询 - 配额创建和更新 **API 路由**: `/api/quotas/*` **主要接口**: - `POST /api/quotas/history` - 配额历史 (转发到 Gateway) - `GET /api/quotas/health` - 健康检查 - `GET /api/quotas/rules` - 规则列表 - `POST /api/quotas/rules` - 创建规则 - `PUT /api/quotas/rules/:id` - 更新规则 - `DELETE /api/quotas/rules/:id` - 删除规则 - `GET /api/quotas/user-project` - 用户项目配额 - `POST /api/quotas/user-project` - 创建配额 --- ### 10. 消息推送系统 **位置**: `internal/services/message_push_service.go`, `internal/api/handlers/message_push_handler.go` **功能**: - 向 GoalfyMax 后端发送消息推送 - 推送日志记录 - 用户搜索 **API 路由**: `/api/admin/message-push/*` --- ### 11. 用户反馈系统 **位置**: `internal/api/handlers/user_feedback_handler.go`, `internal/services/user_feedback_service.go` **功能**: - 反馈列表查询 - 标记已处理 - 反馈统计信息 **API 路由**: `/api/admin/user-feedback/*` --- ### 12. 审计日志系统 **位置**: `internal/models/audit_log.go`, `internal/services/audit_log_service.go` **核心模型**: ```go type AuditLog struct { BaseModel UserID uint `json:"user_id" gorm:"index;not null"` OperationType string `json:"operation_type" gorm:"size:50;not null"` // create/update/delete TargetTable string `json:"target_table" gorm:"size:100;not null"` // 目标表名 TargetID uint `json:"target_id" gorm:"index"` // 目标记录ID OldValue string `json:"old_value" gorm:"type:text"` // 修改前的值(JSON) NewValue string `json:"new_value" gorm:"type:text"` // 修改后的值(JSON) IP string `json:"ip" gorm:"size:50"` UserAgent string `json:"user_agent" gorm:"size:255"` } ``` **功能**: - 记录所有数据修改操作 - 记录 API 调用日志 - 变更前后值对比 - IP 和 UserAgent 追踪 **自动日志记录**: - 通过中间件自动记录 POST/PUT/DELETE 请求 - 中间件位置: `internal/api/middlewares/api_log_middleware.go` --- ### 13. 定时任务系统 **位置**: `internal/jobs/` #### 13.1 MCP Usage Balance Job **文件**: `internal/jobs/mcp_usage_job.go` **功能**: - 定时同步 MCP 使用余额 - 从外部服务获取余额数据 - 更新到本地数据库 **配置**: ```yaml jobs: mcp_usage_balance: enabled: true run_on_startup: true delay_minutes: 5 ``` **执行频率**: 每小时 #### 13.2 Model Token Balance Job **文件**: `internal/jobs/model_token_job.go` **功能**: - 定时同步模型 Token 余额 - 统计 Token 使用情况 - 更新余额数据 **配置**: ```yaml jobs: model_token_balance: enabled: true run_on_startup: true delay_minutes: 5 ``` **执行频率**: 每小时 --- ## 数据库设计 ### 数据库架构 系统使用**三个数据库**: 1. **MySQL**: 主数据库,存储所有业务数据 2. **PostgreSQL**: MCP 配置和相关数据 3. **Redis**: 缓存、会话、余额等临时数据 ### 核心数据表 #### 1. BaseModel (所有表的基础) ```go type BaseModel struct { ID uint `json:"id" gorm:"primarykey"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `json:"deleted_at,omitempty" gorm:"index"` } ``` **特点**: - 自动管理 ID、创建时间、更新时间 - 支持软删除 (DeletedAt) --- #### 2. admin_users (管理员用户表) ```go type User struct { BaseModel Username string `json:"username" gorm:"uniqueIndex;size:100;not null"` Email string `json:"email" gorm:"uniqueIndex;size:255"` Nickname string `json:"nickname" gorm:"size:100"` Avatar string `json:"avatar" gorm:"size:500"` Status int `json:"status" gorm:"default:1"` // 1:正常 0:禁用 SSOProvider string `json:"sso_provider" gorm:"size:50"` RoleID *uint `json:"role_id" gorm:"index"` Role *Role `json:"role,omitempty" gorm:"foreignKey:RoleID"` LastLoginAt *time.Time `json:"last_login_at"` LoginCount int `json:"login_count" gorm:"default:0"` } ``` **索引**: - `uniqueIndex`: username, email - `index`: role_id, deleted_at **说明**: - 管理后台的用户表 - 通过 SSO 登录自动创建 - 关联角色表实现权限控制 --- #### 3. admin_goalfymax_users (GoalfyMax 用户表) ```go type GoalfyMaxUser struct { BaseModel UserID uint `json:"user_id" gorm:"uniqueIndex;not null"` Username string `json:"username" gorm:"size:100"` Email string `json:"email" gorm:"size:255"` Nickname string `json:"nickname" gorm:"size:100"` Avatar string `json:"avatar" gorm:"size:500"` UserLevelCode string `json:"user_level_code" gorm:"size:50"` IsBanned bool `json:"is_banned" gorm:"default:false"` BanReason string `json:"ban_reason" gorm:"type:text"` BannedAt *time.Time `json:"banned_at"` BannedBy *uint `json:"banned_by"` GoalfyHubPermission int `json:"goalfy_hub_permission" gorm:"default:0"` Version int `json:"version" gorm:"default:1"` Balance float64 `json:"balance" gorm:"-"` // 虚拟字段,从Redis查询 } ``` **索引**: - `uniqueIndex`: user_id - `index`: deleted_at **说明**: - GoalfyMax 产品的用户表 - user_id 关联 SSO 系统的用户 ID - Balance 字段不存数据库,从 Redis 实时查询 **余额存储** (Redis): ``` key: goalfymax:user:balance:{user_id} value: float64 (余额) ``` --- #### 4. admin_user_level_configs (用户等级配置表) ```go type UserLevelConfig struct { BaseModel LevelName string `json:"level_name" gorm:"size:100;not null"` LevelCode string `json:"level_code" gorm:"uniqueIndex;size:50;not null"` ProjectLimit int `json:"project_limit" gorm:"default:0"` CoderVMLimit int `json:"coder_vm_limit" gorm:"default:0"` BrowserVMLimit int `json:"browser_vm_limit" gorm:"default:0"` ProcessLimit int `json:"process_limit" gorm:"default:0"` Description string `json:"description" gorm:"size:255"` SortOrder int `json:"sort_order" gorm:"default:0"` Status int `json:"status" gorm:"default:1"` // 1:启用 0:禁用 } ``` **索引**: - `uniqueIndex`: level_code - `index`: deleted_at **默认数据**: | level_code | level_name | project_limit | |------------|-----------|---------------| | normal | 普通用户 | 2 | | vip | VIP用户 | 10 | | internal | 内部用户 | 0 (无限) | --- #### 5. admin_invite_codes (邀请码表) ```go type InviteCode struct { BaseModel Code string `json:"code" gorm:"uniqueIndex;size:50;not null"` IsUsed bool `json:"is_used" gorm:"default:false"` ClientID *string `json:"client_id" gorm:"size:255"` Email string `json:"email" gorm:"size:255"` ExpiresAt *time.Time `json:"expires_at"` UserLevelID uint `json:"user_level_id"` UserLevel *UserLevelConfig `json:"user_level,omitempty" gorm:"foreignKey:UserLevelID"` } ``` **索引**: - `uniqueIndex`: code - `index`: deleted_at --- #### 6. admin_invite_code_applications (邀请码申请表) **SQL 迁移**: `migrations/20250131_add_invite_code_applications_table.sql` ```sql CREATE TABLE admin_invite_code_applications ( id BIGINT AUTO_INCREMENT PRIMARY KEY, email VARCHAR(255) NOT NULL, purpose TEXT, language VARCHAR(10) DEFAULT 'zh', status VARCHAR(20) DEFAULT 'pending', -- pending/approved/rejected reviewed_by BIGINT, reviewed_at TIMESTAMP NULL, reject_reason TEXT, invite_code_id BIGINT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted_at TIMESTAMP NULL, INDEX idx_email (email), INDEX idx_status (status), INDEX idx_deleted_at (deleted_at) ); ``` --- #### 7. admin_roles (角色表) ```go type Role struct { BaseModel Name string `json:"name" gorm:"uniqueIndex;size:100;not null"` Level int `json:"level" gorm:"default:1"` Description string `json:"description" gorm:"size:255"` IsDefault bool `json:"is_default" gorm:"default:false"` } ``` **索引**: - `uniqueIndex`: name - `index`: deleted_at --- #### 8. admin_pages (页面/菜单表) ```go type Page struct { BaseModel Name string `json:"name" gorm:"size:100;not null"` Path string `json:"path" gorm:"uniqueIndex;size:255;not null"` Icon string `json:"icon" gorm:"size:100"` SortOrder int `json:"sort_order" gorm:"default:0"` IsActive bool `json:"is_active" gorm:"default:true"` } ``` **索引**: - `uniqueIndex`: path - `index`: deleted_at --- #### 9. admin_role_page_permissions (角色-页面权限关联表) ```go type RolePagePermission struct { BaseModel RoleID uint `json:"role_id" gorm:"not null;index:idx_role_page"` PageID uint `json:"page_id" gorm:"not null;index:idx_role_page"` } ``` **索引**: - `index`: idx_role_page (role_id, page_id 组合索引) - `index`: deleted_at --- #### 10. admin_audit_logs (审计日志表) ```go type AuditLog struct { BaseModel UserID uint `json:"user_id" gorm:"index;not null"` OperationType string `json:"operation_type" gorm:"size:50;not null"` TargetTable string `json:"target_table" gorm:"size:100;not null"` TargetID uint `json:"target_id" gorm:"index"` OldValue string `json:"old_value" gorm:"type:text"` NewValue string `json:"new_value" gorm:"type:text"` IP string `json:"ip" gorm:"size:50"` UserAgent string `json:"user_agent" gorm:"size:255"` } ``` **索引**: - `index`: user_id, target_id, deleted_at --- #### 11. admin_balance_operation_logs (余额操作日志表) ```go type BalanceOperationLog struct { BaseModel UserID uint `json:"user_id" gorm:"index;not null"` OperationType string `json:"operation_type" gorm:"size:50;not null"` // add/deduct Amount float64 `json:"amount" gorm:"type:decimal(20,2);not null"` BalanceBefore float64 `json:"balance_before" gorm:"type:decimal(20,2)"` BalanceAfter float64 `json:"balance_after" gorm:"type:decimal(20,2)"` OperatorID uint `json:"operator_id" gorm:"index"` Remark string `json:"remark" gorm:"type:text"` } ``` **索引**: - `index`: user_id, operator_id, deleted_at --- ### 数据库初始化 **位置**: `internal/storage/database.go` #### 自动迁移 (database.go:119-165) ```go func (d *Database) autoMigrate() error { return d.DB.AutoMigrate( &models.User{}, &models.Role{}, &models.Page{}, &models.RolePagePermission{}, &models.UserLevelConfig{}, &models.InviteCode{}, &models.AuditLog{}, &models.BalanceOperationLog{}, &models.GoalfyMaxUser{}, // ... 其他模型 ) } ``` #### 初始化默认数据 (database.go:167-260) **默认角色**: - 超级管理员 (level: 5) - 普通管理员 (level: 3) - 观察者 (level: 1) **默认用户等级**: - normal (普通用户): 2个项目 - vip (VIP用户): 10个项目 - internal (内部用户): 无限制 **默认页面**: - /dashboard (仪表盘) - /users (用户管理) - /roles (角色管理) - /pages (页面管理) - /invite-codes (邀请码管理) - /user-level-configs (用户等级配置) --- ### SQL 迁移脚本 **位置**: `migrations/` 1. **20250129_add_client_id_to_invite_codes.sql** - 添加 client_id 字段到 invite_codes 表 2. **20250131_add_invite_code_applications_table.sql** - 创建邀请码申请表 3. **20250204_add_language_to_invite_code_applications.sql** - 添加 language 字段到申请表 **执行迁移**: ```bash ./scripts/migrate.sh ``` --- ## API 接口说明 ### 接口规范 #### 请求格式 **认证**: ``` Authorization: Bearer {access_token} ``` **请求体** (POST/PUT): ```json { "field1": "value1", "field2": "value2" } ``` #### 响应格式 **成功响应**: ```json { "code": 200, "message": "success", "data": { // 响应数据 } } ``` **错误响应**: ```json { "code": 400, "message": "错误信息" } ``` **分页响应**: ```json { "code": 200, "message": "success", "data": { "list": [...], "total": 100, "page": 1, "page_size": 20 } } ``` --- ### API 路由列表 #### 公开接口 (无需认证) ``` POST /api/public/invite-code/apply # 提交邀请码申请 ``` --- #### SSO 认证接口 ``` POST /api/sso/login # SSO 登录 POST /api/sso/callback # SSO 回调 POST /api/sso/refresh # 刷新令牌 POST /api/sso/logout # 登出 GET /api/sso/userinfo # 获取用户信息 GET /api/sso/online-users # 在线用户列表 GET /api/sso/online-count # 在线用户数量 POST /api/sso/batch-logout # 批量登出 ``` --- #### 用户管理 (需认证) ``` GET /api/admin/users # 用户列表 POST /api/admin/users # 创建用户 GET /api/admin/users/:id # 用户详情 PUT /api/admin/users/:id # 更新用户 DELETE /api/admin/users/:id # 删除用户 PUT /api/admin/users/:id/status # 更新状态 PUT /api/admin/users/:id/roles # 更新角色 GET /api/admin/users/:id/roles # 获取角色 GET /api/admin/users/:id/permissions # 获取权限 GET /api/admin/users/check-role/:user_id # 检查系统角色 POST /api/admin/users/change-system-role # 变更系统角色 ``` --- #### GoalfyMax 用户管理 (需认证) ``` GET /api/admin/goalfymax-users # 用户列表 POST /api/admin/goalfymax-users # 新增用户 GET /api/admin/goalfymax-users/:id # 用户详情 PUT /api/admin/goalfymax-users/:id # 编辑用户 DELETE /api/admin/goalfymax-users/:id # 删除用户 POST /api/admin/goalfymax-users/:id/ban # 封禁用户 POST /api/admin/goalfymax-users/:id/unban # 解封用户 POST /api/admin/goalfymax-users/:id/add-balance # 增加余额 POST /api/admin/goalfymax-users/:id/deduct-balance # 减少余额 ``` --- #### 角色管理 (需认证) ``` GET /api/admin/roles # 角色列表 POST /api/admin/roles # 创建角色 GET /api/admin/roles/:id # 角色详情 PUT /api/admin/roles/:id # 更新角色 DELETE /api/admin/roles/:id # 删除角色 PUT /api/admin/roles/:id/status # 更新状态 PUT /api/admin/roles/:id/permissions # 更新权限 GET /api/admin/roles/:id/permissions # 获取权限 ``` --- #### 页面管理 (需认证) ``` GET /api/admin/pages # 页面列表 POST /api/admin/pages # 创建页面 GET /api/admin/pages/:id # 页面详情 PUT /api/admin/pages/:id # 更新页面 DELETE /api/admin/pages/:id # 删除页面 ``` --- #### RBAC 权限管理 (需认证) ``` POST /api/admin/rbac/role-page-permissions # 分配角色页面权限 DELETE /api/admin/rbac/roles/:id/page-permissions # 移除角色页面权限 GET /api/admin/rbac/roles/:id/page-permissions # 获取角色页面权限 GET /api/admin/rbac/users/:id/permissions # 获取用户权限 GET /api/admin/rbac/check-page-permission # 检查页面权限 GET /api/admin/rbac/users/:id/accessible-pages # 获取用户可访问页面 ``` --- #### 用户等级配置 (需认证) ``` GET /api/admin/user-level-configs # 等级列表 GET /api/admin/user-level-configs/all # 获取所有(不分页) POST /api/admin/user-level-configs # 创建等级 GET /api/admin/user-level-configs/:id # 等级详情 PUT /api/admin/user-level-configs/:id # 更新等级 DELETE /api/admin/user-level-configs/:id # 删除等级 PUT /api/admin/user-level-configs/:id/status # 更新状态 ``` --- #### 邀请码管理 (需认证) ``` GET /api/admin/invite-codes # 邀请码列表 POST /api/admin/invite-codes # 创建邀请码 GET /api/admin/invite-codes/client-options # 客户端选项 GET /api/admin/invite-codes/statistics # 统计信息 GET /api/admin/invite-codes/:id # 邀请码详情 PUT /api/admin/invite-codes/:id # 更新邀请码 DELETE /api/admin/invite-codes/:id # 删除邀请码 POST /api/admin/invite-codes/mark-used # 标记为已使用 POST /api/admin/invite-codes/validate # 验证有效性 ``` --- #### 邀请码申请管理 (需认证) ``` GET /api/admin/invite-applications # 申请列表 GET /api/admin/invite-applications/statistics # 统计信息 GET /api/admin/invite-applications/pending-count # 待处理数量 POST /api/admin/invite-applications/approve # 审批通过 POST /api/admin/invite-applications/reject # 审批拒绝 POST /api/admin/invite-applications/batch-approve # 批量通过 POST /api/admin/invite-applications/batch-reject # 批量拒绝 ``` --- #### 财务管理 (需认证) ``` GET /api/finance/sandbox-records # Sandbox 记录 GET /api/finance/token-usages # Token 使用 GET /api/finance/mcp-usages # MCP 使用 GET /api/finance/transaction-logs # 交易日志 GET /api/finance/payment-records # 支付记录 POST /api/finance/payment-records/refund # 退款 GET /api/finance/mcp-account-recharge-records # MCP 充值记录 POST /api/finance/mcp-account-recharge-records # 创建充值 GET /api/finance/mcp-account-balances # MCP 余额 POST /api/finance/mcp-account-balances # 创建余额 PUT /api/finance/mcp-account-balances/:provider_id # 调整余额 GET /api/finance/model-account-balances # 模型余额 ``` --- #### 配额管理 (需认证) ``` POST /api/quotas/history # 配额历史 GET /api/quotas/health # 健康检查 GET /api/quotas/rules # 规则列表 POST /api/quotas/rules # 创建规则 PUT /api/quotas/rules/:id # 更新规则 DELETE /api/quotas/rules/:id # 删除规则 GET /api/quotas/user-project # 用户项目配额 POST /api/quotas/user-project # 创建配额 ``` --- #### 消息推送 (需认证) ``` POST /api/admin/message-push/send # 发送消息 GET /api/admin/message-push/logs # 推送日志 POST /api/admin/message-push/search # 用户搜索 ``` --- #### 用户反馈 (需认证) ``` GET /api/admin/user-feedback # 反馈列表 PUT /api/admin/user-feedback/:id/process # 标记已处理 GET /api/admin/user-feedback/statistics # 统计信息 ``` --- #### 审计日志 (需认证) ``` GET /api/admin/audit-logs # 审计日志列表 ``` --- ### CORS 配置 **允许的源** (routes.go:37-44): - http://localhost:5173 - http://localhost:5174 - http://localhost:3000 - http://localhost:3003 - http://localhost:3004 **允许的方法**: - GET, POST, PUT, DELETE, OPTIONS, PATCH **允许的头**: - Origin, Content-Type, Accept, Authorization, X-Requested-With, Cookie --- ## 认证与权限 ### SSO 单点登录 #### OAuth2/OIDC 流程 ``` ┌─────────┐ ┌──────────┐ │ 前端 │ │ SSO 服务器│ └────┬────┘ └─────┬────┘ │ │ │ 1. 请求登录 │ ├──────────────────────────────────────────► │ │ │ │ 2. 返回授权 URL (带 state, code_challenge) │ │◄──────────────────────────────────────────┤ │ │ │ 3. 跳转到 SSO 登录页面 │ ├────────────────────────────────────────────► │ │ │ 4. 用户登录授权 │ │ │ │ 5. 回调 (带 code 和 state) │ │◄────────────────────────────────────────────┤ │ │ │ 6. 交换 token (使用 code_verifier) │ ├──────────────────────────────────────────► │ │ │ │ 7. 返回 access_token, refresh_token, id_token│ │◄──────────────────────────────────────────┤ │ │ │ 8. 获取用户信息 │ ├──────────────────────────────────────────► │ │ │ │ 9. 返回用户信息 (email, name, etc.) │ │◄──────────────────────────────────────────┤ │ │ ``` #### PKCE 安全增强 **什么是 PKCE?** PKCE (Proof Key for Code Exchange) 是 OAuth2 的安全扩展,用于防止授权码拦截攻击。 **流程**: 1. **生成 code_verifier** (随机字符串) ```go codeVerifier := generateRandomString(128) ``` 2. **计算 code_challenge** ```go hash := sha256.Sum256([]byte(codeVerifier)) codeChallenge := base64.RawURLEncoding.EncodeToString(hash[:]) ``` 3. **授权请求携带 code_challenge** ``` GET /authorize? client_id=xxx &redirect_uri=xxx &code_challenge=xxx &code_challenge_method=S256 ``` 4. **Token 交换时携带 code_verifier** ``` POST /token { "code": "xxx", "code_verifier": "xxx" } ``` 5. **服务器验证** ``` hash(code_verifier) == code_challenge ``` #### 自动用户创建 **位置**: `pkg/middleware/auth.go:230-290` **逻辑**: ```go func findOrCreateUser(ssoUserID, username, email string, db *gorm.DB) (*models.User, error) { var user models.User // 1. 查找现有用户 err := db.Where("username = ?", username).First(&user).Error if err == gorm.ErrRecordNotFound { // 2. 用户不存在,创建新用户 user = models.User{ Username: username, Email: email, Nickname: username, Status: 1, // 默认启用 SSOProvider: "goalfy-sso", LoginCount: 1, LastLoginAt: &now, } if err := db.Create(&user).Error; err != nil { return nil, err } } else { // 3. 用户已存在,更新登录信息 updates := map[string]interface{}{ "login_count": user.LoginCount + 1, "last_login_at": &now, } // 同步 SSO 的姓名和邮箱 if user.Email != email { updates["email"] = email } db.Model(&user).Updates(updates) } return &user, nil } ``` **特性**: - 首次登录自动创建用户 - 每次登录更新登录时间和次数 - 自动同步 SSO 的邮箱信息 - 默认启用状态 --- ### RBAC 权限系统 #### 权限模型 ``` User (用户) ↓ (role_id) Role (角色) ↓ (role_page_permissions 中间表) Page (页面/菜单) ``` #### 权限检查流程 **中间件**: `pkg/middleware/rbac.go:31-70` ```go func RequirePagePermission(pagePath string) gin.HandlerFunc { return func(c *gin.Context) { // 1. 从 Context 获取 user_id userID, exists := c.Get("user_id") if !exists { c.JSON(401, gin.H{"error": "Unauthorized"}) c.Abort() return } // 2. 调用 Storage 检查权限 hasPermission, err := storage.CheckUserRolePagePermission(userID, pagePath) if err != nil { c.JSON(500, gin.H{"error": "Failed to check permission"}) c.Abort() return } // 3. 验证权限 if !hasPermission { c.JSON(403, gin.H{"error": "Forbidden: No permission to access this page"}) c.Abort() return } // 4. 继续处理请求 c.Next() } } ``` #### 权限查询 SQL **位置**: `internal/storage/rbac_storage.go` ```sql SELECT COUNT(*) > 0 FROM admin_users u JOIN admin_roles r ON u.role_id = r.id JOIN admin_role_page_permissions rpp ON r.id = rpp.role_id JOIN admin_pages p ON rpp.page_id = p.id WHERE u.id = ? AND p.path = ? AND u.deleted_at IS NULL AND r.deleted_at IS NULL AND rpp.deleted_at IS NULL AND p.deleted_at IS NULL ``` #### 使用示例 **在路由中使用**: ```go // routes.go users := admin.Group("/users") users.Use(rbacMiddleware.RequirePagePermission("/users")) { users.GET("", userHandler.List) // 需要 /users 权限 users.POST("", userHandler.Create) // 需要 /users 权限 users.PUT("/:id", userHandler.Update) // 需要 /users 权限 } ``` **灵活的权限粒度**: ```go // 整个路由组共享权限 usersGroup.Use(rbacMiddleware.RequirePagePermission("/users")) // 或者单个路由单独设置权限 users.GET("", rbacMiddleware.RequirePagePermission("/users/view"), userHandler.List, ) ``` --- ### 会话管理 #### MemorySessionManager **位置**: `pkg/middleware/auth.go:24-58` **数据结构**: ```go type Session struct { UserID uint Username string AccessToken string ExpiresAt time.Time } type MemorySessionManager struct { sessions map[string]*Session // key: access_token mu sync.RWMutex } ``` **功能**: 1. **创建会话** ```go func (m *MemorySessionManager) Create(session *Session) { m.mu.Lock() defer m.mu.Unlock() m.sessions[session.AccessToken] = session } ``` 2. **获取会话** ```go func (m *MemorySessionManager) Get(accessToken string) (*Session, bool) { m.mu.RLock() defer m.mu.RUnlock() session, exists := m.sessions[accessToken] if !exists { return nil, false } // 检查是否过期 if time.Now().After(session.ExpiresAt) { return nil, false } return session, true } ``` 3. **删除会话 (登出)** ```go func (m *MemorySessionManager) Delete(accessToken string) { m.mu.Lock() defer m.mu.Unlock() delete(m.sessions, accessToken) } ``` 4. **获取所有在线用户** ```go func (m *MemorySessionManager) GetAllSessions() []*Session { m.mu.RLock() defer m.mu.RUnlock() var sessions []*Session now := time.Now() for _, session := range m.sessions { if now.Before(session.ExpiresAt) { sessions = append(sessions, session) } } return sessions } ``` **特点**: - 内存存储,重启丢失 - 线程安全 (使用 RWMutex) - 自动过期检查 - 支持批量登出 --- ## 中间件系统 ### 中间件链 ``` Request ↓ CORS Middleware ↓ Logging Middleware ↓ Auth Middleware (RequireAuth) ↓ RBAC Middleware (RequirePagePermission) ↓ API Log Middleware ↓ Handler ↓ Response ``` --- ### 1. 认证中间件 **位置**: `pkg/middleware/auth.go` #### RequireAuth() (第84-159行) **功能**: 验证用户身份 **流程**: ```go func (m *AuthMiddleware) RequireAuth() gin.HandlerFunc { return func(c *gin.Context) { // 1. 从 Header 或 Query 获取 Token token := c.GetHeader("Authorization") if token == "" { token = c.Query("token") } if token == "" { c.JSON(401, gin.H{"error": "Missing token"}) c.Abort() return } // 移除 "Bearer " 前缀 token = strings.TrimPrefix(token, "Bearer ") // 2. 调用 SSO 服务验证 Token userInfo, err := m.ssoService.ValidateToken(token) if err != nil { c.JSON(401, gin.H{"error": "Invalid token"}) c.Abort() return } // 3. 解析用户 ID userID := parseUserID(userInfo) if userID == 0 { c.JSON(401, gin.H{"error": "Invalid user ID"}) c.Abort() return } // 4. 查找或创建本地用户 user, err := findOrCreateUser(userID, userInfo.Username, userInfo.Email, m.db) if err != nil { c.JSON(500, gin.H{"error": "Failed to get user"}) c.Abort() return } // 5. 将用户信息存入 Context c.Set("user_id", user.ID) c.Set("username", user.Username) c.Set("user", user) c.Next() } } ``` **Token 获取方式**: - **Header**: `Authorization: Bearer {token}` - **Query**: `?token={token}` --- ### 2. RBAC 权限中间件 **位置**: `pkg/middleware/rbac.go` #### RequirePagePermission(pagePath) (第31-70行) **功能**: 检查用户是否有访问某个页面的权限 ```go func RequirePagePermission(pagePath string) gin.HandlerFunc { return func(c *gin.Context) { // 1. 从 Context 获取 user_id (由 AuthMiddleware 设置) userID, exists := c.Get("user_id") if !exists { c.JSON(401, gin.H{"error": "Unauthorized"}) c.Abort() return } // 2. 调用 Storage 检查权限 hasPermission, err := storage.CheckUserRolePagePermission(userID.(uint), pagePath) if err != nil { c.JSON(500, gin.H{"error": "Failed to check permission"}) c.Abort() return } // 3. 验证权限 if !hasPermission { c.JSON(403, gin.H{"error": "Forbidden: No permission"}) c.Abort() return } c.Next() } } ``` #### RequireRole(roleName) (第73-126行) **功能**: 检查用户是否具有特定角色 ```go func RequireRole(roleName string) gin.HandlerFunc { return func(c *gin.Context) { userID, _ := c.Get("user_id") // 查询用户角色 var user models.User db.Preload("Role").First(&user, userID) // 检查角色 if user.Role == nil || user.Role.Name != roleName { c.JSON(403, gin.H{"error": "Insufficient role"}) c.Abort() return } c.Next() } } ``` --- ### 3. 日志中间件 #### RequestLogMiddleware **位置**: `internal/api/middlewares/logging.go` **功能**: 记录所有 HTTP 请求 ```go func RequestLogMiddleware() gin.HandlerFunc { return func(c *gin.Context) { startTime := time.Now() // 处理请求 c.Next() // 计算耗时 duration := time.Since(startTime) // 记录日志 logger.Info("HTTP Request", zap.String("method", c.Request.Method), zap.String("path", c.Request.URL.Path), zap.Int("status", c.Writer.Status()), zap.Duration("duration", duration), zap.String("client_ip", c.ClientIP()), ) } } ``` **日志输出示例**: ``` 2025-01-15T10:30:45.123Z INFO HTTP Request method: GET path: /api/admin/users status: 200 duration: 45ms client_ip: 192.168.1.100 ``` --- #### APILogMiddleware **位置**: `internal/api/middlewares/api_log_middleware.go` **功能**: 记录数据修改接口的详细信息 ```go func APILogMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // 只记录 POST/PUT/DELETE 请求 if !shouldLogRequest(c.Request.Method) { c.Next() return } // 读取请求体 bodyBytes, _ := ioutil.ReadAll(c.Request.Body) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // 创建响应写入器 blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer} c.Writer = blw // 处理请求 c.Next() // 记录日志 logger.Info("API Call", zap.String("method", c.Request.Method), zap.String("path", c.Request.URL.Path), zap.String("request_body", string(bodyBytes)), zap.String("response_body", blw.body.String()), zap.Int("status", c.Writer.Status()), ) } } ``` **日志包含**: - 请求方法和路径 - 请求体 (JSON) - 响应体 (JSON) - 状态码 --- ### 4. CORS 中间件 **位置**: `internal/api/routes/routes.go:37-44` ```go config := cors.Config{ AllowOrigins: []string{ "http://localhost:5173", "http://localhost:5174", "http://localhost:3000", "http://localhost:3003", "http://localhost:3004", }, AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"}, AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization", "X-Requested-With", "Cookie"}, AllowCredentials: true, } router.Use(cors.New(config)) ``` --- ## 服务层设计 ### 服务层职责 Service Layer 负责: - **业务逻辑封装**: 所有业务规则都在 Service 中实现 - **事务管理**: 跨表操作的事务控制 - **外部服务调用**: 调用 SSO、Gateway、Email 等外部服务 - **数据转换**: 将 Storage 层的数据转换为业务对象 - **错误处理**: 统一的错误处理和日志记录 --- ### 核心服务 #### 1. SSO 服务 **位置**: `internal/services/sso_service.go` **主要方法**: ```go type SSOService struct { config *config.SSOConfig db *gorm.DB } // Login 生成 SSO 授权 URL func (s *SSOService) Login() (string, error) // HandleCallback 处理 SSO 回调,交换 token func (s *SSOService) HandleCallback(code, state string) (*TokenResponse, error) // RefreshToken 刷新访问令牌 func (s *SSOService) RefreshToken(refreshToken string) (*TokenResponse, error) // ValidateToken 验证 token 有效性 func (s *SSOService) ValidateToken(accessToken string) (*UserInfo, error) // Logout 用户登出 func (s *SSOService) Logout(accessToken string) error // GetOnlineUsers 获取在线用户列表 func (s *SSOService) GetOnlineUsers() []*Session ``` --- #### 2. 用户服务 **位置**: `internal/services/user_service.go` **主要方法**: ```go type UserService struct { storage *storage.UserStorage } // List 用户列表(分页+搜索) func (s *UserService) List(page, pageSize int, search string) ([]models.User, int64, error) // Create 创建用户 func (s *UserService) Create(user *models.User) error // Update 更新用户 func (s *UserService) Update(id uint, user *models.User) error // Delete 删除用户(软删除) func (s *UserService) Delete(id uint) error // UpdateStatus 更新状态 func (s *UserService) UpdateStatus(id uint, status int) error // UpdateRoles 更新用户角色 func (s *UserService) UpdateRoles(userID uint, roleIDs []uint) error // GetPermissions 获取用户权限 func (s *UserService) GetPermissions(userID uint) ([]string, error) // ChangeUserSystemRole 变更用户系统角色 func (s *UserService) ChangeUserSystemRole(userID uint, newRoleID uint) error ``` --- #### 3. GoalfyMax 用户服务 **位置**: `internal/services/goalfymax_user_service.go` **主要方法**: ```go type GoalfyMaxUserService struct { storage *storage.GoalfyMaxUserStorage balanceLogStorage *storage.BalanceOperationLogStorage redis *redis.Client } // Create 创建用户 func (s *GoalfyMaxUserService) Create(user *models.GoalfyMaxUser) error // Update 更新用户 func (s *GoalfyMaxUserService) Update(id uint, user *models.GoalfyMaxUser) error // Delete 删除用户 func (s *GoalfyMaxUserService) Delete(id uint) error // Ban 封禁用户 func (s *GoalfyMaxUserService) Ban(id uint, reason string, bannedBy uint) error // Unban 解封用户 func (s *GoalfyMaxUserService) Unban(id uint) error // AddBalance 增加余额 func (s *GoalfyMaxUserService) AddBalance(id uint, amount float64, operatorID uint, remark string) error // DeductBalance 扣减余额 func (s *GoalfyMaxUserService) DeductBalance(id uint, amount float64, operatorID uint, remark string) error ``` **余额操作逻辑**: ```go func (s *GoalfyMaxUserService) AddBalance(id, amount, operatorID, remark) error { // 1. 开启事务 tx := s.storage.BeginTx() defer tx.Rollback() // 2. 查询用户 user, err := s.storage.GetByID(tx, id) // 3. 从 Redis 获取当前余额 balanceKey := fmt.Sprintf("goalfymax:user:balance:%d", id) currentBalance := redis.Get(balanceKey).Float64() // 4. 计算新余额 newBalance := currentBalance + amount // 5. 更新 Redis 余额 redis.Set(balanceKey, newBalance, 0) // 6. 记录余额操作日志 log := &models.BalanceOperationLog{ UserID: id, OperationType: "add", Amount: amount, BalanceBefore: currentBalance, BalanceAfter: newBalance, OperatorID: operatorID, Remark: remark, } s.balanceLogStorage.Create(tx, log) // 7. 提交事务 tx.Commit() return nil } ``` --- #### 4. 邀请码服务 **位置**: `internal/services/invite_code_service.go` **主要方法**: ```go type InviteCodeService struct { storage *storage.InviteCodeStorage } // CreateInviteCode 创建邀请码(支持批量) func (s *InviteCodeService) CreateInviteCode(req *CreateInviteCodeRequest) ([]models.InviteCode, error) // ValidateInviteCode 验证邀请码有效性 func (s *InviteCodeService) ValidateInviteCode(code string) (bool, error) // MarkAsUsed 标记邀请码为已使用 func (s *InviteCodeService) MarkAsUsed(code string, clientID string) error // GetStatistics 获取邀请码统计 func (s *InviteCodeService) GetStatistics() (*InviteCodeStatistics, error) ``` --- #### 5. 邀请码申请服务 **位置**: `internal/services/invite_code_application_service.go` **主要方法**: ```go type InviteCodeApplicationService struct { storage *storage.InviteCodeApplicationStorage inviteCodeService *InviteCodeService emailService *EmailService } // SubmitApplication 提交申请(公开接口) func (s *InviteCodeApplicationService) SubmitApplication(req *SubmitApplicationRequest) error // ApproveApplication 审批通过 func (s *InviteCodeApplicationService) ApproveApplication(id uint, reviewerID uint) error // RejectApplication 审批拒绝 func (s *InviteCodeApplicationService) RejectApplication(id uint, reviewerID uint, reason string) error // BatchApprove 批量审批通过 func (s *InviteCodeApplicationService) BatchApprove(ids []uint, reviewerID uint) error // BatchReject 批量审批拒绝 func (s *InviteCodeApplicationService) BatchReject(ids []uint, reviewerID uint, reason string) error ``` **审批通过逻辑**: ```go func (s *InviteCodeApplicationService) ApproveApplication(id, reviewerID) error { // 1. 开启事务 tx := s.storage.BeginTx() defer tx.Rollback() // 2. 查询申请 app, err := s.storage.GetByID(tx, id) if app.Status != "pending" { return errors.New("申请状态不是待审批") } // 3. 生成邀请码 inviteCode, err := s.inviteCodeService.CreateInviteCode(tx, &CreateInviteCodeRequest{ Email: app.Email, UserLevelID: defaultLevelID, // normal ExpiresAt: time.Now().Add(30 * 24 * time.Hour), // 30天 Count: 1, }) // 4. 更新申请状态 app.Status = "approved" app.ReviewedBy = &reviewerID now := time.Now() app.ReviewedAt = &now app.InviteCodeID = &inviteCode.ID s.storage.Update(tx, app) // 5. 提交事务 tx.Commit() // 6. 发送邮件通知 s.emailService.SendApprovalEmail(app.Email, inviteCode.Code, app.Language) return nil } ``` --- #### 6. 邮件服务 **位置**: `internal/services/email_service.go` **主要方法**: ```go type EmailService struct { config *config.EmailConfig } // SendApprovalEmail 发送审批通过邮件 func (s *EmailService) SendApprovalEmail(email, code, language string) error // SendRejectionEmail 发送审批拒绝邮件 func (s *EmailService) SendRejectionEmail(email, reason, language string) error ``` **邮件模板**: **中文通过模板** (docs/email_preview_approval.html): ```html
您的邀请码是: {{code}}
有效期: 30天
立即使用 ``` **英文通过模板** (docs/email_preview_en.html): ```htmlYour invite code is: {{code}}
Valid for: 30 days
Use Now ``` **中文拒绝模板** (docs/email_preview_rejection.html): ```html拒绝原因: {{reason}}
您可以重新申请或联系我们的客服。
``` --- #### 7. 财务服务 **位置**: `internal/services/finance_service.go` **主要方法**: ```go type FinanceService struct { db *gorm.DB } // GetSandboxRecords 获取 Sandbox 使用记录 func (s *FinanceService) GetSandboxRecords(req *SandboxRecordRequest) ([]SandboxRecord, int64, error) // GetTokenUsages 获取 Token 使用统计 func (s *FinanceService) GetTokenUsages(req *TokenUsageRequest) ([]TokenUsage, int64, error) // GetMCPUsages 获取 MCP 使用记录 func (s *FinanceService) GetMCPUsages(req *MCPUsageRequest) ([]MCPUsage, int64, error) // GetMCPAccountBalances 获取 MCP 账户余额 func (s *FinanceService) GetMCPAccountBalances() ([]MCPAccountBalance, error) // AdjustMCPBalance 调整 MCP 账户余额 func (s *FinanceService) AdjustMCPBalance(providerID uint, amount float64, operatorID uint, remark string) error // RefundPayment 退款 func (s *FinanceService) RefundPayment(paymentID uint, amount float64, reason string) error ``` --- #### 8. 配额服务 **位置**: `internal/services/quota_service.go` **主要方法**: ```go type QuotaService struct { gatewayClient *GatewayClient storage *storage.QuotaStorage } // GetQuotaHistory 获取配额历史(转发到 Gateway) func (s *QuotaService) GetQuotaHistory(req *QuotaHistoryRequest) (*QuotaHistoryResponse, error) // CreateQuotaRule 创建配额规则 func (s *QuotaService) CreateQuotaRule(rule *models.QuotaRule) error // UpdateQuotaRule 更新配额规则 func (s *QuotaService) UpdateQuotaRule(id uint, rule *models.QuotaRule) error // DeleteQuotaRule 删除配额规则 func (s *QuotaService) DeleteQuotaRule(id uint) error ``` --- #### 9. 网关客户端 **位置**: `internal/services/gateway_client.go` **功能**: 转发请求到 AI Gateway ```go type GatewayClient struct { baseURL string httpClient *http.Client authToken string } // Login 登录 Gateway 获取 token func (c *GatewayClient) Login() error // ForwardRequest 转发请求到 Gateway func (c *GatewayClient) ForwardRequest(method, path string, body interface{}) ([]byte, error) ``` **配置**: ```yaml gateway: base_url: "http://44.247.156.94:8080" timeout: 30 auth: login_url: "/api/auth/login" key: "your-key" ``` --- #### 10. 审计日志服务 **位置**: `internal/services/audit_log_service.go` **主要方法**: ```go type AuditLogService struct { storage *storage.AuditLogStorage } // CreateLog 创建审计日志 func (s *AuditLogService) CreateLog(log *models.AuditLog) error // ListLogs 查询审计日志(分页) func (s *AuditLogService) ListLogs(req *ListAuditLogsRequest) ([]models.AuditLog, int64, error) ``` **日志记录示例**: ```go // 更新用户时记录日志 func (s *UserService) Update(id uint, newUser *models.User) error { // 1. 查询旧值 oldUser, _ := s.storage.GetByID(id) // 2. 更新用户 s.storage.Update(id, newUser) // 3. 记录审计日志 s.auditLogService.CreateLog(&models.AuditLog{ UserID: currentUserID, OperationType: "update", TargetTable: "admin_users", TargetID: id, OldValue: toJSON(oldUser), NewValue: toJSON(newUser), IP: clientIP, UserAgent: userAgent, }) return nil } ``` --- ## 配置管理 ### 配置文件位置 - **开发环境**: `etc/config.yaml` - **生产环境**: `etc/config-prod.yaml` ### 配置加载 **位置**: `internal/config/config.go` ```go type Config struct { Server ServerConfig `mapstructure:"server"` Database DatabaseConfig `mapstructure:"database"` Redis RedisConfig `mapstructure:"redis"` PostgreSQL PostgreSQLConfig `mapstructure:"postgresql"` SSO SSOConfig `mapstructure:"sso"` Gateway GatewayConfig `mapstructure:"gateway"` OSS OSSConfig `mapstructure:"oss"` Email EmailConfig `mapstructure:"email"` Alert AlertConfig `mapstructure:"alert"` Jobs JobsConfig `mapstructure:"jobs"` } func LoadConfig(env string) (*Config, error) { viper.SetConfigName(fmt.Sprintf("config-%s", env)) viper.SetConfigType("yaml") viper.AddConfigPath("./etc") if err := viper.ReadInConfig(); err != nil { return nil, err } var config Config if err := viper.Unmarshal(&config); err != nil { return nil, err } return &config, nil } ``` --- ### 配置示例 #### 完整配置文件 (config.yaml) ```yaml # 服务器配置 server: addr: "0.0.0.0" port: 8087 # MySQL 数据库配置 database: dsn: "root:password@tcp(localhost:3306)/goalfymax_admin?charset=utf8mb4&parseTime=True&loc=Local" maxIdleConns: 10 maxOpenConns: 100 logLevel: "info" # Redis 配置 redis: addr: "localhost:6379" password: "" db: 0 poolSize: 10 # PostgreSQL 配置 (MCP 数据) postgresql: host: "localhost" port: 5432 user: "postgres" password: "password" dbname: "mcp_gateway" sslmode: "disable" max_open_conns: 100 max_idle_conns: 20 # SSO 配置 sso: sso_server_url: "https://passport.goalfy.ai" client_id: "xv5Xesd4ry1_I3hP3xYXNw" client_secret: "your-secret" redirect_uri: "http://localhost:3003" scope: "openid profile email" resource_aud: "api://admin" admin_token: "your-admin-token" # Gateway 配置 gateway: base_url: "http://44.247.156.94:8080" timeout: 30 auth: login_url: "/api/auth/login" key: "your-gateway-key" # OSS (S3) 配置 oss: endpoint: "https://goalfyagent-data-test.s3.us-west-2.amazonaws.com/" region: "us-west-2" access_key_id: "your-access-key" secret_access_key: "your-secret-key" bucket: "goalfyagent-data-test" presign_url_expire: 30m # 邮件配置 email: sender: "invite_goalfymax@goalfyai.com" sender_name: "GoalfyMax" host: "smtp.mxhichina.com" port: 465 username: "invite_goalfymax@goalfyai.com" password: "your-email-password" invite_url_prefix: "https://passport.goalfy.ai/invite/" # 告警配置 alert: dingtalk: enabled: true webhook: "https://oapi.dingtalk.com/robot/send?access_token=xxx" secret: "your-dingtalk-secret" # 定时任务配置 jobs: mcp_usage_balance: enabled: true run_on_startup: true delay_minutes: 5 model_token_balance: enabled: true run_on_startup: true delay_minutes: 5 ``` --- ### 环境变量支持 **使用环境变量覆盖配置**: ```bash export SERVER_PORT=8088 export DATABASE_DSN="root:newpassword@tcp(localhost:3306)/db" export REDIS_ADDR="redis.example.com:6379" ``` **在代码中启用**: ```go viper.AutomaticEnv() viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) ``` --- ## 部署方案 ### Docker 部署 #### Dockerfile **位置**: `Dockerfile` **多阶段构建**: ```dockerfile # Stage 1: 构建 FROM golang:1.25-alpine AS builder WORKDIR /app # 安装依赖 RUN apk add --no-cache git # 复制 go.mod 和 go.sum COPY go.mod go.sum ./ RUN go mod download # 复制源代码 COPY . . # 构建二进制文件 RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ -ldflags="-w -s" \ -o goalfymax-admin \ ./cmd/server # Stage 2: 运行 FROM alpine:latest WORKDIR /app # 安装 ca-certificates (用于 HTTPS 请求) RUN apk --no-cache add ca-certificates # 创建非 root 用户 RUN addgroup -g 1000 appuser && \ adduser -D -u 1000 -G appuser appuser # 复制二进制文件和配置文件 COPY --from=builder /app/goalfymax-admin . COPY --from=builder /app/etc ./etc COPY --from=builder /app/docs ./docs # 设置权限 RUN chown -R appuser:appuser /app # 切换到非 root 用户 USER appuser # 暴露端口 EXPOSE 8087 # 健康检查 HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:8087/health || exit 1 # 启动应用 CMD ["./goalfymax-admin", "--env", "prod"] ``` **构建镜像**: ```bash ./build.sh ``` **推送到 ECR**: ```bash ./build-and-push.sh ``` --- ### Kubernetes 部署 #### Deployment (k8s/deployment.yaml) ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: goalfymax-admin namespace: default spec: replicas: 2 selector: matchLabels: app: goalfymax-admin template: metadata: labels: app: goalfymax-admin spec: containers: - name: goalfymax-admin image: 177603749739.dkr.ecr.us-west-2.amazonaws.com/goalfy/goalfymax-admin:latest imagePullPolicy: Always ports: - containerPort: 8087 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi livenessProbe: httpGet: path: /health port: 8087 initialDelaySeconds: 30 periodSeconds: 30 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /health port: 8087 initialDelaySeconds: 10 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 3 volumeMounts: - name: config mountPath: /app/etc readOnly: true env: - name: ENV value: "prod" volumes: - name: config configMap: name: goalfymax-admin-config ``` #### Service (k8s/service.yaml) ```yaml apiVersion: v1 kind: Service metadata: name: goalfymax-admin namespace: default spec: type: ClusterIP selector: app: goalfymax-admin ports: - protocol: TCP port: 8087 targetPort: 8087 ``` #### ConfigMap (k8s/configmap.yaml) ```yaml apiVersion: v1 kind: ConfigMap metadata: name: goalfymax-admin-config namespace: default data: config-prod.yaml: | server: addr: "0.0.0.0" port: 8087 database: dsn: "root:password@tcp(mysql:3306)/goalfymax_admin" redis: addr: "redis:6379" # ... 其他配置 ``` --- ### 部署流程 #### 1. 本地开发 ```bash # 启动服务 ./start.sh # 启动 SSO 服务 ./start_sso.sh # 启动带 CORS 的服务 ./start_with_cors.sh ``` #### 2. 构建 Docker 镜像 ```bash # 本地构建 ./build.sh # 构建并推送到 ECR ./build-and-push.sh ``` #### 3. 部署到 Kubernetes ```bash # 创建 ConfigMap kubectl apply -f k8s/configmap.yaml # 部署应用 kubectl apply -f k8s/deployment.yaml # 创建 Service kubectl apply -f k8s/service.yaml # 查看部署状态 kubectl get pods -l app=goalfymax-admin kubectl logs -f