Files
LokiStack/docs/web-ui-guide/02-项目架构与目录结构.md
logan f9c6c1d826 feat: opsx-loki 全面升级 — 前端重构/用户与LDAP/部署向导/日志查询
基于 WEB-UI-Development-Guide 标准的整体升级,并更名 LokiDeploy → opsx-loki。

前端(Gin+React+shadcn/ui):
- 按 02/07 章重构:页面域化拆分(无超 400 行文件)、PageContainer/ListQuery、
  反馈分级(成功静默/失败 toast/危险确认)、401 单飞刷新+队列重放
- 暗色模式(next-themes)+ 语义 Token(--primary 品牌色/success/warning/info)
- 部署向导(状态推导四步:环境→存储→Loki→采集,内嵌表单闭环)
- 日志查询页:LogQL 标签构建器/拖拽直方图/日志上下文/Live Tail/查询历史
- 按用户+标签策略的查询授权(服务端强制合并策略)

后端(Go+Gin+JSON DB):
- 简单用户管理(仅 admin,无 RBAC)+ TokenVersion 会话吊销 + 登录 IP 限流
- LDAP 接入:配置(AES 加密存储/hasPassword 脱敏)/测试连接/批量导入/登录
- 存储配置测试连接(SigV4 零依赖)+ 桶不存在自动创建
- Loki 三模式部署修复:SSH 密钥认证、3.x 配置模板、ruler 对象存储嵌套块、
  凭证解密、孤儿 deploying 状态恢复、Grafana 部署(可选默认关)+面板导入
- DaemonSet 采集纯 YAML(去 helm 依赖)+ DNS 自愈回退 ClusterIP + 集群名标签
- 日志查询代理:cluster-svc 经 kube-apiserver proxy/url 直连 + cursor 翻页
- 统一 log 命名空间;部署就绪探活;Grafana 访问地址自动记录

修复:run.sh 增量编译检测、Sanitized 脱敏补全、LokiDeployment Update 白名单、
健康检查 CreatedAt 零值等 10+ 存量 bug。

数据兼容:DB 沿用旧 lokideploy.json(新名优先);localStorage 一次性迁移;
HKDF 加密前缀保留(已加密凭证不受影响)。
2026-10-24 13:20:12 +08:00

12 KiB
Raw Permalink Blame History

02 — 项目架构与目录结构

参考:vue-vben-admin(三层包划分)、soybean-admin(文件路由 + store 纯函数分离)、ant-design-pro(页面 co-location)、vue-pure-admin(页面内聚五件套);补充:gin-vue-admin(分域 api/view)


一、组织目录的三种流派

1.1 流派对照

流派 代表 结构 优点 代价
按层分(layer-first) 早期中台 components/ views/ api/ stores/ 每层内平铺全部业务 上手简单 业务散在 5 个目录,删模块要全局找
按域分(domain-first) 本标准(Vue 单包)/ gva 层目录保留,但 api/user/、views/user/、stores/ 域内聚合 删域=删目录,心智清晰 需要约定维持层内域目录一致
按功能分包(feature-first) adp 页面级、vben 包级 页面目录自带 service/mock/类型/样式;框架能力独立成包 高内聚,可整体搬移 需要晋升机制防止复制泛滥

结论:中小工程用「按域分 + 页面 co-location」混合式——全局按域(api/views 域目录对齐),页面私有代码留在页面目录内。

1.2 四标杆的目录精髓

