VideoController.php 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066
  1. <?php
  2. /**
  3. * 视频api.
  4. *
  5. * @author ningque
  6. * @date 2020-02-18 11:12:58
  7. */
  8. declare(strict_types=1);
  9. namespace Modules\Api\Controllers;
  10. use App\Jobs\RecordSearchJob;
  11. use App\Constants\AdConstrant;
  12. use App\Constants\CommonConstant;
  13. use App\Constants\VideoConstant;
  14. use App\Jobs\VideoViewedJob;
  15. use App\Model\Mongo\UserRelationModel;
  16. use App\Model\TypeModel;
  17. use App\Model\UserBuyVideoModel;
  18. use App\Model\VideoModel;
  19. use App\Repositories\ConfigRepoistory;
  20. use App\Repositories\RelationRepository;
  21. use App\Repositories\UserRepository;
  22. use App\Repositories\Utils\M3u8CacheRepository;
  23. use App\Repositories\VideoRepository;
  24. use Carbon\Carbon;
  25. use Hyperf\Validation\Contract\ValidatorFactoryInterface;
  26. use Modules\Api\Services\VideoService;
  27. use Psr\SimpleCache\CacheInterface;
  28. class VideoController extends BaseController
  29. {
  30. /**
  31. * 视频列表
  32. *
  33. * @param VideoService $videoService
  34. * @param ValidatorFactoryInterface $validatorFactory
  35. * @return void
  36. */
  37. public function list(VideoService $videoService, ValidatorFactoryInterface $validatorFactory)
  38. {
  39. $inputData = $this->getJsonData();
  40. $rules = [
  41. 'category_id' => 'required|int',
  42. 'page' => 'nullable|int',
  43. 'page_size' => 'nullable|int',
  44. ];
  45. $validator = $validatorFactory->make($inputData, $rules);
  46. if ($validator->fails()) {
  47. // 如果有错误(可能是多个信息),这里只取第一个错误信息返回.
  48. return json_fail($validator->errors()->first());
  49. }
  50. $page = $inputData['page'] ?? 1;
  51. $pageSize = $inputData['page_size'] ?? CommonConstant::PAGE_SIZE;
  52. if ($pageSize > 100) {
  53. $pageSize = CommonConstant::PAGE_SIZE;
  54. }
  55. $page = format_page($page);
  56. $pageSize = intval($pageSize);
  57. if ($pageSize <= 0) {
  58. $pageSize = CommonConstant::PAGE_SIZE;
  59. }
  60. $videoService = make(VideoService::class);
  61. $arrAds = $videoService->getAdList(AdConstrant::VIDEO_LIST);
  62. $adStep = 1;
  63. $configRepo = make(ConfigRepoistory::class);
  64. $stepDelimiter = intval($configRepo->get('ad_delimiter_num', 'sys', 10)); // 间隔视频几个出现视频.
  65. $videoRepo = make(VideoRepository::class);
  66. $columns = ['id','title', 'cover', 'like', 'uid', 'coins', 'pay_type', 'comment', 'watch', 'resolution', 'duration'];
  67. $arrData = $result = VideoModel::query()
  68. ->with(['type:type.id as type_id,type.title'])
  69. ->where(['status' => 1, 'category_id' => $inputData['category_id']])
  70. ->orderByDesc('online_at')
  71. ->orderByDesc('created_at')
  72. ->paginate($pageSize, $columns, 'page', $page)->toArray();
  73. $arrData['data'] = [];
  74. if (!empty($result['data'])) {
  75. foreach ($result['data'] as $k => $v) {
  76. $v['cover'] = M3u8CacheRepository::getImageUrl($v['cover']);
  77. $arrData['data'][] = [
  78. 'type' => 'v',
  79. 'video' => $v,
  80. ];
  81. if ($k != 0 && $adStep % $stepDelimiter == 0 && !empty($arrAds)) {
  82. $tmpArrPlay = $videoRepo->getRandAdFromAds($arrAds);
  83. $tmpArrPlay['cover'] = !empty($tmpArrPlay['cover']) ? M3u8CacheRepository::getImageUrl($tmpArrPlay['cover']) : '';
  84. unset($tmpArrPlay['id']);
  85. $arrData['data'][] = [
  86. 'type' => 'a',
  87. 'video' => $tmpArrPlay,
  88. ];
  89. }
  90. $adStep++;
  91. }
  92. }
  93. return json_success($arrData);
  94. }
  95. /**
  96. * 首页数据
  97. * @return mixed
  98. */
  99. public function home()
  100. {
  101. $videoRepo = make(VideoRepository::class);
  102. $relationRepo = make(RelationRepository::class);
  103. $userCode = $this->getUserId();
  104. $userInfo = make(UserRepository::class)->getUserInfoFromCache($userCode);
  105. $userType = null;
  106. if (isset($userInfo['is_vip']) && $userInfo['is_vip'] == 'n') {
  107. $userType = 'no_vip';
  108. }
  109. $maxPondNum = $videoRepo->getMaxPondNum($userType);
  110. if ($maxPondNum <= 0) {
  111. // 容错,如果系统里没有任何首页非会员视频
  112. $userType = null;
  113. $maxPondNum = $videoRepo->getMaxPondNum($userType);
  114. }
  115. $maxNum = $maxPondNum - 1;
  116. if ($maxNum < 0) {
  117. $maxNum = 0;
  118. }
  119. // $videoRepo->clearHomeVideoGroup($groupStep); // 本地开发测试,永远清理桶缓存.
  120. $groupStep = strval(rand(0, $maxNum));
  121. // $cacheKey = VideoConstant::HOME_GROUP_CACHE_KEY_PREFIX . $groupStep;
  122. // if ($userType == 'no_vip') {
  123. // $cacheKey = VideoConstant::HOME_NOVIP_GROUP_CACHE_KEY_PREFIX . $groupStep;
  124. // }
  125. // $cacheRepo = make(CacheInterface::class);
  126. // $arrVideos = $cacheRepo->get($cacheKey, []);
  127. $arrVideos = $videoRepo->makeOneGroup($groupStep, $userType);
  128. if (!empty($arrVideos)) {
  129. foreach ($arrVideos as $k => &$v) {
  130. if ($v['type'] == 'v') {
  131. $v['video']['mu'] = '';
  132. // $v['video']['mu'] = M3u8CacheRepository::getVideoInnerUrl($v['video']['mu'], $userCode, $v['video']['id'], 'video');
  133. $v['video']['cover'] = M3u8CacheRepository::getImageUrl($v['video']['cover']);
  134. if (!empty($v['video']['user'])) {
  135. $v['video']['user']['avatar'] = !empty($v['video']['user']['avatar']) ? M3u8CacheRepository::getImageUrl($v['video']['user']['avatar']) : '';
  136. }
  137. // 优化,前端每个视频单独请求详情.
  138. // if (!empty($v['video']['user'])) {
  139. // $v['video']['user']['is_focus'] = $relationRepo->userIsFocusOtherUser($this->getUserId(), $v['video']['user']['code']);
  140. // }
  141. // $v['video']['is_my_like'] = $relationRepo->userIsLikeVideo($userCode, $v['video']['id']);
  142. } elseif ($v['type'] == 'a') {
  143. if (!empty($v['video']['cover'])) {
  144. $v['video']['cover'] = M3u8CacheRepository::getImageUrl($v['video']['cover']);
  145. }
  146. if (!empty($v['video']['link'])) {
  147. $v['video']['link'] = M3u8CacheRepository::getLinkUrl($v['video']['link'], ['user_code' => $userCode]);
  148. }
  149. if (!empty($v['video']['play'])) {
  150. $v['video']['play'] = M3u8CacheRepository::getVideoInnerUrl($v['video']['play'], $userCode, $v['video']['id'], 'ad');
  151. }
  152. unset($v['video']['id']);
  153. }
  154. }
  155. }
  156. return json_success($arrVideos);
  157. }
  158. /**
  159. * 视频详情
  160. * @return mixed
  161. */
  162. public function info(
  163. VideoService $videoService,
  164. ValidatorFactoryInterface $validatorFactory
  165. ) {
  166. $inputData = $this->getJsonData();
  167. $rules = [
  168. 'video_id' => 'required|int'
  169. ];
  170. $validator = $validatorFactory->make($inputData, $rules);
  171. if ($validator->fails()) {
  172. // 如果有错误(可能是多个信息),这里只取第一个错误信息返回.
  173. return json_fail($validator->errors()->first());
  174. }
  175. $userCode = $this->getUserId();
  176. $result = $videoService->info($inputData['video_id'], $userCode);
  177. return json_success($result);
  178. }
  179. /**
  180. * 首页关注视频
  181. * @return mixed
  182. */
  183. public function recommend(ValidatorFactoryInterface $validatorFactory)
  184. {
  185. $inputData = $this->getJsonData();
  186. $rules = [
  187. 'page' => 'nullable|int',
  188. 'page_size' => 'nullable|int',
  189. ];
  190. $validator = $validatorFactory->make($inputData, $rules);
  191. if ($validator->fails()) {
  192. // 如果有错误(可能是多个信息),这里只取第一个错误信息返回.
  193. return json_fail($validator->errors()->first());
  194. }
  195. $page = $inputData['page'] ?? 1;
  196. $pageSize = $inputData['page_size'] ?? CommonConstant::PAGE_SIZE;
  197. if ($pageSize > 100) {
  198. $pageSize = CommonConstant::PAGE_SIZE;
  199. }
  200. $page = format_page($page);
  201. $pageSize = intval($pageSize);
  202. if ($pageSize <= 0) {
  203. $pageSize = CommonConstant::PAGE_SIZE;
  204. }
  205. $videoService = make(VideoService::class);
  206. $arrAds = $videoService->getAdList(AdConstrant::VIDEO_HOME);
  207. // $arrAds = [];
  208. $adStep = 1;
  209. $configRepo = make(ConfigRepoistory::class);
  210. $stepDelimiter = intval($configRepo->get('ad_delimiter_num', 'sys', 10)); // 间隔视频几个出现视频.
  211. $videoRepo = make(VideoRepository::class);
  212. $relationRepo = make(RelationRepository::class);
  213. $userCode = $this->getUserId();
  214. $columns = ['id','title', 'cover', 'like', 'uid', 'coins', 'pay_type', 'mu', 'comment', 'watch', 'resolution', 'duration'];
  215. //查找100个关注的code
  216. $relation = make(UserRelationModel::class)->db()
  217. ->find(
  218. ['from_user_id' => $this->getUserId()],
  219. ['limit' => 100]
  220. );
  221. $arrData = [];
  222. $arrUserIds = [];
  223. $arrRelation = $relation->toArray();
  224. if ($arrRelation) {
  225. foreach ($arrRelation as $v) {
  226. $arrUserIds[] = $v['to_user_id'];
  227. }
  228. $arrData = $result = VideoModel::query()
  229. ->with(['category:type.id as type_id,type.title','user'])
  230. ->whereIn('uid', $arrUserIds)
  231. ->where(['status' => 1])
  232. //->orderByRaw("field(uid," . $id_str . ")")
  233. ->orderByDesc('online_at')
  234. ->orderByDesc('created_at')
  235. ->paginate($pageSize, $columns, 'page', $page)->toArray();
  236. $arrData['data'] = [];
  237. if (!empty($result['data'])) {
  238. foreach ($result['data'] as $k => $v) {
  239. $v['mu'] = ''; // 列表干掉m3u8地址.
  240. // $v['mu'] = M3u8CacheRepository::getVideoInnerUrl($v['mu'], $userCode, $v['id'], 'video');
  241. $v['cover'] = M3u8CacheRepository::getImageUrl($v['cover']);
  242. if (!empty($v['user'])) {
  243. $v['user']['avatar'] = M3u8CacheRepository::getImageUrl($v['user']['avatar']);
  244. // $v['user']['is_focus'] = $relationRepo->userIsFocusOtherUser($userCode, $v['user']['code']);
  245. }
  246. // $v['is_my_like'] = $relationRepo->userIsLikeVideo($userCode, $v['id']);
  247. if (!empty($v['category'])) {
  248. foreach ($v['category'] as $ck => $t) {
  249. unset($v['category'][$ck]['pivot']);
  250. }
  251. }
  252. $arrData['data'][] = [
  253. 'type' => 'v',
  254. 'video' => $v,
  255. ];
  256. if ($k != 0 && $adStep % $stepDelimiter == 0 && !empty($arrAds)) {
  257. $tmpArrPlay = $videoRepo->getRandAdFromAds($arrAds);
  258. $tmpArrPlay['cover'] = !empty($tmpArrPlay['cover']) ? M3u8CacheRepository::getImageUrl($tmpArrPlay['cover']) : '';
  259. if (!empty($tmpArrPlay['link'])) {
  260. $tmpArrPlay['link'] = M3u8CacheRepository::getLinkUrl($tmpArrPlay['link'], ['user_code' => $userCode]);
  261. }
  262. if (!empty($tmpArrPlay['play'])) {
  263. $tmpArrPlay['play'] = M3u8CacheRepository::getVideoInnerUrl($tmpArrPlay['play'], $userCode, $tmpArrPlay['id'], 'ad');
  264. }
  265. unset($tmpArrPlay['id']);
  266. $arrData['data'][] = [
  267. 'type' => 'a',
  268. 'video' => $tmpArrPlay,
  269. ];
  270. }
  271. $adStep++;
  272. }
  273. }
  274. }
  275. return json_success($arrData);
  276. }
  277. /**
  278. * 搜索口味
  279. * @return mixed
  280. */
  281. public function searchTag(ValidatorFactoryInterface $validatorFactory)
  282. {
  283. $inputData = $this->getJsonData();
  284. $rules = [
  285. 'keywords' => 'required|string|max:64',
  286. 'page' => 'nullable|int',
  287. 'page_size' => 'nullable|int',
  288. ];
  289. $validator = $validatorFactory->make($inputData, $rules);
  290. if ($validator->fails()) {
  291. // 如果有错误(可能是多个信息),这里只取第一个错误信息返回.
  292. return json_fail($validator->errors()->first());
  293. }
  294. $userCode = $this->getUserId();
  295. $defaultPageSize = 10;
  296. $page = !empty($inputData['page']) ? intval($inputData['page']) : 1;
  297. $pageSize = !empty($inputData['page_size']) ? intval($inputData['page_size']) : $defaultPageSize;
  298. if ($pageSize > 100) {
  299. return json_fail('参数错误');
  300. }
  301. $page = format_page($page);
  302. if ($pageSize <= 0) {
  303. $pageSize = CommonConstant::PAGE_SIZE;
  304. }
  305. $columns = ['id', 'title', 'cover', 'description'];
  306. $data = TypeModel::query()
  307. ->withCount('video')
  308. ->where('title', 'like', '%' . $inputData['keywords'] . '%')
  309. ->paginate($pageSize, $columns, 'page', $page);
  310. $data = $data->toArray();
  311. if (!empty($data['data'])) {
  312. foreach ($data['data'] as &$v) {
  313. $v['cover'] = M3u8CacheRepository::getImageUrl($v['cover']);
  314. unset($v['created_at'], $v['updated_at']);
  315. }
  316. }
  317. // 异步记录搜索记录.
  318. $jobParams = [
  319. 'event_name' => 'tag',
  320. 'user_code' => $userCode,
  321. 'keywords' => data_get($inputData, 'keywords', ''),
  322. 'module' => 'video',
  323. ];
  324. dispatch_job(new RecordSearchJob($jobParams));
  325. return json_success($data);
  326. }
  327. /**
  328. * 搜索关键词推荐
  329. * @return mixed
  330. */
  331. public function searchTagHot()
  332. {
  333. $data = make(CacheInterface::class)->remember(
  334. 'hot_tags',
  335. 60 * 60,
  336. function () {
  337. $arrData = [];
  338. $result = TypeModel::query()->where(['is_hot' => 1, 'status' => 1])
  339. ->withCount('upvideo')
  340. ->orderByDesc('sort')
  341. ->take(10)
  342. ->get(['id', 'title', 'cover']);
  343. // $result = TypeModel::query()
  344. // ->select(['id','title', 'cover'])
  345. // ->where('status', 1)
  346. // ->withCount('video')
  347. // ->withCount('upvideo')
  348. // ->orderByDesc('sort')
  349. // ->get();
  350. if ($result->count() > 0) {
  351. $result = $result->toArray();
  352. foreach ($result as $k => $v) {
  353. $arrData[] = [
  354. 'id' => $v['id'],
  355. 'title' => $v['title'],
  356. 'cover' => M3u8CacheRepository::getImageUrl($v['cover']),
  357. 'video_count' => !empty($v['upvideo_count']) ? intval($v['upvideo_count']) : 0, //特殊处理,取上架的视频数量,不用app发新版,
  358. ];
  359. }
  360. }
  361. return $arrData;
  362. }
  363. );
  364. return json_success($data);
  365. }
  366. /**
  367. * 搜索视频
  368. * @return mixed
  369. */
  370. public function searchVideo(ValidatorFactoryInterface $validatorFactory)
  371. {
  372. $inputData = $this->getJsonData();
  373. $rules = [
  374. 'keywords' => 'required|string|max:64',
  375. 'page' => 'nullable|int',
  376. 'page_size' => 'nullable|int',
  377. ];
  378. $validator = $validatorFactory->make($inputData, $rules);
  379. if ($validator->fails()) {
  380. // 如果有错误(可能是多个信息),这里只取第一个错误信息返回.
  381. return json_fail($validator->errors()->first());
  382. }
  383. $page = $inputData['page'] ?? 1;
  384. $pageSize = $inputData['page_size'] ?? CommonConstant::PAGE_SIZE;
  385. if ($pageSize > 100) {
  386. $pageSize = CommonConstant::PAGE_SIZE;
  387. }
  388. $page = format_page($page);
  389. $pageSize = intval($pageSize);
  390. if ($pageSize <= 0) {
  391. $pageSize = CommonConstant::PAGE_SIZE;
  392. }
  393. $videoService = make(VideoService::class);
  394. // $arrAds = $videoService->getAdList(AdConstrant::VIDEO_HOME);
  395. $arrAds = [];
  396. $adStep = 1;
  397. $configRepo = make(ConfigRepoistory::class);
  398. $stepDelimiter = intval($configRepo->get('ad_delimiter_num', 'sys', 10)); // 间隔视频几个出现视频.
  399. $cacheRepo = make(CacheInterface::class);
  400. $cacheTimes = 2 * 60 * 60;
  401. $cacheKey = 'search:video_pages_'. $pageSize .':'. $page. '_' . data_get($inputData, 'keywords', '');
  402. $videoRepo = make(VideoRepository::class);
  403. $relationRepo = make(RelationRepository::class);
  404. $userCode = $this->getUserId();
  405. $cacheData = $cacheRepo->get($cacheKey, []);
  406. $arrData = [];
  407. if (empty($cacheData)) {
  408. $columns = ['id','title', 'cover', 'like', 'uid', 'coins', 'pay_type', 'comment', 'mu', 'watch', 'resolution', 'duration'];
  409. $ret = VideoModel::query()
  410. ->with(['category:type.id as type_id,type.title','user'])
  411. ->where('status', 1)
  412. ->where('title', 'like', '%'. $inputData['keywords'] .'%')
  413. ->orderByDesc('online_at')
  414. ->orderByDesc('created_at')
  415. ->paginate($pageSize, $columns, 'page', $page);
  416. if ($ret->isNotEmpty()) {
  417. $result = $ret->toArray();
  418. if (!empty($result['data'])) {
  419. $arrData = $result;
  420. $cacheRepo->set($cacheKey, $arrData, $cacheTimes);
  421. }
  422. }
  423. } else {
  424. $arrData = $cacheData;
  425. }
  426. $arrData['data'] = [];
  427. if (!empty($cacheData['data'])) {
  428. foreach ($cacheData['data'] as $k => $v) {
  429. $v['mu'] = ''; // 列表干掉m3u8地址.
  430. // $v['mu'] = M3u8CacheRepository::getVideoInnerUrl($v['mu'], $userCode, $v['id'], 'video');
  431. $v['cover'] = M3u8CacheRepository::getImageUrl($v['cover']);
  432. if (!empty($v['user'])) {
  433. $v['user']['avatar'] = M3u8CacheRepository::getImageUrl($v['user']['avatar']);
  434. // $v['user']['is_focus'] = $relationRepo->userIsFocusOtherUser($userCode, $v['user']['code']);
  435. }
  436. // $v['is_my_like'] = $relationRepo->userIsLikeVideo($userCode, $v['id']);
  437. if (!empty($v['category'])) {
  438. foreach ($v['category'] as $ck => $t) {
  439. unset($v['category'][$ck]['pivot']);
  440. }
  441. }
  442. $arrData['data'][] = [
  443. 'type' => 'v',
  444. 'video' => $v,
  445. ];
  446. if ($k != 0 && $adStep % $stepDelimiter == 0 && !empty($arrAds)) {
  447. $tmpArrPlay = $videoRepo->getRandAdFromAds($arrAds);
  448. $tmpArrPlay['cover'] = !empty($tmpArrPlay['cover']) ? M3u8CacheRepository::getImageUrl($tmpArrPlay['cover']) : '';
  449. if (!empty($tmpArrPlay['play'])) {
  450. $tmpArrPlay['play'] = M3u8CacheRepository::getVideoInnerUrl($tmpArrPlay['play'], $userCode, $tmpArrPlay['id'], 'ad');
  451. }
  452. unset($tmpArrPlay['id']);
  453. $arrData['data'][] = [
  454. 'type' => 'a',
  455. 'video' => $tmpArrPlay,
  456. ];
  457. }
  458. $adStep++;
  459. }
  460. }
  461. // 异步记录搜索记录.
  462. $playerTypeName = 'short';
  463. $jobParams = [
  464. 'event_name' => 'video_'. $playerTypeName,
  465. 'user_code' => $userCode,
  466. 'keywords' => data_get($inputData, 'keywords', ''),
  467. 'module' => 'video',
  468. ];
  469. dispatch_job(new RecordSearchJob($jobParams));
  470. return json_success($arrData);
  471. }
  472. /**
  473. * 搜索视频
  474. * @return mixed
  475. */
  476. public function searchHot(ValidatorFactoryInterface $validatorFactory)
  477. {
  478. $inputData = $this->getJsonData();
  479. $rules = [
  480. 'keywords' => 'required|string',
  481. 'page' => 'nullable|int',
  482. 'page_size' => 'nullable|int',
  483. ];
  484. $validator = $validatorFactory->make($inputData, $rules);
  485. if ($validator->fails()) {
  486. // 如果有错误(可能是多个信息),这里只取第一个错误信息返回.
  487. return json_fail($validator->errors()->first());
  488. }
  489. $page = $inputData['page'] ?? 1;
  490. $pageSize = $inputData['page_size'] ?? CommonConstant::PAGE_SIZE;
  491. if ($pageSize > 100) {
  492. $pageSize = CommonConstant::PAGE_SIZE;
  493. }
  494. $page = format_page($page);
  495. $pageSize = intval($pageSize);
  496. if ($pageSize <= 0) {
  497. $pageSize = CommonConstant::PAGE_SIZE;
  498. }
  499. $relationRepo = make(RelationRepository::class);
  500. $cacheRepo = make(CacheInterface::class);
  501. $cacheTimes = 2 * 60 * 60;
  502. $cacheKey = 'search:video_pages_'. $pageSize .':'. $page. '_' . data_get($inputData, 'keywords', '');
  503. $userCode = $this->getUserId();
  504. $cacheData = $cacheRepo->get($cacheKey, []);
  505. $result = [];
  506. if (empty($cacheData)) {
  507. $columns = ['id','title', 'cover', 'like', 'uid', 'coins', 'pay_type', 'mu', 'comment', 'watch', 'resolution', 'duration'];
  508. $ret = VideoModel::query()
  509. ->select()
  510. ->with(['category:type.id as type_id,type.title','user'])
  511. ->where('title', 'like', '%'. $inputData['keywords'] .'%')
  512. ->where('status', 1)
  513. ->orderByDesc('online_at')
  514. ->orderByDesc('created_at')
  515. ->paginate($pageSize, $columns, 'page', $page);
  516. if ($ret->isNotEmpty()) {
  517. $result = $ret->toArray();
  518. $cacheRepo->set($cacheKey, $result, $cacheTimes);
  519. }
  520. }
  521. if (!empty($result['data'])) {
  522. foreach ($result['data'] as $k => $v) {
  523. $v['mu'] = ''; // 列表干掉m3u8地址.
  524. // $v['mu'] = M3u8CacheRepository::getVideoInnerUrl($v['mu'], $userCode, $v['id'], 'video');
  525. $v['cover'] = M3u8CacheRepository::getImageUrl($v['cover']);
  526. if (!empty($result[$k]['user'])) {
  527. $v['user']['avatar'] = M3u8CacheRepository::getImageUrl($v['user']['avatar']);
  528. // $v['user']['is_focus'] = $relationRepo->userIsFocusOtherUser($userCode, $v['user']['code']);
  529. }
  530. // $v['is_my_like'] = $relationRepo->userIsLikeVideo($userCode, $v['id']);
  531. if (!empty($v['category'])) {
  532. foreach ($v['category'] as $ck => $t) {
  533. unset($v['category'][$ck]['pivot']);
  534. }
  535. }
  536. }
  537. }
  538. // 异步记录搜索记录.
  539. $playerTypeName = 'short';
  540. $jobParams = [
  541. 'event_name' => 'video_'. $playerTypeName,
  542. 'user_code' => $userCode,
  543. 'keywords' => data_get($inputData, 'keywords', ''),
  544. 'module' => 'video',
  545. ];
  546. dispatch_job(new RecordSearchJob($jobParams));
  547. return json_success($result);
  548. }
  549. /**
  550. * 搜索视频推荐
  551. * @return mixed
  552. */
  553. public function searchVideoHot()
  554. {
  555. $userCode = $this->getUserId();
  556. $relationRepo = make(RelationRepository::class);
  557. $result = make(CacheInterface::class)->remember(
  558. 'hot_video',
  559. 60 * 60,
  560. function () use ($relationRepo) {
  561. $columns = ['id','title', 'cover', 'like', 'uid', 'coins', 'pay_type', 'mu', 'comment', 'watch', 'resolution', 'duration'];
  562. $arrData = [];
  563. $result = VideoModel::query()
  564. ->with(['category:type.id as type_id,type.title','user'])
  565. ->where('status', 1)
  566. ->orderByDesc('like')
  567. ->limit(200)
  568. ->get($columns)->toArray();
  569. foreach ($result as $k => $v) {
  570. // $result[$k]['mu'] = M3u8CacheRepository::getVideoUrl($result[$k]['mu']);
  571. // $v['is_my_like'] = false;
  572. if (!empty($v['category'])) {
  573. foreach ($v['category'] as $ck => $t) {
  574. unset($v['category'][$ck]['pivot']);
  575. }
  576. }
  577. $arrData[$k] = [
  578. 'type' => 'v',
  579. 'video' => $v,
  580. ];
  581. }
  582. unset($result);
  583. return $arrData;
  584. }
  585. );
  586. if (!empty($result)) {
  587. foreach ($result as &$v) {
  588. if ($v['type'] == 'v') {
  589. $v['video']['mu'] = ''; // 列表干掉m3u8地址.
  590. $v['video']['cover'] = M3u8CacheRepository::getImageUrl($v['video']['cover']);
  591. if (!empty($v['video']['user'])) {
  592. $v['video']['user']['avatar'] = M3u8CacheRepository::getImageUrl($v['video']['user']['avatar']);
  593. // $v['user']['is_focus'] = false;
  594. }
  595. // $v['video']['mu'] = M3u8CacheRepository::getVideoInnerUrl($v['video']['mu'], $userCode, $v['video']['id'], 'video');
  596. // if (!empty($v['video']['user'])) {
  597. // $v['video']['user']['is_focus'] = $relationRepo->userIsFocusOtherUser($userCode, $v['video']['user']['code']);
  598. // }
  599. // $v['video']['is_my_like'] = $relationRepo->userIsLikeVideo($userCode, $v['video']['id']);
  600. }
  601. }
  602. }
  603. return json_success($result);
  604. }
  605. /**
  606. * 搜索视频通过标签
  607. * @return mixed
  608. */
  609. public function getVideoByType(ValidatorFactoryInterface $validatorFactory)
  610. {
  611. $inputData = $this->getJsonData();
  612. $rules = [
  613. 'type_id' => 'required|int',
  614. 'page' => 'nullable|int',
  615. 'page_size' => 'nullable|int',
  616. ];
  617. $validator = $validatorFactory->make($inputData, $rules);
  618. if ($validator->fails()) {
  619. // 如果有错误(可能是多个信息),这里只取第一个错误信息返回.
  620. return json_fail($validator->errors()->first());
  621. }
  622. $page = $inputData['page'] ?? 1;
  623. $pageSize = $inputData['page_size'] ?? CommonConstant::PAGE_SIZE;
  624. if ($pageSize > 100) {
  625. $pageSize = CommonConstant::PAGE_SIZE;
  626. }
  627. $page = format_page($page);
  628. $pageSize = intval($pageSize);
  629. if ($pageSize <= 0) {
  630. $pageSize = CommonConstant::PAGE_SIZE;
  631. }
  632. // $videoService = make(VideoService::class);
  633. // $arrAds = $videoService->getAdList(AdConstrant::VIDEO_HOME);
  634. $arrAds = [];
  635. $adStep = 0;
  636. $configRepo = make(ConfigRepoistory::class);
  637. $stepDelimiter = intval($configRepo->get('ad_delimiter_num', 'sys', 10)); // 间隔视频几个出现视频.
  638. $videoRepo = make(VideoRepository::class);
  639. $relationRepo = make(RelationRepository::class);
  640. $userCode = $this->getUserId();
  641. $cacheRepo = make(CacheInterface::class);
  642. $cacheTimes = 2 * 60 * 60;
  643. $cacheKey = 'search:video_by_tag_pages_'. $pageSize .':'. $page. '_' . data_get($inputData, 'keywords', '');
  644. $cacheData = $cacheRepo->get($cacheKey, []);
  645. $arrData = $result = [];
  646. if (empty($cacheData)) {
  647. $columns = ['video.id','video.title', 'video.cover', 'video.mu', 'video.like', 'video.uid', 'video.comment', 'video.watch','video.resolution', 'video.duration'];
  648. $ret = VideoModel::query()
  649. ->leftJoin('type_video as tv', 'video.id', '=', 'tv.video_id')
  650. ->with(['category:type.id as type_id,type.title','user'])
  651. ->where('tv.type_id', $inputData['type_id'])
  652. ->where('video.status', 1)
  653. ->orderByDesc('video.online_at')
  654. ->paginate($pageSize, $columns, 'page', $page);
  655. if ($ret->isNotEmpty()) {
  656. $arrData = $result = $ret->toArray();
  657. $cacheRepo->set($cacheKey, $result, $cacheTimes);
  658. }
  659. } else {
  660. $arrData = $cacheData;
  661. }
  662. $arrData['data'] = [];
  663. if (!empty($result['data'])) {
  664. foreach ($result['data'] as $k => $v) {
  665. $v['mu'] = '';
  666. // $v['mu'] = M3u8CacheRepository::getVideoInnerUrl($v['mu'], $userCode, $v['id'], 'video');
  667. $v['cover'] = M3u8CacheRepository::getImageUrl($v['cover']);
  668. if (!empty($v['user'])) {
  669. $v['user']['avatar'] = M3u8CacheRepository::getImageUrl($v['user']['avatar']);
  670. // $v['user']['is_focus'] = $relationRepo->userIsFocusOtherUser($userCode, $v['user']['code']);
  671. }
  672. // $v['is_my_like'] = $relationRepo->userIsLikeVideo($userCode, $v['id']);
  673. if (!empty($v['category'])) {
  674. foreach ($v['category'] as $ck => $t) {
  675. unset($v['category'][$ck]['pivot']);
  676. }
  677. }
  678. $arrData['data'][] = [
  679. 'type' => 'v',
  680. 'video' => $v,
  681. ];
  682. if ($k != 0 && $adStep % $stepDelimiter == 0 && !empty($arrAds)) {
  683. $tmpArrPlay = $videoRepo->getRandAdFromAds($arrAds);
  684. $tmpArrPlay['cover'] = !empty($tmpArrPlay['cover']) ? M3u8CacheRepository::getImageUrl($tmpArrPlay['cover']) : '';
  685. if (!empty($tmpArrPlay['link'])) {
  686. $tmpArrPlay['link'] = M3u8CacheRepository::getLinkUrl($tmpArrPlay['link'], ['user_code' => $userCode]);
  687. }
  688. if (!empty($tmpArrPlay['play'])) {
  689. $tmpArrPlay['play'] = M3u8CacheRepository::getVideoInnerUrl($tmpArrPlay['play'], $userCode, $tmpArrPlay['id'], 'ad');
  690. }
  691. unset($tmpArrPlay['id']);
  692. $arrData['data'][] = [
  693. 'type' => 'a',
  694. 'video' => $tmpArrPlay,
  695. ];
  696. }
  697. $adStep++;
  698. }
  699. }
  700. return json_success($arrData);
  701. }
  702. /**
  703. * 搜索视频通过用户code
  704. * @return mixed
  705. */
  706. public function userVideoList(ValidatorFactoryInterface $validatorFactory)
  707. {
  708. $inputData = $this->getJsonData();
  709. $rules = [
  710. 'code' => 'required|string',
  711. 'page' => 'nullable|int',
  712. 'page_size' => 'nullable|int',
  713. ];
  714. $validator = $validatorFactory->make($inputData, $rules);
  715. if ($validator->fails()) {
  716. // 如果有错误(可能是多个信息),这里只取第一个错误信息返回.
  717. return json_fail($validator->errors()->first());
  718. }
  719. $page = $inputData['page'] ?? 1;
  720. $pageSize = $inputData['page_size'] ?? CommonConstant::PAGE_SIZE;
  721. $pageSize = intval($pageSize);
  722. if ($pageSize > 100) {
  723. $pageSize = CommonConstant::PAGE_SIZE;
  724. }
  725. $page = format_page($page);
  726. if ($pageSize <= 0) {
  727. $pageSize = CommonConstant::PAGE_SIZE;
  728. }
  729. $userCode = $this->getUserId();
  730. $relationRepo = make(RelationRepository::class);
  731. $columns = ['id','title', 'cover', 'like', 'uid', 'coins', 'pay_type', 'comment', 'mu', 'watch', 'resolution', 'duration'];
  732. $result = VideoModel::query()
  733. ->with(['category:type.id as type_id,type.title','user'])
  734. ->where('uid', $inputData['code'])
  735. ->where('status', 1)
  736. ->orderByDesc('online_at')
  737. ->orderByDesc('created_at')
  738. ->paginate(
  739. $pageSize,
  740. $columns,
  741. 'page',
  742. $page
  743. )->toArray();
  744. if (!empty($result['data'])) {
  745. foreach ($result['data'] as $k => &$v) {
  746. $v['mu'] = ''; // 列表干掉m3u8地址.
  747. // $v['mu'] = M3u8CacheRepository::getVideoInnerUrl($v['mu'], $userCode, $v['id'], 'video');
  748. $v['cover'] = M3u8CacheRepository::getImageUrl($v['cover']);
  749. if (!empty($v['user'])) {
  750. $v['user']['avatar'] = M3u8CacheRepository::getImageUrl($v['user']['avatar']);
  751. // $v['user']['is_focus'] = $relationRepo->userIsFocusOtherUser($userCode, $v['user']['code']);
  752. }
  753. // $v['is_my_like'] = $relationRepo->userIsLikeVideo($userCode, $v['id']);
  754. if (!empty($v['category'])) {
  755. foreach ($v['category'] as $ck => $t) {
  756. unset($v['category'][$ck]['pivot']);
  757. }
  758. }
  759. }
  760. }
  761. return json_success($result);
  762. }
  763. /**
  764. * 推送已观看的视频.
  765. * @param ValidatorFactoryInterface $validatorFactory
  766. * @return mixed
  767. */
  768. public function pushViewed(ValidatorFactoryInterface $validatorFactory)
  769. {
  770. $inputData = $this->getJsonData();
  771. $rules = [
  772. 'ids' => 'required',
  773. ];
  774. $validator = $validatorFactory->make($inputData, $rules);
  775. if ($validator->fails()) {
  776. // 如果有错误(可能是多个信息),这里只取第一个错误信息返回.
  777. return json_fail($validator->errors()->first());
  778. }
  779. $userCode = $this->getUserId();
  780. $date = Carbon::now()->toDateString();
  781. $arrIds = [];
  782. $ids = $inputData['ids'];
  783. if (is_string($ids)) {
  784. $arrIds = explode(',', $ids);
  785. $arrIds = array_unique($arrIds);
  786. $arrIds = array_filter($arrIds);
  787. } else {
  788. $arrIds = $ids;
  789. }
  790. $count = count($arrIds);
  791. if ($count > 100) {
  792. return json_fail('too many id');
  793. }
  794. $userInfo = make(UserRepository::class)->getUserInfoData($userCode);
  795. // 今日剩余观影次数
  796. $videoSurplus = isset($userInfo['video_surplus']) ? $userInfo['video_surplus'] : 0;
  797. $oldSurplus = $videoSurplus;
  798. if ($userInfo['is_vip'] == 'y') {
  799. $videoSurplus = 999999;
  800. } else {
  801. // 这里只是初略计算,真实计算考异步job完成后才能计算出来.
  802. $videoSurplus = $videoSurplus - $count;
  803. $oldSurplus = $videoSurplus;
  804. }
  805. $isNegativeSurplus = false; // 是否是负数.
  806. if ($videoSurplus < 0) {
  807. // 容错.
  808. $videoSurplus = 0;
  809. $isNegativeSurplus = true;
  810. }
  811. $jobParams = [];
  812. if (!empty($arrIds)) {
  813. $absSurplus = abs($oldSurplus);
  814. foreach ($arrIds as $k => $vid) {
  815. if ($isNegativeSurplus && $k == $absSurplus) {
  816. break;
  817. }
  818. $jobParams[] = [
  819. 'date' => $date,
  820. 'user_id' => $userInfo['id'],
  821. 'user_code' => $userInfo['code'],
  822. 'video_id' => $vid,
  823. ];
  824. }
  825. // 异步处理.
  826. if (!empty($jobParams)) {
  827. dispatch_job(new VideoViewedJob($jobParams));
  828. }
  829. }
  830. $resultData = [
  831. 'video_surplus' => $videoSurplus,
  832. ];
  833. return json_success($resultData);
  834. }
  835. // 购买视频
  836. public function buy()
  837. {
  838. $validatorFactory = make(ValidatorFactoryInterface::class);
  839. $videoService = make(VideoService::class);
  840. $inputData = $this->getJsonData();
  841. $rules = [
  842. 'video_id' => 'required|int'
  843. ];
  844. $validator = $validatorFactory->make($inputData, $rules);
  845. if ($validator->fails()) {
  846. // 如果有错误(可能是多个信息),这里只取第一个错误信息返回.
  847. return json_fail($validator->errors()->first());
  848. }
  849. $userCode = $this->getUserId();
  850. $videoId = intval(data_get($inputData, 'video_id', 0));
  851. $videoObj = $videoService->buyVideo($userCode, $videoId);
  852. $playMu = M3u8CacheRepository::getVideoInnerUrl($videoObj->mu, $userCode, $videoObj->id, 'video');
  853. $result = [
  854. 'video_id' => $videoId,
  855. 'mu' => $playMu,
  856. ];
  857. return json_success($result);
  858. }
  859. // 我已购买的视频
  860. public function myBoughtPages()
  861. {
  862. $validatorFactory = make(ValidatorFactoryInterface::class);
  863. $inputData = $this->getJsonData();
  864. $rules = [
  865. 'page' => 'nullable|int',
  866. 'page_size' => 'nullable|int',
  867. ];
  868. $validator = $validatorFactory->make($inputData, $rules);
  869. if ($validator->fails()) {
  870. // 如果有错误(可能是多个信息),这里只取第一个错误信息返回.
  871. return json_fail($validator->errors()->first());
  872. }
  873. $page = $inputData['page'] ?? 1;
  874. $pageSize = $inputData['page_size'] ?? CommonConstant::PAGE_SIZE;
  875. if ($pageSize > 100) {
  876. $pageSize = CommonConstant::PAGE_SIZE;
  877. }
  878. $page = format_page($page);
  879. $pageSize = intval($pageSize);
  880. if ($pageSize <= 0) {
  881. $pageSize = CommonConstant::PAGE_SIZE;
  882. }
  883. $userCode = $this->getUserId();
  884. $user = make(UserRepository::class)->getUserInfoData($userCode);
  885. $videoColumns = ['id','title', 'cover', 'like', 'uid', 'coins', 'pay_type', 'mu', 'comment', 'watch', 'resolution', 'duration', 'status', 'check'];
  886. $strVideoColumns = implode(',', $videoColumns);
  887. $lists = UserBuyVideoModel::query()->with(['video:'. $strVideoColumns, 'video.user'])
  888. ->where(['user_id' => $user['id']])
  889. ->whereHas('video', function ($q) {
  890. $q->where(['status' => 1])->with(['user']);
  891. })
  892. ->orderBy('id', 'DESC')
  893. ->paginate($pageSize, ['*'], 'page', $page);
  894. $lists = $lists->toArray();
  895. if (!empty($lists['data'])) {
  896. foreach ($lists['data'] as $k => $item) {
  897. $v = $item['video'];
  898. $v['user'] = $item['video']['user'] ?? null;
  899. $v['mu'] = ''; // 列表干掉m3u8地址.
  900. // $v['mu'] = M3u8CacheRepository::getVideoInnerUrl($v['mu'], $userCode, $v['id'], 'video');
  901. $v['cover'] = M3u8CacheRepository::getImageUrl($v['cover']);
  902. if (!empty($v['user'])) {
  903. $v['user']['avatar'] = M3u8CacheRepository::getImageUrl($v['user']['avatar']);
  904. // $v['user']['is_focus'] = $relationRepo->userIsFocusOtherUser($userCode, $v['user']['code']);
  905. unset($v['user']['id']);
  906. }
  907. // $item['is_my_like'] = $relationRepo->userIsLikeVideo($userCode, $videoObj->id);
  908. if (!empty($v['category'])) {
  909. foreach ($v['category'] as $ck => $t) {
  910. unset($v['category'][$ck]['pivot']);
  911. }
  912. } else {
  913. $v['category'] = null;
  914. }
  915. unset($v['status'], $v['check']);
  916. $v['buy_at'] = $item['created_at']; // 购买时间.
  917. $lists['data'][$k] = $v;
  918. }
  919. }
  920. return json_success($lists);
  921. }
  922. /**
  923. * 最新视频分页.
  924. * @return mixed
  925. * @throws \Psr\SimpleCache\InvalidArgumentException
  926. */
  927. public function lastestPages()
  928. {
  929. $userCode = $this->getUserId();
  930. $validatorFactory = make(ValidatorFactoryInterface::class);
  931. $inputData = $this->getJsonData();
  932. $rules = [
  933. 'page' => 'nullable|int',
  934. 'page_size' => 'nullable|int',
  935. ];
  936. $validator = $validatorFactory->make($inputData, $rules);
  937. if ($validator->fails()) {
  938. // 如果有错误(可能是多个信息),这里只取第一个错误信息返回.
  939. return json_fail($validator->errors()->first());
  940. }
  941. $page = $inputData['page'] ?? 1;
  942. $pageSize = $inputData['page_size'] ?? CommonConstant::PAGE_SIZE;
  943. if ($pageSize > 100) {
  944. $pageSize = CommonConstant::PAGE_SIZE;
  945. }
  946. $page = format_page($page);
  947. $pageSize = intval($pageSize);
  948. $videoRepo = make(VideoRepository::class);
  949. // 不让前端随意传,写死10
  950. $pageSize = VideoConstant::LASTEST_PAGE_SIZE;
  951. $arrData = [];
  952. $lists = $videoRepo->lastestPages($page, $pageSize);
  953. if (!empty($lists['data'])) {
  954. $relationRepo = make(RelationRepository::class);
  955. $videoService = make(VideoService::class);
  956. $arrAds = $videoService->getAdList(AdConstrant::VIDEO_HOME);
  957. $adStep = 1;
  958. $configRepo = make(ConfigRepoistory::class);
  959. $stepDelimiter = intval($configRepo->get('ad_delimiter_num', 'sys', 10)); // 间隔视频几个出现视频.
  960. foreach ($lists['data'] as $k => $v) {
  961. $v['mu'] = ''; // 列表干掉m3u8地址.
  962. // $v['mu'] = M3u8CacheRepository::getVideoInnerUrl($v['mu'], $userCode, $v['id'], 'video');
  963. $v['cover'] = M3u8CacheRepository::getImageUrl($v['cover']);
  964. if (!empty($v['user'])) {
  965. $v['user']['avatar'] = !empty($v['user']['avatar']) ? M3u8CacheRepository::getImageUrl($v['user']['avatar']) : '';
  966. }
  967. if (!empty($v['user'])) {
  968. $v['user']['is_focus'] = $relationRepo->userIsFocusOtherUser($userCode, $v['user']['code']);
  969. }
  970. $v['is_my_like'] = $relationRepo->userIsLikeVideo($userCode, $v['id']);
  971. unset($v['status'], $v['check']);
  972. $arrData[] = [
  973. 'type' => 'v',
  974. 'video' => $v,
  975. ];
  976. if ($k != 0 && $adStep % $stepDelimiter == 0 && !empty($arrAds)) {
  977. $tmpArrPlay = $videoRepo->getRandAdFromAds($arrAds);
  978. $tmpArrPlay['cover'] = !empty($tmpArrPlay['cover']) ? M3u8CacheRepository::getImageUrl($tmpArrPlay['cover']) : '';
  979. if (!empty($tmpArrPlay['link'])) {
  980. $tmpArrPlay['link'] = M3u8CacheRepository::getLinkUrl($tmpArrPlay['link'], ['user_code' => $userCode]);
  981. }
  982. if (!empty($tmpArrPlay['play'])) {
  983. $tmpArrPlay['play'] = M3u8CacheRepository::getVideoInnerUrl($tmpArrPlay['play'], $userCode, $tmpArrPlay['id'], 'ad');
  984. }
  985. unset($tmpArrPlay['id']);
  986. $arrData[] = [
  987. 'type' => 'a',
  988. 'video' => $tmpArrPlay,
  989. ];
  990. }
  991. $adStep++;
  992. }
  993. $lists['data'] = $arrData;
  994. }
  995. return json_success($lists);
  996. }
  997. }