Modules_Api_Controllers_VideoController.proxy.php 41 KB

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