diff --git a/docs/commercial-map-and-sms-application-guide.md b/docs/commercial-map-and-sms-application-guide.md new file mode 100644 index 0000000..3925f0f --- /dev/null +++ b/docs/commercial-map-and-sms-application-guide.md @@ -0,0 +1,664 @@ +# 商用高德地图 Key 与阿里云短信申请指南 + +更新日期:2026-07-09 + +本文档用于指导申请人基于当前 Banban / 伴伴项目申请: + +- 高德开放平台商用地图能力:服务端 Web 服务 API Key,主要使用逆地理编码。 +- 阿里云短信服务:国内通知短信,主要用于设备告警通知家长,预留设备留言通知。 + +本文档重点是申请流程、填写内容、材料清单和审核注意事项。代码位置只用于确认本项目实际需要申请哪些能力。 + +## 1. 当前项目要申请什么 + +### 1.1 高德地图 + +当前项目需要申请高德开放平台的 Web 服务 API Key。 + +用途: + +- 后端接收设备上报的经纬度。 +- 后端调用高德逆地理编码接口,把经纬度转换成可读地址。 +- 小程序展示设备当前位置、轨迹点、告警位置。 +- 告警短信里使用较短的地址描述,例如“浙江省杭州市萧山区扬帆路附近”。 + +当前不应申请为主要接入项: + +- Web 端 JavaScript API Key:当前不是浏览器 Web 页面直接渲染高德地图。 +- Android / iOS SDK Key:当前不是原生 App SDK 接入。 +- 小程序端高德 SDK Key:当前小程序地图展示使用小程序 `Map` 组件,地址解析在后端完成。 + +如果后续改成小程序前端直接调用高德 SDK,再单独新增对应 Key,不建议和服务端 Key 混用。 + +### 1.2 阿里云短信 + +当前项目需要申请阿里云短信服务的国内通知短信。 + +主要模板: + +- 设备告警通知:必需。 + +可选模板: + +- 设备留言通知:当前后端已有短信服务方法,但现有设备留言主链路更多是微信公众号模板通知;如果产品希望短信也覆盖留言,需要申请模板并补上触发调用。 + +当前不应申请为主要短信类型: + +- 验证码短信:当前这项需求不是注册/登录验证码。 +- 营销短信:项目场景是安全通知,不是营销推广。 +- 国际/港澳台短信:除非产品明确要发给境外手机号。 + +## 2. 申请前统一准备 + +### 2.1 企业资料 + +申请高德和阿里云短信前,建议先准备以下资料: + +- 企业营业执照扫描件或照片。 +- 统一社会信用代码。 +- 企业全称。 +- 企业简称,如有。 +- 法定代表人姓名。 +- 法定代表人证件号码,按平台要求填写。 +- 法定代表人证件照片,按平台要求上传。 +- 申请经办人姓名。 +- 申请经办人手机号。 +- 申请经办人身份证正反面照片,按平台要求上传。 +- 企业客服电话或产品服务电话,如有。 +- 企业邮箱。 +- 企业官网,如有。 +- 微信小程序名称。 +- 微信小程序 AppID。 +- 微信小程序截图:登录页、设备绑定页、定位页、告警/通知页、手机号授权或绑定页。 +- 产品隐私政策链接或截图。 +- 用户协议链接或截图。 + +### 2.2 项目信息 + +建议统一使用以下描述,避免两个平台材料口径不一致: + +- 产品名称:伴伴智能设备 / Banban。 +- 产品类型:智能儿童陪伴设备及家长端微信小程序。 +- 使用对象:已购买或绑定设备的家长用户。 +- 核心功能:家长通过小程序查看绑定设备状态、位置、轨迹、告警记录和设备留言。 +- 定位来源:绑定设备主动上报定位,不是后台持续读取家长手机位置。 +- 通知对象:已绑定设备且在系统内留存手机号的家长或家庭成员。 +- 通知目的:安全告警、设备状态和亲子沟通提醒。 +- 是否营销:否。 + +### 2.3 技术联系人要提前确认的信息 + +申请前让技术负责人确认: + +- 生产后端公网域名。 +- 生产后端服务器出口公网 IP。 +- 预计日调用量。 +- 预计峰值 QPS。 +- 是否需要多环境 Key:生产、测试、开发是否分开。 +- 短信签名希望使用的名称:例如“伴伴”或公司简称。 +- 当前短信模板变量名是否保持代码默认值:`conference`、`code`、`time`、`address`。 + +## 3. 高德地图 Key 申请流程 + +### 3.1 注册并认证账号 + +1. 打开高德开放平台:https://lbs.amap.com/ +2. 点击登录。 +3. 使用企业邮箱、手机号或阿里云账号等方式登录。 +4. 进入账号中心或控制台。 +5. 找到实名认证 / 企业认证入口。 +6. 选择企业认证。 +7. 填写企业名称和统一社会信用代码。 +8. 上传营业执照。 +9. 填写联系人姓名、手机号、邮箱。 +10. 提交认证。 +11. 等待认证通过。 + +注意: + +- 商用项目建议使用企业主体账号,不要使用员工个人账号。 +- 如果后续需要购买配额、申请发票、签署服务协议,企业账号更容易闭环。 + +### 3.2 创建应用 + +1. 登录高德开放平台控制台。 +2. 进入“应用管理”。 +3. 点击“创建新应用”。 +4. 应用名称填写:`伴伴设备定位服务`。 +5. 应用类型按控制台可选项选择最接近的业务类型,例如“生活服务”“工具”“物联网/硬件设备”。如果没有完全匹配项,选择通用类型。 +6. 应用描述填写: + +```text +伴伴设备定位服务用于智能设备上报定位后的地址解析。后端服务接收已绑定设备上报的经纬度,调用高德 Web 服务逆地理编码接口转换为可读地址,用于家长在微信小程序中查看设备当前位置、轨迹点和告警位置。定位数据仅用于已绑定设备的安全查看和告警通知,不用于营销推广。 +``` + +7. 提交创建。 + +### 3.3 添加 Web 服务 Key + +1. 进入刚创建的应用。 +2. 点击“添加 Key”或“添加新 Key”。 +3. Key 名称填写:`banban-server-prod-regeo`。 +4. 服务平台选择:`Web服务`。 +5. 如果控制台要求选择服务能力,选择: + - 地理/逆地理编码。 + - 逆地理编码。 +6. 如果控制台支持 IP 白名单,填写生产后端服务器出口公网 IP。 +7. 如果还没有固定生产 IP,先不要随意填写内网 IP;等服务器确定后再补充。 +8. 保存 Key。 +9. 复制生成的 Key,交给后端配置。 + +建议: + +- 生产 Key、测试 Key 分开创建。 +- Key 名称里带环境后缀:`prod`、`test`、`dev`。 +- 不要把生产 Key 放到小程序前端代码。 +- 不要把生产 Key 提交到 Git。 + +### 3.4 高德商用配额确认 + +创建 Key 后,还要确认普通配额是否满足商用上线: + +1. 在高德控制台查看 Web 服务 API 的日调用量限制。 +2. 查看逆地理编码接口的 QPS 限制。 +3. 估算当前项目调用量。 +4. 如果普通配额不够,进入高德开放平台的升级、购买或商务咨询入口。 +5. 选择适合的流量包、QPS 包或商业授权服务。 +6. 保存订单、合同、发票信息,交给财务或项目负责人归档。 + +估算方式: + +```text +日调用量 = 日活设备数 * 单设备日定位上报次数 * 需要解析地址的比例 +峰值 QPS = 峰值在线设备数 * 单设备峰值上报频率 +``` + +当前项目有地址解析去重和异步解析机制,但申请时应保守估算,不要按最低调用量报。 + +### 3.5 高德申请材料填写建议 + +业务场景可填写: + +```text +本产品为伴伴智能设备及家长端微信小程序。设备由家长绑定到儿童档案后,会按业务事件上报定位信息。服务端调用高德 Web 服务逆地理编码能力,将经纬度转换为可读地址,供家长查看设备当前位置、历史轨迹和设备告警位置。所有定位展示均基于用户主动绑定的设备,不用于广告、画像或营销。 +``` + +接口用途可填写: + +```text +使用逆地理编码接口,将设备上报的 GCJ-02 经纬度转换为格式化地址,展示在小程序定位页、轨迹页、告警记录页,并在紧急告警短信中展示简短位置。 +``` + +数据安全说明可填写: + +```text +高德 Key 仅配置在服务端环境变量中,不放入微信小程序前端包。服务端只在绑定关系校验通过后处理对应设备定位。定位数据仅对绑定家长和授权家庭成员展示。系统保存必要的当前位置和轨迹记录,用于设备安全查看和告警追溯。 +``` + +## 4. 阿里云短信申请流程 + +### 4.1 注册、实名认证并开通短信服务 + +1. 打开阿里云官网:https://www.aliyun.com/ +2. 登录或注册账号。 +3. 进入账号中心。 +4. 完成企业实名认证。 +5. 搜索“短信服务”。 +6. 进入短信服务控制台。 +7. 点击开通短信服务。 +8. 同意服务协议。 +9. 确认账号余额、支付方式和发票主体。 + +注意: + +- 生产项目建议用企业实名认证账号。 +- 短信费用通常按发送量计费,申请前确认财务充值或后付费权限。 +- 不建议使用个人账号申请生产短信服务。 + +### 4.2 创建 RAM 子账号和 AccessKey + +建议不要用阿里云主账号 AccessKey 直接接入后端。 + +1. 进入阿里云 RAM 访问控制。 +2. 创建用户,例如:`banban-sms-prod`。 +3. 访问方式选择 OpenAPI 调用访问。 +4. 创建 AccessKey。 +5. 复制 `AccessKey ID` 和 `AccessKey Secret`。 +6. 给 RAM 用户授权短信服务发送相关权限。 +7. 保存 AccessKey 到密码管理器。 +8. 只把密钥交给后端部署负责人。 + +后端需要配置: + +```env +SMS_ENABLED=true +SMS_PROVIDER=aliyun +ALIYUN_SMS_ACCESS_KEY_ID=实际 AccessKey ID +ALIYUN_SMS_ACCESS_KEY_SECRET=实际 AccessKey Secret +ALIYUN_SMS_SIGN_NAME=审核通过的短信签名 +ALIYUN_SMS_ALARM_TEMPLATE_CODE=审核通过的告警模板 Code +ALIYUN_SMS_LEAVE_MESSAGE_TEMPLATE_CODE=审核通过的留言模板 Code +``` + +如果只启用告警短信,可以先不配置 `ALIYUN_SMS_LEAVE_MESSAGE_TEMPLATE_CODE`。 + +### 4.3 申请短信资质 + +短信资质是签名和模板申请前的重要步骤。控制台名称可能是“资质管理”“资质申请”或“国内消息资质”,以阿里云当前页面为准。 + +操作步骤: + +1. 进入阿里云短信服务控制台。 +2. 进入“资质管理”。 +3. 点击“新增资质”或“申请资质”。 +4. 选择资质用途:国内短信。 +5. 选择主体类型:企业。 +6. 填写企业名称。 +7. 填写统一社会信用代码。 +8. 上传营业执照。 +9. 填写法定代表人姓名。 +10. 填写法定代表人证件类型和证件号码。 +11. 按要求上传法定代表人证件照。 +12. 填写经办人姓名和手机号。 +13. 上传经办人证件照。 +14. 如果经办人不是法人,按要求上传授权委托书。 +15. 填写业务网站、App 或小程序信息。 +16. 上传产品截图和用户接收短信场景截图。 +17. 提交审核。 + +资料说明: + +- 如果签名使用公司全称或简称,通常需要营业执照支撑。 +- 如果签名使用产品名、小程序名或商标名,需要准备产品归属证明、软著、商标证书、小程序后台截图等材料。 +- 如果代其他公司发送短信,需要准备他用授权材料。 + +建议申请主体: + +```text +企业自用资质 +``` + +建议业务说明: + +```text +本产品为伴伴智能设备及家长端微信小程序。用户购买或绑定设备后,可在小程序中绑定儿童档案和家长手机号。设备触发紧急告警时,系统向已绑定家长手机号发送通知短信,提醒家长及时打开小程序查看设备状态和位置。短信仅用于设备安全通知,不用于营销推广。 +``` + +### 4.4 申请短信签名 + +短信签名是短信开头的品牌标识,例如: + +```text +【伴伴】 +``` + +操作步骤: + +1. 进入阿里云短信服务控制台。 +2. 进入“国内消息”。 +3. 进入“签名管理”。 +4. 点击“添加签名”。 +5. 签名用途选择:自用。 +6. 签名来源根据实际情况选择: + - 公司简称。 + - App 应用。 + - 小程序。 + - 网站。 + - 商标名。 +7. 签名名称填写:`伴伴`。 +8. 上传证明材料。 +9. 填写申请说明。 +10. 提交审核。 + +如果使用“伴伴”作为签名,材料取决于“伴伴”在企业名、产品名、小程序名或商标中的归属: + +- 如果营业执照企业名称或简称包含“伴伴”,上传营业执照即可作为主要证明。 +- 如果微信小程序名称是“伴伴”或包含“伴伴”,上传小程序后台基础信息截图。 +- 如果“伴伴”是商标,上传商标注册证或受理通知书。 +- 如果“伴伴”只是内部项目名,审核风险较高,建议改用公司简称或已经能证明归属的产品名。 + +签名申请说明建议: + +```text +“伴伴”为本公司智能设备及家长端微信小程序产品名称。该签名用于向已绑定设备的家长发送设备告警等通知短信,不用于营销推广。用户在小程序中完成设备绑定和手机号授权后才会接收相关通知。 +``` + +审核注意: + +- 签名不要包含“测试”“Demo”“通知中心”等泛称。 +- 签名要能证明归属。 +- 签名不要使用第三方品牌、平台名或容易混淆的名称。 + +### 4.5 申请短信模板:设备告警通知 + +这是当前项目必须申请的模板。 + +控制台选择: + +- 模板类型:通知短信。 +- 模板名称:`设备告警通知`。 +- 适用场景:设备安全告警通知。 +- 是否营销:否。 + +当前后端模板变量名: + +- `conference`:当前代码用于儿童名或设备名。 +- `code`:当前代码用于事件名称,例如“设备告警”。 +- `time`:事件时间。 +- `address`:地址或兜底文案。 + +告警模板正文建议: + +```text +您绑定的${conference}触发${code},时间${time},位置${address},请及时打开小程序查看。 +``` + +变量示例: + +```text +conference = 小明 +code = 设备告警 +time = 2026-07-09 15:30 +address = 浙江省杭州市萧山区扬帆路附近 +``` + +模板申请说明建议: + +```text +该模板用于伴伴智能设备触发紧急告警后,向已绑定设备的家长手机号发送安全通知。接收人均为小程序内已绑定设备并授权留存手机号的家长或家庭成员。短信内容包含儿童/设备名称、告警类型、告警时间和简短位置,提醒家长及时打开小程序查看详情。该模板不用于营销推广。 +``` + +场景截图建议上传: + +- 小程序设备绑定成功页。 +- 小程序当前孩子或设备管理页。 +- 小程序定位页。 +- 小程序告警记录或系统通知页。 +- 手机号授权/绑定入口截图。 + +审核风险点: + +- 模板里不要写“点击链接”“加微信”“领取”“优惠”等营销词。 +- 地址变量不要过长。当前后端会把地址裁剪到 30 个字符,并过滤特殊字符。 +- 如果阿里云认为变量名 `conference` 不符合语义,可选择两种处理方式: + - 先按当前代码变量名提交,并在变量说明里解释 `conference` 表示儿童/设备名称。 + - 或者让技术先把后端变量名改为 `child_name` / `event_type` / `event_time` / `address`,再提交模板。 + +### 4.6 申请短信模板:设备留言通知 + +这是预留模板。只有产品明确希望“设备留言也发短信”时才必须申请。 + +控制台选择: + +- 模板类型:通知短信。 +- 模板名称:`设备留言通知`。 +- 适用场景:设备留言提醒。 +- 是否营销:否。 + +模板正文建议: + +```text +您绑定的${conference}收到${code},时间${time},请打开${address}查看。 +``` + +变量示例: + +```text +conference = 小明 +code = 设备留言 +time = 2026-07-09 15:30 +address = 小程序 +``` + +模板申请说明建议: + +```text +该模板用于伴伴智能设备向家长发送语音留言后,提醒已绑定家长进入小程序查看留言内容。接收人均为小程序内已绑定设备并留存手机号的家长或家庭成员。短信仅用于亲子沟通提醒和设备消息提醒,不用于营销推广。 +``` + +注意: + +- 当前代码有 `notify_leave_message` 服务方法,但实际留言主链路更多调用微信公众号模板通知。 +- 如果要启用这条短信,需要后端在留言成功后调用 `sms_notification_service.schedule_leave_message_notification(...)`,否则即使模板审核通过也不会发送。 + +### 4.7 模板变量说明建议 + +提交模板时,如果控制台要求填写变量含义,可按下表填写: + +| 变量名 | 业务含义 | 示例 | 长度建议 | +| --- | --- | --- | --- | +| `conference` | 儿童名、设备名或绑定对象名称 | 小明 | 20 字以内 | +| `code` | 通知事件类型 | 设备告警 | 10 字以内 | +| `time` | 事件发生时间 | 2026-07-09 15:30 | 固定格式 | +| `address` | 告警位置或查看入口 | 浙江省杭州市萧山区扬帆路附近 | 30 字以内 | + +更推荐的长期变量名: + +| 推荐变量名 | 当前变量名 | 说明 | +| --- | --- | --- | +| `child_name` | `conference` | 更贴合儿童/设备名称语义 | +| `event_type` | `code` | 更贴合告警/留言事件语义 | +| `event_time` | `time` | 更清晰 | +| `address` | `address` | 可保留 | + +如果现在不改代码,模板必须使用当前变量名。 + +### 4.8 等待运营商实名报备 + +阿里云短信的资质、签名、模板审核通过后,并不一定代表可以立刻稳定向所有号码发送。还要关注运营商实名报备状态。 + +操作步骤: + +1. 在短信服务控制台查看资质审核状态。 +2. 查看签名审核状态。 +3. 查看模板审核状态。 +4. 查看实名报备或运营商报备状态。 +5. 等全部状态正常后再做生产发送。 + +上线前不要只用“模板审核通过”判断完成。 + +### 4.9 测试发送 + +测试前确认: + +- `SMS_ENABLED=true`。 +- `SMS_DRY_RUN=false`。 +- AccessKey 可用。 +- 签名已审核通过。 +- 模板已审核通过。 +- 模板变量名和后端一致。 +- 接收手机号是已绑定家长手机号。 + +建议测试流程: + +1. 先用阿里云控制台提供的测试发送功能验证签名和模板。 +2. 用测试手机号发送告警模板。 +3. 确认短信内容正常展示。 +4. 再在后端测试环境配置真实参数。 +5. 触发一条设备告警。 +6. 确认后端日志返回 `OK`。 +7. 确认家长手机号收到短信。 +8. 确认同一设备短时间内不会重复轰炸。 + +当前后端有防抖配置: + +```env +SMS_ALARM_DEDUP_SECONDS=300 +SMS_LEAVE_MESSAGE_DEDUP_SECONDS=60 +``` + +## 5. 材料清单 + +### 5.1 高德材料清单 + +- 企业营业执照。 +- 企业名称。 +- 统一社会信用代码。 +- 联系人姓名。 +- 联系人手机号。 +- 联系人邮箱。 +- 产品名称。 +- 产品介绍。 +- 小程序名称和 AppID。 +- 小程序截图。 +- 服务端域名。 +- 服务端出口公网 IP。 +- 使用接口说明:逆地理编码。 +- 使用场景说明:设备位置、轨迹、告警位置。 +- 预计日调用量。 +- 预计峰值 QPS。 +- 隐私政策或定位授权说明。 + +### 5.2 阿里云短信材料清单 + +- 企业营业执照。 +- 统一社会信用代码。 +- 法定代表人姓名。 +- 法定代表人证件号码。 +- 法定代表人证件照片。 +- 经办人姓名。 +- 经办人手机号。 +- 经办人身份证正反面。 +- 经办人授权委托书,如平台要求。 +- 产品名称。 +- 小程序名称和 AppID。 +- 小程序后台基础信息截图。 +- 小程序设备绑定页截图。 +- 手机号授权或绑定页截图。 +- 告警记录或通知页截图。 +- 定位页截图。 +- 隐私政策。 +- 用户协议。 +- 短信签名归属证明。 +- 短信模板申请说明。 +- 测试手机号。 + +## 6. 后端配置交接表 + +高德申请完成后交付: + +| 配置项 | 示例 | 说明 | +| --- | --- | --- | +| `Amap_key` | `xxxxxxxx` | 高德 Web 服务 Key | +| `AMAP_REVERSE_GEOCODE_URL` | `https://restapi.amap.com/v3/geocode/regeo` | 一般保持默认 | +| `AMAP_HTTP_TIMEOUT_SECONDS` | `5` | 一般保持默认 | + +阿里云短信申请完成后交付: + +| 配置项 | 示例 | 说明 | +| --- | --- | --- | +| `SMS_ENABLED` | `true` | 是否启用短信 | +| `SMS_PROVIDER` | `aliyun` | 当前只支持阿里云 | +| `SMS_DRY_RUN` | `false` | 生产必须关闭 dry run | +| `ALIYUN_SMS_ACCESS_KEY_ID` | `LTAI...` | RAM 子账号 AK | +| `ALIYUN_SMS_ACCESS_KEY_SECRET` | `***` | RAM 子账号 SK | +| `ALIYUN_SMS_SIGN_NAME` | `伴伴` | 审核通过的签名,不带方括号 | +| `ALIYUN_SMS_ALARM_TEMPLATE_CODE` | `SMS_123456789` | 告警模板 Code | +| `ALIYUN_SMS_LEAVE_MESSAGE_TEMPLATE_CODE` | `SMS_987654321` | 留言模板 Code,可选 | +| `ALIYUN_SMS_ENDPOINT` | `dysmsapi.aliyuncs.com` | 一般保持默认 | + +安全要求: + +- AccessKey Secret 不要发到群聊。 +- 不要提交到 Git。 +- 不要写进小程序前端代码。 +- 生产、测试分开使用不同 AccessKey 和模板更好。 +- 离职或泄露风险时及时轮换密钥。 + +## 7. 审核失败常见原因 + +### 7.1 高德 + +- 账号未完成企业认证。 +- Key 选错平台,把服务端 Web 服务误选成 Web JS API。 +- 没有说明清楚商用场景。 +- 没有评估调用量和 QPS。 +- Key 暴露在前端代码。 +- 生产服务没有固定出口 IP,无法配置安全限制。 + +### 7.2 阿里云短信 + +- 签名无法证明归属。 +- 使用产品名但没有小程序、商标或软著证明。 +- 模板内容像营销短信。 +- 模板变量过多或变量含义不清。 +- 模板变量可能导致整句内容不可控。 +- 申请验证码模板但实际是通知场景。 +- 没有上传用户绑定手机号或接收通知的页面截图。 +- 资质、签名、模板虽然通过,但运营商实名报备未完成。 +- AccessKey 使用主账号,安全风险高。 + +## 8. 推荐申请顺序 + +建议按以下顺序推进: + +1. 确认企业主体和产品签名名称。 +2. 准备营业执照、法人、经办人、小程序截图、隐私政策。 +3. 申请高德企业认证。 +4. 创建高德应用和 Web 服务 Key。 +5. 确认高德商用配额。 +6. 申请阿里云企业实名认证。 +7. 开通阿里云短信服务。 +8. 创建 RAM 子账号和 AccessKey。 +9. 申请短信资质。 +10. 申请短信签名。 +11. 申请设备告警通知模板。 +12. 需要时申请设备留言通知模板。 +13. 等运营商实名报备完成。 +14. 控制台测试发送。 +15. 后端测试环境配置。 +16. 触发真实设备告警验证。 +17. 生产环境配置。 +18. 上线后观察发送成功率、失败码和费用。 + +## 9. 可直接复制的申请说明 + +### 9.1 高德用途说明 + +```text +本产品为伴伴智能设备及家长端微信小程序。用户完成设备绑定后,设备会在定位、轨迹和紧急告警等场景上报经纬度。服务端调用高德 Web 服务逆地理编码接口,将经纬度转换为可读地址,用于家长在小程序内查看设备当前位置、历史轨迹和告警位置。定位数据仅用于已绑定设备的安全查看和告警追溯,不用于广告投放、营销推广或用户画像。 +``` + +### 9.2 阿里云短信资质业务说明 + +```text +本产品为伴伴智能设备及家长端微信小程序。家长购买或绑定设备后,可在小程序中绑定儿童档案和手机号。当设备触发紧急告警时,系统会向已绑定家长手机号发送通知短信,提醒家长及时打开小程序查看设备状态和位置。短信接收人均为已绑定设备并留存手机号的家长或家庭成员,短信仅用于设备安全通知,不用于营销推广。 +``` + +### 9.3 阿里云签名说明 + +```text +“伴伴”为本公司智能设备及家长端微信小程序产品名称。该短信签名用于向已绑定设备的家长发送设备告警等通知短信。用户在小程序中完成设备绑定和手机号授权后才会接收相关短信。该签名不用于营销推广。 +``` + +### 9.4 阿里云告警模板说明 + +```text +该模板用于伴伴智能设备触发紧急告警后,向已绑定设备的家长手机号发送安全通知。短信内容包含儿童/设备名称、告警类型、告警时间和简短位置,提醒家长及时打开小程序查看详情。接收人均为小程序内已绑定设备并授权留存手机号的家长或家庭成员。该模板不用于营销推广。 +``` + +### 9.5 阿里云留言模板说明 + +```text +该模板用于伴伴智能设备向家长发送语音留言后,提醒已绑定家长进入小程序查看留言内容。接收人均为小程序内已绑定设备并留存手机号的家长或家庭成员。短信仅用于亲子沟通提醒和设备消息提醒,不用于营销推广。 +``` + +## 10. 官方参考链接 + +- 高德开放平台:https://lbs.amap.com/ +- 高德 Web 服务 Key 获取说明:https://lbs.amap.com/api/webservice/guide/create-project/get-key +- 高德逆地理编码接口:https://lbs.amap.com/api/webservice/guide/api/georegeo +- 阿里云短信服务新手指引:https://help.aliyun.com/zh/sms/getting-started/get-started-with-sms +- 阿里云短信资质说明:https://help.aliyun.com/zh/sms/user-guide/qualification-application-description +- 阿里云短信签名申请:https://help.aliyun.com/zh/sms/user-guide/create-signatures +- 阿里云短信模板申请:https://help.aliyun.com/zh/sms/user-guide/create-message-templates-1 +- 阿里云 SendSms API:https://help.aliyun.com/zh/sms/developer-reference/api-dysmsapi-2017-05-25-sendsms + +## 11. 当前项目代码锚点 + +- 高德逆地理编码服务:`talkingq-url/banban/service/amap_geocode.py` +- 当前位置地址解析:`talkingq-url/banban/service/location.py` +- 告警地址解析:`talkingq-url/banban/service/device_alarm.py` +- 短信发送服务:`talkingq-url/banban/service/sms_notification.py` +- 短信配置项:`talkingq-url/config.py` +- 设备告警触发短信:`talkingq-url/handlers/mqtt_handler.py` diff --git a/docs/local-mqtt-stability-concurrency-test-plan.md b/docs/local-mqtt-stability-concurrency-test-plan.md new file mode 100644 index 0000000..bb11e35 --- /dev/null +++ b/docs/local-mqtt-stability-concurrency-test-plan.md @@ -0,0 +1,507 @@ +# 本地 MQTT 并发与稳定性测试方案 + +更新日期:2026-07-14 + +本文档用于说明 Banban / TalkingQ 后端在不调用真实外部服务的前提下,如何做本地全流程并发和稳定性测试。测试重点是 MQTT 设备接入、消息解析、服务端回包、GPS 查询响应、告警处理、数据库落库、后端健康状态和资源变化。 + +## 1. 测试目标 + +### 1.1 并发度 + +并发度用于回答系统同时承载多少设备连接和消息流量。 + +本项目里主要看: + +- 同时在线的 MQTT 设备客户端数量。 +- 设备状态上报 `msg_id=000` 的吞吐。 +- 告警事件 `msg_id=010` 的吞吐和落库。 +- 服务端 GPS 查询 `msg_id=001` 的下发和设备响应。 +- 服务端事件响应 `event_resp` 是否完整返回。 + +### 1.2 稳定性 + +稳定性用于回答系统在持续运行一段时间后是否保持正确和健康。 + +本项目里主要看: + +- 设备连接是否持续保持。 +- 上行消息和服务端回包是否丢失。 +- 数据库写入是否持续成功。 +- 后端健康检查是否持续返回 `200`。 +- 错误日志是否新增。 +- CPU、内存、数据库连接数、MQTT 连接数是否持续爬升或堆积。 + +## 2. 测试边界 + +### 2.1 本次覆盖 + +- 本地 FastAPI 后端。 +- 本地 MQTT broker。 +- 本地 MySQL。 +- 真实 MQTT 客户端连接,每个模拟设备一个 MQTT client。 +- MQTT 主流程: + - `device/{device_id}/event` 上行。 + - `device/{device_id}/event_resp` 下行响应。 + - `device/{device_id}/command` 服务端命令。 + - `device/{device_id}/response` 设备响应。 +- 状态上报、告警、GPS 查询、异步持久化、告警落库。 + +### 2.2 本次不覆盖 + +- 线上 MQTT broker。 +- 线上服务器网络和 TLS。 +- 真实高德逆地理编码调用。 +- 真实短信发送。 +- 真实设备固件网络抖动。 +- 断线重连风暴。 +- 慢数据库、数据库断连、外部服务长超时。 + +这些场景需要单独做线上灰度压测或故障注入测试。 + +## 3. 外部服务隔离策略 + +本地全流程测试要验证主流程,但不能依赖真实外部服务。 + +推荐策略: + +- GPS 响应使用无效坐标 `lat=0,lng=0`。 +- 后端应忽略无效 MQTT 定位,不调用高德解析。 +- 告警测试账号不配置可用家长手机号。 +- 后端短信链路应走“无手机号跳过”,不调用短信服务。 +- 不使用真实 COS 上传、微信模板消息或其他公网依赖作为通过条件。 + +通过标准不是“外部服务成功”,而是“外部服务不可用或被跳过时,MQTT 主流程、回包和落库不受影响”。 + +## 4. 测试环境 + +### 4.1 本地服务 + +在 `talkingq-url` 目录运行: + +```bash +.venv/bin/amqtt +``` + +启动后本地 MQTT broker 监听: + +```text +127.0.0.1:1883 +``` + +启动后端: + +```bash +.venv/bin/uvicorn main:app --host 127.0.0.1 --port 8080 +``` + +健康检查: + +```bash +curl -sS -o /dev/null -w 'openapi_http=%{http_code} time=%{time_total}\n' \ + http://127.0.0.1:8080/openapi.json +``` + +预期: + +```text +openapi_http=200 +``` + +### 4.2 配置要求 + +`talkingq-url/.env` 应指向本地服务: + +```text +DB_HOST=127.0.0.1 +DB_PORT=3306 +TALKINGQ_MQTT_BROKER=127.0.0.1 +TALKINGQ_MQTT_PORT=1883 +``` + +### 4.3 测试设备数据 + +建议使用专门的压测设备 ID,避免污染真实设备: + +```text +TalkingQ_LOAD000001 +TalkingQ_LOAD000002 +... +TalkingQ_LOAD000500 +``` + +测试前确认: + +- `device_auth` 有对应设备。 +- `device_bindings` 有对应绑定。 +- 告警落库使用的 `owner_user_id`、`child_id` 不超过当前数据库字段范围。 + +检查示例: + +```sql +SELECT COUNT(*) FROM device_auth WHERE device_id LIKE 'TalkingQ_LOAD%'; +SELECT COUNT(*) FROM device_bindings WHERE device_id LIKE 'TalkingQ_LOAD%'; +``` + +## 5. 压测脚本要求 + +压测脚本应满足以下行为: + +- 每个设备使用一个独立 MQTT client。 +- 每个 client 订阅: + - `device/{device_id}/event_resp` + - `device/{device_id}/command` +- 每个 client 周期性发布 `msg_id=000` 设备信息。 +- 按固定比例发布 `msg_id=010` 告警。 +- 收到服务端 `msg_id=001` GPS 查询后,回复 `device/{device_id}/response`。 +- 默认 GPS 模式使用 `invalid`,返回 `0,0`,避免真实高德调用。 +- 统计发送数、回包数、GPS 命令数、GPS 响应数、发布延迟。 + +状态上报 payload 示例: + +```json +{ + "msg_id": "000", + "data": { + "id": "TalkingQ_LOAD000001", + "power": 79, + "signal": 4, + "version": "load-client-1.0.0", + "voice": 60, + "imei": "990000000000001", + "device_type": 2, + "latitude": 0, + "longitude": 0, + "coord_type": "gcj02" + } +} +``` + +告警 payload 示例: + +```json +{ + "msg_id": "010" +} +``` + +GPS 响应 payload 示例: + +```json +{ + "msg_id": "001", + "status": "success", + "data": { + "latitude": 0, + "longitude": 0, + "coord_type": "gcj02", + "accuracy_m": 10, + "source": 1, + "battery_pct": 80 + } +} +``` + +## 6. 推荐测试阶段 + +### 6.1 冒烟测试 + +目的:确认脚本、broker、后端、DB 基本可用。 + +建议参数: + +```text +20 个 MQTT client +1 轮状态上报 +每 10 台设备触发 1 次告警 +settle 5 秒 +``` + +通过标准: + +- client 连接成功数等于请求数。 +- `sent_total == event_resp_total`。 +- `resp_000 == sent_000`。 +- `resp_010 == sent_010`。 +- `command_001 == gps_response_sent`。 + +### 6.2 短时并发测试 + +目的:验证较高设备规模下的瞬时吞吐。 + +建议参数: + +```text +500 个 device_id +3 轮状态上报 +每 10 台设备触发 1 次告警 +settle 20 秒 +``` + +通过标准: + +- 所有事件收到 `event_resp`。 +- 告警落库数量与发送数量一致。 +- 健康检查仍返回 `200`。 +- 新增错误日志为 `0`。 + +### 6.3 真实连接稳定性测试 + +目的:验证 500 个独立 MQTT client 持续在线时,主流程是否稳定。 + +建议参数: + +```text +500 个 MQTT client +持续 30 分钟 +每 15 秒每台设备上报一次 msg_id=000 +每 60 秒每 20 台设备触发一次 msg_id=010 +GPS mode = invalid +settle 20 秒 +``` + +通过标准: + +- 500 个 client 全部连接成功。 +- 连接数在测试期间保持稳定。 +- `sent_total == event_resp_total`。 +- `sent_000 == resp_000`。 +- `sent_010 == resp_010`。 +- `command_001 == gps_response_sent`。 +- 30 分钟内后端健康检查持续返回 `200`。 +- 新增错误日志为 `0`。 +- MySQL 连接数没有持续堆积。 +- 后端和 broker RSS 没有持续单调增长。 + +### 6.4 长时稳定性测试 + +目的:把 30 分钟结论提升为更可靠的上线前参考。 + +建议参数: + +```text +500 个 MQTT client +持续 2 小时 +或过夜 8-12 小时 +保持 15 秒状态上报和 60 秒告警节奏 +``` + +增加观察项: + +- 日志滚动后是否还能正常写入。 +- 定时任务是否干扰 MQTT 主流程。 +- 数据库表增长后查询是否变慢。 +- 后端进程 RSS 是否长期爬升。 +- broker 是否出现连接残留。 + +## 7. 监控指标 + +建议每 60 秒记录一次: + +```text +sample +elapsed_s +backend_cpu +backend_mem +backend_rss_kb +broker_cpu +broker_mem +broker_rss_kb +mqtt_established +mysql_threads +mysql_processes +http_code +http_time_s +new_error_lines +``` + +采集方式示例: + +```bash +ps -o %cpu=,%mem=,rss= -p +ps -o %cpu=,%mem=,rss= -p +lsof -nP -iTCP:1883 -sTCP:ESTABLISHED +curl -sS -o /dev/null -w '%{http_code} %{time_total}' http://127.0.0.1:8080/openapi.json +``` + +MySQL 连接数: + +```sql +SHOW STATUS LIKE 'Threads_connected'; +SHOW PROCESSLIST; +``` + +错误日志过滤: + +```bash +rg -n "(ERROR|failed|DataError|Out of range|告警地址解析失败|当前位置地址解析失败|amap|高德)" \ + talkingq-url/logs/talkingq_YYYY-MM-DD.log +``` + +## 8. 本次执行结果 + +执行日期:2026-07-14 + +### 8.1 单元测试 + +```text +86 passed +``` + +### 8.2 500 客户端 30 分钟稳定性测试 + +测试参数: + +```text +devices_requested=500 +duration_seconds=1800 +info_interval_seconds=15 +alarm_every=20 +alarm_interval_seconds=60 +gps_mode=invalid +``` + +结果: + +```text +elapsed_seconds=1820.053 +client_ready_success=500 +client_ready_failed=0 +sent_total=60750 +event_resp_total=60750 +sent_000=60000 +resp_000=60000 +sent_010=750 +resp_010=750 +command_001=750 +gps_response_sent=750 +publish_latency_ms_avg=29.301 +publish_latency_ms_p95=48.727 +``` + +### 8.3 数据库验证 + +```text +device_settings_load=500 +device_alarm_events_load_total=877 +device_alarm_events_load_last_40m=750 +child_location_current_load=0 +mysql_threads_connected=6 +``` + +说明: + +- `device_alarm_events_load_last_40m=750` 与本轮告警发送数一致。 +- `child_location_current_load=0` 是预期结果,因为 GPS 使用 `0,0`,后端忽略无效定位。 + +### 8.4 资源变化 + +后端: + +```text +压测前 RSS 约 129.9MB +500 客户端连接后 RSS 约 131-132MB +中途回落到约 120-121MB +结束后约 121.6MB +CPU 基本 0.1%-0.2% +``` + +MQTT broker: + +```text +压测前 RSS 约 43.9MB +500 客户端连接后 RSS 约 73-75MB +后半程稳定在约 69-70MB +结束后约 70.1MB +CPU 基本 0.0%-0.3% +``` + +连接数: + +```text +压测前 mqtt_established=2 +压测中 mqtt_established=1002 +压测后 mqtt_established=2 +``` + +说明:本机 loopback 下 `lsof` 会看到连接两端,`1002` 约等于 `501` 条 MQTT 连接,包括 500 个设备客户端和 1 条后端服务连接。 + +### 8.5 日志验证 + +测试窗口内新增错误日志: + +```text +0 +``` + +未发现: + +- `ERROR` +- `failed` +- `DataError` +- `Out of range` +- `告警地址解析失败` +- `当前位置地址解析失败` +- `amap` +- `高德` + +## 9. 本次结论 + +本地环境下,当前项目已验证可以稳定承载: + +```text +500 个独立 MQTT 客户端 +持续 30 分钟 +60750 条事件完整回包 +750 条告警完整落库 +750 次 GPS 查询完整响应 +``` + +测试期间: + +- 后端健康检查持续 `200`。 +- 新增错误日志为 `0`。 +- MySQL 连接没有堆积。 +- 后端和 broker 内存没有持续爬升。 +- 外部服务未调用时,MQTT 主流程不受影响。 + +因此可以判断:本地单实例下,500 台设备级 MQTT 连接和当前消息节奏的 30 分钟稳定性通过。 + +## 10. 风险和下一步 + +### 10.1 当前结论不能直接等同线上容量 + +原因: + +- 本地 broker 是 `amqtt`,不是线上 broker。 +- 本地网络没有公网延迟、TLS、丢包、移动网络抖动。 +- 高德和短信没有真实调用。 +- 测试时长是 30 分钟,不是 24 小时。 +- 没有模拟断线重连风暴。 + +### 10.2 建议补充测试 + +上线前建议继续做: + +- 500 客户端持续 2 小时。 +- 500 客户端过夜 8-12 小时。 +- 断线重连测试:一次性断开 20%、50%、100% 客户端后重连。 +- 慢 DB 测试:模拟 DB 写入变慢,确认 MQTT 回包和主流程是否可控。 +- 高德失败测试:模拟超时、HTTP 500、无效 Key,确认地址解析失败不影响告警落库。 +- 短信失败测试:模拟短信服务超时或失败,确认告警主流程不被拖垮。 +- 线上灰度测试:在真实 broker 和服务器规格下从 50、100、300、500 逐步提升。 + +## 11. 通过标准汇总 + +一次测试可判定通过,需要同时满足: + +- 客户端连接成功数等于目标数。 +- `sent_total == event_resp_total`。 +- `sent_000 == resp_000`。 +- `sent_010 == resp_010`。 +- `command_001 == gps_response_sent`。 +- 告警新增落库数量等于 `sent_010`。 +- 健康检查全程 `200`。 +- 新增错误日志为 `0`。 +- MySQL 连接数没有持续增长。 +- 后端和 broker RSS 没有持续单调增长。 +- 测试结束后 MQTT 连接数回落到基线。