Vod.php 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903
  1. <?php
  2. namespace app\api\controller;
  3. use think\Controller;
  4. use think\Cache;
  5. use think\Db;
  6. use think\Request;
  7. use think\Validate;
  8. class Vod extends Base
  9. {
  10. use PublicApi;
  11. public function __construct()
  12. {
  13. parent::__construct();
  14. $this->check_config();
  15. }
  16. public function index()
  17. {
  18. }
  19. /**
  20. * 获取视频列表
  21. *
  22. * @param Request $request
  23. * @return \think\response\Json
  24. */
  25. public function get_list(Request $request)
  26. {
  27. // 参数校验
  28. $param = $request->param();
  29. $validate = validate($request->controller());
  30. if (!$validate->scene($request->action())->check($param)) {
  31. return json([
  32. 'code' => 1001,
  33. 'msg' => '参数错误: ' . $validate->getError(),
  34. ]);
  35. }
  36. $offset = isset($param['offset']) ? (int)$param['offset'] : 0;
  37. $limit = isset($param['limit']) ? (int)$param['limit'] : 20;
  38. // 查询条件组装(与前台分类一致:父类下视频多为子类 type_id + 父类 type_id_1)
  39. $where = [];
  40. $where['vod_status'] = ['eq', 1];
  41. if (isset($param['type_id'])) {
  42. $tid = (int)$param['type_id'];
  43. if ($tid > 0) {
  44. $where['type_id|type_id_1'] = ['eq', $tid];
  45. }
  46. }
  47. if (isset($param['id'])) {
  48. $where['vod_id'] = (int)$param['id'];
  49. }
  50. // if (isset($param['type_id_1'])) {
  51. // $where['type_id_1'] = (int)$param['type_id_1'];
  52. // }
  53. if (!empty($param['vod_letter'])) {
  54. $where['vod_letter'] = $param['vod_letter'];
  55. }
  56. if (isset($param['vod_tag']) && strlen($param['vod_tag']) > 0) {
  57. $where['vod_tag'] = ['like', '%' . $this->format_sql_string($param['vod_tag']) . '%'];
  58. }
  59. if (isset($param['vod_name']) && strlen($param['vod_name']) > 0) {
  60. $where['vod_name'] = ['like', '%' . $this->format_sql_string($param['vod_name']) . '%'];
  61. }
  62. if (isset($param['vod_blurb']) && strlen($param['vod_blurb']) > 0) {
  63. $where['vod_blurb'] = ['like', '%' . $this->format_sql_string($param['vod_blurb']) . '%'];
  64. }
  65. if (isset($param['vod_class']) && strlen($param['vod_class']) > 0) {
  66. $where['vod_class'] = ['like', '%' . $this->format_sql_string($param['vod_class']) . '%'];
  67. }
  68. if (isset($param['vod_area']) && strlen($param['vod_area']) > 0) {
  69. $where['vod_area'] = $this->format_sql_string($param['vod_area']);
  70. }
  71. if (isset($param['vod_year']) && strlen($param['vod_year']) > 0) {
  72. $where['vod_year'] = $this->format_sql_string($param['vod_year']);
  73. }
  74. if (isset($param['vod_lang']) && strlen($param['vod_lang']) > 0) {
  75. $where['vod_lang'] = $this->format_sql_string($param['vod_lang']);
  76. }
  77. if (isset($param['vod_level']) && strlen($param['vod_level']) > 0) {
  78. $where['vod_level'] = ['in', $this->format_sql_string($param['vod_level'])];
  79. }
  80. if (isset($param['vod_state']) && strlen($param['vod_state']) > 0) {
  81. $where['vod_state'] = $this->format_sql_string($param['vod_state']);
  82. }
  83. if (isset($param['vod_isend']) && strlen($param['vod_isend']) > 0) {
  84. $where['vod_isend'] = (int)$param['vod_isend'];
  85. }
  86. if (isset($param['vod_actor']) && strlen($param['vod_actor']) > 0) {
  87. $an = $this->format_sql_string($param['vod_actor']);
  88. if (strlen($an) > 0) {
  89. $where['vod_actor'] = ['like', mac_like_arr($an), 'OR'];
  90. }
  91. }
  92. // 数据获取
  93. $total = model('Vod')->getCountByCond($where);
  94. $list = [];
  95. if ($total > 0) {
  96. // 排序
  97. $order = "vod_time DESC";
  98. if (!empty($param['orderby'])) {
  99. $order = 'vod_' . $param['orderby'] . " DESC";
  100. }
  101. $field = 'vod_id,vod_en,vod_name,vod_sub,vod_pic,vod_actor,vod_hits,vod_hits_day,vod_hits_week,vod_hits_month,vod_time,vod_remarks,vod_score,vod_area,vod_year,vod_class,vod_blurb,vod_points_play,vod_isend,type_id,type_id_1';
  102. // $list = model('Vod')->getListByCond($offset, $limit, $where, $order, $field, []);
  103. $list = model('Vod')->getListByCond($offset, $limit, $where, $order, $field);
  104. // 补充 vod_pic、vod_link;主题「进播放页」时补充 vod_play_link(与 mac_url_vod_play 一致,避免前端拼 URL 与伪静态不一致)
  105. $playlinkOn = mac_tpl_vod_playlink_on();
  106. foreach ($list as &$v) {
  107. $v['vod_pic'] = mac_url_img($v['vod_pic'] ?? '');
  108. $v['vod_link'] = mac_url_vod_detail($v);
  109. if ($playlinkOn) {
  110. $v['vod_play_link'] = mac_url_vod_play($v, ['sid' => 1, 'nid' => 1]);
  111. } else {
  112. $v['vod_play_link'] = '';
  113. }
  114. }
  115. unset($v);
  116. // 与 model Vod::listData 一致:mac_get_vip_exclusive_type_ids()(会员组播放权限 popedom 3)
  117. mac_append_type_is_vip_exclusive_for_rows($list);
  118. }
  119. // 返回
  120. return json([
  121. 'code' => 1,
  122. 'msg' => '获取成功',
  123. 'info' => [
  124. 'offset' => $offset,
  125. 'limit' => $limit,
  126. 'total' => $total,
  127. 'rows' => $list,
  128. ],
  129. ]);
  130. }
  131. /**
  132. * 视频详细信息
  133. *
  134. * @param Request $request
  135. * @return \think\response\Json
  136. * @throws \think\db\exception\DataNotFoundException
  137. * @throws \think\db\exception\ModelNotFoundException
  138. * @throws \think\exception\DbException
  139. */
  140. public function get_detail(Request $request)
  141. {
  142. $param = $request->param();
  143. $validate = validate($request->controller());
  144. if (!$validate->scene($request->action())->check($param)) {
  145. return json([
  146. 'code' => 1001,
  147. 'msg' => '参数错误: ' . $validate->getError(),
  148. ]);
  149. }
  150. $res = Db::table('mac_vod')->where(['vod_id' => $param['vod_id']])->find();
  151. if (empty($res)) {
  152. return json(['code' => 1001, 'msg' => '数据不存在']);
  153. }
  154. // 处理图片 URL
  155. $res['vod_pic'] = mac_url_img($res['vod_pic']);
  156. $res['vod_pic_thumb'] = mac_url_img($res['vod_pic_thumb'] ?? '');
  157. $res['vod_pic_slide'] = mac_url_img($res['vod_pic_slide'] ?? '');
  158. $res['vod_link'] = mac_url_vod_detail($res);
  159. // 解析播放源列表 vod_play_list
  160. $playList = [];
  161. if (!empty($res['vod_play_from']) && !empty($res['vod_play_url'])) {
  162. $playerConfig = config('maccms.player') ?: [];
  163. $froms = explode('$$$', $res['vod_play_from']);
  164. $urls = explode('$$$', $res['vod_play_url']);
  165. foreach ($froms as $k => $from) {
  166. $from = trim($from);
  167. if (empty($from)) continue;
  168. $episodes = [];
  169. $urlStr = isset($urls[$k]) ? $urls[$k] : '';
  170. if (!empty($urlStr)) {
  171. $parts = explode('#', $urlStr);
  172. foreach ($parts as $idx => $part) {
  173. $part = trim($part);
  174. if (empty($part)) continue;
  175. $arr = explode('$', $part);
  176. $episodes[] = [
  177. 'name' => isset($arr[0]) ? $arr[0] : '第' . ($idx + 1) . '集',
  178. 'url' => isset($arr[1]) ? $arr[1] : $arr[0],
  179. ];
  180. }
  181. }
  182. $show = $from;
  183. if (isset($playerConfig[$from]) && !empty($playerConfig[$from]['show'])) {
  184. $show = $playerConfig[$from]['show'];
  185. }
  186. $playList[] = [
  187. 'from' => $from,
  188. 'player_info' => ['show' => $show, 'from' => $from],
  189. 'urls' => $episodes,
  190. ];
  191. }
  192. }
  193. $res['vod_play_list'] = $playList;
  194. // 解析下载源列表
  195. $downList = [];
  196. if (!empty($res['vod_down_from']) && !empty($res['vod_down_url'])) {
  197. $froms = explode('$$$', $res['vod_down_from']);
  198. $urls = explode('$$$', $res['vod_down_url']);
  199. foreach ($froms as $k => $from) {
  200. $from = trim($from);
  201. if (empty($from)) continue;
  202. $episodes = [];
  203. $urlStr = isset($urls[$k]) ? $urls[$k] : '';
  204. if (!empty($urlStr)) {
  205. $parts = explode('#', $urlStr);
  206. foreach ($parts as $idx => $part) {
  207. $part = trim($part);
  208. if (empty($part)) continue;
  209. $arr = explode('$', $part);
  210. $episodes[] = [
  211. 'name' => isset($arr[0]) ? $arr[0] : '下载' . ($idx + 1),
  212. 'url' => isset($arr[1]) ? $arr[1] : $arr[0],
  213. ];
  214. }
  215. }
  216. $downList[] = [
  217. 'from' => $from,
  218. 'urls' => $episodes,
  219. ];
  220. }
  221. }
  222. $res['vod_down_list'] = $downList;
  223. // 清理原始大字段(可选)
  224. unset($res['vod_play_url'], $res['vod_play_server'], $res['vod_play_note']);
  225. unset($res['vod_down_url'], $res['vod_down_server'], $res['vod_down_note']);
  226. // 与 get_list / model Vod 一致:mac_get_vip_exclusive_type_ids()
  227. $detailWrap = [$res];
  228. mac_append_type_is_vip_exclusive_for_rows($detailWrap);
  229. $res = $detailWrap[0];
  230. $uid = (int) ($GLOBALS['user']['user_id'] ?? 0);
  231. $vid = (int) ($res['vod_id'] ?? 0);
  232. $fav = mac_user_fav_state($uid, 1, $vid);
  233. $res['is_fav'] = $fav['is_fav'];
  234. $res['fav_ulog_id'] = $fav['fav_ulog_id'];
  235. $res['user_has_up'] = mac_user_has_digg(1, $vid);
  236. // 返回
  237. return json([
  238. 'code' => 1,
  239. 'msg' => '获取成功',
  240. 'info' => $res
  241. ]);
  242. }
  243. /**
  244. * 获取视频的年份
  245. *
  246. * @return \think\response\Json
  247. * @throws \think\db\exception\DataNotFoundException
  248. * @throws \think\db\exception\ModelNotFoundException
  249. * @throws \think\exception\DbException
  250. */
  251. public function get_year(Request $request)
  252. {
  253. $param = $request->param();
  254. $validate = validate($request->controller());
  255. if (!$validate->scene($request->action())->check($param)) {
  256. return json([
  257. 'code' => 1001,
  258. 'msg' => '参数错误: ' . $validate->getError(),
  259. ]);
  260. }
  261. $result = Db::table('mac_vod')->distinct(true)->field('vod_year')->where(['type_id_1' => $param['type_id_1']])->select();
  262. $return = [];
  263. foreach ($result as $index => $item) {
  264. if (!empty($item['vod_year'])){
  265. array_push($return,$item['vod_year']);
  266. }
  267. }
  268. // 返回
  269. return json([
  270. 'code' => 1,
  271. 'msg' => '获取成功',
  272. 'info' => [
  273. 'total' => count($return),
  274. 'rows' => $return,
  275. ],
  276. ]);
  277. }
  278. /**
  279. * 获取该视频类型名称
  280. *
  281. * @return \think\response\Json
  282. * @throws \think\db\exception\DataNotFoundException
  283. * @throws \think\db\exception\ModelNotFoundException
  284. * @throws \think\exception\DbException
  285. */
  286. public function get_class(Request $request)
  287. {
  288. $param = $request->param();
  289. $validate = validate($request->controller());
  290. if (!$validate->scene($request->action())->check($param)) {
  291. return json([
  292. 'code' => 1001,
  293. 'msg' => '参数错误: ' . $validate->getError(),
  294. ]);
  295. }
  296. $result = Db::table('mac_vod')->distinct(true)->field('vod_class')->where(['type_id_1' => $param['type_id_1']])->select();
  297. $return = [];
  298. foreach ($result as $index => $item) {
  299. if (!empty($item['vod_class'])){
  300. array_push($return,$item['vod_class']);
  301. }
  302. }
  303. // 返回
  304. return json([
  305. 'code' => 1,
  306. 'msg' => '获取成功',
  307. 'info' => [
  308. 'total' => count($return),
  309. 'rows' => $return,
  310. ],
  311. ]);
  312. }
  313. /**
  314. * 获取该视频类型的地区
  315. *
  316. * @return \think\response\Json
  317. * @throws \think\db\exception\DataNotFoundException
  318. * @throws \think\db\exception\ModelNotFoundException
  319. * @throws \think\exception\DbException
  320. */
  321. public function get_area(Request $request)
  322. {
  323. $param = $request->param();
  324. $validate = validate($request->controller());
  325. if (!$validate->scene($request->action())->check($param)) {
  326. return json([
  327. 'code' => 1001,
  328. 'msg' => '参数错误: ' . $validate->getError(),
  329. ]);
  330. }
  331. $result = Db::table('mac_vod')->distinct(true)->field('vod_area')->where(['type_id_1' => $param['type_id_1']])->select();
  332. $return = [];
  333. foreach ($result as $index => $item) {
  334. if (!empty($item['vod_area'])){
  335. array_push($return,$item['vod_area']);
  336. }
  337. }
  338. // 返回
  339. return json([
  340. 'code' => 1,
  341. 'msg' => '获取成功',
  342. 'info' => [
  343. 'total' => count($return),
  344. 'rows' => $return,
  345. ],
  346. ]);
  347. }
  348. /**
  349. * 获取 Banner 推荐影片
  350. * 对应首页 Banner 轮播区,取推荐等级高的影片
  351. *
  352. * @param Request $request
  353. * @return \think\response\Json
  354. *
  355. * 参数说明:
  356. * num - 可选,数量,默认5
  357. * start - 可选,偏移量,默认0(换一换分页)
  358. * level - 可选,推荐等级,默认9,多个用逗号分隔
  359. */
  360. public function get_banner(Request $request)
  361. {
  362. $param = $request->param();
  363. $num = isset($param['num']) ? (int)$param['num'] : 5;
  364. $start = isset($param['start']) ? (int)$param['start'] : 0;
  365. if ($start < 0) {
  366. $start = 0;
  367. }
  368. $level = isset($param['level']) ? trim($param['level']) : '9';
  369. $where = [];
  370. $where['vod_status'] = ['eq', 1];
  371. $where['vod_level'] = ['in', $level];
  372. $list = Db::table('mac_vod')
  373. ->field('vod_id,vod_name,vod_sub,vod_pic,vod_pic_slide,vod_actor,vod_director,vod_score,vod_content,vod_blurb,vod_remarks,vod_year,vod_area,vod_class,vod_points_play,type_id,type_id_1')
  374. ->where($where)
  375. ->order('vod_time desc')
  376. ->limit($start, $num)
  377. ->select();
  378. $userId = intval($GLOBALS['user']['user_id'] ?? 0);
  379. $favMap = [];
  380. if ($userId > 0 && !empty($list)) {
  381. $vodIds = [];
  382. foreach ($list as $v) {
  383. if (!empty($v['vod_id'])) {
  384. $vodIds[] = intval($v['vod_id']);
  385. }
  386. }
  387. if (!empty($vodIds)) {
  388. $favRows = model('Ulog')->where([
  389. 'user_id' => $userId,
  390. 'ulog_type' => 2,
  391. 'ulog_rid' => ['in', implode(',', array_unique($vodIds))],
  392. ])->column('ulog_id', 'ulog_rid');
  393. if (is_array($favRows)) {
  394. $favMap = $favRows;
  395. }
  396. }
  397. }
  398. foreach ($list as &$v) {
  399. $v['vod_pic'] = mac_url_img($v['vod_pic']);
  400. $v['vod_pic_slide'] = mac_url_img($v['vod_pic_slide']);
  401. $v['vod_link'] = mac_url_vod_detail($v);
  402. $vodId = intval($v['vod_id'] ?? 0);
  403. $favUid = isset($favMap[$vodId]) ? intval($favMap[$vodId]) : 0;
  404. $v['is_fav'] = $favUid > 0 ? 1 : 0;
  405. $v['fav_uid'] = $favUid;
  406. // 清理 HTML 标签
  407. $v['vod_content'] = strip_tags($v['vod_content']);
  408. if (mb_strlen($v['vod_content']) > 100) {
  409. $v['vod_content'] = mb_substr($v['vod_content'], 0, 100) . '...';
  410. }
  411. }
  412. unset($v);
  413. mac_append_type_is_vip_exclusive_for_rows($list);
  414. return json([
  415. 'code' => 1,
  416. 'msg' => '获取成功',
  417. 'info' => [
  418. 'total' => count($list),
  419. 'rows' => $list,
  420. ],
  421. ]);
  422. }
  423. /**
  424. * 获取热门推荐影片
  425. * 对应首页热门推荐 Tab 区块,按月度点击量排序
  426. *
  427. * @param Request $request
  428. * @return \think\response\Json
  429. *
  430. * 参数说明:
  431. * num - 可选,数量,默认6
  432. * type_id - 可选,分类ID,不传则查全站
  433. * start - 可选,偏移量,默认0
  434. * level - 可选,推荐等级筛选,多个用逗号分隔,如 "1,2,3,4,5,6,7,8,9"
  435. * by - 可选,排序字段,默认 hits_month,可选: hits,hits_day,hits_week,hits_month,score,time
  436. */
  437. public function get_hot(Request $request)
  438. {
  439. $param = $request->param();
  440. $num = isset($param['num']) ? (int)$param['num'] : 6;
  441. $start = isset($param['start']) ? (int)$param['start'] : 0;
  442. $typeId = isset($param['type_id']) ? (int)$param['type_id'] : 0;
  443. $level = isset($param['level']) ? trim($param['level']) : '';
  444. $by = isset($param['by']) ? trim($param['by']) : 'hits_month';
  445. // 验证排序字段
  446. $allowBy = ['hits', 'hits_day', 'hits_week', 'hits_month', 'score', 'time'];
  447. if (!in_array($by, $allowBy)) {
  448. $by = 'hits_month';
  449. }
  450. $where = [];
  451. $where['vod_status'] = ['eq', 1];
  452. if ($typeId > 0) {
  453. // 同时匹配 type_id 和 type_id_1(父分类)
  454. $where['type_id|type_id_1'] = ['eq', $typeId];
  455. }
  456. if (!empty($level)) {
  457. $where['vod_level'] = ['in', $level];
  458. }
  459. $list = Db::table('mac_vod')
  460. ->field('vod_id,vod_name,vod_sub,vod_pic,vod_actor,vod_director,vod_score,vod_remarks,vod_year,vod_area,vod_class,vod_blurb,vod_time,vod_hits_month,type_id,type_id_1')
  461. ->where($where)
  462. ->order('vod_' . $by . ' desc')
  463. ->limit($start, $num)
  464. ->select();
  465. foreach ($list as &$v) {
  466. $v['vod_pic'] = mac_url_img($v['vod_pic']);
  467. $v['vod_time_text'] = date('m-d', $v['vod_time']);
  468. $v['vod_link'] = mac_url_vod_detail($v);
  469. }
  470. unset($v);
  471. mac_append_type_is_vip_exclusive_for_rows($list);
  472. return json([
  473. 'code' => 1,
  474. 'msg' => '获取成功',
  475. 'info' => [
  476. 'total' => count($list),
  477. 'rows' => $list,
  478. ],
  479. ]);
  480. }
  481. /**
  482. * 按分类获取最新影片
  483. * 对应首页各分类区块的最新影片列表
  484. *
  485. * @param Request $request
  486. * @return \think\response\Json
  487. *
  488. * 参数说明:
  489. * type_id - 必须,分类ID
  490. * num - 可选,数量,默认24
  491. *
  492. * info.today_new_count:当前分类(含子类)下,vod_time_add 或 vod_time 任一落在服务器当天自然日内的条数(去重按行,每条视频计 1)
  493. */
  494. public function get_latest_by_type(Request $request)
  495. {
  496. $param = $request->param();
  497. if (empty($param['type_id'])) {
  498. return json(['code' => 1001, 'msg' => '参数错误: type_id 必须']);
  499. }
  500. $typeId = (int)$param['type_id'];
  501. $num = isset($param['num']) ? (int)$param['num'] : 24;
  502. $num = max(1, min($num, 60));
  503. $start = isset($param['start']) ? max(0, (int)$param['start']) : 0;
  504. $typeIds = mac_vod_type_filter_ids_for_list($typeId);
  505. if (empty($typeIds)) {
  506. return json([
  507. 'code' => 1,
  508. 'msg' => '获取成功',
  509. 'info' => ['total' => 0, 'today_new_count' => 0, 'rows' => []],
  510. ]);
  511. }
  512. $cacheFlag = $GLOBALS['config']['app']['cache_flag'] ?? 'maccms';
  513. $cacheTime = (int)($GLOBALS['config']['app']['cache_time'] ?? 0);
  514. $idsForKey = $typeIds;
  515. sort($idsForKey, SORT_NUMERIC);
  516. $cacheKey = $cacheFlag . '_api_vod_latest_by_type_' . md5($typeId . '_' . $num . '_' . $start . '_' . implode(',', $idsForKey) . '_' . date('Y-m-d'));
  517. if (!empty($GLOBALS['config']['app']['cache_core']) && $cacheTime > 0) {
  518. $cached = Cache::get($cacheKey);
  519. if (is_array($cached) && isset($cached['code'])) {
  520. return json($cached);
  521. }
  522. }
  523. // 仅列表所需字段;去掉 vod_director、vod_trysee 等,减轻行缓冲与 IO
  524. $fields = 'vod_id,vod_name,vod_sub,vod_pic,vod_actor,vod_score,vod_remarks,vod_year,vod_area,vod_class,vod_blurb,vod_time,vod_isend,vod_points_play,type_id,type_id_1';
  525. $list = Db::table('mac_vod')
  526. ->field($fields)
  527. ->where('vod_status', 1)
  528. ->where('type_id', 'in', $typeIds)
  529. ->order('vod_time', 'desc')
  530. ->limit($start, $num)
  531. ->select();
  532. foreach ($list as &$v) {
  533. $v['vod_pic'] = mac_url_img($v['vod_pic']);
  534. $v['vod_time_text'] = date('m-d', $v['vod_time']);
  535. $v['vod_link'] = mac_url_vod_detail($v);
  536. }
  537. unset($v);
  538. mac_append_type_is_vip_exclusive_for_rows($list);
  539. $dayStart = (int)strtotime('today');
  540. $dayEnd = (int)strtotime('tomorrow');
  541. $todayNewCount = (int)Db::table('mac_vod')
  542. ->where('vod_status', 1)
  543. ->where('type_id', 'in', $typeIds)
  544. ->where(function ($query) use ($dayStart, $dayEnd) {
  545. $query->where(function ($q) use ($dayStart, $dayEnd) {
  546. $q->where('vod_time_add', '>=', $dayStart)->where('vod_time_add', '<', $dayEnd);
  547. })->whereOr(function ($q) use ($dayStart, $dayEnd) {
  548. $q->where('vod_time', '>=', $dayStart)->where('vod_time', '<', $dayEnd);
  549. });
  550. })
  551. ->count();
  552. $payload = [
  553. 'code' => 1,
  554. 'msg' => '获取成功',
  555. 'info' => [
  556. 'total' => count($list),
  557. 'today_new_count' => $todayNewCount,
  558. 'rows' => $list,
  559. ],
  560. ];
  561. if (!empty($GLOBALS['config']['app']['cache_core']) && $cacheTime > 0) {
  562. Cache::set($cacheKey, $payload, $cacheTime);
  563. }
  564. return json($payload);
  565. }
  566. /**
  567. * 获取排行榜数据
  568. * 对应首页热播排行榜区块
  569. *
  570. * @param Request $request
  571. * @return \think\response\Json
  572. *
  573. * 参数说明:
  574. * type_id - 可选,分类ID,不传则查全站
  575. * num - 可选,数量,默认10
  576. * by - 可选,排序字段,默认 hits_month
  577. */
  578. public function get_rank(Request $request)
  579. {
  580. $param = $request->param();
  581. $typeId = isset($param['type_id']) ? (int)$param['type_id'] : 0;
  582. $num = isset($param['num']) ? (int)$param['num'] : 10;
  583. $start = isset($param['start']) ? max(0, (int)$param['start']) : 0;
  584. $by = isset($param['by']) ? trim($param['by']) : 'hits_month';
  585. $allowBy = ['hits', 'hits_day', 'hits_week', 'hits_month', 'score', 'time'];
  586. if (!in_array($by, $allowBy)) {
  587. $by = 'hits_month';
  588. }
  589. $where = [];
  590. $where['vod_status'] = ['eq', 1];
  591. if ($typeId > 0) {
  592. $where['type_id|type_id_1'] = ['eq', $typeId];
  593. }
  594. $list = Db::table('mac_vod')
  595. ->field('vod_id,vod_name,vod_pic,vod_score,vod_remarks,vod_hits,vod_hits_day,vod_hits_week,vod_hits_month,vod_year,vod_area,vod_class,vod_isend,vod_points_play,type_id,type_id_1')
  596. ->where($where)
  597. ->order('vod_' . $by . ' desc')
  598. ->limit($start, $num)
  599. ->select();
  600. foreach ($list as $k => &$v) {
  601. $v['vod_pic'] = mac_url_img($v['vod_pic']);
  602. $v['rank'] = $k + 1;
  603. $v['vod_link'] = mac_url_vod_detail($v);
  604. }
  605. unset($v);
  606. mac_append_type_is_vip_exclusive_for_rows($list);
  607. return json([
  608. 'code' => 1,
  609. 'msg' => '获取成功',
  610. 'info' => [
  611. 'total' => count($list),
  612. 'rows' => $list,
  613. ],
  614. ]);
  615. }
  616. /**
  617. * 更新/获取点击数
  618. * api.php/vod/update_hits?id=1&type=update
  619. * type: update=更新并返回; 默认=只获取
  620. */
  621. public function update_hits(Request $request)
  622. {
  623. $param = $request->param();
  624. $id = intval($param['id'] ?? 0);
  625. if ($id < 1) return json(['code' => 1001, 'msg' => '参数错误']);
  626. $where = ['vod_id' => $id];
  627. $field = 'vod_hits,vod_hits_day,vod_hits_week,vod_hits_month,vod_time_hits';
  628. $res = model('Vod')->infoData($where, $field);
  629. if ($res['code'] > 1) return json($res);
  630. $info = $res['info'];
  631. if ($param['type'] == 'update') {
  632. $update = [
  633. 'vod_hits' => $info['vod_hits'],
  634. 'vod_hits_day' => $info['vod_hits_day'],
  635. 'vod_hits_week' => $info['vod_hits_week'],
  636. 'vod_hits_month' => $info['vod_hits_month'],
  637. ];
  638. $new = getdate();
  639. $old = getdate($info['vod_time_hits']);
  640. if ($new['year'] == $old['year'] && $new['mon'] == $old['mon']) {
  641. $update['vod_hits_month']++;
  642. } else {
  643. $update['vod_hits_month'] = 1;
  644. }
  645. $ws = mktime(0,0,0,$new["mon"],$new["mday"],$new["year"]) - ($new["wday"] * 86400);
  646. $we = mktime(23,59,59,$new["mon"],$new["mday"],$new["year"]) + ((6-$new["wday"])*86400);
  647. if ($info['vod_time_hits'] >= $ws && $info['vod_time_hits'] <= $we) {
  648. $update['vod_hits_week']++;
  649. } else {
  650. $update['vod_hits_week'] = 1;
  651. }
  652. if ($new['year']==$old['year'] && $new['mon']==$old['mon'] && $new['mday']==$old['mday']) {
  653. $update['vod_hits_day']++;
  654. } else {
  655. $update['vod_hits_day'] = 1;
  656. }
  657. $update['vod_hits'] += 1;
  658. $update['vod_time_hits'] = time();
  659. model('Vod')->where($where)->update($update);
  660. return json(['code' => 1, 'msg' => 'ok', 'data' => [
  661. 'hits' => $update['vod_hits'], 'hits_day' => $update['vod_hits_day'],
  662. 'hits_week' => $update['vod_hits_week'], 'hits_month' => $update['vod_hits_month'],
  663. ]]);
  664. }
  665. return json(['code' => 1, 'msg' => 'ok', 'data' => [
  666. 'hits' => $info['vod_hits'], 'hits_day' => $info['vod_hits_day'],
  667. 'hits_week' => $info['vod_hits_week'], 'hits_month' => $info['vod_hits_month'],
  668. ]]);
  669. }
  670. /**
  671. * 影片顶/踩
  672. * api.php/vod/digg?id=1&type=up
  673. * type: up=顶; down=踩; 仅查询不传type
  674. */
  675. public function digg(Request $request)
  676. {
  677. $param = $request->param();
  678. $id = intval($param['id'] ?? 0);
  679. if ($id < 1) return json(['code' => 1001, 'msg' => '参数错误']);
  680. $where = ['vod_id' => $id];
  681. $model = model('Vod');
  682. $type = trim($param['type'] ?? '');
  683. if ($type) {
  684. $cookie = 'vod-digg-' . $id;
  685. if (!empty(cookie($cookie))) return json(['code' => 1002, 'msg' => lang('index/haved')]);
  686. if ($type == 'up') { $model->where($where)->setInc('vod_up'); cookie($cookie, 't', 30); }
  687. elseif ($type == 'down') { $model->where($where)->setInc('vod_down'); cookie($cookie, 't', 30); }
  688. }
  689. $res = $model->infoData($where, 'vod_up,vod_down');
  690. if ($res['code'] > 1) return json($res);
  691. return json(['code' => 1, 'msg' => 'ok', 'data' => ['up' => $res['info']['vod_up'] ?? 0, 'down' => $res['info']['vod_down'] ?? 0]]);
  692. }
  693. /**
  694. * 影片评分
  695. * api.php/vod/update_score?id=1&score=8
  696. * score: 1-10 评分值;不传只获取当前评分
  697. */
  698. public function update_score(Request $request)
  699. {
  700. $param = $request->param();
  701. $id = intval($param['id'] ?? 0);
  702. if ($id < 1) return json(['code' => 1001, 'msg' => '参数错误']);
  703. $where = ['vod_id' => $id];
  704. $res = model('Vod')->infoData($where, 'vod_score,vod_score_num,vod_score_all');
  705. if ($res['code'] > 1) return json($res);
  706. $info = $res['info'];
  707. $score = intval($param['score'] ?? 0);
  708. if ($score > 0) {
  709. $cookie = 'vod-score-' . $id;
  710. if (!empty(cookie($cookie))) return json(['code' => 1002, 'msg' => lang('index/haved')]);
  711. $num = intval($info['vod_score_num']) + 1;
  712. $all = intval($info['vod_score_all']) + $score;
  713. $avg = number_format($all / $num, 1, '.', '');
  714. model('Vod')->where($where)->update(['vod_score_num' => $num, 'vod_score_all' => $all, 'vod_score' => $avg]);
  715. cookie($cookie, 't', 30);
  716. return json(['code' => 1, 'msg' => lang('score_ok'), 'data' => ['score' => $avg, 'score_num' => $num, 'score_all' => $all]]);
  717. }
  718. return json(['code' => 1, 'msg' => 'ok', 'data' => ['score' => $info['vod_score'] ?? 0, 'score_num' => $info['vod_score_num'] ?? 0, 'score_all' => $info['vod_score_all'] ?? 0]]);
  719. }
  720. /**
  721. * 搜索建议/自动完成
  722. * api.php/vod/suggest?wd=战狼&limit=10
  723. */
  724. public function suggest(Request $request)
  725. {
  726. if ($GLOBALS['config']['app']['search'] != '1') return json(['code' => 999, 'msg' => lang('suggest_close')]);
  727. $param = $request->param();
  728. $wd = trim($param['wd'] ?? '');
  729. if (empty($wd)) return json(['code' => 1001, 'msg' => '参数错误']);
  730. $limit = max(1, min(20, intval($param['limit'] ?? 10)));
  731. $where = ['vod_name|vod_en' => ['like', '%' . $wd . '%']];
  732. // 需 type / type_1 / vod_time 等以正确生成伪静态详情链接;返回时再精简字段
  733. $field = 'vod_id,vod_name,vod_en,vod_pic,vod_time,type_id,type_id_1';
  734. $res = model('Vod')->listData($where, 'vod_id desc', 1, $limit, 0, $field, 1, 1);
  735. if ($res['code'] == 1 && !empty($res['list'])) {
  736. $out = [];
  737. foreach ($res['list'] as $v) {
  738. $out[] = [
  739. 'id' => (int)($v['vod_id'] ?? 0),
  740. 'name' => (string)($v['vod_name'] ?? ''),
  741. 'en' => (string)($v['vod_en'] ?? ''),
  742. 'pic' => mac_url_img($v['vod_pic'] ?? ''),
  743. 'vod_link'=> mac_url_vod_detail($v),
  744. ];
  745. }
  746. $res['list'] = $out;
  747. }
  748. $res['url'] = mac_url_search(['wd' => urlencode($wd)], 'vod');
  749. return json($res);
  750. }
  751. /**
  752. * 验证播放/下载密码
  753. * api.php/vod/verify_pwd?id=1&pwd=123&type=4
  754. * type: 1=访问密码 4=播放密码 5=下载密码
  755. */
  756. public function verify_pwd(Request $request)
  757. {
  758. $param = $request->param();
  759. $id = intval($param['id'] ?? 0);
  760. $type = intval($param['type'] ?? 0);
  761. $pwd = trim($param['pwd'] ?? '');
  762. if ($id < 1 || empty($pwd) || !in_array($type, [1, 4, 5])) return json(['code' => 1001, 'msg' => lang('param_err')]);
  763. $key = '1-' . $type . '-' . $id;
  764. if (session($key) == '1') return json(['code' => 1002, 'msg' => lang('index/pwd_repeat')]);
  765. if (mac_get_time_span("last_pwd") < 5) return json(['code' => 1003, 'msg' => lang('index/pwd_frequently')]);
  766. $res = model('Vod')->infoData(['vod_id' => ['eq', $id]]);
  767. if ($res['code'] > 1) return json(['code' => 1011, 'msg' => $res['msg']]);
  768. $pwdMap = [1 => 'vod_pwd', 4 => 'vod_pwd_play', 5 => 'vod_pwd_down'];
  769. if ($res['info'][$pwdMap[$type]] != $pwd) return json(['code' => 1012, 'msg' => lang('pass_err')]);
  770. session($key, '1');
  771. return json(['code' => 1, 'msg' => 'ok']);
  772. }
  773. /**
  774. * 获取播放页信息(含源列表、权限检查)
  775. * api.php/vod/get_play_info?id=1&sid=1&nid=1
  776. */
  777. public function get_play_info(Request $request)
  778. {
  779. $param = $request->param();
  780. $id = intval($param['id'] ?? 0);
  781. $sid = intval($param['sid'] ?? 1);
  782. $nid = intval($param['nid'] ?? 1);
  783. if ($id < 1) return json(['code' => 1001, 'msg' => '参数错误: id 必须']);
  784. $where = ['vod_id' => $id, 'vod_status' => ['eq', 1]];
  785. $res = model('Vod')->infoData($where);
  786. if ($res['code'] > 1) return json($res);
  787. $info = $res['info'];
  788. // 解析播放源
  789. $playList = mac_play_list(
  790. $info['vod_play_from'] ?? '', $info['vod_play_url'] ?? '',
  791. $info['vod_play_server'] ?? '', $info['vod_play_note'] ?? '', 'play'
  792. );
  793. // 取当前集播放地址
  794. $currentPlay = $playList[$sid]['urls'][$nid] ?? null;
  795. return json(['code' => 1, 'msg' => 'ok', 'info' => [
  796. 'vod_id' => intval($info['vod_id']),
  797. 'vod_name' => $info['vod_name'] ?? '',
  798. 'vod_pic' => mac_url_img($info['vod_pic'] ?? ''),
  799. 'vod_remarks' => $info['vod_remarks'] ?? '',
  800. 'vod_score' => $info['vod_score'] ?? '',
  801. 'vod_copyright' => intval($info['vod_copyright'] ?? 0),
  802. 'vod_points_play' => intval($info['vod_points_play'] ?? 0),
  803. 'vod_pwd_play' => !empty($info['vod_pwd_play']) ? 1 : 0,
  804. 'type_id' => intval($info['type_id'] ?? 0),
  805. 'play_list' => $playList,
  806. 'current' => $currentPlay,
  807. 'sid' => $sid,
  808. 'nid' => $nid,
  809. 'play_url' => mac_url_vod_play($info, ['sid' => $sid, 'nid' => $nid]),
  810. ]]);
  811. }
  812. /**
  813. * 获取下载页信息
  814. * api.php/vod/get_down_info?id=1&sid=1&nid=1
  815. */
  816. public function get_down_info(Request $request)
  817. {
  818. $param = $request->param();
  819. $id = intval($param['id'] ?? 0);
  820. $sid = intval($param['sid'] ?? 1);
  821. $nid = intval($param['nid'] ?? 1);
  822. if ($id < 1) return json(['code' => 1001, 'msg' => '参数错误: id 必须']);
  823. $where = ['vod_id' => $id, 'vod_status' => ['eq', 1]];
  824. $res = model('Vod')->infoData($where);
  825. if ($res['code'] > 1) return json($res);
  826. $info = $res['info'];
  827. $downList = mac_play_list(
  828. $info['vod_down_from'] ?? '', $info['vod_down_url'] ?? '',
  829. $info['vod_down_server'] ?? '', $info['vod_down_note'] ?? '', 'down'
  830. );
  831. $currentDown = $downList[$sid]['urls'][$nid] ?? null;
  832. return json(['code' => 1, 'msg' => 'ok', 'info' => [
  833. 'vod_id' => intval($info['vod_id']),
  834. 'vod_name' => $info['vod_name'] ?? '',
  835. 'vod_pic' => mac_url_img($info['vod_pic'] ?? ''),
  836. 'vod_points_down' => intval($info['vod_points_down'] ?? 0),
  837. 'vod_pwd_down' => !empty($info['vod_pwd_down']) ? 1 : 0,
  838. 'type_id' => intval($info['type_id'] ?? 0),
  839. 'down_list' => $downList,
  840. 'current' => $currentDown,
  841. 'sid' => $sid,
  842. 'nid' => $nid,
  843. ]]);
  844. }
  845. }