belongsTo('\app\common\model\course\Course', 'course_id', 'id', [], 'RIGHT')->setEagerlyType(0); } public function user() { return $this->belongsTo('\app\admin\model\User', 'user_id', 'id', [], 'RIGHT')->setEagerlyType(0); } /** * 获取统计信息 * @param $userId * @param $time * @return int[] */ public function getTotal($userId,$time=null){ $where = []; $fields = ["sum(total_time) as time","COUNT(DISTINCT `course_id`) as course"]; if($userId != 0){ $where['user_id'] = $userId; }else{ $fields[] = "COUNT(DISTINCT `user_id`) as user"; } if($time){ $where['end_time'] = ['between',[$time['start'],$time['end']]]; } $data = self::where($where) ->field($fields) ->find(); if(!$data){ $empty = [ 'time'=>0, 'course'=>0 ]; if($userId == 0){ $empty['user'] = 0; } return $empty; } $data['time'] = floor($data['time']/ 60 / 60 * 10 )/10; return $data; } /** * 获取课程列表 * @param $userId 分组ID * @param $params 分页信息 page页码 limit获取数量 * @return array */ public function getStudyLog($userId = 0,$params = []){ $rows = self::with(['course']); $where = [ 'user_id'=>$userId, 'course.status'=>1 ]; $buildSql = self::where($where)->order('end_time','desc')->limit($params['limit'])->page($params['page'])->buildSql(); $list = $rows->table($buildSql . ' as t') ->group('t.course_id') ->order('end_time','desc') ->field('sum(t.total_time) as total_time,count(*) as count,course__name,course__type,course__cover,course__id,end_time') ->select(); $data = []; if(!empty($list)){ foreach ($list as $item){ $item['course'] = [ 'id'=>$item['course__id'], 'name'=>$item['course__name'], 'type'=>$item['course__type'], 'cover'=>$item['course__cover'], ]; $data[] = $item; } } return $data; } /** * 设置学习记录 * @param $userId 用户ID * @param $courseId 课程ID * @param $mediaProgress 媒体播放进度 * @param $finish 是否播放完成 * @return void */ public function setLog($userId, $courseId, $columnId, $mediaProgress = 0, $finish = 0) { $cacheKey = \app\common\constant\cache\Keys::STUDY_LOG . ':' . $userId . ':' . $courseId; $log = Cache::get($cacheKey); $now = time(); if (!$log) { // 新建日志 $log = [ 'user_id' => $userId, 'course_id' => $courseId, 'column_id' => $columnId, 'media_progress' => $mediaProgress, 'start_time' => $now - $this->timeStep, 'end_time' => $now, 'finish' => $finish, 'uniacid' => UNIACID, 'total_time' => $this->timeStep ]; } else { // 合并已有日志,避免覆盖 $log['end_time'] = $now; $log['media_progress'] = $mediaProgress; $log['total_time'] = ($log['total_time'] ?? 0) + ($now - ($log['end_time'] ?? $now)); if(isset($log['finish']) && $log['finish'] == 1){ $log['finish'] = $finish; // 如果已完成,可以覆盖 } // 保留 start_time 最早值 if(!isset($log['start_time'])){ $log['start_time'] = $now - $this->timeStep; } } // 写入缓存 Cache::set($cacheKey, $log, \app\common\constant\cache\Timeout::STUDY_LOG_CACHE_SYNC); // 维护缓存列表 $this->updateCacheKeyList($cacheKey); return true; } /** * 批量同步学习缓存数据到数据库 * @return void */ public function batchSyncStudyCache() { // 获取缓存键列表 $cacheKeyList = Cache::get(\app\common\constant\cache\Keys::STUDY_LOG_CACHE_KEYS, []); if (!$cacheKeyList) { return; // 如果没有缓存键,直接返回 } // 遍历缓存键列表,将数据同步到数据库 foreach ($cacheKeyList as $cacheKey) { $this->syncCacheToDatabase($cacheKey); } // 清理缓存键列表 Cache::rm(\app\common\constant\cache\Keys::STUDY_LOG_CACHE_KEYS); } /** * 同步单个缓存数据到数据库 * @param $userId * @param $courseId * @return void */ public function singleSyncCache($userId, $courseId){ // 生成缓存键 $cacheKey = \app\common\constant\cache\Keys::STUDY_LOG.':' . $userId . ':' . $courseId; $log = Cache::get($cacheKey); if($log){ $this->syncCacheToDatabase($cacheKey); } } /** * 同步缓存到数据库 * @param $cacheKey * @return void */ public function syncCacheToDatabase($cacheKey){ $log = Cache::get($cacheKey); // 获取缓存数据 if ($log) { // 判断缓存中的数据是否需要同步到数据库 $logExists = self::where([ 'user_id' => $log['user_id'], 'course_id' => $log['course_id'], 'column_id' => $log['column_id'], ])->order('id','desc')->find(); if ($logExists) { // 如果记录存在,更新数据库 $data = [ 'end_time' => $log['end_time'], 'media_progress'=>$log['media_progress'], 'total_time' => $log['total_time'] + $logExists['total_time'] ]; if(isset($log['finish']) && $log['finish'] == 1){ $data['finish'] = $log['finish']; } self::where(['id' => $logExists['id']])->update($data); } else { // 插入新的记录 self::insert($log); } // 同步完后,清除缓存 Cache::rm($cacheKey); } } /** * 维护缓存键列表 * 用于跟踪所有缓存的日志键,供后台任务使用 */ private function updateCacheKeyList($cacheKey) { // 获取现有的缓存键列表 $cacheKeyList = Cache::get(\app\common\constant\cache\Keys::STUDY_LOG_CACHE_KEYS, []); // 如果缓存键列表为空,则初始化为空数组 if (!$cacheKeyList) { $cacheKeyList = []; } // 如果缓存键不在列表中,则添加 if (!in_array($cacheKey, $cacheKeyList)) { $cacheKeyList[] = $cacheKey; } // 更新缓存键列表 Cache::set(\app\common\constant\cache\Keys::STUDY_LOG_CACHE_KEYS, $cacheKeyList, (\app\common\constant\cache\Timeout::STUDY_LOG_CACHE_SYNC + 60)); } /** * 获取课程学习数量 * @param $courseId * @return void */ public function getCourseStudyNum($courseId){ return self::where([ 'course_id'=>$courseId ])->count(); } /** * 获取连续学习天数 * @param $userId * @return int */ public function getContinuousStudyDays($userId) { // 获取当前时间 $today = strtotime('today'); // 获取用户的学习记录,并按日期分组 $records = self::where('user_id', $userId) ->field("FROM_UNIXTIME(start_time, '%Y-%m-%d') as date") ->group('date') ->order('date', 'desc') ->select(); if (empty($records)) { return 0; } $records = collection($records)->toArray(); // 计算连续学习天数 $continuousDays = 0; $previousDate = $today; foreach ($records as $record) { $recordDate = strtotime($record['date']); // 检查是否是连续日期 if ($recordDate == $previousDate || $recordDate == strtotime('-1 day', $previousDate)) { $continuousDays++; $previousDate = $recordDate; } else { break; } } // 检查今天是否有学习记录 $todayRecord = self::where('user_id', $userId) ->where('start_time', '>=', strtotime('today')) ->find(); if ($todayRecord) { $continuousDays++; } return $continuousDays; } }