Modules_Api_Controllers_M3u8Controller.proxy.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. <?php
  2. namespace Modules\Api\Controllers;
  3. use App\Constants\CommonConstant;
  4. use App\Constants\VideoConstant;
  5. use App\Model\UserModel;
  6. use App\Repositories\AdRepository;
  7. use App\Repositories\OriginalRepository;
  8. use App\Repositories\Service\CommonService;
  9. use App\Repositories\ShopsRepository;
  10. use App\Repositories\UserRepository;
  11. use App\Repositories\Utils\JwtRepository;
  12. use App\Repositories\Utils\M3u8CacheRepository;
  13. use App\Repositories\VideoRepository;
  14. use Carbon\Carbon;
  15. use Hyperf\HttpServer\Request;
  16. use Modules\Api\Services\AuthService;
  17. use Psr\SimpleCache\CacheInterface;
  18. class M3u8Controller extends BaseController
  19. {
  20. use \Hyperf\Di\Aop\ProxyTrait;
  21. use \Hyperf\Di\Aop\PropertyHandlerTrait;
  22. function __construct()
  23. {
  24. if (method_exists(parent::class, '__construct')) {
  25. parent::__construct(...func_get_args());
  26. }
  27. $this->__handlePropertyHandler(__CLASS__);
  28. }
  29. /**
  30. * 获取raw
  31. * @param Request $request
  32. * @param VideoRepository $videoRepository
  33. * @param AdRepository $adRepository
  34. * @return \Psr\Http\Message\ResponseInterface
  35. */
  36. public function raw(Request $request, VideoRepository $videoRepository, AdRepository $adRepository, OriginalRepository $goodsRepository, ShopsRepository $shopsRepository)
  37. {
  38. $zuhe = urldecode($request->input('tk', ''));
  39. $module = trim(strtolower($request->input('module', '')));
  40. $pk = substr($request->input('pk', ''), 0, 16);
  41. $inputSign = $request->input('sn', '');
  42. $timestamp = intval($request->input('tm', 0));
  43. $rateType = substr($request->input('rate_type', ''), 0, 32);
  44. // 指定的比特率.
  45. $authService = new AuthService();
  46. if (empty($zuhe)) {
  47. return self::responseM3u8Raw('参数错误1', true);
  48. }
  49. if (!in_array($module, ['video', 'ad', 'goods', 'shop'])) {
  50. // 判断模块合法性.
  51. return self::responseM3u8Raw('参数错误2', true);
  52. }
  53. $nowDateObj = Carbon::now();
  54. $startCheckDate = '2022-08-01 04:45:33';
  55. $startCheckObj = Carbon::parse($startCheckDate);
  56. if ($nowDateObj > $startCheckObj && in_array($module, ['video'])) {
  57. // 大于这个日期, 且视频模块, 才检查签名参数.
  58. if (!empty($timestamp) && strlen($timestamp) == 10) {
  59. $inputDateObj = Carbon::createFromTimestamp($timestamp);
  60. if (abs($inputDateObj->diffInHours($nowDateObj)) >= 3) {
  61. return self::responseM3u8Raw('参数错误6', true);
  62. }
  63. } else {
  64. return self::responseM3u8Raw('参数错误7', true);
  65. }
  66. }
  67. $zuhe2 = caesar_decode($zuhe, 'l,n', 3);
  68. // 偏移了3
  69. $previewFlag = substr($zuhe2, 0, 2);
  70. $queryDeviceType = substr($zuhe2, 2, 1);
  71. $queryDeviceNo = substr($zuhe2, 7, strlen($zuhe2) - 7);
  72. $sign = M3u8CacheRepository::generateSign($zuhe2, $timestamp);
  73. if ($sign !== $inputSign) {
  74. return self::responseM3u8Raw('参数错误8', true);
  75. }
  76. $cacheKey = $authService->getUserDeviceCacheKey($queryDeviceNo, $queryDeviceType);
  77. $cache = make(CacheInterface::class);
  78. $cacheToken = $cache->has($cacheKey) ? $cache->get($cacheKey) : '';
  79. if (empty($cacheToken)) {
  80. return self::responseM3u8Raw('参数错误4', true);
  81. }
  82. $jwtRepo = make(JwtRepository::class);
  83. $aud = 'app-users';
  84. $validateResult = $jwtRepo->validateToken($cacheToken, $aud);
  85. if (!$validateResult['is_valid']) {
  86. if ($validateResult['is_expired']) {
  87. return self::responseM3u8Raw('参数错误2', true);
  88. }
  89. return self::responseM3u8Raw('参数错误3', true);
  90. }
  91. // 破解版专门一个m3u8
  92. $tokenObj = $validateResult['token_obj'];
  93. $hUserCode = $tokenObj->claims()->get('user_id');
  94. // $hDeviceNo = $tokenObj->claims()->get('device_no');
  95. $hDeviceType = $tokenObj->claims()->get('device_type');
  96. // $hFingerHash = $tokenObj->claims()->get('finger_hash');
  97. $hIp = $tokenObj->claims()->get('ip');
  98. $hVersion = $tokenObj->claims()->get('version');
  99. if ($hDeviceType == 'A') {
  100. if (version_compare($hVersion, '1.1.1', '<') || version_compare($hVersion, '1.99.1', '>')) {
  101. $crackedM3u8Url = 'v2|/20220831/pPlRwPcF/1000kb/hls/index.m3u8';
  102. $m3u8Params1 = ['url' => $crackedM3u8Url, 'is_preview' => false];
  103. $raw = M3u8CacheRepository::getMu38Raw($m3u8Params1, true);
  104. return self::responseM3u8Raw($raw);
  105. }
  106. }
  107. // M3u8验证IP.
  108. $clientIp = make(CommonService::class)->getIp();
  109. if ($hIp !== $clientIp) {
  110. // make(UserRepository::class)->userLogout($hUserCode);
  111. logger('api-M3u8Controller-ip', ['token_ip' => $hIp, 'user_ip' => $clientIp]);
  112. // return self::responseM3u8Raw('参数错误9', true);
  113. }
  114. $cache = make(CacheInterface::class);
  115. $rateTypeKey = !empty($rateType) ? $rateType : 'empty';
  116. $cacheKey = $module . ':raw:' . $pk . ':' . $rateTypeKey;
  117. $cacheSeconds = 7 * 24 * 60 * 60;
  118. $arrItem = $cache->get($cacheKey, []);
  119. $m3u8PlayUrl = '';
  120. $previewTime = VideoConstant::PREVIEW_TIME;
  121. $playerType = 1;
  122. if (empty($arrItem)) {
  123. switch ($module) {
  124. case 'video':
  125. $item = $videoRepository->findBy('id', $pk, ['id', 'mu']);
  126. if (!empty($item->mu)) {
  127. $m3u8PlayUrl = $item->mu;
  128. }
  129. break;
  130. case 'ad':
  131. $item = $adRepository->findBy('id', $pk, ['id', 'play']);
  132. if (!empty($item->play)) {
  133. $m3u8PlayUrl = $item->play;
  134. }
  135. break;
  136. case 'shop':
  137. $item = $shopsRepository->findBy('uid', $pk, ['video_address']);
  138. if (!empty($item->video_address)) {
  139. $m3u8PlayUrl = $item->video_address;
  140. }
  141. break;
  142. case 'goods':
  143. $item = $goodsRepository->findBy('uid', $pk, ['video_address']);
  144. if (!empty($item->video_address)) {
  145. $m3u8PlayUrl = $item->video_address;
  146. }
  147. break;
  148. }
  149. if (empty($item)) {
  150. return self::responseM3u8Raw('参数错误5', true);
  151. }
  152. $arrItem = $item->toArray();
  153. $arrItem['m3u8_play_url'] = $m3u8PlayUrl;
  154. $cache->set($cacheKey, $arrItem, $cacheSeconds);
  155. }
  156. $isPreview = true;
  157. // 默认预览
  158. if ($previewFlag == 'k4') {
  159. // 标识 k4=完整,t5=预览
  160. $isPreview = false;
  161. }
  162. $m3u8FieldUrl = $arrItem['m3u8_play_url'];
  163. $itemInfo = ['url' => $m3u8FieldUrl, 'is_preview' => $isPreview];
  164. $raw = M3u8CacheRepository::getMu38Raw($itemInfo, true);
  165. return self::responseM3u8Raw($raw);
  166. }
  167. /**
  168. * @return string[]
  169. */
  170. private function getHeaders()
  171. {
  172. return ['Content-Type' => 'application/x-mpegURL'];
  173. }
  174. /**
  175. * 返回raw
  176. * @param $raw
  177. * @param bool $isError
  178. * @return \Psr\Http\Message\ResponseInterface
  179. */
  180. private function responseM3u8Raw($raw, $isError = false)
  181. {
  182. if (config('app_debug') && $isError == true && config('app_env') != 'prod') {
  183. return json_fail($raw);
  184. } else {
  185. if ($isError == true) {
  186. return response('', 200, self::getHeaders());
  187. } else {
  188. return response($raw, 200, self::getHeaders());
  189. }
  190. }
  191. }
  192. }