PaperService.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | LARAVEL8.0 框架 [ LARAVEL ][ RXThinkCMF ]
  4. // +----------------------------------------------------------------------
  5. // | 版权所有 2017~2021 LARAVEL研发中心
  6. // +----------------------------------------------------------------------
  7. // | 官方网站: http://www.laravel.cn
  8. // +----------------------------------------------------------------------
  9. // | Author: laravel开发员 <laravel.qq.com>
  10. // +----------------------------------------------------------------------
  11. namespace App\Services\Api;
  12. use App\Models\ExamAnswerModel;
  13. use App\Models\ExamAnswerTopicModel;
  14. use App\Models\ExamPaperModel;
  15. use App\Models\ExamTopicModel;
  16. use App\Services\BaseService;
  17. use App\Services\ConfigService;
  18. use App\Services\RedisService;
  19. /**
  20. * 试卷服务-服务类
  21. * @author laravel开发员
  22. * @since 2020/11/11
  23. * @package App\Services\Api
  24. */
  25. class PaperService extends BaseService
  26. {
  27. // 静态对象
  28. protected static $instance = null;
  29. /**
  30. * 构造函数
  31. * @author laravel开发员
  32. * @since 2020/11/11
  33. */
  34. public function __construct()
  35. {
  36. $this->model = new ExamPaperModel();
  37. }
  38. /**
  39. * 静态入口
  40. */
  41. public static function make()
  42. {
  43. if (!self::$instance) {
  44. self::$instance = new static();
  45. }
  46. return self::$instance;
  47. }
  48. /**
  49. * @param $params
  50. * @param int $pageSize
  51. * @return array
  52. */
  53. public function getDataList($params, $pageSize = 15)
  54. {
  55. $page = isset($params['page'])? $params['page'] : 1;
  56. $cacheKey = "caches:paper:list_{$page}_{$pageSize}:".md5(json_encode($params));
  57. $datas = RedisService::get($cacheKey);
  58. if($datas){
  59. return $datas;
  60. }
  61. $query = $this->getQuery($params);
  62. $list = $query->select(['a.id','a.user_id','a.paper_id','a.score','a.accurate_count','b.name','b.type','b.topic_count','b.score_total','b.is_charge','a.create_time','a.answer_times','a.status'])
  63. ->orderBy('a.create_time','desc')
  64. ->paginate($pageSize > 0 ? $pageSize : 9999999);
  65. $list = $list? $list->toArray() :[];
  66. if($list){
  67. foreach($list['data'] as &$item){
  68. $item['create_time'] = $item['create_time']? datetime($item['create_time'],'Y-m-d H.i.s') : '';
  69. }
  70. }
  71. $rows = isset($list['data'])? $list['data'] : [];
  72. $datas = [
  73. 'pageSize'=> $pageSize,
  74. 'total'=> isset($list['total'])? $list['total'] : 0,
  75. 'list'=> $rows
  76. ];
  77. if($rows){
  78. RedisService::set($cacheKey, $datas, rand(300, 600));
  79. }
  80. return $datas;
  81. }
  82. /**
  83. * 查询
  84. * @param $params
  85. * @return mixed
  86. */
  87. public function getQuery($params)
  88. {
  89. $where = ['b.status'=>1,'b.mark'=>1,'a.mark' => 1];
  90. $status = isset($params['status'])? $params['status'] : 0;
  91. $type = isset($params['type'])? $params['type'] : 0;
  92. $sceneType = isset($params['scene_type'])? $params['scene_type'] : 0;
  93. $subjectId = isset($params['subject_id'])? $params['subject_id'] : 0;
  94. if($status>0){
  95. $where['a.status'] = $status;
  96. }
  97. if($type>0){
  98. $where['b.type'] = $type;
  99. }
  100. if($sceneType>0){
  101. $where['b.scene_type'] = $sceneType;
  102. }
  103. if($subjectId>0){
  104. $where['b.subject_id'] = $subjectId;
  105. }
  106. return $this->model->from('exam_answers as a')
  107. ->leftJoin('exam_papers as b','b.id','=','a.paper_id')
  108. ->where($where)
  109. ->where(function ($query) use($params){
  110. $keyword = isset($params['keyword'])? $params['keyword'] : '';
  111. if($keyword){
  112. $query->where('b.name','like',"%{$keyword}%");
  113. }
  114. });
  115. }
  116. /**
  117. * 最近是否答过该题
  118. * @param $userId 用户ID
  119. * @param $paperId 试卷ID
  120. * @param int $submit 是否已交卷,1-是,0-否
  121. * @return array|mixed
  122. */
  123. public function getLastAnswer($userId, $paperId, $submit=0)
  124. {
  125. $cacheKey = "caches:paper:answer_last_{$userId}:{$paperId}_{$submit}";
  126. $data = RedisService::get($cacheKey);
  127. if($data){
  128. return $data;
  129. }
  130. $lastTime = ConfigService::make()->getConfigByCode('submit_paper_time', 30);
  131. $lastTime = $lastTime>=1 && $lastTime <= 150? $lastTime : 30;
  132. $data = ExamAnswerModel::where(['user_id'=>$userId,'is_submit'=> $submit,'paper_id'=> $paperId,'status'=>1,'mark'=>1])
  133. ->where(function($query) use($lastTime, $submit){
  134. if($submit<=0){
  135. // 未交卷
  136. $query->where('is_submit', 0)->orWhere('answer_last_at','>=', time() - $lastTime * 60);
  137. }else{
  138. $query->where('is_submit', $submit)->orWhere('answer_last_at','<', time() - $lastTime * 60);
  139. }
  140. })
  141. ->select(['id','paper_id','score','answer_last_at','answer_last_id'])
  142. ->first();
  143. $data = $data? $data->toArray() : [];
  144. if($data){
  145. RedisService::set($cacheKey, $data, rand(5, 10));
  146. }
  147. return $data;
  148. }
  149. /**
  150. * 获取详情
  151. * @param $id
  152. * @return array|mixed
  153. */
  154. public function getInfo($userId, $paperId, $params=[])
  155. {
  156. $lid = isset($params['lid'])? intval($params['lid']) : 0;
  157. $tid = isset($params['tid'])? intval($params['tid']) : 0;
  158. $rid = isset($params['rid'])? intval($params['rid']) : 0;
  159. $type = isset($params['type'])? intval($params['type']) : 1;
  160. $cacheKey = "caches:paper:info_{$userId}:p{$paperId}_t{$tid}_r{$rid}_l{$lid}";
  161. $info = RedisService::get($cacheKey);
  162. if($info){
  163. return $info;
  164. }
  165. // 若进行答题
  166. if($rid<=0 && $type != 1) {
  167. // 判断N分钟内是否有未交卷的答题
  168. $lastAnswerInfo = $this->getLastAnswer($userId, $paperId, 0);
  169. $rid = isset($lastAnswerInfo['id']) ? $lastAnswerInfo['id'] : 0;
  170. }
  171. $where = ['a.id'=> $paperId,'a.status'=>1,'a.mark'=>1];
  172. $info = $this->model->from('exam_papers as a')
  173. ->leftJoin('exam_answers as b',function($join) use($rid){
  174. $join->on('b.paper_id','=','a.id')->where(['b.id'=>$rid]);
  175. })
  176. ->where($where)
  177. ->select(['a.id as paper_id','b.id as rid','b.score','b.accurate_count','b.answer_count','b.answer_last_id','b.answer_times','a.name','a.type','a.scene_type','a.subject_id','a.score_total','a.topic_count','a.total_time','a.is_charge','a.create_time','a.status'])
  178. ->first();
  179. $info = $info? $info->toArray() : [];
  180. if($info){
  181. $info['create_time'] = $info['create_time']? datetime($info['create_time'],'Y-m-d') : '';
  182. $lastId = isset($info['answer_last_id'])? $info['answer_last_id'] : 0;
  183. $info['answer_count'] = isset($info['answer_count'])? intval($info['answer_count']) : 0;
  184. $info['accurate_count'] = isset($info['accurate_count'])? intval($info['accurate_count']) : 0;
  185. $info['score'] = isset($info['score'])? intval($info['score']) : 0;
  186. $info['rid'] = isset($info['rid'])? intval($info['rid']) : 0;
  187. // 剩余时间
  188. $totalTime = isset($info['total_time'])? intval($info['total_time']) : 0;
  189. $totalTime = $totalTime>0? $totalTime : ConfigService::make()->getConfigByCode('answer_total_time', 1800);
  190. $info['answer_times'] = isset($info['answer_times'])? intval($info['answer_times']) : 0;
  191. $info['remain_time'] = $totalTime>$info['answer_times']? $totalTime-$info['answer_times'] : 0;
  192. $info['remain_time_text'] = $info['remain_time']? format_times($info['remain_time']) : '00:00';
  193. // 当前题目
  194. //$prefix = env('DB_PREFIX','_lev');
  195. $model = ExamTopicModel::from('exam_topics as a')
  196. ->leftJoin('exam_answers_topics as b', function($join) use($rid){
  197. // 是否有最近答题记录
  198. $join->on('b.topic_id','=',"a.id")->where("b.answer_log_id",'=', $rid)->where("b.mark",'=', 1);
  199. })
  200. ->where(['a.paper_id'=> $paperId,'a.status'=>1,'a.mark'=>1])
  201. ->where(function($query) use($tid, $lastId){
  202. // 答题卡选择的题目,否则默认按题目排序返回第一题
  203. if($tid>0){
  204. $query->where('a.id', $tid);
  205. }else if($lastId){
  206. $query->where('a.id', '>', $lastId);
  207. }
  208. });
  209. $model1 = clone $model;
  210. if($model->value('a.id')<=0 && $lastId){
  211. $model = $model1->where('a.id', $lastId);
  212. }
  213. $info['topic'] = $model->select(['a.*','b.id as answer_topic_id','b.answer_log_id','b.answer as submit_answer','b.answer_type as submit_answer_type','b.score as submit_score','b.accurate'])
  214. ->orderBy('a.sort','desc')
  215. ->orderBy('a.id','asc')
  216. ->first();
  217. $info['topic'] = $info['topic']? $info['topic']->toArray() : [];
  218. $topicId = isset($info['topic']['id'])? $info['topic']['id'] : 0;
  219. if($info['topic'] && $topicId) {
  220. $info['topic']['accurate'] = isset($info['topic']['accurate'])?$info['topic']['accurate'] : -1;
  221. $info['topic']['submit_answer'] = isset($info['topic']['submit_answer'])?$info['topic']['submit_answer'] : '';
  222. $info['topic']['submit_answer_type'] = isset($info['topic']['submit_answer_type'])?$info['topic']['submit_answer_type'] : 1;
  223. $info['topic']['submit_answers'] = '';
  224. $info['topic']['collect_answers'] = [];
  225. if($info['topic']['submit_answer_type'] == 3){
  226. $info['topic']['submit_answer'] = get_image_url($info['topic']['submit_answer']);
  227. }else if($info['topic']['submit_answer_type'] == 4){
  228. $info['topic']['submit_answer'] = $info['topic']['submit_answer']?json_decode($info['topic']['submit_answer'], true):[];
  229. }
  230. if($info['topic']['show_type'] == 1){
  231. $info['topic']['topic_name'] = format_content($info['topic']['topic_name']);
  232. }else if($info['topic']['show_type'] == 2){
  233. $info['topic']['topic_name'] = get_image_url($info['topic']['topic_name']);
  234. // 已经答题,返回答案
  235. if($rid>0 || $info['topic']['submit_answer']){
  236. $info['topic']['topic_analysis'] = get_image_url($info['topic']['topic_analysis']);
  237. if(preg_match("/(images|temp)/", $info['topic']['correct_answer'])){
  238. $info['topic']['correct_answer'] = get_image_url($info['topic']['correct_answer']);
  239. }
  240. if(preg_match("/(images|temp)/", $info['topic']['answer_A'])){
  241. $info['topic']['answer_A'] = get_image_url($info['topic']['answer_A']);
  242. }
  243. if(preg_match("/(images|temp)/", $info['topic']['answer_B'])){
  244. $info['topic']['answer_B'] = get_image_url($info['topic']['answer_B']);
  245. }
  246. if(preg_match("/(images|temp)/", $info['topic']['answer_C'])){
  247. $info['topic']['answer_C'] = get_image_url($info['topic']['answer_C']);
  248. }
  249. if(preg_match("/(images|temp)/", $info['topic']['answer_D'])){
  250. $info['topic']['answer_D'] = get_image_url($info['topic']['answer_D']);
  251. }
  252. if(preg_match("/(images|temp)/", $info['topic']['answer_E'])){
  253. $info['topic']['answer_E'] = get_image_url($info['topic']['answer_E']);
  254. }
  255. if(preg_match("/(images|temp)/", $info['topic']['answer_F'])){
  256. $info['topic']['answer_F'] = get_image_url($info['topic']['answer_F']);
  257. }
  258. }
  259. }
  260. // 未答题隐藏答案
  261. if(empty($info['topic']['submit_answer'])){
  262. $info['topic']['correct_answer'] = '未答题';
  263. $info['topic']['topic_analysis'] = '';
  264. }
  265. // 多选题
  266. $topicType = isset($info['topic']['topic_type'])? $info['topic']['topic_type'] : '';
  267. if($topicType == '多选题'){
  268. $info['topic']['submit_answers'] = $info['topic']['submit_answer']?explode(',', $info['topic']['submit_answer']) : '';
  269. $info['topic']['correct_answers'] = $info['topic']['correct_answer']?explode(',', $info['topic']['correct_answer']) : [];
  270. }
  271. $info['topic']['answers'] = [];
  272. if($info['topic']['answer_A']){
  273. $info['topic']['answers'][] = ['code'=>'A','value'=> $info['topic']['answer_A']];
  274. }
  275. if($info['topic']['answer_B']){
  276. $info['topic']['answers'][] = ['code'=>'B','value'=> $info['topic']['answer_B']];
  277. }
  278. if($info['topic']['answer_C']){
  279. $info['topic']['answers'][] = ['code'=>'C','value'=> $info['topic']['answer_C']];
  280. }
  281. if($info['topic']['answer_D']){
  282. $info['topic']['answers'][] = ['code'=>'D','value'=> $info['topic']['answer_D']];
  283. }
  284. if($info['topic']['answer_E']){
  285. $info['topic']['answers'][] = ['code'=>'E','value'=> $info['topic']['answer_E']];
  286. }
  287. if($info['topic']['answer_F']){
  288. $info['topic']['answers'][] = ['code'=>'F','value'=> $info['topic']['answer_F']];
  289. }
  290. // 上一题
  291. $info['last'] = ExamTopicModel::where(['paper_id'=> $paperId,'status'=>1,'mark'=>1])
  292. ->where('id','<', $topicId)
  293. ->select(['id','topic_name'])
  294. ->orderBy('sort','asc')
  295. ->orderBy('id','desc')
  296. ->first();
  297. $info['last'] = $info['last']? $info['last']->toArray() :['id'=>0];
  298. // 下一题
  299. $info['next'] = ExamTopicModel::where(['paper_id'=> $paperId,'status'=>1,'mark'=>1])
  300. ->where('id','>', $topicId)
  301. ->select(['id','topic_name'])
  302. ->orderBy('sort','desc')
  303. ->orderBy('id','asc')
  304. ->first();
  305. $info['next'] = $info['next']? $info['next']->toArray() :['id'=>0];
  306. }
  307. RedisService::set($cacheKey, $info, rand(10, 20));
  308. }
  309. return $info;
  310. }
  311. /**
  312. * 获取随机试卷
  313. * @param $userId 用户
  314. * @param $type 试卷类型
  315. * @param $sceneType 场景
  316. * @param false $refresh
  317. * @return array|mixed
  318. */
  319. public function getRandomPaper($userId, $type, $sceneType, $refresh=false)
  320. {
  321. $cacheKey = "caches:papers:random_{$userId}:{$type}_{$sceneType}";
  322. $data = RedisService::get($cacheKey);
  323. if($data && !$refresh){
  324. return $data;
  325. }
  326. $data = $this->model->where(['type'=>$type,'scene_type'=>$sceneType,'status'=>1,'mark'=>1])
  327. ->orderByRaw('RAND()')
  328. ->first();
  329. $data = $data? $data->toArray() : [];
  330. if($data){
  331. $data['score'] = 0;
  332. $data['paper_id'] = $data['id'];
  333. $data['accurate_count'] = 0;
  334. $data['date'] = date('Y-m-d');
  335. $data['answer_count'] = 0;
  336. $data['answer_times'] = 0;
  337. RedisService::set($cacheKey, $data, rand(10, 20));
  338. }
  339. return $data;
  340. }
  341. }