wesmiler 3 ماه پیش
والد
کامیت
317a71cc79

+ 0 - 591
app/Console/Commands/SocketServer.php

@@ -1,591 +0,0 @@
-<?php
-
-namespace App\Console\Commands;
-
-use App\Helpers\Jwt;
-use App\Models\DepositModel;
-use App\Models\MessageModel;
-use App\Services\Api\DepositService;
-use App\Services\Api\MemberService;
-use App\Services\Api\OrderService;
-use App\Services\Common\MessageService;
-use App\Services\ConfigService;
-use App\Services\RedisService;
-use Illuminate\Console\Command;
-use const http\Client\Curl\SSL_VERSION_SSLv3;
-use const http\Client\Curl\SSL_VERSION_TLSv1_2;
-
-class SocketServer extends Command
-{
-    public $ws;
-
-    /**
-     * The name and signature of the console command.
-     *
-     * @var string
-     */
-    protected $signature = 'swoole:chat {op?}';
-
-    /**
-     * The console command description.
-     *
-     * @var string
-     */
-    protected $description = 'Chat server run';
-
-    protected $logger = null;
-
-    /**
-     * Create a new command instance.
-     *
-     * @return void
-     */
-    public function __construct()
-    {
-        parent::__construct();
-    }
-
-    /**
-     * Execute the console command.
-     *
-     * @return mixed
-     */
-    public function handle()
-    {
-        $op = $this->argument('op');
-        $op = $op ? $op : 'start';
-        if ($op == 'start') {
-            echo "swoole chat service start ...\n";
-            $this->start();
-        } else if ($op == 'stop') {
-            echo "swoole chat service stop ...\n";
-            $this->stop();
-        }
-    }
-
-    /**
-     * 运行
-     */
-    public function start()
-    {
-        try {
-            //创建websocket服务器对象,监听0.0.0.0:7104端口
-            // HTTPS 通过nginx 配置证书即可
-            $this->ws = new \Swoole\WebSocket\Server("0.0.0.0", env('SOCKET_PORT', '8660'));
-
-            //监听WebSocket连接打开事件
-            $this->ws->on('open', [$this, 'open']);
-
-            //监听WebSocket消息事件
-            $this->ws->on('message', [$this, 'message']);
-
-            //监听WebSocket主动推送消息事件
-            $this->ws->on('request', [$this, 'request']);
-
-            //监听WebSocket连接关闭事件
-            $this->ws->on('close', [$this, 'close']);
-
-            $this->ws->start();
-        } catch (\Exception $exception) {
-            $date = date('Y-m-d H:i:s');
-            RedisService::set("caches:sockets:error", $exception->getMessage(), 600);
-            $this->info("【{$date}】Socket:运行异常=》" . $exception->getMessage());
-        }
-    }
-
-    /**
-     * 建立连接
-     * @param $ws
-     * @param $request
-     */
-    public function open($ws, $request)
-    {
-        $date = date('Y-m-d H:i:s');
-        $logFile = '/storage/logs/swoole-task-'.date('Y-m-d', time() - 86400).'.log';
-        if(file_exists(base_path().$logFile)){
-            unlink(base_path().$logFile);
-        }
-
-        $fdData = RedisService::get("chats:bind:chat_1");
-        $customFd = $fdData && isset($fdData['fd'])? $fdData['fd'] : 0;
-        $checkFd = RedisService::get("chats:frames:" . $customFd);
-        RedisService::clear('chats:checkCustom:from_'.$request->fd);
-        $this->ws->push($request->fd, json_encode(['success' => 'true', 'op' => 'conn', 'message' => '连接成功','custom_fd'=>$checkFd && $customFd? $customFd:0, 'fd' => $request->fd], 256));
-        $this->info("【{$date}】Socket:客户端【{$request->fd}】连接成功");
-    }
-
-    /**
-     * 接收消息
-     * @param $ws
-     * @param $frame
-     */
-    public function message($ws, $frame)
-    {
-        $date = date('Y-m-d H:i:s');
-        RedisService::set("chats:frames:" . $frame->fd, json_decode($frame->data, true), 86400);
-        $fdData = RedisService::get("chats:bind:chat_1");
-        $customFd = $fdData && isset($fdData['fd'])? $fdData['fd'] : 0;
-        if ($frame->data == 'ping') {
-            $this->ws->push($frame->fd, 'pong');
-            if($customFd != $frame->fd){
-                $this->sendMsg($frame->fd, ['success' => true,'op'=>'custom', 'message' => $customFd?'客服上线':'客服离线', 'scene'=>'check', 'data' => ['online'=>$customFd,'to_fd'=>$customFd], 't' => time()]);
-            }
-
-            // 推送客服保证金信息
-            if($customFd == $frame->fd){
-               $messages = MessageService::make()->getUnreadList(0);
-               $count = isset($messages['count'])? $messages['count'] : 0;
-               if($count>0){
-                   $this->sendMsg($frame->fd, ['success' => true,'op'=>'notice', 'message' => '消息通知', 'scene'=>'deposit', 'data' => $messages, 't' => time()]);
-               }
-            }
-
-            $this->info("【{$date}】Socket:客户端【{$frame->fd}】心跳包",false);
-            return false;
-        }
-        // 推送客服保证金信息
-        else if($customFd && RedisService::get("chats:frames:" . $customFd)){
-            $messages = MessageService::make()->getUnreadList(0);
-            $count = isset($messages['count'])? $messages['count'] : 0;
-            if($count>0){
-                $this->sendMsg($customFd, ['success' => true,'op'=>'notice', 'message' => '消息通知', 'scene'=>'deposit', 'data' => $messages, 't' => time()]);
-            }
-        }
-
-        // 消息处理
-        $frameId = $frame->fd;
-        $data = $frame->data ? json_decode($frame->data, true) : [];
-        $fromUid = isset($data['from_uid']) ? intval($data['from_uid']) : 0;
-        $isCustom = isset($data['is_custom']) ? intval($data['is_custom']) : 0;
-        $token = isset($data['token']) ? $data['token'] : '';
-        $op = isset($data['op']) ? $data['op'] : '';
-        $scene = isset($data['scene']) && $data['scene'] ? $data['scene'] : 'chat';
-        $toUid = isset($data['to_uid']) ? intval($data['to_uid']) : 0;
-        $jwt = new Jwt('jwt_jd_app');
-        $userId = $jwt->verifyToken($token);
-        if (!$isCustom && $userId != $fromUid) {
-            $this->info("【{$scene} {$date}】Socket:请先登录再连接【{$frameId}-{$fromUid}】");
-            $this->sendMsg($frameId, ['success' => false,'op'=>'error', 'message' => '请先登录', 'scene'=>$scene, 'data' => $data, 't' => time()]);
-            return false;
-        }
-
-        if ($op != 'login' && ($toUid<=0 || $fromUid == $toUid)) {
-            $this->info("【{$scene} {$date}】Socket:参数错误【{$fromUid}-{$toUid}】");
-            $this->sendMsg($frameId, ['success' => false,'op'=>'error', 'message' => '参数错误,请先选择回复用户~', 'scene'=>$scene, 'data' => $data, 't' => time()]);
-            return false;
-        }
-
-        // 签名验证
-        $system = isset($data['system']) ? $data['system'] : [];
-        $uuid = isset($system['uuid'])? $system['uuid'] : 0;
-        $ctime = isset($system['ct'])? $system['ct'] : 0;
-        $message = isset($data['message']) ? trim($data['message']) : '';
-        $sign = isset($data['sign']) ? trim($data['sign']) : '';
-        $checkSign = getSign($op.'&'.$message.'&'.$fromUid.'&'.$uuid.'&'.$ctime);
-        if($checkSign != $sign){
-            $this->info("【{$scene} {$date}】Socket:签名失败【{$frameId}-{$fromUid}】");
-            $this->sendMsg($frameId, ['success' => false,'op'=>'error', 'message' => '请求签名失败', 'scene'=>$scene,'sign'=>$checkSign, 'data' => $data, 't' => time()]);
-            return false;
-        }
-
-        $apiUrl = env('APP_URL','');
-        $chatKey = isset($data['chat_key']) ? trim($data['chat_key']) : '';
-        $chatKey = $chatKey ? $chatKey : getChatKey($fromUid, $toUid);
-        try {
-            // 推送Fd处理
-            if ($fromUid && $frameId) {
-                RedisService::set("chats:bind:{$scene}_{$fromUid}", ['fd' => $frameId,'scene'=>$scene, 'user_id' => $fromUid, 'uuid' => $uuid, 'chat_key' => $chatKey], 3600);
-            }
-
-            switch ($op) {
-                case 'chat': // 图文聊天
-                    $msgType = isset($data['msg_type']) ? $data['msg_type'] : 1;
-
-
-
-                    // 发送参数验证
-                    if ($fromUid <= 0 || empty($message)) {
-                        $this->info("【{$scene} {$date}】Chat:参数错误,from@{$fromUid}-to@{$toUid}。");
-                        $this->sendMsg($frameId, ['success' => false,'op'=>'push','scene'=>$scene,'data'=>$data, 'message' => '参数错误']);
-                        return false;
-                    }
-
-                    // 敏感词过滤
-                    $message = \App\Services\Api\MessageService::make()->checkMessage($message);
-
-                    // 用户私聊
-                    $fromInfo = [];
-                    if($fromUid>1){
-                        $fromInfo = MemberService::make()->getInfo(['id'=> $fromUid,'status'=>1],[],false);
-                        if(empty($fromInfo)){
-                            $this->info("【{$scene} {$date}】Chat:发送用户不存在,from@{$fromUid}-to@{$toUid}。");
-                            $this->sendMsg($frameId, ['success' => false,'op'=>'push','scene'=>$scene,'data'=>$data, 'message' => '您的账号不可用或已冻结,请联系客服']);
-                            return false;
-                        }
-                    }
-
-                    $toInfo = [];
-                    if($toUid>1){
-                        $toInfo = MemberService::make()->getInfo(['id'=> $toUid,'status'=>1],[],false);
-                        if(empty($toInfo)){
-                            $this->info("【{$scene} {$date}】Chat:接收用户不存在,from@{$fromUid}-to@{$toUid}。");
-                            $this->sendMsg($frameId, ['success' => false,'op'=>'push','scene'=>$scene,'data'=>$data, 'message' => '对方账号不可用或无法接收消息']);
-                            return false;
-                        }
-                    }
-
-                    $fromUserName = isset($fromInfo['realname'])? $fromInfo['realname'] : ($fromUid>1? '用户'.$fromUid:'客服');
-                    $fromAvatar = isset($fromInfo['avatar']) && $fromInfo['avatar']? $fromInfo['avatar'] : get_image_url($fromUid>1?'/images/member/logo.png':'/images/member/custom.png');
-                    $toUserName = isset($toInfo['realname'])? $toInfo['realname'] : ($toUid>1? '用户'.$toUid:'客服');
-                    $toAvatar = isset($toInfo['avatar']) && $toInfo['avatar']? $toInfo['avatar'] : get_image_url($toUid>1?'/images/member/logo.png':'/images/member/custom.png');
-                    $msgData = [
-                        'from_uid' => $fromUid,
-                        'to_uid' => $toUid,
-                        'type' => 1,
-                        'msg_type' => $msgType,
-                        'title' => '聊天消息',
-                        'description' => $msgType == 2 ? '[图片]' : mb_substr($message, 0, 20),
-                        'content' => $msgType==2? get_image_path($message) : $message,
-                        'chat_key' => $chatKey,
-                        'create_time' => time(),
-                        'update_time' => time(),
-                        'is_read' => 2,
-                        'status' => 1
-                    ];
-                    // 敏感消息
-                    if(empty($message)){
-                        $msgData['from_user_name'] = $fromUserName;
-                        $msgData['from_user_avatar'] = get_image_url($fromAvatar, $apiUrl);
-                        $msgData['to_user_name'] = $toUserName;
-                        $msgData['to_user_avatar'] = get_image_url($toAvatar, $apiUrl);
-                        $msgData['time_text'] = dateFormat($msgData['create_time']);
-                        $msgData['content'] = '<span style="color: red;">敏感内容</span>';
-                        $this->sendMsg($frameId, ['success' => true, 'op' => 'push', 'scene'=> $scene,'custom_fd'=>$customFd, 'data' => $msgData, 'message' => '发送成功:' . $frameId]);
-                        return false;
-                    }
-
-                    if (!$id = MessageModel::insertGetId($msgData)) {
-                        $data = ['success' => false,'op'=>'push','scene'=>$scene,'data'=>$msgData, 'message' => '消息发送失败'];
-                        $this->sendMsg($frameId, $data);
-                        return false;
-                    }
-
-                    // 推送消息给对方
-                    $msgData['from_user_name'] = $fromUserName;
-                    $msgData['from_user_avatar'] = get_image_url($fromAvatar, $apiUrl);
-                    $msgData['to_user_name'] = $toUserName;
-                    $msgData['to_user_avatar'] = get_image_url($toAvatar, $apiUrl);
-                    $msgData['time_text'] = dateFormat($msgData['create_time']);
-                    if($msgData['msg_type'] == 1 || $msgData['msg_type'] == 3){
-                        $msgData['content'] = format_message($msgData['content']);
-                    }else if($msgData['msg_type'] == 2){
-                        $msgData['content'] = $msgData['content']? get_image_url($msgData['content'],$apiUrl) : '';
-                    }else if($msgData['msg_type'] == 4){
-                        $msgData['content'] = $msgData['content']? json_decode($msgData['content'], true) : [];
-                    }
-
-                    // 接收方缓存
-                    RedisService::clear("caches:messages:unread_count_{$toUid}");
-
-                    // 返回自身消息
-                    $this->sendMsg($frameId, ['success' => true, 'op' => 'push', 'scene'=> $scene,'custom_fd'=>$customFd, 'data' => $msgData, 'message' => '发送成功:' . $frameId]);
-
-                    // 推送给对方消息
-                    $pushCustom = ConfigService::make()->getConfigByCode('push_custom_message',1);
-                    $toBindData = RedisService::get("chats:bind:{$scene}_{$toUid}");
-                    $toFd = isset($toBindData['fd']) ? $toBindData['fd'] : 0;
-                    if ($toBindData && $toFd) {
-                        $this->sendMsg($toFd, ['success' => true, 'op' => 'push' ,'scene'=> $scene,'custom_fd'=>$customFd, 'data' => $msgData, 'message' => '推送消息成功:' . $toFd]);
-                        $this->info("【{$date}】Chat:客户端【{$frameId}-{$fromUid}】推送消息给【{$toFd}-{$toUid}。");
-                    }
-
-                    // 如果开启离线推送,或一直推送,根据关键词推送客服消息
-                    if((($pushCustom == 1 && !$toFd)||($pushCustom==2) && ($msgType == 1) )&& $fromUid >1){
-                        // 搜索内容
-                        $replyContent = ConfigService::make()->getContentByKw($message);
-                        $sendData = $msgData = [
-                            'from_uid' => 1,
-                            'to_uid' => $fromUid,
-                            'type' => 1,
-                            'msg_type' => 3,
-                            'title' => '客服消息',
-                            'description' => mb_substr($message, 0, 20),
-                            'content' => $replyContent,
-                            'chat_key' => $chatKey,
-                            'create_time' => time(),
-                            'update_time' => time(),
-                            'is_read' => 1,
-                            'status' => 1
-                        ];
-                        $sendData['from_user_name'] = $toUserName;
-                        $sendData['from_user_avatar'] = $toAvatar;
-                        $sendData['to_user_name'] = $fromUserName;
-                        $sendData['to_user_avatar'] = $fromAvatar;
-                        $sendData['content'] = format_message($replyContent);
-                        $sendData['time_text'] = dateFormat($msgData['create_time']);
-                        if($replyContent){
-                            if (MessageModel::insertGetId($msgData)) {
-                                $this->sendMsg($frameId, ['success' => true, 'op' => 'push', 'scene'=> $scene, 'data' => $sendData, 'message' => '回复成功:' . $frameId]);
-                            }
-                        }else{
-                            $replyContent = ConfigService::make()->getConfigByCode('custom_offline_reply','');
-                            if($replyContent){
-                                $sendData['content'] = format_message($replyContent);
-                                $this->sendMsg($frameId, ['success' => true, 'op' => 'push', 'scene'=> $scene, 'data' => $sendData, 'message' => '回复成功:' . $frameId]);
-
-                            }
-                        }
-                    }
-
-                    break;
-                case 'refund_apply': // 保证金退款申请消息通知
-                    $orderId = isset($data['order_id'])? $data['order_id'] : 0;
-
-                    // 发送参数验证
-                    if ($fromUid <= 0 || empty($message) || empty($orderId)) {
-                        $this->info("【{$scene} {$date}】Message:参数错误,from@{$fromUid}-to@{$toUid}。");
-                        $this->sendMsg($frameId, ['success' => false,'op'=>'notice','scene'=>$scene,'data'=>$data, 'message' => '参数错误']);
-                        return false;
-                    }
-
-                    // 订单信息
-                    $orderInfo = DepositService::make()->getInfo($orderId);
-                    $orderNo = isset($orderInfo['refund_no'])? $orderInfo['refund_no'] : '';
-                    $orderMoney = isset($orderInfo['refund_money'])? $orderInfo['refund_money'] : 0;
-                    if(empty($orderInfo) || $orderMoney<=0){
-                        $this->info("【{$scene} {$date}】Message:订单信息不存在,from@{$fromUid}-to@{$toUid}-{$orderId}。");
-                        $this->sendMsg($frameId, ['success' => false,'op'=>'notice','scene'=>$scene,'data'=>$data, 'message' => '订单信息不存在']);
-                        return false;
-                    }
-
-                    $msgData = [
-                        'from_uid' => $fromUid,
-                        'to_uid' => $toUid,
-                        'type' => 4,
-                        'msg_type' => 4,
-                        'title' => '保证金退款申请消息',
-                        'description' => $message,
-                        'order_no' => $orderNo,
-                        'content' => json_encode([
-                            'title'=> $message.'<span class="ele-text-primary">查看订单</span>',
-                            'order_no'=> $orderNo,
-                            'money'=> $orderMoney,
-                            'date'=> date('Y-m-d H:i:s'),
-                            'user_id'=> $fromUid,
-                            'type'=> 'deposit',
-                            'remark'=> '退保申请',
-                        ],256),
-                        'chat_key' => $chatKey,
-                        'create_time' => time(),
-                        'update_time' => time(),
-                        'is_read' => 2,
-                        'status' => 1
-                    ];
-                    if (!$id = MessageModel::insertGetId($msgData)) {
-                        $data = ['success' => false,'op'=>'notice','scene'=>$scene,'data'=>$msgData, 'message' => '消息发送失败'];
-                        $this->sendMsg($frameId, $data);
-                        return false;
-                    }
-
-                    // 推送消息给对方
-                    $msgData['time_text'] = dateFormat($msgData['create_time']);
-                    $msgData['content'] = $msgData['content']? json_decode($msgData['content'], true) : [];
-
-                    // 返回自身消息
-                    $this->sendMsg($frameId, ['success' => true, 'op' => 'notice', 'scene'=> $scene,'custom_fd'=>$customFd, 'data' => $msgData, 'message' => '发送成功:' . $frameId]);
-
-                    // 推送给对方消息
-                    $toBindData = RedisService::get("chats:bind:{$scene}_{$toUid}");
-                    $toFd = isset($toBindData['fd']) ? $toBindData['fd'] : 0;
-                    if ($toBindData && $toFd) {
-                        $this->sendMsg($toFd, ['success' => true, 'op' => 'notice' ,'scene'=> $scene,'custom_fd'=>$customFd, 'data' => ['count'=>1,'list'=>[$msgData]], 'message' => '推送消息成功:' . $toFd]);
-                        $this->info("【{$date}】Message:客户端【{$frameId}-{$fromUid}】推送消息给【{$toFd}-{$toUid}。");
-                    }
-                    break;
-                case 'notice': // 其他消息通知
-                    $type = isset($data['type']) ? $data['type'] : 5;
-                    $order = isset($data['order']) ? $data['order'] : [];
-
-                    // 发送参数验证
-                    if ($fromUid <= 0 || empty($message) || empty($order)) {
-                        $this->info("【{$scene} {$date}】Message:参数错误,from@{$fromUid}-to@{$toUid}。");
-                        $this->sendMsg($frameId, ['success' => false,'op'=>'notice','scene'=>$scene,'data'=>$data, 'message' => '参数错误']);
-                        return false;
-                    }
-
-                    $msgData = [
-                        'from_uid' => $fromUid,
-                        'to_uid' => $toUid,
-                        'type' => $type,
-                        'msg_type' => 4,
-                        'title' => $message,
-                        'order_no' => isset($order['order_no'])?$order['order_no'] : '',
-                        'description' => isset($order['title'])? $order['title'] : '有新的订单消息',
-                        'content' => json_encode($order,256),
-                        'chat_key' => $chatKey,
-                        'create_time' => time(),
-                        'update_time' => time(),
-                        'is_read' => 2,
-                        'status' => 1
-                    ];
-                    if (!$id = MessageModel::insertGetId($msgData)) {
-                        $data = ['success' => false,'op'=>'notice','scene'=>$scene,'data'=>$msgData, 'message' => '消息发送失败'];
-                        $this->sendMsg($frameId, $data);
-                        return false;
-                    }
-
-                    // 推送消息给对方
-                    $msgData['time_text'] = dateFormat($msgData['create_time']);
-                    $msgData['content'] = $msgData['content']? json_decode($msgData['content'], true) : [];
-
-                    // 返回自身消息
-                    $this->sendMsg($frameId, ['success' => true, 'op' => 'notice', 'scene'=> $scene,'custom_fd'=>$customFd, 'data' => $msgData, 'message' => '发送成功:' . $frameId]);
-
-                    // 推送给对方消息
-                    $toBindData = RedisService::get("chats:bind:{$scene}_{$toUid}");
-                    $toFd = isset($toBindData['fd']) ? $toBindData['fd'] : 0;
-                    echo $toFd;
-                    if ($toBindData && $toFd) {
-                        $this->sendMsg($toFd, ['success' => true, 'op' => 'notice' ,'scene'=> $scene,'custom_fd'=>$customFd, 'data' => ['count'=>1,'list'=>[$msgData]], 'message' => '推送消息成功:' . $toFd]);
-                        $this->info("【{$date}】Message:客户端【{$frameId}-{$fromUid}】推送消息给【{$toFd}-{$toUid}。");
-                    }
-                    break;
-                case 'login': // 登录
-                    if($toUid<=0){
-                        $toUid = 1;
-                        $data['to_uid'] = $toUid;
-                        $data['is_custom'] = true;
-                        $data['to_user_name'] = '客服';
-                    }
-
-                    // 未读消息
-                    $unreadCount = 0;
-                    if($fromUid==1){
-                        $unreadCount = intval(\App\Services\Api\MessageService::make()->getUnreadCount(1));
-                    }
-
-
-                    $fdData = RedisService::get("chats:bind:chat_".$toUid);
-                    $toFd = $fdData && isset($fdData['fd'])? $fdData['fd'] : 0;
-                    $checkFd = RedisService::get("chats:frames:" . $toFd);
-                    $online = $checkFd && $toFd? 1 : 0;
-                    $data['to_fd'] = $online? $toFd : 0;
-                    $data['chat_key'] = getChatKey($fromUid,$toUid);
-                    $this->info("【{$scene} {$date}】Socket:登录成功【{$frameId}-{$fromUid}-{$op}】。");
-                    $this->sendMsg($frameId, ['success' => true,'op'=> $op, 'scene'=>$scene,'custom_fd'=>$customFd,'unread'=>$unreadCount, 'message' => '登录成功', 'data' => $data, 't' => time()]);
-                    break;
-                 default:
-                    $this->sendMsg($frameId, ['success' => false, 'message' => 'ok', 'scene'=>$scene,'custom_fd'=>$customFd, 'data' => $data, 't' => time()]);
-                    break;
-            }
-
-            $this->info("【{$scene} {$date}】Chat:客户端【{$frameId}】消息处理成功");
-        } catch (\Exception $exception) {
-            RedisService::set("caches:sockets:error_{$frameId}", ['error' => $exception->getMessage(),'trace'=>$exception->getTrace(), 'date' => $date], 7200);
-            $this->info("【{$scene} {$date}】Chat:客户端【{$frameId}】消息处理错误 " . $exception->getMessage());
-        }
-    }
-
-    /**
-     * 签名验证
-     * @param $data
-     * @return bool
-     */
-    public function checkSign($data)
-    {
-        $checkSign = isset($data['sign']) ? $data['sign'] : '';
-        $sign = getSign($data);
-        if ($sign != $checkSign) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * 推送消息
-     * @param $fd
-     * @param $op
-     * @param $data
-     */
-    public function sendMsg($fd, $data)
-    {
-        $date = date('Y-m-d H:i:s');
-        try {
-            if (!RedisService::exists("chats:frames:" . $fd)) {
-                $this->info("【{$date}】Chat:客户端【{$fd}】推送用户已经掉线 ");
-                return false;
-            }
-            $this->ws->push($fd, json_encode($data, 256));
-        } catch (\Exception $exception) {
-            $this->info("【{$date}】Chat:客户端【{$fd}】消息处理错误 " . $exception->getMessage());
-        }
-    }
-
-    /**
-     * 接收请求
-     * @param $request
-     * @param $response
-     */
-    public function request($request, $response)
-    {
-
-    }
-
-    /**
-     * 关闭连接
-     * @param $ws
-     * @param $fd
-     */
-    public function close($ws, $fd = '')
-    {
-        $date = date('Y-m-d H:i:s');
-        RedisService::clear("chats:frames:" . $fd);
-        $this->info("【{$date}】Chat:客户端【{$fd}】连接关闭");
-        RedisService::clear('chats:checkCustom:from_'.$fd);
-        $this->ws->close($fd);
-
-        // 清理历史消息
-        if(!RedisService::get("caches:messages:clear")){
-            $chatLogCount = ConfigService::make()->getConfigByCode('chat_log_count',300);
-            $expireTime = ConfigService::make()->getConfigByCode('chat_log_expire',30);
-            $expireTime = $expireTime>0 && $expireTime <= 365? $expireTime : 30;
-            $expireTime = $expireTime * 24 * 3600;
-            if(MessageModel::count('id')>$chatLogCount){
-                MessageModel::where('create_time','<', time() - $expireTime)->delete();
-            }
-        }
-    }
-
-    /**
-     * 停止运行
-     */
-    public function stop()
-    {
-        // 直接杀
-        $stoSh = base_path().'/crontab/socketStop.sh';
-        if(file_exists($stoSh) && function_exists('exec')){
-            exec("{$stoSh}");
-        }
-
-        echo "$stoSh\n";
-        echo "chat stop success...\n";
-
-        if ($this->ws) {
-            $date = date('Y-m-d H:i:s');
-            $this->info("【{$date}】Chat:停止运行服务");
-            $this->ws->close();
-        }
-    }
-
-    /**
-     * 消息
-     * @param string $data
-     */
-    public function info($data,$verbosity=true)
-    {
-        \logger()->channel('chat')->info($data);
-        if(env('SWOOLE_LOG', true) && $verbosity){
-            parent::info($data);
-        }
-    }
-}

+ 323 - 0
app/Console/Commands/SwooleTask.php

@@ -0,0 +1,323 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Services\Common\PayOrdersService;
+use App\Services\RedisService;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+class SwooleTask extends Command
+{
+
+    protected $serv;
+    protected $host = '127.0.0.1';
+    protected $port = 6670;
+    // 进程名称
+    protected $taskName = 'swooleTask';
+    // PID路径
+    protected $pidPath = '/storage/swoole.pid';
+    // task
+    protected $onlyReloadTaskWorker = false;
+    // 设置运行时参数
+    protected $options = [
+        'worker_num' => 8, //worker进程数,一般设置为CPU数的1-4倍
+        'daemonize' => true, //启用守护进程
+        'log_file' => '/storage/logs/swoole-task.log', //指定swoole错误日志文件
+        'log_level' => 5, //日志级别 范围是0-5,0-DEBUG,1-TRACE,2-INFO,3-NOTICE,4-WARNING,5-ERROR
+        'dispatch_mode' => 1, //数据包分发策略,1-轮询模式
+        'task_worker_num' => 6, //task进程的数量
+        'task_ipc_mode' => 3, //使用消息队列通信,并设置为争抢模式
+    ];
+
+
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'swoole:task {op}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Swoole task server description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * 入口
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        ini_set("default_socket_timeout", -1);
+        // 项目根目录
+        defined('ROOT_PATH') or define('ROOT_PATH', base_path());
+
+        // 文件上传目录
+        defined('ATTACHMENT_PATH') or define('ATTACHMENT_PATH', base_path('public/uploads'));
+
+        // 图片上传目录
+        defined('IMG_PATH') or define('IMG_PATH', base_path('public/uploads/images'));
+
+        // 临时存放目录
+        defined('UPLOAD_TEMP_PATH') or define('UPLOAD_TEMP_PATH', ATTACHMENT_PATH . "/temp");
+
+        // 定义普通图片域名
+        defined('IMG_URL') or define('IMG_URL', env('IMG_URL'));
+
+        // 数据表前缀
+        defined('DB_PREFIX') or define('DB_PREFIX', DB::connection()->getTablePrefix());
+
+        $this->options['log_file'] = base_path() . $this->options['log_file'];
+        $this->pidPath = base_path() . $this->pidPath;
+        $op = $this->argument('op');
+        switch ($op) {
+            case 'status': // 状态
+                $res = $this->status();
+                echo $res ? $res : 0;
+                break;
+            case 'start': // 运行
+                return $this->start();
+                break;
+            case 'reload': // 平滑重启
+                return $this->reload();
+                break;
+            case 'stop': // 停止运行
+                return $this->stop();
+                break;
+            default:
+                exit("{$op} command does not exist");
+                break;
+        }
+    }
+
+    /**
+     * 启动
+     */
+    public function start()
+    {
+        date_default_timezone_set('PRC');
+        // 构建Server对象,监听对应地址
+        $this->serv = new \Swoole\Server($this->host, env('SWOOLE_PORT', $this->port));
+        $this->serv->set($this->options);
+
+        // 注册事件
+        $this->serv->on('start', [$this, 'onStart']);
+        $this->serv->on('receive', [$this, 'onReceive']);
+        $this->serv->on('task', [$this, 'onTask']);
+        $this->serv->on('finish', [$this, 'onFinish']);
+
+        // Run worker
+        echo "swoole start...\n";
+        $this->serv->start();
+
+    }
+
+    // 安全重启
+    public function reload()
+    {
+
+        $pids = file_exists($this->pidPath) ? file_get_contents($this->pidPath) : '';
+        $pids = $pids ? explode("\n", $pids) : [];
+        $masterPid = isset($pids[0]) ? $pids[0] : '';
+        $managePid = isset($pids[1]) ? $pids[1] : '';
+        if (empty($masterPid)) {
+            return false;
+        }
+
+        if (!$this->status($masterPid)) {
+            return false;
+        }
+
+        \Swoole\Process::kill($managePid, SIGUSR1);
+
+        echo "swoole reload...\n";
+    }
+
+    /**
+     * 停止
+     * @param bool $smooth
+     * @return bool
+     */
+    public function stop($smooth = false)
+    {
+        $pids = file_exists($this->pidPath) ? file_get_contents($this->pidPath) : '';
+        $pids = $pids ? explode("\n", $pids) : [];
+        $masterPid = isset($pids[0]) ? $pids[0] : '';
+        if (empty($masterPid)) {
+            return false;
+        }
+
+        if (!$this->status($masterPid)) {
+            return false;
+        }
+
+        // 直接杀
+        $stoSh = base_path().'/crontab/swooleTaskStop.sh';
+        echo $stoSh;
+        if(file_exists($stoSh) && function_exists('exec')){
+            exec("{$stoSh}");
+        }
+
+        @unlink($this->pidPath);
+        echo "swoole stop...\n";
+    }
+
+    /**
+     * 状态
+     * @return mixed
+     */
+    public function status($masterPid = 0)
+    {
+        $res = false;
+        if (empty($masterPid) && file_exists($this->pidPath)) {
+            $pids = file_get_contents($this->pidPath);
+            $pids = $pids ? explode("\n", $pids) : [];
+            $masterPid = isset($pids[0]) ? $pids[0] : '';
+        }
+
+        if ($masterPid) {
+            $res = \Swoole\Process::kill($masterPid, 0);
+        }
+        return $res;
+    }
+
+    public function onStart($serv)
+    {
+        if (!is_dir(dirname($this->pidPath))) {
+            @mkdir(dirname($this->pidPath), true, 755);
+        }
+
+        //记录进程id,脚本实现自动重启
+        $pid = "{$serv->master_pid}\n{$serv->manager_pid}";
+        file_put_contents($this->pidPath, $pid);
+
+        // 定时任务
+        $time = 0;
+        $date = date('Y-m-d H:i:s');
+        if(file_exists($this->options['log_file'])){
+            $time = 0;
+            file_put_contents($this->options['log_file'],"Task {$date}:清空日志\n");
+        }
+
+        // 清除日志
+        \swoole_timer_tick(1000, function ($timer) use ($serv, &$time) { // 启用定时器,每5秒执行一次
+            $date = date('Y-m-d H:i:s');
+            if($time>7200 && file_exists($this->options['log_file'])){
+                $time = 0;
+                file_put_contents($this->options['log_file'],"Task {$date}:清空日志\n");
+            }
+            $time++;
+        });
+
+
+        // 充值订单状态检查
+        \swoole_timer_tick(20000, function ($timer) use ($serv, &$time) { // 启用定时器,每20秒执行一次
+            $date = date('Y-m-d H:i:s');
+            $orders = PayOrdersService::make()->getCheckOrderList();
+            if($orders){
+                foreach ($orders as $k => $item){
+                    if(!RedisService::get('caches:task:lock:order_check_loaded_'.$k)){
+                        $taskData = [
+                            'taskName' => 'checkOrder',
+                            'name' => "充值订单状态更新",
+                            'date' => date('Y-m-d'),
+                            'params'=> $item,
+                        ];
+                        $res = $serv->task($taskData);
+                        RedisService::set('caches:task:lock:order_check_loaded_'.$k, true, rand(3,5));
+                        echo "[Task checkOrder {$date}] 充值订单状态更新结果:{$res}\n";
+                    }else{
+                        echo "[Task checkOrder {$date}] 充值订单状态更新间隔时间调用\n";
+                    }
+                }
+            }else{
+                echo "[Task checkOrder {$date}] 暂无可验证状态的充值订单\n";
+            }
+        });
+
+    }
+
+    //监听连接进入事件
+    public function onConnect($serv, $fd, $from_id)
+    {
+        $serv->send($fd, "Success {$fd}!");
+    }
+
+    // 监听数据接收事件
+    public function onReceive(\Swoole\Server $serv, $fd, $from_id, $data)
+    {
+        echo "Get Message From Client {$fd}:{$data}\n";
+        $res['result'] = 'success';
+        $serv->send($fd, json_encode($res)); // 同步返回消息给客户端
+        $serv->task($data);  // 执行异步任务
+    }
+
+    /**
+     * @param \Swoole\Server $serv
+     * @param $task_id
+     * @param $from_id
+     * @param $data
+     * @return false|string
+     */
+    public function onTask(\Swoole\Server $serv, $task_id, $from_id, $data)
+    {
+
+        $date = date('Y-m-d H:i:s');
+        $taskName = isset($data['taskName']) ? $data['taskName'] : '';
+        $params = isset($data['params']) ? $data['params'] : [];
+        try {
+            switch ($taskName) {
+                case 'checkOrder': // 更新状态
+                    // 调用处理
+                    if($res = PayOrdersService::make()->checkOrder($params)){
+                        $res = is_array($res) && $res? json_encode($res, 256) : 'success';
+                        echo "[Task {$taskName} {$date}][{$task_id}] 充值订单状态更新处理结果:{$res}\n";
+                    }else{
+                        $error = PayOrdersService::make()->getError();
+                        $error = $error? lang($error) : 'failed';
+                        echo "[Task {$taskName} {$date}][{$task_id}] 充值订单状态更新处理结果:{$error}\n";
+                    }
+                    break;
+            }
+        } catch(\Exception $exception){
+            return $exception->getMessage();
+        }
+
+        return "[{$task_id}]暂无任务处理";
+    }
+
+
+    /**
+     * @param $serv swoole_server swoole_server对象
+     * @param $task_id int 任务id
+     * @param $data string 任务返回的数据
+     */
+    public function onFinish(\Swoole\Server $serv, $task_id, $data)
+    {
+        //
+        //echo "任务处理完成...\n";
+    }
+
+
+    // 监听连接关闭事件
+    public function onClose($serv, $fd, $from_id)
+    {
+        echo "Client {$fd} close connection\n";
+        $serv->close();
+    }
+}

