# 淡然太阳码 · HTTP 接口文档

> 版本 **20261107-0838** · 生成时间 2026-11-07 09:19:55
> 基础地址 `https://wx.xbao.fun`

---

## 一、接口一览

| 方法 | 路径 | 说明 |
|---|---|---|
| POST | `/api/encode` | 输入内容，返回太阳码图片（png / jpg / svg / gif）或 JSON |
| GET | `/api/encode` | 同上，参数走 query，可直接放进 `<img src>` |
| POST | `/api/decode` | 上传太阳码图片，解析出内容 |
| POST | `/api/qr` | 上传普通二维码（如微信「加好友」名片），取出其中的链接 |
| GET | `/api/health` | 健康检查（Docker HEALTHCHECK 用它） |
| GET | `/badge.png` | 内置角落徽标图片，可用相对地址引用 |
| GET | `/icon.png` | 站点图标（同 `/favicon.ico`、`/apple-touch-icon.png`） |
| GET | `/api/docs.md` | 本文档（加 `?inline=1` 可在浏览器里直接预览） |

---

## 二、通用约定

| 项 | 说明 |
|---|---|
| 鉴权 | 无。**要暴露公网请在前面挂一层网关做认证** |
| 成功 | 图片接口返回二进制，JSON 接口返回对象 |
| 失败 | 统一 `{"error":"原因"}`，配合 4xx/5xx 状态码 |
| 状态码 | `400` 参数/图片不合法 · `403` 目标地址属内网被拒 · `404` 路径不存在 · `413` 请求体过大 · `429` 请求过于频繁 · `503` 服务繁忙 |
| 请求体上限 | JSON 16MB · multipart 16MB · 待解析图片 24MB · 单张 logo 8MB |
| 图片尺寸 | 单边 ≤ 8000 px，总像素 ≤ 4194 万；GIF 最多取 120 帧 |
| 内容长度 | `text` ≤ 8192 字节（太阳码本身也只能装几十字节） |
| 限速 | 单 IP 120 次/分钟，超限返回 429 并带 `Retry-After` |
| 并发 | 编码/解析同时最多 8 个，超限返回 503 |
| 跨域 | 图片响应通过 `Access-Control-Expose-Headers` 暴露 `X-WXCode-*` 头 |

### 出站请求限制

`logo`、`badge`、`url` 里的 http(s) 地址由服务端下载，且**只能访问公网**：
回环 / 内网 / 链路本地 / 云元数据（`169.254.169.254`）/ CGNAT / 组播一律拒绝（返回 403），
最多跟随 3 次重定向，每一跳都重新校验。默认也**不允许把参数当本地文件路径**读取，
如需读取服务器本地图片，设置环境变量 `ALLOW_LOCAL_IMAGE=1`。

---

## 三、POST /api/encode

生成太阳码。请求体可以是 JSON，也可以是 multipart（可带 `logo` 文件），或普通表单。

### 请求参数

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `text` | string | 是 | 要编码的内容。URL 字符集约 ≤58 字符；含中文走字节模式约 ≤44 字节 |
| `format` | string | 否 | `png`（默认）/ `jpg` / `svg` / `gif` / `json` |
| `size` | int | 否 | 位图边长 120~2000，默认 600；对 svg 无效 |
| `margin` | int | 否 | 位图四周白边像素 0~200，默认 0 |
| `fg` / `bg` | string | 否 | 前景 / 背景色，`#rrggbb`，默认黑 / 白 |
| `level` | string | 否 | 允许的**最低**纠错等级 `L`/`M`/`Q`/`H`，容量够时会自动取更高 |
| `version` | string | 否 | `auto`（默认，取最小可容纳版本）/ `0` / `1` / `2` 钉死版本 |
| `mask` | int | 否 | `-1`（默认）自动在 32 种掩码里取最优；0~31 可固定 |
| `logo` | string | 否 | 中心 logo：data URL / http(s) URL / 裸 base64（本地路径需开 `ALLOW_LOCAL_IMAGE`）。传 GIF 动图且 `format=gif` 时逐帧替换 |
| `logoRatio` | float | 否 | logo 半径缩放，默认 1（半径 108/600），范围 0.2~3.0 |
| `badge` | string | 否 | 角落徽标：`default`（内嵌图，SVG 写成 data URI，离线可用）/ `native`（写原生远程 URL）/ `none` 不画 / 自定义图片地址 |

### 示例

```bash
curl -X POST https://wx.xbao.fun/api/encode \
  -H 'Content-Type: application/json' \
  -d '{"text":"https://example.com/","format":"png","size":600}' \
  -o code.png

# 带中心 logo（本地文件上传）
curl -X POST https://wx.xbao.fun/api/encode \
  -F 'text=https://example.com/' -F 'format=png' -F 'logo=@logo.png'
```

### 返回

**`format=png|jpg|gif`** → 对应 `Content-Type` 的图片二进制：

| 响应头 | 含义 |
|---|---|
| `X-WXCode-Version` | 实际使用的版本（0~2） |
| `X-WXCode-Level` | 实际纠错等级 `L`/`M`/`Q`/`H` |
| `X-WXCode-Mask` | 选中的掩码 0~31 |
| `X-WXCode-Typebits` | 18 位格式位 |
| `X-WXCode-Mode` | `NUMERIC` / `ALPHANUMERIC` / `BYTE` |
| `X-WXCode-Codewords` / `-DataCodewords` / `-ECCodewords` | 码字总数 / 数据 / 纠错 |
| `Content-Disposition` | `inline; filename="wxcode-v0Q-m14.png"` |

