Files
goalfylearning-admin/ARCHITECTURE.md

3429 lines
88 KiB
Markdown

# 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
<h2>您的邀请码申请已通过!</h2>
<p>您的邀请码是: <strong>{{code}}</strong></p>
<p>有效期: 30天</p>
<a href="https://passport.goalfy.ai/invite/{{code}}">立即使用</a>
```
**英文通过模板** (docs/email_preview_en.html):
```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):
```html
<h2>您的邀请码申请未通过</h2>
<p>拒绝原因: {{reason}}</p>
<p>您可以重新申请或联系我们的客服。</p>
```
---
#### 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 <pod-name>
```
#### 4. 数据库迁移
```bash
# 执行迁移脚本
./scripts/migrate.sh
```
#### 5. 健康检查
```bash
# 检查服务健康状态
curl http://localhost:8087/health
# 输出: {"status":"ok"}
```
---
### 监控与告警
#### 健康检查端点
```
GET /health
```
**响应**:
```json
{
"status": "ok"
}
```
#### 钉钉告警
**配置**:
```yaml
alert:
dingtalk:
enabled: true
webhook: "https://oapi.dingtalk.com/robot/send?access_token=xxx"
secret: "your-secret"
```
**使用**:
```go
// 发送告警
notifier.SendDingTalk("服务异常", "数据库连接失败")
```
---
## 开发指南
### 本地开发环境搭建
#### 1. 环境要求
- Go 1.25+
- MySQL 8.0+
- Redis 6.0+
- PostgreSQL 13+
#### 2. 安装依赖
```bash
go mod download
```
#### 3. 配置数据库
**创建数据库**:
```sql
CREATE DATABASE goalfymax_admin CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
**配置文件** (etc/config.yaml):
```yaml
database:
dsn: "root:password@tcp(localhost:3306)/goalfymax_admin?charset=utf8mb4&parseTime=True&loc=Local"
```
#### 4. 启动服务
```bash
./start.sh
```
或者:
```bash
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. 错误处理
**统一错误响应**:
```go
utils.ErrorResponse(c, http.StatusBadRequest, "参数错误")
```
**统一成功响应**:
```go
utils.SuccessResponse(c, data)
```
#### 4. 日志规范
**使用结构化日志**:
```go
logger.Info("创建用户",
zap.String("username", username),
zap.Uint("user_id", userID),
)
logger.Error("数据库错误",
zap.Error(err),
zap.String("operation", "create user"),
)
```
---
### 添加新功能
#### 1. 添加新的数据模型
**步骤**:
1. 在 `internal/models/` 创建模型文件
2. 定义模型结构
3. 在 `storage/database.go` 的 `autoMigrate()` 中注册
**示例**:
```go
// 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"`
}
```
```go
// storage/database.go
func (d *Database) autoMigrate() error {
return d.DB.AutoMigrate(
// ... 其他模型
&models.Product{},
)
}
```
---
#### 2. 添加新的 Storage
**步骤**:
1. 在 `internal/storage/` 创建 storage 文件
2. 实现 CRUD 方法
3. 依赖 Database 对象
**示例**:
```go
// 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
**步骤**:
1. 在 `internal/services/` 创建 service 文件
2. 实现业务逻辑
3. 调用 Storage 方法
**示例**:
```go
// 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
**步骤**:
1. 在 `internal/api/handlers/` 创建 handler 文件
2. 实现 HTTP 处理方法
3. 在 routes.go 中注册路由
**示例**:
```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 中添加**:
```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. 单元测试
**创建测试文件**:
```go
// 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(), "价格不能为负数")
}
```
**运行测试**:
```bash
go test ./internal/services/...
```
---
#### 2. API 测试
**使用 curl 测试**:
```bash
# 登录
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}"
```
**使用测试脚本**:
```bash
./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 是一个功能完善、架构清晰的企业级后端管理系统:
### 核心优势
1. **架构清晰**: 严格的三层架构,职责分离明确
2. **功能完善**: 用户管理、权限控制、财务管理、邀请码系统等
3. **安全可靠**: SSO 单点登录、RBAC 权限、审计日志、PKCE 安全增强
4. **生产就绪**: Docker + Kubernetes 部署、健康检查、日志监控、钉钉告警
5. **代码规范**: 符合 Go 最佳实践,模块化设计,接口抽象
6. **可扩展性**: 易于添加新功能,支持多数据库
### 技术亮点
- **SSO 单点登录**: 完整的 OAuth2/OIDC 实现,支持 PKCE
- **RBAC 权限**: 灵活的角色-页面权限管理
- **邀请码系统**: 完整的申请-审批-发邮件流程
- **财务管理**: MCP 账户、模型账户、余额管理
- **审计日志**: 记录所有数据变更操作
- **定时任务**: 余额同步、统计任务
- **多数据库**: MySQL + PostgreSQL + Redis
- **容器化部署**: Docker 多阶段构建 + Kubernetes
### 部署环境
- **容器化**: Docker + Kubernetes
- **云平台**: AWS EKS + RDS + S3 + Redis
- **监控告警**: 健康检查 + 钉钉告警
- **高可用**: 多副本部署 + 负载均衡
---
## 附录
### 相关文档
- [快速开始指南](discuss/QUICK_START.md)
- [架构指南](discuss/architecture_guide.md)
- [用户等级配置实现总结](discuss/user-level-config-implementation-summary.md)
- [部署和测试文档](docs/deployment_and_testing.md)
- [邀请码功能文档](docs/invite_code_application_feature.md)
- [邮件模板预览](docs/email_templates_preview.html)
### 脚本工具
- **启动脚本**: `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