# /service 杂项

> 分类代号：C.12  本章共 **3 个接口**

这里汇集了"不方便归到其它分类、但二开很常用"的三个能力：

- **QR Code 生成**：把字符串直接渲染成 PNG 二维码
- **字体字形 (PBF)**：vjmap地图渲染依赖的字体文件
- **图片上传**：把任意图片文件塞到服务器指定路径（配合前端水印/图床）

---

## 快速索引

| 序号 | SDK 方法 | URL |
|----:|---------|-----|
| 68 | `qrcode_url` / `qrcode_bytes` / `qrcodeUrl` / `qrcodeBytes` | `GET /api/v1/service/qrcode?content=...&size=` |
| 69 | `glyphs_url` / `glyphs_bytes` / `glyphsUrl` / `glyphsBytes` | `GET /api/v1/service/fonts/{fontstack}/{range}.pbf` |
| 70 | `upload_img` / `uploadImg` | `POST /api/v1/service/uploadImg` (multipart) |


---

## 68 · 二维码 `qrcode`

### 查询参数

| 字段 | 默认 | 说明 |
|------|-----:|------|
| `content` | -（必填） | 要编码的字符串；后端会 `decodeURIComponent`，所以**不用双重编码** |
| `size` | 256 | 图片边长（像素） |

> ⚠️ 后端解析方式特殊（`QrCode` handler）：直接从原始 `RequestURI` 截取 `?content=` 之后、`&size=`/`&token=` 之前的子串——**`content` 必须排在 query 第一位**，且 value 中含 `&size=`/`&token=` 字样会被截断。SDK 构造的 URL 顺序正确，手写 URL 时注意。

### 响应

直接返回 `image/png` 字节，**不是 JSON**。

### SDK 两种用法

1. **URL 构造**（适合前端直接 `<img src=...>`）：

```python
url = svc.qrcode_url("https://example.com/project/42", size=512)
# http://127.0.0.1:27660/api/v1/service/qrcode?content=https%3A%2F%2Fexample.com%2Fproject%2F42&size=512&token=<jwt>
```

2. **下载字节**（适合服务端生成后转发/保存）：

```python
png = svc.qrcode_bytes("VJMAP", size=128)
open("qr.png", "wb").write(png)
```

Node.js：

```javascript
const url = svc.qrcodeUrl('VJMAP', 512);
const buf = await svc.qrcodeBytes('VJMAP', 128);
```

### 实采信息

```json
// qrcode_bytes_info.json
{ "len": 284, "md5": "2577b1157cf93feefce8c6b180a0ff42", "magic_ok": true }
```

---

## 69 · 字体 `fonts/{fontstack}/{range}.pbf`

vjmap 矢量渲染在画文字时，需要按需下载对应字体、对应 Unicode 区间的 PBF 字形文件。这个接口就是字形的分发端。

### 路径参数

| 字段 | 说明 |
|------|------|
| `fontstack` | 字体名，可用 `,` 分隔多个**回退栈**。后端会依次尝试，并**自动在末尾追加 `Arial Unicode MS Regular`** 作为兜底 |
| `range` | Unicode 字形区间，形如 `0-255`、`256-511`。通常是 256 个字形一包 |

### 响应

`application/x-protobuf` 字节（PBF）。

### SDK

```python
url = svc.glyphs_url("Arial Unicode MS Regular", "0-255")
pbf = svc.glyphs_bytes("Arial Unicode MS Regular", "0-255")
```

```javascript
const url = svc.glyphsUrl('Arial Unicode MS Regular', '0-255');
```

### 实采

```json
// glyphs_bytes_info.json
{
  "len": 75282,
  "md5": "638faf3bf9a90424d23513cfab7e9ce1"
}
```

### 用在 vjmap 里

```js
map.setStyle({
  glyphs: 'http://127.0.0.1:27660/api/v1/service/fonts/{fontstack}/{range}.pbf?token=' + TOKEN,
  // ...其它
});
```

注意 `{fontstack}`/`{range}` 是 **vjmap 自己替换**的占位符，别让 URL 编码器把 `{` / `}` percent-encode 掉。

---

## 70 · 上传图片 `uploadImg`

### 特殊鉴权

这个接口**两道都要过**：

1. 普通 JWT token——路由注册在全局 JWT 鉴权、casbin 权限中间件之后，缺 token 照样 403 `Token is invalid`；guest 角色被 casbin 拒
2. 处理函数再检查固定的 HTTP header：

```
Authorization: Basic X3ZqbWFwQF8=
```

即 Basic base64("`_vjmap@_`")，后端硬编码。SDK 两者都会自动带上；`authorization` 参数可覆盖后者。

### Headers

| Header | 说明 |
|--------|------|
| `Authorization` | 上述固定值（SDK 默认） |
| `Uri` | **必填**：服务器端保存目录（以 `/` 结尾）。SDK 参数名 `uri` |
| `watermark` | `"1"` 时后端自动叠加水印 |
| `ImgHost` | 可选：响应 `url` = 该头的值 + 文件名；不传则 `url` 只是文件名（SDK 不发此头） |

### multipart 字段

| 字段 | 说明 |
|------|------|
| `upload` | 文件内容。**注意 field name 是 `upload` 不是 `file`** |

### 响应

成功（实采）：

```json
{
  "code": 0,
  "path": "./data/test_upload/nb_upload_smoke.png",
  "url":  "nb_upload_smoke.png"
}
```

`path` = `Uri` 头 + 文件名（服务器本地路径）；`url` = `ImgHost` 头 + 文件名（未传 ImgHost 时即文件名）。

失败（实采：错误 Authorization）：

```json
{
  "code": 7,
  "data": {},
  "msg": "no Authorization"
}
```

### Python

```python
# 从本地文件上传
svc.upload_img(
    "./avatar.png",
    uri="./data/upload/avatars/",
    filename="user_42.png",
    watermark=True,
)

# 从字节上传
svc.upload_img(
    ("user_42.png", png_bytes),
    uri="./data/upload/avatars/",
)
```

### Node.js

```javascript
// Node 18+
await svc.uploadImg('./avatar.png', {
  uri: './data/upload/avatars/',
  filename: 'user_42.png',
  watermark: true,
});

// Buffer
await svc.uploadImg(buf, {
  uri: './data/upload/avatars/',
  filename: 'user_42.png',
});
```
