fix: 接入文档页——签名规范深底配色修复;接入模型改CSS时序图(不再字符对齐);补请求参数表/curl自测/信封格式说明

This commit is contained in:
Logan.Li committed 2026-09-14 23:19:46 +08:00
1 parent 53678b3ca9
commit 722b719688
2 files changed
+104 -40

No files matched your search

+57 -40
View File
@@ -13,7 +13,27 @@
title="接入三步:① 拿到产品序列号(后台或购买页) ② 客户端调 activate 激活并保存 activation_token ③ 每周调 verify 周检(验签 sig + 时钟校验)" /> title="接入三步:① 拿到产品序列号(后台或购买页) ② 客户端调 activate 激活并保存 activation_token ③ 每周调 verify 周检(验签 sig + 时钟校验)" />
<h3 class="h3">接入模型</h3> <h3 class="h3">接入模型</h3>
<pre class="flow">{{ flowText }}</pre> <div class="flow-diagram">
<div class="flow-row">
<div class="flow-box">
<b>客户端</b>
<span>桌面 / CLI / Web 前端</span>
</div>
<div class="flow-arrows">
<span class="fa-line">① activate(序列号,设备ID)⟶</span>
<span class="fa-line back">⟵ activation_token + sig</span>
</div>
<div class="flow-box">
<b>授权服务器(双节点)</b>
<span>licence.rlwj.cn + l123.91ai.io</span>
</div>
</div>
<div class="flow-steps">
<div v-for="st in flowSteps" :key="st.title" class="flow-step">
<b>{{ st.title }}</b><span>{{ st.detail }}</span>
</div>
</div>
</div>
<h3 class="h3">客户端 API(无需认证,按 IP 限流)</h3> <h3 class="h3">客户端 API(无需认证,按 IP 限流)</h3>
<el-table :data="apis" stripe size="small"> <el-table :data="apis" stripe size="small">
@@ -23,6 +43,18 @@
<el-table-column prop="desc" label="说明" min-width="300" /> <el-table-column prop="desc" label="说明" min-width="300" />
</el-table> </el-table>
<h3 class="h3">请求体参数</h3>
<el-table :data="params" stripe size="small">
<el-table-column prop="api" label="接口" width="110" />
<el-table-column prop="body" label="JSON 请求体" min-width="480" />
</el-table>
<h3 class="h3">快速自测</h3>
<div class="code-wrap">
<el-button class="copy-btn" size="small" @click="copy(CURL_EXAMPLE, 'curl')">{{ copied === 'curl' ? '已复制' : '复制' }}</el-button>
<pre class="code"><code class="hljs" v-html="hl(CURL_EXAMPLE, 'bash')"></code></pre>
</div>
<h3 class="h3">验证/激活响应关键字段</h3> <h3 class="h3">验证/激活响应关键字段</h3>
<el-table :data="fields" stripe size="small"> <el-table :data="fields" stripe size="small">
<el-table-column prop="name" label="字段" width="150" /> <el-table-column prop="name" label="字段" width="150" />
@@ -120,8 +152,10 @@ import hljsJava from 'highlight.js/lib/languages/java'
import hljsPython from 'highlight.js/lib/languages/python' import hljsPython from 'highlight.js/lib/languages/python'
import hljsXml from 'highlight.js/lib/languages/xml' import hljsXml from 'highlight.js/lib/languages/xml'
import hljsJavascript from 'highlight.js/lib/languages/javascript' import hljsJavascript from 'highlight.js/lib/languages/javascript'
import hljsBash from 'highlight.js/lib/languages/bash'
import 'highlight.js/styles/github-dark.css' import 'highlight.js/styles/github-dark.css'
import { clientSnippets, cliSnippets, backendSnippets, API_ENDPOINTS, CANONICAL_SPEC } from './integration/snippets/index.js' import { clientSnippets, cliSnippets, backendSnippets, CANONICAL_SPEC } from './integration/snippets/index.js'
import { apis as API_ROWS, params as PARAM_ROWS, fields as FIELD_ROWS, flowSteps as FLOW_STEPS, CURL_EXAMPLE, API_ENDPOINTS as EP } from './integration/docData.js'
import request from '@/api/request' import request from '@/api/request'
hljs.registerLanguage('go', hljsGo) hljs.registerLanguage('go', hljsGo)
@@ -130,6 +164,13 @@ hljs.registerLanguage('java', hljsJava)
hljs.registerLanguage('python', hljsPython) hljs.registerLanguage('python', hljsPython)
hljs.registerLanguage('xml', hljsXml) hljs.registerLanguage('xml', hljsXml)
hljs.registerLanguage('javascript', hljsJavascript) hljs.registerLanguage('javascript', hljsJavascript)
hljs.registerLanguage('bash', hljsBash)
const apis = API_ROWS
const params = PARAM_ROWS
const fields = FIELD_ROWS
const flowSteps = FLOW_STEPS
const API_ENDPOINTS = EP
const tab = ref('overview') const tab = ref('overview')
const cliKind = ref('go') const cliKind = ref('go')
@@ -166,38 +207,7 @@ const copy = async (text, key) => {
setTimeout(() => { copied.value = '' }, 1200) setTimeout(() => { copied.value = '' }, 1200)
} }
const flowText = `┌──────────┐ ① activate(序列号, 设备ID) ┌──────────────────┐
│ 客户端 │ ────────────────────────────▶ │ 授权服务器(双节点) │
│ 桌面/CLI │ ◀──────────────────────────── │ 国内 + 香港容灾 │
│ Web前端 │ activation_token + sig └──────────────────┘
└────┬─────┘
│ ② 每周 verify / heartbeat(多端点按序尝试,超时8s切换)
│ 验签 sig → 通过则按 valid / in_grace 放行
│ ③ 本机解绑 deactivate(须携带 activation_token)
▼
离线证 LICOFF-:Ed25519 自包含验证,绑设备ID,最长365天,不依赖网络`
const apis = [
{ method: 'POST', path: '/api/v1/license/verify', who: '客户端', desc: '周检验证:返回 valid / in_grace / sig / server_time' },
{ method: 'POST', path: '/api/v1/license/activate', who: '客户端', desc: '激活占座:新设备下发一次性 activation_token' },
{ method: 'POST', path: '/api/v1/license/deactivate', who: '客户端', desc: '解绑本机:必须携带 activation_token' },
{ method: 'POST', path: '/api/v1/license/heartbeat', who: '客户端', desc: '活跃上报:刷新 last_seen(后台"在线"依据)' },
{ method: 'POST', path: '/api/v1/license/devices', who: '客户端', desc: '本序列号的已绑定设备列表' },
{ method: 'GET', path: '/api/v1/products/code/:code', who: '购买页', desc: '产品信息与定价(展示用)' },
{ method: 'POST', path: '/api/v1/orders · /checkout · /purchase/claim', who: '购买页', desc: '下单 / 支付 / 支付完成领取序列号' },
]
const fields = [
{ name: 'valid', desc: '是否有效。宽限期内为 true(配合 in_grace 判断提示续费)' },
{ name: 'status', desc: 'active / inactive / expired / revoked / suspended;吊销与暂停无宽限' },
{ name: 'expires_at / expires_at_unix', desc: '到期时间(字符串展示用;unix 为签名字段,直接回用免时区换算)' },
{ name: 'days_left / grace_days_left', desc: '剩余天数 / 宽限剩余天数' },
{ name: 'max_devices / plan_type', desc: '最大设备数 / 套餐类型(trial | monthly | yearly | perpetual)' },
{ name: 'in_grace', desc: '宽限期标记:商业过期 15 天内为 true,照常放行' },
{ name: 'server_time', desc: '服务端时间(unix 秒):客户端本地时钟不得早于历史最大值(防回拨)' },
{ name: 'sig', desc: 'Ed25519 签名(hex):验签失败按未授权处理(假服务器/篡改)' },
{ name: 'activation_token', desc: '仅 activate 响应:一次性下发的设备令牌,解绑必须携带,须持久保存' },
]
</script> </script>
<style scoped> <style scoped>
@@ -206,18 +216,25 @@ const fields = [
.h3 { margin: 20px 0 8px; font-size: 15px; font-weight: 600; } .h3 { margin: 20px 0 8px; font-size: 15px; font-weight: 600; }
.steps { line-height: 1.9; padding-left: 20px; font-size: 13px; } .steps { line-height: 1.9; padding-left: 20px; font-size: 13px; }
.steps code, .tip code { background: var(--el-fill-color); padding: 1px 6px; border-radius: 3px; } .steps code, .tip code { background: var(--el-fill-color); padding: 1px 6px; border-radius: 3px; }
.flow { .flow-diagram {
padding: 12px; padding: 14px;
background: #2f3530; background: #2f3530;
color: #cfe0d4;
border-radius: 8px; border-radius: 8px;
font: 12px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
white-space: pre;
overflow: auto;
} }
.tip { color: var(--el-text-color-secondary); font-size: 13px; line-height: 1.7; } .tip { color: var(--el-text-color-secondary); font-size: 13px; line-height: 1.7; }
.code-wrap { position: relative; margin: 12px 0 16px; } .code-wrap { position: relative; margin: 12px 0 16px; }
.copy-btn { position: absolute; right: 8px; top: 8px; z-index: 1; } .copy-btn { position: absolute; right: 8px; top: 8px; z-index: 1; }
.code { margin: 0; padding: 16px; background: #1e2227; border-radius: 8px; white-space: pre; overflow: auto; max-height: 620px; } .code { margin: 0; padding: 16px; background: #1e2227; color: #d6dbe1; border-radius: 8px; white-space: pre; overflow: auto; max-height: 620px; }
/* 接入模型时序:CSS 盒模型,避免字符画跨字体错位 // Logan.Li */
.flow-diagram .flow-row { display: flex; align-items: stretch; gap: 12px; flex-wrap: wrap; }
.flow-diagram .flow-box { flex: 1 1 180px; background: #3a4436; border: 1px solid #5b6b52; color: #cfe0d4; border-radius: 8px; padding: 12px 14px; display: flex; flex-direction: column; justify-content: center; gap: 4px; }
.flow-diagram .flow-box b { color: #fff; font-size: 14px; }
.flow-diagram .flow-box span { font-size: 12px; color: #b9c7b6; word-break: break-all; }
.flow-diagram .flow-arrows { flex: 1 1 200px; display: flex; flex-direction: column; justify-content: center; gap: 10px; }
.flow-diagram .fa-line { font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; color: #ffd666; background: #2f3530; border-radius: 6px; padding: 6px 10px; white-space: nowrap; overflow-x: auto; }
.flow-diagram .fa-line.back { color: #95d475; }
.flow-diagram .flow-steps { margin-top: 12px; display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 10px; }
.flow-diagram .flow-step { background: #2f3530; border-radius: 8px; padding: 10px 12px; font-size: 12px; line-height: 1.6; color: #cfe0d4; }
.flow-diagram .flow-step b { display: block; color: #95d475; margin-bottom: 2px; }
.code code.hljs { display: block; background: transparent; padding: 0; font: 12px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; } .code code.hljs { display: block; background: transparent; padding: 0; font: 12px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; }
</style> </style>
+47
View File
@@ -0,0 +1,47 @@
/**
* @file docData.js
* @description 接入文档页的结构化数据:API 契约表、字段表、参数表、时序步骤。 // Logan.Li
*/
export const API_ENDPOINTS = ['https://licence.rlwj.cn', 'https://l123.91ai.io']
export const apis = [
{ method: 'POST', path: '/api/v1/license/verify', who: '客户端', desc: '周检验证:返回 valid / in_grace / sig / server_time(裸对象)' },
{ method: 'POST', path: '/api/v1/license/activate', who: '客户端', desc: '激活占座:新设备下发一次性 activation_token(信封,签名在 data)' },
{ method: 'POST', path: '/api/v1/license/deactivate', who: '客户端', desc: '解绑本机:必须携带 activation_token' },
{ method: 'POST', path: '/api/v1/license/heartbeat', who: '客户端', desc: '活跃上报:刷新 last_seen(后台"在线"依据,信封同 activate)' },
{ method: 'POST', path: '/api/v1/license/devices', who: '客户端', desc: '本序列号的已绑定设备列表' },
{ method: 'GET', path: '/api/v1/products/code/:code', who: '购买页', desc: '产品信息与定价(展示用)' },
{ method: 'POST', path: '/api/v1/checkout · /orders · /purchase/claim', who: '购买页', desc: '下单 / 支付 / 支付完成领取序列号' },
]
export const params = [
{ api: 'verify', body: '{ "license_key": "LIC-…", "device_id": "设备唯一ID" }' },
{ api: 'activate', body: '{ "license_key": "LIC-…", "device_id": "设备唯一ID", "device_name": "可选展示名" }' },
{ api: 'deactivate', body: '{ "license_key": "LIC-…", "device_id": "设备唯一ID", "activation_token": "激活时下发" }' },
{ api: 'heartbeat', body: '{ "license_key": "LIC-…", "device_id": "设备唯一ID" }' },
]
export const fields = [
{ name: 'valid', desc: '是否有效。宽限期内为 true(配合 in_grace 判断提示续费)' },
{ name: 'status', desc: 'active / inactive / expired / revoked / suspended;吊销与暂停无宽限' },
{ name: 'expires_at / expires_at_unix', desc: '到期时间(字符串展示用;unix 为签名字段,直接回用免时区换算)' },
{ name: 'days_left / grace_days_left', desc: '剩余天数 / 宽限剩余天数' },
{ name: 'max_devices / plan_type', desc: '最大设备数 / 套餐类型(trial | monthly | yearly | perpetual)' },
{ name: 'in_grace', desc: '宽限期标记:商业过期 15 天内为 true,照常放行' },
{ name: 'server_time', desc: '服务端时间(unix 秒):客户端本地时钟不得早于历史最大值(防回拨)' },
{ name: 'sig', desc: 'Ed25519 签名(hex):验签失败按未授权处理(假服务器/篡改)' },
{ name: 'activation_token', desc: '仅 activate 响应:一次性下发的设备令牌,解绑必须携带,须持久保存' },
]
export const flowSteps = [
{ title: '① 激活', detail: 'activate(序列号, 设备ID) → 服务端占座并下发一次性 activation_token + 签名 sig,客户端持久保存两者' },
{ title: '② 周检', detail: '每周 verify / heartbeat(多端点按序尝试,超时 8s 切换)→ 验签 sig → 按 valid / in_grace 放行,宽限期提示续费' },
{ title: '③ 解绑', detail: '本机解绑 deactivate 必须携带 activation_token;仅凭序列号无法解绑他人设备' },
{ title: '离线证', detail: 'LICOFF- 前缀为 Ed25519 自包含验证:绑设备 ID、最长 365 天、不依赖网络,服务端仅留审计' },
]
export const CURL_EXAMPLE = `# 快速验证(替换为真实序列号)
curl -sS -X POST https://licence.rlwj.cn/api/v1/license/verify \\
-H 'Content-Type: application/json' \\
-d '{"license_key":"LIC-XXXX-XXXX-XXXX-XXXX","device_id":"demo"}'`