故障时开放
如果队列服务不可达或队列不存在,脚本不会执行任何操作,商店仍可正常使用。等候室中断绝不会阻断商店。
ADITUS-Queue 是一个独立的虚拟等候室,可保护任何网站、API 或在线服务免受流量峰值冲击。它既能与 ADITUS 票务平台无缝集成,也能独立部署在任何 Web 应用之前。访客会进入托管等候室,并严格按照先到先得原则,以配置的每分钟速率获准进入。集成方式涵盖无需运营方基础设施的单个 Gateway 链接或 script 标签、在反向代理或 CDN 中强制执行,以及直接使用 REST API。
新增:自调节准入速率。 队列读取商店的健康检查并自动调整速率——商店有余量时放行更多访客,承压时立即降低负载。
功能虽全面,设置只需几分钟,而非数天。 只需发布一个链接或添加一个 script 标签——无需自行准备基础设施、部署或修改代码。
不到 2 分钟即可保护你的应用:将 script 标签复制到 head 中,或发布 Gateway 链接。无需更改服务器基础设施。只有在需要最大控制力时,才启用可选的高级功能——本页其余内容均为可供日后采用的进阶选项。
在 ADITUS 管理控制台中填写名称、商店地址和每分钟准入速率。其余设置均提供合理的默认值。
发布现成的 Gateway 链接来替代商店链接,或像添加分析代码片段一样,将准备好的 script 标签粘贴到商店页面中。
访客流量低于准入速率时不会看到等候室。只有流量激增时队列才会介入——托管、扩缩容和运维均由我们负责。
队列的强大功能是一条升级路径,而不是使用前提。仅第一级即可满足绝大多数使用场景。
选择哪种方式取决于一个问题:已发布的商店链接能否更改?如果可以,Gateway 链接是更好的选择——流量在获准进入前不会到达商店。如果已发布的商店 URL 固定不变,script 标签可在不更改发布方式的情况下保护商店。除此之外,还可在反向代理或 CDN 中强制检查(见下文)、直接通过 REST API 控制,或在任意后端语言中使用 JWT 库验证——包括 Node.js、PHP、Java、.NET、Python 和 Go。
每个等候室都有自己的入口 URL,可发布在原本展示商店链接的任何位置——新闻邮件、网站和社交媒体。队列接收请求后在服务端一次完成判断:流量低于准入速率时,访客直接转至配置的商店地址,完全感知不到队列;超过速率后,访客进入等候室,并在轮到时自动转至商店。获准进入前,商店本身不会收到任何流量——请求在到达商店基础设施前即被吸收。
<!-- Der veröffentlichte Shop-Link zeigt auf die Queue statt auf den Shop: -->
<a href="https://developers.aditus.com/queue/<slug>/enter">Tickets kaufen</a>使用此方式时,必须为队列配置商店的目标地址(Ziel-URL)——获准进入的访客会被转至该地址。访客到达商店时,URL 中会携带签名的准入 token,即标准 HS256 JWT;商店可选择在自己的后端使用共享 secret 离线验证。下文说明了结构、claims 和后端示例。 注意:Gateway 保护的是已发布的入口点;直接知道商店 URL 的访客仍可绕过它。如需避免这种情况,请将 Gateway 链接与 script 标签结合使用,或在商店后端验证 token。
如果无法更改已发布的商店链接,只需集成一个 script 标签。它与商店页面一同交付,方式与分析代码片段相同;等候室需要保护的每个页面都必须包含该标签。建议将其放在 <head> 中并添加 defer 属性,具体位置并不关键。
<!-- Auf jeder zu schützenden Shop-Seite im <head>: -->
<script src="https://developers.aditus.com/queue/<slug>/embed.js" defer></script>每个队列对应的准确标签可在 ADITUS 管理控制台中获取,其中已填入正确的 slug。每次页面加载时,脚本都会执行三项检查:
不执行任何操作。通行凭证按浏览器会话存储,访客可不受干扰地使用商店。
服务端会验证 URL 中的准入 token(签名、队列归属和有效期)。验证成功后,将存储会话通行凭证,并从地址栏移除 token。无效或过期的 token 会使访客返回等候室。
当前无人等候时,访客会在后台无感获准并停留在页面上,不会看到等候室。只有超过配置的准入速率并形成队列后,访客才会被重定向至等候室;当前页面会作为已验证的返回 URL 传递,因此获准后访客会准确返回最初请求的页面。
如果队列服务不可达或队列不存在,脚本不会执行任何操作,商店仍可正常使用。等候室中断绝不会阻断商店。
服务端会依据队列配置的域名验证返回 URL。等候室只会转至已注册商店的页面。
上述两种方式都保护已发布的入口点。如果要求即使访客直接知道商店 URL,未经准入也无法访问,则应将检查移到商店之前,即终止其流量的反向代理或 CDN 中。逻辑始终分为三步:访客从等候室返回时,从 aditus_queue_token URL 参数中获取 token 并存为 cookie;每次请求时,使用共享 secret 将 cookie 中的 token 作为标准 HS256 JWT 进行验证(issuer 为 aditus-queue、audience 为队列 slug,并检查有效期);没有有效 token 时,重定向至 https://developers.aditus.com/queue/<slug> 的等候室。验证完全离线——代理无需调用队列,也不会增加延迟。以下示例展示四种常见环境中的实现模式,请根据你的环境进行调整。
# Skizze: Nginx mit njs (js_import) — prüft den Zugriffs-Token als HS256-JWT
# direkt am Proxy, ohne Rückruf an die Queue. An die eigene Umgebung anpassen.
# nginx.conf:
# env ADITUS_QUEUE_SECRET;
# js_import queue from /etc/nginx/queue.js;
# server {
# location / { js_content queue.gate; }
# location @shop { proxy_pass http://shop_backend; }
# }
// /etc/nginx/queue.js:
const crypto = require('crypto');
const SLUG = '<slug>';
const SECRET = process.env.ADITUS_QUEUE_SECRET; // Shared Secret der Queue
function claims(token) {
const p = token.split('.');
if (p.length !== 3) return null;
const sig = crypto.createHmac('sha256', SECRET)
.update(p[0] + '.' + p[1]).digest('base64url');
if (sig !== p[2]) return null;
const c = JSON.parse(Buffer.from(p[1], 'base64url'));
const ok = c.iss === 'aditus-queue' && c.aud === SLUG && c.exp > Date.now() / 1000;
return ok ? c : null;
}
function gate(r) {
const fromUrl = r.args.aditus_queue_token;
const m = (r.headersIn.Cookie || '').match(/(?:^|;\s*)aditus_queue=([^;]+)/);
const token = fromUrl || (m && m[1]);
if (!token || !claims(token)) {
// Kein gültiger Token -> zurück in den Warteraum
r.return(302, 'https://developers.aditus.com/queue/' + SLUG);
return;
}
if (fromUrl) {
// Token aus der Rücksprung-URL in ein Sitzungs-Cookie übernehmen
r.headersOut['Set-Cookie'] =
'aditus_queue=' + fromUrl + '; Path=/; Secure; HttpOnly; Max-Age=600';
}
r.internalRedirect('@shop');
}
export default { gate };# Skizze: Caddy mit dem jwtauth-Plugin (github.com/ggicci/caddy-jwt).
# Token kommt beim Rücksprung als ?aditus_queue_token=… oder danach als Cookie;
# jwtauth prüft beide Quellen. An die eigene Umgebung anpassen.
shop.example.com {
route {
jwtauth {
sign_key {env.ADITUS_QUEUE_SECRET}
sign_alg HS256
from_query aditus_queue_token
from_cookies aditus_queue
issuer_whitelist aditus-queue
audience_whitelist <slug>
user_claims qnr
}
# Token aus der URL einmalig in ein Cookie übernehmen
@rueckkehr query aditus_queue_token=*
header @rueckkehr Set-Cookie "aditus_queue={http.request.uri.query.aditus_queue_token}; Path=/; Secure; HttpOnly; Max-Age=600"
reverse_proxy shop_backend:8080
}
# Ohne gültigen Token: zurück in den Warteraum
handle_errors {
@unauth expression {http.error.status_code} == 401
redir @unauth https://developers.aditus.com/queue/<slug> 302
}
}// Skizze: Cloudflare Worker vor dem Shop — prüft den HS256-JWT offline per
// WebCrypto. Secret als Worker-Secret ADITUS_QUEUE_SECRET hinterlegen.
const SLUG = "<slug>";
const ROOM = "https://developers.aditus.com/queue/" + SLUG;
export default {
async fetch(req, env) {
const url = new URL(req.url);
const fromUrl = url.searchParams.get("aditus_queue_token");
const m = (req.headers.get("Cookie") || "").match(/(?:^|;\s*)aditus_queue=([^;]+)/);
const token = fromUrl || (m && m[1]);
if (!token || !(await valid(token, env.ADITUS_QUEUE_SECRET))) {
return Response.redirect(ROOM, 302); // zurück in den Warteraum
}
if (fromUrl) {
// Token in ein Cookie übernehmen und die URL bereinigen
url.searchParams.delete("aditus_queue_token");
return new Response(null, {
status: 302,
headers: {
Location: url.toString(),
"Set-Cookie": "aditus_queue=" + token +
"; Path=/; Secure; HttpOnly; Max-Age=600",
},
});
}
return fetch(req); // gültiger Pass -> durch zum Shop
},
};
async function valid(token, secret) {
const p = token.split(".");
if (p.length !== 3) return false;
const key = await crypto.subtle.importKey(
"raw", new TextEncoder().encode(secret),
{ name: "HMAC", hash: "SHA-256" }, false, ["verify"],
);
const ok = await crypto.subtle.verify(
"HMAC", key, b64url(p[2]),
new TextEncoder().encode(p[0] + "." + p[1]),
);
if (!ok) return false;
const c = JSON.parse(new TextDecoder().decode(b64url(p[1])));
return c.iss === "aditus-queue" && c.aud === SLUG && c.exp > Date.now() / 1000;
}
function b64url(v) {
return Uint8Array.from(
atob(v.replace(/-/g, "+").replace(/_/g, "/")),
(ch) => ch.charCodeAt(0),
);
}// Skizze: AWS CloudFront mit Lambda@Edge (Viewer-Request, Node.js).
// Gleiches Muster: Token aus ?aditus_queue_token oder Cookie, HS256 offline
// prüfen, ohne gültigen Token in den Warteraum umleiten.
// Hinweis: Lambda@Edge hat keine Umgebungsvariablen — das Secret z. B. beim
// Deployment einbetten oder aus SSM/Secrets Manager cachen.
'use strict';
const crypto = require('crypto');
const SLUG = '<slug>';
const ROOM = 'https://developers.aditus.com/queue/' + SLUG;
const SECRET = '<ADITUS_QUEUE_SECRET>';
exports.handler = async (event) => {
const req = event.Records[0].cf.request;
const qs = new URLSearchParams(req.querystring || '');
const fromUrl = qs.get('aditus_queue_token');
const cookie = (req.headers.cookie || []).map((h) => h.value).join('; ');
const m = cookie.match(/(?:^|;\s*)aditus_queue=([^;]+)/);
const token = fromUrl || (m && m[1]);
if (!token || !valid(token)) {
return redirect(ROOM); // zurück in den Warteraum
}
if (fromUrl) {
// Token in ein Cookie übernehmen und die URL bereinigen
qs.delete('aditus_queue_token');
const clean = req.uri + (qs.toString() ? '?' + qs.toString() : '');
return redirect(clean, {
'set-cookie': [{ key: 'Set-Cookie', value:
'aditus_queue=' + token + '; Path=/; Secure; HttpOnly; Max-Age=600' }],
});
}
return req; // gültiger Pass -> durch zum Shop (Origin)
};
function valid(token) {
const p = token.split('.');
if (p.length !== 3) return false;
const sig = crypto.createHmac('sha256', SECRET)
.update(p[0] + '.' + p[1]).digest();
const got = Buffer.from(p[2], 'base64url');
if (got.length !== sig.length || !crypto.timingSafeEqual(sig, got)) {
return false;
}
const c = JSON.parse(Buffer.from(p[1], 'base64url'));
return c.iss === 'aditus-queue' && c.aud === SLUG && c.exp > Date.now() / 1000;
}
function redirect(location, extraHeaders) {
return {
status: '302',
headers: Object.assign(
{ location: [{ key: 'Location', value: location }] },
extraHeaders || {},
),
};
}注意:启用边缘强制执行后,每位访客都需要 token,包括原本会在低于准入速率时无感进入的访客。请同时发布 Gateway 链接(方式 A),或让代理将没有 token 的访客重定向至等候室;无人排队时,等候室会立即签发 token。
等候室由 ADITUS 配置。设置时请提供以下信息:
等候室页面的页眉图片,例如活动主视觉图。采用横向格式,至少 920 × 380 像素(显示宽度最大 460 px,裁剪后高度最大 190 px),格式为 JPG 或 PNG,并以可公开访问的 HTTPS URL 或文件形式提供给 ADITUS。
显示在等候室标题下方的简短消息,例如开售说明或预计等候时间。使用纯文本并保留换行,建议不超过约 300 个字符。文本会按原样显示——如果服务国际访客,请提供相应语言或双语内容。
以十六进制值提供品牌颜色(例如 #0a63c9)。它用于等候室页面的进度条和实时指示器;未提供时使用 ADITUS 默认颜色。结合图片和文本,等候室即可呈现主办方的品牌形象。
脚本运行页面的域名(例如 shop.example.com)。这些域名决定等候室接受哪些返回 URL。
等候室标题(例如活动名称)、获准访客的目标 URL(Gateway 链接必填,即 Gateway 转至的商店地址),以及每分钟访客数表示的准入速率。该速率是吞吐量阈值:只要到达的访客少于速率允许值,所有人都可直接进入而无需经过等候室。队列运行期间可随时调整。
排队号码 ——打开页面时分配一次,并在整个等候期间保留,即使重新加载页面也不会改变。
当前位置和已准入号码 ——前面还有多少号码,以及当前已准入至哪个号码。已准入号码会按准入速率持续增长。
进度条 ——直观显示准入进度距离访客本人号码还有多远。
预计等候时间 ——根据当前位置和准入速率计算;随着准入推进,时间会逐渐缩短。访客号码获准后,页面会自动转至商店,无需任何操作。
页眉图片 ——可选,例如活动主视觉图。
显示名称 ——等候室标题,例如活动名称。
消息 ——标题下方的可选文本,按所提供的内容原样显示。
等候室页面本身支持德语和英语。系统会根据访客的浏览器设置自动选择语言,无需配置。
开售期间,ADITUS 通过实时仪表板监控每个等候室:仪表显示当前等候人数、新访客流入量与准入速率的对比、准入速率本身(启用自调节速率时还显示配置的上限),以及新访客的预计等候时间。双序列图表跟踪等候人数和流入量随时间的变化,处理流量的队列实例还会报告自身负载——CPU、内存和请求速率。队列运行期间可随时调整准入速率,例如商店显示仍有余量时。
队列的抗负载能力并非只停留在宣称层面,而是会定期接受测试。可从 ADITUS 管理控制台向生产系统中的专用测试等候室发送真实请求进行负载测试(绝不会触及正式等候室)。测试以可配置的速率和时长模拟访客流程——加入队列并轮询状态。实时仪表显示实际请求速率、中位数与 p95 延迟及错误率;逐秒图表对比吞吐量和延迟,每次运行都会保留在历史记录中,便于长期比较结果。测试还可按周期计划运行;如某次计划运行的性能明显低于此前的可比运行,系统会自动标记。
队列服务在 /queue/healthz 提供公开健康检查,供外部监控使用。该端点无需身份验证,也不访问数据库,可作为运行时间监控的简单可用性信号。负载测试期间,管理控制台还会显示队列实例自行报告的 CPU、内存、事件循环延迟和请求速率;负载升高时,平台会自动启动更多实例,测试界面会清晰展示这一过程。
准入速率是开售期间决定全局的核心参数,而以往必须有人持续监控。现在无需如此:队列可根据受保护商店的实际状态自行调节速率。由此,等候室始终能以商店可安全承载的最快速度促成销售,无需人工调整。
商店状态健康时,速率会小步提升,将未使用的余量转化为销售。一旦商店出现压力,速率会迅速下降——这种非对称设计让减压即时生效,而负载谨慎恢复。
健康检查不可达时绝不会扩大放行:速率会先冻结,随后降至配置的最小值。即使无人值守,商店也能得到保护。
由你定义“健康”的含义——响应时间、CPU 负载或商店报告的任何指标。每次自动调整都会记录在案,并在实时仪表板中显示,同时标明触发规则。
等候室无需手动调整准入速率,可根据受保护商店的健康检查自动调节。ADITUS 以可配置的间隔轮询商店的健康 URL,并依据运营方定义的规则评估响应时间,以及可选的 JSON 响应数值字段(例如:响应时间低于 500 ms、CPU 负载低于 0.8)。商店状态健康时,速率会缓慢小步提升;一旦违反规则,速率会按百分比快速降低——这种有意的非对称设计可立即为商店减压,同时谨慎恢复负载。速率始终保持在配置的最小值与最大值之间。
如果健康端点未响应,系统会按故障安全方式处理:先冻结速率,连续多次失败后降至配置的最小值——不可达的健康检查绝不会扩大放行。每次自动调整都会记录并显示在实时仪表板中;启用自动调节前,该端点必须先通过管理控制台的一次性测试。
商店端的健康端点非常简单:一个无需身份验证即可访问、返回 2xx 的 HTTPS URL。JSON 响应体为可选;其中任何数值字段(嵌套对象会展平为点路径)都可成为规则引用的指标:
{
"status": "ok",
"responseBudgetMs": 500,
"metrics": {
"cpuLoad": 0.42,
"dbPoolWaiting": 0,
"openCheckouts": 118
}
}
// Nutzbare Messwerte (Punktpfade):
// responseBudgetMs, metrics.cpuLoad, metrics.dbPoolWaiting, metrics.openCheckouts
// Dazu immer verfügbar: die gemessene Antwortzeit (ms).对于高需求开售,等候室还可要求访客在获得排队号码前完成工作量证明。浏览器会在后台解答一个小型密码学难题——访客无感;采用默认难度时,普通设备远不到一秒即可完成。对于机器人集群则不同:每次加入都需要相同的计算工作,因此获取数千个号码不再免费,成本会按比例增长。该防护默认关闭,可在 ADITUS 管理控制台中按等候室启用;难度可在 8 至 24 位之间调整,默认为 15 位,每增加一位,工作量都会翻倍。托管等候室页面会自动处理难题,商店集成无需更改。
一个有意设计的结果是:防护启用期间,低于准入速率时的无感直通会被禁用——每位访客都必须经过等候室,因为工作量证明只在那里执行。还需明确适用范围:工作量证明可保护排队号码发放,抵御批量加入。它是防范自动化买家的一项措施,而非完整的机器人防御方案;必要时请与商店自身的措施(如购买限制)结合使用。
只有直接调用队列 API 的自定义集成才需要自行解答难题。流程为:获取 challenge,寻找一个 nonce,使 challenge 与 nonce 组合后的 SHA-256 哈希以指定数量的零位开头,然后在加入请求中一并提交。Challenge 带有签名,有效期为五分钟;此外,每个实例还会阻止重复使用:
// 1. Challenge holen (nur relevant, wenn PoW für die Queue aktiv ist)
GET https://developers.aditus.com/queue/<slug>/pow
// -> { queue, enabled: true, challenge, bits, expiresInSeconds: 300,
// algorithm: "sha256" }
// 2. Nonce suchen: SHA-256(challenge + "." + nonce) muss mit <bits>
// Null-Bits beginnen. Im Browser per WebCrypto — bei der
// Standard-Schwierigkeit deutlich unter einer Sekunde.
// 3. Beitritt mit Lösung
POST https://developers.aditus.com/queue/<slug>/join
{ "pow": { "challenge": "…", "nonce": "…" } }
// Ohne oder mit ungültiger Lösung antwortet /join mit
// 428 Precondition Required — und liefert im Fehlerkörper direkt eine
// frische Challenge mit, sodass kein zweiter Abruf nötig ist.对于预告开售,可为等候室设定固定开放时间。提前到达的访客会进入预队列:等候室页面显示开放倒计时,此时不会准入任何人。到达开放时间时会执行关键步骤——在此前已等候的所有访客中公平随机抽取准入顺序。因此,提前三小时到达与提前三分钟相比并无优势;反复刷新和长时间占用浏览器标签页也失去意义。开放后加入的访客会按正常到达顺序排在抽签组之后。
抽签是对开放前发放的排队号码进行数学排列——每个号码恰好获得一个位置,不会遗漏或重复,且无法根据加入顺序预测分配结果。在管理控制台中更改开放时间会重新启用抽签;商店集成和 API 均无需更改。
并非每位领取排队号码的访客都会继续等候。关闭的标签页和被放弃的浏览器通常会留下空位:准入窗口仍会经过已无人持有的号码,使实际吞吐量低于配置速率。启用离队检测后,托管等候室页面会定期通过 heartbeat 报告在线状态。如果某个号码静默时间超过配置的宽限期(60 至 3600 秒,默认 180 秒),即视为已离队;准入窗口会相应加快推进,将空出的位置分配给仍在等候的访客。
该检测采用宽容设计:如果返回的访客号码已被视为离队,但已处于准入窗口内,仍会正常获准进入——heartbeat 只会加速准入,绝不会撤销准入资格。自行构建等候页面的自定义集成可通过 POST /queue/<slug>/heartbeat 发送相同信号(JSON 请求体包含排队号码,建议每 60 秒发送一次)。
媒体、合作伙伴、粉丝俱乐部配额等访客不应看到等候室。可在 ADITUS 管理控制台中为每个等候室创建免排队码——每个代码均有标签,可选配置使用次数上限和到期日期,并可随时禁用或删除。代码可通过简单链接分享:/queue/<slug>/enter?code=… 会携带有效准入 token 直接转至商店,完全跳过队列。自定义集成也可通过 POST /queue/<slug>/bypass 兑换代码,并以 JSON 接收 token。
这里有两项有意设计的安全属性:Gateway 链接中的无效代码会静默进入正常流程,外部人员无法判断某个代码是否存在过;JSON 路由对无效、过期、次数用尽和已禁用代码返回完全相同的响应,避免为猜测代码提供判定依据。免排队准入会在 token 中明确标记,且不占用常规准入名额。
以下章节并非集成所必需——上述 script 标签已构成完整集成。这里记录系统底层的工作方式,供技术评估以及希望在自身后端验证准入的商店参考。
每位访客通过一次数据库原子递增获得连续的排队号码。准入持续推进:当前已准入号码按“基数 + 速率 × 已过分钟数”计算。状态轮询为只读操作,每次请求都不访问数据库。服务端不保存每位访客的状态,也没有 cron 任务或计时器,因此队列服务本身不会在高负载下成为瓶颈。暂停队列会同时停止准入和新号码发放;速率变更会连续生效,已准入号码绝不会减少。
准入严格遵循先到先得(FCFS):原子递增会在访客加入时固定其位置,之后位置绝不改变。系统有意不设置抽签、随机化或优先通道——资源紧缺时,到达顺序是访客唯一认可的公平排序,也是唯一无法操纵的排序。系统从两个层面在技术上杜绝插队:排队号码只增不减;服务端准入检查会将访客号码与当前已准入号码进行比较,只有检查通过后才会签发使用队列 secret 签名的准入 token。访客无法声称更靠前的位置,伪造 token 也无法通过签名验证。需要分离流量的运营方,例如每个活动或每次销售使用一个等候室,可运行多个独立等候室,每个都有自己的 slug、secret、速率和配置。
该设计目标直接源于上述准入逻辑:由于已准入号码根据经过时间推导,状态检查也不读取每位访客的状态,因此几乎所有访客流量都是无状态计算。任意实例都可响应任意请求,实例之间除数据库外不共享任何内容;负载升高时,平台会自动增加实例。每位访客仅有一次数据库写入——加入时的原子递增——这是唯一的串行化点,而且每位访客只发生一次,而非每次轮询都发生。以下数据来自上述内置负载测试运行器,通过公开 HTTPS 端点对队列服务单个实例进行测量;mix 配置文件按真实访客流程的比例组合加入和状态轮询:
mix · 10 req/s · 30 s延迟 p50 7,6 ms · p95 12,0 ms · 错误率 0 %mix · 25 req/s · 60 s延迟 p50 7,3 ms · p95 11,4 ms · 错误率 0 %mix · 50 req/s · 45 s延迟 p50 6,7 ms · p95 11,0 ms · 错误率 0 %status · 50 req/s · 45 s延迟 p50 6,4 ms · p95 8,9 ms · 错误率 0 %数据于 2026 年 7 月使用生产代码路径测得;每次运行均零错误完成,中位延迟保持个位数毫秒。单个实例可轻松处理每秒 50 个请求——相当于数千名等候访客按建议间隔轮询;超过这一负载后,额外实例会接管。扩缩容并非黑箱:每个运行实例都会持续报告 CPU、内存和请求吞吐量等实时指标,随时可在 ADITUS 管理控制台中查看,因此可清楚了解一次开售由多少实例承载,以及剩余多少余量。计划负载测试会持续在生产系统上重新验证这些数据,明显落后于先前运行的结果会被自动标记。
共十个端点,访客无需身份验证,CORS 限制为每个队列配置的域名。托管等候室页面使用的正是此 API;也可直接调用它,打造完全自定义的等候体验。
GET /queue/<slug>托管等候室页面:获取排队号码、轮询状态,并在获准进入后自动将访客转至目标页面。POST /queue/<slug>/join发放排队号码(访客流程中唯一的写操作)。队列暂停时返回 423。GET /queue/<slug>/status?number=N轮询号码的准入状态——只读,并包含 Retry-After 标头。未发放过的号码返回 404。POST /queue/<slug>/token将已获准进入的号码兑换为签名的 HS256 JWT。号码尚未获准时返回 425,未发放过的号码返回 404。GET /queue/<slug>/enterGateway 入口:代替商店 URL 对外发布。有可用容量时,直接转至配置的目标 URL(携带准入 token);否则进入等候室。必须配置目标 URL。支持使用 ?code=… 传入 VIP 免排队码:有效代码可完全跳过队列,无效代码则不作提示地进入正常流程。GET /queue/<slug>/embed.js商店端集成脚本:无人排队时让访客无感进入;队列形成后,将没有有效通行凭证的访客引导至等候室,并准确送回其来源页面。GET /queue/<slug>/verify?token=…集成脚本使用的服务端 token 检查:验证准入 token 的签名、audience 和有效期。GET /queue/<slug>/info公开队列信息:启用状态、当前已准入号码和等候人数。POST /queue/<slug>/heartbeat用于可选离队检测的在线信号:等候室页面报告某个号码仍在线。检测关闭时,该请求会被静默接受。POST /queue/<slug>/bypass通过 JSON 兑换 VIP 免排队码:返回准入 token 和目标 URL。无效、过期、次数用尽和已禁用的代码均返回相同的 404,避免为猜测代码提供判定依据。GET /queue/<slug>/pow用于可选机器人防护的工作量证明 challenge:防护关闭时返回 enabled: false;否则返回一个签名 challenge,/join 要求提交其解答。GET /queue/healthz用于可用性监控的公开健康检查:无需身份验证,也不访问数据库。该请求有意不计入实例指标,避免监控探测影响流量视图。// Wartenummer anfordern (einziger Schreibzugriff im Besucherpfad).
const res = await fetch("https://developers.aditus.com/queue/<slug>/join", {
method: "POST",
});
const ticket: {
queue: string; // Queue-Slug
name: string; // Anzeigename des Warteraums
number: number; // vergebene Wartenummer
serving: number; // bis zu dieser Nummer wird weitergeleitet
ahead: number; // Anzahl Nummern vor dieser
admitted: boolean; // true -> sofort weiter zu /token
estimatedWaitSeconds: number;
retryAfterSeconds: number; // empfohlenes Polling-Intervall
} = await res.json();
// 423 Locked -> der Warteraum ist pausiert (keine Weiterleitung, keine neuen Nummern).// Statusabfrage — rein lesend, beliebig oft wiederholbar.
// Der Server sendet zusätzlich einen Retry-After-Header.
const res = await fetch(
"https://developers.aditus.com/queue/<slug>/status?number=" + ticket.number,
);
const status: {
queue: string;
name: string;
active: boolean; // false -> Weiterleitung pausiert
number: number;
serving: number;
ahead: number;
admitted: boolean;
estimatedWaitSeconds: number;
retryAfterSeconds: number;
} = await res.json();
if (status.admitted) {
// -> Token abholen und zum Shop weiterleiten
}// Sobald admitted=true: Zugriffs-Token abholen.
const res = await fetch("https://developers.aditus.com/queue/<slug>/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ number: ticket.number }),
});
// 425 Too Early -> die Nummer ist noch nicht zur Weiterleitung freigegeben.
const grant: {
queue: string;
number: number;
token: string; // signierter HS256-JWT
tokenType: "JWT";
expiresInSeconds: number; // Standard: 600 s
targetUrl: string | null; // konfiguriertes Weiterleitungsziel
} = await res.json();
// Weiterleitung übernimmt die gehostete Warteraum-Seite automatisch:
// targetUrl + "?aditus_queue_token=" + grant.tokenscript 标签集成本身已经完整。可控制自身后端的商店还可在后端验证准入 token:它是标准 HS256 JWT,使用共享 secret 离线验证——无需回调队列,也不会增加延迟。Secret 在创建队列时生成,仅显示一次,并可随时轮换。Claims:iss 始终为 aditus-queue,aud 为队列 slug,qnr 为已准入的排队号码,exp 限制有效期(默认 10 分钟)。注意:低于准入速率时的无感直通不会生成 token;严格要求 token 的后端应将没有 token 的访客送至等候室,无人等候时等候室会立即签发 token。
// ZIELSYSTEM (dein Shop / deine Seite): Token prüfen — Standard-JWT (HS256).
// Das Secret erhältst du einmalig von ADITUS beim Einrichten des Warteraums.
import jwt from "jsonwebtoken";
const payload = jwt.verify(token, process.env.ADITUS_QUEUE_SECRET!, {
algorithms: ["HS256"],
issuer: "aditus-queue", // iss ist immer aditus-queue
audience: "<slug>", // aud ist der Queue-Slug deines Warteraums
}) as { qnr: number; iat: number; exp: number };
// payload.qnr = die weitergeleitete Wartenummer.
// Gültiger Token -> Besucher passieren lassen (z. B. Cookie setzen).
// Fehlender/ungültiger Token -> zurück in den Warteraum:
// https://developers.aditus.com/queue/<slug>上述流程已在相应位置说明各项安全机制,本节集中汇总。签名 token:准入 token 为 HS256 JWT,使用每个队列独有的 secret 签名;secret 仅显示一次并可随时轮换。Audience 绑定(队列 slug)和较短有效期(默认 10 分钟)会限制被截获 token 的价值。机器人防护:可选的工作量证明检查使机器人集群批量加入的成本按比例增长;challenge 使用 HMAC 签名并在五分钟后过期;每个实例还会尽力阻止已使用 challenge 被重复利用——真正的成本来源是哈希计算本身。重定向:服务端依据每个队列配置的域名验证返回 URL,等候室绝不会成为开放重定向。API 范围:访客端点完全不携带凭据;CORS 按队列限制;管理访问使用 API key,每个 key 仅绑定一个等候室,只以哈希形式存储,无法管理其他 key,并会留下审计记录。流量峰值:无状态架构从设计上即可吸收负载——任意实例均可响应任意请求,平台会自动扩缩实例;服务前端的托管平台负责网络层 DDoS 过滤。
等候室位于收入链路之前,因此故障行为与正常流程同样重要。
ADITUS-Queue 解决的技术问题与专业企业级虚拟等候室产品相同。下表将此类产品的关键衡量能力与 ADITUS-Queue 的实现方式对应起来,其中也包括一项有意不提供的功能。
ADITUS 管理控制台针对单个等候室执行的所有操作——调整准入速率、暂停和更改配置——也都可由机器完成。访问时使用带 qak_ 前缀的 API key,并以 Bearer token 发送;每个 key 只为一个等候室创建,只能读取和更改该等候室。Key 在管理控制台中创建和撤销,仅显示一次,并且只以哈希形式存储。典型使用场景是开售运行手册:计划任务在开售前提高准入速率;脚本在紧急情况下暂停队列;监控读取当前配置。系统设置了三项防护:key 无法访问其他等候室或全局管理功能(负载测试、计划和实例),服务端会拒绝此类请求;有意禁止使用 key 管理 key,泄露的 key 永远无法创建或列出其他 key;通过 key 进行的每项更改都会以该 key 名称和对应等候室记录在审计日志中。
# Konfiguration des zugeordneten Warteraums lesen. Jeder Key ist an genau
# einen Warteraum gebunden — die Liste enthält daher höchstens diesen einen
# Eintrag; andere Warteräume und globale Verwaltungsfunktionen sind mit
# einem Key nicht erreichbar.
curl -H "Authorization: Bearer qak_…" \
https://developers.aditus.com/api/admin/queue/sites
# Weiterleitungsrate im laufenden Betrieb anheben: Lesen -> Feld ändern -> Schreiben.
# PATCH erwartet die vollständige Konfiguration (ohne slug); unbekannte
# Felder wie id oder Zeitstempel werden serverseitig ignoriert.
KEY="qak_…"; BASE="https://developers.aditus.com/api/admin/queue"
SITE=$(curl -s -H "Authorization: Bearer $KEY" "$BASE/sites" \
| jq '.sites[0]')
echo "$SITE" | jq '.ratePerMinute = 300' | curl -s -X PATCH \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d @- "$BASE/sites/$(echo "$SITE" | jq -r .id)"
# Zugeordneten Warteraum pausieren (keine Weiterleitung, keine neuen Nummern):
# active = false
echo "$SITE" | jq '.active = false' | curl -s -X PATCH \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d @- "$BASE/sites/$(echo "$SITE" | jq -r .id)"# .github/workflows/onsale.yml — Weiterleitungsrate zum Vorverkaufsstart anheben.
# Den API-Key als Repository-Secret ADITUS_QUEUE_API_KEY hinterlegen. Der Key
# ist an den Warteraum des Vorverkaufs gebunden — mehr kann er nicht.
name: Weiterleitungsrate zum On-Sale anheben
on:
schedule:
- cron: "55 8 14 3 *" # 14. März, 08:55 UTC — kurz vor dem On-Sale
workflow_dispatch: {}
jobs:
raise-rate:
runs-on: ubuntu-latest
steps:
- name: Rate auf 300/min anheben
env:
KEY: ${{ secrets.ADITUS_QUEUE_API_KEY }}
run: |
BASE="https://developers.aditus.com/api/admin/queue"
# Der Key sieht nur seinen gebundenen Warteraum — .sites[0] genügt.
SITE=$(curl -sf -H "Authorization: Bearer $KEY" "$BASE/sites" \
| jq '.sites[0]')
echo "$SITE" | jq '.ratePerMinute = 300' \
| curl -sf -X PATCH \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d @- "$BASE/sites/$(echo "$SITE" | jq -r .id)"