spark-server/internal/transport/http/docs/index.html
DESKTOP-L8OUL9F\gfl0034 b10c5d63a3 feat(brand): 三站替换四色火焰 logo 与 favicon
官网/docs 嵌入新品牌图;sparkapi nginx 可在未重建镜像时提供 /favicon.ico 与页头 logo。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-24 10:25:44 +08:00

299 lines
12 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"/>
<title>Spark IM API</title>
<link rel="icon" href="/favicon.ico?v=20260924-flames" sizes="any"/>
<link rel="icon" href="/docs/static/favicon-32.png?v=20260924-flames" type="image/png" sizes="32x32"/>
<link rel="apple-touch-icon" href="/apple-touch-icon.png?v=20260924-flames"/>
<script>
(function () {
var p = location.pathname.replace(/\/+$/, "") || "/docs";
var k = p === "/docs/enums" ? "enums" : p === "/docs/errors" ? "errors" : "api";
document.documentElement.setAttribute("data-page", k);
})();
</script>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.17.14/swagger-ui.css"/>
<style>
body { margin: 0; font-family: sans-serif; color: #3b4151; }
a { color: #3b82f6; }
a:hover { text-decoration: underline; }
.offline { padding: 16px; }
.docs-bar {
display: flex; align-items: center; gap: 24px; flex-wrap: wrap;
padding: 10px 20px; border-bottom: 1px solid #e0e0e0; background: #fafafa;
position: sticky; top: 0; z-index: 20;
}
.docs-brand {
display: inline-flex; align-items: center; gap: 8px; color: #3b4151;
font-weight: 700; text-decoration: none;
}
.docs-brand img {
width: 28px; height: 28px; border-radius: 6px; display: block;
}
.docs-bar strong { color: #3b4151; }
.docs-nav { display: flex; gap: 20px; margin: 0; padding-left: 22px; }
.docs-nav a { color: #3b4151; text-decoration: none; }
html[data-page="api"] #nav-api,
html[data-page="enums"] #nav-enums,
html[data-page="errors"] #nav-errors { color: #3b82f6; font-weight: 600; }
.docs-bar select { font: inherit; }
.page { display: none; }
html[data-page="api"] #page-api,
html[data-page="enums"] #page-enums,
html[data-page="errors"] #page-errors { display: block; }
.catalog { max-width: 1100px; padding: 16px 20px 48px; }
.catalog h1 { margin: 0 0 8px; font-size: 22px; }
.lead { margin: 0 0 16px; color: #6b7280; font-size: 14px; }
.layout { display: grid; grid-template-columns: 220px 1fr; gap: 24px; align-items: start; }
.toc {
position: sticky; top: 56px; border: 1px solid #e5e7eb; border-radius: 8px;
padding: 12px; background: #fff; max-height: calc(100vh - 80px); overflow: auto;
}
.toc h2 { margin: 0 0 8px; font-size: 13px; }
.toc ol { margin: 0; padding-left: 18px; font-size: 13px; line-height: 1.7; }
.toc a { color: #3b4151; text-decoration: none; }
.toc a:hover { color: #3b82f6; }
.set { scroll-margin-top: 64px; margin-bottom: 24px; }
.set h2 { margin: 0 0 8px; font-size: 16px; }
.set h2 a { color: inherit; text-decoration: none; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td { border: 1px solid #d8d8d8; padding: 6px 8px; text-align: left; }
th { background: #f3f3f3; }
code { font-size: 12px; }
.err-row { scroll-margin-top: 64px; }
.err-row:target { background: #fff7ed; }
.set:target { outline: 2px solid #93c5fd; outline-offset: 4px; border-radius: 4px; }
.swagger-jump {
margin: 16px 20px 0; padding: 12px 16px 12px 36px;
border: 1px solid #e5e7eb; border-radius: 8px; background: #fff;
line-height: 1.8;
}
.swagger-jump a { font-weight: 600; }
.swagger-ui .markdown table,
.swagger-ui table {
table-layout: auto;
width: 100%;
}
.swagger-ui .markdown table th,
.swagger-ui .markdown table td,
.swagger-ui table th,
.swagger-ui table td {
white-space: normal !important;
word-break: break-word;
overflow-wrap: anywhere;
vertical-align: top;
}
.swagger-ui table.tcp-cmd th:nth-child(3),
.swagger-ui table.tcp-cmd td:nth-child(3) {
min-width: 8em;
white-space: nowrap !important;
}
.swagger-ui .markdown details {
margin: 12px 0;
padding: 8px 12px;
border: 1px solid #e5e7eb;
border-radius: 8px;
background: #fff;
}
.swagger-ui .markdown details summary {
cursor: pointer;
font-weight: 600;
}
@media (max-width: 800px) {
.layout { grid-template-columns: 1fr; }
.toc { position: static; max-height: none; }
}
</style>
</head>
<body>
<div class="docs-bar">
<a class="docs-brand" href="/docs">
<img src="/docs/static/logo.png?v=20260924-flames" width="28" height="28" alt=""/>
<strong>Spark IM</strong>
</a>
<ol class="docs-nav">
<li><a id="nav-api" href="/docs">API</a></li>
<li><a id="nav-enums" href="/docs/enums">enums</a></li>
<li><a id="nav-errors" href="/docs/errors">errors</a></li>
</ol>
<a href="/openapi.yaml">openapi.yaml</a>
<label>Language
<select id="lang">
<option value="zh-CN">zh-CN</option>
<option value="en">en</option>
<option value="zh-TW">zh-TW</option>
</select>
</label>
</div>
<section id="page-api" class="page">
<ol class="swagger-jump">
<li><a href="/docs/enums">enums</a> — 类型 / 状态枚举</li>
<li><a href="/docs/errors">errors</a> — 错误码 envelope.code</li>
</ol>
<div id="swagger-ui"></div>
</section>
<section id="page-enums" class="page">
<div class="catalog">
<h1>Enums</h1>
<p class="lead">与 JSON / TCP / SQL 同一套 <code>value</code>。<code>name</code> 不随语言变化。<code>label</code> 随右上角语言变化。数据来自 <a href="/v1/meta/enums">GET /v1/meta/enums</a>。点左侧目录跳转。</p>
<div class="layout">
<nav class="toc" id="enum-toc"></nav>
<div id="enum-sets"></div>
</div>
</div>
</section>
<section id="page-errors" class="page">
<div class="catalog">
<h1>错误码</h1>
<p class="lead">HTTP 与 TCP Body 共用 <code>{"code","msg","request_id","data"}</code>。<code>code</code> 是字符串,只有 <code>"0"</code> 成功。业务失败也是 HTTP 200,看 <code>code</code> 不要只看状态码(<code>/readyz</code> 未就绪才是 503)。<code>request_id</code> 由服务端生成,从响应体读取。点目录可跳到对应码。</p>
<div class="layout">
<nav class="toc" id="error-toc"></nav>
<div>
<table>
<thead><tr><th>code</th><th>name</th><th>msg</th></tr></thead>
<tbody id="error-rows"></tbody>
</table>
</div>
</div>
</div>
</section>
<noscript>
<div class="offline">JavaScript 未开启。规范:<a href="/openapi.yaml">/openapi.yaml</a>;枚举:<a href="/v1/meta/enums">/v1/meta/enums</a>。</div>
</noscript>
<script>
function pageKind() {
return document.documentElement.getAttribute("data-page") || "api";
}
function detectLang() {
var q = new URLSearchParams(location.search).get("lang");
if (q === "zh-CN" || q === "en" || q === "zh-TW") return q;
var n = (navigator.language || "").toLowerCase();
if (n.indexOf("zh-tw") === 0 || n.indexOf("zh-hk") === 0 || n.indexOf("zh-hant") === 0) return "zh-TW";
if (n.indexOf("en") === 0) return "en";
return "zh-CN";
}
function esc(s) {
return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
function currentLang() {
return document.getElementById("lang").value;
}
function withLang(href) {
return href + (href.indexOf("?") >= 0 ? "&" : "?") + "lang=" + encodeURIComponent(currentLang());
}
function jumpHash() {
if (!location.hash) return;
var el = document.getElementById(location.hash.slice(1));
if (el) el.scrollIntoView();
}
function loadCatalog() {
var lang = currentLang();
document.documentElement.lang = lang;
var kind = pageKind();
if (kind === "api") return;
fetch("/v1/meta/enums", { headers: { "X-Language": lang, "Accept-Language": lang } })
.then(function (r) { return r.json(); })
.then(function (env) {
var data = env && env.data ? env.data : {};
if (kind === "enums") renderEnums(data);
else renderErrors(data.envelope_code || []);
jumpHash();
})
.catch(function () {
var box = kind === "enums" ? document.getElementById("enum-sets") : document.getElementById("error-rows");
box.innerHTML = '<p class="offline">无法加载 <a href="/v1/meta/enums">/v1/meta/enums</a>。</p>';
});
}
function renderEnums(data) {
var keys = Object.keys(data).filter(function (k) { return k !== "envelope_code"; });
var toc = document.getElementById("enum-toc");
var box = document.getElementById("enum-sets");
if (!keys.length) {
toc.innerHTML = "";
box.innerHTML = '<p class="offline">empty catalog</p>';
return;
}
var tocHtml = "目录<ol>";
var html = "";
keys.forEach(function (k) {
var id = "enum-" + k;
tocHtml += '<li><a href="#' + id + '"><code>' + esc(k) + "</code></a></li>";
var rows = data[k] || [];
html += '<article class="set" id="' + id + '"><h2><a href="#' + id + '"><code>' + esc(k) + "</code></a></h2>";
html += "<table><thead><tr><th>value</th><th>name</th><th>label</th></tr></thead><tbody>";
rows.forEach(function (it) {
html += "<tr><td><code>" + esc(it.value) + "</code></td><td><code>" + esc(it.name) + "</code></td><td>" + esc(it.label) + "</td></tr>";
});
html += "</tbody></table></article>";
});
toc.innerHTML = tocHtml + "</ol>";
box.innerHTML = html;
}
function renderErrors(rows) {
var toc = document.getElementById("error-toc");
var body = document.getElementById("error-rows");
if (!rows.length) {
toc.innerHTML = "";
body.innerHTML = '<tr><td colspan="3">empty catalog</td></tr>';
return;
}
var tocHtml = "目录<ol>";
var html = "";
rows.forEach(function (it) {
var id = "code-" + String(it.value);
tocHtml += '<li><a href="#' + id + '"><code>' + esc(it.value) + "</code> " + esc(it.name) + "</a></li>";
html += '<tr class="err-row" id="' + id + '"><td><code>' + esc(it.value) + "</code></td><td><code>"
+ esc(it.name) + "</code></td><td>" + esc(it.label) + "</td></tr>";
});
toc.innerHTML = tocHtml + "</ol>";
body.innerHTML = html;
}
function bootSwagger() {
if (pageKind() !== "api") return;
if (!window.SwaggerUIBundle) {
document.getElementById("swagger-ui").innerHTML =
'<div class="offline">Swagger UI 未加载。规范:<a href="/openapi.yaml">/openapi.yaml</a></div>';
return;
}
window.SwaggerUIBundle({
url: "/openapi.yaml",
dom_id: "#swagger-ui",
presets: [SwaggerUIBundle.presets.apis],
persistAuthorization: true,
onComplete: function () {
document.querySelectorAll(".swagger-ui table").forEach(function (t) {
var txt = (t.textContent || "");
if (txt.indexOf("AUTH") >= 0 && txt.indexOf("SEND_MESSAGE") >= 0) t.classList.add("tcp-cmd");
});
},
requestInterceptor: function (req) {
req.headers["X-Language"] = currentLang();
if (!req.headers["X-App-Version"]) req.headers["X-App-Version"] = "docs-ui";
return req;
}
});
}
document.getElementById("lang").value = detectLang();
document.getElementById("nav-api").href = withLang("/docs");
document.getElementById("nav-enums").href = withLang("/docs/enums");
document.getElementById("nav-errors").href = withLang("/docs/errors");
var enumJump = document.querySelector(".swagger-jump a[href='/docs/enums']");
var errJump = document.querySelector(".swagger-jump a[href='/docs/errors']");
if (enumJump) enumJump.href = withLang("/docs/enums");
if (errJump) errJump.href = withLang("/docs/errors");
document.getElementById("lang").onchange = function () {
var u = new URL(location.href);
u.searchParams.set("lang", currentLang());
location.href = u.pathname + u.search + u.hash;
};
loadCatalog();
</script>
<script src="https://unpkg.com/swagger-ui-dist@5.17.14/swagger-ui-bundle.js" onload="bootSwagger()" onerror="bootSwagger()"></script>
</body>
</html>