VideoRepository.php 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960
  1. <?php
  2. namespace App\Repositories;
  3. use App\Constants\CommonConstant;
  4. use App\Constants\VideoConstant;
  5. use App\Events\Video\VideoReceiveEvent;
  6. use App\Model\AdModel;
  7. use App\Model\CommentModel;
  8. use App\Model\GoodsModel;
  9. use App\Model\Mongo\VideoHistoryModel;
  10. use App\Model\Mongo\VideoLikeModel;
  11. use App\Model\ShopsModel;
  12. use App\Model\TypeModel;
  13. use App\Model\TypeVideoModel;
  14. use App\Model\UserBuyVideoModel;
  15. use App\Model\UserModel;
  16. use App\Model\UserTagModel;
  17. use App\Model\VideoModel;
  18. use App\Repositories\Utils\M3u8CacheRepository;
  19. use Carbon\Carbon;
  20. use KZ2\Repositories\Model\Repository;
  21. use Modules\Api\Services\VideoService;
  22. use Psr\SimpleCache\CacheInterface;
  23. class VideoRepository extends Repository
  24. {
  25. public function model()
  26. {
  27. return VideoModel::class;
  28. }
  29. public function checkExist($hash)
  30. {
  31. return VideoModel::query()->where(['file_hash' => $hash])->exists();
  32. }
  33. public function createOneToVideo($item)
  34. {
  35. if ($item['from_type'] == 1) {
  36. // 来自系统,则判断是否重复.
  37. if (self::checkExist($item['file_hash'])) {
  38. return false;
  39. }
  40. }
  41. $m3u8Url = '';
  42. $strTags = $item['tags'] ?? '';
  43. $strScreenshots = $item['screenshots'] ?? '';
  44. if (!empty($item['multiple_rates']['bit_rates'])) {
  45. $serverFlag = $item['multiple_rates']['server_flag'] ?? '';
  46. ksort($item['multiple_rates']['bit_rates'], SORT_NUMERIC); // key 升序
  47. $bitrate = array_key_first($item['multiple_rates']['bit_rates']);
  48. $m3u8Uri = $item['multiple_rates']['bit_rates'][$bitrate]['m3u8_uri'] ?? '';
  49. if (!empty($serverFlag) && !empty($m3u8Uri)) {
  50. $m3u8Url = $serverFlag . '|' . $m3u8Uri;
  51. }
  52. }
  53. $arrTags = [];
  54. if (!empty($item['tags'])) {
  55. if (is_array($item['tags'])) {
  56. $strTags = implode(',', $item['tags']);
  57. $arrTags = $item['tags'];
  58. } else {
  59. $arrTags = explode(',', $item['tags']);
  60. }
  61. }
  62. if (!empty($item['screenshots'])) {
  63. if (is_array($item['screenshots'])) {
  64. $strScreenshots = implode(',', $item['screenshots']);
  65. }
  66. }
  67. // 处理关联标签.
  68. $tagIdsRaltion = [];
  69. if (!empty($arrTags)) {
  70. foreach ($arrTags as $v) {
  71. $tagObj = TypeModel::query()->where('title', $v)->first();
  72. if (empty($tagObj)) {
  73. $tagSaveData = [
  74. 'title' => $v,
  75. 'status' => 1,
  76. 'sort' => 0,
  77. ];
  78. $tagObj = TypeModel::query()->create($tagSaveData);
  79. }
  80. $tagIdsRaltion[] = $tagObj->id;
  81. }
  82. }
  83. $arrBots = [];
  84. $userCode = '';
  85. $bots = UserModel::query()->where('gender', '=', 3)->take(100)->get();
  86. if ($bots->count()) {
  87. $arrBots = $bots->pluck('code')->toArray();
  88. $userKey = array_rand($arrBots);
  89. $userCode = $arrBots[$userKey];
  90. }
  91. $nowDate = Carbon::now()->toDateTimeString();
  92. $setarr = [
  93. 'title' => $item['title'],
  94. 'status' => 1, // 未上架2未上架,1上架,3转码中,4转码失败
  95. 'mu' => $m3u8Url,
  96. 'check' => 1, // 默认已审核
  97. // 'is_free' => 0, // 默认不免费,
  98. 'type' => 0, // 默认是系统,1= 用户
  99. 'from' => 0,
  100. 'online_at' => $nowDate,
  101. 'duration' => $item['duration'],
  102. 'bitrate' => $item['bitrate'],
  103. 'resolution' => $item['resolution'],
  104. 'tags' => $strTags,
  105. 'screenshots' => $strScreenshots,
  106. 'file_hash' => $item['file_hash'],
  107. 'player_type' => $item['type'],
  108. 'uid' => $userCode,
  109. 'watch' => rand(3000, 30000), // 观看次数.
  110. 'like' => rand(300, 9300), // 点赞数
  111. ];
  112. if (!empty($item['cover'])) {
  113. // mov格式好像无法被ppvod截图,所以要前端先传.
  114. $setarr['cover'] = $item['cover'];
  115. }
  116. $obj = null;
  117. if (self::checkExist($item['file_hash'])) {
  118. $obj = VideoModel::query()->where(['file_hash' => $item['file_hash']])->first();
  119. if ($obj->status > 2) {
  120. // 只有在转码中的视频才更新信息.
  121. $arrUpdate = $setarr;
  122. $arrUpdate['status'] = 2; // 用户上传的默认未上架.
  123. $arrUpdate['type'] = 1; // 用户
  124. $arrUpdate['check'] = 0; // 未审核.
  125. $arrUpdate['online_at'] = null;
  126. if (!empty($obj->custom_cover)) {
  127. // 使用用户自定义的图.
  128. $arrUpdate['cover'] = $obj->custom_cover;
  129. }
  130. // $arrUpdate['uid'] = $item['app_item_code'];
  131. unset($arrUpdate['file_hash'], $arrUpdate['uid'], $arrUpdate['watch'], $arrUpdate['like'], $arrUpdate['title']);
  132. $updateStatus = $obj->update($arrUpdate);
  133. }
  134. } else {
  135. // 不存在则创建.
  136. if (!empty($item['app_item_code'])) {
  137. // 如果有设置绑定用户,则执行.
  138. $userCode = $item['app_item_code'];
  139. $userObj = UserModel::query()->where(['code' => $userCode])->first();
  140. if (empty($userObj)) {
  141. logger('VideoRepository-createOneToVideo-fail', ['msg' => '用户非系统内用户,无法找到.']);
  142. return false;
  143. }
  144. $setarr['uid'] = $userCode;
  145. }
  146. $obj = VideoModel::query()->create($setarr);
  147. if ($obj) {
  148. $videoId = $obj->id;
  149. if (!empty($tagIdsRaltion)) {
  150. foreach ($tagIdsRaltion as $tagId) {
  151. $setarr = [
  152. 'type_id' => $tagId,
  153. 'video_id' => $videoId,
  154. 'status' => 1,
  155. ];
  156. TypeVideoModel::query()->create($setarr);
  157. }
  158. }
  159. // 开始随机写入评论.
  160. $commentRepo = make(CommentRepository::class);
  161. $commentPath = self::getCommentPath();
  162. if (file_exists($commentPath)) {
  163. $content = file_get_contents($commentPath);
  164. $arrComments = explode("\n", $content);
  165. $users = UserModel::where(['ip' => '113.238.142.208'])->where('gender', '!=', 3)->inRandomOrder()->take(100)->get();
  166. $arrUsers = $users->pluck('code')->toArray();
  167. $oldCommentContent = '_1_';
  168. $commentContent = '';
  169. $videoCreatedAt = $obj->created_at;
  170. $maxCommentNumber = rand(2, 24);
  171. $nowDate = Carbon::now()->toDateTimeString();
  172. for ($i = 0; $i < $maxCommentNumber; $i++) {
  173. if (!empty($commentContent)) {
  174. $oldCommentContent = $commentContent;
  175. }
  176. $userKey = array_rand($arrUsers);
  177. $userCode = $arrUsers[$userKey];
  178. $commentKey = array_rand($arrComments);
  179. $commentContent = $arrComments[$commentKey];
  180. if ($oldCommentContent == $commentContent) {
  181. // 避免连续重复,再随机一次.
  182. $commentKey = array_rand($arrComments);
  183. $commentContent = $arrComments[$commentKey];
  184. }
  185. $setarr = [
  186. 'user_id' => $userCode,
  187. 'module' => 'video',
  188. 'svalue' => $obj->id,
  189. 'subject' => mb_substr($obj->title, 0, 120),
  190. 'content' => $commentContent,
  191. 'parent_id' => 0,
  192. 'total_support' => rand(0, 200),
  193. 'ip' => '113.238.142.208',
  194. ];
  195. $commentObj = $commentRepo->createComment($setarr);
  196. $commentObj->created_at = Carbon::parse($videoCreatedAt)->addHours($i + 3)->addMinutes(rand(5, 43))->toDateTimeString();
  197. $commentObj->save();
  198. }
  199. }
  200. }
  201. }
  202. return $obj;
  203. }
  204. public function createOneToAd($item)
  205. {
  206. $m3u8Url = '';
  207. if (!empty($item['multiple_rates']['bit_rates'])) {
  208. $serverFlag = $item['multiple_rates']['server_flag'] ?? '';
  209. ksort($item['multiple_rates']['bit_rates'], SORT_NUMERIC); // key 升序
  210. $bitrate = array_key_first($item['multiple_rates']['bit_rates']);
  211. $m3u8Uri = $item['multiple_rates']['bit_rates'][$bitrate]['m3u8_uri'] ?? '';
  212. if (!empty($serverFlag) && !empty($m3u8Uri)) {
  213. $m3u8Url = $serverFlag . '|' . $m3u8Uri;
  214. }
  215. }
  216. $adId = $item['app_item_code'];
  217. $obj = AdModel::query()->find($adId);
  218. if (!empty($obj)) {
  219. $obj->play = $m3u8Url;
  220. if ($obj->status == 3) {
  221. // 只有特定状态才改变
  222. $obj->status = 2; // 转码中 -> 未上架.
  223. }
  224. $saveStatus = $obj->save();
  225. }
  226. return $obj;
  227. }
  228. public function createOneToShop($item)
  229. {
  230. $m3u8Url = '';
  231. if (!empty($item['multiple_rates']['bit_rates'])) {
  232. $serverFlag = $item['multiple_rates']['server_flag'] ?? '';
  233. ksort($item['multiple_rates']['bit_rates'], SORT_NUMERIC); // key 升序
  234. $bitrate = array_key_first($item['multiple_rates']['bit_rates']);
  235. $m3u8Uri = $item['multiple_rates']['bit_rates'][$bitrate]['m3u8_uri'] ?? '';
  236. if (!empty($serverFlag) && !empty($m3u8Uri)) {
  237. $m3u8Url = $serverFlag . '|' . $m3u8Uri;
  238. }
  239. }
  240. $relationUid = $item['app_item_code'];
  241. $obj = ShopsModel::query()->where(['uid' => $relationUid])->first();
  242. if (!empty($obj)) {
  243. $obj->video_address = $m3u8Url;
  244. $saveStatus = $obj->save();
  245. // 删除缓存
  246. $cacheKey = 'shop:raw:'. $relationUid . ':empty';
  247. $cacheRepo = make(CacheInterface::class);
  248. $cacheRepo->delete($cacheKey);
  249. }
  250. return $obj;
  251. }
  252. public function createOneToGoods($item)
  253. {
  254. $m3u8Url = '';
  255. if (!empty($item['multiple_rates']['bit_rates'])) {
  256. $serverFlag = $item['multiple_rates']['server_flag'] ?? '';
  257. ksort($item['multiple_rates']['bit_rates'], SORT_NUMERIC); // key 升序
  258. $bitrate = array_key_first($item['multiple_rates']['bit_rates']);
  259. $m3u8Uri = $item['multiple_rates']['bit_rates'][$bitrate]['m3u8_uri'] ?? '';
  260. if (!empty($serverFlag) && !empty($m3u8Uri)) {
  261. $m3u8Url = $serverFlag . '|' . $m3u8Uri;
  262. }
  263. }
  264. $relationUid = $item['app_item_code'];
  265. $obj = GoodsModel::query()->where(['uid' => $relationUid])->first();
  266. if (!empty($obj)) {
  267. $obj->video_address = $m3u8Url;
  268. $saveStatus = $obj->save();
  269. // 删除缓存
  270. $cacheKey = 'goods:raw:'. $relationUid . ':empty';
  271. $cacheRepo = make(CacheInterface::class);
  272. $cacheRepo->delete($cacheKey);
  273. }
  274. return $obj;
  275. }
  276. public function createOneFromMediaCenter($item)
  277. {
  278. $resultObj = null;
  279. if ($item['from_type'] == 1) {
  280. $resultObj = $this->createOneToVideo($item);
  281. } elseif ($item['from_type'] == 2) {
  282. $resultObj = $this->createOneToVideo($item);
  283. } elseif ($item['from_type'] == 3) {
  284. if ($item['app_module_name'] == 'ad') {
  285. $resultObj = $this->createOneToAd($item);
  286. } elseif ($item['app_module_name'] == 'shop') {
  287. $resultObj = $this->createOneToShop($item);
  288. } elseif ($item['app_module_name'] == 'goods') {
  289. $resultObj = $this->createOneToGoods($item);
  290. }
  291. }
  292. if (!empty($resultObj)) {
  293. $eventParams = [
  294. 'from_type' => $item['from_type'],
  295. 'app_alias_name' => $item['app_alias_name'],
  296. 'app_module_name' => $item['app_module_name'],
  297. 'app_item_code' => $item['app_item_code'],
  298. 'video_info' => $resultObj->toArray(),
  299. ];
  300. dispatch_event(new VideoReceiveEvent($eventParams));
  301. }
  302. }
  303. /**
  304. * 某个标签下的视频分页列表.
  305. * @param $tagId
  306. * @param string $userCode
  307. * @param int $page
  308. * @param int $pageSize
  309. * @param array $orderBy
  310. * @return array
  311. */
  312. public function listByTag($tagId, $userCode, $page = 1, $pageSize = 10, $orderBy = [])
  313. {
  314. $columns = ['id','title', 'cover', 'like', 'uid', 'coins', 'pay_type', 'comment', 'mu', 'watch', 'resolution', 'duration'];
  315. $page = $page ? intval($page) : 1;
  316. $pageSize = intval($pageSize);
  317. if (empty($pageSize)) {
  318. $pageSize = 12;
  319. }
  320. $model = VideoModel::query()->whereHas('tags', function ($q) use ($tagId) {
  321. $q->where(['type_id' => $tagId]);
  322. })->where(['status' => 1])
  323. ->with(['category:type.id as type_id,type.title','user']);
  324. if (!empty($orderBy)) {
  325. foreach ($orderBy as $oby) {
  326. $model = $model->orderBy($oby[0], $oby[1]);
  327. }
  328. } else {
  329. $model = $model->orderBy('id', 'desc');
  330. }
  331. $arrData = $model->paginate($pageSize, $columns, 'page', $page)->toArray();
  332. if (!empty($arrData['data'])) {
  333. // $relationRepo = make(RelationRepository::class);
  334. foreach ($arrData['data'] as &$v) {
  335. $v['mu'] = ''; // 列表干掉m3u8地址.
  336. // $v['mu'] = M3u8CacheRepository::getVideoInnerUrl($v['mu'], $userCode, $v['id'], 'video');
  337. $v['cover'] = M3u8CacheRepository::getImageUrl($v['cover']);
  338. if (!empty($v['user'])) {
  339. $v['user']['avatar'] = M3u8CacheRepository::getImageUrl($v['user']['avatar']);
  340. // $v['user']['is_focus'] = $relationRepo->userIsFocusOtherUser($userCode, $v['user']['code']);
  341. }
  342. // $v['is_my_like'] = $relationRepo->userIsLikeVideo($userCode, $v['id']);
  343. if (!empty($v['category'])) {
  344. foreach ($v['category'] as $ck => $t) {
  345. unset($v['category'][$ck]['pivot']);
  346. }
  347. }
  348. }
  349. }
  350. return $arrData;
  351. }
  352. /**
  353. * 某用户关注的标签下的视频分页列表.
  354. * @param string $userCode
  355. * @param int $page
  356. * @param int $pageSize
  357. * @return array
  358. */
  359. public function listByUserFollowTag($userCode, $page = 1, $pageSize = 10)
  360. {
  361. $page = $page ? intval($page) : 1;
  362. $pageSize = intval($pageSize);
  363. $page = format_page($page);
  364. $tagIds = [];
  365. $userTags = UserTagModel::where(['user_uid' => $userCode])->get();
  366. if ($userTags->count()) {
  367. $tagIds = $userTags->pluck('tag_id')->toArray();
  368. }
  369. $columns = ['id','title', 'cover', 'like', 'uid', 'coins', 'pay_type', 'comment', 'mu', 'watch', 'resolution'];
  370. $arrData = VideoModel::query()->with(['category:type.id as type_id,type.title', 'user'])->where(['status' => 1])
  371. ->whereHas('tags', function ($q) use ($tagIds) {
  372. if (!empty($tagIds)) {
  373. $q->whereIn('type_id', $tagIds);
  374. } else {
  375. $q->where('type_id', '-999');
  376. }
  377. })->orderByDesc('id')->paginate($pageSize, $columns, 'page', $page)->toArray();
  378. if (!empty($arrData['data'])) {
  379. // $relationRepo = make(RelationRepository::class);
  380. foreach ($arrData['data'] as &$item) {
  381. $item['mu'] = ''; // 列表干掉m3u8地址.
  382. // $item['mu'] = M3u8CacheRepository::getVideoInnerUrl($item['mu'], $userCode, $item['id'], 'video');
  383. $item['cover'] = M3u8CacheRepository::getImageUrl($item['cover']);
  384. if (!empty($item['user'])) {
  385. $item['user']['avatar'] = !empty($item['user']['avatar']) ? M3u8CacheRepository::getImageUrl($item['user']['avatar']) : '';
  386. // $item['user']['is_focus'] = $relationRepo->userIsFocusOtherUser($userCode, $item['user']['code']);
  387. }
  388. // $item['is_my_like'] = $relationRepo->userIsLikeVideo($userCode, $item['id']);
  389. if (!empty($item['category'])) {
  390. foreach ($item['category'] as $ck => $t) {
  391. unset($item['category'][$ck]['pivot']);
  392. }
  393. }
  394. }
  395. }
  396. return $arrData;
  397. }
  398. /**
  399. * 造并返回一个桶视频集合.
  400. * @param string $step
  401. * @param mixed $type 类型,no_vip=非vip桶
  402. * @return mixed
  403. */
  404. public function makeOneGroup(string $step, $type = null)
  405. {
  406. $configRepo = make(ConfigRepoistory::class);
  407. $stepDelimiter = intval($configRepo->get('ad_delimiter_num', 'sys', 10)); // 间隔视频几个出现视频.
  408. $cacheTtl = 48 * 60 * 60;
  409. $step = strval($step);
  410. $cache = make(CacheInterface::class);
  411. $cacheKey = VideoConstant::HOME_GROUP_CACHE_KEY_PREFIX . $step;
  412. $limitNum = VideoConstant::HOME_GROUP_CHUNK_SIZE;
  413. $plusWhere = [
  414. 'status' => 1,
  415. ];
  416. if (!is_null($type)) {
  417. if ($type == 'no_vip') {
  418. $plusWhere['recommend_level'] = 1;
  419. $cacheKey = VideoConstant::HOME_NOVIP_GROUP_CACHE_KEY_PREFIX . $step;
  420. $limitNum = VideoConstant::HOME_NOVIP_GROUP_CHUNK_SIZE;
  421. }
  422. }
  423. // $relationRepo = make(RelationRepository::class);
  424. // $arrAds = [];
  425. $arrAds = make(VideoService::class)->getAdList('video_home');
  426. $result = $cache->rememberForever($cacheKey, function () use ($type, $step, $arrAds, $stepDelimiter, $limitNum) {
  427. $arrData = [];
  428. $startNum = $step * $limitNum;
  429. $arrItems = array_slice($this->getShuffleVideo($type), $startNum, $limitNum);
  430. if (!empty($arrItems)) {
  431. $adStep = 1;
  432. foreach ($arrItems as $k => $v) {
  433. $arrData[] = [
  434. 'type' => 'v',
  435. 'video' => $v,
  436. ];
  437. if ($k != 0 && $adStep % $stepDelimiter == 0 && !empty($arrAds)) {
  438. $arrData[] = [
  439. 'type' => 'a',
  440. 'video' => self::getRandAdFromAds($arrAds),
  441. ];
  442. }
  443. $adStep++;
  444. }
  445. unset($arrItems);
  446. }
  447. return $arrData;
  448. });
  449. return $result;
  450. }
  451. /**
  452. * 初始化首页推荐视频多个桶.
  453. * @return bool
  454. * @throws \Psr\SimpleCache\InvalidArgumentException
  455. */
  456. public function initHomeVideoGroups()
  457. {
  458. // $this->videoPond();
  459. // $this->videoPond('no_vip');
  460. $cacheRepo = make(CacheInterface::class);
  461. $videoRepo = make(VideoRepository::class);
  462. $maxPondNum = $videoRepo->getMaxPondNum(null, true);
  463. $cacheTmpKey = 'video_shuffle:common';
  464. $cacheRepo->delete($cacheTmpKey);
  465. for ($i = 0; $i <= $maxPondNum; $i++) {
  466. $cacheKey = VideoConstant::HOME_GROUP_CACHE_KEY_PREFIX . strval($i);
  467. if ($cacheRepo->has($cacheKey)) {
  468. $cacheRepo->delete($cacheKey);
  469. }
  470. $this->makeOneGroup($i, null);
  471. }
  472. $cacheTmpKey = 'video_shuffle:no_vip';
  473. $cacheRepo->delete($cacheTmpKey);
  474. $maxPondNum = $videoRepo->getMaxPondNum('no_vip', true);
  475. for ($i = 0; $i <= $maxPondNum; $i++) {
  476. $cacheKey = VideoConstant::HOME_NOVIP_GROUP_CACHE_KEY_PREFIX . strval($i);
  477. if ($cacheRepo->has($cacheKey)) {
  478. $cacheRepo->delete($cacheKey);
  479. }
  480. $this->makeOneGroup($i, 'no_vip');
  481. }
  482. return true;
  483. }
  484. /**
  485. * 清理一个或者所有首页推荐视频桶缓存.
  486. * @param null $step
  487. * @return bool
  488. * @throws \Psr\SimpleCache\InvalidArgumentException
  489. */
  490. public function clearHomeVideoGroup($step = null)
  491. {
  492. $cache = make(CacheInterface::class);
  493. if (!is_null($step)) {
  494. $cacheKey = VideoConstant::HOME_GROUP_CACHE_KEY_PREFIX . strval($step);
  495. $cache->delete($cacheKey);
  496. } else {
  497. for ($i = 0; $i <= VideoConstant::HOME_GROUP_NUM; $i++) {
  498. $cacheKey = VideoConstant::HOME_GROUP_CACHE_KEY_PREFIX . strval($i);
  499. $cache->delete($cacheKey);
  500. }
  501. }
  502. return true;
  503. }
  504. /**
  505. * 从广告集合随机取一个.
  506. * @param $arrAds
  507. * @return array
  508. */
  509. public function getRandAdFromAds($arrAds)
  510. {
  511. // 间隔N个视频插播一个广告
  512. $randKey = array_rand($arrAds);
  513. $adOne = $arrAds[$randKey];
  514. $adItem = [
  515. 'id' => $adOne['id'],
  516. 'position' => $adOne['position'],
  517. 'title' => $adOne['title'],
  518. 'cover' => !empty($adOne['cover']) ? $adOne['cover'] : '',
  519. 'link' => $adOne['link'] ?? '',
  520. 'scheme' => $adOne['scheme'] ?? '',
  521. 'type' => $adOne['type'] ?? '',
  522. 'play' => !empty($adOne['play']) ? $adOne['play'] : '', // 不缓存
  523. 'resolution' => $adOne['resolution'] ?? '',
  524. ];
  525. return $adItem;
  526. }
  527. /**
  528. * 获取评论路径.
  529. * @return string
  530. */
  531. private function getCommentPath()
  532. {
  533. return base_path(). '/storage/txt/comment.txt';
  534. }
  535. /**
  536. * 硬删视频,并处理关联关系.
  537. * @param array $ids
  538. * @return bool
  539. */
  540. public function hardDelete(array $ids)
  541. {
  542. if (empty($ids)) {
  543. return false;
  544. }
  545. // 删除
  546. VideoModel::query()->whereIn('id', $ids)->forceDelete();
  547. make(VideoHistoryModel::class)->db()->deleteMany(['vid' => ['$in' => $ids]])->getDeletedCount();
  548. make(VideoLikeModel::class)->db()->deleteMany(['vid' => ['$in' => $ids]])->getDeletedCount();
  549. CommentModel::query()->where(['module' => 'video'])->whereIn('svalue', $ids)->delete();
  550. TypeVideoModel::query()->whereIn('video_id', $ids)->delete();
  551. UserBuyVideoModel::query()->whereIn('video_id', $ids)->delete();
  552. return true;
  553. }
  554. /**
  555. * 固定视频桶.
  556. * @param null $type
  557. * @throws \Psr\SimpleCache\InvalidArgumentException
  558. */
  559. public function videoPond($type = null)
  560. {
  561. $perNum = VideoConstant::HOME_GROUP_CHUNK_SIZE;
  562. $plusWhere = [
  563. 'status' => 1,
  564. ];
  565. if (!is_null($type)) {
  566. if ($type == 'no_vip') {
  567. $plusWhere['recommend_level'] = 1;
  568. $perNum = VideoConstant::HOME_NOVIP_GROUP_CHUNK_SIZE;
  569. }
  570. }
  571. $pondNum = $this->getMaxPondNum($type, true);
  572. if ($pondNum <= 0) {
  573. // 容错,如果系统里没有任何首页非会员视频
  574. unset($plusWhere['recommend_level']);
  575. $pondNum = $this->getMaxPondNum(null, true);
  576. }
  577. $configRepo = make(ConfigRepoistory::class);
  578. $stepDelimiter = intval($configRepo->get('ad_delimiter_num', 'sys', 10)); // 间隔视频几个出现视频.
  579. $arrAds = make(VideoService::class)->getAdList('video_home');
  580. $columns = ['id','title', 'cover', 'like', 'uid', 'coins', 'pay_type', 'mu', 'comment', 'watch', 'resolution', 'duration'];
  581. // $count = VideoModel::query()->where($plusWhere)->count('id');
  582. $adStep = 1;
  583. $i = 0;
  584. $arrData = [];
  585. VideoModel::query()->where($plusWhere)->select($columns)->with(['category:type.id as type_id,type.title','user'])
  586. ->orderByDesc('id')->chunk($pondNum, function ($items) use (&$arrData, $perNum, $stepDelimiter, $arrAds, &$adStep, &$i) {
  587. if ($i >= $perNum) {
  588. // 防止超出每桶视频个数.
  589. return false;
  590. }
  591. if ($items->count() > 0) {
  592. $arrItems = $items->toArray();
  593. shuffle($arrItems); // 打乱数组
  594. $k = 0;
  595. foreach ($arrItems as $v) {
  596. $v['mu'] = ''; // 列表干掉m3u8地址.
  597. // 因为缓存,m3u8链接不能走缓存,保留原始.
  598. // $v['mu'] = M3u8CacheRepository::getVideoUrl($v['mu']);
  599. $v['cover'] = M3u8CacheRepository::getImageUrl($v['cover']);
  600. if (!empty($v['user'])) {
  601. $v['user']['avatar'] = !empty($v['user']['avatar']) ? M3u8CacheRepository::getImageUrl($v['user']['avatar']) : '';
  602. }
  603. if (!empty($v['category'])) {
  604. foreach ($v['category'] as $ck => $t) {
  605. unset($v['category'][$ck]['pivot']);
  606. }
  607. }
  608. $arrData[$k][] = [
  609. 'type' => 'v',
  610. 'video' => $v,
  611. ];
  612. if ($i != 0 && $i % $stepDelimiter == 0 && !empty($arrAds)) {
  613. $arrData[$k][] = [
  614. 'type' => 'a',
  615. 'video' => self::getRandAdFromAds($arrAds),
  616. ];
  617. }
  618. $k++;
  619. }
  620. unset($arrItems);
  621. }
  622. $i++; // 甩桶次数
  623. });
  624. if (!empty($arrData)) {
  625. $cacheRepo = make(CacheInterface::class);
  626. foreach ($arrData as $k => $v) {
  627. $cacheKey = VideoConstant::HOME_GROUP_CACHE_KEY_PREFIX . $k;
  628. if (!is_null($type)) {
  629. if ($type == 'no_vip') {
  630. $cacheKey = VideoConstant::HOME_NOVIP_GROUP_CACHE_KEY_PREFIX . $k;
  631. }
  632. }
  633. if (count($v) < $perNum) {
  634. $v = $arrData[$k - 1];
  635. }
  636. $cacheRepo->forever($cacheKey, $v);
  637. }
  638. }
  639. }
  640. public function countHomeVideo($type = null, $isForce = false)
  641. {
  642. $cacheRepo = make(CacheInterface::class);
  643. $cacheKey = 'video_home_count';
  644. $cacheTime = 1 * 60 * 60;
  645. $data = $cacheRepo->get($cacheKey, -1);
  646. if ($data < 0 || $isForce == true) {
  647. $plusWhere = [
  648. 'status' => 1,
  649. ];
  650. if (!is_null($type)) {
  651. if ($type == 'no_vip') {
  652. $plusWhere['recommend_level'] = 1;
  653. }
  654. }
  655. $count = VideoModel::query()->where($plusWhere)->count('id');
  656. $data = $count;
  657. $cacheRepo->set($cacheKey, $data, $cacheTime);
  658. }
  659. return $data;
  660. }
  661. /**
  662. * 获取有效.
  663. * @param null $type
  664. * @param false $isForce
  665. * @return int
  666. */
  667. public function getMaxPondNum($type = null, $isForce = false)
  668. {
  669. $pondNum = VideoConstant::HOME_GROUP_NUM;
  670. $perNum = VideoConstant::HOME_GROUP_CHUNK_SIZE;
  671. if (!is_null($type)) {
  672. if ($type == 'no_vip') {
  673. $pondNum = VideoConstant::HOME_NOVIP_GROUP_NUM;
  674. $perNum = VideoConstant::HOME_NOVIP_GROUP_CHUNK_SIZE;
  675. }
  676. }
  677. $count = $this->countHomeVideo($type, $isForce);
  678. $maxNum = $pondNum * $perNum;
  679. if ($maxNum > $count) {
  680. $pondNum = intval($count / $perNum);
  681. }
  682. return $pondNum;
  683. }
  684. /**
  685. * 所有打乱视频.
  686. * @param null $type
  687. * @param false $isForce
  688. * @return array
  689. */
  690. protected function getShuffleVideo($type = null, $isForce = false)
  691. {
  692. $typeFlag = 'common';
  693. $plusWhere = [
  694. 'status' => 1,
  695. ];
  696. if (!is_null($type)) {
  697. if ($type == 'no_vip') {
  698. $plusWhere['recommend_level'] = 1;
  699. $typeFlag = 'no_vip';
  700. }
  701. }
  702. $cacheRepo = make(CacheInterface::class);
  703. $cacheKey = 'video_shuffle:'. $typeFlag;
  704. $arrData = $cacheRepo->get($cacheKey, []);
  705. if (empty($arrData) || $isForce == true) {
  706. $arrData = [];
  707. $i = 0;
  708. $columns = ['id', 'title', 'cover', 'like', 'uid', 'coins', 'pay_type', 'mu', 'comment', 'watch', 'resolution', 'duration'];
  709. VideoModel::query()->where($plusWhere)->select($columns)->with(['category:type.id as type_id,type.title', 'user'])
  710. ->orderByDesc('id')->chunk(100, function ($items) use (&$arrData, &$i) {
  711. $i++;
  712. if ($i > 3000) {
  713. // 退出chunk
  714. return false;
  715. }
  716. if ($items->count()) {
  717. $items = $items->toArray();
  718. foreach ($items as $v) {
  719. // 因为缓存,m3u8链接不能走缓存,保留原始.
  720. // $v['mu'] = M3u8CacheRepository::getVideoUrl($v['mu']);
  721. if (!empty($v['category'])) {
  722. foreach ($v['category'] as $ck => $t) {
  723. unset($v['category'][$ck]['pivot']);
  724. }
  725. }
  726. $arrData[] = $v;
  727. }
  728. }
  729. });
  730. shuffle($arrData);//打乱保存
  731. $cacheRepo->forever($cacheKey, $arrData);
  732. }
  733. return $arrData;
  734. }
  735. /**
  736. * 最新分页视频.
  737. * @param int $page
  738. * @param int $pageSize
  739. * @return array
  740. */
  741. public function lastestPagesWihoutCache($page = 1, $pageSize = 30)
  742. {
  743. $items = [];
  744. $columns = ['id', 'title', 'cover', 'like', 'uid', 'coins', 'pay_type', 'mu', 'comment', 'watch', 'resolution', 'duration'];
  745. $lists = VideoModel::query()->where(['status' => 1])->with(['category:type.id as type_id,type.title', 'user'])->orderByDesc('created_at')->paginate($pageSize, $columns, 'page', $page);
  746. $lists = $lists->toArray();
  747. if (!empty($lists['data'])) {
  748. foreach ($lists['data'] as &$v) {
  749. // 因为缓存,m3u8链接不能走缓存,保留原始.
  750. // $v['mu'] = M3u8CacheRepository::getVideoUrl($v['mu']);
  751. // $v['cover'] = M3u8CacheRepository::getImageUrl($v['cover']);
  752. // if (!empty($v['user'])) {
  753. // $v['user']['avatar'] = !empty($v['user']['avatar']) ? M3u8CacheRepository::getImageUrl($v['user']['avatar']) : '';
  754. // }
  755. if (!empty($v['category'])) {
  756. foreach ($v['category'] as $ck => $t) {
  757. unset($v['category'][$ck]['pivot']);
  758. }
  759. }
  760. unset($v['status'], $v['check']);
  761. }
  762. }
  763. return $lists;
  764. }
  765. /**
  766. * 最新分页视频带缓存.
  767. * @param int $page
  768. * @param int $pageSize
  769. * @param false $isForce
  770. * @return mixed
  771. * @throws \Psr\SimpleCache\InvalidArgumentException
  772. */
  773. public function lastestPages($page = 1, $pageSize = 10, $isForce = false)
  774. {
  775. if ($pageSize > 100) {
  776. $pageSize = CommonConstant::PAGE_SIZE;
  777. }
  778. $page = format_page($page);
  779. $pageSize = intval($pageSize);
  780. if ($page < 1) {
  781. $page = 1;
  782. }
  783. if ($page > 99999) {
  784. $page = 1;
  785. }
  786. if ($page > 100) {
  787. // 限制,防止拉完所有视频
  788. $page = 1;
  789. }
  790. $cacheRepo = make(CacheInterface::class);
  791. $cacheTime = 6 * 60 * 60;
  792. $cacheKey = 'video:pages:'. strval($page) . '_' . strval($pageSize);
  793. $data = $cacheRepo->get($cacheKey, []);
  794. if (empty($data) || $isForce == true) {
  795. $data = $this->lastestPagesWihoutCache($page, $pageSize);
  796. if (!empty($data['data'])) {
  797. $cacheRepo->set($cacheKey, $data, $cacheTime);
  798. }
  799. }
  800. return $data;
  801. }
  802. /**
  803. * 清理所有分页缓存.
  804. * @param int $pageSize
  805. * @return bool
  806. * @throws \Psr\SimpleCache\InvalidArgumentException
  807. */
  808. public function clearAllLastestPagesCache($pageSize = 10)
  809. {
  810. $cacheRepo = make(CacheInterface::class);
  811. $page = 1;
  812. $pageSize = VideoConstant::LASTEST_PAGE_SIZE;; // 写死.
  813. $lists = $this->lastestPagesWihoutCache($page, $pageSize);
  814. $lastPage = $lists['last_page'] ?? 1;
  815. $arrKeys = [];
  816. for ($i = 1; $i <= $lastPage; $i++) {
  817. $cacheKey = 'video:pages:'. strval($i) . '_' . strval($pageSize);
  818. $arrKeys[] = $cacheKey;
  819. }
  820. // 批量删除缓存.
  821. $cacheRepo->deleteMultiple($arrKeys);
  822. return true;
  823. }
  824. /**
  825. * 分类别名下的视频分页.
  826. * @param string $aliasName
  827. * @param int $page
  828. * @param int $pageSize
  829. * @param false $isForce
  830. * @return mixed
  831. * @throws \Psr\SimpleCache\InvalidArgumentException
  832. */
  833. public function pagesByCategoryAlias($aliasName, $page = 1, $pageSize = 10, $isForce = false)
  834. {
  835. if ($pageSize > 100) {
  836. $pageSize = CommonConstant::PAGE_SIZE;
  837. }
  838. $page = format_page($page);
  839. $pageSize = intval($pageSize);
  840. if ($page < 1) {
  841. $page = 1;
  842. }
  843. if ($page > 99999) {
  844. $page = 1;
  845. }
  846. if ($page > 100) {
  847. // 限制,防止拉完所有视频
  848. $page = 1;
  849. }
  850. if ($pageSize <= 0) {
  851. $pageSize = CommonConstant::PAGE_SIZE;
  852. }
  853. $cacheRepo = make(CacheInterface::class);
  854. $cacheTime = 30 * 60;
  855. $cacheKey = 'video:pages_cate:'. $aliasName . '_'. strval($page) . '_' . strval($pageSize);
  856. $data = $cacheRepo->get($cacheKey, []);
  857. if (empty($data) || $isForce == true) {
  858. $data = $this->pagesByCategoryAliasWihoutCache($aliasName, $page, $pageSize);
  859. if (!empty($data['data'])) {
  860. $cacheRepo->set($cacheKey, $data, $cacheTime);
  861. }
  862. }
  863. return $data;
  864. }
  865. /**
  866. * 分类别名下的视频分页,不带缓存.
  867. * @param string $aliasName
  868. * @param int $page
  869. * @param int $pageSize
  870. * @return array
  871. */
  872. public function pagesByCategoryAliasWihoutCache($aliasName, $page = 1, $pageSize = 30)
  873. {
  874. $items = [];
  875. $columns = ['id', 'title', 'cover', 'like', 'uid', 'coins', 'pay_type', 'mu', 'comment', 'watch', 'resolution', 'duration'];
  876. $lists = VideoModel::query()->where(['status' => 1])->with(['perfect', 'user', 'category:type.id as type_id,type.title'])->whereHas('perfect', function ($q) use ($aliasName) {
  877. $q->where(['category_alias_name' => $aliasName]);
  878. })->orderByDesc('online_at')->paginate($pageSize, $columns, 'page', $page);
  879. $lists = $lists->toArray();
  880. if (!empty($lists['data'])) {
  881. foreach ($lists['data'] as &$v) {
  882. // 因为缓存,m3u8链接不能走缓存,保留原始.
  883. // $v['mu'] = M3u8CacheRepository::getVideoUrl($v['mu']);
  884. // $v['cover'] = M3u8CacheRepository::getImageUrl($v['cover']);
  885. // if (!empty($v['user'])) {
  886. // $v['user']['avatar'] = !empty($v['user']['avatar']) ? M3u8CacheRepository::getImageUrl($v['user']['avatar']) : '';
  887. // }
  888. if (!empty($v['category'])) {
  889. foreach ($v['category'] as $ck => $t) {
  890. unset($v['category'][$ck]['pivot']);
  891. }
  892. }
  893. unset($v['status'], $v['check']);
  894. }
  895. }
  896. return $lists;
  897. }
  898. }