Files
aiops/CLAUDE.md
T
attacker a7e2351ad2 feat: 定时任务编辑入口全栈打通 + 修复ApprovalFlow编译错误 + 完成大文件拆分重构
- 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
2026-06-15 01:09:49 +08:00

8.1 KiB
Raw Blame History

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(双通道: 指标采集+远程命令)

技术栈

层 技术 端口
后端 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 代码规范

  1. 所有 handler 返回必须用 models.Response{Code:200, Data:...} 包裹 — 前端 http.ts 从 json.data 取值,裸 gin.H{} 会导致前端拿到 undefined 静默失败
  2. 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/
    
  3. 新增 handler 不绑 model — 用独立 request struct 接收 ShouldBindJSON,再映射到 GORM model
  4. GORM 指针字段必须解引用 — model 中 *int/*string 字段传给 fmt.Sprintf 时先 *field
  5. SQLite 必须 CGO_ENABLED=1(macOS 默认开)
  6. 新模块 4 步: Model → Handler → Routes(routes.go 注册) → AutoMigrate(database.go 添加)

⛔ React 前端规范

  1. API 列表响应解包 — 后端返回 {code, data: {list: T[], total: N}},http.ts 提取 .data 后仍是 {list, total} 对象,组件中必须 .list 提取数组
  2. 禁止 window.confirm()/alert() — 用 ConfirmProvider(useConfirm() hook)替代 confirm,用 toast.error()/toast.success() 替代 alert
  3. Radix Dialog 必须有 <DialogDescription> — 至少加 <DialogDescription className="sr-only">
  4. Token 不在 localStorage — 用 useUserStore.getState().token(Zustand persist)
  5. 新增组件 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"'