CourseController.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: wniu
  5. * Date: 2022/3/15
  6. * Time: 10:17 上午
  7. */
  8. namespace addons\Course\api\modules\v1\controllers;
  9. use addons\Course\common\enums\CourseEnum;
  10. use addons\Course\common\logic\Video;
  11. use addons\Course\common\logic\Watch;
  12. use addons\Course\common\models\AddonsCourse;
  13. use addons\Course\common\models\AddonsCourseBought;
  14. use addons\Course\common\models\AddonsCourseChapter;
  15. use addons\Course\common\models\AddonsCourseChapterLikes;
  16. use addons\Course\common\models\AddonsCourseDirectorys;
  17. use addons\Course\common\models\AddonsCourseRelays;
  18. use addons\Course\common\models\CoursePromoter;
  19. use addons\Course\common\models\CourseWatchLog;
  20. use api\controllers\OnAuthController;
  21. use common\enums\RabbitMqEnum;
  22. use common\enums\StatusEnum;
  23. use common\models\user\User;
  24. class CourseController extends OnAuthController
  25. {
  26. public $modelClass = '';
  27. /**
  28. * 不用进行登录验证的方法
  29. *
  30. * 例如: ['index', 'update', 'create', 'view', 'delete']
  31. * 默认全部需要验证
  32. *
  33. * @var array
  34. */
  35. protected $authOptional = ['detail', 'chapter'];
  36. public function actionDetail()
  37. {
  38. $get = \Yii::$app->request->get();
  39. $res = \Yii::$app->services->validate->validate($get, [
  40. [['course_id'], 'required'],
  41. ]);
  42. if ($res === false) return $this->error(\Yii::$app->services->validate->getErrorMessage());
  43. //课程详情
  44. $list = AddonsCourse::getCourseListWithMemberprice($this->user, $this->mall_id, $get['course_id'], [], '*');
  45. $course = $list['list'][0] ?? [];
  46. if (empty($course) || $course['status'] != StatusEnum::ENABLED) {
  47. return $this->error("课程不存在或已下架");
  48. }
  49. //去掉不需要返回的数据字段
  50. $course['created_at'] = date("m月d日", $course['created_at']);
  51. unset($course['is_open_vm'], $course['vm_shares'], $course['vm_likes'], $course['vm_downloads'], $course['vm_views'], $course['updated_at'], $course['status']);
  52. $can_play_res = AddonsCourse::courseCanPlay($get['course_id'], $this->mall_id, $this->user);
  53. $is_can_play = $can_play_res['is_can_play'];
  54. // 当前用户还需要转发的次数。如果不是转发观看的课程,need_relays = 0;
  55. $need_relays = $can_play_res['need_relays'] ?? 0;
  56. // 当前用户已经转发的次数。如果不是转发观看的课程,current_user_relayed_times = 0;
  57. $current_user_relayed_times = $can_play_res['current_user_relayed_times'] ?? 0;
  58. if (!empty($this->user_id)) {
  59. $is_bought = AddonsCourse::courseIsBought($get['course_id'], $this->mall_id, $this->user_id);
  60. //用户是否点赞
  61. $is_likes = AddonsCourseChapterLikes::getUserIsLikes($get['course_id'], $this->user_id);
  62. } else {
  63. $is_bought = 0;
  64. $is_likes = 0;
  65. }
  66. //章节列表,只有一个,是点击课程进来之后默认播放的章节
  67. $chapter_model = AddonsCourseChapter::find()->where(["mall_id" => $this->mall_id, "course_id" => $get['course_id'], "status" => StatusEnum::ENABLED]);
  68. $chapter_lists = $chapter_model->select("id,mall_id,course_id,name,type,goods_list,can_see,views,sort,video_length,thumb_url")
  69. ->orderBy("sort asc,created_at desc")
  70. ->asArray()
  71. ->all();
  72. $chapter_list = $chapter_lists[0] ?? [];
  73. $goods_list = [];
  74. $goods_ids = [];
  75. if (!empty($chapter_lists)) {
  76. //获取课程所有商品
  77. $good_id_strs = array_column($chapter_lists, 'goods_list');
  78. if (!empty($good_id_strs)) {
  79. foreach ($good_id_strs as $id_str) {
  80. $goods_ids = array_merge($goods_ids, explode(',', $id_str));
  81. }
  82. $goods_ids = array_unique($goods_ids);
  83. $goods_list = AddonsCourseChapter::getAllGoods($goods_ids, false);
  84. }
  85. }
  86. if (!empty($chapter_list)) {
  87. if ($chapter_list['type'] == CourseEnum::CHAPTER_TYPE_AUDIO) {
  88. $chapter_list['thumb_url'] = $course['logo'];
  89. }
  90. }
  91. if (1 !== $is_can_play) {
  92. $chapter_list = null;
  93. }
  94. return $this->success('success', compact("course", "chapter_list", "goods_list", "is_likes", 'is_can_play', 'is_bought', 'current_user_relayed_times', 'need_relays'));
  95. }
  96. /**
  97. * 获取章节列表
  98. * @return mixed
  99. */
  100. public function actionChapter()
  101. {
  102. $get = \Yii::$app->request->get();
  103. $res = \Yii::$app->services->validate->validate($get, [
  104. [['course_id'], 'required'],
  105. ]);
  106. if ($res === false) return $this->error(\Yii::$app->services->validate->getErrorMessage());
  107. $data['list'] = AddonsCourseChapter::find()
  108. ->select('id,mall_id,course_id,dir_id,name,can_see,views,sort, thumb_url,video_length,goods_list,video_length')
  109. ->where(['course_id' => $get['course_id'], 'mall_id' => $this->mall_id, "status" => StatusEnum::ENABLED])
  110. ->orderBy('sort asc,created_at desc')
  111. ->asArray()
  112. ->all();
  113. if (!empty($data['list'])) {
  114. foreach ($data['list'] as $dk => $dv) {
  115. $dv['video_length'] = '';
  116. // if (!empty($dv['video_length'])) {
  117. // $video_lengths = explode(':', $dv['video_length']);
  118. // $video_lengths[2] = ceil($video_lengths[2]);
  119. // $dv['video_length'] = implode(':', $video_lengths);
  120. // }
  121. $dv['goods_list'] = AddonsCourseChapter::getAllGoods($dv["goods_list"], false);
  122. $chapter_list[$dv['dir_id']][] = $dv;
  123. }
  124. // 课程目录数据
  125. $dir_info = AddonsCourseDirectorys::getDirTreeList($this->mall_id, $get['course_id'], 'id,course_id,name as directory_name,parent_id');
  126. $dir_info = $dir_info['tree'];
  127. // dd($dir_info);
  128. if (!empty($dir_info)) {
  129. foreach ($dir_info as $dik => $div) {
  130. if (!empty($chapter_list[$div['id']])) {
  131. $dir_info[$dik]['chapter_list'] = $chapter_list[$div['id']];
  132. } else {
  133. $dir_info[$dik]['chapter_list'] = [];
  134. }
  135. if (!empty($div['sub_dir'])) {
  136. foreach ($div['sub_dir'] as $dsk => $dsv) {
  137. if (!empty($chapter_list[$dsv['id']])) {
  138. $dir_info[$dik]['sub_dir'][$dsk]['chapter_list'] = $chapter_list[$dsv['id']];
  139. } else {
  140. $dir_info[$dik]['sub_dir'][$dsk]['chapter_list'] = [];
  141. }
  142. }
  143. }
  144. }
  145. // dd($dir_info);
  146. }
  147. } else {
  148. $dir_info = [];
  149. }
  150. $data['list'] = $dir_info;
  151. // dd($data);
  152. return $this->success('success', $data);
  153. }
  154. /**
  155. * 课程下载
  156. * @return mixed
  157. */
  158. public function actionDownload()
  159. {
  160. $get = \Yii::$app->request->get();
  161. $res = \Yii::$app->services->validate->validate($get, [
  162. [['course_id', 'chapter_id'], 'required'],
  163. ]);
  164. if ($res === false) return $this->error(\Yii::$app->services->validate->getErrorMessage());
  165. $course_id = \Yii::$app->request->get('course_id');
  166. $chapter_id = \Yii::$app->request->get('chapter_id');
  167. $course = AddonsCourse::getDetail($course_id, $this->mall_id, '*');
  168. if (empty($course)) {
  169. return $this->error("课程不存在或已下架");
  170. }
  171. $chapter = AddonsCourseChapter::getDetail($chapter_id);
  172. if (empty($chapter)) {
  173. return $this->error("课程不存在或已下架");
  174. }
  175. //检查用户是否有查看章节的权限
  176. $can_play_res = AddonsCourse::courseCanPlay($course_id, $this->mall_id, $this->user);
  177. if (empty($chapter->can_see) && empty($can_play_res['is_can_play'])) {
  178. return $this->error("您没有权限查看该课程");
  179. }
  180. //设置课程视频下载次数 + 1
  181. //$course->downloads = $course->downloads + 1;
  182. if (!$course->updateCounters(['downloads' => 1])) {
  183. return $this->error("保存课程下载次数失败");
  184. }
  185. $data = ["video_url" => $chapter->video_url];
  186. return $this->success('success', $data);
  187. }
  188. /**
  189. * 课程播放
  190. * @Author: guyu
  191. * @DateTime:2021-03-22 14:55
  192. * @return mixed
  193. */
  194. public function actionPlay()
  195. {
  196. $get = \Yii::$app->request->get();
  197. $res = \Yii::$app->services->validate->validate($get, [
  198. [['course_id'], 'required'],
  199. [['course_id'], 'integer'],
  200. [['chapter_id'], 'safe'],
  201. ]);
  202. if ($res === false) return $this->error(\Yii::$app->services->validate->getErrorMessage());
  203. $course = AddonsCourse::getDetail($get['course_id'], $this->mall_id, '*');
  204. if (empty($course)) {
  205. return $this->error("课程不存在或已下架");
  206. }
  207. $chapter = AddonsCourseChapter::getDetail($get['chapter_id']);
  208. if (empty($chapter)) {
  209. return $this->error("该课程无目录章节");
  210. }
  211. if ($chapter->type == CourseEnum::CHAPTER_TYPE_AUDIO) {
  212. $chapter->thumb_url = $course->logo;
  213. }
  214. //检查用户是否有查看章节的权限
  215. //检查用户是否有查看章节的权限
  216. $can_play_res = AddonsCourse::courseCanPlay($get['course_id'], $this->mall_id, $this->user);
  217. if (empty($chapter->can_see) && empty($can_play_res['is_can_play'])) {
  218. return $this->error("您没有权限查看该课程");
  219. }
  220. //增加章节的播放次数 +1
  221. if (!AddonsCourseChapter::updateViews($get['chapter_id'], $get['course_id'])) {
  222. return $this->error("保存课程播放次数失败");
  223. }
  224. //获取章节的所有商品
  225. $goods_list = AddonsCourseChapter::getAllGoods($chapter->goods_list, false);
  226. $data = [
  227. "video_url" => $chapter->video_url,
  228. "video_type" => $chapter->type,
  229. "video_length" => $chapter->video_length,
  230. "thumb_url" => $chapter->thumb_url,
  231. "goods_info" => $goods_list,
  232. "can_see_time" => $chapter->can_see_time,
  233. ];
  234. return $this->success('success', $data);
  235. }
  236. /**
  237. * @name:
  238. * @msg: 已购买课程列表
  239. * @param {*}
  240. * @return {*}
  241. */
  242. public function actionCourseBought()
  243. {
  244. $where[] = ['mall_id' => $this->mall_id, 'user_id' => $this->user_id, 'is_pay' => StatusEnum::ENABLED, 'status' => StatusEnum::ENABLED];
  245. $params = [
  246. 'where' => $where,
  247. 'with' => 'course',
  248. 'order' => 'created_at desc',
  249. ];
  250. $list = AddonsCourseBought::listPage($params, false, true);
  251. if (!empty($list['list'])) {
  252. $discount = (string)AddonsCourse::getCurrMemberDiscount($this->user, $this->mall_id);
  253. foreach ($list['list'] as $k => &$v) {
  254. $v['course']['member_price'] = bcmul($v['course']['member_price'], $discount, 2);
  255. }
  256. }
  257. return $this->success('success', $list);
  258. }
  259. /**
  260. * @name:
  261. * @msg: 删除已购买的课程
  262. * @param {int} $mall_id
  263. * @param {int} $user_id
  264. * @param {int} $id 数据记录 id
  265. * @return {*}
  266. */
  267. public function actionDelBought()
  268. {
  269. $get = \Yii::$app->request->get();
  270. $res = \Yii::$app->services->validate->validate($get, [
  271. [['id'], 'required'],
  272. ]);
  273. if ($res === false) return $this->error(\Yii::$app->services->validate->getErrorMessage());
  274. $model = AddonsCourseBought::getOne([['id' => $get['id'], 'mall_id' => $this->mall_id, 'user_id' => $this->user_id]]);
  275. if (!$model) {
  276. return $this->error('课程不存在');
  277. }
  278. $model->status = StatusEnum::DELETE;
  279. $res = $model->save();
  280. if ($res) {
  281. return $this->success('删除成功');
  282. } else {
  283. return $this->error('删除失败');
  284. }
  285. }
  286. /**
  287. * @name:
  288. * @msg:
  289. * @param {integer} $course_id 课程 id
  290. * @param {integer} $relay_type 转发方式,1:微信好友,2:微信朋友圈
  291. * @return {*}
  292. */
  293. public function actionCourseRelay()
  294. {
  295. $get = \Yii::$app->request->get();
  296. $res = \Yii::$app->services->validate->validate($get, [
  297. [['course_id', 'relay_type'], 'required'],
  298. ]);
  299. if ($res === false) return $this->error(\Yii::$app->services->validate->getErrorMessage());
  300. $params['mall_id'] = $this->mall_id;
  301. $params['user_id'] = $this->user_id;
  302. $params['course_id'] = $get['course_id'];
  303. $params['relay_type'] = $get['relay_type'];
  304. $data = AddonsCourseRelays::setData($params);
  305. //检查用户是否有查看章节的权限
  306. $can_play_res = AddonsCourse::courseCanPlay($get['course_id'], $this->mall_id, $this->user);
  307. if ($data) {
  308. return $this->success('转发成功', $can_play_res);
  309. }
  310. return $this->error('转发失败');
  311. }
  312. //增加视频课程观看时长
  313. public function actionAddWatchLog(){
  314. try {
  315. $post = \Yii::$app->request->post();
  316. $res = \Yii::$app->services->validate->validate($post, [
  317. [['course_id', 'duration'], 'required'],
  318. ]);
  319. if ($res === false) return $this->error(\Yii::$app->services->validate->getErrorMessage());
  320. $course = AddonsCourse::findOne(['mall_id'=>$this->mall_id,'id'=>$post['course_id'],'status'=>StatusEnum::ENABLED]);
  321. if(empty($course)) return $this->success();
  322. $post['mall_id'] = $this->mall_id;
  323. $post['user_id'] = $this->user_id;
  324. $res = Watch::add($post);
  325. if($res==false) return $this->error(Watch::getStaticError());
  326. if($post['duration']>20){
  327. //发公众号模板消息 ToDO
  328. \Yii::$app->services->rabbitMq->push(RabbitMqEnum::EXCHANGE_TASK, RabbitMqEnum::TASK_QUEUE, [
  329. 'user_id'=>$this->user_id,
  330. 'mall_id'=>$this->mall_id
  331. ], Video::class, 'sendWechatMessage');
  332. }
  333. return $this->success();
  334. }catch (\Exception $e){
  335. return $this->error($e->getMessage());
  336. }
  337. }
  338. //观看进度
  339. public function actionWatchLog(){
  340. try {
  341. $res = CoursePromoter::findOne(['user_id'=>$this->user_id,'mall_id'=>$this->mall_id,'status'=>StatusEnum::ENABLED]);
  342. if(empty($res)) return $this->error('您不是推广员');
  343. $params = \Yii::$app->request->get();
  344. $user_id = $params['user_id'];
  345. $user = User::findOne(['id'=>$user_id]);
  346. if(empty($user)||$user['parent_id']!=$this->user_id){
  347. return $this->error('无查看权限');
  348. }
  349. $params['where'][] = ['mall_id'=>$this->mall_id];
  350. $params['where'][] = ['user_id'=>$user_id];
  351. $params['where'][] = ['status'=> StatusEnum::ENABLED];
  352. $params['order'] = 'id desc';
  353. $params['select'] = 'id,mall_id,user_id,course_id,duration,nums';
  354. $list = CourseWatchLog::listPage($params, false);
  355. if(!empty($list['list'])){
  356. $course_ids = array_column($list['list'],'course_id');
  357. $course_chapter_list = AddonsCourseChapter::lists(['where'=>[['course_id'=>$course_ids]]]);
  358. $course_list = AddonsCourse::lists(['where'=>[['id'=>$course_ids]],'select'=>'id,name,logo','index'=>'id']);
  359. $course_arr = [];
  360. foreach ($course_chapter_list as $item){
  361. if(!empty($item['video_length'])){
  362. $video_length_arr = explode(':',$item['video_length']);
  363. if(count($video_length_arr)>1){
  364. $duration = $video_length_arr[0]*3600+$video_length_arr[1]*60+ bcadd($video_length_arr[2],0,0);
  365. }else{
  366. $duration =$video_length_arr[0];
  367. }
  368. if(isset($course_arr[$item['course_id']])){
  369. $course_arr[$item['course_id']]+=$duration;
  370. }else{
  371. $course_arr[$item['course_id']] = $duration;
  372. }
  373. }
  374. }
  375. foreach ($list['list'] as &$item){
  376. $total_duration = $course_arr[$item['course_id']]??0;
  377. $schedule = 0;
  378. if($total_duration>0){
  379. $schedule = bcmul($item['duration']/$total_duration,100,2);
  380. }
  381. $intention = '一般意向';
  382. if($schedule>70){
  383. $intention='高意向';
  384. }elseif ($schedule>30){
  385. $intention='中意向';
  386. }
  387. $item['intention'] = $intention;
  388. $item['schedule'] = $schedule.'%';
  389. $item['logo'] = '';
  390. $item['name'] = '';
  391. if(isset($course_list[$item['course_id']])){
  392. $item['logo'] = $course_list[$item['course_id']]['logo'];
  393. $item['name'] = $course_list[$item['course_id']]['name'];
  394. }
  395. $item['text'] =Watch::getText($item['duration']);
  396. unset($item['duration']);
  397. }
  398. }
  399. return $this->success('成功', $list);
  400. }catch (\Exception $e){
  401. return $this->error($e->getMessage());
  402. }
  403. }
  404. //转发记录
  405. public function actionForwardLog(){
  406. try {
  407. $res = CoursePromoter::findOne(['user_id'=>$this->user_id,'mall_id'=>$this->mall_id,'status'=>StatusEnum::ENABLED]);
  408. if(empty($res)) return $this->error('您不是推广员');
  409. $params = \Yii::$app->request->get();
  410. $user_id = $params['user_id'];
  411. $user = User::findOne(['id'=>$user_id]);
  412. if(empty($user)||$user['parent_id']!=$this->user_id){
  413. return $this->error('无查看权限');
  414. }
  415. $user_list = User::lists(['where'=>[['parent_id'=>$user_id]],'select'=>'id,nickname,avatar_url','index'=>'id']);
  416. $child_ids = array_column($user_list,'id');
  417. $params['where'][] = ['mall_id'=>$this->mall_id];
  418. $params['where'][] = ['user_id'=>$child_ids];
  419. $params['where'][] = ['status'=> StatusEnum::ENABLED];
  420. $params['order'] = 'id desc';
  421. $params['select'] = 'id,mall_id,user_id,course_id,duration,updated_at';
  422. $list = CourseWatchLog::listPage($params, false);
  423. if(!empty($list['list'])){
  424. $course_ids = array_column($list['list'],'course_id');
  425. $course_chapter_list = AddonsCourseChapter::lists(['where'=>[['course_id'=>$course_ids]]]);
  426. $course_list = AddonsCourse::lists(['where'=>[['id'=>$course_ids]],'select'=>'id,name,logo','index'=>'id']);
  427. $course_arr = [];
  428. foreach ($course_chapter_list as $item){
  429. if(!empty($item['video_length'])){
  430. $video_length_arr = explode(':',$item['video_length']);
  431. if(count($video_length_arr)>1){
  432. $duration = $video_length_arr[0]*3600+$video_length_arr[1]*60+ bcadd($video_length_arr[2],0,0);
  433. }else{
  434. $duration =$video_length_arr[0];
  435. }
  436. if(isset($course_arr[$item['course_id']])){
  437. $course_arr[$item['course_id']]+=$duration;
  438. }else{
  439. $course_arr[$item['course_id']] = $duration;
  440. }
  441. }
  442. }
  443. foreach ($list['list'] as &$item){
  444. $total_duration = $course_arr[$item['course_id']]??0;
  445. $schedule = 0;
  446. if($total_duration>0){
  447. $schedule = bcmul($item['duration']/$total_duration,100,2);
  448. }
  449. $intention = '一般意向';
  450. if($schedule>70){
  451. $intention='高意向';
  452. }elseif ($schedule>30){
  453. $intention='中意向';
  454. }
  455. $item['intention'] = $intention;
  456. $item['schedule'] = $schedule.'%';
  457. $item['name'] = '';
  458. $item['nickname'] = '';
  459. $item['avatar_url'] = '';
  460. if(isset($course_list[$item['course_id']])){
  461. $item['name'] = $course_list[$item['course_id']]['name'];
  462. }
  463. if(isset($user_list[$item['user_id']])){
  464. $item['nickname'] = $user_list[$item['user_id']]['nickname'];
  465. $item['avatar_url'] = $user_list[$item['user_id']]['avatar_url'];
  466. }
  467. unset($item['duration']);
  468. }
  469. }
  470. return $this->success('成功', $list);
  471. }catch (\Exception $e){
  472. return $this->error($e->getMessage());
  473. }
  474. }
  475. }