Files
platform-prototype/本地服务器独立微服务化方案.md
T
冯普andClaude Opus 4.6 6e05f10396 feat: 迁移 screenshot-modals.js 和 md-viewer.html 至 _templates/ 目录
- md-viewer.html 添加 <base href="../"> 修复子目录相对路径
- screenshot-modals.js 新增 fullPage 长图截取支持,action 改为可选

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-26 17:59:30 +08:00

16 KiB

本地服务器独立微服务化方案

项目:智能餐养系统 - 边缘服务节点重构 版本:v1.0 日期:2026-05-26 作者:技术团队 范围:面向技术团队的设计与落地方案 周期:1 个月落地,边跑边迁


一、定位与边界

本地服务器 ≠ 业务系统。本次重构将其重新定位为:部署在每个食堂边缘的独立微服务,只做"硬件 ↔ 云端"的可靠中转桥梁,与现有"健康长庆"业务系统、小程序后端、admin 后台完全解耦

做(In Scope) 不做(Out of Scope) 不混用(独立部署)
任务化数据中转 业务规则、菜品算法 不依赖小程序后端
任务链状态管理与重试 用户登录鉴权 不调用四合一业务接口
终端设备接入与协议适配 报表/统计/分析 不耦合 admin 业务模块
本地资源缓存(视频/人脸/升级包) 任何 UI 业务逻辑 独立进程、独立升级
设备远程升级

一句话原则:本地服务器只回答两个问题——云端要的数据收齐了吗?云端下发的指令落地了吗? 其他一概不管。


二、总体架构

2.1 系统拓扑

flowchart TB
    subgraph Cloud["云端"]
        CloudAPI[云端任务网关<br>Spring Boot]
        CloudDB[(任务主库<br>PostgreSQL)]
        OpsUI[运维后台<br>Vue3 + Ant Design Vue<br>复用 admin 技术栈]
    end

    subgraph Edge["食堂本地 - 每个食堂一套"]
        LS[本地服务器微服务<br>Spring Boot 独立 JAR]
        LSDB[(本地任务库<br>SQLite)]
        Cache[本地资源缓存<br>视频/人脸库/升级包]
    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 任务状态机

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)。

-- ============================================
-- 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.tsmodules 数组中新增「边缘运维」分组,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:readedge:retryedge:upgradeedge:config 四类细粒度权限
  • 仅限内部运维人员访问,不对客户开放

五、1 个月落地计划(边跑边迁)

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/任务模型共享包