Compare commits
	
		
			10 Commits
		
	
	
		
			1ff1dceab7
			...
			9a0d43ce89
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9a0d43ce89 | |||
| 5c203379d7 | |||
| 779237afcd | |||
| 463487b939 | |||
| 4ec5166f50 | |||
| 6b2eb3cff8 | |||
| fe656375b4 | |||
| e6266edc07 | |||
| 0b6f55b429 | |||
| 144caadd3f | 
| @ -25,5 +25,5 @@ class ErrorCode extends AbstractConstants | ||||
|     /** | ||||
|      * @Message("Something went wrong!") | ||||
|      */ | ||||
|     public const COMMON_ERROR = 1000000; | ||||
|     public const COMMON_ERROR = 1200000; | ||||
| } | ||||
|  | ||||
| @ -13,5 +13,10 @@ class AuthErrorCode extends AbstractConstants | ||||
|     /** | ||||
|      * @Message("请传入微信Code") | ||||
|      */ | ||||
|     public const CODE_EMPTY_ERROR = 2000001; | ||||
|     public const CODE_EMPTY_ERROR = 1200001; | ||||
| 
 | ||||
|     /** | ||||
|      * @Message("授权失败") | ||||
|      */ | ||||
|     public const CODE_TO_AUTH_FAIL = 1200002; | ||||
| } | ||||
							
								
								
									
										119
									
								
								app/Controller/PunchCard/User/UserController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								app/Controller/PunchCard/User/UserController.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,119 @@ | ||||
| <?php | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace App\Controller\PunchCard\User; | ||||
| 
 | ||||
| use App\Controller\BaseController; | ||||
| use App\JsonRpc\PunchCardSystemExternalServiceInterface; | ||||
| use App\JsonRpc\UserExternalServiceInterface; | ||||
| use App\Middleware\AuthMiddleware; | ||||
| use Hyperf\Di\Annotation\Inject; | ||||
| use Hyperf\HttpServer\Annotation\Controller; | ||||
| use Hyperf\HttpServer\Annotation\GetMapping; | ||||
| use Hyperf\HttpServer\Annotation\Middleware; | ||||
| use Hyperf\HttpServer\Annotation\PostMapping; | ||||
| 
 | ||||
| #[Middleware(AuthMiddleware::class)]
 | ||||
| #[Controller(prefix: "kq")]
 | ||||
