Dracalon OpenAPI 开发者文档

Dracalon 开放接口的鉴权、签名、端点与接入指南,面向第三方凭证调用方。

约 11 分钟 2026-07-02 更新

简介

Dracalon OpenAPI 是给第三方合作方用的开放接口。调用时用 AccessKey 凭证加 HMAC-SHA256 签名鉴权,再配合接口授权、限流和调用日志,让对外开放的数据始终可控、可审计。

所有接口都在专用子域 https://openapi.dracalon.com 下,路径形如 /v1/...。它是机器到机器(服务端到服务端)的接口,不走浏览器,别在前端页面里暴露 Secret

接口分两类。背景图列表完全公开,不用凭证、直接 GET 就能拿到;其余接口要先申请凭证(AccessKey)并获得授权才能调用。

准备:获取凭证

背景图列表是公开接口,不用凭证就能直接调。下面这些步骤只针对其余需要授权的接口。

  1. 通过 申请入口 提交申请,我们会为你创建一对 AccessKey IDAccessKey Secret
  2. Secret 仅在创建 / 重置时返回一次,请妥善保存;一旦泄露,立即通过 /contact 申请重置。
  3. 我们会按你的需要给凭证授权可调用的接口;调用没授权的接口会返回 40106
  4. 可选:为凭证绑定 IP 白名单、单独的限流阈值、过期时间。

鉴权与签名

背景图列表是公开接口,不需要任何认证头和签名,直接 GET 就行;下面的签名规则只适用于其余接口。

请求头

必填 说明
X-Access-Key AccessKey ID
X-Timestamp Unix 时间戳(秒),需在服务器时间 ±300 秒内
X-Nonce 随机串(≤128 字符),360 秒内不可重复(防重放)
X-Signature 见下,小写 hex
X-Sign-Method 默认 HMAC-SHA256,传别的值会被拒
X-Request-Id 便于端到端排障;不传则服务端生成

待签名串(Canonical String)

7 行,用 \n 连接,顺序固定

HTTP_METHOD          # 大写,如 POST
REQUEST_PATH         # 如 /v1/account/profile
CANONICAL_QUERY      # 查询串按键名排序后的 RFC3986 编码;无 query 时为空行
BODY_SHA256          # 原始 body 的 sha256(小写 hex);空 body 为空串的 sha256
ACCESS_KEY_ID
TIMESTAMP
NONCE

最终签名:

signature = lowercase_hex( HMAC_SHA256(canonical_string, AccessKey_Secret) )

⚠️ BODY_SHA256 必须对实际发送的原始 body 字节计算:先把 JSON 序列化成字符串,对该字符串求 sha256,再把同一个字符串作为 body 发出。不要序列化两次,字段顺序或空格只要不一样就会验签失败。

⚠️ 你签名用的 REQUEST_PATH 必须与你实际请求的 URL 路径逐字一致,统一为 /v1/...

参考实现

PHP:

// $query:URL 查询参数(无则传 []);$body:JSON body 数组(GET 等无 body 传 null)
function sign(string $method, string $path, array $query, ?array $body, string $ak, string $secret): array {
    $ts    = (string)time();
    $nonce = bin2hex(random_bytes(8));

    ksort($query); // 顶层按键名排序(嵌套数组需递归排序)
    $canonicalQuery = http_build_query($query, '', '&', PHP_QUERY_RFC3986);
    $raw = $body === null ? '' : json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

    $canonical = implode("\n", [
        strtoupper($method), $path, $canonicalQuery, hash('sha256', $raw), $ak, $ts, $nonce,
    ]);
    $sig = hash_hmac('sha256', $canonical, $secret);

    // GET 带参数时,URL 用同样规则拼 query:$host . $path . '?' . $canonicalQuery
    return [
        'headers' => [
            'Content-Type'  => 'application/json',
            'X-Access-Key'  => $ak,
            'X-Timestamp'   => $ts,
            'X-Nonce'       => $nonce,
            'X-Signature'   => $sig,
            'X-Sign-Method' => 'HMAC-SHA256',
        ],
        'body' => $raw, // POST 发这个原始串;GET 为空串
    ];
}

Python:

import time, os, json, hashlib, hmac, requests
from urllib.parse import urlencode, quote

