# 本地服务器独立微服务化方案 > **项目**:智能餐养系统 - 边缘服务节点重构 > **版本**:v1.0 > **日期**:2026-05-26 > **作者**:技术团队 > **范围**:面向技术团队的设计与落地方案 > **周期**:1 个月落地,边跑边迁 --- ## 一、定位与边界 **本地服务器 ≠ 业务系统**。本次重构将其重新定位为:**部署在每个食堂边缘的独立微服务,只做"硬件 ↔ 云端"的可靠中转桥梁**,与现有"健康长庆"业务系统、小程序后端、admin 后台**完全解耦**。 | 做(In Scope) | 不做(Out of Scope) | 不混用(独立部署) | | --- | --- | --- | | 任务化数据中转 | 业务规则、菜品算法 | 不依赖小程序后端 | | 任务链状态管理与重试 | 用户登录鉴权 | 不调用四合一业务接口 | | 终端设备接入与协议适配 | 报表/统计/分析 | 不耦合 admin 业务模块 | | 本地资源缓存(视频/人脸/升级包) | 任何 UI 业务逻辑 | 独立进程、独立升级 | | 设备远程升级 | | | **一句话原则**:本地服务器只回答两个问题——*云端要的数据收齐了吗?云端下发的指令落地了吗?* 其他一概不管。 --- ## 二、总体架构 ### 2.1 系统拓扑 ```mermaid flowchart TB subgraph Cloud["云端"] CloudAPI[云端任务网关
Spring Boot] CloudDB[(任务主库
PostgreSQL)] OpsUI[运维后台
Vue3 + Ant Design Vue
复用 admin 技术栈] end subgraph Edge["食堂本地 - 每个食堂一套"] LS[本地服务器微服务
Spring Boot 独立 JAR] LSDB[(本地任务库
SQLite)] Cache[本地资源缓存
视频/人脸库/升级包] end subgraph Devices["终端硬件"] T1[点餐终端] T2[入库秤/配比秤] T3[智能货柜/绑盘机] T4[人脸设备] T5[显示大屏] end OpsUI --> CloudAPI CloudAPI <-->|MQTT QoS2 持久会话| LS CloudAPI --- CloudDB LS --- LSDB LS --- Cache LS <-->|HTTP/MQTT 局域网| T1 LS <-->|HTTP/MQTT 局域网| T2 LS <-->|HTTP/MQTT 局域网| T3 LS <-->|HTTP/MQTT 局域网| T4 LS -->|本地 HTTP / RTSP| T5 ``` ### 2.2 通信协议选型(针对弱网) | 链路 | 协议 | 选型理由 | | --- | --- | --- | | 云端 ↔ 本地服务器 | **MQTT QoS 2 + Persistent Session**(EMQX) | 弱网友好、断线自动续传、消息至少一次且不重复 | | 大文件下发(视频/升级包) | HTTPS 断点续传(Range) | 弱网重连不重头,降低带宽浪费 | | 本地服务器 ↔ 终端 | HTTP / MQTT(局域网) | 局域网稳定,简单优先,按设备类型适配 | | 运维后台 ↔ 云端 | HTTPS REST | 复用 admin 现有 axios + token 鉴权 | ### 2.3 技术栈 | 模块 | 选型 | 备注 | | --- | --- | --- | | **云端任务网关** | Spring Boot 3 + Spring Integration MQTT | 与现有 Java 后端栈一致 | | **本地服务器微服务** | Spring Boot 3,打包成单 JAR | 一键部署,内存占用控制在 512MB 以内 | | **MQTT Broker** | EMQX 5(集群) | 支持 QoS 2、共享订阅、规则引擎 | | **云端数据库** | **PostgreSQL 15** ✅ 推荐 / MySQL 8 备选 | PG 的 JSONB 对 `target_scope` 等字段更友好,CTE/窗口函数利于运维查询 | | **本地任务库** | SQLite + WAL 模式 | 零运维,适合边缘场景,单文件备份 | | **运维后台** | Vue 3 + Vite + TypeScript + Ant Design Vue | **复用现有 admin 工程技术栈与登录体系** | | **配置中心 / 注册** | 暂不引入,本地服务器走配置文件 + 远程下发 | 边缘节点数量级小,过早引入 Nacos 增加复杂度 | | **部署** | Docker 镜像 + 一键安装脚本 | 边缘节点需支持离线部署 | --- ## 三、核心机制:全链路任务化 ### 3.1 设计原则 每一次数据流转(无论下行还是上行)都是一个**有状态、可追溯、可重试**的任务。云端与本地各持一份任务记录,通过 `task_id` 关联,任意时刻**任意任务卡在哪一跳一查就知道**。 ### 3.2 任务状态机 ```mermaid stateDiagram-v2 [*] --> Created: 云端创建任务 Created --> Dispatched: 推送到本地服务器 Dispatched --> LocalReceived: 本地落库 ACK LocalReceived --> TerminalDispatched: 分发到终端 TerminalDispatched --> TerminalDone: 终端确认完成 TerminalDone --> CloudReported: 上报云端 CloudReported --> [*]: 任务完成 Dispatched --> RetryPending: 网络中断 LocalReceived --> RetryPending: 终端离线 TerminalDispatched --> RetryPending: 终端执行失败 RetryPending --> Dispatched: 自动指数退避重试 RetryPending --> Failed: 达到最大重试次数 Failed --> Dispatched: 运维后台人工触发重试 ``` **重试策略**:指数退避 `1s → 5s → 30s → 2min → 10min`,默认最多 5 次,达到上限转 `Failed` 并触发告警。 ### 3.3 数据库 Schema(核心三表) > 以下 DDL 以 PostgreSQL 为例,MySQL 版本仅类型微调(JSONB → JSON、ENUM → VARCHAR + CHECK)。 ```sql -- ============================================ -- 1. 任务主表 -- ============================================ CREATE TABLE task ( task_id UUID PRIMARY KEY, task_type VARCHAR(32) NOT NULL, -- 'DISH_DOWN','FACE_DOWN','UPGRADE_DOWN','VIDEO_DOWN','MEAL_UP',... direction VARCHAR(8) NOT NULL, -- 'DOWN' / 'UP' target_scope JSONB NOT NULL, -- {"org_id":1,"canteen_ids":[10,11],"device_ids":["T01","T02"]} -- 支持二级单位/食堂/设备级定向下发 payload_ref VARCHAR(255), -- 大字段指针(对象存储 key 或外部表 ID) total_count INT NOT NULL DEFAULT 0, success_count INT NOT NULL DEFAULT 0, failed_count INT NOT NULL DEFAULT 0, state VARCHAR(24) NOT NULL, -- CREATED/DISPATCHED/LOCAL_RECEIVED/TERMINAL_DISPATCHED/ -- TERMINAL_DONE/CLOUD_REPORTED/RETRY_PENDING/FAILED retry_count INT NOT NULL DEFAULT 0, max_retry INT NOT NULL DEFAULT 5, next_retry_at TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, finished_at TIMESTAMP, creator VARCHAR(64) ); CREATE INDEX idx_task_state_retry ON task(state, next_retry_at); CREATE INDEX idx_task_target ON task USING GIN (target_scope); -- ============================================ -- 2. 任务跳转流转表(全链路审计) -- ============================================ CREATE TABLE task_hop ( hop_id BIGSERIAL PRIMARY KEY, task_id UUID NOT NULL REFERENCES task(task_id), hop_name VARCHAR(24) NOT NULL, -- CLOUD_OUT/LOCAL_IN/LOCAL_OUT/TERMINAL_IN/TERMINAL_DONE/ -- LOCAL_REPORT/CLOUD_IN status VARCHAR(8) NOT NULL, -- 'OK','FAIL','TIMEOUT' err_code VARCHAR(32), err_msg TEXT, occurred_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_task_hop_task ON task_hop(task_id, occurred_at); -- ============================================ -- 3. 任务明细表(批量任务的逐条结果) -- ============================================ CREATE TABLE task_item ( item_id BIGSERIAL PRIMARY KEY, task_id UUID NOT NULL REFERENCES task(task_id), biz_key VARCHAR(128) NOT NULL, -- 业务键,例如菜品 ID、人脸 ID status VARCHAR(8) NOT NULL, -- 'OK','FAIL','PENDING' err_msg TEXT, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE (task_id, biz_key) ); CREATE INDEX idx_task_item_task ON task_item(task_id, status); ``` **这套表的关键能力**: - 任意任务卡在哪一跳,一查 `task_hop` 就知道 - 1000 条菜品下发,失败的 3 条精准告诉你是哪 3 条(`task_item.status='FAIL'`) - 本地服务器同样跑这套 Schema(SQLite 简化版),云端拉取本地 hop 状态做对账 ### 3.4 可靠性三大不变量 1. **先持久化再前进**:每一跳必须本地落库成功后才回 ACK;落库失败 → 让上游重发 2. **幂等键 = `task_id` + `biz_key`**:重发不会产生重复数据;终端拿到同一个 task_id 直接覆盖原记录 3. **任务永不丢**:本地服务器即使断电重启,任务从最近持久化的状态继续往下走;运维后台可强制重放 ### 3.5 接口契约(节选) **云端 → 本地:任务下发** ``` Topic: tenant/{org_id}/canteen/{canteen_id}/task/down QoS: 2 Payload: { "task_id": "uuid", "task_type": "DISH_DOWN", "target_scope": { "device_ids": ["T01","T02"] }, "payload_ref": "https://oss.../dish-snapshot-2026052601.json", "items_count": 487, "issued_at": "2026-05-26T10:00:00Z" } ``` **本地 → 云端:跳转回执** ``` Topic: tenant/{org_id}/canteen/{canteen_id}/task/hop Payload: { "task_id": "uuid", "hop_name": "TERMINAL_DONE", "status": "OK", "success_count": 485, "failed_count": 2, "failed_items": [ {"biz_key":"DISH_1024","err_msg":"image download timeout"} ] } ``` **本地 → 云端:就餐数据上传** ``` Topic: tenant/{org_id}/canteen/{canteen_id}/task/up Payload: 同 task 主表结构 + items 数组 ``` --- ## 四、运维后台 > 复用现有 admin 工程的 **Vue 3 + Vite + TypeScript + Ant Design Vue + Pinia + axios** 技术栈,**沿用 admin 登录体系与权限模型**,作为 admin 工程内的一组独立路由模块(`/edge-ops/*`),与业务模块隔离。 ### 4.1 功能模块 ``` 运维后台 (admin 工程内 /edge-ops 路由组) ├── 任务总览 │ ├── 实时大盘:各食堂在线状态、今日任务数、失败率、堆积量 │ └── 异常告警:失败率突增、任务堆积超阈值、设备掉线 ├── 任务管理 │ ├── 任务列表(按食堂/类型/状态/时间筛选) │ ├── 任务详情(全链路流转图 + 明细 + 错误堆栈) │ ├── 一键重试(单任务/批量) │ └── 手动创建任务(应急下发) ├── 设备管理 │ ├── 食堂/本地服务器健康状态 │ ├── 终端设备清单(在线、版本、最后心跳) │ └── 远程软件升级触发 ├── 资源管理 │ ├── 视频/人脸库/升级包上传与下发 │ └── 各食堂本地缓存命中情况 ├── 日志与查询 │ ├── 实时日志流(SSE 推送) │ └── 数据库可视化查询(只读 SQL + 预设查询模板) └── 系统配置 ├── 重试策略、超时阈值 └── 二级单位/食堂组织树 ``` ### 4.2 关键页面示意:任务详情 ``` [任务 #abc123 · 菜品下发 · 目标:总部食堂 + 5 个分食堂] 状态: ⚠ 部分失败 进度: 487/500 耗时: 2m13s 全链路流转: ✓ 云端创建 10:23:01 ✓ 推送本地 10:23:02 (6/6 食堂) ✓ 本地落库 10:23:03 (6/6 食堂) ⚠ 终端分发 10:23:05 (5 食堂成功 / 1 食堂超时,自动重试中) └─ 失败明细: 分食堂C - 终端 T07 离线 操作: [一键重试] [跳过失败项] [取消任务] [导出日志] ``` ### 4.3 路由与权限 - 在 `src/router/index.ts` 的 `modules` 数组中新增「边缘运维」分组,`meta.menuKey = 'edge-ops'` - 路由命名:`/edge-ops/dashboard`、`/edge-ops/tasks`、`/edge-ops/devices`、`/edge-ops/resources`、`/edge-ops/logs`、`/edge-ops/config` - 权限模型:复用 admin 已有 `role`,新增 `edge:read`、`edge:retry`、`edge:upgrade`、`edge:config` 四类细粒度权限 - 仅限内部运维人员访问,不对客户开放 --- ## 五、1 个月落地计划(边跑边迁) ```mermaid gantt title 落地排期(2026-05-26 至 2026-06-22) dateFormat YYYY-MM-DD section 第1周 框架 项目骨架/任务模型/DB Schema :a1, 2026-05-26, 3d 云端↔本地通信层(MQTT QoS2) :a2, after a1, 4d section 第2周 三大功能迁移 菜品/营养数据下发改造 :b1, after a2, 3d 就餐数据上传改造 :b2, after a2, 3d 视频资源下发改造 :b3, after b1, 4d section 第3周 运维后台 + 联调 运维后台前后端 :c1, after b3, 5d 全链路联调 + 弱网压测 :c2, after b3, 7d section 第4周 灰度 + 切换 单食堂灰度试运行 :d1, after c2, 3d 分批切换 + 监控 + 回滚预案 :d2, after d1, 4d ``` ### 边跑边迁策略 - **新老并存**:旧 MQ 链路保留,新任务系统**双写一段时间**(老链路真实下发,新链路影子运行) - **每日对账**:跑对账脚本比对新老链路数据一致性,差异 > 0 立即告警 - **按食堂灰度**:第 4 周第 1-3 天选 1-2 个食堂切换,稳定后批量推 - **回滚开关**:每个食堂有独立的 `use_new_pipeline` 开关,出问题秒级切回老链路 - **冻结老链路功能**:迁移期间老链路只修 P0 bug,不再加新功能 ### 验收标准 | 指标 | 目标 | | --- | --- | | 弱网模拟丢包 30% 下任务最终成功率 | ≥ 99.9% | | 任务平均端到端延迟(局域网正常) | ≤ 3s | | 任意失败任务可定位到具体跳转 + 失败明细 | 100% | | 运维后台一键重试可用率 | 100% | | 灰度食堂连续 3 天无 P0 故障 | 通过 | --- ## 六、风险与对策 | 风险 | 影响 | 对策 | | --- | --- | --- | | 弱网导致云端推送积压 | 数据延迟 | MQTT 持久会话 + 本地服务器主动拉取兜底 + 队列水位告警 | | 本地服务器断电/磁盘损坏 | 任务丢失 | SQLite 启用 WAL + 每日云端快照备份 + 关键任务云端可重放 | | 终端固件升级失败变砖 | 设备瘫痪 | 双分区 A/B 升级 + 失败自动回滚 + 升级前强制健康检查 | | 1 个月排期紧张 | 延期 | **第 1 周架构定型不可压缩**;运维后台 V1 可砍范围(先保任务列表 + 重试 + 日志,大盘和告警 V1.5 补) | | 与四合一系统数据不一致 | 业务事故 | 双写期间每日对账脚本,差异 > 0 告警;切换前必须对账连续 3 天为 0 | | MQTT Broker 单点 | 整体瘫痪 | EMQX 至少双节点集群 + VIP/DNS 漂移 | | 边缘节点版本不一致 | 兼容问题 | 本地服务器协议层做版本协商,云端兼容旧版本 N-1 | --- ## 七、待办与开放问题 1. **EMQX 部署形态**:云端机房自建 vs 公有云托管,需运维侧拉齐 2. **对象存储**:大文件(视频/升级包)走哪个 OSS,断点续传 SDK 选型 3. **告警通道**:钉钉机器人 / 企业微信 / 短信,需明确接入哪一种 4. **灰度食堂选择**:建议选 1 个网络条件好的 + 1 个网络条件差的,覆盖两种典型场景 5. **历史任务数据迁移**:老 MQ 在途消息如何处理,是否需要补录到新任务表 --- ## 八、附录 ### 8.1 与现有 admin 工程的接入点 - **新增路由模块**:`src/views/edge-ops/`(dashboard / tasks / devices / resources / logs / config) - **新增 API 聚合**:`src/api/edge-ops.ts`,baseURL `/api/edge`,走云端任务网关 - **菜单配置**:`MainLayout.vue` 新增「边缘运维」一级菜单,key = `edge-ops` - **权限**:复用 `src/stores/user.ts` 的角色与权限校验,新增四个权限位 - **登录态**:完全沿用 admin 现有 token + axios 拦截器机制,**不新建鉴权体系** ### 8.2 文件清单(新增) ``` zhican/admin/ ├── src/views/edge-ops/ │ ├── dashboard/index.vue │ ├── tasks/index.vue │ ├── tasks/detail.vue │ ├── devices/index.vue │ ├── resources/index.vue │ ├── logs/index.vue │ └── config/index.vue ├── src/api/edge-ops.ts └── docs/本地服务器独立微服务化方案.md (本文件) 服务端(独立工程): zhican-edge/ ├── edge-cloud-gateway/ 云端任务网关 Spring Boot ├── edge-local-server/ 本地服务器 Spring Boot(单 JAR) └── edge-common/ 协议/DTO/任务模型共享包 ```