+ 10 - 4
app/Http/Controllers/Api/v1/TestController.php

@@ -5,9 +5,9 @@ namespace App\Http\Controllers\Api\v1;
 use App\Http\Controllers\Api\webApp;
 use App\Services\Api\MemberService;
 use App\Services\Api\MessageService;
+use App\Services\Common\PayOrdersService;
 use App\Services\DyrPayService;
 use App\Services\Kd100Service;
-use App\Services\MapService;
 use App\Services\MpService;
 use App\Services\RedisService;
 
@@ -26,9 +26,15 @@ class TestController extends webApp
         $productId = '1105';
         $amount = 50;
         $orderNo = get_order_num('PR');
-$result = DyrPayService::make()->recharge($orderNo, '17877188025', $productId, $amount);
-        dump(DyrPayService::make()->getError());
-        dump($result);
+        $orders =  PayOrdersService::make()->getCheckOrderList();
+        dump($orders);
+        $result = '';
+        if($orders){
+            $result = PayOrdersService::make()->checkOrder($orders[0]);
+            dump(PayOrdersService::make()->getError());
+            dump($result);
+        }
+
         //array:3 [▼
         //  "errno" => 0
         //  "errmsg" => "提交成功"

+ 1 - 1
app/Services/Api/AccountService.php

