Forráskód Böngészése

发送客服消息

liuzejian 1 éve
szülő
commit
6087373671

+ 89 - 0
app/Console/Commands/WechatPlatform/KFMessageSend.php

@@ -0,0 +1,89 @@
+<?php
+
+namespace App\Console\Commands\WechatPlatform;
+
+use App\Jobs\WechatPlatform\GZHSendKFMessage;
+use App\Service\Util\Support\Trace\TraceContext;
+use App\Service\WechatPlatform\WechatPlatformConstService;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+
+class KFMessageSend extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'WechatPlatform:KFMessageSend {--pk= : wechat_kf_messages.id}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Execute the console command.
+     */
+    public function handle()
+    {
+        $pk = $this->option('pk');
+        $traceContext = new TraceContext();
+        $now = date('Y-m-d H:i:s');
+        DB::table('wechat_kf_messages')
+            ->where([
+                ['status', '=', WechatPlatformConstService::KF_MESSAGE_STATUS_PRE_SEND],
+                ['is_enabled', '=', 1],
+                ['gzh_ids', '<>', '']
+            ])->whereNotNull('send_at')
+            ->when($pk, function ($query, $pk){
+                return $query->where('id', $pk);
+            }, function ($query) use ($now) {
+                return $query->where('send_at', '<=', $now)
+                    ->where('send_at', '>', date('Y-m-d H:i:s', strtotime('-5 minute')));
+            })->orderBy('id')
+            ->chunk(100, function ($items) use ($traceContext, $now){
+                DB::table('wechat_kf_messages')
+                    ->whereIn('id', $items->pluck('id'))
+                    ->update([
+                        'status'  => WechatPlatformConstService::KF_MESSAGE_STATUS_SENDING,
+                        'updated_at' => $now
+                    ]);
+
+                foreach ($items as $item) {
+                    myLog('KFMessageSend')->info('开始处理消息', [
+                        'message_id' => $item->id,
+                        'traceInfo' => $traceContext->getTraceInfo()
+                    ]);
+                    try {
+                        $gzhIds = explode('#', trim($item->gzh_ids, '#'));
+
+                        foreach ($gzhIds as $gzhId) {
+                            GZHSendKFMessage::dispatch([
+                                'gzhId' => $gzhId,
+                                'messageId' => $item->id,
+                                'traceInfo' => $traceContext->getTraceInfo()
+                            ])->onConnection('queue-redis')
+                                ->onQueue('{duanju_manage}.wechatPlatform.sendKFMessage');
+                        }
+                    }catch (\Exception $exception) {
+                        myLog('KFMessageSend')->error('发送客服消息异常', [
+                            'message_id' => $item->id,
+                            'exceptionMsg' => $exception->getMessage(),
+                            'traceInfo' => $traceContext->getTraceInfo()
+                        ]);
+                    }
+                }
+
+                DB::table('wechat_kf_messages')
+                    ->whereIn('id', $items->pluck('id'))
+                    ->update([
+                        'status'  => WechatPlatformConstService::KF_MESSAGE_STATUS_FINISH,
+                        'updated_at' => $now
+                    ]);
+            });
+    }
+}

+ 183 - 0
app/Jobs/WechatPlatform/GZHSendKFMessage.php

