fastbee/docs/platform/platform-integration-flow-design.html
2026-05-31 08:42:02 +08:00

407 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>平台对接 — 流程编排设计说明</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #f5f6fa; color: #2d3436; line-height: 1.7; }
.container { max-width: 960px; margin: 0 auto; padding: 40px 24px; }
h1 { font-size: 28px; font-weight: 700; color: #1a1a2e; margin-bottom: 8px; }
.subtitle { color: #636e72; font-size: 15px; margin-bottom: 40px; }
h2 { font-size: 20px; font-weight: 600; color: #1a1a2e; margin: 40px 0 16px; padding-left: 12px; border-left: 4px solid #6c5ce7; }
h3 { font-size: 16px; font-weight: 600; color: #2d3436; margin: 24px 0 10px; }
p { margin-bottom: 12px; color: #4a4a4a; }
/* 卡片 */
.card { background: #fff; border-radius: 12px; padding: 24px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,.06); }
.card-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px; }
@media(max-width:640px){ .card-grid { grid-template-columns: 1fr; } }
/* 标签 */
.tag { display: inline-block; padding: 2px 10px; border-radius: 20px; font-size: 12px; font-weight: 600; margin-right: 6px; }
.tag-once { background: #dfe6e9; color: #636e72; }
.tag-auto { background: #d4efdf; color: #1e8449; }
.tag-rt { background: #d6eaf8; color: #1a5276; }
.tag-flow { background: #f0e6ff; color: #6c5ce7; }
/* 流程图 */
.flow { display: flex; align-items: center; flex-wrap: wrap; gap: 0; margin: 16px 0; }
.flow-step { background: #f8f9fa; border: 1.5px solid #dee2e6; border-radius: 8px; padding: 10px 16px; font-size: 13px; text-align: center; min-width: 140px; }
.flow-step .step-num { font-size: 11px; color: #adb5bd; margin-bottom: 2px; }
.flow-step .step-name { font-weight: 600; color: #2d3436; }
.flow-step .step-api { font-size: 11px; color: #6c5ce7; margin-top: 2px; font-family: monospace; }
.flow-step .step-out { font-size: 11px; color: #00b894; margin-top: 4px; }
.flow-arrow { font-size: 20px; color: #adb5bd; padding: 0 8px; }
.flow-step.highlight { border-color: #6c5ce7; background: #f8f4ff; }
.flow-step.green { border-color: #00b894; background: #f0faf7; }
.flow-step.blue { border-color: #0984e3; background: #f0f7ff; }
/* 对比表 */
.compare { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 14px; }
.compare th { background: #f8f9fa; padding: 10px 14px; text-align: left; font-weight: 600; border-bottom: 2px solid #dee2e6; }
.compare td { padding: 10px 14px; border-bottom: 1px solid #f1f3f5; vertical-align: top; }
.compare tr:last-child td { border-bottom: none; }
.compare .yes { color: #00b894; font-weight: 600; }
.compare .no { color: #d63031; }
.compare .na { color: #adb5bd; }
/* 痛点/优势 */
.pain { background: #fff5f5; border-left: 4px solid #d63031; border-radius: 0 8px 8px 0; padding: 14px 18px; margin-bottom: 12px; }
.pain .pain-title { font-weight: 600; color: #d63031; margin-bottom: 4px; }
.solve { background: #f0faf7; border-left: 4px solid #00b894; border-radius: 0 8px 8px 0; padding: 14px 18px; margin-bottom: 12px; }
.solve .solve-title { font-weight: 600; color: #00b894; margin-bottom: 4px; }
/* 阶段说明 */
.phase { display: flex; gap: 16px; margin-bottom: 16px; align-items: flex-start; }
.phase-badge { min-width: 80px; text-align: center; padding: 6px 0; border-radius: 8px; font-size: 12px; font-weight: 700; }
.phase-badge.p1 { background: #f0e6ff; color: #6c5ce7; }
.phase-badge.p2 { background: #d4efdf; color: #1e8449; }
.phase-badge.p3 { background: #d6eaf8; color: #1a5276; }
.phase-badge.p4 { background: #fef9e7; color: #b7950b; }
.phase-content { flex: 1; }
.phase-content strong { display: block; margin-bottom: 4px; }
/* 代码块 */
pre { background: #1e1e2e; color: #cdd6f4; border-radius: 8px; padding: 16px; font-size: 13px; overflow-x: auto; margin: 12px 0; line-height: 1.6; }
code { font-family: "JetBrains Mono", "Fira Code", monospace; }
.kw { color: #cba6f7; }
.str { color: #a6e3a1; }
.cm { color: #6c7086; }
.fn { color: #89b4fa; }
/* 警告框 */
.notice { background: #fffbeb; border: 1px solid #f59e0b; border-radius: 8px; padding: 14px 18px; margin: 16px 0; font-size: 14px; }
.notice strong { color: #b45309; }
hr { border: none; border-top: 1px solid #e9ecef; margin: 32px 0; }
</style>
</head>
<body>
<div class="container">
<h1>平台对接 — 流程编排设计说明</h1>
<p class="subtitle">厘清流程编排的边界:什么时候需要它,什么时候不需要它</p>
<!-- ===== 核心问题 ===== -->
<h2>一、先回答核心问题</h2>
<div class="card">
<p>用户的质疑是对的:<strong>实时推送数据只需要调一个接口,根本不需要流程编排。</strong></p>
<p>流程编排解决的是另一类问题:<strong>多个接口必须按顺序调用,且上一步的返回值是下一步的入参。</strong></p>
<p>把这两类场景混在一起设计,会让系统过度复杂。下面把它们彻底分开。</p>
</div>
<!-- ===== 四个阶段 ===== -->
<h2>二、对接 DCP 的四个阶段</h2>
<div class="card">
<div class="phase">
<div class="phase-badge p1">阶段一<br>初始化</div>
<div class="phase-content">
<strong>平台初始化 <span class="tag tag-once">一次性</span></strong>
获取并保存 userId、projectId 等平台侧的基础信息。这些信息不会变,保存到数据库后后续直接读取,不再重复调用。
</div>
</div>
<div class="phase">
<div class="phase-badge p2">阶段二<br>设备入网</div>
<div class="phase-content">
<strong>设备注册到 DCP <span class="tag tag-once">每设备一次</span></strong>
在 DCP 侧创建测点 → 绑定传感器 → 获得 DCP deviceId,保存到设备绑定表。同一设备只做一次,之后实时推送直接用这个 deviceId。
</div>
</div>
<div class="phase">
<div class="phase-badge p3">阶段三<br>Token刷新</div>
<div class="phase-content">
<strong>Token 自动续期 <span class="tag tag-auto">自动触发</span></strong>
Token 过期时自动调用认证接口,更新 access_token。这是一个单步操作,不需要流程编排,由系统在每次 HTTP 调用前自动检查处理。
</div>
</div>
<div class="phase">
<div class="phase-badge p4">阶段四<br>实时推送</div>
<div class="phase-content">
<strong>设备数据实时推送 <span class="tag tag-rt">每次上报</span></strong>
设备上报数据 → 按字段映射转换 → 调用一个推送接口。<strong>单步,无编排,直接执行。</strong>
</div>
</div>
</div>
<!-- ===== 流程编排的边界 ===== -->
<h2>三、流程编排只解决哪类问题</h2>
<div class="card-grid">
<div class="pain">
<div class="pain-title">痛点:多步串联,出参传入参</div>
<p style="font-size:13px">创建测点返回 pointId,绑定传感器时需要传 pointId。如果写死在代码里,换个平台就要改代码。</p>
</div>
<div class="solve">
<div class="solve-title">流程编排解决:可配置的串联调用</div>
<p style="font-size:13px">在 UI 里配置"第1步的出参 id → 第2步的入参 pointId",引擎自动完成传递,无需改代码。</p>
</div>
<div class="pain">
<div class="pain-title">痛点:初始化步骤因平台而异</div>
<p style="font-size:13px">DCP 需要先获取 userId 再获取 projectId,另一个平台可能直接返回 projectId。每个平台都写一套代码难以维护。</p>
</div>
<div class="solve">
<div class="solve-title">流程编排解决:平台差异配置化</div>
<p style="font-size:13px">每个平台配置自己的初始化流程,引擎统一执行,业务代码不感知平台差异。</p>
</div>
</div>
<div class="notice">
<strong>⚠️ 流程编排不适合的场景:</strong>实时数据推送。每次设备上报都走流程引擎会引入不必要的开销和复杂度。实时推送是高频操作,应该走最短路径:字段映射 → 直接 HTTP 调用。
</div>
<!-- ===== 优化后的设计 ===== -->
<h2>四、优化后的执行模式划分</h2>
<div class="card">
<table class="compare">
<thead>
<tr>
<th>场景</th>
<th>触发方式</th>
<th>执行模式</th>
<th>需要流程编排</th>
<th>频率</th>
</tr>
</thead>
<tbody>
<tr>
<td>平台初始化(获取userId/projectId)</td>
<td>手动点击</td>
<td>流程引擎</td>
<td class="yes">✓ 需要</td>
<td>一次性</td>
</tr>
<tr>
<td>设备入网(创建测点+绑定传感器)</td>
<td>手动点击</td>
<td>流程引擎</td>
<td class="yes">✓ 需要</td>
<td>每设备一次</td>
</tr>
<tr>
<td>Token 刷新</td>
<td>自动(过期前)</td>
<td>单接口调用</td>
<td class="na">— 不需要</td>
<td>按需</td>
</tr>
<tr>
<td>实时数据推送</td>
<td>设备上报触发</td>
<td>直接调用</td>
<td class="no">✗ 不需要</td>
<td>高频</td>
</tr>
<tr>
<td>定时批量推送</td>
<td>Quartz 定时</td>
<td>直接调用</td>
<td class="no">✗ 不需要</td>
<td>定时</td>
</tr>
</tbody>
</table>
</div>
<!-- ===== 三个流程的具体设计 ===== -->
<h2>五、三个流程的具体设计</h2>
<h3>流程 1:平台初始化(手动执行一次)</h3>
<div class="card">
<div class="flow">
<div class="flow-step highlight">
<div class="step-num">Step 1</div>
<div class="step-name">获取 Token</div>
<div class="step-api">POST /connect/token</div>
<div class="step-out">→ 出参: access_token</div>
</div>
<div class="flow-arrow">→</div>
<div class="flow-step">
<div class="step-num">Step 2</div>
<div class="step-name">获取用户ID</div>
<div class="step-api">GET /api/app/user/user-id</div>
<div class="step-out">→ 出参: userId</div>
</div>
<div class="flow-arrow">→</div>
<div class="flow-step">
<div class="step-num">Step 3</div>
<div class="step-name">获取项目列表</div>
<div class="step-api">GET /project/by-user-id/{userId}</div>
<div class="step-out">→ 出参: projectId</div>
</div>
</div>
<p style="font-size:13px;color:#636e72">执行完成后,<strong>userId 和 projectId 自动保存到 platform_instance 的 extra_config 字段</strong>,后续所有操作直接读取,不再重复调用。</p>
</div>
<h3>流程 2:设备入网(每个设备执行一次)</h3>
<div class="card">
<div class="flow">
<div class="flow-step green">
<div class="step-num">Step 1</div>
<div class="step-name">创建测点</div>
<div class="step-api">POST /api/app/point</div>
<div class="step-out">→ 出参: pointId</div>
</div>
<div class="flow-arrow">→</div>
<div class="flow-step green">
<div class="step-num">Step 2</div>
<div class="step-name">绑定传感器</div>
<div class="step-api">POST /api/app/inclino-sensor</div>
<div class="step-out">→ 出参: deviceId</div>
</div>
</div>
<p style="font-size:13px;color:#636e72">Step 2 的入参 <code>projectId</code> 自动从 Step 1 的出参获取。执行完成后,<strong>DCP deviceId 自动回填到 platform_device_mapping 表</strong>,设备状态变为"已入网"。</p>
<p style="font-size:13px;color:#636e72">不同传感器类型(倾角/轴力/雨量/静力水准)配置不同的 Step 2 接口,其余逻辑相同。</p>
</div>
<h3>实时推送:不走流程,直接调用</h3>
<div class="card">
<div class="flow">
<div class="flow-step blue">
<div class="step-num">触发</div>
<div class="step-name">设备上报</div>
<div class="step-api">MQTT / HTTP</div>
</div>
<div class="flow-arrow">→</div>
<div class="flow-step blue">
<div class="step-num">转换</div>
<div class="step-name">字段映射</div>
<div class="step-api">identifier → DCP字段</div>
</div>
<div class="flow-arrow">→</div>
<div class="flow-step blue">
<div class="step-num">推送</div>
<div class="step-name">调用推送接口</div>
<div class="step-api">POST /inclino-data/many</div>
</div>
<div class="flow-arrow">→</div>
<div class="flow-step blue">
<div class="step-num">记录</div>
<div class="step-name">写推送日志</div>
<div class="step-api">platform_push_log</div>
</div>
</div>
<p style="font-size:13px;color:#636e72">全程只有一次 HTTP 调用。Token 检查在调用前自动完成(过期则先刷新),对业务逻辑透明。</p>
</div>
<!-- ===== 数据流转 ===== -->
<h2>六、关键数据的存储与流转</h2>
<div class="card">
<table class="compare">
<thead>
<tr><th>数据</th><th>何时获取</th><th>存储位置</th><th>后续如何使用</th></tr>
</thead>
<tbody>
<tr>
<td>access_token</td>
<td>初始化 / 过期自动刷新</td>
<td>platform_instance.access_token</td>
<td>每次 HTTP 请求自动注入 Authorization 头</td>
</tr>
<tr>
<td>userId</td>
<td>初始化流程 Step 2</td>
<td>platform_instance.extra_config</td>
<td>设备入网流程中作为固定入参</td>
</tr>
<tr>
<td>projectId</td>
<td>初始化流程 Step 3</td>
<td>platform_instance.extra_config</td>
<td>设备入网流程中作为固定入参</td>
</tr>
<tr>
<td>DCP deviceId</td>
<td>设备入网流程 Step 2</td>
<td>platform_device_mapping.platform_device_id</td>
<td>实时推送时作为 deviceId 字段</td>
</tr>
<tr>
<td>DCP pointId</td>
<td>设备入网流程 Step 1</td>
<td>platform_device_mapping.platform_point_id</td>
<td>重新绑定时使用,一般不再需要</td>
</tr>
</tbody>
</table>
</div>
<!-- ===== 前端操作流程 ===== -->
<h2>七、前端操作流程(用户视角)</h2>
<div class="card">
<h3>第一步:新建平台</h3>
<p>填写平台名称、baseURL、账号密码 → 保存。系统自动内置 DCP 的所有接口定义,无需手动配置接口。</p>
<h3>第二步:初始化平台</h3>
<p>点击"初始化"按钮 → 系统自动执行初始化流程(获取Token → userId → projectId)→ 成功后显示"已初始化,项目:XXX"。</p>
<h3>第三步:绑定设备</h3>
<p>在设备绑定页面,搜索选择 FastBee 设备 → 选择传感器类型 → 配置字段映射(物模型字段下拉选择,DCP字段根据传感器类型预填建议值)→ 点击"入网"按钮 → 系统自动执行入网流程 → 成功后显示 DCP deviceId。</p>
<h3>第四步:开始推送</h3>
<p>设备绑定完成后,设备上报数据时自动推送,无需任何操作。在推送日志页面可查看每次推送的详情。</p>
</div>
<!-- ===== 流程编排 UI ===== -->
<h2>八、流程编排 UI 的简化设计</h2>
<div class="card">
<p>流程编排 UI 只服务于"初始化"和"设备入网"两类场景,不需要做成通用的可视化拖拽编辑器(过度设计)。</p>
<h3>简化方案:步骤列表 + 字段映射</h3>
<p>每个流程展示有序步骤列表,每步配置:</p>
<ul style="margin-left:20px;font-size:14px;color:#4a4a4a">
<li>选择接口(下拉,仅展示当前平台的接口)</li>
<li>出参保存为(输入一个 key,如 <code>step1</code>,供后续步骤引用)</li>
<li>展开字段映射:每个入参字段的来源(固定值 / 认证配置 / 上一步出参 / 平台已保存数据)</li>
</ul>
<p style="margin-top:12px">对于 DCP,系统内置两个流程的默认配置,用户开箱即用,不需要手动编排。流程编排 UI 是给高级用户或对接新平台时使用的。</p>
</div>
<!-- ===== 总结 ===== -->
<h2>九、设计总结</h2>
<div class="card">
<table class="compare">
<thead>
<tr><th>设计决策</th><th>原因</th></tr>
</thead>
<tbody>
<tr>
<td>实时推送不走流程引擎</td>
<td>高频操作,单步调用,流程引擎引入不必要开销</td>
</tr>
<tr>
<td>初始化和入网走流程引擎</td>
<td>多步串联,出参传入参,不同平台步骤不同,配置化解决</td>
</tr>
<tr>
<td>Token 刷新不是流程步骤</td>
<td>是横切关注点,在 HTTP 调用前自动处理,对业务透明</td>
</tr>
<tr>
<td>userId/projectId 保存到 extra_config</td>
<td>一次获取,永久复用,不重复调用</td>
</tr>
<tr>
<td>DCP 内置接口和流程定义</td>
<td>降低用户配置成本,开箱即用</td>
</tr>
<tr>
<td>字段映射用下拉选择,不手填</td>
<td>物模型字段和 DCP 字段都有固定枚举,选择比输入更可靠</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>