@@ -116,7 +116,7 @@ class AccountService extends BaseService
         $list = $list? $list->toArray() :[];
         if($list){
             $types = [1=>'话费充值',2=>'电费充值',3=>'燃气充值'];
-            $statusArr = [1=>'支付',2=>'已支付',3=>'充值中',4=>'充值成功',5=>'充值失败资金原路退回',6=>'充值成功部分其他原路退回'];
+            $statusArr = [1=>'取消支付',2=>'已支付',3=>'充值中',4=>'充值成功',5=>'充值失败资金原路退回',6=>'充值成功部分其他原路退回'];
             foreach($list['data'] as &$item){
                 $item['time_text'] = $item['create_time']? datetime($item['create_time'],'Y/m/d H:i:s') : '';
                 $type = isset($item['type'])? intval($item['type']) : 0;

+ 103 - 1
app/Services/Common/PayOrdersService.php

@@ -3,14 +3,28 @@
 namespace App\Services\Common;
 
 use App\Models\PayOrdersModel;
-use App\Models\MemberModel;
 use App\Services\BaseService;
+use App\Services\DyrPayService;
+use App\Services\PaymentService;
+use App\Services\RedisService;
 
 /**
  * 充值缴费订单服务
  */
 class PayOrdersService extends BaseService
 {
+
+    /**
+     * 构造函数
+     * @author laravel开发员
+     * @since 2020/11/11
+     * AccountService constructor.
+     */
+    public function __construct()
+    {
+        $this->model = new PayOrdersModel();
+    }
+
     /**
      * 获取列表
      */
@@ -161,4 +175,92 @@ class PayOrdersService extends BaseService
             'msg' => "成功删除{$count}条记录"
         ];
     }
+
+    /**
+     * 已支付或充值中的订单
+     * @return array|mixed
+     */
+    public function getCheckOrderList()
+    {
+        $cacheKey = "caches:orders:payCheckList";
+        $datas = RedisService::get($cacheKey);
+        if($datas){
+            return $datas;
+        }
+
+        $datas = $this->model->where(['mark'=>1])
+            ->whereIn('status',[2,3])
+            ->select(['id','order_no','transaction_id','total as money','status'])
+            ->orderBy('id','desc')
+            ->limit(500)
+            ->get()
+            ->keyBy('order_no');
+        $datas = $datas? $datas->toArray() : [];
+        if($datas){
+            $datas = array_chunk($datas, 100);
+            RedisService::set($cacheKey, $datas, rand(20,30));
+        }
+
+        return $datas;
+    }
+
+    /**
+     * 验证订单状态
+     * @param $orders
+     * @return array
+     * @throws \Yansongda\Pay\Exception\ContainerException
+     * @throws \Yansongda\Pay\Exception\InvalidParamsException
+     * @throws \Yansongda\Pay\Exception\ServiceNotFoundException
+     */
+    public function checkOrder($orders)
+    {
+        $nos = array_keys($orders);
+        $noStr = implode(',', $nos);
+
+        $result = DyrPayService::make()->query($noStr);
+        $datas = isset($result['data'])?$result['data']:[];
+
+        $errors = [];
+        $success = [];
+        if($datas){
+            foreach($datas as $item){
+                $no = isset($item['out_trade_num'])?$item['out_trade_num']:'';
+                $remark = isset($item['remark'])?$item['remark']:'充值失败';
+                $order_number = isset($item['order_number'])?$item['order_number']:'';
+                $charge_amount = isset($item['charge_amount'])?$item['charge_amount']:0;
+                $charge_kami = isset($item['charge_kami'])?$item['charge_kami']:'';
+                $state = isset($item['state'])?$item['state']:-1;
+                if(in_array($state, [1,3])){
+                    $success[$no]= $state;
+                    $updateData = ['status'=> $state==3?6:4,'order_trade_no'=>$order_number,'charge_kami'=>$charge_kami,'charge_amount'=>$charge_amount,'update_time'=>time()];
+                    if(!PayOrdersModel::where(['order_no'=>$no])->update($updateData)){
+                        $errors[$no] = '充值失败退款处理失败';
+                    }
+                    unset($orders[$no]);
+                }else if($state == 0){
+                    unset($orders[$no]);
+                    $errors[$no] = '充值中';
+                }else{
+                    $errors[$no] = $remark;
+                }
+            }
+        }
+
+        // 未查询到充值记录,批量退款
+        if($orders){
+            foreach($orders as $order){
+                $total = isset($order['money'])?$order['money']:0;
+                $refundStatus = PaymentService::make()->refund($order,'pay');
+                $updateData = ['refund_status'=> $refundStatus?1:3,'status'=>5,'failed_remark'=> '充值失败原路退款','refund_money'=>$refundStatus?$total:0,'update_time'=>time()];
+                if(!PayOrdersModel::where(['id'=>$order['id']])->update($updateData)){
+                    $errors[$order['order_no']] = '充值失败退款处理失败';
+                }else{
+                    $errors[$order['order_no']] = $refundStatus?'充值失败原路退款':'充值失败退款失败';
+                }
+            }
+        }
+
+        $this->error = '订单状态验证处理成功';
+        return ['success'=>$success,'errors'=>$errors];
+    }
 }

