docs: add Platform+Subsystem architecture proposal
12 节方案: 1. 目标 2. 顶层架构(Platform 在中间 / N 个 Subsystem 围着) 3. Subsystem Manifest 协议(6 个标准端点 + JSON Schema 设置) 4. SSO(JWT + 30min 短期 token) 5. 数据模型(Platform DB carplatform 5 张表 / 子系统各自独立 DB) 6. 集成方式对比(推荐 iframe 嵌入 + 独立部署 fallback) 7. 5 步改造路线图 8. 技术选型 9. 风险 & 缓解 10. Roadmap(W1~W8+) 11. 不做的事 12. 立即动手清单 决策依据: - Q1: 10+ 子系统 → 完整的平台层 - Q2: 同构为主偶尔异构 → manifest + 6 端点语言无关 - Q3: 基座 DB + 子系统独立 DB - Q4: 永远单用户 → 不要 RBAC / 多租户
This commit is contained in:
@@ -0,0 +1,365 @@
|
||||
# 平台化架构方案(Platform + Subsystem)
|
||||
|
||||
## 1. 目标
|
||||
|
||||
把现在这套洗车管理系统(CarLog)从独立 app 升级成「生活操作系统」的子系统之一。整体目标:
|
||||
|
||||
- **平台层(Platform)**:一个薄壳。管身份 / 导航 / 子系统注册 / 总设置 / 跨子系统跳转
|
||||
- **子系统(Subsystem)**:每个子系统是独立 web app,跑自己业务。CarLog 是第一个,将来加「健身」「阅读」「理财」……
|
||||
- **UI 保持不变**:CarLog 的界面原样,只是从平台跳进去(类似 Google Workspace 里 Drive → Docs 的体验)
|
||||
- **同构优先**:主用 Vue + Express,但协议设计成语言无关,Python / Go / 静态站也能挂
|
||||
- **永远单用户**:不需要复杂的权限模型,但保留将来扩展可能
|
||||
|
||||
## 2. 顶层架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Platform (大基座) │
|
||||
│ │
|
||||
│ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
|
||||
│ │ 登录 │ │ 总设置 │ │ 子系统 │ │ 跨子系统 │ │
|
||||
│ │ /login │ │ /settings│ │ 注册中心 │ │ 跳转 / SSO │ │
|
||||
│ └─────────┘ └──────────┘ └──────────┘ └────────────┘ │
|
||||
│ │
|
||||
│ Platform DB (MySQL @ 162.14.110.130:33306/carplatform) │
|
||||
│ - users / sessions │
|
||||
│ - subsystems (注册表 + manifest 缓存) │
|
||||
│ - platform_settings (UI 主题 / 语言 / 全局开关) │
|
||||
│ - shortcuts (跨子系统跳转配置) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────────────┼───────────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
||||
│ CarLog │ │ Fitness │ │ Reading │
|
||||
│ Subsystem │ │ Subsystem │ │ Subsystem │
|
||||
│ │ │ │ │ │
|
||||
│ 独立部署 │ │ 独立部署 │ │ 独立部署 │
|
||||
│ 独立 DB │ │ 独立 DB │ │ 独立 DB │
|
||||
│ 自有 UI │ │ 自有 UI │ │ 自有 UI │
|
||||
│ 自有设置 │ │ 自有设置 │ │ 自有设置 │
|
||||
│ manifest.yaml │ │ manifest.yaml │ │ manifest.yaml │
|
||||
└───────────────┘ └───────────────┘ └───────────────┘
|
||||
localhost:8788 localhost:8789 localhost:8790
|
||||
```
|
||||
|
||||
**集成方式**:平台用 iframe 嵌入子系统核心页面(保留子系统 UI 不变)。子系统也提供纯 API 模式,平台可以选择 iframe / 直接跳独立域名。
|
||||
|
||||
## 3. 子系统协议(Subsystem Manifest)
|
||||
|
||||
每个子系统在 `/subsystem-manifest.yaml`(或 `.json`)声明身份。平台启动时拉取 + 缓存到 `subsystems` 表。
|
||||
|
||||
```yaml
|
||||
# subsystems/carlog/subsystem-manifest.yaml
|
||||
id: carlog # 唯一 ID(注册时校验)
|
||||
name: 洗车管理系统
|
||||
version: 2.8.0
|
||||
description: 给自己的私家车记账
|
||||
icon: 🚗 # emoji 或 URL
|
||||
color: '#1B6EF3'
|
||||
category: vehicle # vehicle / fitness / finance / reading / ...
|
||||
|
||||
# 平台需要调用的 6 个标准端点
|
||||
endpoints:
|
||||
health: /api/subsystem/health # GET, {ok, version, uptime}
|
||||
manifest: /api/subsystem/manifest # GET, 完整 manifest
|
||||
nav: /api/subsystem/nav # GET, {items: [{label, icon, path}]}
|
||||
ssoVerify: /api/subsystem/sso/verify # POST {token}, {user_id, expires_at}
|
||||
settings: /api/subsystem/settings # GET / PUT, 子系统自有设置 schema
|
||||
proxy: /api/subsystem/proxy/* # 平台代理访问子系统 API(可选)
|
||||
|
||||
# 子系统定义自己的设置项(平台只展示,不解释)
|
||||
settings_schema:
|
||||
- key: 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
|
||||
# ... 任意 JSON Schema 字段
|
||||
|
||||
# 导航(在平台 Dashboard 上展示)
|
||||
nav:
|
||||
- label: 概览
|
||||
path: /
|
||||
icon: 🏠
|
||||
- label: 洗车记录
|
||||
path: /washes
|
||||
icon: 🧽
|
||||
- label: 加油
|
||||
path: /refuels
|
||||
icon: ⛽
|
||||
# ...
|
||||
|
||||
# 子系统独立运行时的 URL(平台 iframe 嵌入)
|
||||
entry_url: http://localhost:8788
|
||||
|
||||
# SSO 期望的 token 类型
|
||||
auth:
|
||||
type: jwt
|
||||
issuer: platform
|
||||
shared_secret: env.SUBSYSTEM_SSO_SECRET # 平台签发 JWT 用这个 secret
|
||||
|
||||
# 子系统专属权限 scope
|
||||
scopes: [vehicles:read, vehicles:write, washes:write, ...]
|
||||
```
|
||||
|
||||
## 4. SSO(单点登录)
|
||||
|
||||
**流程**:
|
||||
|
||||
```
|
||||
用户在平台登录
|
||||
↓
|
||||
平台发 JWT: {sub: 'admin', iss: 'platform', exp: now+30min, scopes: [...]}
|
||||
↓
|
||||
平台跳子系统(iframe / window.open):
|
||||
URL: {entry_url}/auth/callback?token={JWT}
|
||||
↓
|
||||
子系统验证 JWT(用 SUBSYSTEM_SSO_SECRET 校验签名)
|
||||
↓
|
||||
子系统发自己的 session cookie(子系统自己管)
|
||||
↓
|
||||
iframe 内正常操作
|
||||
```
|
||||
|
||||
**关键约束**:
|
||||
- JWT 短期(30 分钟),过期前 iframe 内静默续期
|
||||
- SUBSYSTEM_SSO_SECRET **不**写代码,每个子系统从 env 读
|
||||
- 子系统不需要单独登录(直接 SSO);保留独立登录入口作为 fallback
|
||||
|
||||
## 5. 数据模型
|
||||
|
||||
### 5.1 Platform DB(基座)
|
||||
|
||||
复用现有的 MySQL,但**新建独立 database** `carplatform`(不污染现有 `carlog`)。
|
||||
|
||||
```sql
|
||||
CREATE DATABASE IF NOT EXISTS carplatform
|
||||
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE carplatform;
|
||||
|
||||
-- 用户(单用户,但保留 user_id 字段方便将来扩展)
|
||||
CREATE TABLE users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(64) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
display_name VARCHAR(100),
|
||||
is_active TINYINT(1) DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login_at DATETIME
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- Session(如果走 JWT 模式就不需要;但保留给 fallback)
|
||||
CREATE TABLE sessions (
|
||||
sid VARCHAR(64) PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
expires_at DATETIME NOT NULL,
|
||||
data JSON,
|
||||
INDEX idx_expires (expires_at)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- 子系统注册表
|
||||
CREATE TABLE subsystems (
|
||||
id VARCHAR(50) PRIMARY KEY, -- 'carlog' / 'fitness' / ...
|
||||
name VARCHAR(100) NOT NULL,
|
||||
version VARCHAR(20) NOT NULL,
|
||||
description TEXT,
|
||||
icon VARCHAR(20),
|
||||
color VARCHAR(20),
|
||||
category VARCHAR(50),
|
||||
entry_url VARCHAR(255) NOT NULL, -- http://localhost:8788
|
||||
manifest_url VARCHAR(255) NOT NULL, -- http://localhost:8788/subsystem-manifest.yaml
|
||||
manifest_cache JSON, -- 平台启动时拉取缓存
|
||||
enabled TINYINT(1) DEFAULT 1,
|
||||
sort_order INT DEFAULT 0,
|
||||
installed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
last_health_at DATETIME,
|
||||
health_status VARCHAR(20) DEFAULT 'unknown' -- ok / warn / down / unknown
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- 平台级设置(UI 主题 / 语言 / 全局开关)
|
||||
CREATE TABLE platform_settings (
|
||||
key_name VARCHAR(100) PRIMARY KEY,
|
||||
value TEXT,
|
||||
description TEXT,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- 跨子系统跳转配置("看完车的保养,去看看日历里有没有预约")
|
||||
CREATE TABLE shortcuts (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
from_system VARCHAR(50) NOT NULL,
|
||||
from_path VARCHAR(255),
|
||||
to_system VARCHAR(50) NOT NULL,
|
||||
to_path VARCHAR(255),
|
||||
label VARCHAR(100) NOT NULL,
|
||||
icon VARCHAR(20),
|
||||
sort_order INT DEFAULT 0
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- 初始数据
|
||||
INSERT INTO users (username, password_hash, display_name) VALUES
|
||||
('admin', '$2b$10$...', 'Admin'); -- 第一次启动创建
|
||||
|
||||
INSERT INTO platform_settings (key_name, value, description) VALUES
|
||||
('ui.theme', 'auto', 'UI 主题:light / dark / auto'),
|
||||
('ui.language', 'zh-CN', '界面语言'),
|
||||
('subsystem.discovery', 'manual', '子系统发现方式:manual / auto');
|
||||
```
|
||||
|
||||
### 5.2 子系统 DB
|
||||
|
||||
**完全不动**。CarLog 继续用现有的 `carlog` DB。其他子系统各自创建自己的 DB。
|
||||
|
||||
平台 DB 知道子系统存在 + 怎么访问,但**不**读写子系统业务数据。
|
||||
|
||||
## 6. 集成方式对比
|
||||
|
||||
| 方式 | 优点 | 缺点 | 适用 |
|
||||
|---|---|---|---|
|
||||
| iframe 嵌入 | 子系统 UI 100% 不变 / 独立 deploy | cookie 跨域 / iframe 通信麻烦 / UX 不连贯 | 短期最快出活 |
|
||||
| Module Federation | 共享 chunk / SPA 体验一致 | Vue 生态 MF 支持弱 / 调试难 | 不推荐 |
|
||||
| 子系统独立部署 + 平台跳转 | 简单 / 各自 deploy | 用户跳来跳去 | 现在没用 / 将来可作为 fallback |
|
||||
| 平台写死子系统代码 | 部署最简 | 失去「独立子系统」的意义 | 不推荐 |
|
||||
|
||||
**推荐**:**iframe 嵌入为主,独立部署为辅**。CarLog 子系统自己跑(端口 8788),平台 Dashboard 用 iframe 嵌入 `/subsystems/carlog/index.html`。
|
||||
|
||||
## 7. CarLog → 子系统改造步骤
|
||||
|
||||
### Phase 1:拆 CarLog 为独立 deploy(保留 UI 完全不变)
|
||||
|
||||
目标:把 CarLog 从根目录拆出来,但仍跑在原位置 + 原 URL,UI 不变。
|
||||
|
||||
```bash
|
||||
# 在 git root
|
||||
mkdir -p subsystems
|
||||
mv client subsystems/carlog-client
|
||||
mv server subsystems/carlog-server
|
||||
# package.json / README / docs / migrations 等留在根目录
|
||||
# subsystems/carlog/{client,server}/ 各自有 package.json
|
||||
```
|
||||
|
||||
加 `subsystems/carlog/subsystem-manifest.yaml` + 6 个标准端点的 stub(先返 mock 数据)。
|
||||
|
||||
更新部署:CarLog 从 `127.0.0.1:8787` 改成 `127.0.0.1:8788`(端口错开,留给平台)。
|
||||
|
||||
### Phase 2:建 Platform 骨架
|
||||
|
||||
```bash
|
||||
mkdir platform/{client,server}
|
||||
```
|
||||
|
||||
Platform 极薄:
|
||||
- `platform/server/`:Express + 上面 5 张表的 CRUD + 子系统注册接口 + SSO token 签发
|
||||
- `platform/client/`:Vue 3 壳子,左侧子系统菜单 + 右侧 iframe 嵌入当前子系统核心页
|
||||
- 总设置页:UI 主题 / 语言 / 子系统启停
|
||||
- 子系统注册页:填 URL → 平台 GET manifest → 校验 → 写 `subsystems` 表
|
||||
|
||||
### Phase 3:接 SSO
|
||||
|
||||
1. 平台登录成功 → 写平台 session + 发 JWT
|
||||
2. Dashboard 加载 → 平台前端 GET `/api/subsystems?enabled=1` → 渲染左侧菜单
|
||||
3. 用户点菜单项 → 平台前端把 JWT 写到 iframe 的 `src` URL 里:
|
||||
```
|
||||
http://localhost:8788/auth/callback?token=eyJhbGc...
|
||||
```
|
||||
4. 子系统 `/auth/callback` 端点:
|
||||
- 校验 JWT 签名
|
||||
- 在子系统自己的 session 里建 session(user_id = JWT.sub)
|
||||
- 重定向到 `?redirect=/` 或 manifest 里指定的路径
|
||||
5. iframe 内子系统操作完成,回到平台(postMessage 或直接顶层跳转)
|
||||
|
||||
### Phase 4:通用子系统模板(scaffolding)
|
||||
|
||||
为了「偶尔异构」(Python / Go / 静态站)也能接入,**manifest + 6 个端点** 是语言无关的。给个最小模板:
|
||||
|
||||
```bash
|
||||
# Vue + Express
|
||||
npx create-subsystem -t vue-express -n fitness
|
||||
|
||||
# Python + FastAPI
|
||||
npx create-subsystem -t python-fastapi -n reading
|
||||
|
||||
# Static site
|
||||
npx create-subsystem -t static -n blog
|
||||
```
|
||||
|
||||
每个模板预置:
|
||||
- `subsystem-manifest.yaml`(带 ID 替换占位符)
|
||||
- 6 个端点的实现(health / manifest / nav / sso-verify / settings / proxy)
|
||||
- SSO callback 处理
|
||||
- 一个示例页面(Hello from {subsystem_name})
|
||||
- 部署 Dockerfile / 宝塔步骤
|
||||
|
||||
### Phase 5:跨子系统聚合(可选 v3.0)
|
||||
|
||||
Dashboard widget 系统:
|
||||
- 平台前端提供 `<SubsystemWidget subsystem="carlog" type="recent_washes" />`
|
||||
- widget 是个 Vue 组件,通过 postMessage 跟子系统 iframe 通信
|
||||
- 子系统暴露 `window.parent.postMessage({type: 'widget_data', ...})` 响应
|
||||
|
||||
但**这个等 5+ 个子系统上线后再做**,过早抽象是万恶之源。
|
||||
|
||||
## 8. 关键技术选型
|
||||
|
||||
| 项 | 选型 | 理由 |
|
||||
|---|---|---|
|
||||
| 平台后端 | Express 4(**和 CarLog 同构**) | 复用现有工具链 / auth / csrf 中间件 |
|
||||
| 平台前端 | Vue 3 + Vite(**和 CarLog 同构**) | 同上 |
|
||||
| SSO token | JWT(HS256) | 无状态 / 跨进程可验签 / 子系统不依赖平台 DB |
|
||||
| JWT secret | env `PLATFORM_JWT_SECRET` | 不写代码 / 各子系统从 env 拿同一个 secret 即可验证 |
|
||||
| 子系统发现 | 平台手动注册(Phase 1)/ 平台 GET manifest URL 自动注册(Phase 2) | Phase 1 简单可控 / Phase 2 自动但需要重试机制 |
|
||||
| 平台 DB | MySQL(**复用现有连接** + 新 database `carplatform`) | 同一个 MySQL server,不增加运维复杂度 |
|
||||
| 子系统 DB | 各自独立 | 数据隔离 / 独立备份 |
|
||||
| iframe 通信 | postMessage | 标准 API / 跨域安全 |
|
||||
| 反向代理 | Nginx(**不动现有配置**,加平台子域名如 `app.img2img.com`) | 子系统用子路径或子域名 |
|
||||
|
||||
## 9. 风险 & 缓解
|
||||
|
||||
| 风险 | 缓解 |
|
||||
|---|---|
|
||||
| cookie 跨域 | JWT 走 URL query,平台给每个子系统独立子域名,子系统不依赖 cookie 跨域 |
|
||||
| 子系统挂掉平台跟着挂 | iframe 加载失败时平台 Dashboard 显示降级页面 + 「子系统不可用」提示 |
|
||||
| JWT secret 泄漏 | 不写代码 / 部署时 env 注入 / 定期 rotate |
|
||||
| 子系统版本兼容 | manifest 里 version 字段,平台校验最低支持版本 |
|
||||
| 单点登录过多子系统 | JWT 30 分钟过期 + 静默续期;如果用户长时间不操作,重新登录 |
|
||||
| 10+ 子系统的导航 UX | 按 category 分组(vehicle / fitness / finance / reading),折叠 / 搜索 |
|
||||
| 平台 DB 变瓶颈 | 单用户场景下不可能;如果哪天变了,分表即可 |
|
||||
|
||||
## 10. Roadmap(按周排)
|
||||
|
||||
| 周 | 任务 | 验证 |
|
||||
|---|---|---|
|
||||
| W1 | Phase 1:CarLog 拆目录 + 加 manifest stub + 改端口到 8788 | CarLog 独立跑 / UI 完全不变 |
|
||||
| W2 | Phase 2 平台骨架:Express + 5 张表 + 子系统注册 API + Vue 壳 | 平台能启动 / 显示「暂无子系统」 |
|
||||
| W3 | Phase 3 SSO:JWT 签发 / iframe 嵌入 / 子系统 callback | 平台登录 → 点 CarLog → 看到 CarLog 内容 |
|
||||
| W4 | Phase 4 scaffolding:3 个模板(vue-express / python-fastapi / static) | 用模板 1 分钟建出 fitness 子系统骨架 |
|
||||
| W5+ | 加第二个真子系统(fitness 或 reading)+ 测试跨子系统跳转 | 第二个子系统跑通 / 验证 v2 的 sso 流程 |
|
||||
| W8+ | Phase 5 跨子系统聚合 widget(**等 5+ 子系统再开始**) | 暂缓 |
|
||||
|
||||
## 11. 不做的事(明确边界)
|
||||
|
||||
- ❌ 多租户 / RBAC(永远单用户)
|
||||
- ❌ 子系统 marketplace / 第三方接入(只自用)
|
||||
- ❌ 复杂插件系统 / Hook(manifest + 6 端点已经够用)
|
||||
- ❌ 实时协同 / WebSocket(单用户不需要)
|
||||
- ❌ 移动端原生 app(PWA 够了)
|
||||
- ❌ 服务端渲染 SSR(Vue SPA + PWA 已经够)
|
||||
|
||||
## 12. 立即动手清单(按优先级)
|
||||
|
||||
1. **本周**:决定 CarLog 拆目录的具体路径(是 `subsystems/carlog/` 还是独立 git repo)
|
||||
2. **本周**:在现有 MySQL 建 `carplatform` database
|
||||
3. **本周**:写平台 server 的 5 张表 migration
|
||||
4. **下周**:开始 Phase 1 拆 CarLog
|
||||
|
||||
---
|
||||
|
||||
> 实施过程中如果发现某块要复杂得多(比如 iframe 通信坑很多),可以**降级到独立部署 + 平台跳转**——架构方案是手段不是目的,能让自己日常用得爽才是。
|
||||
Reference in New Issue
Block a user