TrainService.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. <?php
  2. namespace app\admin\service\train;
  3. use app\admin\model\train\QuestionModel;
  4. use app\admin\model\train\TrainAdmin;
  5. use app\admin\model\train\TrainExam;
  6. use app\admin\model\train\TrainExamReQuestion;
  7. use app\admin\model\train\TrainExamResult;
  8. use app\admin\model\train\TrainExamResultRecord;
  9. use think\Db;
  10. use think\exception\DbException;
  11. use think\Request;
  12. class TrainService
  13. {
  14. protected $exam = null;
  15. protected $examResult = null;
  16. protected $question = null;
  17. protected $result_arr = [
  18. 1 => 'A',
  19. 2 => 'B',
  20. 3 => 'C',
  21. 4 => 'D',
  22. 5 => 'E',
  23. 6 => 'F',
  24. ];
  25. protected $judge = [
  26. 1 => '对',
  27. 2 => '错',
  28. ];
  29. protected $type_arr = [
  30. 1 => '单选',
  31. 2 => '多选',
  32. 3 => '判断'
  33. ];
  34. protected static $instance;
  35. protected static $error_message = null;
  36. public function __construct()
  37. {
  38. $this->exam = model(TrainExam::class);
  39. $this->examResult = model(TrainExamResult::class);
  40. $this->question = model(QuestionModel::class);
  41. }
  42. /**
  43. * 初始化
  44. * @param array $options
  45. * @return object|static
  46. * @author matielong
  47. */
  48. public static function instance($options = [])
  49. {
  50. if (is_null(self::$instance)) {
  51. self::$instance = new static($options);
  52. }
  53. return self::$instance;
  54. }
  55. public function getError()
  56. {
  57. return self::$error_message;
  58. }
  59. protected function setError($msg)
  60. {
  61. self::$error_message = $msg;
  62. return false;
  63. }
  64. /**
  65. * 获取未完成的考试
  66. * @param $phone
  67. * @return bool|\think\Collection
  68. */
  69. public function getUndoneExam($phone)
  70. {
  71. try {
  72. $admin = $this->getAdminByPhone($phone);
  73. if(!$admin){
  74. return false;
  75. }
  76. $admin_id = $admin['id'];
  77. $data = $this->examResult->alias('result')
  78. ->join($this->exam->getTable().' exam', 'exam.id = exam_id')
  79. ->where('admin_id', $admin_id)
  80. ->where(function ($query){
  81. $query->where('result.status', 0)
  82. ->whereOr('result.status', 1);
  83. })
  84. ->field('result.id as result_id, exam_id, title, duration, exam.start_time, end_time, remark, result.status as result_status, result.start_time as result_start_time')
  85. ->select();
  86. foreach ($data as &$val){
  87. $val['residue_time'] = '';
  88. if($val['result_status'] === 1){
  89. $val['residue_time'] = date('H:i:s', ($val['duration'] * 60) - time());
  90. }
  91. }
  92. unset($val);
  93. return $data;
  94. } catch (DbException $exception){
  95. return false;
  96. }
  97. }
  98. /**
  99. * 获取及格/不集合的考试
  100. * @param $phone
  101. * @param false $unqualified
  102. * @return array|false
  103. */
  104. public function getQualifiedExam($phone, $unqualified = false)
  105. {
  106. try {
  107. $admin = $this->getAdminByPhone($phone);
  108. if(!$admin){
  109. return false;
  110. }
  111. $admin_id = $admin['id'];
  112. $where = [
  113. 'admin_id' => $admin_id,
  114. 'result.status' => 2,
  115. ];
  116. $data = $this->examResult->alias('result')
  117. ->join($this->exam->getTable().' exam', 'exam.id = exam_id')
  118. ->where($where)
  119. ->field('result.id as result_id, exam_id, title, scores, result.start_time, submit_time, total, is_qualified, 0 as newest')
  120. ->order('result.id','desc')
  121. ->select();
  122. $is_qualified = $unqualified === true ? 0 :1;
  123. $result = [];
  124. if($data){
  125. $data[0]['newest'] = 1;
  126. foreach ($data as $val){
  127. if($val['is_qualified'] === $is_qualified){
  128. $temp = $val;
  129. // 时长
  130. $temp['duration'] = '';
  131. if($temp['start_time'] && $temp['submit_time']){
  132. $temp['duration'] = date('H:i:s', strtotime($temp['submit_time']) - strtotime($temp['start_time']));
  133. }
  134. $result[] = $temp;
  135. }
  136. }
  137. }
  138. return $result;
  139. } catch (DbException $exception){
  140. return false;
  141. }
  142. }
  143. /**
  144. * 获取缺考的考试
  145. * @param $phone
  146. * @return bool|\think\Collection
  147. */
  148. public function getMissedExam($phone)
  149. {
  150. try {
  151. $admin = $this->getAdminByPhone($phone);
  152. if(!$admin){
  153. return false;
  154. }
  155. $admin_id = $admin['id'];
  156. $now = date('Y-m-d H:i:s', Request::instance()->time());
  157. return $this->examResult->alias('result')
  158. ->join($this->exam->getTable().' exam', 'exam.id = exam_id')
  159. ->where('admin_id', $admin_id)
  160. ->where(function ($query){
  161. $query->where('result.status', 0)
  162. ->whereOr('result.status', 1);
  163. })
  164. ->where('exam.end_time','<', $now)
  165. ->field('exam_id, title, scores, exam.start_time, exam.end_time, total')
  166. ->order('result.id','desc')
  167. ->select();
  168. } catch (DbException $exception){
  169. return false;
  170. }
  171. }
  172. /**
  173. * 获取考试的考题
  174. * @param $exam_id
  175. * @return array
  176. */
  177. public function getExamQuestions($exam_id)
  178. {
  179. $question_ids = model(TrainExamReQuestion::class)
  180. ->where('exam_id', $exam_id)
  181. ->column('question_id');
  182. $data = $this->question
  183. ->whereIn('id', $question_ids)
  184. ->field('class_id, result, is_del, created_at', true)
  185. ->order('type')
  186. ->select();
  187. $result = [];
  188. foreach ($data as $val){
  189. $temp = [
  190. "id" => $val['id'],
  191. "title" => $val['title'],
  192. "type" => $val['type'],
  193. "answer" => []
  194. ];
  195. if($val['answer1']){
  196. $temp['answer'][] = ['key' => 1, 'title' => $val['answer1'], 'checked' => 0];
  197. }
  198. if($val['answer2']){
  199. $temp['answer'][] = ['key' => 2, 'title' => $val['answer2'], 'checked' => 0];
  200. }
  201. if($val['answer3']){
  202. $temp['answer'][] = ['key' => 3, 'title' => $val['answer3'], 'checked' => 0];
  203. }
  204. if($val['answer4']){
  205. $temp['answer'][] = ['key' => 4, 'title' => $val['answer4'], 'checked' => 0];
  206. }
  207. if($val['answer5']){
  208. $temp['answer'][] = ['key' => 5, 'title' => $val['answer5'], 'checked' => 0];
  209. }
  210. if($val['answer6']){
  211. $temp['answer'][] = ['key' => 6, 'title' => $val['answer6'], 'checked' => 0];
  212. }
  213. $result[] = $temp;
  214. }
  215. return $result;
  216. }
  217. public function getExam($exam_id)
  218. {
  219. return $this->exam->get(['id' => $exam_id]);
  220. }
  221. public function getResult($result_id)
  222. {
  223. return $this->examResult->get($result_id);
  224. }
  225. /**
  226. * 获取最新未开始的考试
  227. * @param $phone
  228. * @param $exam_id
  229. * @return array|bool
  230. */
  231. public function getNestExamResult($phone, $exam_id)
  232. {
  233. $admin = $this->getAdminByPhone($phone);
  234. if(!$admin){
  235. return false;
  236. }
  237. $admin_id = $admin['id'];
  238. return model(TrainExamResult::class)
  239. ->where('admin_id', $admin_id)
  240. ->where('exam_id', $exam_id)
  241. ->where('status', 0)
  242. ->order('id','desc')
  243. ->find();
  244. }
  245. /**
  246. * 开始考试
  247. * @param $result_id
  248. * @return bool
  249. */
  250. public function startExam($result_id)
  251. {
  252. $res = model(TrainExamResult::class)
  253. ->where('id', $result_id)
  254. ->update([
  255. 'status' => 1,
  256. 'start_time' => date('Y-m-d H:i:s')
  257. ]);
  258. if(!$res){
  259. return false;
  260. }
  261. return true;
  262. }
  263. /**
  264. * 重考
  265. * @param $result_id
  266. * @return array|false
  267. */
  268. public function retakeExam($result_id)
  269. {
  270. $result = $this->getResult($result_id);
  271. $number = model(TrainExamResult::class)
  272. ->where('exam_id', $result['exam_id'])
  273. ->where('admin_id', $result['admin_id'])
  274. ->max('number') + 1;
  275. $res = model(TrainExamResult::class)
  276. ->insertGetId([
  277. 'exam_id' => $result['exam_id'],
  278. 'admin_id' => $result['admin_id'],
  279. 'number' => $number,
  280. 'status' => 1,
  281. 'start_time' => date('Y-m-d H:i:s')
  282. ]);
  283. return $res !== false ? ['result_id' => $res] : false;
  284. }
  285. /**
  286. * 获取考题column
  287. * @param $question_ids
  288. * @return array|false|string
  289. */
  290. public function getQuestionColumn($question_ids)
  291. {
  292. return $this->question
  293. ->whereIn('id', $question_ids)
  294. ->column('id, type, result');
  295. }
  296. /**
  297. * 提交考试
  298. * @param $result_id
  299. * @param $record
  300. * @return array
  301. */
  302. public function subExam($result_id, $record)
  303. {
  304. // 创建答题记录
  305. Db::startTrans();
  306. $record_save_res = $this->insertResultRecord($result_id, $record);
  307. if(!$record_save_res){
  308. Db::rollback();
  309. $this->setError('插入答题记录失败');
  310. }
  311. // 更新考卷数据
  312. $result = $this->getResult($result_id);
  313. $total = $this->calculateScore($result['exam_id'], $result_id);
  314. $exam = $this->getExam($result['exam_id']);
  315. $is_qualified = $total >= $exam['qualified'] ? 1 : 0;
  316. $update_res = $this->examResult
  317. ->where('id', $result_id)
  318. ->update([
  319. 'scores' => $total,
  320. 'status' => 2,
  321. 'is_qualified' => $is_qualified,
  322. 'submit_time' => date('Y-m-d H:i:s', Request::instance()->time())
  323. ]);
  324. if(!$update_res){
  325. Db::rollback();
  326. $this->setError('更新考卷数据失败 ');
  327. }
  328. // ok
  329. Db::commit();
  330. return [
  331. 'scores' => $total,
  332. 'qualified' => $is_qualified,
  333. ];
  334. }
  335. /**
  336. * 插入答题记录
  337. * @param $result_id
  338. * @param $records
  339. * @return bool
  340. */
  341. public function insertResultRecord($result_id, $records)
  342. {
  343. $question_ids = array_column($records, 'question_id');
  344. $question_arr = $this->getQuestionColumn($question_ids);
  345. $save = [];
  346. foreach ($records as $record){
  347. if(!$record){
  348. continue;
  349. }
  350. $question_id = $record['question_id'];
  351. if(!isset($question_arr[$question_id])){
  352. continue;
  353. }
  354. // 格式化答案
  355. $right_res = $question_arr[$question_id]['result'];
  356. $checked_arr = [];
  357. foreach ($record['answer'] as $answer){
  358. if((int) $answer['checked'] === 1){
  359. $checked_arr[] = (int) $answer['key'];
  360. }
  361. }
  362. // 判断对错
  363. $question_answer = implode('', $checked_arr);
  364. $result = $this->checkQuestionResult($question_answer, $right_res) ? 1 : 0;
  365. // 组装数据
  366. $save[] = [
  367. 'result_id' => $result_id,
  368. 'question_id' => $question_id,
  369. 'answer' => $question_answer,
  370. 'result' => $result,
  371. ];
  372. }
  373. $res = model(TrainExamResultRecord::class)
  374. ->insertAll($save);
  375. return $res !== false;
  376. }
  377. /**
  378. * 检查题目是否正确
  379. * @param $answer
  380. * @param $right_result
  381. * @return bool
  382. */
  383. public function checkQuestionResult($answer, $right_result)
  384. {
  385. $answer_arr = $this->ch2arr((int) $answer);
  386. $right_arr = $this->ch2arr((int) $right_result);
  387. if(array_diff($answer_arr, $right_arr)){
  388. return false;
  389. }
  390. return true;
  391. }
  392. /**
  393. * 计算得分
  394. * @param $exam_id
  395. * @param $result_id
  396. * @return float|int|string
  397. */
  398. public function calculateScore($exam_id, $result_id)
  399. {
  400. $right_question_ids = model(TrainExamResultRecord::class)
  401. ->where('result_id', $result_id)
  402. ->where('result', 1)
  403. ->column('question_id');
  404. return model(TrainExamReQuestion::class)
  405. ->where('exam_id', $exam_id)
  406. ->whereIn('question_id', $right_question_ids)
  407. ->sum('score');
  408. }
  409. /**
  410. * 字符串分割数组
  411. * @param $str
  412. * @return array
  413. */
  414. public function ch2arr($str)
  415. {
  416. $length = mb_strlen($str, 'utf-8');
  417. $array = [];
  418. for ($i = 0; $i < $length; $i++)
  419. $array[] = mb_substr($str, $i, 1, 'utf-8');
  420. return $array;
  421. }
  422. /**
  423. * 获取考试结果根据用户id
  424. * @param $exam_id
  425. * @param $admin_ids
  426. * @return array
  427. */
  428. public function getResultByAdminIds($exam_id, $admin_ids)
  429. {
  430. $data = model(TrainExamResult::class)
  431. ->where('exam_id', $exam_id)
  432. ->whereIn('admin_id', $admin_ids)
  433. ->field('id, admin_id, number, status, is_qualified, scores')
  434. ->order('id','desc')
  435. ->select();
  436. $result = [];
  437. foreach ($data as $val){
  438. if(!isset($result[$val['admin_id']])){
  439. $result[$val['admin_id']] = [
  440. 'result_id' => $val['id'],
  441. 'status' => $val['status'],
  442. 'scores' => $val['scores'],
  443. 'is_qualified' => $val['is_qualified']
  444. ];
  445. }
  446. }
  447. return $result;
  448. }
  449. /**
  450. * 获取考试剩余时间
  451. * @param $result_id
  452. * @return false|float|int
  453. */
  454. public function getRemainingTime($result_id)
  455. {
  456. $result = $this->getResult($result_id);
  457. $exam = $this->getExam($result['exam_id']);
  458. return strtotime($result['start_time']) + ($exam['duration'] * 60) - Request::instance()->time();
  459. }
  460. public function formatResult($result, $type)
  461. {
  462. switch ($type){
  463. case 1:
  464. case 2:
  465. $str = (string) $result;
  466. $iMax = strlen($str);
  467. $temp = [];
  468. for($i=0; $i< $iMax; $i++){//遍历字符串追加给数组
  469. $temp[] = $this->result_arr[$str[$i]];
  470. }
  471. return implode($temp);
  472. case 3:
  473. return $this->judge[$result] ?? $result;
  474. default;
  475. }
  476. }
  477. public function formatType($type){
  478. return $this->type_arr[$type] ?? $type;
  479. }
  480. public function getAdminByPhone($phone)
  481. {
  482. return model(TrainAdmin::class)
  483. ->where('mobile', $phone)
  484. ->find();
  485. }
  486. }