# 阿里云短信模板调用说明

项目通过 `@alicloud/pop-core` 调用阿里云 `SendSms`，统一封装在 `src/mobile/utils/sms.js`。

## 1. 核心方法：`sendSms`

```js
const { sendSms } = require('../utils/sms.js');
const smsConfig = require('../config/smsConfig.js');

await sendSms(
  phoneNumbers,   // 手机号，支持 1xxxxxxxxxx / +86xxxxxxxxxx，多号码用逗号分隔
  signName,       // 签名，一般用 smsConfig.signName
  templateCode,   // 模板 CODE，如 SMS_xxxxxx
  templateParam   // 对象，会 JSON.stringify 传给阿里云
);
```

内部实际请求参数：

| 参数 | 说明 |
|------|------|
| `PhoneNumbers` | 格式化后的国内 11 位号 |
| `SignName` | 短信签名 |
| `TemplateCode` | 模板 ID |
| `TemplateParam` | `JSON.stringify(templateParam)` |

成功判定：

```js
const { isAliyunSmsOk, getAliyunSmsErrorMessage } = require('../utils/sms.js');

const result = await sendSms(...);
if (isAliyunSmsOk(result)) {
  // result.BizId 可用于查投递
} else {
  console.error(getAliyunSmsErrorMessage(result));
}
```

## 2. 已配置模板（`smsConfig.js`）

| 配置项 | 环境变量 | 默认模板 CODE | 用途 | 模板变量 |
|--------|----------|---------------|------|----------|
| `signName` | `ALI_SMS_SIGN_NAME` | `徐州鑫群机械科技有限公司` | 签名 | — |
| `templateRegisterCode` | `ALI_SMS_TEMPLATE_REGISTER` | `SMS_506920324` | 注册/重置验证码 | `{ code }` |
| `templateReimburseAdmin` | `ALI_SMS_TEMPLATE_REIMBURSE_ADMIN` | `SMS_501670791` | 新报销通知管理员 | `{ name, amount }` |
| `templateReimburseResult` | `ALI_SMS_TEMPLATE_REIMBURSE_RESULT` | `SMS_501895724` | 报销审核结果通知员工 | `{ name, status }` |

签名必须与阿里云控制台一致，否则会报 `SMS_SIGNATURE_ILLEGAL`。

当前账号已审核签名为「徐州鑫群机械科技有限公司」（「股份有限公司」在该账号下不存在）。

## 3. 业务封装方法

### 3.1 验证码（HTTP）

- 路由：`POST /api/user/send-sms-code`
- 调用：`sendSms(phone, signName, templateRegisterCode, { code })`
- Body：`{ phone, type }`（`type`: `register` / `reset`）
- 限制：同号每日最多 5 次；验证码 5 分钟有效（内存 + `T_sms_send_log`）

`type` 校验逻辑：

- `register`：手机号已注册则拒绝
- `reset`：手机号未注册则拒绝

### 3.2 新报销通知管理员

```js
const { sendNewReimbursementToAdmin } = require('../utils/sms.js');

await sendNewReimbursementToAdmin(adminPhone, employeeName, amount);
// 模板变量: { name: employeeName, amount }
```

调用位置：`mobile/controllers/reimbursementController.js`（单笔创建、批量创建）。

### 3.3 报销结果通知员工

```js
const { sendReimbursementResultToEmployee } = require('../utils/sms.js');

await sendReimbursementResultToEmployee(employeePhone, employeeName, status, auditRemark);
// status: 1 → 「已通过」，其它 → 「已拒绝」
// 模板变量: { name, status: statusText }
```

## 4. 新增模板的标准写法

1. 在阿里云控制台创建并审核模板，记下 `TemplateCode` 和变量名。
2. 在 `src/mobile/config/smsConfig.js` 增加配置（建议带环境变量）：

```js
templateXxx: process.env.ALI_SMS_TEMPLATE_XXX || 'SMS_xxxxxx'
```

3. 业务侧调用：

```js
await sendSms(
  phone,
  smsConfig.signName,
  smsConfig.templateXxx,
  { /* 与控制台变量名完全一致 */ }
);
```

变量名必须与控制台模板一致，例如模板是 `${code}`，代码里就要传 `{ code: '123456' }`。

## 5. 环境变量（`.env`）

```env
ALI_SMS_ACCESS_KEY_ID=
ALI_SMS_ACCESS_KEY_SECRET=
# 未配短信专用 Key 时，会回退到 ALI_OSS_ACCESS_KEY_ID / SECRET

ALI_SMS_SIGN_NAME=徐州鑫群机械科技有限公司
ALI_SMS_TEMPLATE_REGISTER=SMS_506920324
ALI_SMS_TEMPLATE_REIMBURSE_ADMIN=SMS_501670791
ALI_SMS_TEMPLATE_REIMBURSE_RESULT=SMS_501895724

# 仅本地调试：发送失败仍返回成功并把验证码打到日志
# SMS_ALLOW_DEV_FALLBACK=1
```

生产环境务必设置 `NODE_ENV=production`，且不要开启 `SMS_ALLOW_DEV_FALLBACK`。

密钥配置入口：`src/mobile/config/alisms.js`（endpoint: `https://dysmsapi.aliyuncs.com`，apiVersion: `2017-05-25`）。

## 6. 投递查询（可选）

`SendSms` 返回 `OK` 只表示受理成功，不等于已送达。可用：

```js
const { querySendDetails, logSendDeliveryStatus, parseSendDetailStatus } = require('../utils/sms.js');

// 发送后延迟查一次并打日志
await logSendDeliveryStatus(phone, result.BizId);

// 或手动查
const detail = await querySendDetails(phone, bizId /*, sendDate YYYYMMDD */);
const { status, hint } = parseSendDetailStatus(detail);
// status: delivered | failed | pending | unknown
```

验证码接口在受理成功后会异步调用 `logSendDeliveryStatus`，便于排查「OK 但收不到」。

## 7. 相关文件

| 文件 | 作用 |
|------|------|
| `src/mobile/utils/sms.js` | `sendSms` 及业务封装 |
| `src/mobile/config/smsConfig.js` | 签名 + 模板 CODE |
| `src/mobile/config/alisms.js` | 阿里云客户端 |
| `src/mobile/utils/smsVerifyStore.js` | 验证码存取/校验 |
| `src/mobile/controllers/userController.js` | 发验证码接口 |
| `src/mobile/routes/user.js` | `POST /send-sms-code` 路由 |
| `src/mobile/controllers/reimbursementController.js` | 报销通知短信 |
| `.env.example` | 环境变量示例 |
