Modules_Api_Controllers_AuthController.proxy.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. <?php
  2. /**
  3. * 认证控制器.
  4. *
  5. * @author nj <nj@gmail.com>
  6. * @date 2020-01-22 09:37:54
  7. */
  8. declare (strict_types=1);
  9. namespace Modules\Api\Controllers;
  10. use App\Constants\AdConstrant;
  11. use App\Constants\CreatorConstant;
  12. use App\Events\Task\DailyLoginEvent;
  13. use App\Events\Task\FillCodeEvent;
  14. use App\Events\Task\FirstRegisterEvent;
  15. use App\Events\Task\InviteFriendsEvent;
  16. use App\Events\Task\TimeTaskEvent;
  17. use App\Events\User\UserLoginEvent;
  18. use App\Model\UserModel;
  19. use App\Model\VersionModel;
  20. use App\Model\VipOrderModel;
  21. use App\Repositories\BannedIpRepository;
  22. use App\Repositories\ConfigRepoistory;
  23. use App\Repositories\MessageRepository;
  24. use App\Repositories\Service\CommonService;
  25. use App\Repositories\Service\QueueService;
  26. use App\Repositories\ShopsRepository;
  27. use App\Repositories\UserRepository;
  28. use App\Repositories\Utils\JwtRepository;
  29. use App\Repositories\Utils\M3u8CacheRepository;
  30. use App\Repositories\Utils\RandomUserInfoRepository;
  31. use App\Repositories\VersionRepository;
  32. use Carbon\Carbon;
  33. use Hyperf\Validation\Contract\ValidatorFactoryInterface;
  34. use Lcobucci\JWT\UnencryptedToken;
  35. use Modules\Api\Services\AuthService;
  36. use Modules\Api\Services\VideoService;
  37. use Psr\SimpleCache\CacheInterface;
  38. class AuthController extends BaseController
  39. {
  40. use \Hyperf\Di\Aop\ProxyTrait;
  41. use \Hyperf\Di\Aop\PropertyHandlerTrait;
  42. function __construct()
  43. {
  44. if (method_exists(parent::class, '__construct')) {
  45. parent::__construct(...func_get_args());
  46. }
  47. $this->__handlePropertyHandler(__CLASS__);
  48. }
  49. /**
  50. * Jwt受众.
  51. *
  52. * @var string
  53. */
  54. private $jwtAud = 'app-users';
  55. /**
  56. * 设备登录并返回Token.
  57. * @param ValidatorFactoryInterface $validatorFactory
  58. * @param AuthService $authService
  59. * @return mixed
  60. * @throws \Psr\SimpleCache\InvalidArgumentException
  61. */
  62. public function loginByDevice(ValidatorFactoryInterface $validatorFactory, AuthService $authService)
  63. {
  64. // Json Body Raw获取
  65. $inputData = $this->getJsonData();
  66. // Demo 这是在控制器内校验器的写法.
  67. $rules = [
  68. 'code' => 'nullable',
  69. // 父级邀请人的UID
  70. 'token' => 'nullable',
  71. 'channel' => 'nullable',
  72. // 渠道UID
  73. 'version' => 'required',
  74. 'device_no' => 'required',
  75. 'device_type' => 'required|in:A,I,H',
  76. 'finger_hash' => 'nullable|string|max:32',
  77. 'self_sign' => 'nullable|string',
  78. // 苹果自签,yes=是的
  79. 'bundle_id' => 'nullable|string',
  80. ];
  81. $validator = $validatorFactory->make($inputData, $rules);
  82. if ($validator->fails()) {
  83. // 如果有错误(可能是多个信息),这里只取第一个错误信息返回.
  84. return json_fail($validator->errors()->first());
  85. }
  86. $clientIp = make(CommonService::class)->getIp();
  87. if (!filter_var($clientIp, FILTER_VALIDATE_IP)) {
  88. return json_fail('认证失败8');
  89. }
  90. $inputData['device_no'] = strval($inputData['device_no']);
  91. $hVersion = strval(data_get($inputData, 'version', ''));
  92. $hDeviceNo = data_get($inputData, 'device_no', '');
  93. $hDeviceType = data_get($inputData, 'device_type', '');
  94. $hFingerHash = strval(data_get($inputData, 'finger_hash', ''));
  95. $hSelfSign = strval(data_get($inputData, 'self_sign', ''));
  96. // 苹果自签,yes字符串
  97. $hBundleId = strval(data_get($inputData, 'bundle_id', ''));
  98. // 包名
  99. $cache = make(CacheInterface::class);
  100. $user = null;
  101. $auth = [];
  102. //有token的情况
  103. if (!empty($inputData['token'])) {
  104. $jwtRepository = make(JwtRepository::class);
  105. $validateResult = $jwtRepository->validateToken($inputData['token'], $this->jwtAud);
  106. if (!$validateResult['is_valid']) {
  107. if ($validateResult['is_expired']) {
  108. return json_fail('认证过期', 401);
  109. }
  110. return json_fail('认证失败1', 403);
  111. } else {
  112. // 验证单点.
  113. /**
  114. * @var UnencryptedToken $tokenObj
  115. */
  116. $tokenObj = $validateResult['token_obj'];
  117. $deviceNo = $tokenObj->claims()->get('device_no');
  118. $deviceType = $tokenObj->claims()->get('device_type');
  119. $fingerHash = $tokenObj->claims()->get('finger_hash');
  120. $cacheKey = $authService->getUserDeviceCacheKey($deviceNo, $deviceType);
  121. $cacheToken = $cache->has($cacheKey) ? $cache->get($cacheKey) : '';
  122. if (empty($cacheToken) || $cacheToken !== $inputData['token']) {
  123. return json_fail('认证失败2', 403);
  124. }
  125. if ($hDeviceNo !== $deviceNo || $hDeviceType != $deviceType) {
  126. return json_fail('认证失败3', 403);
  127. }
  128. $auth['token'] = $cacheToken;
  129. }
  130. }
  131. //没有token的情况
  132. if (empty($inputData['token'])) {
  133. // 可能串号,重复的设备号,做一次排序查询处理.
  134. $user = UserModel::query()->where(['device_no' => $hDeviceNo, 'device_type' => $hDeviceType])->orderBy('id', 'ASC')->first();
  135. // 临时关闭ios指纹
  136. /*if (!$user) {
  137. if ($hDeviceType == 'I' && !empty($hFingerHash)) {
  138. // 仅iOS再查指纹.
  139. $user = UserModel::query()->where(['finger_hash' => $hFingerHash, 'device_type' => $hDeviceType])->first();
  140. }
  141. }*/
  142. }
  143. if ($hDeviceType == 'A') {
  144. if (version_compare($hVersion, '1.1.0', '<') || version_compare($hVersion, '1.99.1', '>')) {
  145. return json_fail('认证失败7', 403);
  146. }
  147. }
  148. //启动页广告
  149. $startup = null;
  150. // 视频内引导广告(多个)
  151. $adsGuide = null;
  152. //配置
  153. $config = [];
  154. $configModel = make(ConfigRepoistory::class);
  155. $config['rule'] = $configModel->gets([], 'rule');
  156. $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');
  157. //公告
  158. if (empty($config['sys']['notice'])) {
  159. $config['sys']['app_notice'] = '';
  160. unset($config['sys']['notice']);
  161. }
  162. if (!empty($user)) {
  163. // 检查是否被删,是否被锁定.
  164. if (intval($user->status) != 1) {
  165. return json_fail('该用户被禁用');
  166. }
  167. make(QueueService::class)->refreshLoginTimePush(['user_id' => $user->code, 'version' => $user->version], 1);
  168. } else {
  169. //注册
  170. $isBannedIp = make(BannedIpRepository::class)->isBanned($clientIp);
  171. if ($isBannedIp) {
  172. return json_fail('认证失败9', 403);
  173. }
  174. // 注册时,校验提交的版本和最新强制版本对比.
  175. $cacheVersionKey = 'force_version:' . $hDeviceType;
  176. $lastForceVersionInfo = make(VersionRepository::class)->getLastForce($hDeviceType);
  177. if (!empty($lastForceVersionInfo)) {
  178. if (version_compare($hVersion, $lastForceVersionInfo['version_code'], '<')) {
  179. return json_fail('认证失败8', 403);
  180. }
  181. }
  182. if (!empty($inputData['code'])) {
  183. $info = make(UserRepository::class)->getUserInfoFromCache($inputData['code']);
  184. if ($info) {
  185. $parentUid = $info['code'];
  186. }
  187. }
  188. // 随机头像.
  189. $sysAvatars = make(UserRepository::class)->getSysAvatars();
  190. $sysAvatars[] = 'f2|/' . env('STATIC_DIR_NAME', 'doukui') . '/avatars/default.ceb';
  191. $randKey = array_rand($sysAvatars);
  192. $avatar = $sysAvatars[$randKey];
  193. $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];
  194. $user = UserModel::query()->create($createData);
  195. //生成邀请码
  196. $code = hashEncodeId($user->id);
  197. $user->code = $code;
  198. $user->nick = RandomUserInfoRepository::getNickname();
  199. $user->save();
  200. //会员裂变关系逻辑 丢队列异步执行
  201. if (!empty($parentUid)) {
  202. make(QueueService::class)->createUserRelationJob(['invite_id' => $parentUid, 'invited_id' => $user->code], 1);
  203. // 绑定邀请码任务
  204. dispatch_event(new FillCodeEvent(['user_id' => $user->code]));
  205. // 邀请好友任务事件
  206. dispatch_event(new InviteFriendsEvent(['user_id' => $parentUid]));
  207. // 限时任务队列
  208. make(QueueService::class)->timeTaskSendRewardJob(['user_id' => $parentUid], 5);
  209. }
  210. //注册奖励事件
  211. $registerEventParams = ['ip' => $clientIp, 'user_id' => $code, 'device_type' => $hDeviceType, 'self_sign' => $hSelfSign];
  212. dispatch_event(new FirstRegisterEvent($registerEventParams));
  213. $jobParams = ['uid' => $user->id, 'dt' => $hDeviceType, 'is_vip' => 0, 'date' => Carbon::now()->toDateString()];
  214. //异步丢注册统计日志
  215. make(QueueService::class)->addRegisterJob($jobParams);
  216. //代理系统异步注册
  217. make(QueueService::class)->agentUserRegistJob($user->toArray());
  218. }
  219. if (empty($auth)) {
  220. $auth = $authService->createSingleJwt($user->code, $hDeviceNo, $hDeviceType, $hVersion, $hFingerHash);
  221. }
  222. // 拼接版本里的zip_url
  223. $tmpHeaderVersion = $hVersion;
  224. $versionCacheKey = 'version:' . $hDeviceType . ':v' . $tmpHeaderVersion;
  225. $versionItem = make(CacheInterface::class)->remember($versionCacheKey, 60 * 60, function () use($inputData, $hVersion, $hDeviceType, $tmpHeaderVersion) {
  226. if ($hDeviceType == 'I') {
  227. // 针对马甲包,特例处理,状态可以是隐藏.
  228. $conditions = ['device_type' => $hDeviceType, 'version_type' => 'free', 'version_code' => $tmpHeaderVersion];
  229. } else {
  230. $conditions = ['status' => 1, 'device_type' => $hDeviceType, 'version_type' => 'free', 'version_code' => $hVersion];
  231. }
  232. $item = VersionModel::query()->where($conditions)->orderByDesc('version_code')->first();
  233. if (!empty($item)) {
  234. $item = $item->toArray();
  235. $item['app_url'] = make(ConfigRepoistory::class)->get('share_url', 'sys');
  236. return $item;
  237. } else {
  238. return null;
  239. }
  240. });
  241. $config['sys']['version_zip_url'] = !empty($versionItem['zip_url']) ? $versionItem['zip_url'] : '';
  242. // ppvod上传最大容量,单位MB
  243. $config['sys']['file_max_size_mb'] = CreatorConstant::VIDEO_MAX_SIZE_MB;
  244. // 文件最大多少MB
  245. //邀请链接组装
  246. $config['sys']['share_url'] .= '?invite_code=' . $user->code . '&channel_code=';
  247. $config['sys']['share_text'] = str_replace('{{share_url}}', $config['sys']['share_url'], $config['sys']['share_text']);
  248. $config['sys']['share_bg'] = isset($config['sys']['share_bg']) ? M3u8CacheRepository::getImageUrl($config['sys']['share_bg']) : '';
  249. $videoService = make(VideoService::class);
  250. $startupItems = $videoService->getAdList('startup');
  251. $adsGuideItems = $videoService->getAdList(AdConstrant::VIDEO_GUIDE);
  252. // 视频内引导广告,多个
  253. if ($startupItems) {
  254. $randKey = array_rand($startupItems);
  255. $startupOne = $startupItems[$randKey];
  256. $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'] ?? ''];
  257. if (!empty($startup['link'])) {
  258. $startup['link'] = M3u8CacheRepository::getLinkUrl($startup['link'], ['user_code' => $user->code]);
  259. }
  260. }
  261. if (!empty($adsGuideItems)) {
  262. $adsGuide = [];
  263. foreach ($adsGuideItems as $v) {
  264. $tmpLink = $v['link'];
  265. if (!empty($tmpLink)) {
  266. $tmpLink = M3u8CacheRepository::getLinkUrl($tmpLink, ['user_code' => $user->code]);
  267. }
  268. $adsGuide[] = ['title' => $v['title'], 'cover' => M3u8CacheRepository::getImageUrl($v['cover']), 'link' => $tmpLink, 'scheme' => $v['scheme']];
  269. }
  270. }
  271. $data['auth'] = $auth;
  272. $data['startup'] = $startup;
  273. $data['config'] = $config;
  274. $data['skip'] = $config['sys']['skip'];
  275. // 启动屏跳转秒数.
  276. $data['ads_guide'] = $adsGuide;
  277. $kefuUrl = isset($data['config']['sys']['server_link']) ? $data['config']['sys']['server_link'] : '';
  278. if (strripos($kefuUrl, '?')) {
  279. $kefuUrl .= '&user_code=' . $user->code;
  280. } else {
  281. $kefuUrl .= '?user_code=' . $user->code;
  282. }
  283. $data['config']['sys']['server_link'] = $kefuUrl;
  284. $data['config']['sys']['video_preview_seconds'] = intval($data['config']['sys']['video_preview_seconds']);
  285. $data['config']['sys']['withdraw_money'] = (int) $data['config']['sys']['withdraw_money'];
  286. $data['config']['sys']['withdraw_usdt'] = (int) $data['config']['sys']['withdraw_usdt'];
  287. $data['config']['sys']['hotel_coin'] = (int) $data['config']['sys']['hotel_coin'];
  288. $data['config']['sys']['chat_coin'] = $data['config']['sys']['chat_coin'];
  289. //使用redis统计日活
  290. // $jobParams = [
  291. // 'user_id' => $user->code,
  292. // 'version' => $hVersion,
  293. // 'device_type' => $hDeviceType,
  294. // ];
  295. // 触发事件(可以作为演示hyperf event+listener实现应用).
  296. $eventParams = ['user_id' => $user->code, 'version' => $hVersion, 'device_type' => $hDeviceType, 'device_no' => $hDeviceNo, 'finger_hash' => $hFingerHash, 'ip' => $clientIp];
  297. dispatch_event(new UserLoginEvent($eventParams));
  298. //登陆奖励事件
  299. dispatch_event(new DailyLoginEvent(['user_id' => $user->code]));
  300. // 代理系统异步登陆
  301. if (empty($user->version)) {
  302. $user->version = $hVersion;
  303. }
  304. make(QueueService::class)->agentUserLoginJob($user->toArray());
  305. return json_success($data);
  306. }
  307. /**
  308. * 刷新Token并返回token
  309. *
  310. * @param ValidatorFactoryInterface $validatorFactory
  311. * @param JwtRepository $jwtRepository
  312. * @param AuthService $authService
  313. * @return mixed
  314. * @throws \Psr\SimpleCache\InvalidArgumentException
  315. */
  316. public function refreshToken(ValidatorFactoryInterface $validatorFactory, JwtRepository $jwtRepository, AuthService $authService)
  317. {
  318. $inputData = $this->getJsonData();
  319. $rules = ['device_no' => 'required', 'device_type' => 'required|in:I,A,H', 'old_token' => 'required'];
  320. $validator = $validatorFactory->make($inputData, $rules);
  321. if ($validator->fails()) {
  322. // 如果有错误(可能是多个信息),这里只取第一个错误信息返回.
  323. return json_fail($validator->errors()->first());
  324. }
  325. $inputData['device_no'] = strval($inputData['device_no']);
  326. $hDeviceNo = data_get($inputData, 'device_no', '');
  327. $hDeviceType = data_get($inputData, 'device_type', '');
  328. $hOldToken = data_get($inputData, 'old_token', '');
  329. $hFingerHash = strval(data_get($inputData, 'finger_hash', ''));
  330. $hSelfSign = strval(data_get($inputData, 'self_sign', ''));
  331. // 苹果自签,yes字符串
  332. // 是否强制刷新.
  333. $validateResult = $jwtRepository->validateToken($hOldToken, $this->jwtAud);
  334. if ($validateResult['is_illegal']) {
  335. return json_fail('旧token非法.');
  336. }
  337. $clientIp = make(CommonService::class)->getIp();
  338. if (!filter_var($clientIp, FILTER_VALIDATE_IP)) {
  339. return json_fail('认证失败8');
  340. }
  341. /**
  342. * @var UnencryptedToken $tokenObj
  343. */
  344. $tokenObj = $validateResult['token_obj'];
  345. $userId = $tokenObj->claims()->get('user_id');
  346. $deviceNo = $tokenObj->claims()->get('device_no');
  347. $deviceType = $tokenObj->claims()->get('device_type');
  348. $version = $tokenObj->claims()->get('version');
  349. $fingerHash = $tokenObj->claims()->get('finger_hash');
  350. // 检查入参设备信息是否有效.
  351. if ($deviceNo != $hDeviceNo) {
  352. return json_fail('无效数据匹配.');
  353. }
  354. if ($deviceType != $hDeviceType) {
  355. return json_fail('无效数据匹配.');
  356. }
  357. if (!$validateResult['is_valid'] && $validateResult['is_expired']) {
  358. $tokenInfo = $authService->createSingleJwt($userId, $deviceNo, $deviceType, $version, $fingerHash);
  359. } else {
  360. return json_fail('旧Token无效或者未过期');
  361. }
  362. //更新登陆时间
  363. make(QueueService::class)->refreshLoginTimePush(['user_id' => $userId, 'version' => $version], 1);
  364. // 触发事件(可以作为演示hyperf event+listener实现应用).
  365. $eventParams = ['user_id' => $userId, 'version' => $version, 'device_type' => $hDeviceType, 'device_no' => $hDeviceNo, 'finger_hash' => $hFingerHash, 'ip' => $clientIp];
  366. dispatch_event(new UserLoginEvent($eventParams));
  367. $data = ['token' => $tokenInfo['token'], 'expired_at' => $tokenInfo['expired_at']];
  368. return json_success($data);
  369. }
  370. }