| class UserController extends BaseController | ||||
| { | ||||
|     /** | ||||
|      * 用户中心对外RPC服务 | ||||
|      * | ||||
|      * @var UserExternalServiceInterface | ||||
|      */ | ||||
|     #[Inject]
 | ||||
|     protected UserExternalServiceInterface $userExternalService; | ||||
| 
 | ||||
|     /** | ||||
|      * 考勤系统设置对外RPC服务 | ||||
|      * | ||||
|      * @var PunchCardSystemExternalServiceInterface | ||||
|      */ | ||||
|     #[Inject]
 | ||||
|     protected PunchCardSystemExternalServiceInterface $punchCardSystemExternalService; | ||||
| 
 | ||||
|     /** | ||||
|      * 获取个人资料 | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     #[GetMapping(path: "user/information")]
 | ||||
|     public function information() : array | ||||
|     { | ||||
|         $user = $this->request->getAttribute('AuthUser'); | ||||
|         return $this->getServiceResult($this->userExternalService->getUserInfo($user['openid'], [ | ||||
|             'user_name', | ||||
|             'user_phone', | ||||
|         ])); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 保存个人资料 | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     #[PostMapping(path: "user/save_information")]
 | ||||
|     public function saveInformation() : void | ||||
|     { | ||||
|         $phone = $this->request->input('phone', ''); | ||||
|         // TODO 逻辑完善
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取问题反馈类型列表 | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     #[GetMapping(path: "user/feedback_type")]
 | ||||
|     public function getFeedbackTypeList() : array | ||||
|     { | ||||
|         return $this->getServiceResult($this->punchCardSystemExternalService->getFeedbackTypeList()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 保存问题反馈 | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     #[PostMapping(path: "user/save_feedback")]
 | ||||
|     public function saveFeedback() : array | ||||
|     { | ||||
|         $user = $this->request->getAttribute('AuthUser'); | ||||
|         return $this->getServiceResult($this->punchCardSystemExternalService->saveFeedback($this->request, $user['user_id'])); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取紧急联系人关系列表 | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     #[GetMapping(path: "user/emergency_contact_kinship")]
 | ||||
|     public function getEmergencyContactKinshipList() : array | ||||
|     { | ||||
|         return $this->getServiceResult($this->userExternalService->getEmergencyContactKinshipList()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 保存紧急联系人 | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     #[PostMapping(path: "user/add_emergency_contact")]
 | ||||
|     public function addEmergencyContact() : array | ||||
|     { | ||||
|         return $this->getServiceResult($this->userExternalService->addEmergencyContact($this->request)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 测试打印JWT认证信息 | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     #[GetMapping(path: "user/test")]
 | ||||
|     public function test() : array | ||||
|     { | ||||
|         return $this->request->getAttribute('AuthUser'); | ||||
|     } | ||||
| } | ||||
| @ -2,24 +2,33 @@ | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace App\Controller\PunchCard\Wechat; | ||||
| namespace App\Controller\PunchCard\User; | ||||
| 
 | ||||
| use App\Controller\BaseController; | ||||
| use App\Exception\BusinessException; | ||||
| use app\Constants\Wechat\AuthErrorCode; | ||||
| use App\Service\Wechat\AuthService; | ||||
| use App\Middleware\AuthMiddleware; | ||||
| use App\Service\User\WechatAuthService; | ||||
| use Hyperf\Di\Annotation\Inject; | ||||
| use Hyperf\HttpServer\Annotation\Controller; | ||||
| use Hyperf\HttpServer\Annotation\GetMapping; | ||||
| use Hyperf\HttpServer\Annotation\Middleware; | ||||
| use Hyperf\HttpServer\Annotation\RequestMapping; | ||||
| use Phper666\JWTAuth\Middleware\JWTAuthDefaultSceneMiddleware; | ||||
| 
 | ||||
| /** | ||||
|  * 微信授权处理控制器 | ||||
|  */ | ||||
| #[Controller(prefix: "kq")]
 | ||||
| class AuthController extends BaseController | ||||
| class WechatAuthController extends BaseController | ||||
| { | ||||
|     /** | ||||
|      * 微信授权服务 | ||||
|      * | ||||
|      * @var WechatAuthService | ||||
|      */ | ||||
|     #[Inject]
 | ||||
|     protected AuthService $service; | ||||
|     protected WechatAuthService $service; | ||||
| 
 | ||||
|     /** | ||||
|      * 通过微信授权新建用户 | ||||
| @ -37,4 +46,16 @@ class AuthController extends BaseController | ||||
| 
 | ||||
|         return $this->service->codeToOpenID($code); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 刷新token | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     #[Middleware(AuthMiddleware::class)]
 | ||||
|     #[GetMapping(path: "auth/refresh_token")]
 | ||||
|     public function refreshToken() : array | ||||
|     { | ||||
|         return $this->service->refreshToken(); | ||||
|     } | ||||
| } | ||||
| @ -6,6 +6,7 @@ namespace App\Controller; | ||||
| 
 | ||||
| use App\Exception\BusinessException; | ||||
| use App\JsonRpc\UserExternalServiceInterface; | ||||
| use App\Service\User\WechatAuthService; | ||||
| use Hyperf\Di\Annotation\Inject; | ||||
| use Hyperf\HttpServer\Annotation\AutoController; | ||||
| use Hyperf\Utils\Collection; | ||||
| @ -28,7 +29,7 @@ class TestController extends BaseController | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function index() | ||||
|     public function index() : array | ||||
|     { | ||||
|         $data['user_id'] = 'test'; | ||||
|         return $data; | ||||
| @ -39,7 +40,7 @@ class TestController extends BaseController | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function empty() | ||||
|     public function empty() : void | ||||
|     {} | ||||
| 
 | ||||
|     /** | ||||
| @ -47,7 +48,7 @@ class TestController extends BaseController | ||||
|      * | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function error() | ||||
|     public function error(): mixed | ||||
|     { | ||||
|         throw new BusinessException(500, 'error'); | ||||
|     } | ||||
| @ -57,7 +58,7 @@ class TestController extends BaseController | ||||
|      * | ||||
|      * @return Paginator | ||||
|      */ | ||||
|     public function page() | ||||
|     public function page() : Paginator | ||||
|     { | ||||
|         $currentPage = (int) $this->request->input('page', 1); | ||||
|         $perPage = (int) $this->request->input('per_page', 2); | ||||
| @ -76,12 +77,20 @@ class TestController extends BaseController | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * RPC调用示例 | ||||
|      * 获取token(用于测试) | ||||
|      * | ||||
|      * @param WechatAuthService $wechatAuthService | ||||
|      * @return array | ||||
|      */ | ||||
|     public function user_info() | ||||
|     public function token(WechatAuthService $wechatAuthService) : array | ||||
|     { | ||||
|         return $this->getServiceResult($this->userService->getUserInfo()); | ||||
|         $openid = $this->request->input('openid', '1111111'); | ||||
|         $user = $this->getServiceResult($this->userService->getUserInfo($openid, ['user_id', 'user_nickname'])); | ||||
| 
 | ||||
|         if (!empty($user)) { | ||||
|             return $wechatAuthService->getToken($user['user_id'], $user['user_nickname'], $openid); | ||||
|         } | ||||
| 
 | ||||
|         return []; | ||||
|     } | ||||
| } | ||||
| @ -11,6 +11,8 @@ declare(strict_types=1); | ||||
|  */ | ||||
| namespace App\Exception\Handler; | ||||
| 
 | ||||
| use App\Constants\ErrorCode; | ||||
| use App\Helper\Result; | ||||
| use Hyperf\Contract\StdoutLoggerInterface; | ||||
| use Hyperf\ExceptionHandler\ExceptionHandler; | ||||
| use Hyperf\HttpMessage\Stream\SwooleStream; | ||||
| @ -25,9 +27,13 @@ class AppExceptionHandler extends ExceptionHandler | ||||
| 
 | ||||
|     public function handle(Throwable $throwable, ResponseInterface $response) | ||||
|     { | ||||
|         $this->logger->error(sprintf('%s[%s] in %s', $throwable->getMessage(), $throwable->getLine(), $throwable->getFile())); | ||||
|         $this->logger->error($throwable->getTraceAsString()); | ||||
|         return $response->withHeader('Server', 'Hyperf')->withStatus(500)->withBody(new SwooleStream('Internal Server Error.')); | ||||
|         $message = $throwable->getMessage(); | ||||
|         $error = explode('|', $message, 2); | ||||
|         $code = $error[1] ?? $throwable->getCode() ?: ErrorCode::COMMON_ERROR; | ||||
|         $data = Result::error($code, $error[0]); | ||||
|         $responseStr = json_encode($data, JSON_UNESCAPED_UNICODE); | ||||
| 
 | ||||
|         return $response->withHeader('Content-Type', 'application/json')->withBody(new SwooleStream($responseStr)); | ||||
|     } | ||||
| 
 | ||||
|     public function isValid(Throwable $throwable): bool | ||||
|  | ||||
| @ -4,6 +4,7 @@ declare(strict_types=1); | ||||
| 
 | ||||
| namespace App\Helper; | ||||
| 
 | ||||
| use App\Exception\BusinessException; | ||||
| use GuzzleHttp\Client; | ||||
| use Hyperf\Guzzle\HandlerStackFactory; | ||||
| 
 | ||||
| @ -35,7 +36,6 @@ class Curl | ||||
|      * @param string $url | ||||
|      * @param array $headers | ||||
|      * @return array | ||||
|      * @throws \GuzzleHttp\Exception\GuzzleException | ||||
|      */ | ||||
|     public static function get(string $url, array $headers = []) : array | ||||
|     { | ||||
| @ -46,7 +46,11 @@ class Curl | ||||
|             $options['headers'] = $headers; | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             $response = $client->get($url, $options); | ||||
|         } catch (\Throwable $throwable) { | ||||
|             throw new BusinessException($throwable->getCode(), $throwable->getMessage()); | ||||
|         } | ||||
| 
 | ||||
|         if ($response->getStatusCode() === 200) { | ||||
|             return json_decode($response->getBody()->getContents(), true); | ||||
| @ -62,7 +66,6 @@ class Curl | ||||
|      * @param array $data | ||||
|      * @param array $headers | ||||
|      * @return array | ||||
|      * @throws \GuzzleHttp\Exception\GuzzleException | ||||
|      */ | ||||
|     public static function post(string $url, array $data = [], array $headers = []) : array | ||||
|     { | ||||
| @ -73,7 +76,11 @@ class Curl | ||||
|             $options['headers'] = $headers; | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             $response = $client->post($url, $options); | ||||
|         } catch (\Throwable $throwable) { | ||||
|             throw new BusinessException($throwable->getCode(), $throwable->getMessage()); | ||||
|         } | ||||
| 
 | ||||
|         if ($response->getStatusCode() === 200) { | ||||
|             return json_decode($response->getBody()->getContents(), true); | ||||
|  | ||||
							
								
								
									
										36
									
								
								app/JsonRpc/PunchCardSystemExternalServiceConsumer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								app/JsonRpc/PunchCardSystemExternalServiceConsumer.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\JsonRpc; | ||||
| 
 | ||||
| use Hyperf\RpcClient\AbstractServiceClient; | ||||
| use Hyperf\HttpServer\Contract\RequestInterface; | ||||
| 
 | ||||
| class PunchCardSystemExternalServiceConsumer extends AbstractServiceClient implements PunchCardSystemExternalServiceInterface | ||||
| { | ||||
|     /** | ||||
|      * 定义对应服务提供者的服务名称 | ||||
|      * @var string | ||||
|      */ | ||||
|     protected $serviceName = 'PunchCardSystemExternalService'; | ||||
| 
 | ||||
|     /** | ||||
|      * 获取问题反馈类型列表 | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function getFeedbackTypeList() : array | ||||
|     { | ||||
|         return $this->__request(__FUNCTION__, []); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 保存问题反馈 | ||||
|      * | ||||
|      * @param RequestInterface $request | ||||
|      * @return array | ||||
|      */ | ||||
|     public function saveFeedback(RequestInterface $request) : array | ||||
|     { | ||||
|         return $this->__request(__FUNCTION__, [...$request->all(), ...['user_id' => $request->getAttribute('AuthUser')['user_id']]]); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										23
									
								
								app/JsonRpc/PunchCardSystemExternalServiceInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								app/JsonRpc/PunchCardSystemExternalServiceInterface.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\JsonRpc; | ||||
| 
 | ||||
| use Hyperf\HttpServer\Contract\RequestInterface; | ||||
| 
 | ||||
| interface PunchCardSystemExternalServiceInterface | ||||
| { | ||||
|     /** | ||||
|      * 获取问题反馈类型列表 | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function getFeedbackTypeList() : array; | ||||
| 
 | ||||
|     /** | ||||
|      * 保存问题反馈 | ||||
|      * | ||||
|      * @param RequestInterface $request | ||||
|      * @return array | ||||
|      */ | ||||
|     public function saveFeedback(RequestInterface $request) : array; | ||||
| } | ||||
							
								
								
									
										59
									
								
								app/JsonRpc/UserExternalServiceConsumer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								app/JsonRpc/UserExternalServiceConsumer.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\JsonRpc; | ||||
| 
 | ||||
| use Hyperf\RpcClient\AbstractServiceClient; | ||||
| use Hyperf\HttpServer\Contract\RequestInterface; | ||||
| 
 | ||||
| class UserExternalServiceConsumer extends AbstractServiceClient implements UserExternalServiceInterface | ||||
| { | ||||
|     /** | ||||
|      * 定义对应服务提供者的服务名称 | ||||
|      * @var string | ||||
|      */ | ||||
|     protected $serviceName = 'UserExternalService'; | ||||
| 
 | ||||
|     /** | ||||
|      * 获取用户信息 | ||||
|      * | ||||
|      * @param string $openid | ||||
|      * @param array $field | ||||
|      * @return array | ||||
|      */ | ||||
|     public function getUserInfo(string $openid, array $field = []) : array | ||||
|     { | ||||
|         return $this->__request(__FUNCTION__, compact('openid', 'field')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 通过微信授权新建用户 | ||||
|      * | ||||
|      * @param string $openid | ||||
|      * @return array | ||||
|      */ | ||||
|     public function wechatNewUser(string $openid) : array | ||||
|     { | ||||
|         return $this->__request(__FUNCTION__, compact('openid')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取紧急联系人关系列表 | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function getEmergencyContactKinshipList() : array | ||||
|     { | ||||
|         return $this->__request(__FUNCTION__, []); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 保存紧急联系人 | ||||
|      * | ||||
|      * @param RequestInterface $request | ||||
|      * @return array | ||||
|      */ | ||||
|     public function addEmergencyContact(RequestInterface $request) : array | ||||
|     { | ||||
|         return $this->__request(__FUNCTION__, $request->all()); | ||||
|     } | ||||
| } | ||||
| @ -2,15 +2,18 @@ | ||||
| 
 | ||||
| namespace App\JsonRpc; | ||||
| 
 | ||||
| use Hyperf\HttpServer\Contract\RequestInterface; | ||||
| 
 | ||||
| interface UserExternalServiceInterface | ||||
| { | ||||
|     /** | ||||
|      * 获取用户信息 | ||||
|      * | ||||
|      * @param string $name | ||||
|      * @param string $openid | ||||
|      * @param array $field | ||||
|      * @return array | ||||
|      */ | ||||
|     public function getUserInfo(string $name = 'test') : array; | ||||
|     public function getUserInfo(string $openid, array $field = []) : array; | ||||
| 
 | ||||
|     /** | ||||
|      * 通过微信授权新建用户 | ||||
| @ -19,4 +22,19 @@ interface UserExternalServiceInterface | ||||
|      * @return array | ||||
|      */ | ||||
|     public function wechatNewUser(string $openid) : array; | ||||
| 
 | ||||
|     /** | ||||
|      * 获取紧急联系人关系列表 | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function getEmergencyContactKinshipList() : array; | ||||
| 
 | ||||
|     /** | ||||
|      * 保存紧急联系人 | ||||
|      * | ||||
|      * @param RequestInterface $request | ||||
|      * @return array | ||||
|      */ | ||||
|     public function addEmergencyContact(RequestInterface $request) : array; | ||||
| } | ||||
							
								
								
									
										53
									
								
								app/Middleware/AuthMiddleware.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								app/Middleware/AuthMiddleware.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | ||||
| <?php | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace App\Middleware; | ||||
| 
 | ||||
| use App\Constants\ErrorCode; | ||||
| use Hyperf\Context\Context; | ||||
| use Hyperf\HttpServer\Contract\ResponseInterface as HttpResponse; | ||||
| use Phper666\JWTAuth\Exception\JWTException; | ||||
| use Phper666\JWTAuth\Util\JWTUtil; | ||||
| use Psr\Http\Message\ResponseInterface; | ||||
| use Psr\Http\Message\ServerRequestInterface; | ||||
| use Psr\Http\Server\MiddlewareInterface; | ||||
| use Psr\Http\Server\RequestHandlerInterface; | ||||
| use Phper666\JWTAuth\JWT; | ||||
| use Phper666\JWTAuth\Exception\TokenValidException; | ||||
| 
 | ||||
| /** | ||||
|  * jwt token 校验的中间件,校验场景是否一致 | ||||
|  */ | ||||
| class AuthMiddleware implements MiddlewareInterface | ||||
| { | ||||
|     public function __construct(protected HttpResponse $response, protected JWT $jwt) | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param ServerRequestInterface  $request | ||||
|      * @param RequestHandlerInterface $handler | ||||
|      * @return ResponseInterface | ||||
|      * @throws \Psr\SimpleCache\InvalidArgumentException | ||||
|      * @throws \Throwable | ||||
|      */ | ||||
|     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface | ||||
|     { | ||||
|         $token = $request->getHeaderLine('Authorization') ?? ''; | ||||
|         if ($token === "") { | ||||
|             throw new JWTException('Missing token', ErrorCode::COMMON_ERROR); | ||||
|         } | ||||
| 
 | ||||
|         $token = JWTUtil::handleToken($token); | ||||
|         if ($token !== false && $this->jwt->verifyTokenAndScene('default', $token)) { | ||||
|             // 封装认证用户信息
 | ||||
|             $request = $request->withAttribute('AuthUser', JWTUtil::getParserData($request)); | ||||
|             Context::set(ServerRequestInterface::class, $request); | ||||
| 
 | ||||
|             return $handler->handle($request); | ||||
|         } | ||||
| 
 | ||||
|         throw new TokenValidException('Token authentication does not pass', ErrorCode::COMMON_ERROR); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										133
									
								
								app/Service/User/WechatAuthService.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								app/Service/User/WechatAuthService.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,133 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Service\User; | ||||
| 
 | ||||
| use App\Constants\Wechat\AuthErrorCode; | ||||
| use App\Exception\BusinessException; | ||||
| use App\Helper\Curl; | ||||
| use App\JsonRpc\UserExternalServiceInterface; | ||||
| use Hyperf\Config\Annotation\Value; | ||||
| use Hyperf\Di\Annotation\Inject; | ||||
| use Phper666\JWTAuth\JWT; | ||||
| use Psr\SimpleCache\InvalidArgumentException; | ||||
| 
 | ||||
| /** | ||||
|  * 微信授权处理服务层 | ||||
|  */ | ||||
| class WechatAuthService | ||||
| { | ||||
|     /** | ||||
|      * 考勤系统小程序APPID | ||||
|      * | ||||
|      * @var string | ||||
|      */ | ||||
|     #[Value("app.wechat_punch_card_appid")]
 | ||||
|     protected string $appid; | ||||
| 
 | ||||
|     /** | ||||
|      * 考勤系统小程序APPSecret | ||||
|      * | ||||
|      * @var string | ||||
|      */ | ||||
|     #[Value("app.wechat_punch_card_secret")]
 | ||||
|     protected string $secret; | ||||
| 
 | ||||
|     /** | ||||
|      * 用户中心对外RPC服务 | ||||
|      * | ||||
|      * @var UserExternalServiceInterface | ||||
|      */ | ||||
|     #[Inject]
 | ||||
|     protected UserExternalServiceInterface $userExternalService; | ||||
| 
 | ||||
|     /** | ||||
|      * JWT认证组件 | ||||
|      * | ||||
|      * @var JWT | ||||
|      */ | ||||
|     #[Inject]
 | ||||
|     protected JWT $jwt; | ||||
| 
 | ||||
|     /** | ||||
|      * 微信通过code获取session信息请求地址 | ||||
|      * | ||||
|      * @var string | ||||
|      */ | ||||
|     protected string $wechat_auth_url = 'https://api.weixin.qq.com/sns/jscode2session'; | ||||
| 
 | ||||
|     /** | ||||
|      * 通过微信授权新建用户 | ||||
|      * | ||||
|      * @param string $code | ||||
|      * @return array | ||||
|      */ | ||||
|     public function codeToOpenID(string $code) : array | ||||
|     { | ||||
|         $url = $this->wechat_auth_url . '?' . http_build_query([ | ||||
|             'appid' => $this->appid, | ||||
|             'secret' => $this->secret, | ||||
|             'js_code' => $code, | ||||
|             'grant_type' => 'authorization_code' | ||||
|         ]); | ||||
| 
 | ||||
|         $result = Curl::get($url); | ||||
|         $res = $this->userExternalService->wechatNewUser($result['data']['openid']); | ||||
| 
 | ||||
|         if (!$res['code'] !== 200) { | ||||
|             throw new BusinessException($res['code'], $res['msg']); | ||||
|         } | ||||
| 
 | ||||
|         if (empty($res['data']['user'])) { | ||||
|             throw new BusinessException(AuthErrorCode::CODE_TO_AUTH_FAIL); | ||||
|         } | ||||
| 
 | ||||
|         return $this->getToken($res['data']['user']['user_id'], $res['data']['user']['user_nickname'], $res['data']['user']['user_openid']); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取JWT认证token | ||||
|      * | ||||
|      * @param int $user_id | ||||
|      * @param string $nickname | ||||
|      * @param string $openid | ||||
|      * @return array | ||||
|      */ | ||||
|     public function getToken(int $user_id, string $nickname, string $openid) | ||||
|     { | ||||
|         try { | ||||
|             $token = $this->jwt->getToken('default', [ | ||||
|                 'user_id' => $user_id, | ||||
|                 'nickname' => $nickname, | ||||
|                 'openid' => $openid | ||||
|             ]); | ||||
|         } catch (InvalidArgumentException) { | ||||
|             // TODO 记录日志
 | ||||
|             throw new BusinessException(AuthErrorCode::CODE_TO_AUTH_FAIL); | ||||
|         } | ||||
| 
 | ||||
|         return [ | ||||
|             'token' => $token->toString(), | ||||
|             'exp' => $this->jwt->getTTL($token->toString()) | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 刷新token | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function refreshToken() : array | ||||
|     { | ||||
|         try { | ||||
|             $token = $this->jwt->refreshToken(); | ||||
|         } catch (InvalidArgumentException) { | ||||
|             // TODO 记录日志
 | ||||
|             throw new BusinessException(AuthErrorCode::CODE_TO_AUTH_FAIL); | ||||
|         } | ||||
| 
 | ||||
|         return [ | ||||
|             'token' => $token->toString(), | ||||
|             'exp' => $this->jwt->getTTL($token->toString()) | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
| @ -1,71 +0,0 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Service\Wechat; | ||||
| 
 | ||||
| use App\Exception\BusinessException; | ||||
| use App\Helper\Curl; | ||||
| use App\JsonRpc\UserExternalServiceInterface; | ||||
| use Hyperf\Config\Annotation\Value; | ||||
| use Hyperf\Di\Annotation\Inject; | ||||
| 
 | ||||
| /** | ||||
|  * 微信授权处理服务层 | ||||
|  */ | ||||
| class AuthService | ||||
| { | ||||
|     /** | ||||
|      * 考勤系统小程序APPID | ||||
|      * | ||||
|      * @var string | ||||
|      */ | ||||
|     #[Value("app.wechat_punch_card_appid")]
 | ||||
|     protected string $appid; | ||||
| 
 | ||||
|     /** | ||||
|      * 考勤系统小程序APPSecret | ||||
|      * | ||||
|      * @var string | ||||
|      */ | ||||
|     #[Value("app.wechat_punch_card_secret")]
 | ||||
|     protected string $secret; | ||||
| 
 | ||||
|     /** | ||||
|      * 用户中心对外RPC服务 | ||||
|      * | ||||
|      * @var UserExternalServiceInterface | ||||
|      */ | ||||
|     #[Inject]
 | ||||
|     protected UserExternalServiceInterface $userExternalService; | ||||
| 
 | ||||
|     /** | ||||
|      * 微信通过code获取session信息请求地址 | ||||
|      * | ||||
|      * @var string | ||||
|      */ | ||||
|     protected string $wechat_auth_url = 'https://api.weixin.qq.com/sns/jscode2session'; | ||||
| 
 | ||||
|     /** | ||||
|      * 通过微信授权新建用户 | ||||
|      * | ||||
|      * @param string $code | ||||
|      * @return array | ||||
|      */ | ||||
|     public function codeToOpenID(string $code) : array | ||||
|     { | ||||
|         $url = $this->wechat_auth_url . '?' . http_build_query([ | ||||
|             'appid' => $this->appid, | ||||
|             'secret' => $this->secret, | ||||
|             'js_code' => $code, | ||||
|             'grant_type' => 'authorization_code' | ||||
|         ]); | ||||
| 
 | ||||
|         $result = Curl::get($url); | ||||
|         $res = $this->userExternalService->wechatNewUser($result['data']['openid']); | ||||
| 
 | ||||
|         if (!$res['code'] !== 200) { | ||||
|             throw new BusinessException($res['code'], $res['msg']); | ||||
|         } | ||||
| 
 | ||||
|         return ['openid' => $result['data']['openid']]; | ||||
|     } | ||||
| } | ||||
| @ -34,7 +34,8 @@ | ||||
|         "hyperf/rpc-client": "~2.2.0", | ||||
|         "hyperf/rpc-server": "~2.2.0", | ||||
|         "hyperf/service-governance-consul": "~2.2.0", | ||||
|         "hyperf/validation": "^2.2" | ||||
|         "hyperf/validation": "^2.2", | ||||
|         "phper666/jwt-auth": "~4.0.0" | ||||
|     }, | ||||
|     "require-dev": { | ||||
|         "friendsofphp/php-cs-fixer": "^3.0", | ||||
|  | ||||
							
								
								
									
										184
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										184
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							| @ -4,7 +4,7 @@ | ||||
|         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", | ||||
|         "This file is @generated automatically" | ||||
|     ], | ||||
|     "content-hash": "7cc40bd39d6d590a4e06bfb1d56c0abb", | ||||
|     "content-hash": "7f8915491bc01099466689096453ac63", | ||||
|     "packages": [ | ||||
|         { | ||||
|             "name": "doctrine/annotations", | ||||
| @ -2515,6 +2515,101 @@ | ||||
|             ], | ||||
|             "time": "2022-10-10T19:10:24+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "lcobucci/clock", | ||||
|             "version": "2.2.0", | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://mirrors.cloud.tencent.com/repository/composer/lcobucci/clock/2.2.0/lcobucci-clock-2.2.0.zip", | ||||
|                 "reference": "fb533e093fd61321bfcbac08b131ce805fe183d3", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
|                 "php": "^8.0", | ||||
|                 "stella-maris/clock": "^0.1.4" | ||||
|             }, | ||||
|             "require-dev": { | ||||
|                 "infection/infection": "^0.26", | ||||
|                 "lcobucci/coding-standard": "^8.0", | ||||
|                 "phpstan/extension-installer": "^1.1", | ||||
|                 "phpstan/phpstan": "^0.12", | ||||
|                 "phpstan/phpstan-deprecation-rules": "^0.12", | ||||
|                 "phpstan/phpstan-phpunit": "^0.12", | ||||
|                 "phpstan/phpstan-strict-rules": "^0.12", | ||||
|                 "phpunit/phpunit": "^9.5" | ||||
|             }, | ||||
|             "type": "library", | ||||
|             "autoload": { | ||||
|                 "psr-4": { | ||||
|                     "Lcobucci\\Clock\\": "src" | ||||
|                 } | ||||
|             }, | ||||
|             "license": [ | ||||
|                 "MIT" | ||||
|             ], | ||||
|             "authors": [ | ||||
|                 { | ||||
|                     "name": "Luís Cobucci", | ||||
|                     "email": "lcobucci@gmail.com" | ||||
|                 } | ||||
|             ], | ||||
|             "description": "Yet another clock abstraction", | ||||
|             "time": "2022-04-19T19:34:17+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "lcobucci/jwt", | ||||
|             "version": "4.1.5", | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://mirrors.cloud.tencent.com/repository/composer/lcobucci/jwt/4.1.5/lcobucci-jwt-4.1.5.zip", | ||||
|                 "reference": "fe2d89f2eaa7087af4aa166c6f480ef04e000582", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
|                 "ext-hash": "*", | ||||
|                 "ext-json": "*", | ||||
|                 "ext-mbstring": "*", | ||||
|                 "ext-openssl": "*", | ||||
|                 "ext-sodium": "*", | ||||
|                 "lcobucci/clock": "^2.0", | ||||
|                 "php": "^7.4 || ^8.0" | ||||
|             }, | ||||
|             "require-dev": { | ||||
|                 "infection/infection": "^0.21", | ||||
|                 "lcobucci/coding-standard": "^6.0", | ||||
|                 "mikey179/vfsstream": "^1.6.7", | ||||
|                 "phpbench/phpbench": "^1.0", | ||||
|                 "phpstan/extension-installer": "^1.0", | ||||
|                 "phpstan/phpstan": "^0.12", | ||||
|                 "phpstan/phpstan-deprecation-rules": "^0.12", | ||||
|                 "phpstan/phpstan-phpunit": "^0.12", | ||||
|                 "phpstan/phpstan-strict-rules": "^0.12", | ||||
|                 "phpunit/php-invoker": "^3.1", | ||||
|                 "phpunit/phpunit": "^9.5" | ||||
|             }, | ||||
|             "type": "library", | ||||
|             "autoload": { | ||||
|                 "psr-4": { | ||||
|                     "Lcobucci\\JWT\\": "src" | ||||
|                 } | ||||
|             }, | ||||
|             "license": [ | ||||
|                 "BSD-3-Clause" | ||||
|             ], | ||||
|             "authors": [ | ||||
|                 { | ||||
|                     "name": "Luís Cobucci", | ||||
|                     "email": "lcobucci@gmail.com", | ||||
|                     "role": "Developer" | ||||
|                 } | ||||
|             ], | ||||
|             "description": "A simple library to work with JSON Web Token and JSON Web Signature", | ||||
|             "keywords": [ | ||||
|                 "JWS", | ||||
|                 "jwt" | ||||
|             ], | ||||
|             "time": "2021-09-28T19:34:56+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "markrogoyski/math-php", | ||||
|             "version": "v2.6.0", | ||||
| @ -3020,6 +3115,56 @@ | ||||
|             ], | ||||
|             "time": "2020-10-12T12:39:22+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "phper666/jwt-auth", | ||||
|             "version": "v4.0.7", | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://mirrors.cloud.tencent.com/repository/composer/phper666/jwt-auth/v4.0.7/phper666-jwt-auth-v4.0.7.zip", | ||||
|                 "reference": "c42e1ef968d300e7e74e449e5e93cda7d22de33f", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
|                 "ext-swoole": ">=4.4", | ||||
|                 "lcobucci/jwt": "4.1.5", | ||||
|                 "nesbot/carbon": "^1.0 || ^2.0", | ||||
|                 "php": ">=7.4" | ||||
|             }, | ||||
|             "suggest": { | ||||
|                 "hyperf/cache": "required hyperf/cache ~2.2.0", | ||||
|                 "hyperf/command": "required hyperf/command ~2.2.0", | ||||
|                 "hyperf/config": "required hyperf/config ~2.2.0", | ||||
|                 "hyperf/di": "required hyperf/di ~2.2.0" | ||||
|             }, | ||||
|             "type": "library", | ||||
|             "extra": { | ||||
|                 "hyperf": { | ||||
|                     "config": "Phper666\\JWTAuth\\ConfigProvider" | ||||
|                 } | ||||
|             }, | ||||
|             "autoload": { | ||||
|                 "psr-4": { | ||||
|                     "Phper666\\JWTAuth\\": "src/" | ||||
|                 } | ||||
|             }, | ||||
|             "license": [ | ||||
|                 "MIT" | ||||
|             ], | ||||
|             "authors": [ | ||||
|                 { | ||||
|                     "name": "Li Yuzhao", | ||||
|                     "email": "562405704@qq.com", | ||||
|                     "homepage": "https://github.com/phper666/jwt-auth", | ||||
|                     "role": "Developer" | ||||
|                 } | ||||
|             ], | ||||
|             "description": "jwt-auth Component", | ||||
|             "keywords": [ | ||||
|                 "hyperf", | ||||
|                 "php" | ||||
|             ], | ||||
|             "time": "2022-04-19T11:54:50+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "phpoption/phpoption", | ||||
|             "version": "1.9.0", | ||||
| @ -3628,6 +3773,43 @@ | ||||
|             "description": "A polyfill for getallheaders.", | ||||
|             "time": "2019-03-08T08:55:37+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "stella-maris/clock", | ||||
|             "version": "0.1.6", | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://mirrors.cloud.tencent.com/repository/composer/stella-maris/clock/0.1.6/stella-maris-clock-0.1.6.zip", | ||||
|                 "reference": "a94228dac03c9a8411198ce8c8dacbbe99c930c3", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
|                 "php": "^7.0|^8.0" | ||||
|             }, | ||||
|             "type": "library", | ||||
|             "autoload": { | ||||
|                 "psr-4": { | ||||
|                     "StellaMaris\\Clock\\": "src" | ||||
|                 } | ||||
|             }, | ||||
|             "license": [ | ||||
|                 "MIT" | ||||
|             ], | ||||
|             "authors": [ | ||||
|                 { | ||||
|                     "name": "Andreas Heigl", | ||||
|                     "role": "Maintainer" | ||||
|                 } | ||||
|             ], | ||||
|             "description": "A pre-release of the proposed PSR-20 Clock-Interface", | ||||
|             "homepage": "https://gitlab.com/stella-maris/clock", | ||||
|             "keywords": [ | ||||
|                 "clock", | ||||
|                 "datetime", | ||||
|                 "point in time", | ||||
|                 "psr20" | ||||
|             ], | ||||
|             "time": "2022-09-27T15:03:11+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/console", | ||||
|             "version": "v5.4.11", | ||||
|  | ||||
| @ -14,4 +14,6 @@ use Hyperf\JsonRpc\JsonRpcTransporter; | ||||
| 
 | ||||
| return [ | ||||
|     JsonRpcTransporter::class => JsonRpcPoolTransporter::class, | ||||
|     App\JsonRpc\UserExternalServiceInterface::class => App\JsonRpc\UserExternalServiceConsumer::class, | ||||
|     App\JsonRpc\PunchCardSystemExternalServiceInterface::class => App\JsonRpc\PunchCardSystemExternalServiceConsumer::class, | ||||
| ]; | ||||
|  | ||||
							
								
								
									
										93
									
								
								config/autoload/jwt.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								config/autoload/jwt.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | ||||
| <?php | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| return [ | ||||
|     /** | ||||
|      * 不需要检查的路由,如果使用jwt提供的默认中间件,可以对某些不用做检验的路由进行配置,例如登录等 | ||||
|      * 具体的逻辑可以效仿JWT提供的默认中间件 | ||||
|      * [ | ||||
|      *      ["GET", "/index/test"], | ||||
|      *      ["**", "/test"] | ||||
|      * ] | ||||
|      * | ||||
|      * 第一个填写请求方法('**'代表支持所有的请求方法),第二个填写路由路径('/**'代表支持所有的路径) | ||||
|      * 如果数组中存在["**", "/**"],则默认所有的请求路由都不做jwt token的校验,直接放行,如果no_check_route为一个空数组,则 | ||||
|      * 所有的请求路由都需要做jwt token校验 | ||||
|      * 路由路径支持正则的写法 | ||||
|      * 正则写法:["**", "/api/{name:.+}"]  支持模块化不做jwt token的校验,例如:/api/login/login | ||||
|      */ | ||||
|     'no_check_route' => [ | ||||
| //        ["**", "/**"],
 | ||||
|     ], | ||||
| 
 | ||||
|     'login_type' => env('JWT_LOGIN_TYPE', 'mpop'), //  登录方式,sso为单点登录,同一个用户只能登录一个端,mpop为多点登录
 | ||||
| 
 | ||||
|     /** | ||||
|      * 单点登录自定义数据中必须存在uid的键值,这个key你可以自行定义,只要自定义数据中存在该键即可 | ||||
|      */ | ||||
|     'sso_key' => 'user_id', | ||||
| 
 | ||||
|     /** | ||||
|      * 只能用于Hmac包下的加密非对称算法,其它的都会使用公私钥 | ||||
|      */ | ||||
|     'secret' => env('JWT_SECRET', 'juzhongGroup'), | ||||
| 
 | ||||
|     /** | ||||
|      * JWT 权限keys | ||||
|      * 对称算法: HS256, HS384 & HS512 使用 `JWT_SECRET`. | ||||
|      * 非对称算法: RS256, RS384 & RS512 / ES256, ES384 & ES512 使用下面的公钥私钥,需要自己去生成. | ||||
|      */ | ||||
|     'keys' => [ | ||||
|         'public' => env('JWT_PUBLIC_KEY'), // 公钥,例如:'file:///path/to/public/key'
 | ||||
|         'private' => env('JWT_PRIVATE_KEY'), // 私钥,例如:'file:///path/to/private/key'
 | ||||
| 
 | ||||
|         /** | ||||
|          * 你的私钥的密码。不需要密码可以不用设置 | ||||
|          */ | ||||
|         'passphrase' => env('JWT_PASSPHRASE'), | ||||
|     ], | ||||
| 
 | ||||
|     'ttl' => env('JWT_TTL', 7200), // token过期时间,单位为秒
 | ||||
| 
 | ||||
|     /** | ||||
|      * 支持的对称算法:HS256、HS384、HS512 | ||||
|      * 支持的非对称算法:RS256、RS384、RS512、ES256、ES384、ES512 | ||||
|      */ | ||||
|     'alg' => env('JWT_ALG', 'HS256'), // jwt的hearder加密算法
 | ||||
| 
 | ||||
|     /** | ||||
|      * jwt使用到的缓存前缀 | ||||
|      * 建议使用独立的redis做缓存,这样比较好做分布式 | ||||
|      */ | ||||
|     'cache_prefix' => 'juzhong:jwt', | ||||
| 
 | ||||
|     /** | ||||
|      * 是否开启黑名单,单点登录和多点登录的注销、刷新使原token失效,必须要开启黑名单,目前黑名单缓存只支持hyperf缓存驱动 | ||||
|      */ | ||||
|     'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true), | ||||
| 
 | ||||
|     /** | ||||
|      * 黑名单的宽限时间 单位为:秒,注意:如果使用单点登录,该宽限时间无效 | ||||
|      */ | ||||
|     'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 0), | ||||
| 
 | ||||
|     /** | ||||
|      * 签发者 | ||||
|      */ | ||||
|     'issued_by' => 'phper666/jwt', | ||||
| 
 | ||||
|     /** | ||||
|      * 区分不同场景的token,比如你一个项目可能会有多种类型的应用接口鉴权,下面自行定义,我只是举例子 | ||||
|      * 下面的配置会自动覆盖根配置,比如application1会里面的数据会覆盖掉根数据 | ||||
|      * 下面的scene会和根数据合并 | ||||
|      * scene必须存在一个default | ||||
|      * 什么叫根数据,这个配置的一维数组,除了scene都叫根配置 | ||||
|      */ | ||||
|     'scene' => [ | ||||
|         'default' => [ | ||||
|             'secret' => 'juzhongGroup', // 非对称加密使用字符串,请使用自己加密的字符串
 | ||||
|             'login_type' => 'mpop', //  登录方式,sso为单点登录,mpop为多点登录
 | ||||
|             'ttl' => 7200, // token过期时间,单位为秒
 | ||||
|         ] | ||||
|     ] | ||||
| ]; | ||||
| @ -18,7 +18,8 @@ return [ | ||||
|         $consumers = []; | ||||
|         // 这里示例自动创建代理消费者类的配置形式,顾存在 name 和 service 两个配置项,这里的做法不是唯一的,仅说明可以通过 PHP 代码来生成配置
 | ||||
|         $services = [ | ||||
|             'UserExternalService' => App\JsonRpc\UserExternalServiceInterface::class, | ||||
|             'UserExternalService' => '', | ||||
|             'PunchCardSystemExternalService' => '' | ||||
|         ]; | ||||
|         foreach ($services as $name => $interface) { | ||||
|             $consumers[] = [ | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user