docs: initial README + ARCHITECTURE.md + .gitignore

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
This commit is contained in:
2026-06-20 22:18:23 +08:00
commit 77adc8e498
3 changed files with 436 additions and 0 deletions
+47
View File
@@ -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/
+91
View File
@@ -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 schemaJSON 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
+298
View File
@@ -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 schemaJSON 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_settingsCarLog 自己的设置继续用 `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
<!-- AppLayout.vue 左侧导航 -->
<template v-for="cat in groupedCategories" :key="cat">
<div class="nav-category">{{ cat.label }}</div>
<router-link v-for="item in cat.items" :key="item.path" :to="item.path">
{{ item.icon }} {{ item.label }}
</router-link>
</template>
<script setup>
import { computed } from 'vue';
import { usePlatformStore } from '../stores/platform';
const platform = usePlatformStore();
const groupedCategories = computed(() => {
const groups = {};
for (const sub of platform.subsystems.filter(s => s.enabled)) {
if (!groups[sub.category]) groups[sub.category] = { label: sub.category, items: [] };
for (const nav of (sub.nav_items || [])) {
groups[sub.category].items.push({ ...nav, subsystem: sub.id });
}
}
return Object.values(groups);
});
</script>
```
## 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.sqlsubsystems 表 + 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 时再说。