698dddf5d1
- 添加 CLAUDE.md 开发指南,规定 MVP 实现阶段文档阅读优先级 - 添加 MVP 方案文档,定义项目骨架结构和模块划分 - 添加原型文档参考 (xinfra-prototype.v3.html)
32 KiB
32 KiB
MVP 方案文档
概述
本文档定义 XINFRA MVP 阶段的代码骨架结构、模块划分、接口定义和技术实现要点。
技术栈锁定:
- 后端: Go 1.21+ / Gin
- 前端: Vue 3.3+ / Element Plus / Vite
- 数据库: MySQL 8.0
- 缓存: Redis
- 部署: K8s + Nginx
MVP 范围:
- 认证(LDAP + SAML + OAuth 2.0)
- 统一子系统导航(卡片展示 + OAuth 2.0 跳转)
- 审计面板(登录审计 + 主系统 Ansible 运维操作审计)
- 子系统对接(仅 OAuth 2.0 跳转,主系统后端向前端返回 Mock 数据)
- 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 任务执行超时 |
错误响应格式
{
"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
// 请求
{
"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
// 请求头
Authorization: Bearer <token>
// 响应
{
"code": 0,
"message": "success"
}
GET /api/v1/auth/userinfo
// 请求头
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
// 响应
{
"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
// 响应
{
"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
// 查询参数
?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
// 查询参数
?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
// 请求
{
"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
// 响应
{
"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]"
}
}
数据库设计
核心表结构
-- 用户表
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)
后端实现:
// 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
}
前端实现:
// 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)
后端实现:
// 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
}
前端实现:
// 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 调度模块
后端实现:
// 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:
// 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. 审计模块
后端实现:
// 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)
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)
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 协议