WxTemplateMessageService.php 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. <?php
  2. namespace App\Service\Weixin;
  3. use Exception;
  4. class WxTemplateMessageService extends WeixinService
  5. {
  6. /**
  7. * 发送微信模板消息
  8. *
  9. * @param string $templateKey 模板配置key(对应 config 中的 template_map)
  10. * @param array $dataData 模板参数(key 对应 config 中定义)
  11. * @param array $options [
  12. * 'pagepath' => '', // 小程序路径
  13. * 'url' => '', // 网页路径
  14. * 'openid' => '', // 可选,优先使用
  15. * ]
  16. * @return array [bool, string]
  17. */
  18. public function sendTemplateMessage(string $templateKey, array $dataData, array $options = []): array
  19. {
  20. try {
  21. $config = config("wx_msg.template_map.$templateKey");
  22. if (!$config) return [false, "微信消息模板键值不存在: {$templateKey}"];
  23. $openid = $options['openid'] ?? "";
  24. if (empty($openid)) return [false, "openid 不能为空"];
  25. list($tokenOk, $token) = $this->getTokenSTABLE();
  26. if (! $tokenOk) return [false, $token];
  27. $jumpType = $config['jump_type'] ?? '';
  28. $payload = [
  29. 'touser' => $openid,
  30. 'template_id' => $config['tmp_id'],
  31. 'data' => $this->buildTemplateData($config, $dataData, $options),
  32. ];
  33. // 动态跳转处理
  34. if ($jumpType === 'h5') {
  35. $payload['url'] = $options['url'] ?? '';
  36. } elseif ($jumpType === 'mp') {
  37. $payload['miniprogram'] = [
  38. 'appid' => config('wx_msg.appid'),
  39. 'pagepath' => $options['pagepath'] ?? '',
  40. ];
  41. }
  42. $url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={$token}";
  43. $res = $this->curlOpen($url, ['post' => json_encode($payload, JSON_UNESCAPED_UNICODE)]);
  44. $res = json_decode($res, true);
  45. if (isset($res['errcode']) && $res['errcode'] === 0) return [true, ''];
  46. return [false , $res['errmsg']];
  47. } catch (Exception $e) {
  48. return [false, $e->getMessage()];
  49. }
  50. }
  51. /**
  52. * 构建模板数据结构
  53. */
  54. protected function buildTemplateData(array $config, array $dataData, array $options = []): array
  55. {
  56. $result = [];
  57. foreach ($config['params'] as $key => $field) {
  58. $value = $dataData[$key] ?? '';
  59. $maxLen = $this->getWxFieldMaxLength($field);
  60. $value_f = $this->safeWxValue((string)$value, $maxLen);
  61. $result[$field] = ['value' => $value_f, 'color' => '#173177'];
  62. }
  63. return $result;
  64. }
  65. /**
  66. * 微信字段类型最大长度映射
  67. */
  68. protected function getWxFieldMaxLength(string $type): int
  69. {
  70. $map = [
  71. 'thing' => 20,
  72. 'thing1' => 20,
  73. 'thing2' => 20,
  74. 'thing3' => 20,
  75. 'thing4' => 20,
  76. 'thing5' => 20,
  77. 'character_string' => 32,
  78. 'character_string1' => 32,
  79. 'character_string2' => 32,
  80. 'character_string3' => 32,
  81. 'number' => 10,
  82. 'amount' => 10,
  83. 'time' => 20,
  84. 'date' => 20,
  85. 'phone_number' => 11,
  86. 'car_number' => 10,
  87. ];
  88. // 截取字段类型前缀(比如 thing5 → thing)
  89. $prefix = preg_replace('/\d+$/', '', $type);
  90. return $map[$type] ?? $map[$prefix] ?? 30;
  91. }
  92. /**
  93. * 安全处理微信模板值(截断 + 清理)
  94. */
  95. protected function safeWxValue(string $value, int $maxLen): string
  96. {
  97. $value = trim(strip_tags($value)); // 去除HTML标签
  98. $value = preg_replace('/\s+/', ' ', $value); // 合并空白字符
  99. // 多字节安全截断(支持中文)
  100. if (mb_strlen($value, 'UTF-8') > $maxLen) {
  101. $value = mb_substr($value, 0, $maxLen - 1, 'UTF-8') . '…';
  102. }
  103. return $value;
  104. }
  105. }