# 创蓝云智（253/蓝创云智）模板短信 v2 发送接口

> 官方文档：<https://doc.chuanglan.com/document/HAQYSZKH9HT5Z50L>
> 用途：手机号 + 验证码登录的**验证码短信下发**（本服务生成码 → 创蓝只负责发送，属自管码 Mode B，与极光同模式）。
> 本文件为**接口调研摘要**，供 `app/integrations/sms/chuanglan.py` 实现对照。以线上文档为准。

## 接口概览

| 项 | 值 |
|---|---|
| 请求地址 | `POST https://smssh.253.com/msg/sms/v2/tpl/send` |
| Content-Type | `application/json`（UTF-8） |
| 协议 | HTTPS |
| 鉴权 | HMAC-SHA256 签名头 `X-QA-Hmac-Signature`（推荐）**或** body 明文 `password`（二选一） |

## 鉴权：两种方式（二选一，不可并用）

1. **HMAC 签名头（推荐，密码不上行）**：请求头带 `X-QA-Hmac-Signature`，body **不放** `password`。
2. **明文密码**：body 放 `password`，不带签名头。

### HMAC-SHA256 签名算法

1. `md5Password = MD5(password)` —— 32 位**小写十六进制**。
2. 取三个值 `[md5Password, timestamp, nonce]`，**按字典序升序排序**，**无分隔符拼接**，再 `replaceAll("\\s+", "")` 去除所有空白。
3. `signature = HmacSHA256(key = md5Password, message = 上一步拼接串)` —— 输出**小写十六进制**。
4. 放入请求头：`X-QA-Hmac-Signature: <signature>`。

> 注意 `key` 就是 `md5Password` 本身（32 位 hex 字符串），不是原始 password。`timestamp` / `nonce` 同时也是 body 字段，必须与签名里用的一致。

## 请求参数（body，JSON）

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `account` | String | 是 | API 账号；验证码短信用 **`YZM` 前缀**账号（如 `YZM0000001`） |
| `timestamp` | String | 是 | Unix 秒级时间戳；**60 秒**内有效，过期报 139 |
| `nonce` | String | 是 | 32 位随机串（防重放） |
| `phoneNumbers` | String | 是 | 手机号，逗号分隔最多 1000 个；**YZM 验证码账号不支持批量，只能单号** |
| `templateId` | String | 是 | 模板 ID（控制台创建 / 模板接口查询） |
| `templateParamJson` | String | 条件 | 模板变量，JSON 字符串；模板有 `{s}` 占位符时必填（见下） |
| `password` | String | 条件 | 仅在**不使用**签名头时放 body |
| `signature` | String | 条件 | **短信签名文案**（如 `【创蓝云智】`）；模板未关联签名时必填。**注意与鉴权头 `X-QA-Hmac-Signature` 是两回事** |
| `report` | String | 否 | `"true"` 时接收状态回执 |
| `callbackUrl` | String | 否 | 回执回调完整 URL |
| `uid` | String | 否 | 自定义标识（≤256 字符），回执原样返回 |
| `extend` | String | 否 | 数字扩展码（≤5 位），用于上行匹配 |

### `templateParamJson` 格式与 `{s}` 占位

- 模板内容用 `{s}` 作占位符，例：`您正在申请手机注册，验证码为：{s}，5分钟内有效！`
- `templateParamJson` 是 **JSON 数组，元素为对象**，键按 `param1`、`param2`…递增；第 1 个 `{s}` ← `param1`，第 2 个 ← `param2`。
- 单条验证码（一个 `{s}` = 验证码）示例：`"[{\"param1\":\"123456\"}]"`

## 响应格式

```json
{
  "code": "000000",
  "msgId": "25071018345400902898000000000001",
  "time": "20250710183454",
  "successNum": "1",
  "failNum": "0",
  "errorMsg": ""
}
```

| 字段 | 说明 |
|---|---|
| `code` | 状态码，`"000000"` = 成功 |
| `msgId` | 消息 ID（32 位） |
| `time` | 响应时间戳 |
| `successNum` / `failNum` | 提交成功 / 失败条数 |
| `errorMsg` | 错误描述（成功为空） |

## 响应 / 错误码（节选）

| code | 含义 | 归属 |
|---|---|---|
| `000000` | 成功 | — |
| `101` | 账号不存在 | 客服 |
| `102` | 密码错误 | 客服 |
| `103` | 提交速度过快（超频） | 客服 |
| `107` | 手机号码错误 | 客服 |
| `109` | 无发送量（余额/套餐不足） | 销售 |
| `110` | 不在发送时段 | 销售 |
| `116` | 签名不合法 / 未带签名 | 服务 |
| `117` | IP 未加白名单 | 服务 |
| `118` | 账号无发送权限 | 服务 |
| `124` | 模板内容不匹配 | 服务 |
| `129` | JSON 格式错误 | 客服 |
| `135` | 相同手机号内容重复 | 客服 |
| `139` | 时间戳过期 | 客服 |
| `152` | 模板不存在 | 服务 |
| `158` | 退订文案不合规 | 客服 |

## 完整请求示例（单条验证码，明文密码方式省略 password 用签名头）

```json
{
  "account": "YZM0000001",
  "timestamp": "1752143733",
  "nonce": "x4zfk0y5foqwx6cbnw3bfmimy98abqs1",
  "phoneNumbers": "17601337176",
  "templateId": "1021143438",
  "templateParamJson": "[{\"param1\":\"123456\"}]",
  "report": "true"
}
```

（HMAC 方式：另加请求头 `X-QA-Hmac-Signature: <算法输出>`，body 不含 `password`。）

## 接入要点

- **IP 白名单**：服务器出网 IP 必须在控制台加白，否则 117。
- **验证码账号（YZM）**：无发送时段限制；单号发送、不支持批量。
- **时间戳 60s**：`timestamp` 与本地时钟偏差过大会 139，注意服务器时间同步。
- **退订文案**：仅支持 `拒收请回复R`，且必须在短信末尾（验证码短信一般无需）。
- **签名 vs 鉴权头**：`signature`（body）= 短信开头的 `【品牌】` 文案；`X-QA-Hmac-Signature`（header）= 请求鉴权。二者含义完全不同，勿混。
