--- sidebar_position: 2 title: 系统架构 sidebar_label: 系统架构 description: Plaza 的整体架构、技术选型、部署结构和服务边界 --- # 系统架构 ## 总体架构 ```text ┌─────────────────────────────────────────────────────────────────┐ │ 用户访问层 │ │ │ │ 游客/浏览者 登录用户 运营人员 │ │ /plaza, /u/:slug /space, /chat /ops │ └───────────┬────────────────────┬──────────────────┬────────────┘ │ │ │ ▼ ▼ ▼ ┌───────────────────────────────────────────────────────────────┐ │ Nginx 统一入口(go.tkmind.cn) │ │ │ │ /plaza, /u/ → Next.js :3001 /ops → Ops SPA :3002 │ │ /api → Node.js :18006 / → H5 SPA :8080 │ └───────────────────────────┬───────────────────────────────────┘ │ ┌───────────────┼───────────────┐ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Plaza 前端 │ │ MindSpace H5 │ │ 运营后台 │ │ Next.js SSR │ │ Vite CSR │ │ Vite CSR │ │ ui/plaza/ │ │ ui/h5/ │ │ ui/ops/ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ └────────────────┼────────────────┘ │ HTTP /api/* ▼ ┌───────────────────────────────┐ │ Node.js 后端服务 │ │ │ │ /api/mindspace/v1/* │ │ /api/plaza/v1/* (新增,plaza-*.mjs)│ │ /api/ops/v1/* (新增,ops-*.mjs) │ │ /auth/* │ └───────────────┬───────────────┘ │ ┌───────────────┼───────────────┐ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ MySQL │ │ 文件存储 │ │ Redis │ │ 共用数据库 │ │ 本地/OSS │ │ 热度计数 │ │ + plaza_*表 │ │ + 缩略图 │ │ + feed缓存 │ └──────────────┘ └──────────────┘ └──────────────┘ ``` ## 技术选型 ### Plaza 前端(ui/plaza/) | 技术 | 选型 | 原因 | | --- | --- | --- | | 框架 | Next.js 14+ App Router | 原生 SSR/SSG,SEO 最佳实践 | | 语言 | TypeScript | 与现有 H5 保持一致 | | 样式 | Tailwind CSS | 快速开发,与 H5 互不干扰 | | 状态管理 | React Server Components + SWR | 服务端数据优先,客户端增量更新 | | 图片优化 | next/image | 自动 WebP、懒加载、尺寸优化 | | 分析 | 自建埋点(复用后端 analytics API) | 不依赖第三方 | ### 运营后台(ui/ops/) | 技术 | 选型 | 原因 | | --- | --- | --- | | 框架 | Vite + React | 与 H5 工具链一致,快速启动 | | UI 组件库 | 复用现有组件风格 | 不引入新依赖 | | 权限 | 独立 ops_role 中间件 | 与普通 admin 权限隔离 | ### 后端扩展 | 模块 | 实现方式 | | --- | --- | | Plaza API | 在现有 Node 服务中新增 `/api/plaza/v1/` 路由模块 | | 热度计数 | Redis INCR + 定时回写 MySQL | | 缩略图生成 | 发布时异步截图任务(Puppeteer worker) | | 内容审核队列 | MySQL 任务表(与现有 upload scan 模式一致) | ## 服务边界原则 ### Plaza 可以做的 - 读取 `h5_publish_records` 表(逻辑名 *publications*)判断帖子来源是否 `online`。 - 读取 `h5_users` 表(逻辑名 *users*)获取创作者基础信息(id、slug、display_name、avatar_url)。 - 在**同一 MySQL 实例**内,通过定时任务将广场计数回写到 `h5_publish_records.plaza_view_count` / `plaza_like_count`(不调用 MindSpace service 函数,但字段由 MindSpace 侧迁移脚本统一定义)。 ### Plaza 不能做的 - 直接 JOIN `h5_user_spaces`、`h5_page_records`、`h5_assets` 等 MindSpace 核心表。 - 调用 MindSpace 的内部 service 函数(如 `mindspace_service.getPage()`)。 - 绕过 MindSpace 发布流程直接操作 `publications` 状态。 ### 跨模块通信方式 ```text Plaza 需要 MindSpace 数据时: ① 读 plaza_posts 中的冗余快照字段(title, cover_url, user_slug 等) ② 调用 /api/mindspace/v1/ 公开接口(只读场景) ③ 通过共用数据库字段或异步任务表传递事件(不 import MindSpace service 模块) MindSpace 下线 publication 时: → 在 mindspace-publications 的同一数据库事务内 UPDATE plaza_posts SET status = 'hidden' Plaza 回写 publication 广场统计时: → 独立定时任务 UPDATE h5_publish_records(见 04-backend-api「互动数据回写」) ``` ## 部署结构 ### 进程列表 | 进程 | 端口 | 启动命令 | 说明 | | --- | --- | --- | --- | | Nginx | 443 | systemd | 统一入口,SSL 终止 | | Node.js 后端 | 18006 | `node server.mjs` | 现有服务,扩展新路由 | | MindSpace H5 | 8080 | `pnpm preview` | 现有 Vite SPA | | Plaza Next.js | 3001 | `pnpm start` | 新建,SSR | | 运营后台 | 3002 | `pnpm preview` | 新建,CSR SPA,仅内网 | | Redis | 6379 | systemd | 新增 | ### Nginx 配置核心 ```nginx server { server_name go.tkmind.cn; # 广场和用户主页 → Next.js SSR location ~ ^/(plaza|u/) { proxy_pass http://127.0.0.1:3001; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 运营后台 → 内网限制 location /ops { allow 10.0.0.0/8; # 内网 IP 段 deny all; proxy_pass http://127.0.0.1:3002; } # 所有 API → 共用后端 location /api { proxy_pass http://127.0.0.1:18006; } # 其余 → MindSpace H5 location / { proxy_pass http://127.0.0.1:8080; } } ``` ### Cookie 共享策略 三个前端应用同 host(`go.tkmind.cn`),Nginx 按路径转发到不同端口。**Session Cookie 使用 host-only**(不设置 `Domain` 属性),则 `/`、`/plaza`、`/ops` 天然共享登录态,无需 SSO。 ``` Set-Cookie: session_id=xxx; Path=/; HttpOnly; Secure; SameSite=Lax ``` 若未来拆到子域(如 `plaza.go.tkmind.cn`),再改为 `Domain=.go.tkmind.cn` 并统一调整各前端 API 基址。 ## 目录结构 ```text ui/ ├── h5/ # 现有 MindSpace,不动 ├── plaza/ # 新建 Next.js 广场 │ ├── app/ │ │ ├── plaza/ # 广场首页和分类 │ │ │ ├── page.tsx # /plaza SSR │ │ │ └── cat/[slug]/page.tsx │ │ ├── plaza/p/[id]/ # 帖子详情 │ │ │ └── page.tsx │ │ └── u/[slug]/ # 用户主页 │ │ └── page.tsx │ ├── components/ │ │ ├── PostCard.tsx # 卡片组件 │ │ ├── PostGrid.tsx # 瀑布流 │ │ ├── CategoryNav.tsx # 分类导航 │ │ └── UserProfile.tsx # 用户主页 │ ├── lib/ │ │ └── api.ts # 调用 /api/plaza/v1/ │ └── types/ │ └── plaza.ts └── ops/ # 新建运营后台 └── src/ ├── pages/ │ ├── ReviewQueue.tsx │ ├── FeaturedManager.tsx │ └── Analytics.tsx └── api/ └── client.ts # 调用 /api/ops/v1/ ``` ## 扩展路径 当以下信号出现时,将 Plaza 拆出为独立后端服务: | 信号 | 行动 | | --- | --- | | 广场流量是 MindSpace 的 10 倍+ | 将 plaza_* 表迁移到独立数据库,后端服务拆分 | | 推荐算法需要 Python ML 服务 | 新建推荐微服务,后端调用其接口 | | 内容审核需要独立合规团队 | 运营后台独立部署,API 拆分 | | 广场开放第三方内容(非 MindSpace) | 数据模型不再依赖 publications 外键,彻底解耦 | 拆分时要搬走的只有:`plaza_*` 表、`/api/plaza/v1/` 路由、`ui/plaza/`,其他不动。