+ 39 - 6
app/Services/DyrPayService.php

@@ -25,7 +25,8 @@ class DyrPayService extends BaseService
     protected $apiKey = '';
     protected $apiClientId = '';
     protected $apiUrls = [
-        'recharge' => '/index/recharge'
+        'recharge' => '/index/recharge',
+        'query' => '/index/check',
     ];
 
     public function __construct()
@@ -50,10 +51,11 @@ class DyrPayService extends BaseService
 
 
     /**
-     * 查询
-     * @param $no 快递单号
-     * @param string $phone // 手机号
-     * @param string $code 快递公司编号
+     * 充值
+     * @param $no 单号
+     * @param string $account // 充值账号
+     * @param string $productId 产品 ID
+     * @param string $amount 充值面值
      * @return bool
      */
     public function recharge($no, $account, $productId, $amount)
@@ -63,7 +65,7 @@ class DyrPayService extends BaseService
             return false;
         }
 
-        $cacheKey = "caches:kd100:{$no}_{$productId}_{$account}";
+        $cacheKey = "caches:dryPay:{$no}_{$productId}_{$account}";
         if (RedisService::get($cacheKey . '_lock')) {
             $this->error = '充值处理中~';
             return false;
@@ -88,6 +90,37 @@ class DyrPayService extends BaseService
     }
 
     /**
+     * 查询订单
+     * @param $nos 查询单号
+     * @return bool
+     */
+    public function query($nos)
+    {
+        if (empty($this->apiUrl) || empty($this->apiKey) || empty($this->apiClientId)) {
+            $this->error = '接口参数未配置';
+            return false;
+        }
+
+        $cacheKey = "caches:dryPay:queryOrder:".md5($nos);
+        if (RedisService::get($cacheKey . '_lock')) {
+            $this->error = '充值处理中~';
+            return false;
+        }
+
+        $param = [
+            'userid' => $this->apiClientId,             // 商户ID
+            'out_trade_nums' => $nos,     // 单号
+        ];
+
+        //请求参数
+        $param['sign'] = $this->makeSign($param);
+        $url = $this->apiUrl.$this->apiUrls['query'];
+        $result = httpRequest($url, $param, 'post', '', 5);
+        $this->saveLog($cacheKey,  ['url'=>$url,'param'=>$param,'result'=>$result]);
+        return $result;
+    }
+
+    /**
      * 日志
      * @param $key
      * @param $data

+ 3 - 2
app/Services/PaymentService.php

@@ -684,6 +684,7 @@ class PaymentService extends BaseService
         if($refundAmount>0){
             $orderInfo['money'] = $refundAmount;
             $orderInfo['remark'] = $this->error;
+            $orderInfo['order_trade_no'] = '';
             $refundStatus = PaymentService::make()->refund($orderInfo,'pay');
             if(!PayOrdersModel::where(['id'=>$orderId])->update(['refund_status'=> $refundStatus?1:3,'refund_money'=>$refundStatus?$refundAmount:0,'update_time'=>time()])){
                 $this->saveLog("caches:payments:payNotify_{$productId}:notify_{$out_trade_num}_error", ['error'=>$this->error,'order' => $orderInfo, 'notify' => $data]);
@@ -738,7 +739,7 @@ class PaymentService extends BaseService
         switch ($payType) {
             case 10: // 微信支付
                 $data = [
-                    'out_trade_no' => $outTradeNo,
+                    'out_trade_no' => $outTradeNo?$outTradeNo:$orderNo,
                     'out_refund_no' => get_order_num('RF'),
                     'transaction_id' => $transactionId,
                     'notify_url' => url("/api/notify/{$scene}/{$payType}"),
@@ -763,7 +764,7 @@ class PaymentService extends BaseService
                 break;
             case 20: // 支付宝
                 $data = [
-                    'out_request_no' => $outTradeNo,
+                    'out_request_no' => $outTradeNo?$outTradeNo:$orderNo,
                     'trade_no' => $transactionId,
                     'refund_amount' => $money,
                     'query_options' => ['deposit_back_info'],

+ 0 - 6
crontab/swooleChat.sh

@@ -1,6 +0,0 @@
-#!/bin/bash
-basepath=$(cd `dirname $0`; pwd)
-cd $basepath
-
-nohup php ../artisan swoole:chat start >/dev/null 2>&1 &
-echo 'Chat任务服务运行成功 ...'

+ 0 - 6
crontab/swooleChatStop.sh

@@ -1,6 +0,0 @@
-#!/bin/bash
-basepath=$(cd `dirname $0`; pwd)
-cd $basepath
-pidlist=`ps -ef | grep swoole:chat | grep -v grep |awk '{print $2}'`
-kill -9 $pidlist
-echo 'Chat服务停止成功...'

+ 6 - 0
crontab/swooleTask.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+basepath=$(cd `dirname $0`; pwd)
+cd $basepath
+
+nohup php ../artisan swoole:task start >/dev/null 2>&1 &
+echo 'Swoole 任务服务运行成功 ...'

+ 6 - 0
crontab/swooleTaskStop.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+basepath=$(cd `dirname $0`; pwd)
+cd $basepath
+pidlist=`ps -ef | grep swoole:task | grep -v grep |awk '{print $2}'`
+kill -9 $pidlist
+echo 'Swoole 服务停止成功...'