88 KiB
GoalfyMax Admin 后端管理系统 - 架构文档
目录
项目概述
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) │
└─────────────────────────────────────────────┘
架构特点
-
严格的层次分离
- Handler 只负责 HTTP 请求处理
- Service 封装所有业务逻辑
- Storage 封装所有数据访问
-
依赖注入
- Service 依赖 Storage 接口
- Handler 依赖 Service 接口
- 便于单元测试和模块替换
-
统一错误处理
- 使用
utils.ErrorResponse统一错误响应 - 使用
utils.SuccessResponse统一成功响应
- 使用
-
中间件链
- 认证 -> 权限 -> 日志 -> 业务处理
数据流向
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
核心字段:
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):
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: 角色-页面关联表
核心模型:
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/*
权限检查中间件:
// 检查用户是否有访问某个页面的权限
rbacMiddleware.RequirePagePermission("/users")
使用示例:
// 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
配置内容:
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
核心模型:
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
核心模型:
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):
通过审批:
1. 验证申请状态 (必须是 pending)
2. 生成邀请码
- 关联申请邮箱
- 设置默认用户等级 (normal)
- 设置过期时间 (30天)
3. 更新申请状态为 approved
4. 记录审核人和审核时间
5. 发送邮件通知 (根据语言选择模板)
6. 在事务中完成所有操作
拒绝审批:
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
核心模型:
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 使用余额
- 从外部服务获取余额数据
- 更新到本地数据库
配置:
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 使用情况
- 更新余额数据
配置:
jobs:
model_token_balance:
enabled: true
run_on_startup: true
delay_minutes: 5
执行频率: 每小时
数据库设计
数据库架构
系统使用三个数据库:
- MySQL: 主数据库,存储所有业务数据
- PostgreSQL: MCP 配置和相关数据
- Redis: 缓存、会话、余额等临时数据
核心数据表
1. BaseModel (所有表的基础)
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 (管理员用户表)
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, emailindex: role_id, deleted_at
说明:
- 管理后台的用户表
- 通过 SSO 登录自动创建
- 关联角色表实现权限控制
3. admin_goalfymax_users (GoalfyMax 用户表)
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_idindex: deleted_at
说明:
- GoalfyMax 产品的用户表
- user_id 关联 SSO 系统的用户 ID
- Balance 字段不存数据库,从 Redis 实时查询
余额存储 (Redis):
key: goalfymax:user:balance:{user_id}
value: float64 (余额)
4. admin_user_level_configs (用户等级配置表)
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_codeindex: deleted_at
默认数据:
| level_code | level_name | project_limit |
|---|---|---|
| normal | 普通用户 | 2 |
| vip | VIP用户 | 10 |
| internal | 内部用户 | 0 (无限) |
5. admin_invite_codes (邀请码表)
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: codeindex: deleted_at
6. admin_invite_code_applications (邀请码申请表)
SQL 迁移: migrations/20250131_add_invite_code_applications_table.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 (角色表)
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: nameindex: deleted_at
8. admin_pages (页面/菜单表)
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: pathindex: deleted_at
9. admin_role_page_permissions (角色-页面权限关联表)
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 (审计日志表)
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 (余额操作日志表)
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)
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/
-
20250129_add_client_id_to_invite_codes.sql
- 添加 client_id 字段到 invite_codes 表
-
20250131_add_invite_code_applications_table.sql
- 创建邀请码申请表
-
20250204_add_language_to_invite_code_applications.sql
- 添加 language 字段到申请表
执行迁移:
./scripts/migrate.sh
API 接口说明
接口规范
请求格式
认证:
Authorization: Bearer {access_token}
请求体 (POST/PUT):
{
"field1": "value1",
"field2": "value2"
}
响应格式
成功响应:
{
"code": 200,
"message": "success",
"data": {
// 响应数据
}
}
错误响应:
{
"code": 400,
"message": "错误信息"
}
分页响应:
{
"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 的安全扩展,用于防止授权码拦截攻击。
流程:
-
生成 code_verifier (随机字符串)
codeVerifier := generateRandomString(128) -
计算 code_challenge
hash := sha256.Sum256([]byte(codeVerifier)) codeChallenge := base64.RawURLEncoding.EncodeToString(hash[:]) -
授权请求携带 code_challenge
GET /authorize? client_id=xxx &redirect_uri=xxx &code_challenge=xxx &code_challenge_method=S256 -
Token 交换时携带 code_verifier
POST /token { "code": "xxx", "code_verifier": "xxx" } -
服务器验证
hash(code_verifier) == code_challenge
自动用户创建
位置: pkg/middleware/auth.go:230-290
逻辑:
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
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
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
使用示例
在路由中使用:
// 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 权限
}
灵活的权限粒度:
// 整个路由组共享权限
usersGroup.Use(rbacMiddleware.RequirePagePermission("/users"))
// 或者单个路由单独设置权限
users.GET("",
rbacMiddleware.RequirePagePermission("/users/view"),
userHandler.List,
)
会话管理
MemorySessionManager
位置: pkg/middleware/auth.go:24-58
数据结构:
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
}
功能:
-
创建会话
func (m *MemorySessionManager) Create(session *Session) { m.mu.Lock() defer m.mu.Unlock() m.sessions[session.AccessToken] = session } -
获取会话
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 } -
删除会话 (登出)
func (m *MemorySessionManager) Delete(accessToken string) { m.mu.Lock() defer m.mu.Unlock() delete(m.sessions, accessToken) } -
获取所有在线用户
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行)
功能: 验证用户身份
流程:
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行)
功能: 检查用户是否有访问某个页面的权限
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行)
功能: 检查用户是否具有特定角色
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 请求
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
功能: 记录数据修改接口的详细信息
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
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
主要方法:
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
主要方法:
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
主要方法:
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
余额操作逻辑:
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
主要方法:
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
主要方法:
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
审批通过逻辑:
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
主要方法:
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):
<h2>您的邀请码申请已通过!</h2>
<p>您的邀请码是: <strong>{{code}}</strong></p>
<p>有效期: 30天</p>
<a href="https://passport.goalfy.ai/invite/{{code}}">立即使用</a>
英文通过模板 (docs/email_preview_en.html):
<h2>Your invite code application has been approved!</h2>
<p>Your invite code is: <strong>{{code}}</strong></p>
<p>Valid for: 30 days</p>
<a href="https://passport.goalfy.ai/invite/{{code}}">Use Now</a>
中文拒绝模板 (docs/email_preview_rejection.html):
<h2>您的邀请码申请未通过</h2>
<p>拒绝原因: {{reason}}</p>
<p>您可以重新申请或联系我们的客服。</p>
7. 财务服务
位置: internal/services/finance_service.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
主要方法:
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
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)
配置:
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
主要方法:
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)
日志记录示例:
// 更新用户时记录日志
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
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)
# 服务器配置
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
环境变量支持
使用环境变量覆盖配置:
export SERVER_PORT=8088
export DATABASE_DSN="root:newpassword@tcp(localhost:3306)/db"
export REDIS_ADDR="redis.example.com:6379"
在代码中启用:
viper.AutomaticEnv()
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
部署方案
Docker 部署
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"]
构建镜像:
./build.sh
推送到 ECR:
./build-and-push.sh
Kubernetes 部署
Deployment (k8s/deployment.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)
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)
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. 本地开发
# 启动服务
./start.sh
# 启动 SSO 服务
./start_sso.sh
# 启动带 CORS 的服务
./start_with_cors.sh
2. 构建 Docker 镜像
# 本地构建
./build.sh
# 构建并推送到 ECR
./build-and-push.sh
3. 部署到 Kubernetes
# 创建 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 <pod-name>
4. 数据库迁移
# 执行迁移脚本
./scripts/migrate.sh
5. 健康检查
# 检查服务健康状态
curl http://localhost:8087/health
# 输出: {"status":"ok"}
监控与告警
健康检查端点
GET /health
响应:
{
"status": "ok"
}
钉钉告警
配置:
alert:
dingtalk:
enabled: true
webhook: "https://oapi.dingtalk.com/robot/send?access_token=xxx"
secret: "your-secret"
使用:
// 发送告警
notifier.SendDingTalk("服务异常", "数据库连接失败")
开发指南
本地开发环境搭建
1. 环境要求
- Go 1.25+
- MySQL 8.0+
- Redis 6.0+
- PostgreSQL 13+
2. 安装依赖
go mod download
3. 配置数据库
创建数据库:
CREATE DATABASE goalfymax_admin CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
配置文件 (etc/config.yaml):
database:
dsn: "root:password@tcp(localhost:3306)/goalfymax_admin?charset=utf8mb4&parseTime=True&loc=Local"
4. 启动服务
./start.sh
或者:
go run cmd/server/main.go --env dev
5. 访问接口
- 健康检查: http://localhost:8087/health
- API 文档: http://localhost:8087/swagger (如果有)
代码规范
1. 项目结构规范
- Handler: 只负责 HTTP 请求处理,不包含业务逻辑
- Service: 封装所有业务逻辑,调用 Storage
- Storage: 封装所有数据库操作
2. 命名规范
- 文件名: 小写蛇形命名 (user_service.go)
- 变量名: 驼峰命名 (userService)
- 常量名: 大写蛇形命名 (MAX_PAGE_SIZE)
- 接口名: I 开头 (IUserService)
3. 错误处理
统一错误响应:
utils.ErrorResponse(c, http.StatusBadRequest, "参数错误")
统一成功响应:
utils.SuccessResponse(c, data)
4. 日志规范
使用结构化日志:
logger.Info("创建用户",
zap.String("username", username),
zap.Uint("user_id", userID),
)
logger.Error("数据库错误",
zap.Error(err),
zap.String("operation", "create user"),
)
添加新功能
1. 添加新的数据模型
步骤:
- 在
internal/models/创建模型文件 - 定义模型结构
- 在
storage/database.go的autoMigrate()中注册
示例:
// internal/models/product.go
type Product struct {
BaseModel
Name string `json:"name" gorm:"size:100;not null"`
Price float64 `json:"price" gorm:"type:decimal(10,2)"`
Description string `json:"description" gorm:"type:text"`
}
// storage/database.go
func (d *Database) autoMigrate() error {
return d.DB.AutoMigrate(
// ... 其他模型
&models.Product{},
)
}
2. 添加新的 Storage
步骤:
- 在
internal/storage/创建 storage 文件 - 实现 CRUD 方法
- 依赖 Database 对象
示例:
// internal/storage/product_storage.go
type ProductStorage struct {
db *Database
}
func NewProductStorage(db *Database) *ProductStorage {
return &ProductStorage{db: db}
}
func (s *ProductStorage) Create(product *models.Product) error {
return s.db.DB.Create(product).Error
}
func (s *ProductStorage) GetByID(id uint) (*models.Product, error) {
var product models.Product
err := s.db.DB.First(&product, id).Error
return &product, err
}
func (s *ProductStorage) List(page, pageSize int) ([]models.Product, int64, error) {
var products []models.Product
var total int64
offset := (page - 1) * pageSize
s.db.DB.Model(&models.Product{}).Count(&total)
err := s.db.DB.Offset(offset).Limit(pageSize).Find(&products).Error
return products, total, err
}
func (s *ProductStorage) Update(id uint, product *models.Product) error {
return s.db.DB.Model(&models.Product{}).Where("id = ?", id).Updates(product).Error
}
func (s *ProductStorage) Delete(id uint) error {
return s.db.DB.Delete(&models.Product{}, id).Error
}
3. 添加新的 Service
步骤:
- 在
internal/services/创建 service 文件 - 实现业务逻辑
- 调用 Storage 方法
示例:
// internal/services/product_service.go
type ProductService struct {
storage *storage.ProductStorage
}
func NewProductService(storage *storage.ProductStorage) *ProductService {
return &ProductService{storage: storage}
}
func (s *ProductService) CreateProduct(product *models.Product) error {
// 业务验证
if product.Price < 0 {
return errors.New("价格不能为负数")
}
// 调用 Storage
return s.storage.Create(product)
}
func (s *ProductService) GetProduct(id uint) (*models.Product, error) {
return s.storage.GetByID(id)
}
func (s *ProductService) ListProducts(page, pageSize int) ([]models.Product, int64, error) {
return s.storage.List(page, pageSize)
}
4. 添加新的 Handler
步骤:
- 在
internal/api/handlers/创建 handler 文件 - 实现 HTTP 处理方法
- 在 routes.go 中注册路由
示例:
// internal/api/handlers/product_handler.go
type ProductHandler struct {
service *services.ProductService
}
func NewProductHandler(service *services.ProductService) *ProductHandler {
return &ProductHandler{service: service}
}
// Create 创建产品
func (h *ProductHandler) Create(c *gin.Context) {
var req models.Product
if err := c.ShouldBindJSON(&req); err != nil {
utils.ErrorResponse(c, http.StatusBadRequest, "参数错误")
return
}
if err := h.service.CreateProduct(&req); err != nil {
utils.ErrorResponse(c, http.StatusInternalServerError, err.Error())
return
}
utils.SuccessResponse(c, req)
}
// List 产品列表
func (h *ProductHandler) List(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
products, total, err := h.service.ListProducts(page, pageSize)
if err != nil {
utils.ErrorResponse(c, http.StatusInternalServerError, err.Error())
return
}
utils.SuccessResponse(c, gin.H{
"list": products,
"total": total,
"page": page,
"page_size": pageSize,
})
}
5. 注册路由
在 routes.go 中添加:
// internal/api/routes/routes.go
func SetupRoutes(r *gin.Engine, deps *Dependencies) {
// ... 初始化 handler
productHandler := handlers.NewProductHandler(deps.ProductService)
// 添加路由
admin := r.Group("/api/admin")
admin.Use(authMiddleware.RequireAuth())
{
products := admin.Group("/products")
products.Use(rbacMiddleware.RequirePagePermission("/products"))
{
products.GET("", productHandler.List)
products.POST("", productHandler.Create)
products.GET("/:id", productHandler.GetByID)
products.PUT("/:id", productHandler.Update)
products.DELETE("/:id", productHandler.Delete)
}
}
}
测试
1. 单元测试
创建测试文件:
// internal/services/product_service_test.go
package services
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestProductService_CreateProduct(t *testing.T) {
// 创建 mock storage
mockStorage := &MockProductStorage{}
service := NewProductService(mockStorage)
// 测试用例
product := &models.Product{
Name: "Test Product",
Price: 99.99,
}
err := service.CreateProduct(product)
assert.NoError(t, err)
assert.NotZero(t, product.ID)
}
func TestProductService_CreateProduct_NegativePrice(t *testing.T) {
mockStorage := &MockProductStorage{}
service := NewProductService(mockStorage)
product := &models.Product{
Name: "Test Product",
Price: -10, // 负数价格
}
err := service.CreateProduct(product)
assert.Error(t, err)
assert.Contains(t, err.Error(), "价格不能为负数")
}
运行测试:
go test ./internal/services/...
2. API 测试
使用 curl 测试:
# 登录
curl -X POST http://localhost:8087/api/sso/login
# 创建用户
curl -X POST http://localhost:8087/api/admin/users \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"username": "testuser",
"email": "test@example.com",
"nickname": "Test User"
}'
# 查询用户列表
curl -X GET "http://localhost:8087/api/admin/users?page=1&page_size=20" \
-H "Authorization: Bearer {token}"
使用测试脚本:
./scripts/test_api.sh
./test_sso_api.sh
./test_quota_api.sh
常见问题
1. 数据库连接失败
问题:
Error 1045: Access denied for user 'root'@'localhost'
解决:
- 检查 MySQL 用户名和密码
- 检查 MySQL 是否启动
- 检查配置文件中的 DSN
2. Redis 连接失败
问题:
dial tcp 127.0.0.1:6379: connect: connection refused
解决:
- 检查 Redis 是否启动
- 检查 Redis 端口是否正确
- 检查 Redis 密码配置
3. SSO 登录失败
问题:
invalid_grant: PKCE validation failed
解决:
- 检查 code_verifier 是否正确存储
- 检查 SSO 服务器配置
- 检查 redirect_uri 是否匹配
4. 权限被拒绝
问题:
403 Forbidden: No permission to access this page
解决:
- 检查用户是否分配角色
- 检查角色是否有对应页面权限
- 检查页面路径是否匹配
总结
GoalfyMax Admin 是一个功能完善、架构清晰的企业级后端管理系统:
核心优势
- 架构清晰: 严格的三层架构,职责分离明确
- 功能完善: 用户管理、权限控制、财务管理、邀请码系统等
- 安全可靠: SSO 单点登录、RBAC 权限、审计日志、PKCE 安全增强
- 生产就绪: Docker + Kubernetes 部署、健康检查、日志监控、钉钉告警
- 代码规范: 符合 Go 最佳实践,模块化设计,接口抽象
- 可扩展性: 易于添加新功能,支持多数据库
技术亮点
- SSO 单点登录: 完整的 OAuth2/OIDC 实现,支持 PKCE
- RBAC 权限: 灵活的角色-页面权限管理
- 邀请码系统: 完整的申请-审批-发邮件流程
- 财务管理: MCP 账户、模型账户、余额管理
- 审计日志: 记录所有数据变更操作
- 定时任务: 余额同步、统计任务
- 多数据库: MySQL + PostgreSQL + Redis
- 容器化部署: Docker 多阶段构建 + Kubernetes
部署环境
- 容器化: Docker + Kubernetes
- 云平台: AWS EKS + RDS + S3 + Redis
- 监控告警: 健康检查 + 钉钉告警
- 高可用: 多副本部署 + 负载均衡
附录
相关文档
脚本工具
- 启动脚本:
start.sh,start_sso.sh,start_with_cors.sh - 部署脚本:
build.sh,build-and-push.sh - 数据库迁移:
scripts/migrate.sh - 测试脚本:
scripts/test_api.sh,test_sso_api.sh - 邀请码 API:
scripts/invite_code_api.py
联系方式
- 项目地址: /Users/ricardo/Documents/加和科技/goalfylearning-admin
- Git 仓库: (待补充)
- 问题反馈: (待补充)
文档版本: v1.0 更新时间: 2025-12-04 文档作者: Claude AI