| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 |
- <?php
- /**
- * 认证控制器.
- *
- * @author nj <nj@gmail.com>
- * @date 2020-01-22 09:37:54
- */
- declare (strict_types=1);
- namespace Modules\Api\Controllers;
- use App\Constants\AdConstrant;
- use App\Constants\CreatorConstant;
- use App\Events\Task\DailyLoginEvent;
- use App\Events\Task\FillCodeEvent;
- use App\Events\Task\FirstRegisterEvent;
- use App\Events\Task\InviteFriendsEvent;
- use App\Events\Task\TimeTaskEvent;
- use App\Events\User\UserLoginEvent;
- use App\Model\UserModel;
- use App\Model\VersionModel;
- use App\Model\VipOrderModel;
- use App\Repositories\BannedIpRepository;
- use App\Repositories\ConfigRepoistory;
- use App\Repositories\MessageRepository;
- use App\Repositories\Service\CommonService;
- use App\Repositories\Service\QueueService;
- use App\Repositories\ShopsRepository;
- use App\Repositories\UserRepository;
- use App\Repositories\Utils\JwtRepository;
- use App\Repositories\Utils\M3u8CacheRepository;
- use App\Repositories\Utils\RandomUserInfoRepository;
- use App\Repositories\VersionRepository;
- use Carbon\Carbon;
- use Hyperf\Validation\Contract\ValidatorFactoryInterface;
- use Lcobucci\JWT\UnencryptedToken;
- use Modules\Api\Services\AuthService;
- use Modules\Api\Services\VideoService;
- use Psr\SimpleCache\CacheInterface;
- class AuthController extends BaseController
- {
- use \Hyperf\Di\Aop\ProxyTrait;
- use \Hyperf\Di\Aop\PropertyHandlerTrait;
- function __construct()
- {
- if (method_exists(parent::class, '__construct')) {
- parent::__construct(...func_get_args());
- }
- $this->__handlePropertyHandler(__CLASS__);
- }
- /**
- * Jwt受众.
- *
- * @var string
- */
- private $jwtAud = 'app-users';
- /**
- * 设备登录并返回Token.
- * @param ValidatorFactoryInterface $validatorFactory
- * @param AuthService $authService
- * @return mixed
- * @throws \Psr\SimpleCache\InvalidArgumentException
- */
- public function loginByDevice(ValidatorFactoryInterface $validatorFactory, AuthService $authService)
- {
- // Json Body Raw获取
- $inputData = $this->getJsonData();
- // Demo 这是在控制器内校验器的写法.
- $rules = [
- 'code' => 'nullable',
- // 父级邀请人的UID
- 'token' => 'nullable',
- 'channel' => 'nullable',
- // 渠道UID
- 'version' => 'required',
- 'device_no' => 'required',
- 'device_type' => 'required|in:A,I,H',
- 'finger_hash' => 'nullable|string|max:32',
- 'self_sign' => 'nullable|string',
- // 苹果自签,yes=是的
- 'bundle_id' => 'nullable|string',
- ];
- $validator = $validatorFactory->make($inputData, $rules);
- if ($validator->fails()) {
- // 如果有错误(可能是多个信息),这里只取第一个错误信息返回.
- return json_fail($validator->errors()->first());
- }
- $clientIp = make(CommonService::class)->getIp();
- if (!filter_var($clientIp, FILTER_VALIDATE_IP)) {
- return json_fail('认证失败8');
- }
- $inputData['device_no'] = strval($inputData['device_no']);
- $hVersion = strval(data_get($inputData, 'version', ''));
- $hDeviceNo = data_get($inputData, 'device_no', '');
- $hDeviceType = data_get($inputData, 'device_type', '');
- $hFingerHash = strval(data_get($inputData, 'finger_hash', ''));
- $hSelfSign = strval(data_get($inputData, 'self_sign', ''));
- // 苹果自签,yes字符串
- $hBundleId = strval(data_get($inputData, 'bundle_id', ''));
- // 包名
- $cache = make(CacheInterface::class);
- $user = null;
- $auth = [];
- //有token的情况
- if (!empty($inputData['token'])) {
- $jwtRepository = make(JwtRepository::class);
- $validateResult = $jwtRepository->validateToken($inputData['token'], $this->jwtAud);
- if (!$validateResult['is_valid']) {
- if ($validateResult['is_expired']) {
- return json_fail('认证过期', 401);
- }
- return json_fail('认证失败1', 403);
- } else {
- // 验证单点.
- /**
- * @var UnencryptedToken $tokenObj
- */
- $tokenObj = $validateResult['token_obj'];
- $deviceNo = $tokenObj->claims()->get('device_no');
- $deviceType = $tokenObj->claims()->get('device_type');
- $fingerHash = $tokenObj->claims()->get('finger_hash');
- $cacheKey = $authService->getUserDeviceCacheKey($deviceNo, $deviceType);
- $cacheToken = $cache->has($cacheKey) ? $cache->get($cacheKey) : '';
- if (empty($cacheToken) || $cacheToken !== $inputData['token']) {
- return json_fail('认证失败2', 403);
- }
- if ($hDeviceNo !== $deviceNo || $hDeviceType != $deviceType) {
- return json_fail('认证失败3', 403);
- }
- $auth['token'] = $cacheToken;
- }
- }
- //没有token的情况
- if (empty($inputData['token'])) {
- // 可能串号,重复的设备号,做一次排序查询处理.
- $user = UserModel::query()->where(['device_no' => $hDeviceNo, 'device_type' => $hDeviceType])->orderBy('id', 'ASC')->first();
- // 临时关闭ios指纹
- /*if (!$user) {
- if ($hDeviceType == 'I' && !empty($hFingerHash)) {
- // 仅iOS再查指纹.
- $user = UserModel::query()->where(['finger_hash' => $hFingerHash, 'device_type' => $hDeviceType])->first();
- }
- }*/
- }
- if ($hDeviceType == 'A') {
- if (version_compare($hVersion, '1.1.0', '<') || version_compare($hVersion, '1.99.1', '>')) {
- return json_fail('认证失败7', 403);
- }
- }
- //启动页广告
- $startup = null;
- // 视频内引导广告(多个)
- $adsGuide = null;
- //配置
- $config = [];
- $configModel = make(ConfigRepoistory::class);
- $config['rule'] = $configModel->gets([], 'rule');
- $config['sys'] = $configModel->gets(['app_notice', 'notice', 'share_text', 'share_url', 'upload_url', 'skip', 'app_url', 'server_link', 'share_bg', 'video_preview_seconds', 'withdraw_usdt', 'withdraw_money', 'hotel_coin', 'ppvod_base_url', 'ppvod_upload_key', 'ppvod_sign_key', 'chat_coin'], 'sys');
- //公告
- if (empty($config['sys']['notice'])) {
- $config['sys']['app_notice'] = '';
- unset($config['sys']['notice']);
- }
- if (!empty($user)) {
- // 检查是否被删,是否被锁定.
- if (intval($user->status) != 1) {
- return json_fail('该用户被禁用');
- }
- make(QueueService::class)->refreshLoginTimePush(['user_id' => $user->code, 'version' => $user->version], 1);
- } else {
- //注册
- $isBannedIp = make(BannedIpRepository::class)->isBanned($clientIp);
- if ($isBannedIp) {
- return json_fail('认证失败9', 403);
- }
- // 注册时,校验提交的版本和最新强制版本对比.
- $cacheVersionKey = 'force_version:' . $hDeviceType;
- $lastForceVersionInfo = make(VersionRepository::class)->getLastForce($hDeviceType);
- if (!empty($lastForceVersionInfo)) {
- if (version_compare($hVersion, $lastForceVersionInfo['version_code'], '<')) {
- return json_fail('认证失败8', 403);
- }
- }
- if (!empty($inputData['code'])) {
- $info = make(UserRepository::class)->getUserInfoFromCache($inputData['code']);
- if ($info) {
- $parentUid = $info['code'];
- }
- }
- // 随机头像.
- $sysAvatars = make(UserRepository::class)->getSysAvatars();
- $sysAvatars[] = 'f2|/' . env('STATIC_DIR_NAME', 'doukui') . '/avatars/default.ceb';
- $randKey = array_rand($sysAvatars);
- $avatar = $sysAvatars[$randKey];
- $createData = ['ip' => $clientIp, 'avatar' => $avatar, 'device_no' => $hDeviceNo, 'parent_uid' => $parentUid ?? '', 'channel_id' => $inputData['channel'] ?? '', 'device_type' => $hDeviceType, 'version' => $hVersion, 'finger_hash' => $hFingerHash ?? '', 'last_login_at' => Carbon::now()->toDateTimeString(), 'bundle_id' => $hBundleId];
- $user = UserModel::query()->create($createData);
- //生成邀请码
- $code = hashEncodeId($user->id);
- $user->code = $code;
- $user->nick = RandomUserInfoRepository::getNickname();
- $user->save();
- //会员裂变关系逻辑 丢队列异步执行
- if (!empty($parentUid)) {
- make(QueueService::class)->createUserRelationJob(['invite_id' => $parentUid, 'invited_id' => $user->code], 1);
- // 绑定邀请码任务
- dispatch_event(new FillCodeEvent(['user_id' => $user->code]));
- // 邀请好友任务事件
- dispatch_event(new InviteFriendsEvent(['user_id' => $parentUid]));
- // 限时任务队列
- make(QueueService::class)->timeTaskSendRewardJob(['user_id' => $parentUid], 5);
- }
- //注册奖励事件
- $registerEventParams = ['ip' => $clientIp, 'user_id' => $code, 'device_type' => $hDeviceType, 'self_sign' => $hSelfSign];
- dispatch_event(new FirstRegisterEvent($registerEventParams));
- $jobParams = ['uid' => $user->id, 'dt' => $hDeviceType, 'is_vip' => 0, 'date' => Carbon::now()->toDateString()];
- //异步丢注册统计日志
- make(QueueService::class)->addRegisterJob($jobParams);
- //代理系统异步注册
- make(QueueService::class)->agentUserRegistJob($user->toArray());
- }
- if (empty($auth)) {
- $auth = $authService->createSingleJwt($user->code, $hDeviceNo, $hDeviceType, $hVersion, $hFingerHash);
- }
- // 拼接版本里的zip_url
- $tmpHeaderVersion = $hVersion;
- $versionCacheKey = 'version:' . $hDeviceType . ':v' . $tmpHeaderVersion;
- $versionItem = make(CacheInterface::class)->remember($versionCacheKey, 60 * 60, function () use($inputData, $hVersion, $hDeviceType, $tmpHeaderVersion) {
- if ($hDeviceType == 'I') {
- // 针对马甲包,特例处理,状态可以是隐藏.
- $conditions = ['device_type' => $hDeviceType, 'version_type' => 'free', 'version_code' => $tmpHeaderVersion];
- } else {
- $conditions = ['status' => 1, 'device_type' => $hDeviceType, 'version_type' => 'free', 'version_code' => $hVersion];
- }
- $item = VersionModel::query()->where($conditions)->orderByDesc('version_code')->first();
- if (!empty($item)) {
- $item = $item->toArray();
- $item['app_url'] = make(ConfigRepoistory::class)->get('share_url', 'sys');
- return $item;
- } else {
- return null;
- }
- });
- $config['sys']['version_zip_url'] = !empty($versionItem['zip_url']) ? $versionItem['zip_url'] : '';
- // ppvod上传最大容量,单位MB
- $config['sys']['file_max_size_mb'] = CreatorConstant::VIDEO_MAX_SIZE_MB;
- // 文件最大多少MB
- //邀请链接组装
- $config['sys']['share_url'] .= '?invite_code=' . $user->code . '&channel_code=';
- $config['sys']['share_text'] = str_replace('{{share_url}}', $config['sys']['share_url'], $config['sys']['share_text']);
- $config['sys']['share_bg'] = isset($config['sys']['share_bg']) ? M3u8CacheRepository::getImageUrl($config['sys']['share_bg']) : '';
- $videoService = make(VideoService::class);
- $startupItems = $videoService->getAdList('startup');
- $adsGuideItems = $videoService->getAdList(AdConstrant::VIDEO_GUIDE);
- // 视频内引导广告,多个
- if ($startupItems) {
- $randKey = array_rand($startupItems);
- $startupOne = $startupItems[$randKey];
- $startup = ['title' => $startupOne['title'], 'cover' => M3u8CacheRepository::getImageUrl($startupOne['cover']), 'link' => $startupOne['link'], 'scheme' => $startupOne['scheme'], 'type' => $startupOne['type'], 'play' => !empty($startupOne['play']) ? M3u8CacheRepository::getVideoInnerUrl($startupOne['play'], $user->code, $startupOne['id'], 'ad') : '', 'resolution' => $startupOne['resolution'] ?? ''];
- if (!empty($startup['link'])) {
- $startup['link'] = M3u8CacheRepository::getLinkUrl($startup['link'], ['user_code' => $user->code]);
- }
- }
- if (!empty($adsGuideItems)) {
- $adsGuide = [];
- foreach ($adsGuideItems as $v) {
- $tmpLink = $v['link'];
- if (!empty($tmpLink)) {
- $tmpLink = M3u8CacheRepository::getLinkUrl($tmpLink, ['user_code' => $user->code]);
- }
- $adsGuide[] = ['title' => $v['title'], 'cover' => M3u8CacheRepository::getImageUrl($v['cover']), 'link' => $tmpLink, 'scheme' => $v['scheme']];
- }
- }
- $data['auth'] = $auth;
- $data['startup'] = $startup;
- $data['config'] = $config;
- $data['skip'] = $config['sys']['skip'];
- // 启动屏跳转秒数.
- $data['ads_guide'] = $adsGuide;
- $kefuUrl = isset($data['config']['sys']['server_link']) ? $data['config']['sys']['server_link'] : '';
- if (strripos($kefuUrl, '?')) {
- $kefuUrl .= '&user_code=' . $user->code;
- } else {
- $kefuUrl .= '?user_code=' . $user->code;
- }
- $data['config']['sys']['server_link'] = $kefuUrl;
- $data['config']['sys']['video_preview_seconds'] = intval($data['config']['sys']['video_preview_seconds']);
- $data['config']['sys']['withdraw_money'] = (int) $data['config']['sys']['withdraw_money'];
- $data['config']['sys']['withdraw_usdt'] = (int) $data['config']['sys']['withdraw_usdt'];
- $data['config']['sys']['hotel_coin'] = (int) $data['config']['sys']['hotel_coin'];
- $data['config']['sys']['chat_coin'] = $data['config']['sys']['chat_coin'];
- //使用redis统计日活
- // $jobParams = [
- // 'user_id' => $user->code,
- // 'version' => $hVersion,
- // 'device_type' => $hDeviceType,
- // ];
- // 触发事件(可以作为演示hyperf event+listener实现应用).
- $eventParams = ['user_id' => $user->code, 'version' => $hVersion, 'device_type' => $hDeviceType, 'device_no' => $hDeviceNo, 'finger_hash' => $hFingerHash, 'ip' => $clientIp];
- dispatch_event(new UserLoginEvent($eventParams));
- //登陆奖励事件
- dispatch_event(new DailyLoginEvent(['user_id' => $user->code]));
- // 代理系统异步登陆
- if (empty($user->version)) {
- $user->version = $hVersion;
- }
- make(QueueService::class)->agentUserLoginJob($user->toArray());
- return json_success($data);
- }
- /**
- * 刷新Token并返回token
- *
- * @param ValidatorFactoryInterface $validatorFactory
- * @param JwtRepository $jwtRepository
- * @param AuthService $authService
- * @return mixed
- * @throws \Psr\SimpleCache\InvalidArgumentException
- */
- public function refreshToken(ValidatorFactoryInterface $validatorFactory, JwtRepository $jwtRepository, AuthService $authService)
- {
- $inputData = $this->getJsonData();
- $rules = ['device_no' => 'required', 'device_type' => 'required|in:I,A,H', 'old_token' => 'required'];
- $validator = $validatorFactory->make($inputData, $rules);
- if ($validator->fails()) {
- // 如果有错误(可能是多个信息),这里只取第一个错误信息返回.
- return json_fail($validator->errors()->first());
- }
- $inputData['device_no'] = strval($inputData['device_no']);
- $hDeviceNo = data_get($inputData, 'device_no', '');
- $hDeviceType = data_get($inputData, 'device_type', '');
- $hOldToken = data_get($inputData, 'old_token', '');
- $hFingerHash = strval(data_get($inputData, 'finger_hash', ''));
- $hSelfSign = strval(data_get($inputData, 'self_sign', ''));
- // 苹果自签,yes字符串
- // 是否强制刷新.
- $validateResult = $jwtRepository->validateToken($hOldToken, $this->jwtAud);
- if ($validateResult['is_illegal']) {
- return json_fail('旧token非法.');
- }
- $clientIp = make(CommonService::class)->getIp();
- if (!filter_var($clientIp, FILTER_VALIDATE_IP)) {
- return json_fail('认证失败8');
- }
- /**
- * @var UnencryptedToken $tokenObj
- */
- $tokenObj = $validateResult['token_obj'];
- $userId = $tokenObj->claims()->get('user_id');
- $deviceNo = $tokenObj->claims()->get('device_no');
- $deviceType = $tokenObj->claims()->get('device_type');
- $version = $tokenObj->claims()->get('version');
- $fingerHash = $tokenObj->claims()->get('finger_hash');
- // 检查入参设备信息是否有效.
- if ($deviceNo != $hDeviceNo) {
- return json_fail('无效数据匹配.');
- }
- if ($deviceType != $hDeviceType) {
- return json_fail('无效数据匹配.');
- }
- if (!$validateResult['is_valid'] && $validateResult['is_expired']) {
- $tokenInfo = $authService->createSingleJwt($userId, $deviceNo, $deviceType, $version, $fingerHash);
- } else {
- return json_fail('旧Token无效或者未过期');
- }
- //更新登陆时间
- make(QueueService::class)->refreshLoginTimePush(['user_id' => $userId, 'version' => $version], 1);
- // 触发事件(可以作为演示hyperf event+listener实现应用).
- $eventParams = ['user_id' => $userId, 'version' => $version, 'device_type' => $hDeviceType, 'device_no' => $hDeviceNo, 'finger_hash' => $hFingerHash, 'ip' => $clientIp];
- dispatch_event(new UserLoginEvent($eventParams));
- $data = ['token' => $tokenInfo['token'], 'expired_at' => $tokenInfo['expired_at']];
- return json_success($data);
- }
- }
|