# MVP 方案文档 ## 概述 本文档定义 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 // 响应 { "code": 0, "message": "success" } ``` #### GET /api/v1/auth/userinfo ```go // 请求头 Authorization: Bearer // 响应 { "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 ``` ### 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 协议