@@ -0,0 +1,183 @@
+<?php
+
+namespace App\Jobs\WechatPlatform;
+
+use App\Service\Util\Support\Trace\TraceContext;
+use App\Service\WechatPlatform\GZHSendKFMessageService;
+use App\Service\WechatPlatform\WechatPlatform;
+use EasyWeChat\OfficialAccount\Application;
+use Illuminate\Bus\Queueable;
+use Illuminate\Contracts\Queue\ShouldBeUnique;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Foundation\Bus\Dispatchable;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Queue\SerializesModels;
+use Predis\Command\Traits\DB;
+
+class GZHSendKFMessage implements ShouldQueue
+{
+    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+
+    /**
+     * @var
+     * <pre>
+     * [
+     *  'gzhId' => $gzhId,   // wechat_authorization_infos.id
+     *  'messageId' => $item->id,  // wechat_kf_messages.id
+     *  'traceInfo' => $traceContext->getTraceInfo()  // traceInfo
+     * ]
+     * </pre>
+     */
+    private $info;
+
+    private $isTest;
+
+    /**
+     * @var TraceContext
+     */
+    private $traceContext;
+    /**
+     * Create a new job instance.
+     */
+    public function __construct($info)
+    {
+        $this->isTest = $info['isTest'] ?? false;
+    }
+
+    /**
+     * Execute the job.
+     */
+    public function handle(): void
+    {
+        $this->traceContext = TraceContext::newFromParent($this->info['traceInfo']);
+        myLog('KFMessageSend')->info('公众号开始发送客服消息', [
+            'info' => $this->info,
+            'traceInfo' => $this->traceContext->getTraceInfo(),
+        ]);
+
+        $gzh = $this->getGZH();
+        if(!$gzh) return;
+
+        $message = $this->getMessage();
+        if(!$message) return;
+
+
+        $messageContent = collect(\json_decode($message->message_content, true));
+        $messageStr = $messageContent->pluck('text')->join("\n");
+
+        $officialAccount = $this->getOfficialAccount($gzh);
+        if(false === $officialAccount) return;
+
+        if($this->isTest) {
+            $openid = $this->info['openid'] ?? '';
+            if(!$openid) {
+                myLog('KFMessageSend')->error('测试回传没有openid', [
+                    'info' => $this->info
+                ]);
+            }
+            GZHSendKFMessageService::sendText($officialAccount, $openid, $messageStr, $this->traceContext);
+        } else {
+            $next_openid = '';
+            $loop = 1;
+            while (true) {
+                if($loop++ > 10000) {
+                    break;
+                }
+                if(1 == $message->u_type) {
+                    $info = $this->getUserOpenids($officialAccount, $next_openid);
+                    foreach ($info['openid'] as $opid){
+                        GZHSendKFMessageService::sendText($officialAccount, $opid, $messageStr, $this->traceContext);
+                    }
+                    $next_openid = $info['next_openid'];
+                    if(!$next_openid) {
+                        break;
+                    }
+                } elseif (2 == $message->u_type) {
+                    // todo, 从人群包中获取.
+                }
+            }
+        }
+    }
+
+    private function getMessage() {
+        $message = DB::table('wechat_kf_messages')
+            ->where('id', $this->info['messageId'])
+            ->first();
+        if(!$message) {
+            myLog('KFMessageSend')->error('消息不存在', [
+                'info' => $this->info,
+                'traceInfo' => $this->traceContext->getTraceInfo(),
+            ]);
+            return false;
+        }
+        if(1 != $message->message_type) {
+            myLog('KFMessageSend')->error('不支持的消息类型', [
+                'info' => $this->info,
+                'traceInfo' => $this->traceContext->getTraceInfo(),
+            ]);
+            return false;
+        }
+        return $message;
+    }
+
+    /**
+     *
+     * @param $officialAccount Application
+     */
+    private function getUserOpenids($officialAccount, $next_openid) {
+        $result = $officialAccount->getClient()
+            ->get('cgi-bin/user/get', [
+                'query' => [
+                    'next_openid' => $next_openid,
+                ]
+            ])->toArray();
+        if(0 != ($result['errcode'] ?? 0)) {
+            return false;
+        }
+        return $result;
+    }
+
+    /**
+     * 获取公众号调用对象
+     * @param $gzh
+     * @return \EasyWeChat\OfficialAccount\Application
+     * @throws \EasyWeChat\Kernel\Exceptions\BadResponseException
+     * @throws \EasyWeChat\Kernel\Exceptions\HttpException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     * @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface
+     * @throws \Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface
+     * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface
+     * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface
+     * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
+     */
+    private function getOfficialAccount($gzh) {
+        try{
+            return WechatPlatform::buildApplication($gzh)
+                ->getOfficialAccountWithRefreshToken($gzh->authorizer_appid, $gzh->authorizer_refresh_token);
+        } catch (\Throwable $exception) {
+            myLog('KFMessageSend')->error('获取公众号调用对象失败', [
+                'exceptionMessage' => $exception->getMessage(),
+                'traceInfo' => $this->traceContext->getTraceInfo()
+            ]);
+            return false;
+        }
+    }
+
+    private function getGZH() {
+        $gzh = DB::table('wechat_authorization_infos as a')
+            ->join('wechat_open_platform_infos as o', 'a.component_appid', 'o.app_id')
+            ->where([
+                ['a.id', '=', $this->info['gzhId']],
+                ['a.is_enabled', '=', 1],
+                ['b.is_enabled', '=', 1]
+            ])->select('a.authorizer_appid', 'a.authorizer_refresh_token', 'b.app_id', 'b.secret', 'b.token', 'b.aes_key')
+            ->first();
+        if(!$gzh) {
+            myLog('KFMessageSend')->error('公众号不可用', [
+                'traceInfo' => $this->traceContext->getTraceInfo(),
+            ]);
+        }
+        return $gzh;
+    }
+}

+ 6 - 3
app/Service/Util/Support/Http/HttpRequestService.php

@@ -13,7 +13,7 @@ class HttpRequestService
      * @param $postMessage 请求体
      * @return array
      */
-    public static function simplePost($url, $postMessage) {
+    public static function simplePost($url, $postMessage, $traceContext = null) {
         $client = new Client(['timeout' => 10]);
         try {
             $res = $client->post(
@@ -32,12 +32,13 @@ class HttpRequestService
                 'url' => $url,
                 'postJson' => $postMessage,
                 'exceptionMessage' => $exception->getMessage(),
+                'traceInfo' => is_null($traceContext) ? '' : $traceContext->getTraceInfo,
             ]);
         }
         return false;
     }
 
-    public static function post($url, $options) {
+    public static function post($url, $options, $traceContext = null) {
         $client = new Client(['timeout' => 10]);
         try {
             $res = $client->request('POST', $url, $options);
@@ -52,12 +53,13 @@ class HttpRequestService
                 'url' => $url,
                 'postOptions' => $options,
                 'exceptionMessage' => $exception->getMessage(),
+                'traceInfo' => is_null($traceContext) ? '' : $traceContext->getTraceInfo,
             ]);
         }
         return false;
     }
 