# query: dict of query params ({} if none); body: dict for JSON body (None for GET / no body)
def call(method, host, path, query, body, ak, secret):
    ts, nonce = str(int(time.time())), os.urandom(8).hex()
    # sort by key + RFC3986 (quote_via=quote -> space as %20, symmetric with the server)
    canonical_query = urlencode(sorted(query.items()), quote_via=quote)
    raw = "" if body is None else json.dumps(body, ensure_ascii=False, separators=(",", ":"))

    canonical = "\n".join([
        method.upper(), path, canonical_query,
        hashlib.sha256(raw.encode()).hexdigest(), ak, ts, nonce,
    ])
    sig = hmac.new(secret.encode(), canonical.encode(), hashlib.sha256).hexdigest()

    headers = {
        "X-Access-Key": ak, "X-Timestamp": ts, "X-Nonce": nonce,
        "X-Signature": sig, "X-Sign-Method": "HMAC-SHA256",
    }
    if raw:
        headers["Content-Type"] = "application/json"
    url = f"{host}{path}" + (f"?{canonical_query}" if canonical_query else "")
    return requests.request(method, url, data=raw.encode() if raw else None, headers=headers)

开放接口清单

接口 方法 路径 鉴权 说明
小说列表 GET /v1/novel/list 任意有效凭证 分页查询已上架小说,支持分类/标签/篇幅/平台/关键词筛选(半开放)
小说详情 GET /v1/novel/detail 任意有效凭证 按 slug 查询单本已上架小说详情(半开放)
小说分类法 GET /v1/novel/taxonomy 任意有效凭证 一次下发分类/标签/facet 分组/平台清单(半开放)
随机小说 GET /v1/novel/random 任意有效凭证 按筛选条件随机抽取 1-5 本已上架小说(半开放)
推荐小说 POST /v1/novel/recommend 需授权 凭证绑定会员名义提交小说推荐,走后台审核流程
推荐游戏 POST /v1/game/recommend 需授权 凭证绑定会员名义提交游戏推荐,走后台审核流程
背景图列表 GET /v1/background/list 公开 按渠道拉取生效中的背景图列表(凭证授权)
可信域名列表 GET /v1/trusted-domain/list 需授权 按用途(scope)拉取对外公开的可信域名白名单(凭证授权)
当前会员资料 GET / POST /v1/account/profile 任意有效凭证 读取当前 AccessKey 绑定会员的基础资料
当前凭证信息 GET / POST /v1/account/credential 任意有效凭证 读取当前 AccessKey 的凭证与授权概要
已授权接口列表 GET / POST /v1/system/authorized-apis 任意有效凭证 列出当前凭证已授权的开放接口

字段级规格与示例:见 小说库接口游戏库接口;其余接口的字段在后台「开放接口」目录里查看。能不能调用,取决于你的凭证有没有被授权。

通用响应与错误码

统一响应信封:

{
  "code": 1,            // 1 = 成功;其余为失败
  "msg": "ok",          // 失败时是可直接提示的原因
  "data": { "request_id": "..." }  // request_id 与 X-Request-Id 对应,便于排障
}
  • code === 1 视为成功,其余一律按失败处理。
  • 业务校验失败(缺字段 / 防重 / 超配额等)返回 code = 0,HTTP 200
  • 鉴权 / 签名失败返回下列业务码 + 对应 HTTP 状态:
code HTTP 含义
1 200 成功
40101 401 缺少认证头
40102 401 AccessKey 无效
40103 401 签名校验失败 / 签名算法不支持
40104 401 AccessKey 已过期
40105 403 IP 不在白名单
40106 403 未授权访问该接口
40107 401 请求已重放(nonce 重复)
40108 401 时间戳无效 / 超窗口
40109 401 AccessKey 已禁用
42901 429 请求频率过高
40404 404 当前接口未开放(路径 / 方法不匹配)
50301 503 服务暂不可用

限流与防重放

  • 单 IP 默认 300 次/分钟;单凭证 默认 120 次/分钟(可为单个凭证单独配置)。超限返回 42901
  • X-Nonce 单次有效(360 秒内不可重复);X-Timestamp 超出服务器时间 ±300 秒就会被拒签,记得把服务器时间和 NTP 对齐。

接入清单

  1. 通过 申请入口 申请凭证,确认已授权你需要的接口(背景图列表无需此步)。
  2. 妥善保存 Secret(只返回一次)。
  3. 按上文签名规则发起请求,路径统一用 /v1/...
  4. 遇到问题,把响应里的 request_id 反馈给我们,方便排查。

安全建议

  • 不要在前端页面或日志中暴露 AccessKey Secret
  • 全程使用 HTTPS。
  • 为高风险凭证配置 IP 白名单,并定期轮换 Secret。
  • 为不同合作方申请独立凭证,不要共用。