0e29aa2dc1
变更内容: - 新增 AGENTS.md:为 Codex 等 AI 工具提供项目开发指南 - 更新 CLAUDE.md:优化文档阅读层级结构 - 新增 docs/ref/平台需求文档-v3.md:平台整体需求和功能范围 - 新增 docs/ref/架构文档-v3.md:系统架构设计和技术选型 文档阅读层级(P1-P4): - P1: MVP 方案文档(优先阅读) - P2: 原型文档(交互细节) - P3: 架构文档(整体设计) - P4: 需求文档(需求范围) 只有需要时才往下阅读下一层级文档。
1009 lines
32 KiB
Markdown
1009 lines
32 KiB
Markdown
## 概述
|
||
|
||
本文档定义 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 协议
|
||
|
||
|
||
|
||
|