**`format=svg`** → `image/svg+xml`，坐标为 float32 逐步舍入，与原生实现一致；
徽标默认内嵌 data URI，单独拷走也能显示。需要与原生逐字节一致时传 `badge=native`。

**`format=json`** →

| 字段 | 说明 |
|---|---|
| `text` / `format` | 内容与输出格式 |
| `version` / `level` / `levelIndex` / `mask` | 版本、纠错等级（字母/下标）、掩码 |
| `mode` / `transformed` | 模式；ALPHANUMERIC 下附变换后的 QR 内文本 |
| `typebits` / `codewords` / `ecCodewords` | 格式位、总码字、纠错码字 |
| `svg` / `pngBase64` | SVG 文本与 PNG 的 base64（一次拿全，便于预览/下载） |
| `logoAnimated` / `logoFrames` | logo 是否动图、共几帧 |

### 错误示例

```json
{"error":"载荷超出一枚葵花码容量（v0..v2 均装不下，请缩短内容）"}
{"error":"内容过长（上限 8192 字节，葵花码本身也只能装几十字节）"}
```

---

## 四、GET /api/encode

参数与 POST 完全一致，只是放在 query 里，方便直接嵌入页面：

```html
<img src="https://wx.xbao.fun/api/encode?text=https%3A%2F%2Fexample.com%2F&format=png&size=400">
```

响应体与响应头同 POST。`text` 记得做 URL 编码。

---

## 五、POST /api/decode

解析太阳码。支持三种投喂方式，任选其一：

| 参数 | 位置 | 说明 |
|---|---|---|
| `file` | multipart | 表单文件字段（也接受 `image`）：`-F 'file=@code.png'` |
| （原始二进制） | body | `Content-Type: image/png`，body 就是图片字节 |
| `image` | JSON | `{"image":"data:image/png;base64,..."}` 或 http(s) 链接 |
| `url` | query/JSON | 给图片地址，由服务端下载后解析（**仅公网地址**） |

### 示例

```bash
curl -X POST https://wx.xbao.fun/api/decode -F 'file=@code.png'
curl -X POST https://wx.xbao.fun/api/decode --data-binary @code.png -H 'Content-Type: image/png'
curl 'https://wx.xbao.fun/api/decode?url=https%3A%2F%2Fexample.com%2Fcode.png'
```

### 返回

| 字段 | 说明 |
|---|---|
| `payload` | 解出的原始内容（已做逆变换） |
| `family` | 码型，固定 `WX_CODE` |
| `version` / `ecLevel` / `mask` | 版本 / 纠错等级 / 掩码 |
| `typebits` | 18 位格式位 |
| `mode` / `count` | 模式与字符计数 |
| `qrText` | ALPHANUMERIC 模式下 QR 内部承载的文本（调试用） |
| `corrections` | RS 纠正掉的码字数，0 表示读得很干净 |

---

## 六、POST /api/qr

解析普通二维码（微信「加好友」名片即 `https://u.wechat.com/...`）。请求方式与 `/api/decode` 相同。

```bash
curl -X POST https://wx.xbao.fun/api/qr -F 'file=@friend.png'
```

### 返回

| 字段 | 说明 |
|---|---|
| `text` | 二维码里的完整文本 |
| `url` | 文本里的链接（没有则为空），可直接丢给 `/api/encode` |
| `version` / `ecLevel` / `mask` | QR 版本（1~40）/ 纠错等级 / 掩码 |
| `mode` | `BYTE` / `NUMERIC` / `ALPHANUMERIC`（多段时为 `A+B`） |
| `modules` | 边长模块数（= 17 + 4 × version） |
| `corrections` | RS 纠正掉的码字数 |

---

## 七、其他接口

### GET /api/health

```json
{"ok":true,"service":"wxcode","version":"20261107-0838"}
```

### GET /badge.png

返回内置的角落徽标（PNG，255×255），适合用相对地址引用。

### GET /api/docs.md

返回本文档。默认作为附件下载（`Content-Disposition: attachment`），
加 `?inline=1` 则直接在浏览器里显示。

---

## 八、参数速查

| 参数 | 取值 | 说明 |
|---|---|---|
| `format` | `png` `jpg` `svg` `gif` `json` | 输出格式；`gif` + 动图 logo = 循环动图，最多 20 帧 |
| `level` | `L` `M` `Q` `H` | 最低纠错等级，容量够时自动升级 |
| `version` | `auto` `0` `1` `2` | 码格版本；`auto` 取最小可容纳 |
| `mask` | `-1` 或 `0`~`31` | `-1` 自动按评分选最优 |
| `badge` | `default` `native` `none` 或图片地址 | 角落徽标 |
| `fg` / `bg` | `#rrggbb` | 前景 / 背景色 |

### 容量参考

| 内容类型 | 大约上限 |
|---|---|
| 纯数字 | 约 80 字符 |
| URL / 大写字母数字 | 约 58 字符 |
| 中文 / 小写字母（字节模式） | 约 44 字节 |

---

## 九、前端调用示例

```js
// 生成：直接拿 blob 预览 / 下载
const resp = await fetch('/api/encode', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({text: 'https://example.com/', format: 'png', size: 600})
});
const blob = await resp.blob();
img.src = URL.createObjectURL(blob);
console.log('版本', resp.headers.get('X-WXCode-Version'));

// 解析：上传文件
const fd = new FormData();
fd.append('file', fileInput.files[0]);
const data = await (await fetch('/api/decode', {method: 'POST', body: fd})).json();
console.log(data.payload, data.corrections);
```
