feat(gateway): 接入42和72并为42/52/72启用可刷新令牌

This commit is contained in:
xsl
2026-11-07 20:50:22 +08:00
parent dc53a0ecfe
commit 51baf96d78
24 changed files with 1312 additions and 126 deletions
+135
View File
@@ -0,0 +1,135 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>72 号接口 · B 端五个关键点</title>
<style>
* { box-sizing: border-box; }
body { margin: 0; background: #eef2f5; color: #192d3d; font: 16px/1.55 system-ui, sans-serif; }
main { width: min(1040px, 100%); margin: auto; padding: 26px 18px; }
h1 { margin: 0; font-size: clamp(24px, 4vw, 35px); }
h2 { margin: 0 0 10px; font-size: 19px; }
p { margin: 8px 0; }
.note { color: #4f6577; }
.controls, .panel { background: white; border-radius: 12px; padding: 18px; margin-top: 16px; }
form { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
button { background: #156a72; color: white; border: 0; border-radius: 7px; padding: 9px 15px; font: inherit; cursor: pointer; }
button:disabled { opacity: .55; cursor: wait; }
input { max-width: 100%; }
#status[data-error="true"] { color: #a22629; }
.results { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); gap: 16px; }
[hidden] { display: none; }
.photo { display: grid; border-radius: 8px; overflow: hidden; background: #1b2c37; }
.photo img { grid-area: 1 / 1; width: 100%; display: block; }
.panel a { color: #156a72; }
table { width: 100%; border-collapse: collapse; margin: 8px 0 20px; font-variant-numeric: tabular-nums; }
th, td { text-align: left; border-bottom: 1px solid #e6edf2; padding: 5px 7px; }
@media(max-width: 680px) { .results { grid-template-columns: 1fr; gap: 0; } }
</style>
</head>
<body>
<main>
<h1>72 号接口 · B 端五个关键点</h1>
<p class="note">上传正面照,查看扶正裁切后的六个白点、四条虚线和厘米标注。</p>
<form id="client-auth" style="display:flex;align-items:center;flex-wrap:wrap;gap:8px;padding:12px 0">
<label>用户名 <input name="username" autocomplete="username" required></label>
<label>密码 <input name="password" type="password" autocomplete="current-password" required></label>
<button type="submit">登录</button>
<span id="client-auth-status" role="status">登录后可调用接口。</span>
</form>
<script src="/static/client_auth.js" defer></script>
<section class="controls">
<form id="form">
<label for="file">人脸照片</label>
<input id="file" type="file" accept="image/jpeg,image/png" required>
<button id="submit" type="submit">生成标注</button>
<button id="sample" type="button">测试样例</button>
</form>
<p id="status" role="status">请选择 JPG 或 PNG 图片。</p>
</section>
<div id="results" class="results" hidden>
<section class="panel">
<h2>标注预览</h2>
<div class="photo">
<img id="base" alt="扶正裁切后的底图">
<img id="overlay" alt="白点、虚线及距离标注">
</div>
<p><a id="download" download="interface72-overlay.png">下载透明标注层</a></p>
</section>
<section class="panel">
<h2>测量结果</h2>
<table><thead><tr><th>点位</th><th>距离</th></tr></thead><tbody id="distances"></tbody></table>
<h2>坐标(像素)</h2>
<table><thead><tr><th>点号</th><th>X</th><th>Y</th></tr></thead><tbody id="points"></tbody></table>
<p class="note">点 6(MediaPipe 446)是射线锚点。六个白点的直径相同。</p>
</section>
</div>
</main>
<script>
const byId = (id) => document.getElementById(id);
const status = byId("status");
const results = byId("results");
const submit = byId("submit");
const sample = byId("sample");
function setRows(id, rows) {
const body = byId(id);
body.replaceChildren();
for (const values of rows) {
const row = body.insertRow();
for (const value of values) row.insertCell().textContent = value;
}
}
function imageSource(data, field, mime) {
return data[field + "_url"] || ("data:" + mime + ";base64," + data[field + "_base64"]);
}
async function runAnnotation(loadFile) {
submit.disabled = sample.disabled = true;
results.hidden = true;
status.dataset.error = "false";
status.textContent = "正在扶正裁切并生成标注…";
try {
const file = await loadFile();
const form = new FormData();
form.append("image_file", file, file.name || "sample.jpg");
const response = await window.hairClientFetch("/api/v1/face/b-end-points", { method: "POST", body: form });
const { code, message, data } = await response.json();
if (!response.ok || code !== 0) throw new Error(message || "请求失败");
byId("base").src = imageSource(data, "input_image", "image/jpeg");
const overlay = imageSource(data, "annotated_image", "image/png");
byId("overlay").src = overlay;
byId("download").href = overlay;
setRows("distances", Object.entries(data.measurements_cm).map(([key, value]) => [
key, value.toFixed(1) + " cm"
]));
setRows("points", Object.entries(data.points).map(([key, point]) => [
key, point.x.toFixed(1), point.y.toFixed(1)
]));
results.hidden = false;
status.textContent = "标注完成。";
} catch (error) {
status.dataset.error = "true";
status.textContent = error.message;
} finally {
submit.disabled = sample.disabled = false;
}
}
byId("form").addEventListener("submit", (event) => {
event.preventDefault();
const file = byId("file").files[0];
if (file) runAnnotation(() => file);
});
sample.addEventListener("click", () => runAnnotation(async () => {
const response = await fetch("/static/sample.jpg");
if (!response.ok) throw new Error("无法读取样例图片");
return response.blob();
}));
</script>
</body>
</html>