- CronJobManager: 编辑按钮接通 onClick → openEdit(job),弹出编辑对话框预填数据,
复用新建/编辑共用 Dialog(editingId 区分模式),标题/按钮文案动态切换,
保存时根据 editingId 调用 createJob 或 updateJob — 消除占位按钮
- ApprovalFlow: 修复 API 方法名不匹配导致 tsc -b 编译失败
(getInstances→getProcesses, getMyApprovals→getProcesses({status:'pending'}),
approveInstance→approveProcess, rejectInstance→rejectProcess)
+ 添加 ApprovalFlowManager 导出别名兼容 App.tsx 导入
- 合并已完成的大文件拆分重构: automation.go→automation_cron/script/playbook,
ldap.go→ldap_ou/ldap_users, server.go→cloud_accounts/domain/platform/region,
user.go→user_admin; 前端 Dashboard/K8s/Server/Approval/Monitor 子组件拆分
// Logan.Li
8.1 KiB
8.1 KiB
EZOps AIOps — AI 开发全局规范
本文件供 AI 编程助手(Claude/Cursor/Copilot/Hermes)自动读取并遵循。 定义项目架构约束、代码规范、拆分策略和开发流程。 作者: Logan.Li · 语言: 中文(代码注释中文 + 署名
// Logan.Li)
项目概述
EZOps AIOps — AI 智能运维平台,涵盖 13 大模块: 服务器管理 · 云资源(账号/域名/账单) · K8s 集群 · 监控告警(Agent) · CI/CD 流水线 · 自动化运维(CronJob/Script/Ansible) · 审批流程 · AI 智能运维(告警分析/故障诊断/预测/脚本生成) · 导航收藏 · 审计日志 · 系统管理(用户/LDAP) · 监控 Agent(双通道: 指标采集+远程命令)
- 远程仓库: https://gitee.com/attacker/aiops.git
- Git 用户: attacker / attacker@gitee.com
技术栈
| 层 | 技术 | 端口 |
|---|---|---|
| 后端 | Go 1.21 + Gin + GORM + SQLite (+ MySQL/PG 可选) + JWT + Zap | 8080 |
| 前端 | React 19 + TypeScript + Vite + Tailwind + shadcn/ui + Zustand + TanStack Query | 5173 |
| Agent | Go 1.24 (CGO_ENABLED=0 静态二进制, amd64+arm64) | — |
核心规则
⛔ 文件大小红线
| 类型 | 上限 | 超出处理 |
|---|---|---|
| Go handler | 400 行 | 按 sub-domain 拆分到独立文件(同一 package) |
| Go model | 300 行 | 按模块拆分 |
| React 组件 (.tsx) | 700 行 | 提取子组件到同目录,主组件负责编排 |
| TypeScript 工具/服务 | 500 行 | 按功能域拆分 |
| 任何文件 | 700 行(硬上限) | 必须拆分,无例外 |
拆分策略:
- Go: 同 package 下按职责拆文件(如
automation.go→automation_cron.go+automation_script.go+automation_playbook.go),共享 struct 放原文件或新建automation_types.go - React: 子组件提取到同目录独立
.tsx文件(如SystemManager.tsx→SystemManager.tsx+SystemUserTab.tsx+SystemLdapTab.tsx),通过 props 传递数据和回调 - Mock 数据: 按模块拆分(如
data.ts→data/servers.ts+data/cloud.ts+data/k8s.ts+index.ts汇总导出)
⛔ Go 代码规范
- 所有 handler 返回必须用
models.Response{Code:200, Data:...}包裹 — 前端http.ts从json.data取值,裸gin.H{}会导致前端拿到 undefined 静默失败 - Go 编译命令(每次必须):
cd ~/devops/aiops/backend && unset http_proxy https_proxy && export GOPROXY=https://goproxy.cn,direct && GOTOOLCHAIN=local CGO_ENABLED=1 go build -o aiops-server ./cmd/ - 新增 handler 不绑 model — 用独立 request struct 接收
ShouldBindJSON,再映射到 GORM model - GORM 指针字段必须解引用 — model 中
*int/*string字段传给fmt.Sprintf时先*field - SQLite 必须 CGO_ENABLED=1(macOS 默认开)
- 新模块 4 步: Model → Handler → Routes(routes.go 注册) → AutoMigrate(database.go 添加)
⛔ React 前端规范
- API 列表响应解包 — 后端返回
{code, data: {list: T[], total: N}},http.ts 提取.data后仍是{list, total}对象,组件中必须.list提取数组 - 禁止
window.confirm()/alert()— 用ConfirmProvider(useConfirm()hook)替代 confirm,用toast.error()/toast.success()替代 alert - Radix Dialog 必须有
<DialogDescription>— 至少加<DialogDescription className="sr-only"> - Token 不在 localStorage — 用
useUserStore.getState().token(Zustand persist) - 新增组件 2 步: API 层(
services/api.ts添加) → 组件+路由(App.tsx加 Route,Sidebar.tsx加菜单)
⛔ UI 设计原则
- 能去掉的都去掉 — 不要多余边框、高亮框、装饰性分隔线
- 按钮文案 2-4 字 — "批量导入" 而非 "批量导入用户"
- 不留占位按钮 — 功能没做好就不放按钮,不要
toast.info('开发中') - 页面打开即用 — 不需要额外"新建"按钮跳转
- 主题感知 — 用 CSS 变量
bg-[var(--dg-bg-secondary)],不用硬编码颜色 - 表单操作按钮在字段下方 — 用
mt-4 pt-4 border-t分隔,不放 Card 标题右侧 - 主动排查同类问题 — 用户报一个 UI 问题,全局搜索同类一次性修复
⛔ 通用开发流程
分析需求 → 写代码 → go build + tsc --noEmit → ./run.sh restart → 浏览器验证 → git commit + push
- 修改前端后必须在浏览器实际测试,不能只说"刷新试试"
- 提交前编译验证:后端
go build ./...,前端npx tsc --noEmit && npm run build - Git 提交:
git add -A && git commit -m "feat: 描述" && git push origin master - 开发日志: 每次开发后更新
docs/dev-log.md
架构约束
目录结构(核心部分)
backend/
├── cmd/main.go + seed.go # 入口 + 种子数据
├── internal/
│ ├── config/config.go # Viper 配置
│ ├── models/ # GORM 模型(每模块独立文件)
│ └── api/
│ ├── handlers/ # HTTP handler(按模块拆分,每文件 <400 行)
│ ├── middleware/auth.go # JWT + Admin 中间件
│ └── routes/routes.go # 路由注册
├── pkg/database/database.go # DB 连接 + AutoMigrate
└── scripts/mock.go # Mock 种子
frontend/
├── src/
│ ├── components/{模块}/ # 页面组件(每文件 <700 行,超出拆子组件)
│ ├── services/{api.ts, http.ts} # API 服务层
│ ├── store/ # Zustand 状态管理
│ ├── hooks/ # React hooks
│ └── types/index.ts # 全局类型定义
├── App.tsx # 路由 + 顶层 Provider
agent/ # 监控 Agent(独立 Go module)
├── main.go # CLI 入口
├── collector/ # /proc 采集器
├── task/ # 远程任务执行器
└── reporter/http.go # HTTP 上报
API 响应格式
// 统一响应(所有 handler 必须遵循)
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
// 列表响应(Data 内层)
type ListResponse struct {
List interface{} `json:"list"`
Total int64 `json:"total"`
}
认证流程
Login(本地/LDAP fallback) → JWT Token → 前端 Zustand persist 存储 → http.ts 自动注入 Bearer
→ JWTAuth 中间件验证 → 需 admin 权限的路由额外 AdminAuth 中间件
Agent 双通道架构
指标通道: ticker(10s) → collect(/proc) → POST /agent/report
任务通道: ticker(5s) → GET /agent/tasks/poll → execute → POST /agent/tasks/:id/result
主题系统
5 套主题(CSS 变量驱动): ocean-deep(默认暗色) · light-professional(亮色) · midnight-purple · forest-green · dark-rose
所有组件必须用 var(--dg-xxx) 变量,禁止硬编码颜色值。
常见陷阱速查
| # | 陷阱 | 解决 |
|---|---|---|
| Go1 | go build 超时 | unset proxy + GOPROXY=goproxy.cn + GOTOOLCHAIN=local |
| Go2 | handler 不包裹 Response | 必须 models.Response{Code:200, Data:...} |
| Go3 | GORM 指针字段 | *field 解引用后再用 |
| Go4 | mock.go 重运行 | UNIQUE constraint → 用 API 注入新数据 |
| FE1 | 白屏无堆栈 | 90% 是列表 {list,total} 未解包 |
| FE2 | confirm() 冻结自动化 | 用 useConfirm() |
| FE3 | Token 获取 | useUserStore.getState().token,不是 localStorage |
| FE4 | Radix Dialog 警告 | 必须有 <DialogDescription> |
| FE5 | browser_click 不触发 toggle | 改用 browser_console 注入 .click() |
文件大小监控
# 检查超限文件(CI 检查脚本)
find backend -name "*.go" -not -path "*/vendor/*" | xargs wc -l | awk '$1 > 400 && $2 != "total"'
find frontend/src -name "*.tsx" -o -name "*.ts" | xargs wc -l | awk '$1 > 700 && $2 != "total"'