From 0e2787f240ab029824f5aaa04580751db0693c45 Mon Sep 17 00:00:00 2001 From: gzydong Date: Sat, 7 Nov 2020 22:57:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Amqp/Consumer/ChatMessageConsumer.php | 2 +- app/Amqp/Producer/ChatMessageProducer.php | 22 +- app/Bootstrap/ServerStart.php | 43 ++++ app/Constants/ResponseCode.php | 1 - app/Controller/Api/V1/CController.php | 2 +- app/Controller/Api/V1/EmoticonController.php | 209 +++++++++++++++++++ app/Controller/Api/V1/UsersController.php | 171 ++++++++++++++- app/Controller/IndexController.php | 9 - app/Controller/WebSocketController.php | 71 ++++++- app/Middleware/WebSocketAuthMiddleware.php | 45 ++++ app/Service/EmoticonService.php | 161 ++++++++++++++ app/Service/SocketFDService.php | 117 +++++++++++ app/Support/RedisLock.php | 10 +- app/Support/{Http => }/Response.php | 2 +- app/Traits/WebSocketTrait.php | 28 +++ app/helper.php | 112 +++++----- bin/hyperf.php | 2 - composer.json | 5 +- config/autoload/file.php | 94 +++++++++ config/autoload/middlewares.php | 10 +- config/autoload/server.php | 3 +- config/routes.php | 22 +- config/routes/http.php | 17 ++ config/routes/websocket.php | 20 ++ 24 files changed, 1073 insertions(+), 105 deletions(-) create mode 100644 app/Bootstrap/ServerStart.php create mode 100644 app/Middleware/WebSocketAuthMiddleware.php create mode 100644 app/Service/SocketFDService.php rename app/Support/{Http => }/Response.php (98%) create mode 100644 app/Traits/WebSocketTrait.php create mode 100644 config/autoload/file.php create mode 100644 config/routes/http.php create mode 100644 config/routes/websocket.php diff --git a/app/Amqp/Consumer/ChatMessageConsumer.php b/app/Amqp/Consumer/ChatMessageConsumer.php index ce12af7..2902716 100644 --- a/app/Amqp/Consumer/ChatMessageConsumer.php +++ b/app/Amqp/Consumer/ChatMessageConsumer.php @@ -12,7 +12,7 @@ use Hyperf\Amqp\Message\Type; use Hyperf\Amqp\Builder\QueueBuilder; /** - * @Consumer(name=" ChatMessage ",enable=true) + * @Consumer(name="聊天消息消费者",enable=true) */ class ChatMessageConsumer extends ConsumerMessage { diff --git a/app/Amqp/Producer/ChatMessageProducer.php b/app/Amqp/Producer/ChatMessageProducer.php index 1ee3c16..ab49942 100644 --- a/app/Amqp/Producer/ChatMessageProducer.php +++ b/app/Amqp/Producer/ChatMessageProducer.php @@ -6,6 +6,7 @@ namespace App\Amqp\Producer; use Hyperf\Amqp\Message\ProducerMessage; use Hyperf\Amqp\Message\Type; +use Hyperf\Utils\Str; class ChatMessageProducer extends ProducerMessage { @@ -16,13 +17,24 @@ class ChatMessageProducer extends ProducerMessage public function __construct($data) { $message = [ - 'method'=>'', // - 'sender'=>'', // 发送者 - 'receive'=>'', // 接收者 - 'receiveType'=>'', - 'message'=>[] + 'uuid' => $this->uuid(), + 'method' => '', // + 'sender' => '', //发送者ID + 'receive' => '', //接收者ID + 'receiveType' => '', //接收者类型 1:好友;2:群组 + 'message' => [] ]; $this->payload = $data; } + + /** + * 生成唯一ID + * + * @return string + */ + private function uuid() + { + return Str::random() . rand(100000, 999999); + } } diff --git a/app/Bootstrap/ServerStart.php b/app/Bootstrap/ServerStart.php new file mode 100644 index 0000000..d2d4f73 --- /dev/null +++ b/app/Bootstrap/ServerStart.php @@ -0,0 +1,43 @@ +encode(time() . rand(1000, 9999))); + + $this->socketFDService->removeRedisCache(); + + + stdout_log()->info(sprintf('服务运行ID : %s', SERVER_RUN_ID)); + stdout_log()->info('服务启动前回调事件 : beforeStart ...'); + } +} \ No newline at end of file diff --git a/app/Constants/ResponseCode.php b/app/Constants/ResponseCode.php index 54ca80f..a25ed3d 100644 --- a/app/Constants/ResponseCode.php +++ b/app/Constants/ResponseCode.php @@ -25,7 +25,6 @@ class ResponseCode extends AbstractConstants */ const SERVER_ERROR = 500; - /** * @Message("请求数据验证失败!") */ diff --git a/app/Controller/Api/V1/CController.php b/app/Controller/Api/V1/CController.php index 1bf6dcf..c1a779f 100644 --- a/app/Controller/Api/V1/CController.php +++ b/app/Controller/Api/V1/CController.php @@ -3,7 +3,7 @@ namespace App\Controller\Api\V1; use App\Controller\AbstractController; -use App\Support\Http\Response; +use App\Support\Response; use Hyperf\Di\Annotation\Inject; use Phper666\JWTAuth\JWT; diff --git a/app/Controller/Api/V1/EmoticonController.php b/app/Controller/Api/V1/EmoticonController.php index 5c39b66..fa39905 100644 --- a/app/Controller/Api/V1/EmoticonController.php +++ b/app/Controller/Api/V1/EmoticonController.php @@ -2,8 +2,217 @@ namespace App\Controller\Api\V1; +use App\Constants\ResponseCode; +use App\Model\Emoticon; +use App\Model\EmoticonDetail; +use Hyperf\Di\Annotation\Inject; +use Hyperf\HttpServer\Annotation\Controller; +use Hyperf\HttpServer\Annotation\RequestMapping; +use Hyperf\HttpServer\Annotation\Middleware; +use Phper666\JWTAuth\Middleware\JWTAuthMiddleware; +use App\Service\EmoticonService; +/** + * Class EmoticonController + * + * @Controller(path="/api/v1/emoticon") + * @Middleware(JWTAuthMiddleware::class) + * + * @package App\Controller\Api\V1 + */ class EmoticonController extends CController { + /** + * @Inject + * @var EmoticonService + */ + public $emoticonService; + /** + * 获取用户表情包列表 + * + * @RequestMapping(path="user-emoticon", methods="get") + */ + public function getUserEmoticon() + { + $emoticonList = []; + $user_id = $this->uid(); + + if ($ids = $this->emoticonService->getInstallIds($user_id)) { + $items = Emoticon::whereIn('id', $ids)->get(['id', 'name', 'url']); + foreach ($items as $item) { + $emoticonList[] = [ + 'emoticon_id' => $item->id, + 'url' => get_media_url($item->url), + 'name' => $item->name, + 'list' => $this->emoticonService->getDetailsAll([ + ['emoticon_id', '=', $item->id], + ['user_id', '=', 0] + ]) + ]; + } + } + + return $this->response->success([ + 'sys_emoticon' => $emoticonList, + 'collect_emoticon' => $this->emoticonService->getDetailsAll([ + ['emoticon_id', '=', 0], + ['user_id', '=', $user_id] + ]) + ]); + } + + /** + * 获取系统表情包 + * + * @RequestMapping(path="system-emoticon", methods="get") + */ + public function getSystemEmoticon() + { + $items = Emoticon::get(['id', 'name', 'url'])->toArray(); + if ($items) { + $ids = $this->emoticonService->getInstallIds($this->uid()); + array_walk($items, function (&$item) use ($ids) { + $item['status'] = in_array($item['id'], $ids) ? 1 : 0; + $item['url'] = get_media_url($item['url']); + }); + } + + return $this->response->success($items); + } + + /** + * 安装或移除系统表情包 + * + * @RequestMapping(path="set-user-emoticon", methods="post") + */ + public function setUserEmoticon() + { + $params = $this->request->all(); + $this->validate($params, [ + 'emoticon_id' => 'required|integer', + 'type' => 'required|in:1,2', + ]); + + $user_id = $this->uid(); + if ($params['type'] == 2) { + // 移除表情包 + $isTrue = $this->emoticonService->removeSysEmoticon($user_id, $params['emoticon_id']); + if (!$isTrue) { + return $this->response->fail('移除表情包失败...'); + } + + return $this->response->success([], '移除表情包成功...'); + } else { + // 添加表情包 + $emoticonInfo = Emoticon::where('id', $params['emoticon_id'])->first(['id', 'name', 'url']); + if (!$emoticonInfo) { + return $this->response->fail('添加表情包失败...'); + } + + if (!$this->emoticonService->installSysEmoticon($user_id, $params['emoticon_id'])) { + return $this->response->fail('添加表情包失败...'); + } + + $data = [ + 'emoticon_id' => $emoticonInfo->id, + 'url' => get_media_url($emoticonInfo->url), + 'name' => $emoticonInfo->name, + 'list' => $this->emoticonService->getDetailsAll([ + ['emoticon_id', '=', $emoticonInfo->id] + ]) + ]; + + return $this->response->success($data, '添加表情包成功...'); + } + } + + /** + * 自定义上传表情包 + * + * @RequestMapping(path="upload-emoticon", methods="post") + */ + public function uploadEmoticon() + { + $file = $this->request->file('emoticon'); + if (!$file->isValid()) { + return $this->response->fail( + '图片上传失败,请稍后再试...', + ResponseCode::VALIDATION_ERROR + ); + } + + $ext = $file->getExtension(); + if (!in_array($ext, ['jpg', 'png', 'jpeg', 'gif', 'webp'])) { + return $this->response->fail( + '图片格式错误,目前仅支持jpg、png、jpeg、gif和webp', + ResponseCode::VALIDATION_ERROR + ); + } + + $save_path = ''; + + $user_id = $this->uid(); + $imgInfo = getimagesize($file->getPath()); + $filename = create_image_name($ext, $imgInfo[0], $imgInfo[1]); + + $result = EmoticonDetail::create([ + 'user_id' => $user_id, + 'url' => $save_path, + 'file_suffix' => $ext, + 'file_size' => $file->getSize(), + 'created_at' => time() + ]); + + if (!$result) { + return $this->response->fail('表情包上传失败...'); + } + + return $this->response->success([ + 'media_id' => $result->id, + 'src' => get_media_url($result->url) + ]); + } + + /** + * 收藏聊天图片的我的表情包 + * + * @RequestMapping(path="collect-emoticon", methods="post") + */ + public function collectEmoticon() + { + $params = $this->request->all(); + $this->validate($params, [ + 'record_id' => 'required|integer' + ]); + + [$isTrue, $data] = $this->emoticonService->collect($this->uid(), $params['record_id']); + + if (!$isTrue) { + return $this->response->fail('添加表情失败'); + } + + return $this->response->success([ + 'emoticon' => $data + ]); + } + + /** + * 移除收藏的表情包 + * + * @RequestMapping(path="del-collect-emoticon", methods="post") + */ + public function delCollectEmoticon() + { + $params = $this->request->all(); + $this->validate($params, [ + 'ids' => 'required' + ]); + + $ids = explode(',', trim($params['ids'])); + + return $this->emoticonService->deleteCollect($this->uid(), $ids) ? + $this->response->success([], 'success') : + $this->response->fail('fail'); + } } diff --git a/app/Controller/Api/V1/UsersController.php b/app/Controller/Api/V1/UsersController.php index 0c4135a..177c876 100644 --- a/app/Controller/Api/V1/UsersController.php +++ b/app/Controller/Api/V1/UsersController.php @@ -2,8 +2,177 @@ namespace App\Controller\Api\V1; +use Hyperf\Di\Annotation\Inject; +use Hyperf\HttpServer\Annotation\Controller; +use Hyperf\HttpServer\Annotation\RequestMapping; +use Hyperf\HttpServer\Annotation\Middleware; +use Phper666\JWTAuth\Middleware\JWTAuthMiddleware; +use App\Service\FriendService; +use App\Service\UserService; class UsersController extends CController { + /** + * @Inject + * @var FriendService + */ + protected $friendService; -} + /** + * @Inject + * @var UserService + */ + protected $userService; + + /** + * @RequestMapping(path="friends", methods="get") + */ + public function getUserFriends() + { + + } + + /** + * @RequestMapping(path="remove-friend", methods="get") + */ + public function removeFriend() + { + + } + + /** + * @RequestMapping(path="user-groups", methods="get") + */ + public function getUserGroups() + { + + } + + /** + * @RequestMapping(path="detail", methods="get") + */ + public function getUserDetail() + { + + } + + /** + * @RequestMapping(path="setting", methods="get") + */ + public function getUserSetting() + { + + } + + /** + * @RequestMapping(path="edit-user-detail", methods="get") + */ + public function editUserDetail() + { + + } + + /** + * @RequestMapping(path="edit-avatar", methods="get") + */ + public function editAvatar() + { + + } + + /** + * @RequestMapping(path="search-user", methods="get") + */ + public function searchUserInfo() + { + + } + + /** + * @RequestMapping(path="edit-friend-remark", methods="get") + */ + public function editFriendRemark() + { + + } + + /** + * @RequestMapping(path="send-friend-apply", methods="get") + */ + public function sendFriendApply() + { + + } + + /** + * @RequestMapping(path="handle-friend-apply", methods="get") + */ + public function handleFriendApply() + { + + } + + /** + * @RequestMapping(path="delete-friend-apply", methods="get") + */ + public function deleteFriendApply() + { + + } + + /** + * @RequestMapping(path="friend-apply-records", methods="get") + */ + public function getFriendApplyRecords() + { + + } + + /** + * @RequestMapping(path="friend-apply-num", methods="get") + */ + public function getApplyUnreadNum() + { + + } + + /** + * @RequestMapping(path="change-password", methods="get") + */ + public function editUserPassword() + { + + } + + /** + * @RequestMapping(path="change-mobile", methods="get") + */ + public function editUserMobile() + { + + } + + /** + * @RequestMapping(path="change-email", methods="get") + */ + public function editUserEmail() + { + + } + + /** + * @RequestMapping(path="send-mobile-code", methods="get") + */ + public function sendMobileCode() + { + + } + + /** + * @RequestMapping(path="send-change-email-code", methods="get") + */ + public function sendChangeEmailCode() + { + + } +} \ No newline at end of file diff --git a/app/Controller/IndexController.php b/app/Controller/IndexController.php index cc33603..fbb7d93 100644 --- a/app/Controller/IndexController.php +++ b/app/Controller/IndexController.php @@ -13,9 +13,7 @@ declare(strict_types=1); namespace App\Controller; -use App\Amqp\Producer\ChatMessageProducer; use Hyperf\HttpServer\Contract\ResponseInterface; -use Hyperf\Amqp\Producer; class IndexController extends AbstractController { @@ -24,13 +22,6 @@ class IndexController extends AbstractController $user = $this->request->input('user', 'Hyperf'); $method = $this->request->getMethod(); - $producer = container()->get(Producer::class); - - $ip = config('ip_address'); - - $string = time(); - $producer->produce(new ChatMessageProducer("我是来自[{$ip} 服务器的消息],{$string}")); - return [ 'method' => $method, 'message' => "Hello {$user}.", diff --git a/app/Controller/WebSocketController.php b/app/Controller/WebSocketController.php index 50d5880..df1f6be 100644 --- a/app/Controller/WebSocketController.php +++ b/app/Controller/WebSocketController.php @@ -3,33 +3,84 @@ declare(strict_types=1); namespace App\Controller; +use Hyperf\Di\Annotation\Inject; use Hyperf\Contract\OnCloseInterface; use Hyperf\Contract\OnMessageInterface; use Hyperf\Contract\OnOpenInterface; +use Hyperf\Utils\Codec\Json; +use Phper666\JWTAuth\JWT; use Swoole\Http\Request; use Swoole\Websocket\Frame; use Hyperf\Amqp\Producer; use App\Amqp\Producer\ChatMessageProducer; +use Swoole\Http\Response; +use Swoole\WebSocket\Server; +use App\Traits\WebSocketTrait; +use App\Service\SocketFDService; +/** + * Class WebSocketController + * @package App\Controller + */ class WebSocketController implements OnMessageInterface, OnOpenInterface, OnCloseInterface { - public function onMessage($server, Frame $frame): void + use WebSocketTrait; + + /** + * @Inject + * @var JWT + */ + private $jwt; + + /** + * @inject + * @var SocketFDService + */ + private $socketFDService; + + /** + * 连接创建成功回调事件 + * + * @param Response|Server $server + * @param Request $request + */ + public function onOpen($server, Request $request): void { - $producer = container()->get(Producer::class); + $token = $request->get['token'] ?? ''; + $userInfo = $this->jwt->getParserData($token); + stdout_log()->info("用户连接信息 : user_id:{$userInfo['user_id']} | fd:{$request->fd} | data:" . Json::encode($userInfo)); + + // 绑定fd与用户关系 + $this->socketFDService->bindRelation($request->fd, $userInfo['user_id']); $ip = config('ip_address'); + $server->push($request->fd, "成功连接[{$ip}],IM 服务器"); + } + + /** + * 消息接收回调事件 + * + * @param Response|Server $server + * @param Frame $frame + */ + public function onMessage($server, Frame $frame): void + { + $ip = config('ip_address'); + $producer = container()->get(Producer::class); $producer->produce(new ChatMessageProducer("我是来自[{$ip} 服务器的消息],{$frame->data}")); } + /** + * 连接创建成功回调事件 + * + * @param Response|\Swoole\Server $server + * @param int $fd + * @param int $reactorId + */ public function onClose($server, int $fd, int $reactorId): void { - echo PHP_EOL."FD : 【{$fd}】 已断开..."; - } - - public function onOpen($server, Request $request): void - { - $ip = config('ip_address'); - $server->push($request->fd, "成功连接[{$ip}],IM 服务器"); - echo PHP_EOL."FD : 【{$request->fd}】 成功连接..."; + // 解除fd关系 + $this->socketFDService->removeRelation($fd); + echo PHP_EOL . "FD : 【{$fd}】 已断开..."; } } diff --git a/app/Middleware/WebSocketAuthMiddleware.php b/app/Middleware/WebSocketAuthMiddleware.php new file mode 100644 index 0000000..caaa0e9 --- /dev/null +++ b/app/Middleware/WebSocketAuthMiddleware.php @@ -0,0 +1,45 @@ +container = $container; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + // 授权验证拦截握手请求并实现权限检查 + $token = $request->getQueryParams()['token'] ?? ''; + try { + $this->jwt->checkToken($token); + } catch (\Exception $e) { + return $this->container->get(\Hyperf\HttpServer\Contract\ResponseInterface::class)->raw('Forbidden'); + } + + return $handler->handle($request); + } +} diff --git a/app/Service/EmoticonService.php b/app/Service/EmoticonService.php index 65d9fff..6f461f4 100644 --- a/app/Service/EmoticonService.php +++ b/app/Service/EmoticonService.php @@ -2,8 +2,169 @@ namespace App\Service; +use App\Model\Chat\ChatRecord; +use App\Model\Chat\ChatRecordsFile; +use App\Model\EmoticonDetail; +use App\Model\Group\UsersGroup; +use App\Model\UsersEmoticon; +/** + * 表情服务层 + * + * Class EmoticonService + * @package App\Services + */ class EmoticonService extends BaseService { + /** + * 安装系统表情包 + * + * @param int $user_id 用户ID + * @param int $emoticon_id 表情包ID + * @return mixed + */ + public function installSysEmoticon(int $user_id, int $emoticon_id) + { + $info = UsersEmoticon::select(['id', 'user_id', 'emoticon_ids'])->where('user_id', $user_id)->first(); + if (!$info) { + return UsersEmoticon::create(['user_id' => $user_id, 'emoticon_ids' => $emoticon_id]) ? true : false; + } + $emoticon_ids = $info->emoticon_ids; + if (in_array($emoticon_id, $emoticon_ids)) { + return true; + } + + $emoticon_ids[] = $emoticon_id; + return UsersEmoticon::where('user_id', $user_id)->update([ + 'emoticon_ids' => implode(',', $emoticon_ids) + ]) ? true : false; + } + + /** + * 移除已安装的系统表情包 + * + * @param int $user_id 用户ID + * @param int $emoticon_id 表情包ID + * @return bool + */ + public function removeSysEmoticon(int $user_id, int $emoticon_id) + { + $info = UsersEmoticon::select(['id', 'user_id', 'emoticon_ids'])->where('user_id', $user_id)->first(); + if (!$info || !in_array($emoticon_id, $info->emoticon_ids)) { + return false; + } + + $emoticon_ids = $info->emoticon_ids; + foreach ($emoticon_ids as $k => $id) { + if ($id == $emoticon_id) { + unset($emoticon_ids[$k]); + } + } + + if (count($info->emoticon_ids) == count($emoticon_ids)) { + return false; + } + + return UsersEmoticon::where('user_id', $user_id)->update([ + 'emoticon_ids' => implode(',', $emoticon_ids) + ]) ? true : false; + } + + /** + * 获取用户安装的表情ID + * + * @param int $user_id 用户ID + * @return array + */ + public function getInstallIds(int $user_id) + { + $result = UsersEmoticon::where('user_id', $user_id)->value('emoticon_ids'); + return $result ? explode(',', $result) : []; + } + + /** + * 收藏聊天图片 + * + * @param int $user_id 用户ID + * @param int $record_id 聊天消息ID + * @return array + */ + public function collect(int $user_id, int $record_id) + { + $result = ChatRecord::where([ + ['id', '=', $record_id], + ['msg_type', '=', 2], + ['is_revoke', '=', 0], + ])->first(['id', 'source', 'msg_type', 'user_id', 'receive_id', 'is_revoke']); + + if (!$result) { + return [false, []]; + } + + if ($result->source == 1) { + if ($result->user_id != $user_id && $result->receive_id != $user_id) { + return [false, []]; + } + } else { + if (!UsersGroup::isMember($result->receive_id, $user_id)) { + return [false, []]; + } + } + + $fileInfo = ChatRecordsFile::where('record_id', $result->id)->where('file_type', 1)->first([ + 'file_suffix', + 'file_size', + 'save_dir' + ]); + + if (!$fileInfo) { + return [false, []]; + } + + + $result = EmoticonDetail::where('user_id', $user_id)->where('url', $fileInfo->save_dir)->first(); + if ($result) { + return [false, []]; + } + + $res = EmoticonDetail::create([ + 'user_id' => $user_id, + 'url' => $fileInfo->save_dir, + 'file_suffix' => $fileInfo->file_suffix, + 'file_size' => $fileInfo->file_size, + 'created_at' => time() + ]); + + return $res ? [true, ['media_id' => $res->id, 'src' => get_media_url($res->url)]] : [false, []]; + } + + /** + * 移除收藏的表情包 + * + * @param int $user_id 用户ID + * @param array $ids 表情包详情ID + * @return + */ + public function deleteCollect(int $user_id, array $ids) + { + return EmoticonDetail::whereIn('id', $ids)->where('user_id', $user_id)->delete(); + } + + /** + * 获取表情包列表 + * + * @param array $where + * @return mixed + */ + public function getDetailsAll(array $where = []) + { + $list = EmoticonDetail::where($where)->get(['id as media_id', 'url as src'])->toArray(); + + foreach ($list as $k => $value) { + $list[$k]['src'] = get_media_url($value['src']); + } + + return $list; + } } diff --git a/app/Service/SocketFDService.php b/app/Service/SocketFDService.php new file mode 100644 index 0000000..34f140a --- /dev/null +++ b/app/Service/SocketFDService.php @@ -0,0 +1,117 @@ +redis->multi(); + $this->redis->hSet(sprintf('%s:%s', self::BIND_FD_TO_USER, $run_id), (string)$fd, (string)$user_id); + $this->redis->sadd(sprintf('%s:%s:%s', self::BIND_USER_TO_FDS, $run_id, $user_id), $fd); + $this->redis->exec(); + } + + /** + * 解除指定的客户端fd与用户绑定关系 + * + * @param int $fd 客户端ID + * @param string $run_id 服务运行ID(默认当前服务ID) + */ + public function removeRelation(int $fd, $run_id = SERVER_RUN_ID) + { + $user_id = $this->findFdUserId($fd) | 0; + + $this->redis->hdel(sprintf('%s:%s', self::BIND_FD_TO_USER, $run_id), (string)$fd); + $this->redis->srem(sprintf('%s:%s:%s', self::BIND_USER_TO_FDS, $run_id, $user_id), $fd); + } + + /** + * 检测用户当前是否在线 + * + * @param int $user_id 用户ID + * @param string $run_id 服务运行ID(默认当前服务ID) + * @return bool + */ + public function isOnline(int $user_id, $run_id = SERVER_RUN_ID): bool + { + return $this->redis->scard(sprintf('%s:%s:%s', self::BIND_USER_TO_FDS, $run_id, $user_id)) ? true : false; + } + + /** + * 查询客户端fd对应的用户ID + * + * @param int $fd 客户端ID + * @param string $run_id 服务运行ID(默认当前服务ID) + * @return int + */ + public function findFdUserId(int $fd, $run_id = SERVER_RUN_ID) + { + return $this->redis->hget(sprintf('%s:%s', self::BIND_FD_TO_USER, $run_id), (string)$fd) ?: 0; + } + + /** + * 查询用户的客户端fd集合(用户可能存在多端登录) + * + * @param int $user_id 用户ID + * @param string $run_id 服务运行ID(默认当前服务ID) + * @return string + */ + public function findUserFds(int $user_id, $run_id = SERVER_RUN_ID) + { + return ''; + } + + /** + * 清除绑定缓存的信息 + * + * @param string $run_id 服务运行ID(默认当前服务ID) + */ + public function removeRedisCache($run_id = SERVER_RUN_ID) + { + $this->redis->del(self::BIND_FD_TO_USER); + $prefix = self::BIND_USER_TO_FDS; + $iterator = null; + while (true) { + $keys = $this->redis->scan($iterator, "{$prefix}*"); + if ($keys === false) { + return; + } + if (!empty($keys)) { + $this->redis->del(...$keys); + } + } + } +} \ No newline at end of file diff --git a/app/Support/RedisLock.php b/app/Support/RedisLock.php index f80d876..5d9844c 100644 --- a/app/Support/RedisLock.php +++ b/app/Support/RedisLock.php @@ -31,13 +31,13 @@ class RedisLock * * @param string $key 缓存KEY * @param string $requestId 客户端请求唯一ID - * @param int $lockSecond 锁定时间 单位(秒) - * @param int $timeout 取锁超时时间。单位(秒)。等于0,如果当前锁被占用,则立即返回失败。如果大于0,则反复尝试获取锁直到达到该超时时间。 - * @param int $sleep 取锁间隔时间 单位(微秒)。当锁为占用状态时。每隔多久尝试去取锁。默认 0.1 秒一次取锁。 + * @param integer $lockSecond 锁定时间 单位(秒) + * @param integer $timeout 取锁超时时间。单位(秒)。等于0,如果当前锁被占用,则立即返回失败。如果大于0,则反复尝试获取锁直到达到该超时时间。 + * @param integer|float $sleep 取锁间隔时间 单位(秒)。当锁为占用状态时。每隔多久尝试去取锁。默认 0.1 秒一次取锁。 * @return bool * @throws \Exception */ - public static function lock(string $key, string $requestId, $lockSecond = 20, $timeout = 0, $sleep = 100000) + public static function lock(string $key, string $requestId, $lockSecond = 20, $timeout = 0, $sleep = 0.1) { if (empty($key)) { throw new \Exception('获取锁的KEY值没有设置'); @@ -56,7 +56,7 @@ class RedisLock break; } - usleep($sleep); + \Swoole\Coroutine\System::sleep($sleep); } while (!is_numeric($timeout) || (self::getMicroTime()) < ($start + ($timeout * 1000000))); return $acquired ? true : false; diff --git a/app/Support/Http/Response.php b/app/Support/Response.php similarity index 98% rename from app/Support/Http/Response.php rename to app/Support/Response.php index 30d711f..61ff7dc 100644 --- a/app/Support/Http/Response.php +++ b/app/Support/Response.php @@ -1,6 +1,6 @@ get(Redis::class); - } +function redis() +{ + return container()->get(Redis::class); } /** * server 实例 基于 swoole server */ -if (!function_exists('server')) { - function server() - { - return container()->get(ServerFactory::class)->getServer()->getServer(); - } +function server() +{ + return container()->get(ServerFactory::class)->getServer()->getServer(); } + /** * websocket frame 实例 */ -if (!function_exists('frame')) { - function frame() - { - return container()->get(Frame::class); - } +function frame() +{ + return container()->get(Frame::class); } /** * websocket 实例 */ -if (!function_exists('websocket')) { - function websocket() - { - return container()->get(WebSocketServer::class); - } +function websocket() +{ + return container()->get(WebSocketServer::class); } /** * 缓存实例 简单的缓存 */ -if (!function_exists('cache')) { - function cache() - { - return container()->get(Psr\SimpleCache\CacheInterface::class); - } +function cache() +{ + return container()->get(Psr\SimpleCache\CacheInterface::class); } /** * 控制台日志 */ -if (!function_exists('stdLog')) { - function stdLog() - { - return container()->get(StdoutLoggerInterface::class); - } +function stdout_log() +{ + return container()->get(StdoutLoggerInterface::class); } /** * 文件日志 */ -if (!function_exists('logger')) { - function logger(string $name = 'APP') - { - return container()->get(LoggerFactory::class)->get($name); - } +function logger(string $name = 'APP') +{ + return container()->get(LoggerFactory::class)->get($name); } /** * http 请求实例 */ -if (!function_exists('request')) { - function request() - { - return container()->get(ServerRequestInterface::class); - } +function request() +{ + return container()->get(ServerRequestInterface::class); } /** * 请求响应 */ -if (!function_exists('response')) { - function response() - { - return container()->get(ResponseInterface::class); - } +function response() +{ + return container()->get(ResponseInterface::class); } /** @@ -118,7 +98,8 @@ if (!function_exists('response')) { * @param string $password * @return bool|false|null|string */ -function create_password(string $password){ +function create_password(string $password) +{ return password_hash($password, PASSWORD_DEFAULT); } @@ -160,3 +141,28 @@ function diff_date($day1, $day2) return ceil(($second1 - $second2) / 86400); } + + +/** + * 获取媒体文件url + * + * @param string $path 文件相对路径 + * @return string + */ +function get_media_url(string $path) +{ + return '/' . $path; +} + +/** + * 随机生成图片名 + * + * @param string $ext 图片后缀名 + * @param int $width 图片宽度 + * @param int $height 图片高度 + * @return string + */ +function create_image_name(string $ext, int $width, int $height) +{ + return uniqid() . Str::random(18) . uniqid() . '_' . $width . 'x' . $height . '.' . $ext; +} \ No newline at end of file diff --git a/bin/hyperf.php b/bin/hyperf.php index 0c78af2..232e968 100644 --- a/bin/hyperf.php +++ b/bin/hyperf.php @@ -12,8 +12,6 @@ date_default_timezone_set('Asia/Shanghai'); require BASE_PATH . '/vendor/autoload.php'; -echo gethostbyname(null); - // Self-called anonymous function that creates its own scope and keep the global namespace clean. (function () { Hyperf\Di\ClassLoader::init(); diff --git a/composer.json b/composer.json index d4a5799..9549594 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,10 @@ "hyperf/websocket-server": "^2.0", "hyperf/constants": "^2.0", "hyperf/validation": "^2.0", - "phper666/jwt-auth": "^3.0" + "phper666/jwt-auth": "^3.0", + "hyperf/filesystem": "^2.0", + "hyperf/socketio-server": "^2.0", + "hashids/hashids": "^4.0" }, "require-dev": { "swoole/ide-helper": "^4.5", diff --git a/config/autoload/file.php b/config/autoload/file.php new file mode 100644 index 0000000..59cd236 --- /dev/null +++ b/config/autoload/file.php @@ -0,0 +1,94 @@ + 'local', + 'storage' => [ + 'local' => [ + 'driver' => \Hyperf\Filesystem\Adapter\LocalAdapterFactory::class, + 'root' => __DIR__ . '/../../runtime', + ], + 'ftp' => [ + 'driver' => \Hyperf\Filesystem\Adapter\FtpAdapterFactory::class, + 'host' => 'ftp.example.com', + 'username' => 'username', + 'password' => 'password', + // 'port' => 21, + // 'root' => '/path/to/root', + // 'passive' => true, + // 'ssl' => true, + // 'timeout' => 30, + // 'ignorePassiveAddress' => false, + ], + 'memory' => [ + 'driver' => \Hyperf\Filesystem\Adapter\MemoryAdapterFactory::class, + ], + 's3' => [ + 'driver' => \Hyperf\Filesystem\Adapter\S3AdapterFactory::class, + 'credentials' => [ + 'key' => env('S3_KEY'), + 'secret' => env('S3_SECRET'), + ], + 'region' => env('S3_REGION'), + 'version' => 'latest', + 'bucket_endpoint' => false, + 'use_path_style_endpoint' => false, + 'endpoint' => env('S3_ENDPOINT'), + 'bucket_name' => env('S3_BUCKET'), + ], + 'minio' => [ + 'driver' => \Hyperf\Filesystem\Adapter\S3AdapterFactory::class, + 'credentials' => [ + 'key' => env('S3_KEY'), + 'secret' => env('S3_SECRET'), + ], + 'region' => env('S3_REGION'), + 'version' => 'latest', + 'bucket_endpoint' => false, + 'use_path_style_endpoint' => true, + 'endpoint' => env('S3_ENDPOINT'), + 'bucket_name' => env('S3_BUCKET'), + ], + 'oss' => [ + 'driver' => \Hyperf\Filesystem\Adapter\AliyunOssAdapterFactory::class, + 'accessId' => env('OSS_ACCESS_ID'), + 'accessSecret' => env('OSS_ACCESS_SECRET'), + 'bucket' => env('OSS_BUCKET'), + 'endpoint' => env('OSS_ENDPOINT'), + // 'timeout' => 3600, + // 'connectTimeout' => 10, + // 'isCName' => false, + // 'token' => '', + ], + 'qiniu' => [ + 'driver' => \Hyperf\Filesystem\Adapter\QiniuAdapterFactory::class, + 'accessKey' => env('QINIU_ACCESS_KEY'), + 'secretKey' => env('QINIU_SECRET_KEY'), + 'bucket' => env('QINIU_BUCKET'), + 'domain' => env('QINBIU_DOMAIN'), + ], + 'cos' => [ + 'driver' => \Hyperf\Filesystem\Adapter\CosAdapterFactory::class, + 'region' => env('COS_REGION'), + 'credentials' => [ + 'appId' => env('COS_APPID'), + 'secretId' => env('COS_SECRET_ID'), + 'secretKey' => env('COS_SECRET_KEY'), + ], + 'bucket' => env('COS_BUCKET'), + 'read_from_cdn' => false, + // 'timeout' => 60, + // 'connect_timeout' => 60, + // 'cdn' => '', + // 'scheme' => 'https', + ], + ], +]; diff --git a/config/autoload/middlewares.php b/config/autoload/middlewares.php index 6ad681d..0891d9d 100644 --- a/config/autoload/middlewares.php +++ b/config/autoload/middlewares.php @@ -1,6 +1,7 @@ [ - + CorsMiddleware::class ], + 'ws' => [ + + ] ]; diff --git a/config/autoload/server.php b/config/autoload/server.php index 6c2aca3..296b706 100644 --- a/config/autoload/server.php +++ b/config/autoload/server.php @@ -32,6 +32,7 @@ return [ 'port' => 9504, 'sock_type' => SWOOLE_SOCK_TCP, 'callbacks' => [ + // 自定义握手处理 SwooleEvent::ON_HAND_SHAKE => [Hyperf\WebSocketServer\Server::class, 'onHandShake'], SwooleEvent::ON_MESSAGE => [Hyperf\WebSocketServer\Server::class, 'onMessage'], SwooleEvent::ON_CLOSE => [Hyperf\WebSocketServer\Server::class, 'onClose'], @@ -52,7 +53,7 @@ return [ ], 'callbacks' => [ //自定义启动前事件 - //SwooleEvent::ON_BEFORE_START => [Hyperf\Framework\Bootstrap\ServerStartCallback::class, 'beforeStart'], + SwooleEvent::ON_BEFORE_START => [App\Bootstrap\ServerStart::class, 'beforeStart'], SwooleEvent::ON_WORKER_START => [Hyperf\Framework\Bootstrap\WorkerStartCallback::class, 'onWorkerStart'], SwooleEvent::ON_PIPE_MESSAGE => [Hyperf\Framework\Bootstrap\PipeMessageCallback::class, 'onPipeMessage'], diff --git a/config/routes.php b/config/routes.php index 71264cf..9e19c1c 100644 --- a/config/routes.php +++ b/config/routes.php @@ -10,23 +10,19 @@ declare(strict_types=1); * @contact group@hyperf.io * @license https://github.com/hyperf/hyperf/blob/master/LICENSE */ + use Hyperf\HttpServer\Router\Router; -use App\Middleware\CorsMiddleware; - - -Router::addRoute(['GET', 'POST', 'HEAD'], '/', 'App\Controller\IndexController@index'); - -Router::post('/upload', 'App\Controller\IndexController@upload',['middleware' => [CorsMiddleware::class]]); - - - - - Router::get('/favicon.ico', function () { return ''; }); -Router::addServer('ws', function () { - Router::get('/', 'App\Controller\WebSocketController'); +// 加载 Http 路由 +Router::addServer('http', function () { + require __DIR__ . '/routes/http.php'; +}); + +// 加载 Websocket 路由 +Router::addServer('ws', function () { + require __DIR__ . '/routes/websocket.php'; }); diff --git a/config/routes/http.php b/config/routes/http.php new file mode 100644 index 0000000..ac29095 --- /dev/null +++ b/config/routes/http.php @@ -0,0 +1,17 @@ + [\App\Middleware\WebSocketAuthMiddleware::class] +]); \ No newline at end of file