-    public static function simpleGet($url, $query) {
+    public static function simpleGet($url, $query, $traceContext = null) {
         $client = new Client(['timeout' => 10]);
         try {
             $response = $client->get($url, [
@@ -74,6 +76,7 @@ class HttpRequestService
                 'url' => $url,
                 'query' => $query,
                 'exceptionMessage' => $exception->getMessage(),
+                'traceInfo' => is_null($traceContext) ? '' : $traceContext->getTraceInfo,
             ]);
         }
         return false;

+ 4 - 0
app/Service/Util/Support/Http/WechatURL.php

@@ -16,4 +16,8 @@ class WechatURL
     public const vod_gettask = 'https://api.weixin.qq.com/wxa/sec/vod/gettask?access_token=';
     // 获取媒资列表
     public const vod_listmedia = 'https://api.weixin.qq.com/wxa/sec/vod/listmedia?access_token=';
+    /**
+     * 客服接口-发消息
+     */
+    public const send_custom_message = 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=';
 }

+ 42 - 0
app/Service/WechatPlatform/GZHSendKFMessageService.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace App\Service\WechatPlatform;
+
+use App\Service\Util\Support\Http\HttpRequestService;
+use App\Service\Util\Support\Http\WechatURL;
+use EasyWeChat\OpenPlatform\Application;
+use Illuminate\Support\Facades\Cache;
+
+class GZHSendKFMessageService
+{
+    /**
+     * 发送文本客服消息
+     * @param $officialAccount \EasyWeChat\OfficialAccount\Application
+     * @param $openid
+     * @param $content
+     * @param $traceContext
+     * @return bool
+     */
+    public static function sendText($officialAccount, $openid, $content, $traceContext) {
+        try {
+            $officialAccount->getClient()
+                ->postJson('cgi-bin/message/custom/send',  [
+                    'touser' => $openid,
+                    'msgtype' => 'text',
+                    'text' => [
+                        'content' => $content
+                    ]
+                ])->toArray();
+            return true;
+        } catch (\Throwable $exception) {
+            myLog('KFMessageSend')->error('发送客服消息失败', [
+                'openid' => $openid, 'content' => $content,
+                'exceptionMsg' => $exception->getMessage(),
+                'traceInfo' => $traceContext->getTraceInfo()
+            ]);
+            return false;
+        }
+    }
+
+
+}

+ 37 - 0
app/Service/WechatPlatform/WechatPlatform.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace App\Service\WechatPlatform;
+
+use EasyWeChat\OpenPlatform\Application;
+use Illuminate\Support\Facades\Cache;
+
+class WechatPlatform
+{
+    public static function buildApplication($componentInfo) {
+        $config = [
+            'app_id' => $componentInfo->app_id, // 开放平台账号的 appid
+            'secret' => $componentInfo->secret,   // 开放平台账号的 secret
+            'token' => $componentInfo->token,  // 开放平台账号的 token
+            'aes_key' => $componentInfo->aes_key,   // 明文模式请勿填写 EncodingAESKey
+            'http' => [
+                'throw'  => true, // 状态码非 200、300 时是否抛出异常,默认为开启
+                'timeout' => 5.0,
+                'retry' => true, // 使用默认重试配置
+            ],
+        ];
+        $app = new Application($config);
+        $app->setCache(Cache::store('redis'));
+        return $app;
+    }
+
+    /**
+     * @param $app Application
+     * @param $appid
+     * @param $refreshToken
+     * @return \EasyWeChat\OfficialAccount\Application
+     */
+    public static function officialAccount($app, $appid, $refreshToken) {
+
+        return $app->getOfficialAccountWithRefreshToken($appid, $refreshToken);
+    }
+}

+ 36 - 0
app/Service/WechatPlatform/WechatPlatformConstService.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace App\Service\WechatPlatform;
+
+class WechatPlatformConstService
+{
+    /**
+     * 客服消息状态:待发送
+     */
+    public const KF_MESSAGE_STATUS_PRE_SEND=1;
+    /**
+     * 客服消息状态:发送中
+     */
+    public const KF_MESSAGE_STATUS_SENDING=2;
+    /**
+     * 客服消息状态:发送完成
+     */
+    public const KF_MESSAGE_STATUS_FINISH=3;
+    /**
+     * 客服消息状态:已停止
+     */
+    public const KF_MESSAGE_STATUS_STOP=4;
+    /**
+     * 客服消息状态-映射表
+     */
+    public const KF_MESSAGE_STATUS_MAPPER = [
+        '1' => '待发送', '2'  => '发送中', '3' => '发送完毕', '4' => '已停止'
+    ];
+
+    /**
+     * 客服消息类型:纯文本
+     */
+    public const KF_MESSAGE_TYPE_MAPPER = [
+        '1' => '纯文本'
+    ];
+}