初始化

main
gzydong 2020-11-07 22:57:10 +08:00
parent 84e1dfc7a8
commit 0e2787f240
24 changed files with 1073 additions and 105 deletions

View File

@ -12,7 +12,7 @@ use Hyperf\Amqp\Message\Type;
use Hyperf\Amqp\Builder\QueueBuilder; use Hyperf\Amqp\Builder\QueueBuilder;
/** /**
* @Consumer(name=" ChatMessage ",enable=true) * @Consumer(name="聊天消息消费者",enable=true)
*/ */
class ChatMessageConsumer extends ConsumerMessage class ChatMessageConsumer extends ConsumerMessage
{ {

View File

@ -6,6 +6,7 @@ namespace App\Amqp\Producer;
use Hyperf\Amqp\Message\ProducerMessage; use Hyperf\Amqp\Message\ProducerMessage;
use Hyperf\Amqp\Message\Type; use Hyperf\Amqp\Message\Type;
use Hyperf\Utils\Str;
class ChatMessageProducer extends ProducerMessage class ChatMessageProducer extends ProducerMessage
{ {
@ -16,13 +17,24 @@ class ChatMessageProducer extends ProducerMessage
public function __construct($data) public function __construct($data)
{ {
$message = [ $message = [
'uuid' => $this->uuid(),
'method' => '', // 'method' => '', //
'sender'=>'', // 发送者 'sender' => '', //发送者ID
'receive'=>'', // 接收者 'receive' => '', //接收者ID
'receiveType'=>'', 'receiveType' => '', //接收者类型 1:好友;2:群组
'message' => [] 'message' => []
]; ];
$this->payload = $data; $this->payload = $data;
} }
/**
* 生成唯一ID
*
* @return string
*/
private function uuid()
{
return Str::random() . rand(100000, 999999);
}
} }

View File

@ -0,0 +1,43 @@
<?php
namespace App\Bootstrap;
use App\Service\SocketFDService;
use Hyperf\Framework\Bootstrap\ServerStartCallback;
use Hashids\Hashids;
use Hyperf\Di\Annotation\Inject;
/**
* 自定义服务启动前回调事件
*
* Class ServerStart
* @package App\Bootstrap
*/
class ServerStart extends ServerStartCallback
{
/**
* @inject
* @var SocketFDService
*/
private $socketFDService;
/**
* 回调事件
*/
public function beforeStart()
{
$hashids = new Hashids('', 8, 'abcdefghijklmnopqrstuvwxyz');
// 服务运行ID
define('SERVER_RUN_ID', $hashids->encode(time() . rand(1000, 9999)));
$this->socketFDService->removeRedisCache();
stdout_log()->info(sprintf('服务运行ID : %s', SERVER_RUN_ID));
stdout_log()->info('服务启动前回调事件 : beforeStart ...');
}
}

View File

@ -25,7 +25,6 @@ class ResponseCode extends AbstractConstants
*/ */
const SERVER_ERROR = 500; const SERVER_ERROR = 500;
/** /**
* @Message("请求数据验证失败!") * @Message("请求数据验证失败!")
*/ */

View File

@ -3,7 +3,7 @@
namespace App\Controller\Api\V1; namespace App\Controller\Api\V1;
use App\Controller\AbstractController; use App\Controller\AbstractController;
use App\Support\Http\Response; use App\Support\Response;
use Hyperf\Di\Annotation\Inject; use Hyperf\Di\Annotation\Inject;
use Phper666\JWTAuth\JWT; use Phper666\JWTAuth\JWT;

View File

@ -2,8 +2,217 @@
namespace App\Controller\Api\V1; 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 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');
}
} }

View File

@ -2,8 +2,177 @@
namespace App\Controller\Api\V1; 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 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()
{
}
}

View File

