255], [['video_url'], 'string', 'max' => 1000], [['goods_list'], 'string', 'max' => 1000], ['video_length', 'string', 'max' => 50], ]; } /** * {@inheritdoc} */ public function attributeLabels() { return [ 'id' => 'ID', 'mall_id' => '商城id', 'course_id' => '课程id', 'dir_id' => '课程目录id,如果此字段值为0,说明没有二级目录', 'name' => '课程章节名称', 'video_url' => '课程章节视频url地址', 'type' => '视频类型 默认是1(视频) 1:视频 2:音频', 'goods_list' => '商品列表,用逗号连接', 'can_see' => '是否允许试看 默认允许 1:允许 0:不允许', 'views' => '课程章节实际播放次数', 'sort' => '序号 按asc升序排列', 'thumb_url' => '视频首图', 'status' => '状态 0禁用,1启用,-1是删除', 'created_at' => 'Created At', 'updated_at' => 'Updated At', 'video_length' => '视频时长', 'can_see_time' => '试看时长,单位:秒', ]; } public static function setData(int $mall_id, array $data) { if (!empty($data['goods_list']) && is_array($data['goods_list'])) { $data['goods_list'] = implode(',', $data['goods_list']); } if (!empty($data['id'])) { $model = self::findOne(['mall_id' => $mall_id, 'status' => StatusEnum::ENABLED, 'id' => $data['id']]); if (empty($model)) { self::$static_error = '数据异常'; return false; } } else { $model = new self(); $model->loadDefaultValues(); $model->mall_id = $mall_id; $model->status = StatusEnum::ENABLED; } foreach ($data as $key => $val) { $model->$key = $val; } $is_obtain_duration = false; $upload_file_info = Attachment::find() ->select('id,url,duration_str,thumb_url') ->where(['mall_id' => $mall_id, 'upload_type' => Attachment::UPLOAD_TYPE_VIDEOS, 'url' => $model->video_url]) ->limit(1) ->one(); if (empty($upload_file_info)) { if (!empty($data['type']) && CourseEnum::CHAPTER_TYPE_VIDEO == $data['type']) { $is_obtain_duration = true; } } else { // $model->video_length = $upload_file_info->duration_str; $model->thumb_url = $upload_file_info->thumb_url; } if ($is_obtain_duration) { // 需要异步获取视频时长 $params = [ 'id' => $model->id, 'mall_id' => $mall_id, ]; \Yii::$app->services->rabbitMq->push(RabbitMqEnum::EXCHANGE_ORDER, RabbitMqEnum::ORDER_QUEUE, $params, self::class, 'obtainVideoDuration'); } if (!$model->save()) { self::$static_error = '保存数据失败:' . $model->getErrorMessage(); return false; } return $model; } /** * 批量获取商品详情 * @param $goods_id_list string 用逗号连接的商品ID * @param bool $first boolean 获取第一个商品 true 是 false 否 * @return array */ public static function getAllGoods($goods_id_list, $first = true) { if (is_string($goods_id_list)) { $goods_ids = explode(',', $goods_id_list); } else { $goods_ids = $goods_id_list; } $list = Goods::find() ->select( 'id,stock as good_stock,goods_name as name,detail,cover_pic,price,sales_num as sales' ) ->where(['status' => StatusEnum::ENABLED, 'id' => $goods_ids]) ->asArray() ->all(); if ($first) { $list = $list[0]; } return $list; } /** * 拆分商品ID列表数据 * @param $goods_id_list string * @return array */ public static function explodeGoodsIdList($goods_id_list) { $result = []; $goods_id_list = trim($goods_id_list, ","); if (!empty($goods_id_list)) { $result = explode(",", $goods_id_list); } return $result; } /** * 获取单个商品详情 * @param $goods_id int 商品ID * @return array */ public static function getOneGoods($goods_id) { $result = []; $goods_info = Goods::findOne($goods_id); if (!empty($goods_info)) { $result = [ 'id' => $goods_info->id, 'goods_stock' => $goods_info->stock, 'name' => $goods_info->goods_name, 'detail' => $goods_info->detail, 'cover_pic' => $goods_info->cover_pic, 'price' => $goods_info->price, 'sales' => $goods_info->virtual_sales, ]; } return $result; } /** * @name: * @msg: 把秒转换为 时分秒 的表示形式 * @param {int} $seconds * @return {string} */ public static function convertTime(int $seconds): string { if (empty($seconds)) { return $seconds; } // 一个小时的秒数 $hour_seconds = 60 * 60; $minute_seconds = 60; // 小时数 $hours = floor($seconds / $hour_seconds); // 除去整小时后剩余秒数 $hour_surplus_seconds = $seconds % $hour_seconds; // 分钟数 $minutes = floor($hour_surplus_seconds / $minute_seconds); // 除去整分钟后剩余的秒数 $minute_surplus_seconds = $hour_surplus_seconds % $minute_seconds; $res = ''; if (!empty($hours)) { $hours = mb_strlen($hours) > 1 ? $hours : '0' . $hours; $res .= $hours . ':'; } else { $res .= '00:'; } if (!empty($minutes)) { $minutes = mb_strlen($minutes) > 1 ? $minutes : '0' . $minutes; $res .= $minutes . ':'; } else { $res .= '00:'; } if (!empty($minute_surplus_seconds)) { $minute_surplus_seconds = mb_strlen($minute_surplus_seconds) > 1 ? $minute_surplus_seconds : '0' . $minute_surplus_seconds; $res .= $minute_surplus_seconds; } else { $res .= '00'; } return $res; } /** * 获取章节详情 * @Author: guyu * @DateTime:2021-03-23 15:23 * @param $id int 章节ID * @return mixed */ public static function getDetail($id) { $result = self::findOne(['id' => $id, 'status' => StatusEnum::ENABLED]); if (!empty($result)) { $result->goods_list = self::explodeGoodsIdList($result->goods_list); } return $result; } /** * 更新章节的播放次数 课程的播放次数 * @param $id int 章节ID * @param $course_id int 课程ID * @return boolean | \Exception */ public static function updateViews($id, $course_id) { $trans = \Yii::$app->db->beginTransaction(); try { $chapter = self::getDetail($id); $course = AddonsCourse::getDetail($course_id, $chapter->mall_id); //设置章节视频播放次数 + 1 //$chapter->views = $chapter->views + 1; if (!$chapter->updateCounters(['views' => 1])) { throw new \Exception("保存课程章节视频播放次数失败"); } //设置课程视频播放次数 + 1 //$course->views = $course->views + 1; if (!$course->updateCounters(['views' => 1])) { throw new \Exception("保存课程播放次数失败"); } $trans->commit(); return true; } catch (\Exception $ex) { $trans->rollBack(); throw new \Exception($ex->getMessage()); } return false; } /** * @name: * @msg: 获取课程章节视频时长,异步队列调用 * @param {array} $params id:章节 id;mall_id:商城 id * @return {*} */ public static function obtainVideoDuration(array $params = null) { try { if (empty($params) || empty($params['id']) || empty($params['mall_id'])) { throw new \Exception('参数错误。params => ' . var_export($params, true)); } $model = self::find() ->where(['mall_id' => $params['mall_id'], 'id' => $params['id']]) ->one(); if (empty($model) || empty($model->video_url)) { throw new \Exception('章节或视频不存在'); } $path = '/tmp/course-chapter-video/'; if (!file_exists($path)) { mkdir($path, 0755); } $fileinfo = pathinfo($model->video_url); // dd($fileinfo); $tmp_video_file = $path . $fileinfo['basename']; $tmp_thumb_file = $path . $fileinfo['filename'] . '.jpg'; $video_str = file_get_contents($model->video_url); file_put_contents($tmp_video_file, $video_str); $video_info = FfmpegHelper::getVideoInfo($tmp_video_file); // dd($video_info); FfmpegHelper::imageResize($tmp_video_file, $tmp_thumb_file, '00:00:01'); $datas = [ 'id' => $params['id'], 'video_length' => $video_info['duration'], ]; if (file_exists($tmp_thumb_file)) { $query = AccessToken::find()->where(['status' => StatusEnum::ENABLED])->orderBy('created_at desc'); $token = ''; foreach ($query->each(5) as $token_info) { $timestamp = (int)substr($token_info->access_token, strrpos($token_info->access_token, '_') + 1); $expire = 1296000; // 验证有效期 if ($timestamp + $expire <= time()) { continue; } $token = $token_info->access_token; break; } $mall = Mall::findOne($params['mall_id']); $headers = [ 'x-access-token' => $token, 'x-app-platform' => 'h5', 'x-mall-sign' => $mall->mall_sign ?? '', 'x-parent-id' => -1, 'x-source' => 0, ]; $upload_url = \Yii::getAlias('@apiUrl') . '/index.php/v1/file/upload'; $upload_res = CurlHelper::upload($upload_url, $tmp_thumb_file, $headers); if ($upload_res['code'] != 0) { throw new \Exception('首帧图片上传出错--' . $upload_res['msg']); } // dd($thumb_url); $datas['thumb_url'] = $upload_res['data']['url']; } $res = self::setData($params['mall_id'], $datas); if (false === $res) { throw new \Exception(self::getStaticError()); } // 删除临时文件 unlink($tmp_thumb_file); unlink($tmp_video_file); return true; } catch (\Exception $e) { \Yii::error('异步获取章节视频时长出错:' . $e->getMessage() . ', file: ' . $e->getFile() . ', line: ' . $e->getLine()); \Yii::getLogger()->flush(true); } } }