Files
xinfra/docs/ref/架构文档-v3.md
T
wonder 0e29aa2dc1 docs: 补充项目文档体系,完善 AI 工具配置
变更内容:
- 新增 AGENTS.md:为 Codex 等 AI 工具提供项目开发指南
- 更新 CLAUDE.md:优化文档阅读层级结构
- 新增 docs/ref/平台需求文档-v3.md:平台整体需求和功能范围
- 新增 docs/ref/架构文档-v3.md:系统架构设计和技术选型

文档阅读层级(P1-P4):
- P1: MVP 方案文档(优先阅读)
- P2: 原型文档(交互细节)
- P3: 架构文档(整体设计)
- P4: 需求文档(需求范围)

只有需要时才往下阅读下一层级文档。
2026-07-14 15:01:17 +08:00

1009 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 概述
本文档定义 XINFRA MVP 阶段的代码骨架结构、模块划分、接口定义和技术实现要点。
**技术栈锁定**:
- **后端**: Go 1.21+ / Gin
- **前端**: Vue 3.3+ / Element Plus / Vite
- **数据库**: MySQL 8.0
- **缓存**: Redis
- **部署**: K8s + Nginx
**MVP 范围**:
1. 认证(LDAP + SAML + OAuth 2.0)
2. 统一子系统导航(卡片展示 + OAuth 2.0 跳转)
3. 审计面板(登录审计 + 主系统 Ansible 运维操作审计)
4. 子系统对接(仅 OAuth 2.0 跳转,主系统后端向前端返回 Mock 数据)
5. Ansible 调度基础模块
> 前端 MVP 参考平台需求文档 #30,无需在 MVP 中实现后端
---
## 项目目录结构
```
xinfra/
├── frontend/ # 前端 Vue 3 项目,提供用户界面和交互
│ ├── src/
│ │ ├── api/ # 封装后端 API 请求,统一处理请求/响应和错误
│ │ │ ├── auth.ts # 认证相关接口:登录、登出、获取用户信息
│ │ │ ├── audit.ts # 审计相关接口:查询登录审计、运维操作审计
│ │ │ ├── subsystem.ts # 子系统相关接口:获取子系统列表、SSO 跳转 URL
│ │ │ └── request.ts # Axios 实例封装:请求拦截器(Token 注入)、响应拦截器(错误处理)
│ │ ├── components/ # 可复用的 UI 组件,按功能模块组织
│ │ │ ├── Layout/ # 页面布局组件:定义整体页面骨架结构
│ │ │ │ ├── AppHeader.vue # 顶部导航栏:显示 Logo、用户名、退出登录
│ │ │ │ ├── AppSidebar.vue # 侧边菜单栏:导航菜单项(仪表盘、子系统、审计)
│ │ │ │ └── AppMain.vue # 主内容区:包裹路由视图的容器
│ │ │ ├── SubsystemCard.vue # 子系统卡片:展示子系统图标、名称、状态,点击触发 SSO 跳转
│ │ │ └── AuditLogTable.vue # 审计日志表格:展示审计记录列表,支持分页和筛选
│ │ ├── composables/ # Vue 3 组合式函数,封装可复用的业务逻辑
│ │ │ └── useAuth.ts # 认证状态管理:登录、登出、Token 校验、路由守卫
│ │ ├── layouts/ # 页面布局定义,不同页面可使用不同布局
│ │ │ └── DefaultLayout.vue # 默认布局:包含顶栏 + 侧边栏 + 内容区
│ │ ├── router/ # 前端路由配置,定义页面路由映射和导航规则
│ │ │ └── index.ts # 路由实例:路由表定义、路由守卫(未登录重定向)
│ │ ├── stores/ # Pinia 状态管理,跨组件共享全局状态
│ │ │ ├── auth.ts # 认证状态:存储 Token、用户信息,提供登录/登出 actions
│ │ │ └── user.ts # 用户信息状态:缓存当前登录用户的详细信息
│ │ ├── views/ # 页面视图组件,一个文件对应一个完整页面
│ │ │ ├── auth/ # 认证相关页面
│ │ │ │ └── Login.vue # 登录页:用户名密码表单、LDAP 认证入口
│ │ │ ├── dashboard/ # 仪表盘页面
│ │ │ │ └── Index.vue # 首页仪表盘:系统概览、快捷入口
│ │ │ ├── subsystem/ # 子系统导航页面
│ │ │ │ └── Navigation.vue # 子系统导航页:6 个子系统卡片网格展示
│ │ │ └── audit/ # 审计面板页面
│ │ │ ├── LoginAudit.vue # 登录审计页:查询登录记录、按时间/IP/结果筛选
│ │ │ └── OpsAudit.vue # 运维操作审计页:查询操作记录、按类型/状态筛选
│ │ ├── utils/ # 通用工具函数,不依赖业务逻辑
│ │ │ └── auth.ts # 认证工具:Token 解析、过期校验、本地存储读写
│ │ ├── App.vue # 根组件:挂载路由视图,全局样式和 Provider
│ │ └── main.ts # 入口文件:初始化 Vue 应用、注册插件(Pinia、Router、Element Plus)
│ ├── public/ # 静态资源目录,构建时原样复制到 dist
│ ├── index.html # HTML 入口模板,Vite 注入构建后的 JS/CSS
│ ├── vite.config.ts # Vite 构建配置:插件、别名、代理、构建选项
│ ├── tsconfig.json # TypeScript 配置:编译选项、路径映射
│ └── package.json # 依赖管理:项目依赖列表、脚本命令
│
├── server/ # 后端 Go 项目,提供 RESTful API 服务
│ ├── cmd/ # 程序入口目录
│ │ └── server/
│ │ └── main.go # 服务启动入口:初始化配置、数据库、路由,启动 HTTP 服务
│ ├── internal/ # 内部包,不对外暴露,按分层架构组织
│ │ ├── config/ # 配置管理模块
│ │ │ └── config.go # 配置加载:读取 YAML 配置文件,支持环境变量覆盖
│ │ ├── handler/ # HTTP 处理器层(Controller),接收请求、校验参数、调用 Service
│ │ │ ├── auth.go # 认证处理器:处理登录/登出/用户信息请求
│ │ │ ├── audit.go # 审计处理器:处理登录审计/运维操作审计查询请求
│ │ │ ├── subsystem.go # 子系统处理器:处理子系统列表/SSO URL 生成请求
│ │ │ ├── task.go # 任务处理器:处理 Ansible 任务创建/状态查询请求
│ │ │ └── response.go # 统一响应:定义标准 JSON 响应格式和错误码
│ │ ├── middleware/ # HTTP 中间件,在请求处理前后执行通用逻辑
│ │ │ ├── auth.go # 认证中间件:校验 JWT Token,注入用户信息到 Context
│ │ │ ├── logger.go # 日志中间件:记录请求方法、路径、耗时、状态码
│ │ │ └── audit.go # 审计中间件:自动记录非 GET 请求的操作审计日志
│ │ ├── model/ # 数据模型层,定义数据库表结构和业务实体
│ │ │ ├── user.go # 用户模型:对应 users 表,定义用户字段和状态枚举
│ │ │ ├── audit.go # 审计模型:对应 login_audit / ops_audit 表
│ │ │ └── subsystem.go # 子系统模型:对应 subsystems 表,定义子系统信息
│ │ ├── repository/ # 数据访问层(DAO),封装数据库 CRUD 操作
│ │ │ ├── user.go # 用户仓储:用户查询、创建、更新、LDAP DN 映射
│ │ │ └── audit.go # 审计仓储:审计记录插入、分页查询、条件筛选
│ │ ├── service/ # 业务逻辑层,编排 Repository,实现核心业务
│ │ │ ├── auth.go # 认证服务:LDAP 认证、本地降级认证、JWT 生成
│ │ │ ├── ldap.go # LDAP 服务:封装 LDAP 连接、查询、认证逻辑
│ │ │ ├── audit.go # 审计服务:记录登录审计、运维操作审计
│ │ │ ├── subsystem.go # 子系统服务:子系统查询、OAuth 2.0 SSO URL 生成
│ │ │ └── ansible.go # Ansible 调度服务:创建任务、执行 Playbook、推送日志
│ │ ├── websocket/ # WebSocket 实时推送模块
│ │ │ └── hub.go # WebSocket Hub:管理客户端连接,广播 Ansible 任务日志
│ │ └── router/ # 路由注册模块
│ │ └── router.go # 路由注册:定义 API 路由表,绑定 Handler 和 Middleware
│ ├── pkg/ # 可复用的公共包,可被外部项目引用
│ │ ├── ldap/ # LDAP 客户端封装
│ │ │ └── client.go # LDAP 客户端:连接管理、用户认证、属性查询
│ │ ├── sso/ # SSO 协议处理
│ │ │ ├── oauth2.go # OAuth 2.0 客户端:主系统↔子系统的 Authorization Code 流程
│ │ │ └── saml.go # SAML 处理:主系统↔LDAP 的 SAML 断言生成和验证
│ │ └── database/ # 数据库连接管理
│ │ └── mysql.go # MySQL 连接池:初始化连接、健康检查、优雅关闭
│ ├── migrations/ # 数据库迁移脚本,按版本管理表结构变更
│ │ └── 001_init.sql # 初始化迁移:创建 users / subsystems / audit / ansible_tasks 表
│ └── go.mod # Go 模块定义:模块路径、依赖版本管理
│
├── deploy/ # 部署配置,包含容器化和编排所需文件
│ ├── docker/ # Docker 构建配置
│ │ ├── Dockerfile.frontend # 前端镜像:基于 Node 构建 + Nginx 托管静态文件
│ │ └── Dockerfile.server # 后端镜像:基于 Go 编译 + 运行时最小镜像
│ └── k8s/ # Kubernetes 部署配置
│ ├── frontend.yaml # 前端 Deployment:副本数、Service、环境变量配置
│ ├── server.yaml # 后端 Deployment:副本数、Service、Secret 挂载
│ └── ingress.yaml # Ingress 配置:域名路由规则、TLS 证书
│
├── docs/ # 项目文档目录
│ └── mvp-skeleton-plan.md # MVP 方案文档:本文件,定义代码骨架和实现要点
│
├── .gitignore # Git 忽略规则:排除编译产物、依赖、环境配置
├── Makefile # 构建脚本:定义 build / run / test / docker 等快捷命令
└── README.md # 项目说明:项目介绍、快速启动、开发指南
```
---
## 模块划分与职责
### 后端模块
| 模块 | 职责 | 说明 |
|------|------|------|
| `cmd/server` | 服务启动入口 | 初始化配置、数据库、路由,启动 HTTP 服务 |
| `internal/config` | 配置管理 | 加载 YAML 配置,支持环境变量覆盖 |
| `internal/handler` | HTTP 处理器 | 处理请求,调用 Service 层,返回响应 |
| `internal/middleware` | 中间件 | 认证、日志、审计等中间件 |
| `internal/model` | 数据模型 | 定义数据库表结构和业务实体 |
| `internal/repository` | 数据访问层 | 封装数据库操作,实现 CRUD |
| `internal/service` | 业务逻辑层 | 核心业务逻辑,编排 Repository |
| `internal/websocket` | WebSocket 实时推送 | Ansible 任务日志实时推送 |
| `internal/router` | 路由配置 | 定义 API 路由和处理器映射 |
| `pkg/ldap` | LDAP 客户端 | 封装 LDAP 查询和认证 |
| `pkg/sso` | SSO 处理 | OAuth 2.0(主系统↔子系统)+ SAML(主系统↔LDAP) |
| `pkg/database` | 数据库连接 | MySQL 连接池管理 |
### 前端模块
| 模块 | 职责 | 说明 |
|------|------|------|
| `api/` | API 请求 | 封装后端接口调用,统一错误处理 |
| `components/` | 通用组件 | 可复用的 UI 组件 |
| `composables/` | 组合式函数 | 封装可复用的逻辑(认证) |
| `layouts/` | 页面布局 | 定义页面整体布局结构 |
| `router/` | 路由配置 | 定义前端路由 |
| `stores/` | 状态管理 | Pinia 全局状态管理 |
| `views/` | 页面视图 | 具体页面实现 |
| `utils/` | 工具函数 | 通用工具函数 |
---
## 错误码定义
### HTTP 状态码
| 状态码 | 说明 |
|--------|------|
| 200 | 成功 |
| 400 | 请求参数错误 |
| 401 | 未认证(Token 缺失或无效) |
| 403 | 无权限访问 |
| 404 | 资源不存在 |
| 500 | 服务器内部错误 |
### 业务错误码
| 错误码 | 说明 |
|--------|------|
| 10001 | LDAP 认证失败 |
| 10002 | 用户名或密码错误 |
| 10003 | Token 已过期 |
| 10004 | 子系统未配置 SSO |
| 10005 | SSO 跳转生成失败 |
| 20001 | Ansible 任务创建失败 |
| 20002 | Ansible 任务执行超时 |
### 错误响应格式
```json
{
"code": 10001,
"message": "LDAP 认证失败",
"details": "连接 LDAP 服务器超时"
}
```
---
## 安全设计
### 敏感配置管理
| 配置项 | 存储方式 | 说明 |
|--------|----------|------|
| JWT Secret | K8s Secret | 通过环境变量注入 |
| LDAP Bind Password | K8s Secret | 通过环境变量注入 |
| DB Password | K8s Secret | 通过环境变量注入 |
| Redis Password | K8s Secret | 通过环境变量注入 |
| OAuth2 Client Secret | K8s Secret | 各子系统的 OAuth2 密钥 |
### 安全措施
- **传输加密**:所有 API 通信强制 HTTPS
- **Token 安全**:JWT 使用 RS256 签名,过期时间 1 小时
- **密码策略**:LDAP 统一管理,本地账户密码 bcrypt 加密
- **审计日志**:所有敏感操作记录审计日志
---
## 接口定义
### 1. 认证模块 API
#### POST /api/v1/auth/login
```go
// 请求
{
"username": "string", // 用户名
"password": "string" // 密码
}
// 响应
{
"code": 0,
"message": "success",
"data": {
"token": "string", // JWT Token
"expires_in": 3600, // 过期时间(秒)
"user": {
"id": 1,
"username": "string",
"display_name": "string",
"email": "string",
"business_line": "kodo" // 业务线
}
}
}
```
#### POST /api/v1/auth/logout
```go
// 请求头
Authorization: Bearer <token>
// 响应
{
"code": 0,
"message": "success"
}
```
#### GET /api/v1/auth/userinfo
```go
// 请求头
Authorization: Bearer <token>
// 响应
{
"code": 0,
"message": "success",
"data": {
"id": 1,
"username": "string",
"display_name": "string",
"email": "string",
"business_line": "kodo"
}
}
```
### 2. 子系统模块 API
#### GET /api/v1/subsystems
```go
// 响应
{
"code": 0,
"message": "success",
"data": [
{
"id": 1,
"name": "Wayne",
"description": "多集群容器管理平台",
"icon": "wayne.svg",
"url": "https://wayne.qiniu.com",
"status": "integrated", // integrated / integrating
"sso_enabled": true
},
{
"id": 2,
"name": "CloudDM",
"description": "数据库管理与SQL审核",
"icon": "clouddm.svg",
"url": "https://clouddm.qiniu.com",
"status": "integrated",
"sso_enabled": true
},
{
"id": 3,
"name": "CacheCloud",
"description": "Redis 云管理平台",
"icon": "cachecloud.svg",
"url": "https://cachecloud.qiniu.com",
"status": "integrated",
"sso_enabled": true
},
{
"id": 4,
"name": "Apollo",
"description": "配置中心",
"icon": "apollo.svg",
"url": "https://apollo.xinfra.internal",
"status": "integrated",
"sso_enabled": true
},
{
"id": 5,
"name": "qpass",
"description": "密码管理平台",
"icon": "qpass.svg",
"url": "https://qpass.xinfra.internal",
"status": "integrated",
"sso_enabled": true
},
{
"id": 6,
"name": "Grafana",
"description": "监控可视化平台",
"icon": "grafana.svg",
"url": "https://grafana.xinfra.internal",
"status": "integrated",
"sso_enabled": true
}
]
}
```
#### GET /api/v1/subsystems/:id/sso-url
```go
// 响应
{
"code": 0,
"message": "success",
"data": {
"sso_url": "https://wayne.qiniu.com/sso/callback?code=xxx&state=yyy",
"expires_in": 300
}
}
```
### 3. 审计模块 API
#### GET /api/v1/audit/login
```go
// 查询参数
?user_id=1&start_time=2024-01-01T00:00:00Z&end_time=2024-01-31T23:59:59Z&page=1&page_size=20
// 响应
{
"code": 0,
"message": "success",
"data": {
"total": 100,
"items": [
{
"id": 1,
"user_id": 1,
"username": "zhangsan",
"login_time": "2024-01-15T10:30:00Z",
"source_ip": "10.0.0.1",
"target_system": "Wayne",
"status": "success"
}
]
}
}
```
#### GET /api/v1/audit/operations
```go
// 查询参数
?user_id=1&operation_type=ansible&page=1&page_size=20
// 响应
{
"code": 0,
"message": "success",
"data": {
"total": 50,
"items": [
{
"id": 1,
"user_id": 1,
"username": "zhangsan",
"operation_type": "ansible",
"operation": "执行 playbook: node-join",
"target": "node-10.0.0.5",
"status": "success",
"created_at": "2024-01-15T10:35:00Z"
}
]
}
}
```
### 4. Ansible 调度模块 API
#### POST /api/v1/tasks/ansible/execute
```go
// 请求
{
"playbook": "node-join.yml",
"targets": ["node-10.0.0.5", "node-10.0.0.6"],
"extra_vars": {
"cluster": "prod"
}
}
// 响应
{
"code": 0,
"message": "success",
"data": {
"task_id": "task-uuid-xxx",
"status": "running"
}
}
```
#### GET /api/v1/tasks/:id
```go
// 响应
{
"code": 0,
"message": "success",
"data": {
"task_id": "task-uuid-xxx",
"playbook": "node-join.yml",
"targets": ["node-10.0.0.5", "node-10.0.0.6"],
"status": "running", // pending / running / success / failed
"created_at": "2024-01-15T10:35:00Z",
"started_at": "2024-01-15T10:35:01Z",
"finished_at": null
}
}
```
#### WebSocket /ws/tasks/:id/logs
```
// 实时推送 Ansible 执行日志
// 消息格式:
{
"type": "log",
"data": {
"timestamp": "2024-01-15T10:35:02Z",
"host": "node-10.0.0.5",
"task": "Gathering Facts",
"status": "ok",
"message": "ok: [node-10.0.0.5]"
}
}
```
---
## 数据库设计
### 核心表结构
```sql
-- 用户表
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(64) NOT NULL UNIQUE,
display_name VARCHAR(128),
email VARCHAR(128),
password_hash VARCHAR(256),
business_line VARCHAR(32),
ldap_dn VARCHAR(256),
status ENUM('active', 'disabled') DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 子系统表
CREATE TABLE subsystems (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(64) NOT NULL,
description TEXT,
icon VARCHAR(128),
url VARCHAR(256),
status ENUM('integrated', 'integrating') DEFAULT 'integrating',
sso_enabled BOOLEAN DEFAULT FALSE,
sort_order INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 登录审计表
CREATE TABLE login_audit (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT,
username VARCHAR(64),
login_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
source_ip VARCHAR(64),
target_system VARCHAR(64),
status ENUM('success', 'failed') DEFAULT 'success',
INDEX idx_user_id (user_id),
INDEX idx_login_time (login_time)
);
-- 运维操作审计表
CREATE TABLE ops_audit (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT,
username VARCHAR(64),
operation_type VARCHAR(32),
operation TEXT,
target VARCHAR(256),
status ENUM('success', 'failed', 'running') DEFAULT 'running',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_created_at (created_at)
);
-- Ansible 任务表
CREATE TABLE ansible_tasks (
id VARCHAR(64) PRIMARY KEY,
user_id BIGINT,
username VARCHAR(64),
playbook VARCHAR(128),
targets JSON,
extra_vars JSON,
status ENUM('pending', 'running', 'success', 'failed') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
started_at TIMESTAMP NULL,
finished_at TIMESTAMP NULL,
INDEX idx_user_id (user_id),
INDEX idx_status (status)
);
```
---
## 关键模块实现要点
### 1. 认证模块 (auth)
**后端实现**:
```go
// internal/service/auth.go
type AuthService struct {
userRepo repository.UserRepository
ldapClient *ldap.Client
auditService *AuditService
}
// Login 用户登录
func (s *AuthService) Login(username, password string) (*LoginResponse, error) {
// 1. 尝试 LDAP 认证
user, err := s.ldapClient.Authenticate(username, password)
if err != nil {
// 2. 降级到本地数据库认证
user, err = s.userRepo.FindByUsername(username)
if err != nil {
return nil, err
}
if !s.checkPassword(password, user.PasswordHash) {
return nil, ErrInvalidCredentials
}
}
// 3. 生成 JWT Token(无状态,不存储 session)
token, err := s.generateToken(user)
if err != nil {
return nil, err
}
// 4. 记录登录审计
s.auditService.RecordLogin(user.ID, username, "main", "success")
return &LoginResponse{
Token: token,
ExpiresIn: 3600,
User: user,
}, nil
}
```
**前端实现**:
```typescript
// frontend/src/composables/useAuth.ts
export function useAuth() {
const authStore = useAuthStore()
const login = async (username: string, password: string) => {
const response = await authApi.login({ username, password })
authStore.setToken(response.token)
authStore.setUser(response.user)
return response
}
const logout = async () => {
await authApi.logout()
authStore.clearAuth()
router.push('/login')
}
const checkAuth = () => {
const token = authStore.token
if (!token) {
router.push('/login')
return false
}
return true
}
return { login, logout, checkAuth }
}
```
### 2. SSO 跳转模块(OAuth 2.0)
**后端实现**:
```go
// internal/service/subsystem.go
type SubsystemService struct {
subsystemRepo repository.SubsystemRepository
oauth2Client *sso.OAuth2Client
}
// GetSSOURL 获取子系统 SSO 跳转 URL(OAuth 2.0 Authorization Code)
func (s *SubsystemService) GetSSOURL(subsystemID int64, user *model.User) (string, error) {
subsystem, err := s.subsystemRepo.FindByID(subsystemID)
if err != nil {
return "", err
}
if !subsystem.SSOEnabled {
return subsystem.URL, nil
}
// 生成 OAuth 2.0 Authorization Code
code, state, err := s.oauth2Client.GenerateAuthCode(subsystem, user)
if err != nil {
return "", err
}
// 构建 SSO 跳转 URL
ssoURL := fmt.Sprintf("%s/sso/callback?code=%s&state=%s",
subsystem.URL, code, state)
// 记录 SSO 跳转审计
s.auditService.RecordLogin(user.ID, user.Username, subsystem.Name, "success")
return ssoURL, nil
}
```
**前端实现**:
```typescript
// frontend/src/components/SubsystemCard.vue
<template>
<el-card class="subsystem-card" @click="handleClick">
<div class="card-content">
<el-icon :size="48">
<component :is="system.icon" />
</el-icon>
<h3>{{ system.name }}</h3>
<p>{{ system.description }}</p>
<el-tag :type="system.status === 'integrated' ? 'success' : 'warning'">
{{ system.status === 'integrated' ? '已接入' : '改造中' }}
</el-tag>
</div>
</el-card>
</template>
<script setup lang="ts">
const props = defineProps<{
system: Subsystem
}>()
const handleClick = async () => {
if (props.system.sso_enabled) {
const { data } = await subsystemApi.getSSOUrl(props.system.id)
window.open(data.sso_url, '_blank')
} else {
window.open(props.system.url, '_blank')
}
}
</script>
```
### 3. Ansible 调度模块
**后端实现**:
```go
// internal/service/ansible.go
type AnsibleService struct {
taskRepo repository.TaskRepository
auditService *AuditService
wsHub *websocket.Hub
}
// ExecutePlaybook 执行 Ansible Playbook
func (s *AnsibleService) ExecutePlaybook(req *ExecuteRequest, user *model.User) (*AnsibleTask, error) {
task := &AnsibleTask{
ID: generateUUID(),
UserID: user.ID,
Username: user.Username,
Playbook: req.Playbook,
Targets: req.Targets,
ExtraVars: req.ExtraVars,
Status: "pending",
}
if err := s.taskRepo.Create(task); err != nil {
return nil, err
}
// 异步执行 Ansible
go s.runPlaybook(task)
return task, nil
}
// runPlaybook 异步执行并推送日志
func (s *AnsibleService) runPlaybook(task *AnsibleTask) {
// 调用 ansible-playbook 命令
// 通过 WebSocket 实时推送日志到前端
// 更新任务状态
}
```
**WebSocket Hub**:
```go
// internal/websocket/hub.go
type Hub struct {
clients map[string]map[*Client]bool
broadcast chan *Message
register chan *Client
unregister chan *Client
}
// SubscribeTaskLogs 订阅任务日志
func (h *Hub) SubscribeTaskLogs(taskID string, client *Client) {
h.register <- &Client{taskID: taskID, conn: client.conn}
}
```
### 4. 审计模块
**后端实现**:
```go
// internal/middleware/audit.go
func AuditMiddleware(auditService *service.AuditService) gin.HandlerFunc {
return func(c *gin.Context) {
// 记录请求开始
startTime := time.Now()
// 处理请求
c.Next()
// 记录操作审计
if c.Request.Method != "GET" {
user, _ := c.Get("user")
if user != nil {
auditService.RecordOperation(
user.(*model.User).ID,
user.(*model.User).Username,
c.Request.Method,
c.Request.URL.Path,
c.ClientIP(),
c.Writer.Status(),
)
}
}
// 记录请求耗时
duration := time.Since(startTime)
log.Printf("Method: %s, Path: %s, Duration: %v",
c.Request.Method, c.Request.URL.Path, duration)
}
}
```
---
## 配置文件
### 后端配置 (config.yaml)
```yaml
server:
host: "0.0.0.0"
port: 8080
mode: "release" # debug / release / test
database:
host: "mysql"
port: 3306
username: "xinfra"
password: "${DB_PASSWORD}"
database: "xinfra"
max_open_conns: 100
max_idle_conns: 10
redis:
host: "redis"
port: 6379
password: "${REDIS_PASSWORD}"
db: 0
ldap:
host: "ldap.qiniu.com"
port: 389
base_dn: "dc=qiniu,dc=com"
bind_dn: "cn=admin,dc=qiniu,dc=com"
bind_password: "${LDAP_PASSWORD}"
jwt:
secret: "${JWT_SECRET}"
expires_in: 3600
subsystems:
- name: "Wayne"
url: "https://wayne.qiniu.com"
sso_enabled: true
- name: "CloudDM"
url: "https://clouddm.qiniu.com"
sso_enabled: true
- name: "CacheCloud"
url: "https://cachecloud.qiniu.com"
sso_enabled: true
- name: "Apollo"
url: "https://apollo.xinfra.internal"
sso_enabled: true
- name: "qpass"
url: "https://qpass.xinfra.internal"
sso_enabled: true
- name: "Grafana"
url: "https://grafana.xinfra.internal"
sso_enabled: true
```
### 前端配置 (vite.config.ts)
```typescript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
},
},
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
build: {
outDir: 'dist',
sourcemap: false,
},
})
```
---
## MVP 验收标准
| # | 验收项 | 验收标准 |
|---|--------|----------|
| 1 | LDAP 登录 | 用户可通过 LDAP 账号密码登录主系统,登录成功后跳转首页 |
| 2 | 本地降级登录 | LDAP 不可用时,支持本地数据库账号密码登录 |
| 3 | 6 个子系统卡片展示 | 首页展示 Wayne、CloudDM、CacheCloud、Apollo、qpass、Grafana 6 个子系统卡片 |
| 4 | SSO 跳转 | 点击子系统卡片,通过 OAuth 2.0 跳转到子系统,免登录 |
| 5 | 登录审计 | 可查询所有用户的登录记录,包括时间、IP、目标系统、结果 |
| 6 | 运维操作审计 | 可查询运维操作记录,包括操作类型、目标、结果 |
| 7 | Ansible 调度基础 | 可执行 Ansible Playbook,实时查看执行日志 |
| 8 | API 错误处理 | 所有 API 返回标准错误码和错误信息 |
---
## 开发任务清单
### Phase 1: 基础框架搭建 (2天)
- [ ] 初始化前后端项目结构
- [ ] 配置开发环境 (Makefile, docker-compose)
- [ ] 实现基础中间件 (Logger, Recovery)
- [ ] 配置数据库连接和迁移
- [ ] 实现统一响应格式
- [ ] 实现错误码定义
### Phase 2: 认证模块 (3天)
- [ ] 实现 LDAP 客户端
- [ ] 实现用户登录/登出 API
- [ ] 实现 JWT Token 生成和验证(无状态)
- [ ] 实现认证中间件
- [ ] 实现 OAuth 2.0 + SAML SSO 协议
- [ ] 实现前端登录页面
- [ ] 实现前端路由守卫
### Phase 3: 子系统导航 (2天)
- [ ] 实现子系统 CRUD API
- [ ] 实现 OAuth 2.0 Authorization Code 生成
- [ ] 实现 SSO 跳转逻辑(6 个子系统)
- [ ] 实现子系统卡片组件
- [ ] 实现子系统导航页面
### Phase 4: 审计模块 (1.5天)
- [ ] 实现审计数据模型
- [ ] 实现审计记录 API
- [ ] 实现审计查询 API
- [ ] 实现审计中间件
- [ ] 实现登录审计页面
- [ ] 实现运维操作审计页面
### Phase 5: Ansible 调度基础模块 + WebSocket (1天)
- [ ] 实现 Ansible 调度服务
- [ ] 实现任务 API
- [ ] 实现 WebSocket 日志推送
- [ ] 实现任务执行日志前端展示
### Phase 6: 部署配置 + 测试 (0.5天)
- [ ] 编写 Dockerfile
- [ ] 编写 Kubernetes 配置(含 Secret 管理)
- [ ] 编写部署文档
- [ ] 集成测试
---
## 技术要点
### 1. 认证流程
```
用户输入用户名密码
↓
主系统后端接收请求
↓
尝试 LDAP 认证
↓ (失败)
降级到本地数据库认证
↓
生成 JWT Token(无状态,不存储 session)
↓
记录登录审计
↓
返回 Token 给前端
↓
前端存储 Token,跳转首页
```
### 2. SSO 跳转流程(OAuth 2.0)
```
用户点击子系统卡片
↓
前端请求 /api/v1/subsystems/:id/sso-url
↓
后端验证用户 Token
↓
生成 OAuth 2.0 Authorization Code
↓
记录 SSO 跳转审计
↓
返回 SSO URL 给前端
↓
前端打开新窗口跳转(子系统通过 Code 换取 Token)
```
**SSO 协议分工**:
- **主系统 ↔ 子系统**:OAuth 2.0 Authorization Code 流程
- **主系统 ↔ LDAP**:SAML 协议