From 77adc8e498f6323e69b5a1f2341a6eda77947ca5 Mon Sep 17 00:00:00 2001 From: wsh5485 Date: Sat, 20 Jun 2026 22:18:23 +0800 Subject: [PATCH] docs: initial README + ARCHITECTURE.md + .gitignore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit i 仓库第一版: - README.md: 项目简介 + 架构图 + W1-W3 路线图 - docs/ARCHITECTURE.md: 单 SPA + 单库 + 表前缀方案 (10 节) - .gitignore: 完整覆盖 CarLog 仓库(独立)保持 v2.8 + 精简 README,不动。 架构核心决策: - 单 Vue SPA + 单 Express 进程 + 单 MySQL - 表前缀分多子系统 (carlog_ / fitness_ / reading_) - 元数据驱动 UI (subsystems 表的 settings_schema + nav_items) - 永远单用户 — 不要 RBAC / 多租户 / JWT / iframe --- .gitignore | 47 +++++++ README.md | 91 +++++++++++++ docs/ARCHITECTURE.md | 298 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 436 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 docs/ARCHITECTURE.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..86824e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +# Dependencies +node_modules/ + +# Build output +client/dist/ +server/dist/ + +# Environment +.env +.env.local +.env.*.local + +# Database / uploads +server/data/ +server/storage/ +uploads/ +*.sqlite +*.sqlite-journal + +# Testing +coverage/ +.lighthouseci/ +.dbg/ + +# OS / IDE +.DS_Store +Thumbs.db +.vscode/ +.idea/ +*.swp +*.swo + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Setup marker +.setup_done + +# Local artifacts +*.zip +*.tar.gz + +# Mavis +.mavis/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7cbd268 --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ +# i — 个人生活操作系统 + +一个**单 Vue + 单 Express + 单 MySQL** 的生活操作系统平台,按表前缀分多子系统,永远单用户。 + +第一个子系统是 [CarLog](https://gitea.img2img.com/wsh5485/CarLog)(洗车管理系统),后续要加:健身、阅读、健康等。 + +## 架构核心 + +``` +┌────────────────────────────────────────────────────────┐ +│ Vue SPA (一个壳子) │ +│ │ +│ ┌──────────────────────────────────────────────────────┐│ +│ │ 总设置 / 子系统管理 / Dashboard (平台层) ││ +│ └──────────────────────────────────────────────────────┘│ +│ │ +│ ┌──────────────────────────────────────────────────────┐│ +│ │ 🚗 CarLog | 💪 Fitness | 📚 Reading ││ +│ └──────────────────────────────────────────────────────┘│ +│ │ +└────────────────────────────────────────────────────────┘ + │ + ▼ +┌────────────────────────────────────────────────────────┐ +│ Express (一个进程) │ +│ ├── /api/platform/* (总设置 / 子系统管理) │ +│ ├── /api/{resource} (业务路由, 按子系统分组) │ +└────────────────────────────────────────────────────────┘ + │ + ▼ +┌────────────────────────────────────────────────────────┐ +│ MySQL @ 162.14.110.130:33306 / carlog │ +│ │ +│ 平台表 (无前缀): │ +│ subsystems / platform_settings │ +│ │ +│ 子系统表 (有前缀): │ +│ carlog_vehicles / carlog_wash_records / ... │ +│ fitness_workouts / fitness_plans / ... │ +│ reading_books / reading_notes / ... │ +└────────────────────────────────────────────────────────┘ +``` + +## 物理隔离靠什么 + +| 维度 | 做法 | +|---|---| +| 数据 | 表前缀 `{subsystem}_*`(同一 DB 内) | +| 路由 | 子系统自己的路径空间(`/api/{resource}`) | +| 代码 | 子系统独立目录(`server/src/subsystems/{name}/`、`client/src/views/subsystems/{name}/`) | +| 设置 | 每个子系统有自己的 settings schema(JSON Schema,存 `platform_settings` 表,key 前缀 `{name}.*`) | +| 菜单 | 每个子系统在 `subsystems` 表注册,平台层根据 `category` 分组渲染左侧导航 | + +**没有** JWT / SSO / iframe / 6 端点协议 / 独立 DB — 那些都是过度设计。 + +## 实施路线 + +### W1: 平台骨架 + +- [ ] `server/migrations/001_platform.sql`(subsystems + platform_settings 表 + seed CarLog) +- [ ] `server/src/routes/platform/settings.js` +- [ ] `server/src/routes/platform/subsystems.js` +- [ ] `server/src/views/Platform/GlobalSettings.vue` +- [ ] `client/src/views/Platform/SubsystemSettings.vue`(通用 JSON Schema 渲染器) +- [ ] `client/src/AppLayout.vue` 改左侧菜单(按 category 分组) + +### W2: CarLog 表前缀迁移 + +- [ ] 把 CarLog 现有 14 张表加 `carlog_` 前缀(migration) +- [ ] 改 server 端所有 SQL +- [ ] 跑全测试套件 + +### W3+: 加第二个真子系统 + +- [ ] 写一个 Fitness 子系统(健身记录),验证端到端流程 + +## 路线原则 + +- **永远单用户**:不要 RBAC / 多租户 / 权限 scope +- **一个进程一个 SPA**:不分部署不分 iframe +- **元数据驱动 UI**:subsystem 的 settings_schema / nav_items 走 JSON,不硬编码 +- **物理目录隔离**:subsystem 代码独立目录,加新子系统不会乱碰现有代码 +- **永远向后兼容**:CarLog 的现有功能不破 + +详细见 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)。 + +## Git 仓库 + +- 仓库:https://gitea.img2img.com/wsh5485/i.git +- 平台:Gitea +- 推送:osxkeychain 自动记住 token \ No newline at end of file diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..a4c14c2 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,298 @@ +# 架构方案:单 SPA + 单库 + 表前缀 + +## 1. 目标 + +i 平台是一个「生活操作系统」:单 Vue SPA + 单 Express 进程 + 单 MySQL,按表前缀分多子系统,永远单用户。 + +- **不引入分布式复杂度**:不分库、不分进程、不分部署 +- **数据隔离靠表前缀**:`carlog_*` / `fitness_*` / `reading_*` / ... +- **元数据驱动 UI**:subsystem 注册到 `subsystems` 表,平台前端通用渲染设置页和导航 +- **永远单用户**:管什么 RBAC / 多租户 + +理由:个人用,数据量小;分库分进程反而增加运维复杂度而没收益。 + +## 2. 顶层架构 + +``` +┌────────────────────────────────────────────────────────┐ +│ Vue SPA (一个壳子) │ +│ │ +│ ┌──────────────────────────────────────────────────────┐│ +│ │ 总设置 / 子系统管理 / Dashboard (平台层) ││ +│ └──────────────────────────────────────────────────────┘│ +│ │ +│ ┌──────────────────────────────────────────────────────┐│ +│ │ 🚗 CarLog | 💪 Fitness | 📚 Reading ││ +│ │ 概览/洗车/加油 | 训练/打卡/计划 | 书/笔记/进度 ││ +│ └──────────────────────────────────────────────────────┘│ +│ │ +└────────────────────────────────────────────────────────┘ + │ axios + cookie session + ▼ +┌────────────────────────────────────────────────────────┐ +│ Express (一个进程) │ +│ ├── /api/platform/* (总设置 / 子系统管理 / Dashboard) │ +│ ├── /api/vehicles (CarLog 路由, 保持现状) │ +│ ├── /api/washes (CarLog) │ +│ ├── /api/fitness/* (将来加的 Fitness 子系统) │ +│ └── /api/reading/* (将来加的 Reading 子系统) │ +└────────────────────────────────────────────────────────┘ + │ + ▼ +┌────────────────────────────────────────────────────────┐ +│ MySQL @ 162.14.110.130:33306 / carlog │ +│ │ +│ 平台表 (无前缀): │ +│ users / sessions / subsystems / platform_settings │ +│ │ +│ CarLog 表: │ +│ carlog_vehicles / carlog_wash_records / carlog_refuels │ +│ carlog_charging_records / carlog_maintenance_records │ +│ carlog_insurance_records / carlog_chemicals │ +│ carlog_weather_snapshots / carlog_operation_logs │ +│ carlog_tags / carlog_achievements / carlog_notifications│ +│ ... │ +│ │ +│ Fitness 表 (将来): │ +│ fitness_workouts / fitness_plans / fitness_measurements│ +│ │ +│ Reading 表 (将来): │ +│ reading_books / reading_notes / reading_progress │ +└────────────────────────────────────────────────────────┘ +``` + +## 3. 物理隔离靠什么 + +| 隔离维度 | 做法 | +|---|---| +| 数据 | 表前缀 `{subsystem}_*`(同一 DB 内) | +| 路由 | 子系统自己的路径空间(`/api/{resource}` 现有;将来 `/api/fitness/*`) | +| 代码 | 子系统独立目录(`server/src/subsystems/{name}/`、`client/src/views/subsystems/{name}/`) | +| 设置 | 每个子系统有自己的 settings schema(JSON Schema,存 `platform_settings` 表,key 前缀 `{name}.*`) | +| 菜单 | 每个子系统在 `subsystems` 表注册,平台层根据 `category` 分组渲染左侧导航 | + +**没有** JWT / SSO / iframe / 6 端点协议 / 独立 DB — 那些都是过度设计。 + +## 4. Subsystem 注册表 + +平台层只管一张元数据表: + +```sql +CREATE TABLE subsystems ( + id VARCHAR(50) PRIMARY KEY, -- 'carlog' / 'fitness' / 'reading' + name VARCHAR(100) NOT NULL, -- 显示名:「洗车管理系统」 + description TEXT, + icon VARCHAR(20), -- emoji 或文件名 + color VARCHAR(20), + category VARCHAR(50), -- vehicle / fitness / finance / reading + version VARCHAR(20), + enabled TINYINT(1) DEFAULT 1, + sort_order INT DEFAULT 0, + settings_schema JSON, -- JSON Schema 描述这个子系统的设置项 + nav_items JSON, -- [{label, icon, path}] + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB; + +INSERT INTO subsystems (id, name, icon, color, category, version, settings_schema, nav_items) VALUES +('carlog', '洗车管理系统', '🚗', '#1B6EF3', 'vehicle', '2.8.0', +'{"fields":[ + {"key":"weather.default_city","label":"默认城市","type":"select","options":["Beijing","Shanghai","Korla"],"default":"Korla"}, + {"key":"ai.provider","label":"AI 识别 provider","type":"select","options":["openai_compat","minimax_vl"],"default":"minimax_vl"} +]}', +'[{"label":"概览","path":"/","icon":"🏠"},{"label":"洗车记录","path":"/washes","icon":"🧽"}]' +); +``` + +**注意**:subsystem 表里**不存业务数据**,只存「我是谁 / 我有什么设置 / 我在菜单里长啥样」。 + +## 5. 总设置 vs 子系统设置 + +### 5.1 总设置(平台级) + +存 `platform_settings` 表,**不带前缀**: + +| key | value | 说明 | +|---|---|---| +| `ui.theme` | auto / light / dark | UI 主题 | +| `ui.language` | zh-CN / en | 界面语言 | +| `dashboard.layout` | default / compact | Dashboard 布局 | +| `backup.enabled` | 1 / 0 | 自动备份开关 | +| `backup.path` | /path/ | 备份路径 | + +UI 在 `/settings/global`,平台前端渲染。 + +### 5.2 子系统设置 + +存 `platform_settings` 表,**带 `{subsystem}.` 前缀**: + +| key | 说明 | +|---|---| +| `carlog.weather.default_city` | 来自 CarLog manifest 的 settings_schema | +| `carlog.ai.provider` | 同上 | +| `carlog.grocy.url` | Grocy 实例 URL(将来从 settings 表迁过来) | +| `fitness.units.metric` | Fitness 子系统的「公制单位」开关 | +| `reading.sort.default` | Reading 子系统的默认排序 | + +UI 在 `/settings/{subsystem}`,平台前端**通用渲染器**(按 settings_schema 的 type 渲染 input / select / textarea): + +```js +// 通用渲染器伪代码 +async function loadSubsystemSettings(subsystemId) { + const sub = await api.get(`/platform/subsystems/${subsystemId}`); + const values = await api.get(`/platform/settings?prefix=${subsystemId}.`); + return { schema: sub.settings_schema, values }; +} +``` + +**好处**:加新子系统不用改平台前端代码,只写 settings_schema 就完事。 + +### 5.3 兼容现有 settings 表 + +CarLog 现在已经有 `settings` 表(21 个键),存了 AI / Grocy / 登录锁定等配置。**两种方案**: + +- (A) **保留 `settings` 表**,平台层加 `platform_settings` 表,新子系统的设置走 platform_settings,CarLog 自己的设置继续用 `settings` 表 +- (B) **迁移到 `platform_settings`**,所有子系统统一,CarLog 的 key 加 `carlog.` 前缀 + +**推荐 (A)**:迁移工作量小,CarLog 的现有逻辑不用改。platform_settings 主要管「平台级 UI 设置 + 将来新子系统的设置」。 + +## 6. 路由 / 代码目录 + +### 6.1 服务端 + +``` +server/src/ +├── index.js # 统一入口 +├── routes/ +│ ├── platform/ # 总设置 / 子系统管理 / Dashboard 聚合 +│ │ ├── settings.js # GET/PUT /api/platform/settings +│ │ ├── subsystems.js # GET /api/platform/subsystems +│ │ └── dashboard.js # GET /api/platform/dashboard (跨子系统聚合) +│ ├── vehicles.js # CarLog +│ ├── washes.js # CarLog +│ └── ... +└── ... +``` + +### 6.2 前端 + +``` +client/src/ +├── AppLayout.vue # 左侧菜单按 category 分组 +├── views/ +│ ├── Platform/ # 平台层 +│ │ ├── GlobalSettings.vue +│ │ ├── SubsystemSettings.vue # 通用渲染器 +│ │ └── Subsystems.vue # 启停 + 注册 +│ ├── Home.vue # Dashboard, 读 /api/platform/dashboard +│ ├── WashesList.vue +│ └── ... +├── router/index.js +└── stores/ + ├── auth.js + └── platform.js # 总设置 / 子系统列表 / 当前激活子系统 +``` + +### 6.3 子系统如何在平台菜单里显示 + +```vue + + + + +``` + +## 7. 实施步骤 + +### W1: 平台骨架(极简版) + +1. 加 `subsystems` + `platform_settings` 两张表(migration 001_platform.sql) +2. CarLog 自己注册一条记录到 `subsystems` 表(migration 里 INSERT) +3. 加 `server/src/routes/platform/{settings,subsystems,dashboard}.js` 三个文件 +4. 在 `index.js` 把这三个 router mount 到 `/api/platform` +5. 加 `client/src/views/Platform/GlobalSettings.vue`(总设置 UI) +6. 加 `client/src/views/Platform/SubsystemSettings.vue`(通用子系统设置渲染器) +7. AppLayout.vue 改成读 `platform.subsystems` 动态渲染导航 + +### W2: 表前缀迁移(CarLog) + +把所有 CarLog 表加 `carlog_` 前缀: + +```sql +RENAME TABLE vehicles TO carlog_vehicles; +RENAME TABLE wash_records TO carlog_wash_records; +RENAME TABLE refuel_records TO carlog_refuels; +RENAME TABLE charging_records TO carlog_chargings; +-- ... 其他表 +``` + +`db.js` 加 helper: + +```js +const TABLE_PREFIX = { + vehicles: 'carlog_vehicles', + wash_records: 'carlog_wash_records', +}; +export function tableName(name) { + return TABLE_PREFIX[name] || name; +} +``` + +### W3+: 加第二个子系统(健身) + +1. 写 `subsystems.fitness`:3-5 张表 + 3-5 个路由 + 5-10 个前端 view +2. 注册到 `subsystems` 表 +3. 平台菜单自动出现 Fitness 入口 +4. 通用设置渲染器自动支持 Fitness 的 settings +5. 验证:能从平台菜单点进 Fitness,操作后回平台 + +## 8. 不做的事 + +- ❌ JWT / SSO / 跨域 auth +- ❌ iframe 嵌入子系统 +- ❌ 子系统独立部署 / 独立 DB +- ❌ RBAC / 多用户 / 多租户 +- ❌ manifest + 6 端点协议 +- ❌ SSR / 原生 app +- ❌ Marketplace / 插件化 +- ❌ 跨子系统 widget(等 5+ 子系统再做) + +## 9. 风险 & 缓解 + +| 风险 | 缓解 | +|---|---| +| 表前缀迁移漏表 | 写 Python 脚本扫描所有 SQL 提取表名检查 | +| 子系统相互依赖(A 写 B 的表) | 代码 review + lint 规则禁止跨前缀 SQL | +| 表越来越多 DB 卡 | 单用户不可能;几万条再分库 | +| 总设置 vs 子系统设置混淆 | key 命名约定:总设置无前缀,子系统 `{sub}.{key}` | + +## 10. 立即动手清单 + +1. **本周**写 migration 001_platform.sql(subsystems 表 + platform_settings 表 + seed CarLog) +2. **本周**写平台路由:settings / subsystems / dashboard 三个文件 +3. **本周**写平台前端:GlobalSettings + SubsystemSettings + 改 AppLayout 动态菜单 +4. **下下周**做表前缀迁移 + 改 server SQL +5. **下下周**跑完整测试套件 + 手动 E2E + +--- + +> 简单胜过复杂。元数据驱动(settings_schema / nav_items 两个 JSON 字段)覆盖 80% 场景,剩下的真要 iframe / SSO 时再说。 \ No newline at end of file