@ -13,9 +13,7 @@ declare(strict_types=1);
namespace App\Controller; namespace App\Controller;
use App\Amqp\Producer\ChatMessageProducer;
use Hyperf\HttpServer\Contract\ResponseInterface; use Hyperf\HttpServer\Contract\ResponseInterface;
use Hyperf\Amqp\Producer;
class IndexController extends AbstractController class IndexController extends AbstractController
{ {
@ -24,13 +22,6 @@ class IndexController extends AbstractController
$user = $this->request->input('user', 'Hyperf'); $user = $this->request->input('user', 'Hyperf');
$method = $this->request->getMethod(); $method = $this->request->getMethod();
$producer = container()->get(Producer::class);
$ip = config('ip_address');
$string = time();
$producer->produce(new ChatMessageProducer("我是来自[{$ip} 服务器的消息]{$string}"));
return [ return [
'method' => $method, 'method' => $method,
'message' => "Hello {$user}.", 'message' => "Hello {$user}.",

View File

@ -3,33 +3,84 @@ declare(strict_types=1);
namespace App\Controller; namespace App\Controller;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Contract\OnCloseInterface; use Hyperf\Contract\OnCloseInterface;
use Hyperf\Contract\OnMessageInterface; use Hyperf\Contract\OnMessageInterface;
use Hyperf\Contract\OnOpenInterface; use Hyperf\Contract\OnOpenInterface;
use Hyperf\Utils\Codec\Json;
use Phper666\JWTAuth\JWT;
use Swoole\Http\Request; use Swoole\Http\Request;
use Swoole\Websocket\Frame; use Swoole\Websocket\Frame;
use Hyperf\Amqp\Producer; use Hyperf\Amqp\Producer;
use App\Amqp\Producer\ChatMessageProducer; 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 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'); $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}")); $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 public function onClose($server, int $fd, int $reactorId): void
{ {
// 解除fd关系
$this->socketFDService->removeRelation($fd);
echo PHP_EOL . "FD : 【{$fd}】 已断开..."; 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}】 成功连接...";
}
} }

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace App\Middleware;
use Phper666\JWTAuth\JWT;
use Hyperf\Di\Annotation\Inject;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class WebSocketAuthMiddleware implements MiddlewareInterface
{
/**
* @var ContainerInterface
*/
protected $container;
/**
* @inject
* @var JWT
*/
private $jwt;
public function __construct(ContainerInterface $container)
{
$this->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);
}
}

View File

@ -2,8 +2,169 @@
namespace App\Service; 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 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;
}
} }

View File

@ -0,0 +1,117 @@
<?php
namespace App\Service;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Redis\Redis;
/**
* Socket客户端ID服务
*
* @package App\Service
*/
class SocketFDService
{
/**
* fd与用户绑定(使用hash 做处理)
*/
const BIND_FD_TO_USER = 'socket:fd:user';
/**
* 使用集合做处理
*/
const BIND_USER_TO_FDS = 'socket:user:fds';
/**
* @inject
* @var Redis
*/
private $redis;
/**
* 客户端fd与用户ID绑定关系
*
* @param int $fd 客户端fd
* @param int $user_id 用户ID
* @param string $run_id 服务运行ID默认当前服务ID
* @return mixed
*/
public function bindRelation(int $fd, int $user_id, $run_id = SERVER_RUN_ID)
{
$this->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);
}
}
}
}

View File

@ -31,13 +31,13 @@ class RedisLock
* *
* @param string $key 缓存KEY * @param string $key 缓存KEY
* @param string $requestId 客户端请求唯一ID * @param string $requestId 客户端请求唯一ID
* @param int $lockSecond 锁定时间 单位() * @param integer $lockSecond 锁定时间 单位()
* @param int $timeout 取锁超时时间。单位()。等于0,如果当前锁被占用,则立即返回失败。如果大于0,则反复尝试获取锁直到达到该超时时间。 * @param integer $timeout 取锁超时时间。单位()。等于0,如果当前锁被占用,则立即返回失败。如果大于0,则反复尝试获取锁直到达到该超时时间。
* @param int $sleep 取锁间隔时间 单位()。当锁为占用状态时。每隔多久尝试去取锁。默认 0.1 秒一次取锁。 * @param integer|float $sleep 取锁间隔时间 单位()。当锁为占用状态时。每隔多久尝试去取锁。默认 0.1 秒一次取锁。
* @return bool * @return bool
* @throws \Exception * @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)) { if (empty($key)) {
throw new \Exception('获取锁的KEY值没有设置'); throw new \Exception('获取锁的KEY值没有设置');
@ -56,7 +56,7 @@ class RedisLock
break; break;
} }
usleep($sleep); \Swoole\Coroutine\System::sleep($sleep);
} while (!is_numeric($timeout) || (self::getMicroTime()) < ($start + ($timeout * 1000000))); } while (!is_numeric($timeout) || (self::getMicroTime()) < ($start + ($timeout * 1000000)));
return $acquired ? true : false; return $acquired ? true : false;

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Support\Http; namespace App\Support;
use Hyperf\Di\Annotation\Inject; use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Contract\ResponseInterface; use Hyperf\HttpServer\Contract\ResponseInterface;

