407 lines
18 KiB
HTML
407 lines
18 KiB
HTML
<!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>
|