Files
lingniu-vehicle-ingest/vehicle-data-platform/apps/api/internal/openplatform/assets/docs.html
T

215 lines
47 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">
<title>羚牛 · 车辆数据开放平台</title>
<style>
:root{color-scheme:light;--page:#f5f5f7;--surface:#fff;--ink:#1d1d1f;--muted:#6e6e73;--line:rgba(0,0,0,.10);--blue:#0071e3;--blue-soft:#e8f2ff;--green:#007a46;--code:#1d1d1f;--radius:18px;--sidebar:264px}
*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;font:15px/1.58 -apple-system,BlinkMacSystemFont,"SF Pro Text","PingFang SC","Microsoft YaHei",sans-serif;color:var(--ink);background:var(--page);-webkit-font-smoothing:antialiased}
a{color:inherit;text-decoration:none}.layout{min-height:100vh;display:grid;grid-template-columns:var(--sidebar) minmax(0,1fr)}
.sidebar{position:sticky;top:0;height:100vh;padding:26px 14px 20px;border-right:1px solid var(--line);background:rgba(255,255,255,.84);backdrop-filter:blur(18px);overflow-y:auto}.brand{display:block;padding:7px 11px 27px}.brand-name{display:block;font-size:17px;font-weight:650;letter-spacing:-.02em}.brand-subtitle{display:block;margin-top:3px;color:var(--muted);font-size:12px}
.nav-group{padding:15px 0 5px;border-top:1px solid var(--line)}.nav-group:first-of-type{border-top:0;padding-top:0}.nav-title{display:block;padding:0 11px 7px;color:var(--muted);font-size:12px;font-weight:600}.nav-link{display:flex;align-items:center;gap:10px;margin:2px 0;padding:8px 11px;border-radius:9px;color:#424245;font-size:14px}.nav-link:hover{color:var(--blue);background:#f3f7fc}.nav-link::before{content:"";width:5px;height:5px;border-radius:50%;background:#c7c7cc;flex:0 0 auto}.nav-link.primary::before{background:var(--blue)}.nav-link.station::before{background:var(--green)}.nav-link.docs::before{width:6px;height:6px;border-radius:2px;background:#8e8e93}.external{display:inline-flex;align-items:center;gap:8px;margin:22px 11px 0;padding:9px 10px;color:var(--blue);font-size:13px;font-weight:550}.external svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:1.8}
.content{max-width:1200px;width:100%;padding:48px 56px 96px}.intro{padding:0 0 34px;border-bottom:1px solid var(--line)}h1{margin:0;letter-spacing:-.04em;font-size:40px;line-height:1.15;font-weight:680}.intro p{max-width:720px;margin:13px 0 0;color:var(--muted);font-size:16px}.base-url{display:inline-flex;gap:8px;align-items:center;margin-top:21px;padding:8px 11px;border:1px solid var(--line);border-radius:9px;background:var(--surface);font:12px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--muted)}.base-url code{color:var(--blue)}
.section{scroll-margin-top:26px;padding:36px 0;border-bottom:1px solid var(--line)}.section:target{margin:0 -18px;padding-right:18px;padding-left:18px;border-radius:var(--radius);background:rgba(0,113,227,.035);border-bottom-color:transparent}.section:last-child{border-bottom:0}.section-head{display:flex;align-items:flex-start;justify-content:space-between;gap:20px}.section h2{margin:0;font-size:24px;letter-spacing:-.025em;line-height:1.25}.section p{margin:8px 0 0;color:var(--muted)}.endpoint{display:inline-flex;align-items:center;gap:8px;margin-top:15px;font:13px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace;color:#38383a}.method{padding:4px 7px;border-radius:6px;background:#e4f5eb;color:var(--green);font:700 11px/1 -apple-system,BlinkMacSystemFont,"SF Pro Text",sans-serif;letter-spacing:.04em}.anchor{margin-top:4px;color:#8e8e93;font-size:12px}.anchor:hover{color:var(--blue)}
.grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(300px,.92fr);gap:34px;margin-top:23px}.subhead{margin:0 0 9px;font-size:13px;font-weight:650;color:#3a3a3c}.param-table{width:100%;border-collapse:collapse;font-size:13px}.param-table th,.param-table td{padding:10px 8px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}.param-table th{color:var(--muted);font-weight:500}.param-table td:first-child{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;color:#303033}.required{color:var(--green);font-size:12px}.optional{color:var(--muted);font-size:12px}.code{margin:0;overflow:auto;padding:15px;border-radius:12px;background:var(--code);color:#f5f5f7;font:12px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace;white-space:pre}.code .key{color:#82b9ff}.code .number{color:#a8d5a2}.code .string{color:#f5cc88}.note{margin:14px 0 0;padding:11px 12px;border-left:2px solid var(--blue);background:#f4f8fd;color:#59595d;font-size:13px}.note strong{color:#303033}.errors{margin:14px 0 0;padding:0;color:#59595d;font-size:13px;list-style:none}.errors li{position:relative;padding:3px 0 3px 13px}.errors li::before{content:"";position:absolute;top:11px;left:0;width:4px;height:4px;border-radius:50%;background:#ff9f0a}.empty{margin-top:16px;color:var(--muted);font-size:13px}.field-tabs{display:flex;gap:4px;margin:0 0 14px;padding:3px;width:max-content;border-radius:9px;background:#ececf1}.field-tab{appearance:none;border:0;border-radius:7px;padding:7px 10px;background:transparent;color:#6e6e73;font:600 12px/1 -apple-system,BlinkMacSystemFont,"SF Pro Text",sans-serif;cursor:pointer}.field-tab[aria-selected="true"]{background:#fff;color:#1d1d1f;box-shadow:0 1px 3px rgba(0,0,0,.12)}.response-panel{display:none}.response-panel.active{display:block}.response-caption{margin:0 0 9px;color:var(--muted);font-size:12px}.response-table th:nth-child(1){width:31%}.response-table th:nth-child(2){width:17%}.response-table td:nth-child(2){color:#6e6e73;font:12px ui-monospace,SFMono-Regular,Menlo,monospace}.response-table td:nth-child(3){color:#4b4b4f}.tree-field{display:flex;align-items:center;gap:6px;min-height:20px;white-space:nowrap}.tree-field.depth-1{padding-left:14px}.tree-field.depth-2{padding-left:31px}.tree-field.depth-3{padding-left:48px}.tree-branch{width:9px;height:16px;border-left:1px solid #c7c7cc;border-bottom:1px solid #c7c7cc;border-radius:0 0 0 3px}.tree-root{display:inline-flex;align-items:center;gap:5px;font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text",sans-serif;font-size:12px;font-weight:650;color:#303033}.tree-root::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--blue)}.tree-node{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;color:#303033}.tree-kind{display:inline-block;margin-left:5px;padding:1px 5px;border-radius:5px;background:#f0f1f4;color:#6e6e73;font:10px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace}.enum-value{display:inline-block;margin:2px 3px 2px 0;padding:2px 5px;border-radius:5px;background:#f1f6fd;color:#1665b5;font:11px ui-monospace,SFMono-Regular,Menlo,monospace}
.auth{margin-top:20px;padding:17px 18px;border:1px solid var(--line);border-radius:14px;background:var(--surface)}.auth code{display:block;margin-top:8px;color:#38383a;font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace}.protocol-table{width:100%;margin-top:18px;border-collapse:collapse}.protocol-table th,.protocol-table td{padding:12px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}.protocol-table th{color:var(--muted);font-size:13px;font-weight:500}.protocol-table td:first-child{width:170px;font:12px ui-monospace,SFMono-Regular,Menlo,monospace}.error-table{margin-top:18px}.request-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px;margin-top:21px}.request-sample{padding:15px;border:1px solid var(--line);border-radius:14px;background:var(--surface)}.request-sample h3{margin:0 0 6px;font-size:13px;font-weight:650}.request-sample .endpoint{margin:0 0 11px;font-size:11px}.request-sample .code{padding:12px;border-radius:9px;font-size:11px}@media(max-width:680px){.request-grid{grid-template-columns:1fr}.request-sample{padding:13px}}
@media(max-width:900px){:root{--sidebar:218px}.content{padding:42px 34px 76px}.grid{grid-template-columns:1fr;gap:22px}}
@media(max-width:680px){body{background:var(--surface)}.layout{display:block}.sidebar{z-index:3;display:flex;align-items:center;gap:0;position:sticky;width:100%;height:auto;padding:0 12px;border-right:0;border-bottom:1px solid var(--line);overflow-x:auto;overflow-y:hidden;white-space:nowrap;background:rgba(255,255,255,.92)}.brand{padding:13px 10px 13px 0}.brand-name{font-size:14px}.brand-subtitle,.nav-title{display:none}.nav-group{display:contents;border:0;padding:0}.nav-link{margin:0;padding:13px 9px;font-size:13px}.nav-link::before{display:none}.external{display:none}.content{padding:31px 20px 64px}.intro{padding-bottom:27px}h1{font-size:31px}.intro p{font-size:15px}.section{padding:31px 0;scroll-margin-top:54px}.section:target{margin:0 -8px;padding-right:8px;padding-left:8px}.section-head{gap:9px}.section h2{font-size:21px}.anchor{display:none}.grid{margin-top:19px}.param-table th,.param-table td{padding:9px 5px}.param-table th:nth-child(2),.param-table td:nth-child(2){width:47px}.base-url{max-width:100%;overflow:auto}.code{font-size:11px}}
</style>
</head>
<body>
<div class="layout">
<aside class="sidebar" aria-label="接口导航">
<a class="brand" href="#overview"><span class="brand-name">羚牛 · 数据开放平台</span><span class="brand-subtitle">车辆数据 API Reference</span></a>
<nav>
<div class="nav-group"><span class="nav-title">开始使用</span><a class="nav-link docs" href="#overview">平台简介</a><a class="nav-link docs" href="#authentication">认证与授权</a><a class="nav-link docs" href="#request-examples">请求体样例</a></div>
<div class="nav-group"><span class="nav-title">车辆数据</span><a class="nav-link primary" href="#daily-hydrogen">车辆单日用氢量</a><a class="nav-link primary" href="#daily-mileage">车辆单日里程</a><a class="nav-link primary" href="#mileage-range">车辆区间日里程</a><a class="nav-link primary" href="#total-mileage">指定时刻总里程</a><a class="nav-link primary" href="#realtime">实时位置与状态</a></div>
<div class="nav-group"><span class="nav-title">加氢服务</span><a class="nav-link station" href="#stationary">加氢车辆停留核验</a><a class="nav-link station" href="#stations">加氢站地图点位</a></div>
<div class="nav-group"><span class="nav-title">通用说明</span><a class="nav-link docs" href="#response-fields">返回字段与枚举</a><a class="nav-link docs" href="#protocols">总里程协议口径</a><a class="nav-link docs" href="#errors">异常与状态码</a></div>
</nav>
<a class="external" href="/open-api/swagger/">Swagger 在线调试 <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M6 3h7v7M13 3 6.5 9.5M12 9.5V13H3V4h3.5"/></svg></a>
</aside>
<main class="content">
<header class="intro" id="overview">
<h1>接口文档</h1>
<p>面向合作方开放车辆用氢量、里程、实时状态及加氢车辆停留核验数据。每个接口独立说明请求参数、成功返回与异常情况。</p>
<span class="base-url">Base URL <code>https://open.d.lnoneos.com</code></span>
</header>
<section class="section" id="authentication"><div class="section-head"><div><h2>认证与授权</h2><p>所有接口均使用平台管理员签发的 32 位 appKey。appKey 有效期及车辆授权期必须完整覆盖查询时间。</p></div><a class="anchor" href="#authentication">#</a></div><div class="auth"><strong>请求头</strong><code>Authorization: Bearer &lt;YOUR_APP_KEY&gt;<br>Content-Type: application/json</code></div><div class="note"><strong>授权边界:</strong>不传车牌时,仅返回该 appKey 在查询日期或时间段内有效授权的全部车辆;显式传入的车牌未授权时,接口返回 403。所有接口响应均以两空格缩进的 JSON 返回,便于直接阅读和留存。</div></section>
<section class="section" id="request-examples"><div class="section-head"><div><h2>请求体样例</h2><p>所有接口使用 <code>Content-Type: application/json</code>。以下 JSON 可直接复制;未列出的可选字段可省略。</p></div><a class="anchor" href="#request-examples">#</a></div><div class="request-grid"><article class="request-sample"><h3>车辆单日用氢量</h3><div class="endpoint">/vehicles/hydrogen-consumption/query</div><pre class="code">{
"date": "2026-08-06",
"plateNumbers": ["浙F06618F"]
}</pre></article><article class="request-sample"><h3>车辆单日里程</h3><div class="endpoint">/vehicles/mileage/query</div><pre class="code">{
"date": "2026-08-06",
"plateNumbers": ["浙F06618F"],
"protocolPriority": ["GB32960", "MQTT", "JT808"]
}</pre></article><article class="request-sample"><h3>车辆区间日里程</h3><div class="endpoint">/vehicles/mileage/range/query</div><pre class="code">{
"startDate": "2026-08-01",
"endDate": "2026-08-06",
"pageSize": 100
}</pre></article><article class="request-sample"><h3>指定时刻总里程</h3><div class="endpoint">/vehicles/total-mileage/query</div><pre class="code">{
"vin": "LA9GG68L2PBAF4790",
"time": "2026-08-06 10:30:00",
"protocol": "GB32960"
}</pre></article><article class="request-sample"><h3>实时位置与状态</h3><div class="endpoint">/vehicles/realtime/query</div><pre class="code">{
"plateNumbers": ["浙F06618F"]
}</pre></article><article class="request-sample"><h3>加氢车辆停留核验</h3><div class="endpoint">/vehicles/stationary/query</div><pre class="code">{
"startTime": "2026-08-06 10:00:00",
"endTime": "2026-08-06 12:00:00",
"longitude": 120.752312,
"latitude": 30.746281,
"coordinateSystem": "GCJ02",
"radiusMeters": 8
}</pre></article><article class="request-sample"><h3>加氢站地图点位</h3><div class="endpoint">/hydrogen-stations/query</div><pre class="code">{
"province": "浙江省",
"city": "嘉兴市",
"cooperateOnly": true
}</pre></article></div></section>
<section class="section" id="daily-hydrogen"><div class="section-head"><div><h2>车辆单日用氢量</h2><div class="endpoint"><span class="method">POST</span>/api/v1/vehicles/hydrogen-consumption/query</div><p>查询车辆单日用氢量,单位 kg。</p></div><a class="anchor" href="#daily-hydrogen">#</a></div><div class="grid"><div><h3 class="subhead">请求参数</h3><table class="param-table"><tr><th>字段</th><th>必填</th><th>说明</th></tr><tr><td>date</td><td class="required"></td><td>日期,格式 yyyy-MM-dd</td></tr><tr><td>plateNumbers</td><td class="optional"></td><td>车牌数组;省略或 [] 时查询全部授权车辆</td></tr></table></div><div><h3 class="subhead">成功返回 · 200</h3><pre class="code">{<br> <span class="key">"code"</span>: <span class="string">"SUCCESS"</span>,<br> <span class="key">"data"</span>: [{<br> <span class="key">"plateNumber"</span>: <span class="string">"浙F06618F"</span>,<br> <span class="key">"hydrogenConsumptionKg"</span>: <span class="number">12.315</span>,<br> <span class="key">"status"</span>: <span class="string">"NORMAL"</span><br> }]<br>}</pre><ul class="errors"><li>400:日期或车牌格式不正确</li><li>401appKey 无效、停用或过期</li><li>403:指定车辆未授权</li></ul></div></div></section>
<section class="section" id="daily-mileage"><div class="section-head"><div><h2>车辆单日里程</h2><div class="endpoint"><span class="method">POST</span>/api/v1/vehicles/mileage/query</div><p>返回当日行驶里程、当日累计总里程和实际选用的数据协议,单位 km。</p></div><a class="anchor" href="#daily-mileage">#</a></div><div class="grid"><div><h3 class="subhead">请求参数</h3><table class="param-table"><tr><th>字段</th><th>必填</th><th>说明</th></tr><tr><td>date</td><td class="required"></td><td>日期,格式 yyyy-MM-dd</td></tr><tr><td>plateNumbers</td><td class="optional"></td><td>省略时查询全部授权车辆</td></tr><tr><td>protocolPriority</td><td class="optional"></td><td>协议选源顺序,例如 ["GB32960","MQTT","JT808"]</td></tr></table><div class="note"><strong>缺数规则:</strong>若当日没有有效里程,日里程为 0;累计总里程沿用上一个有效统计周期,计算时间也显示该周期时间。</div></div><div><h3 class="subhead">成功返回 · 200</h3><pre class="code">{<br> <span class="key">"code"</span>: <span class="string">"SUCCESS"</span>,<br> <span class="key">"data"</span>: [{<br> <span class="key">"dailyMileageKm"</span>: <span class="number">182.437</span>,<br> <span class="key">"totalMileageKm"</span>: <span class="number">12345.679</span>,<br> <span class="key">"sourceProtocol"</span>: <span class="string">"GB32960"</span>,<br> <span class="key">"status"</span>: <span class="string">"NORMAL"</span><br> }]<br>}</pre><ul class="errors"><li>400:日期、车牌或协议参数错误</li><li>403:授权期未覆盖查询日</li><li>无统计:单车以 NO_DATA 返回</li></ul></div></div></section>
<section class="section" id="mileage-range"><div class="section-head"><div><h2>车辆区间日里程</h2><div class="endpoint"><span class="method">POST</span>/api/v1/vehicles/mileage/range/query</div><p>按车辆、日期分页返回区间日里程,最长查询区间为 366 天。</p></div><a class="anchor" href="#mileage-range">#</a></div><div class="grid"><div><h3 class="subhead">请求参数</h3><table class="param-table"><tr><th>字段</th><th>必填</th><th>说明</th></tr><tr><td>startDate / endDate</td><td class="required"></td><td>日期区间,yyyy-MM-dd</td></tr><tr><td>plateNumbers</td><td class="optional"></td><td>车牌数组,最多 5000 辆</td></tr><tr><td>protocolPriority</td><td class="optional"></td><td>协议选源顺序</td></tr><tr><td>pageSize / cursor</td><td class="optional"></td><td>分页大小及下一页游标</td></tr></table></div><div><h3 class="subhead">成功返回 · 200</h3><pre class="code">{<br> <span class="key">"code"</span>: <span class="string">"SUCCESS"</span>,<br> <span class="key">"data"</span>: [{<br> <span class="key">"date"</span>: <span class="string">"2026-08-06"</span>,<br> <span class="key">"dailyMileageKm"</span>: <span class="number">182.437</span>,<br> <span class="key">"totalMileageKm"</span>: <span class="number">12345.679</span><br> }],<br> <span class="key">"nextCursor"</span>: <span class="string">null</span><br>}</pre><ul class="errors"><li>400:区间超限或游标与原参数不一致</li><li>403:授权未完整覆盖查询区间</li></ul></div></div></section>
<section class="section" id="total-mileage"><div class="section-head"><div><h2>指定时刻总里程</h2><div class="endpoint"><span class="method">POST</span>/api/v1/vehicles/total-mileage/query</div><p>按 VIN 返回不晚于指定时刻的最近一条总里程及实际采集协议。</p></div><a class="anchor" href="#total-mileage">#</a></div><div class="grid"><div><h3 class="subhead">请求参数</h3><table class="param-table"><tr><th>字段</th><th>必填</th><th>说明</th></tr><tr><td>vin</td><td class="required"></td><td>17 位已授权 VIN</td></tr><tr><td>time</td><td class="required"></td><td>北京时间 yyyy-MM-dd HH:mm:ss</td></tr><tr><td>protocol</td><td class="optional"></td><td>GB32960、MQTT 或 JT808;省略时按默认顺序选取</td></tr></table></div><div><h3 class="subhead">成功返回 · 200</h3><pre class="code">{<br> <span class="key">"code"</span>: <span class="string">"SUCCESS"</span>,<br> <span class="key">"data"</span>: {<br> <span class="key">"totalMileageKm"</span>: <span class="number">12345.678</span>,<br> <span class="key">"protocol"</span>: <span class="string">"GB32960"</span>,<br> <span class="key">"recordTime"</span>: <span class="string">"2026-08-06 10:29:45"</span>,<br> <span class="key">"timeDifferenceSeconds"</span>: <span class="number">15</span><br> }<br>}</pre><ul class="errors"><li>400VIN、时间或协议不合法</li><li>403VIN 未授权</li><li>无记录:返回 NO_DATA</li></ul></div></div></section>
<section class="section" id="realtime"><div class="section-head"><div><h2>车辆实时位置与状态</h2><div class="endpoint"><span class="method">POST</span>/api/v1/vehicles/realtime/query</div><p>返回最新位置、在线状态、速度、总里程、实际协议和记录时间。</p></div><a class="anchor" href="#realtime">#</a></div><div class="grid"><div><h3 class="subhead">请求参数</h3><table class="param-table"><tr><th>字段</th><th>必填</th><th>说明</th></tr><tr><td>plateNumbers</td><td class="optional"></td><td>省略或 [] 时返回全部当前有效授权车辆</td></tr></table></div><div><h3 class="subhead">成功返回 · 200</h3><pre class="code">{<br> <span class="key">"data"</span>: [{<br> <span class="key">"vin"</span>: <span class="string">"LA9GG68L2PBAF4790"</span>,<br> <span class="key">"plateNumber"</span>: <span class="string">"浙F06618F"</span>,<br> <span class="key">"sourceProtocol"</span>: <span class="string">"GB32960"</span>,<br> <span class="key">"longitude"</span>: <span class="number">120.75</span>,<br> <span class="key">"latitude"</span>: <span class="number">30.74</span>,<br> <span class="key">"speedKmh"</span>: <span class="number">0</span>,<br> <span class="key">"totalMileageKm"</span>: <span class="number">12345.678</span>,<br> <span class="key">"recordTime"</span>: <span class="string">"2026-08-17 17:40:12"</span>,<br> <span class="key">"timeDifferenceSeconds"</span>: <span class="number">8</span>,<br> <span class="key">"online"</span>: <span class="number">true</span>,<br> <span class="key">"activeToday"</span>: <span class="number">true</span>,<br> <span class="key">"motionStatus"</span>: <span class="string">"idle"</span>,<br> <span class="key">"locationAvailable"</span>: <span class="number">true</span>,<br> <span class="key">"status"</span>: <span class="string">"NORMAL"</span><br> }]<br>}</pre><ul class="errors"><li>400:车牌格式错误</li><li>403:指定车牌未授权</li><li>无实时记录:返回 NO_DATA</li></ul></div></div></section>
<section class="section" id="stationary"><div class="section-head"><div><h2>加氢车辆停留核验</h2><div class="endpoint"><span class="method">POST</span>/api/v1/vehicles/stationary/query</div><p>按站点坐标、坐标系、时间段和半径,核验速度接近 0 的车辆停留记录,并按匹配度降序返回。</p></div><a class="anchor" href="#stationary">#</a></div><div class="grid"><div><h3 class="subhead">请求参数</h3><table class="param-table"><tr><th>字段</th><th>必填</th><th>说明</th></tr><tr><td>startTime / endTime</td><td class="required"></td><td>北京时间;查询区间最长 24 小时</td></tr><tr><td>longitude / latitude</td><td class="required"></td><td>加氢站经纬度;由 coordinateSystem 说明坐标系</td></tr><tr><td>coordinateSystem</td><td class="optional"></td><td>WGS84(默认)或 GCJ02(高德);GCJ02 自动转换后核验</td></tr><tr><td>radiusMeters</td><td class="optional"></td><td>核验半径,单位 m;1–100,默认 5</td></tr><tr><td>plateNumbers</td><td class="optional"></td><td>省略时核验全部有效授权车辆</td></tr></table><div class="note"><strong>匹配规则:</strong>速度 ≤ 3 km/h,至少 2 个样本且停留 ≥ 60 秒;相邻定位点间隔超过 10 分钟时拆分停留区间。</div></div><div><h3 class="subhead">成功返回 · 200</h3><pre class="code">{<br> <span class="key">"data"</span>: [{<br> <span class="key">"plateNumber"</span>: <span class="string">"浙F06618F"</span>,<br> <span class="key">"stayStartTime"</span>: <span class="string">"2026-08-06 10:16:02"</span>,<br> <span class="key">"stayEndTime"</span>: <span class="string">"2026-08-06 10:42:18"</span>,<br> <span class="key">"stayDurationSeconds"</span>: <span class="number">1576</span>,<br> <span class="key">"matchScore"</span>: <span class="number">93.842</span><br> }]<br>}</pre><ul class="errors"><li>400:时间、坐标、坐标系或半径不合法</li><li>403:授权期未覆盖整个时间段</li><li>无匹配:data 为空数组</li></ul></div></div></section>
<section class="section" id="stations"><div class="section-head"><div><h2>加氢站地图点位</h2><div class="endpoint"><span class="method">POST</span>/api/v1/hydrogen-stations/query</div><p>只读查询有有效坐标的加氢站,可按行政区划和合作属性筛选。</p></div><a class="anchor" href="#stations">#</a></div><div class="grid"><div><h3 class="subhead">请求参数</h3><table class="param-table"><tr><th>字段</th><th>必填</th><th>说明</th></tr><tr><td>province / city</td><td class="optional"></td><td>行政区划筛选</td></tr><tr><td>cooperateOnly</td><td class="optional"></td><td>true 仅合作站;false 仅外部站</td></tr></table></div><div><h3 class="subhead">成功返回 · 200</h3><pre class="code">{<br> <span class="key">"data"</span>: [{<br> <span class="key">"id"</span>: <span class="string">"1"</span>,<br> <span class="key">"name"</span>: <span class="string">"示例加氢站"</span>,<br> <span class="key">"longitude"</span>: <span class="number">120.752312</span>,<br> <span class="key">"latitude"</span>: <span class="number">30.746281</span>,<br> <span class="key">"cooperative"</span>: <span class="number">true</span><br> }]<br>}</pre><ul class="errors"><li>400:行政区划参数过长</li><li>401appKey 无效</li><li>无符合站点:data 为空数组</li></ul></div></div></section>
<section class="section" id="response-fields"><div class="section-head"><div><h2>返回字段与枚举说明</h2><p>所有成功响应外层固定包含 code、message、data 和 traceId;以下为 data 内的字段。无数据时按对应接口返回 null、NO_DATA 或空数组。</p></div><a class="anchor" href="#response-fields">#</a></div><h3 class="subhead" style="margin-top:22px">通用响应与状态</h3><table class="protocol-table"><tr><th>字段/枚举</th><th>说明</th></tr><tr><td>code = SUCCESS</td><td>请求已成功处理。业务数据是否存在由每条数据的 status 判断。</td></tr><tr><td>message</td><td>成功时固定为 success。</td></tr><tr><td>traceId</td><td>本次请求唯一追踪标识;异常排查时请完整提供。</td></tr><tr><td>status = NORMAL</td><td>存在可用数据,相关数值、协议和时间字段有效。</td></tr><tr><td>status = NO_DATA</td><td>在授权范围内未找到可用数据;可选数值、位置、时间字段为 null 或省略。</td></tr><tr><td>status = DATA_ANOMALY</td><td>检测到数据异常,当前不返回可能错误的里程;dataQuality 给出原因。</td></tr><tr><td>dataQuality = TOTAL_MILEAGE_ROLLBACK</td><td>终端累计里程明显回退,已阻止其作为正常累计里程返回。</td></tr></table><h3 class="subhead" style="margin-top:28px">车辆单日用氢量</h3><table class="protocol-table"><tr><th>字段</th><th>说明</th></tr><tr><td>plateNumber / date</td><td>车牌与查询自然日(Asia/Shanghai)。</td></tr><tr><td>hydrogenConsumptionKg</td><td>当日用氢量,单位 kg;无有效数据时为 null。</td></tr><tr><td>status</td><td>NORMAL 表示已计算;NO_DATA 表示无可用统计。</td></tr></table><h3 class="subhead" style="margin-top:28px">车辆单日里程与区间日里程</h3><table class="protocol-table"><tr><th>字段</th><th>说明</th></tr><tr><td>vin / plateNumber / date</td><td>车辆唯一 VIN、车牌和统计自然日。</td></tr><tr><td>dailyMileageKm</td><td>当日行驶里程,单位 km。缺少当日记录且可前向补齐时为 0。</td></tr><tr><td>totalMileageKm</td><td>所选协议的日末累计总里程,单位 km;不会用 GPS 日里程估算值替代。</td></tr><tr><td>sourceProtocol</td><td>实际采用的协议:GB32960、MQTT 或 JT808。</td></tr><tr><td>dataTime</td><td>实际采用的最后一条车辆源数据时间。</td></tr><tr><td>updatedAt</td><td>该统计周期的计算时间;前向补齐时保持上一有效统计周期时间。</td></tr><tr><td>snapshotId / nextCursor</td><td>仅区间接口返回;snapshotId 固定本次分页车辆范围,nextCursor 为下一页游标,最后一页为 null。</td></tr></table><h3 class="subhead" style="margin-top:28px">指定时刻总里程</h3><table class="protocol-table"><tr><th>字段</th><th>说明</th></tr><tr><td>vin / queryTime</td><td>查询车辆 VIN 与请求的北京时间。</td></tr><tr><td>totalMileageKm</td><td>不晚于请求时间的最近一条累计总里程,单位 km。</td></tr><tr><td>protocol / protocolInput</td><td>protocol 为实际命中协议;protocolInput 为请求显式传入的协议,未传则省略。</td></tr><tr><td>recordTime / timeDifferenceSeconds</td><td>实际命中记录时间,以及请求时间与记录时间的差值,单位秒。</td></tr><tr><td>mileageMeaning</td><td>该协议累计里程的业务口径;JT808 为定位终端/GPS侧累计值。</td></tr></table><h3 class="subhead" style="margin-top:28px">车辆实时位置与状态</h3><table class="protocol-table"><tr><th>字段/枚举</th><th>说明</th></tr><tr><td>sourceProtocol</td><td>本条位置、速度、累计里程和记录时间实际采用的协议:GB32960、MQTT 或 JT808。</td></tr><tr><td>longitude / latitude</td><td>最新有效经纬度;无有效位置时为 nulllocationAvailable 为 false。</td></tr><tr><td>speedKmh / totalMileageKm</td><td>所选来源的瞬时速度(km/h)和累计总里程(km)。</td></tr><tr><td>recordTime / timeDifferenceSeconds</td><td>所选来源的记录时间,以及与当前查询时间的差值,单位秒。</td></tr><tr><td>online</td><td>true:任一协议最近 60 秒内上报;false:没有任一协议满足该实时阈值。</td></tr><tr><td>activeToday</td><td>true:任一协议在当前自然日曾上报;不等同于实时 online。</td></tr><tr><td>motionStatus = driving</td><td>在线且所选来源速度大于 3 km/h。</td></tr><tr><td>motionStatus = idle</td><td>在线且所选来源速度不大于 3 km/h。</td></tr><tr><td>motionStatus = offline</td><td>当前不在线,或没有实时记录。</td></tr><tr><td>protocol</td><td>旧版兼容字段;新接入请使用 sourceProtocol。</td></tr></table><h3 class="subhead" style="margin-top:28px">加氢车辆停留核验</h3><table class="protocol-table"><tr><th>字段</th><th>说明</th></tr><tr><td>stayStartTime / stayEndTime</td><td>满足核验条件的连续停留开始、结束时间。</td></tr><tr><td>stayDurationSeconds / stayDurationMinutes</td><td>停留时长,分别以秒、分钟表示。</td></tr><tr><td>matchScore</td><td>0–100;位置越接近、速度越低、停留越久、样本越充分,分数越高。</td></tr><tr><td>averageDistanceMeters / maxDistanceMeters</td><td>定位点相对输入站点坐标的平均/最大距离,单位 m。</td></tr><tr><td>averageSpeedKmh / maxSpeedKmh / matchedSamples</td><td>核验区间内速度统计与匹配定位样本数。</td></tr><tr><td>sourceProtocols</td><td>本停留片段采用过的协议数组,枚举值为 GB32960、MQTT、JT808。</td></tr></table><h3 class="subhead" style="margin-top:28px">加氢站地图点位</h3><table class="protocol-table"><tr><th>字段</th><th>说明</th></tr><tr><td>id / name / shortName</td><td>站点唯一标识、标准名称和简称;id 为字符串以避免 JavaScript 精度丢失。</td></tr><tr><td>address / province / city / district</td><td>站点地址及行政区划。</td></tr><tr><td>longitude / latitude</td><td>站点坐标。</td></tr><tr><td>cooperative</td><td>true:合作站(内部站或已导入合作名录);false:外部站。</td></tr></table></section>
<section class="section" id="protocols"><div class="section-head"><div><h2>总里程协议口径</h2><p>未指定 protocol 时,按 GB32960 &gt; MQTT &gt; JT808 选择第一个有数据的协议;不会跨协议拼接里程。</p></div><a class="anchor" href="#protocols">#</a></div><table class="protocol-table"><tr><th>规范值</th><th>总里程含义</th></tr><tr><td>GB32960</td><td>车辆仪表盘累计总里程,对应 GB/T 32960 整车数据累计里程。</td></tr><tr><td>MQTT</td><td>车辆仪表盘或车端控制器累计总里程,由 MQTT 平台上报。</td></tr><tr><td>JT808</td><td>定位终端累计里程,由 GPS/终端侧计算,不等同于车辆仪表盘里程。</td></tr></table></section>
<section class="section" id="errors"><div class="section-head"><div><h2>异常与状态码</h2><p>所有响应均包含 <code>code</code><code>message</code><code>traceId</code>;请在工单中提供 traceId 以便排查。</p></div><a class="anchor" href="#errors">#</a></div><table class="protocol-table error-table"><tr><th>HTTP</th><th>code</th><th>说明</th></tr><tr><td>200</td><td>SUCCESS</td><td>查询成功;无统计数据时,数据行的 status 为 NO_DATA。</td></tr><tr><td>400</td><td>INVALID_REQUEST</td><td>请求格式、数量、日期、时间、坐标或协议不正确。</td></tr><tr><td>401</td><td>UNAUTHORIZED</td><td>appKey 不存在、停用或已过期。</td></tr><tr><td>403</td><td>FORBIDDEN</td><td>车辆或 appKey 授权期未覆盖请求时间。</td></tr><tr><td>500</td><td>INTERNAL_ERROR</td><td>服务内部异常;请提供 traceId。</td></tr></table></section>
</main>
</div>
<script>
(() => {
const common = [
["code", "string", "固定为 SUCCESS,表示 HTTP 请求处理成功;请结合 data 内每条 status 判断是否有业务数据。"],
["message", "string", "成功时固定为 success。"],
["traceId", "string", "本次请求的唯一追踪标识;出现问题时请完整提供给羚牛技术支持。"]
];
const fields = {
"daily-hydrogen": [
["data[]", "array", "按请求车牌顺序返回的车辆数据数组。"],
["data[].plateNumber", "string", "车辆车牌号。"],
["data[].date", "date", "查询自然日,格式 yyyy-MM-dd,时区 Asia/Shanghai。"],
["data[].hydrogenConsumptionKg", "number | null", "当日用氢量,单位 kg;无有效统计时为 null。"],
["data[].status", "enum", "<span class='enum-value'>NORMAL</span> 已计算出有效用氢量;<span class='enum-value'>NO_DATA</span> 无可用统计。"]
],
"daily-mileage": [
["data[]", "array", "按请求车牌顺序返回的车辆数据数组。"],
["data[].vin", "string", "车辆唯一 VIN。"], ["data[].plateNumber", "string", "车辆车牌号。"], ["data[].date", "date", "查询自然日,yyyy-MM-ddAsia/Shanghai。"],
["data[].dailyMileageKm", "number | null", "当日行驶里程,单位 km;若当日无记录但存在此前有效累计里程,则返回 0。"],
["data[].totalMileageKm", "number | null", "日末累计总里程,单位 km;仅采用终端累计里程,不以 GPS 日里程估算替代。"],
["data[].sourceProtocol", "enum | null", "<span class='enum-value'>GB32960</span> GB/T 32960 车辆仪表累计里程;<span class='enum-value'>MQTT</span> 车端/MQTT 平台累计里程;<span class='enum-value'>JT808</span> 定位终端/GPS侧累计里程。"],
["data[].dataTime", "datetime | null", "本条统计实际采用的最后一条车辆源数据时间。"], ["data[].updatedAt", "datetime | null", "本行统计计算时间;前向补齐时为上一个有效统计周期的计算时间。"],
["data[].status", "enum", "<span class='enum-value'>NORMAL</span> 有效;<span class='enum-value'>NO_DATA</span> 无数据;<span class='enum-value'>DATA_ANOMALY</span> 发现异常,里程不返回。"],
["data[].dataQuality", "enum | null", "仅 DATA_ANOMALY 时返回。<span class='enum-value'>TOTAL_MILEAGE_ROLLBACK</span> 表示终端累计里程明显回退,已拦截。"]
],
"mileage-range": [
["data[]", "array", "当前页的车辆×日期数据,按请求区间与授权车辆快照排序。单条字段与“车辆单日里程”完全一致。"],
["data[].vin / plateNumber / date", "string / date", "车辆 VIN、车牌和该行统计自然日。"], ["data[].dailyMileageKm", "number | null", "当日里程,单位 km。"], ["data[].totalMileageKm", "number | null", "该日累计总里程,单位 km。"],
["data[].sourceProtocol", "enum | null", "<span class='enum-value'>GB32960</span> / <span class='enum-value'>MQTT</span> / <span class='enum-value'>JT808</span>;含义见单日里程接口。"],
["data[].dataTime / updatedAt", "datetime | null", "采用源数据时间 / 统计计算时间。"], ["data[].status / dataQuality", "enum | null", "NORMAL、NO_DATA、DATA_ANOMALY;异常原因目前为 TOTAL_MILEAGE_ROLLBACK。"],
["snapshotId", "string", "本次分页授权车辆快照标识;后续翻页必须保持原参数并继续使用。"], ["nextCursor", "string | null", "下一页游标;为 null 表示已到最后一页。"]
],
"total-mileage": [
["data.vin", "string", "查询车辆 VIN。"], ["data.queryTime", "datetime", "请求指定的北京时间。"], ["data.totalMileageKm", "number | null", "不晚于 queryTime 的最近一条累计总里程,单位 km;无记录时为 null。"],
["data.protocol", "enum | null", "实际命中协议。<span class='enum-value'>GB32960</span> 仪表累计;<span class='enum-value'>MQTT</span> 车端累计;<span class='enum-value'>JT808</span> 定位/GPS累计。"],
["data.protocolInput", "string | null", "请求中传入的协议原值;未指定时为 null/省略。"], ["data.mileageMeaning", "string | null", "当前 protocol 的累计里程业务口径说明。"],
["data.recordTime", "datetime | null", "实际命中的源数据记录时间,北京时间。"], ["data.timeDifferenceSeconds", "integer | null", "queryTime 减 recordTime 的秒数。"],
["data.selectionPolicy", "string", "未指定协议时使用的默认选源顺序。"], ["data.status", "enum", "<span class='enum-value'>NORMAL</span> 找到记录;<span class='enum-value'>NO_DATA</span> 未找到不晚于请求时间的记录。"]
],
"realtime": [
["data[]", "array", "按请求车牌顺序返回;省略车牌时返回当前授权范围内全部车辆。"], ["data[].vin / plateNumber", "string", "车辆唯一 VIN / 车牌。"],
["data[].protocol", "enum | null", "本条位置、速度、累计里程和记录时间实际采用的唯一协议字段。<span class='enum-value'>GB32960</span> 车辆协议;<span class='enum-value'>MQTT</span> MQTT 车端来源;<span class='enum-value'>JT808</span> 定位终端来源。"],
["data[].longitude / latitude", "number | null", "所选来源的最新有效经度/纬度;无位置时为 null。"], ["data[].speedKmh", "number | null", "所选来源瞬时速度,单位 km/h。"], ["data[].socPercent", "number", "所选来源的动力电池荷电状态,单位 %;仅在采集值为 0–100 时返回,无值或无效值时字段省略。"], ["data[].totalMileageKm", "number | null", "所选来源累计总里程,单位 km。"],
["data[].recordTime", "datetime | null", "所选来源实际记录时间,北京时间。"], ["data[].timeDifferenceSeconds", "integer | null", "当前查询时间减 recordTime,单位秒。"],
["data[].online", "boolean", "true:任一采集协议在最近 60 秒内上报;false:没有协议满足实时阈值。"], ["data[].activeToday", "boolean", "true:任一采集协议在当前自然日曾上报;它不等同于 online。"],
["data[].motionStatus", "enum", "<span class='enum-value'>driving</span> 在线且所选速度 &gt; 3 km/h<span class='enum-value'>idle</span> 在线且速度 ≤ 3 km/h<span class='enum-value'>offline</span> 当前不在线或无实时记录。"],
["data[].locationAvailable", "boolean", "truelongitude 与 latitude 有效;false:当前无有效位置。"], ["data[].status", "enum", "<span class='enum-value'>NORMAL</span> 找到实时记录;<span class='enum-value'>NO_DATA</span> 无实时记录。"]
],
"stationary": [
["data[]", "array", "满足站点停留匹配条件的车辆片段;无匹配时为空数组。"], ["data[].vin / plateNumber", "string", "车辆唯一 VIN / 车牌。"],
["data[].stayStartTime / stayEndTime", "datetime", "连续停留片段的起止定位时间,北京时间。"], ["data[].stayDurationSeconds / stayDurationMinutes", "integer / number", "停留时长,分别以秒/分钟表示。"],
["data[].matchScore", "number", "0–100;位置越接近、速度越低、停留越久、样本越充分,分数越高。"], ["data[].averageDistanceMeters / maxDistanceMeters", "number", "定位点相对输入站点坐标的平均/最大距离,单位 m。"],
["data[].averageSpeedKmh / maxSpeedKmh", "number", "停留片段内平均/最大速度,单位 km/h。"], ["data[].matchedSamples", "integer", "用于该停留片段的定位样本数,至少为 2。"],
["data[].sourceProtocols", "array<enum>", "片段中实际采用过的协议数组:GB32960、MQTT、JT808。"]
],
"stations": [
["data[]", "array", "符合行政区划与合作属性筛选条件的站点数组;无符合项时为空数组。"], ["data[].id", "string", "站点 ID 使用字符串,避免 JavaScript 大整数精度丢失。"], ["data[].name / shortName", "string", "站点标准名称 / 简称。"],
["data[].address", "string", "站点地址。"], ["data[].longitude / latitude", "number", "站点经纬度。"], ["data[].province / city / district", "string", "站点省、市、区县行政区划。"],
["data[].cooperative", "boolean", "true:合作站(内部站或已导入合作名录);false:外部站。"]
]
};
const sectionIds = Object.keys(fields);
const esc = value => String(value).replace(/[&<>\"]/g, char => ({"&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;"}[char]));
const expandFields = rows => rows.flatMap(([path, type, description]) => {
if (path === "data[]") return [[path, type, description]];
const prefix = path.startsWith("data[].") ? "data[]." : (path.startsWith("data.") ? "data." : "");
const names = path.slice(prefix.length).split(" / ");
const types = type.split(" / ");
return names.map((name, index) => [prefix + name, types[index] || type, description]);
});
const treeCell = (depth, name, kind, root) => {
const branch = root ? "" : "<span class='tree-branch' aria-hidden='true'></span>";
const label = root ? "<span class='tree-root'>" + esc(name) + "</span>" : "<span class='tree-node'>" + esc(name) + "</span>";
return "<div class='tree-field depth-" + depth + "'>" + branch + label + (kind ? "<span class='tree-kind'>" + esc(kind) + "</span>" : "") + "</div>";
};
const renderResponseTree = rows => {
const expanded = expandFields(rows);
const dataArray = expanded.find(row => row[0] === "data[]");
const dataObject = expanded.some(row => row[0].startsWith("data."));
const regular = expanded.filter(row => row[0] !== "data[]");
const outer = regular.filter(row => !row[0].startsWith("data.") && !row[0].startsWith("data[]."));
const nested = regular.filter(row => row[0].startsWith("data.") || row[0].startsWith("data[]."));
let html = "<tr class='tree-root-row'><td>" + treeCell(0, "响应对象", "object", true) + "</td><td>object</td><td>本接口成功响应的 JSON 根对象。</td></tr>";
html += common.map(row => "<tr><td>" + treeCell(1, row[0], "", false) + "</td><td>" + esc(row[1]) + "</td><td>" + row[2] + "</td></tr>").join("");
if (dataArray) {
html += "<tr><td>" + treeCell(1, "data", "array", false) + "</td><td>array</td><td>" + dataArray[2] + "</td></tr>";
html += "<tr><td>" + treeCell(2, "[]", "object", false) + "</td><td>object</td><td>data 数组中的一条车辆/站点数据。</td></tr>";
} else if (dataObject) {
html += "<tr><td>" + treeCell(1, "data", "object", false) + "</td><td>object</td><td>本次查询对应的一条业务数据。</td></tr>";
}
html += nested.map(row => {
const name = row[0].replace(/^data(?:\[\])?\./, "");
return "<tr><td>" + treeCell(dataArray ? 3 : 2, name, "", false) + "</td><td>" + esc(row[1]) + "</td><td>" + row[2] + "</td></tr>";
}).join("");
html += outer.map(row => "<tr><td>" + treeCell(1, row[0], "", false) + "</td><td>" + esc(row[1]) + "</td><td>" + row[2] + "</td></tr>").join("");
return html;
};
sectionIds.forEach(id => {
const section = document.getElementById(id);
const left = section && section.querySelector(".grid > div:first-child");
if (!left) return;
const requestTitle = left.querySelector(".subhead");
const requestTable = left.querySelector("table");
if (!requestTitle || !requestTable) return;
requestTitle.remove();
const tabs = document.createElement("div"); tabs.className = "field-tabs";
const requestButton = document.createElement("button"); requestButton.className = "field-tab"; requestButton.type = "button"; requestButton.textContent = "请求参数"; requestButton.setAttribute("aria-selected", "true");
const responseButton = document.createElement("button"); responseButton.className = "field-tab"; responseButton.type = "button"; responseButton.textContent = "返回字段"; responseButton.setAttribute("aria-selected", "false");
tabs.append(requestButton, responseButton);
const requestPanel = document.createElement("div"); requestPanel.className = "request-panel"; requestPanel.append(requestTitle, requestTable);
const responsePanel = document.createElement("div"); responsePanel.className = "response-panel";
responsePanel.innerHTML = "<h3 class='subhead'>返回字段</h3><p class='response-caption'>按响应 JSON 的对象层级展示;数组元素以 <code>[]</code> 标记,枚举与空值规则已逐项说明。</p><table class='param-table response-table'><tr><th>响应结构</th><th>类型</th><th>说明/枚举</th></tr>" + renderResponseTree(fields[id]) + "</table>";
left.replaceChildren(tabs, requestPanel, responsePanel);
requestButton.addEventListener("click", () => { requestButton.setAttribute("aria-selected", "true"); responseButton.setAttribute("aria-selected", "false"); requestPanel.style.display = "block"; responsePanel.classList.remove("active"); });
responseButton.addEventListener("click", () => { responseButton.setAttribute("aria-selected", "true"); requestButton.setAttribute("aria-selected", "false"); requestPanel.style.display = "none"; responsePanel.classList.add("active"); });
});
const realtimeSample = document.querySelector("#realtime .code");
if (realtimeSample) realtimeSample.innerHTML = realtimeSample.innerHTML.replace("sourceProtocol", "protocol").replace('<span class="key">"speedKmh"</span>:', '<span class="key">"socPercent"</span>: <span class="number">78.5</span>,<br> <span class="key">"speedKmh"</span>:');
})();
</script>
</body>
</html>