View File

@ -0,0 +1,28 @@
<?php
namespace App\Traits;
/**
* Trait WebSocketTrait
*
* @package App\Traits
*/
trait WebSocketTrait
{
/**
* 聊天记录回调事件
*/
public function onTalk()
{
}
/**
* 键盘输入回调事件
*/
public function onInput()
{
}
}

View File

@ -1,6 +1,5 @@
<?php <?php
use Hyperf\Contract\StdoutLoggerInterface; use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\HttpServer\Contract\ResponseInterface; use Hyperf\HttpServer\Contract\ResponseInterface;
use Hyperf\Logger\LoggerFactory; use Hyperf\Logger\LoggerFactory;
@ -9,108 +8,89 @@ use Hyperf\Utils\ApplicationContext;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Swoole\Websocket\Frame; use Swoole\Websocket\Frame;
use Swoole\WebSocket\Server as WebSocketServer; use Swoole\WebSocket\Server as WebSocketServer;
use Hyperf\Utils\Str;
/** /**
* 容器实例 * 容器实例
*/ */
if (!function_exists('container')) {
function container() function container()
{ {
return ApplicationContext::getContainer(); return ApplicationContext::getContainer();
} }
}
/** /**
* Redis 客户端实例 * Redis 客户端实例
*/ */
if (!function_exists('redis')) {
function redis() function redis()
{ {
return container()->get(Redis::class); return container()->get(Redis::class);
} }
}
/** /**
* server 实例 基于 swoole server * server 实例 基于 swoole server
*/ */
if (!function_exists('server')) {
function server() function server()
{ {
return container()->get(ServerFactory::class)->getServer()->getServer(); return container()->get(ServerFactory::class)->getServer()->getServer();
} }
}
/** /**
* websocket frame 实例 * websocket frame 实例
*/ */
if (!function_exists('frame')) {
function frame() function frame()
{ {
return container()->get(Frame::class); return container()->get(Frame::class);
} }
}
/** /**
* websocket 实例 * websocket 实例
*/ */
if (!function_exists('websocket')) {
function websocket() function websocket()
{ {
return container()->get(WebSocketServer::class); return container()->get(WebSocketServer::class);
} }
}
/** /**
* 缓存实例 简单的缓存 * 缓存实例 简单的缓存
*/ */
if (!function_exists('cache')) {
function cache() function cache()
{ {
return container()->get(Psr\SimpleCache\CacheInterface::class); return container()->get(Psr\SimpleCache\CacheInterface::class);
} }
}
/** /**
* 控制台日志 * 控制台日志
*/ */
if (!function_exists('stdLog')) { function stdout_log()
function stdLog()
{ {
return container()->get(StdoutLoggerInterface::class); return container()->get(StdoutLoggerInterface::class);
} }
}
/** /**
* 文件日志 * 文件日志
*/ */
if (!function_exists('logger')) {
function logger(string $name = 'APP') function logger(string $name = 'APP')
{ {
return container()->get(LoggerFactory::class)->get($name); return container()->get(LoggerFactory::class)->get($name);
} }
}
/** /**
* http 请求实例 * http 请求实例
*/ */
if (!function_exists('request')) {
function request() function request()
{ {
return container()->get(ServerRequestInterface::class); return container()->get(ServerRequestInterface::class);
} }
}
/** /**
* 请求响应 * 请求响应
*/ */
if (!function_exists('response')) {
function response() function response()
{ {
return container()->get(ResponseInterface::class); return container()->get(ResponseInterface::class);
} }
}
/** /**
* 获取加密后的密码字符 * 获取加密后的密码字符
@ -118,7 +98,8 @@ if (!function_exists('response')) {
* @param string $password * @param string $password
* @return bool|false|null|string * @return bool|false|null|string
*/ */
function create_password(string $password){ function create_password(string $password)
{
return password_hash($password, PASSWORD_DEFAULT); return password_hash($password, PASSWORD_DEFAULT);
} }
@ -160,3 +141,28 @@ function diff_date($day1, $day2)
return ceil(($second1 - $second2) / 86400); 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;
}