vue-vben-admin(依赖方向三层,分层标杆)
apps/* ──▶ packages/effects/* ──▶ packages/@core/*
(app 是唯一允许直接依赖具体 UI 库的层;
 @core 零 UI 依赖零业务;effects 通过 adapter 适配 UI 生态)

ant-design-pro(页面五件套 co-location)
src/pages/table-list/
├── index.tsx          # 页面(ProTable 声明式列配置)
├── service.ts         # 本页接口(手写层)
├── data.d.ts          # 本页类型
├── _mock.ts           # 本页 mock(config 显式 include)
└── components/
    ├── CreateForm.tsx # 新建弹窗(trigger 模式)
    └── UpdateForm.tsx # 编辑弹窗(StepsForm 进 Modal)

soybean-admin(生成物 + store 纯函数分离,★★★★☆)
src/
├── router/elegant/        # 文件路由生成物(routes/transform/imports + RouteKey 类型 d.ts)
├── service/{api,request}/ # api=纯请求函数、request=实例拦截器,两层分离
├── store/modules/<mod>/   # 每模块 index.ts(状态编排) + shared.ts(可单测纯函数)
├── typings/{api,...}      # 全局 namespace 类型(Api/App/Storage/Env)
└── views/_builtin/        # 403/404/500/login 内置页统一收纳,业务页平级

vue-pure-admin(页面内聚五件套,★★★★☆)
src/views/system/user/
├── index.vue              # 纯模板(零逻辑,只解构 hook)
├── form/index.vue         # 弹窗表单
├── tree.vue               # 左树
└── utils/{hook.tsx,rule.ts,types.ts}  # 逻辑/校验/类型全在本域

gin-vue-admin(补充:全局分域 + 指令/钩子独立)
src/
├── api/            # 一域一文件:user.js menu.js ...
├── pinia/modules/  # 一域一 store
├── view/superAdmin/  view/example/   # 按业务分组
├── directive/auth.js                  # v-auth 指令独立目录
├── hooks/responsive.js                # 组合式函数独立目录
└── plugin/<name>/                    # 业务插件 = 自带 view/api/assets 的小应用

二、标准目录结构(Vue 工程)

2.1 目标结构

src/
├── main.js                  # 入口:仅装配(EP按需、pinia、router、指令、全局组件)
├── App.vue
├── api/                     # ★ 按功能域分目录,与 views 域目录同名
│   ├── auth/                #   captcha.js mfa.js
│   ├── user/                #   users.js roles.js permissions.js
│   ├── rbac/                #   域小可单文件,大了再拆
│   ├── system/              #   menu.js modules.js logs.js ...
│   └── <domain>/            #   业务域
├── views/                   # ★ 域目录与 api/ 一一对应
│   ├── user/                #   Users.vue + components/XxxFormDialog.vue ...
│   ├── <domain>/
│   └── _builtin/            #   403/404/500/login 内置页统一收纳(soybean 范式)
├── stores/                  # Pinia:一域一文件(auth.js menu.js site.js ...)
├── composables/             # useXxx(依赖业务上下文的钩子)
├── components/              # ★ 全局组件(需晋升,见第四章)
├── layout/                  # 布局骨架(业务页只挂内容区,禁止重写布局)
├── router/                  # index.js(含守卫) modules/(路由分文件)
├── config/                  # theme.js modules.js(纯配置,无副作用导入)
├── typings/                 # 全局契约类型(TS 渐进路线,soybean 四区)
├── utils/                   # request.js permission.js(无状态工具)
└── styles/                  # 全部文件必须有入口引用链

React 工程同构映射:views→pages、composables→hooks、stores→store(zustand slices)、components→components(含 barrel 出口)。

2.2 层内规则(必须)

  1. api 域目录与 views 域目录强制同名(api/cron/ ↔ views/cron/);禁止在 api 根级别放散文件
  2. store 文件名 = 导出名(stores/auth.js → useAuthStore);单 store ≤300 行;推荐 soybean 的「index + shared」目录式(状态编排与纯函数分离)
  3. layout/ 是禁改区:业务页通过页面容器组件的 header/slot 定制,不碰布局骨架(可裁剪底座的前提)
  4. components/ 只放「晋升组件」(见第四章晋升机制),新页面私有组件一律 views/<域>/components/

2.3 可裁剪分层与模块开关(底座/脚手架推荐)

┌─────────────────────────────────────────────────────┐
│ 壳层 shell      登录/MFA/403/500 —— 不挂主布局       │
├─────────────────────────────────────────────────────┤
│ 系统层 system   用户/RBAC/审计/设置 —— 底座自带      │
├─────────────────────────────────────────────────────┤
│ 示例层 examples 演示模块 —— fork 后可整层删除        │
└─────────────────────────────────────────────────────┘
  • 路由 meta.layer: 'shell' | 'system' | 'examples' 声明归属
  • 运行时开关:源真相在后端(如 GET/PUT /api/system/modules),localStorage 仅作缓存——禁止把 localStorage 当源真相
  • 构建期裁剪:环境变量(如 VITE_ENABLE_EXAMPLES=false)+ 路由中字面量比较,保证打包器 tree-shake 掉示例 chunk
  • 示例层代码不得 import 系统层内聚单元,保证整层可摘除;示例域附删除指引文档(EXAMPLES.md 惯例)

分层是目录结构之外的第二根轴:每个新模块必须先回答「我在哪一层」。

2.4 命名规范与类型目录

对象 规范 示例
组件文件 PascalCase UserFormDialog.vue
工具 / composable camelCase,composable 带 use 前缀 request.js、usePagedQuery.js
目录 kebab-case(views 域目录用业务域名) user-management/、cron/
store 文件 文件名与导出同名 stores/auth.js → useAuthStore
API 文件 资源名复数 api/user/users.js
常量 UPPER_SNAKE_CASE SYSTEM_ORDER

类型目录(TS 渐进路线,soybean 范式):全局契约类型集中 typings/(Api / App / Storage / Env 四区 namespace,storage 与 env 均键级类型);页面私有类型随页面(data.d.ts / utils/types.ts);禁止同一类型定义在多个组件内重复出现。


三、页面 co-location(adp 五件套)

3.1 什么时候页面自带 service

全局 api/<域>/xxx.js     # 多页面复用、域级语义(users.js 的 list/create/delete)
页面内 service.ts        # 只服务本页的接口(dashboard 聚合、报表导出)

判定:接口第二个消费者出现时,从页面 service.ts 提升到 api/<域>/。反向不成立——不要预防性提升。

3.2 页面目录模板

views/<domain>/<Page>.vue          # 页面骨架:容器 + 列表组件 + 子组件编排(≤400 行)
views/<domain>/components/         # 本页私有组件(表单弹窗、详情抽屉、单元格渲染器)
views/<domain>/composables/        # 本页私有逻辑(可选,超过 2 个页面组件共享时建)
views/<domain>/utils/              # 本页 hook/rule/types(pure 范式,可选)
views/<domain>/EXAMPLES.md         # 仅示例层:删除指引

四、公共组件晋升机制

第 1 次使用:写在页面 components/ 里
第 2 次使用:复制一份,页面内注明来源        ← 允许的重复
第 3 次使用:晋升 src/components/,删除两份复制 ← 必须执行

晋升时同步做三件事:

  1. 去业务化:props 收口(fetcher/permissions 进来,具体 API 调用出去)
  2. 注册出口:Vue 端全局组件在 main.js 注册;React 端进 components/index.ts barrel(adp:按「布局组件 / 业务组件」分组导出)
  3. 写使用说明:组件头部注释 3 行(用途/必需 props/已知限制)

必备的两个全局页面组件(详见 05/07 章):PageContainer(页面容器:页头/面包屑/slot)与 ListPage(分页列表:fetcher + 筛选 slot + 表格透传 + 分页)。


五、架构守护:让目录约定不被腐蚀

守护手段 出处 可落地做法
循环依赖扫描 vben check:circular npx madge --circular src/ 进 CI
依赖方向检查 vben check:dep(@core 禁依赖 UI 库) 示例层禁 import 系统层:ESLint no-restricted-imports 按路径分组
死代码检测 adp coverage 排除生成物 CI 跑 knip/depcheck,styles 无入口文件直接报错
生成物隔离 adp services/ant-design-pro/(OpenAPI 生成,禁手改) 生成物独立目录 + lint ignore
路由自动收集 pure router/modules/ + import.meta.glob 新增路由文件零注册代码

六、最佳实践清单

✅ 要做

  1. api 域目录与 views 域目录同名;新域从脚手架起步(自动生成五处对照:菜单/路由/页面/API/权限码)
  2. 页面私有组件不出页面目录;晋升走「三次法则」
  3. 每个新模块先声明 meta.layer,示例层保持可整层摘除
  4. store 按域拆分、文件名=导出名、单 store ≤300 行
  5. 目录结构变化(新增域/晋升组件/删示例)在同一 PR 内更新架构文档

❌ 不要

  1. 不要在 api 根级别放散文件
  2. 不要把页面私有组件直接丢进 src/components/
  3. 不要让业务页 import layout 内部组件(只准用 slot 协议)
  4. 不要建「future/」「temp/」「new/」这类无语义目录
  5. 不要保留无入口引用的样式文件和空目录

七、AI 检查清单(对比目标工程逐项核对)

P0

  1. api 与 views 域目录是否同名对齐?有无 api 根级散文件?
  2. store 文件名与导出名是否一致?有无超 300 行的 store?
  3. 有无空目录、无引用样式、描述已不存在目录的文档残留?

P1

  1. 页面骨架是否 ≤400 行?超 400 行的视图列出拆分建议(参照 07 章)
  2. 私有组件是否越界进全局 components/?全局组件是否都有注册出口与头注释?
  3. 若为可裁剪底座:meta.layer 是否全覆盖?模块开关源真相是否在后端?
  4. CI 是否有循环依赖与死代码检查?

P2

  1. 是否值得引入路由文件自动收集(router/modules/ + glob)或文件路由(soybean @elegant-router)?
  2. typings/ 全局类型目录是否建立(Api/App/Storage/Env 四区)?

下一篇:03-请求层与数据流 —— 前端工程的血管系统。