View File

@ -12,8 +12,6 @@ date_default_timezone_set('Asia/Shanghai');
require BASE_PATH . '/vendor/autoload.php'; require BASE_PATH . '/vendor/autoload.php';
echo gethostbyname(null);
// Self-called anonymous function that creates its own scope and keep the global namespace clean. // Self-called anonymous function that creates its own scope and keep the global namespace clean.
(function () { (function () {
Hyperf\Di\ClassLoader::init(); Hyperf\Di\ClassLoader::init();

View File

@ -31,7 +31,10 @@
"hyperf/websocket-server": "^2.0", "hyperf/websocket-server": "^2.0",
"hyperf/constants": "^2.0", "hyperf/constants": "^2.0",
"hyperf/validation": "^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": { "require-dev": {
"swoole/ide-helper": "^4.5", "swoole/ide-helper": "^4.5",

94
config/autoload/file.php Normal file
View File

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
return [
'default' => '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',
],
],
];

View File

@ -1,6 +1,7 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
/** /**
* This file is part of Hyperf. * This file is part of Hyperf.
* *
@ -9,8 +10,15 @@ declare(strict_types=1);
* @contact group@hyperf.io * @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/ */
use App\Middleware\CorsMiddleware;
use App\Middleware\WebSocketAuthMiddleware;
return [ return [
'http' => [ 'http' => [
CorsMiddleware::class
], ],
'ws' => [
]
]; ];

View File

@ -32,6 +32,7 @@ return [
'port' => 9504, 'port' => 9504,
'sock_type' => SWOOLE_SOCK_TCP, 'sock_type' => SWOOLE_SOCK_TCP,
'callbacks' => [ 'callbacks' => [
// 自定义握手处理
SwooleEvent::ON_HAND_SHAKE => [Hyperf\WebSocketServer\Server::class, 'onHandShake'], SwooleEvent::ON_HAND_SHAKE => [Hyperf\WebSocketServer\Server::class, 'onHandShake'],
SwooleEvent::ON_MESSAGE => [Hyperf\WebSocketServer\Server::class, 'onMessage'], SwooleEvent::ON_MESSAGE => [Hyperf\WebSocketServer\Server::class, 'onMessage'],
SwooleEvent::ON_CLOSE => [Hyperf\WebSocketServer\Server::class, 'onClose'], SwooleEvent::ON_CLOSE => [Hyperf\WebSocketServer\Server::class, 'onClose'],
@ -52,7 +53,7 @@ return [
], ],
'callbacks' => [ '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_WORKER_START => [Hyperf\Framework\Bootstrap\WorkerStartCallback::class, 'onWorkerStart'],
SwooleEvent::ON_PIPE_MESSAGE => [Hyperf\Framework\Bootstrap\PipeMessageCallback::class, 'onPipeMessage'], SwooleEvent::ON_PIPE_MESSAGE => [Hyperf\Framework\Bootstrap\PipeMessageCallback::class, 'onPipeMessage'],

View File

@ -10,23 +10,19 @@ declare(strict_types=1);
* @contact group@hyperf.io * @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/ */
use Hyperf\HttpServer\Router\Router; 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 () { Router::get('/favicon.ico', function () {
return ''; return '';
}); });
Router::addServer('ws', function () { // 加载 Http 路由
Router::get('/', 'App\Controller\WebSocketController'); Router::addServer('http', function () {
require __DIR__ . '/routes/http.php';
});
// 加载 Websocket 路由
Router::addServer('ws', function () {
require __DIR__ . '/routes/websocket.php';
}); });

17
config/routes/http.php Normal file
View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
use Hyperf\HttpServer\Router\Router;
Router::get('/', 'App\Controller\IndexController@index');

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
use Hyperf\HttpServer\Router\Router;
// 添加 ws 服务对应的路由
Router::get('/socket.io', 'App\Controller\WebSocketController', [
'middleware' => [\App\Middleware\WebSocketAuthMiddleware::class]
]);