wesmiler 5 meses atrás
pai
commit
994cb8db64

+ 1 - 5
app/Helpers/common.php

@@ -1039,7 +1039,7 @@ if (!function_exists('format_content')) {
             return false;
         }
 
-        $content = str_replace(["\n"], ["<br/>"], htmlspecialchars_decode($content));
+        $content = str_replace(["\n",'\n'], ["<br/>","<br/>"], htmlspecialchars_decode($content));
         return get_format_content($content);
     }
 }
@@ -2212,10 +2212,6 @@ if (!function_exists('aiRequest')) {
             curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
             $ret = curl_exec($ch);
             curl_close($ch);
-            if (preg_match("/^{/", $ret)) {
-                return json_decode($ret, true);
-            }
-
             return $ret;
     }
 }

+ 1 - 0
app/Http/Controllers/Admin/IndexController.php

@@ -113,6 +113,7 @@ class IndexController extends Backend
         RedisService::keyDel("caches:member*");
         RedisService::keyDel("caches:videos*");
         RedisService::keyDel("caches:config*");
+        RedisService::keyDel("caches:paper*");
         RedisService::keyDel("caches:mpQrcode*");
         RedisService::keyDel("caches:order*");
         RedisService::keyDel(env('APP_NAME').'_cache:*');

+ 30 - 5
app/Http/Controllers/Api/v1/CourseController.php

@@ -4,7 +4,6 @@ namespace App\Http\Controllers\Api\v1;
 
 use App\Http\Controllers\Api\webApp;
 use App\Services\Api\CourseService;
-use App\Services\Api\PaperService;
 
 /**
  * 视频课管理
@@ -23,8 +22,9 @@ class CourseController extends webApp
             $params = request()->all();
             $datas = CourseService::make()->getListByCate($params);
             return message(1010, true, $datas);
-        } catch (\Exception $exception) {
-            return message(1009, false, $exception->getMessage());
+        }  catch (\Exception $exception) {
+            $error = ['error' => $exception->getMessage(), 'trace' => $exception->getTrace()];
+            return message(1009, false, $error);
         }
     }
 
@@ -44,7 +44,8 @@ class CourseController extends webApp
             $datas = CourseService::make()->getListByGroup($groupId, $params, $pageSize);
             return message(1010, true, $datas);
         } catch (\Exception $exception) {
-            return message(1009, false, $exception->getMessage());
+            $error = ['error' => $exception->getMessage(), 'trace' => $exception->getTrace()];
+            return message(1009, false, $error);
         }
     }
 
@@ -66,7 +67,31 @@ class CourseController extends webApp
                 return message(1009, false);
             }
         } catch (\Exception $exception) {
-            return message(1009, false, $exception->getMessage());
+            $error = ['error' => $exception->getMessage(), 'trace' => $exception->getTrace()];
+            return message(1009, false, $error);
+        }
+    }
+
+    /**
+     * 学习
+     */
+    public function learn()
+    {
+        try {
+            $params = request()->all();
+            $id = isset($params['id']) ? intval($params['id']) : 0;
+            if (empty($id)) {
+                return message(1036, false);
+            }
+
+            if ($info = CourseService::make()->learn($this->userId, $id)) {
+                return message(1010, true, $info);
+            } else {
+                return message(1009, false);
+            }
+        }  catch (\Exception $exception) {
+            $error = ['error' => $exception->getMessage(), 'trace' => $exception->getTrace()];
+            return message(1009, false, $error);
         }
     }
 

+ 45 - 32
app/Http/Controllers/Api/v1/ExamController.php

@@ -3,7 +3,6 @@
 namespace App\Http\Controllers\Api\v1;
 
 use App\Http\Controllers\Api\webApp;
-use App\Services\Api\ArticleService;
 use App\Services\Api\ExamService;
 
 /**
@@ -74,14 +73,14 @@ class ExamController extends webApp
     {
         try {
             $params = request()->all();
-            if($result = ExamService::make()->answer($this->userId, $params)){
+            if ($result = ExamService::make()->answer($this->userId, $params)) {
                 return showJson(ExamService::make()->getError(), true, $result);
-            }else{
+            } else {
                 return showJson(ExamService::make()->getError(), false);
             }
         } catch (\Exception $exception) {
             $error = ['error' => $exception->getMessage(), 'trace' => $exception->getTrace()];
-            return message(1009, false, $error);
+            return message(1003, false, $error);
         }
     }
 
@@ -93,17 +92,16 @@ class ExamController extends webApp
     {
         try {
             $id = request()->post('id');
-            if(ExamService::make()->reset($id, $this->userId)){
+            if (ExamService::make()->reset($id, $this->userId)) {
                 return showJson(1002, true);
-            }else{
+            } else {
                 return showJson(ExamService::make()->getError(), false);
             }
         } catch (\Exception $exception) {
             $error = ['error' => $exception->getMessage(), 'trace' => $exception->getTrace()];
-            return message(1009, false, $error);
+            return message(1003, false, $error);
         }
     }
-    
 
 
     /**
@@ -111,16 +109,21 @@ class ExamController extends webApp
      */
     public function info()
     {
-        $params = request()->all();
-        $rid = isset($params['rid']) ? intval($params['rid']) : 0;
-        if (empty($rid)) {
-            return message(1036, false);
-        }
+        try {
+            $params = request()->all();
+            $rid = isset($params['rid']) ? intval($params['rid']) : 0;
+            if (empty($rid)) {
+                return message(1036, false);
+            }
 
-        if ($info = ExamService::make()->getInfo($rid)) {
-            return message(1010, true, $info);
-        } else {
-            return message(1009, false);
+            if ($info = ExamService::make()->getInfo($rid)) {
+                return message(1010, true, $info);
+            } else {
+                return message(1009, false);
+            }
+        } catch (\Exception $exception) {
+            $error = ['error' => $exception->getMessage(), 'trace' => $exception->getTrace()];
+            return message(1009, false, $error);
         }
     }
 
@@ -130,14 +133,19 @@ class ExamController extends webApp
      */
     public function ranks()
     {
-        $params = request()->all();
-        $type = isset($params['type']) ? intval($params['type']) : 0;
-        if (empty($type)) {
-            return message(1031, false);
-        }
+        try {
+            $params = request()->all();
+            $type = isset($params['type']) ? intval($params['type']) : 0;
+            if (empty($type)) {
+                return message(1031, false);
+            }
 
-        $datas = ExamService::make()->getRankByType($type);
-        return message(1010, true, $datas);
+            $datas = ExamService::make()->getRankByType($type);
+            return message(1010, true, $datas);
+        } catch (\Exception $exception) {
+            $error = ['error' => $exception->getMessage(), 'trace' => $exception->getTrace()];
+            return message(1009, false, $error);
+        }
     }
 
     /**
@@ -145,14 +153,19 @@ class ExamController extends webApp
      */
     public function cards()
     {
-        $params = request()->all();
-        $rid = isset($params['rid']) ? intval($params['rid']) : 0;
-        $paperId = isset($params['id']) ? intval($params['id']) : 0;
-        if (empty($paperId)) {
-            return message(1031, false);
-        }
+        try {
+            $params = request()->all();
+            $rid = isset($params['rid']) ? intval($params['rid']) : 0;
+            $paperId = isset($params['id']) ? intval($params['id']) : 0;
+            if (empty($paperId)) {
+                return message(1031, false);
+            }
 
-        $datas = ExamService::make()->getCardList($this->userId, $paperId,  $rid);
-        return message(1010, true, $datas);
+            $datas = ExamService::make()->getCardList($this->userId, $paperId, $rid);
+            return message(1010, true, $datas);
+        } catch (\Exception $exception) {
+            $error = ['error' => $exception->getMessage(), 'trace' => $exception->getTrace()];
+            return message(1009, false, $error);
+        }
     }
 }

+ 4 - 1
app/Http/Controllers/Api/v1/IndexController.php

@@ -3,6 +3,7 @@
 namespace App\Http\Controllers\Api\v1;
 
 use App\Http\Controllers\Api\webApp;
+use App\Services\Api\ArticleService;
 use App\Services\Api\CourseService;
 use App\Services\Common\AdService;
 use App\Services\Common\NoticeService;
@@ -35,7 +36,7 @@ class IndexController extends webApp
                     'wxpay_open' => ConfigService::make()->getConfigByCode('wxpay_open', 1),
                     'vip_buy_desc' => format_content(ConfigService::make()->getConfigByCode('vip_buy_desc', '')),
                     'video_vip_tips' => format_content(ConfigService::make()->getConfigByCode('video_vip_tips', '')),
-                    'kfUrl' => ConfigService::make()->getConfigByCode('wechat_kf_url', ''),
+                    'errors' => config('platform.errors'),
                 ];
                 RedisService::set($cacheKey, $config, 3600);
             }
@@ -58,6 +59,8 @@ class IndexController extends webApp
             'menuList' => config('platform.menuList'),
             // 公告
             'notices' => NoticeService::make()->getRecommandList(),
+            // 资讯
+            'articles' => ArticleService::make()->getIndexList(1),
             // 轮播
             'banners' => AdService::make()->getListByPosition(1),
             // 线上课程

+ 20 - 0
app/Http/Controllers/Api/v1/PaperController.php

@@ -19,6 +19,7 @@ class PaperController extends webApp
     public function index()
     {
         $params =request()->post();
+        $params['user_id'] = $this->userId;
         $pageSize = request()->post('pageSize', 15);
         $datas = PaperService::make()->getDataList($params, $pageSize);
         return message(1010, true, $datas);
@@ -47,4 +48,23 @@ class PaperController extends webApp
         }
     }
 
+    /**
+     * 试题纠错
+     * @return array
+     */
+    public function error()
+    {
+        try {
+            $params = request()->all();
+            if ($result = PaperService::make()->submitError($this->userId, $params)) {
+                return showJson(PaperService::make()->getError(), true, $result);
+            } else {
+                return showJson(PaperService::make()->getError(), false);
+            }
+        } catch (\Exception $exception) {
+            $error = ['error' => $exception->getMessage(), 'trace' => $exception->getTrace()];
+            return message(1003, false, $error);
+        }
+    }
+
 }

+ 4 - 4
app/Http/Controllers/Api/v1/TestController.php

@@ -25,17 +25,17 @@ class TestController extends webApp
 
 
         $data = [
-            'answer'=> '表达了诗人孤独、愁苦的心境和对时光流逝的感慨。',
+            'answer'=> 'answer: "亲爱的小雅:↵  本人因临时有急事不得不出门,故原计划与你相约去看的话剧无法准时应约,望谅解。我们改日再约。↵友:晓芙↵2025年2月1日',
 //            'answer'=> 'https://shuati.derkj.com/uploads/temp/20250922/answer1.jpeg',
             'score'=>10,
 //            'topic'=> 'https://shuati.derkj.com/uploads/temp/20250922/topic2.png',
-            'topic'=> '2.请简述一下杜甫的诗《登高》中“无边落木萧萧下,不尽长江滚滚来”这两句诗描绘了怎样的景象?表达了诗人怎样的情感?',
+            'topic'=> '四、材料写作题(共40分)<br/>(一)应用文写作(10分)<br/>29晓芙和小雅原计划2025年2月1日中午12点去看话剧,可晓芙临时有急事要出门。请你根据下面的模板以晓芙的名义给小雅写一张留言条。<br/>__:<br/>__,望谅解。我们改日再约。<br/>友:__<br/>2025年__',
             'type'=> 2
         ];
 
 
-        $result = DeepSeekService::make()->upload('/temp/20250922/topic2.jpeg');
-//        $result = DeepSeekService::make()->apiRequest($data,'deepseek-chat');
+//        $result = DeepSeekService::make()->upload('/temp/20250922/topic2.jpeg');
+        $result = DeepSeekService::make()->apiRequest($data,'deepseek-chat');
         dump($result);
 //        $papers = [28,29];
 //        $datas = [];

+ 10 - 0
app/Models/ArticleModel.php

@@ -21,4 +21,14 @@ class ArticleModel extends BaseModel
 {
     // 设置数据表
     protected $table = 'article';
+
+    public function getCoverAttribute($value)
+    {
+        return $value? get_image_url($value) : '';
+    }
+
+    public function setCoverAttribute($value)
+    {
+        return $value? get_image_path($value) : '';
+    }
 }

+ 12 - 0
app/Models/ExamPaperModel.php

@@ -22,4 +22,16 @@ class ExamPaperModel extends BaseModel
         'mark',
         'create_time'
     ];
+
+    /**
+     * 最新答题数据
+     * @return \Illuminate\Database\Eloquent\Relations\HasOne
+     */
+    public function answer()
+    {
+        return $this->hasOne(ExamAnswerModel::class, 'paper_id', 'id')
+            ->where(['status' => 1, 'mark' => 1])
+            ->select(['id', 'score', 'accurate_count','user_id', 'answer_times', 'answer_count', 'is_submit'])
+            ->orderBy('create_time','desc');
+    }
 }

+ 29 - 1
app/Services/Api/ArticleService.php

@@ -97,7 +97,7 @@ class ArticleService extends BaseService
         $list = $list? $list->toArray() :[];
         if($list){
             foreach($list['data'] as &$item){
-                $item['create_time'] = $item['create_time']? datetime($item['create_time'],'Y-m-d H.i.s') : '';
+                $item['create_time'] = $item['create_time']? datetime($item['create_time'],'Y-m-d') : '';
                 $item['cover'] = $item['cover']? get_image_url($item['cover']) : '';
             }
         }
@@ -210,6 +210,34 @@ class ArticleService extends BaseService
      * @param int $type 1-普通文章分类
      * @return array|mixed
      */
+    public function getIndexList($type=1)
+    {
+        $cacheKey = "caches:articles:list_{$type}";
+        $datas = RedisService::get($cacheKey);
+        if($datas){
+            return $datas;
+        }
+
+        $limitNum = ConfigService::make()->getConfigByCode('index_article_num', 6);
+        $limitNum = $limitNum? $limitNum : 6;
+        $datas = $this->model::where(['type'=> $type,'status'=>1,'mark'=>1])
+            ->orderBy('sort','desc')
+            ->orderBy('id','desc')
+            ->limit($limitNum)
+            ->get();
+        $datas = $datas? $datas->toArray() : [];
+        if($datas){
+            RedisService::set($cacheKey, $datas, rand(300,600));
+        }
+
+        return $datas;
+    }
+
+    /**
+     * 获取文章推荐分类
+     * @param int $type 1-普通文章分类
+     * @return array|mixed
+     */
     public function getCateList($type=2)
     {
         $cacheKey = "caches:articles:cateList_{$type}";

+ 45 - 3
app/Services/Api/CourseService.php

@@ -213,18 +213,19 @@ class CourseService extends BaseService
 
         $data = $this->model->from('videos_courses as videos_courses')
             ->leftJoin('videos as b','b.id','=','videos_courses.video_id')
-            /*->leftJoin('videos_learn_logs as c',function($join) use($userId){
+            ->leftJoin('videos_learn_logs as c',function($join) use($userId){
                 $join->on('c.course_id','=','videos_courses.id')
                     ->where(['c.user_id'=>$userId,'c.status'=>1,'c.mark'=>1]);
-            })*/
+            })
             ->with(['vip'])
             ->where(['videos_courses.id'=>$id,'videos_courses.status'=>1,'videos_courses.mark'=>1])
-            ->select(['videos_courses.id','videos_courses.video_id','videos_courses.course_name','videos_courses.course_url','videos_courses.fee','videos_courses.poster','videos_courses.description','videos_courses.sort','b.type'])
+            ->select(['videos_courses.id','videos_courses.video_id','videos_courses.course_name','videos_courses.course_url','videos_courses.fee','videos_courses.poster','videos_courses.description','videos_courses.sort','c.id as learn_id','b.type'])
             ->first();
         $data = $data? $data->toArray() : [];
         if($data){
             // 验证付费视频是否有播放权限
             $fee = isset($data['fee'])? $data['fee'] : 0;
+            $data['learn_id'] = !empty($data['learn_id'])? $data['learn_id'] : 0;
             $buyVipData = isset($data['vip'])? $data['vip'] : [];
             $data['buy_vip'] = $buyVipData? 1 : 0;
             $data['can_play'] = $buyVipData || $fee<=0? 1 : 0;
@@ -239,4 +240,45 @@ class CourseService extends BaseService
         $this->model->where(['id'=> $id])->increment('views', 1);
         return $data;
     }
+
+    /**
+     * 课程学习记录
+     * @param $userId 用户
+     * @param $id 课程ID
+     * @return array|false|mixed
+     */
+    public function learn($userId, $id)
+    {
+        $cacheKey = "caches:videos:learn_{$userId}_{$id}";
+        $data = RedisService::get($cacheKey, $cacheKey);
+        if($data){
+            $this->error = '课程已学习过';
+            return $data;
+        }
+
+        $info = $this->model->where(['id'=> $id,'mark'=>1])->select(['id','video_id'])->first();
+        $videoId = isset($info['video_id'])? $info['video_id'] : 0;
+        if(empty($info)){
+            $this->error = '课程不存在';
+            return false;
+        }
+
+        if($videoId<=0){
+            $this->error = '课程集参数错误';
+            return false;
+        }
+
+        // 是否已有学习记录
+        if($learnInfo = VideoLearnLogModel::where(['user_id'=>$userId,'course_id'=>$id,'video_id'=>$videoId])->select(['id','user_id','course_id','video_id'])->first()){
+            RedisService::set($cacheKey, $learnInfo->toArray(), rand(300, 600));
+            $this->error = '课程已学习过';
+            return false;
+        }
+
+        $data = ['user_id'=> $userId,'course_id'=>$id,'video_id'=>$videoId,'create_time'=>time(),'status'=>1,'mark'=>1];
+        $data['id'] = VideoLearnLogModel::insertGetId($data);
+        RedisService::set($cacheKey, $data, rand(300, 600));
+        $this->error = '课程学习完成';
+        return false;
+    }
 }

+ 167 - 167
app/Services/Api/ExamService.php

@@ -152,7 +152,7 @@ class ExamService extends BaseService
         }
 
         $query = $this->getQuery($params);
-        $list = $query->select(['a.id', 'a.user_id', 'a.paper_id', 'a.score', 'a.accurate_count', 'b.name', 'b.type','b.scene_type', 'b.topic_count', 'b.score_total', 'b.is_charge', 'a.create_time', 'a.answer_times', 'a.status'])
+        $list = $query->select(['a.id', 'a.user_id', 'a.paper_id', 'a.score', 'a.accurate_count', 'b.name', 'b.type', 'b.scene_type', 'b.topic_count', 'b.score_total', 'b.is_charge', 'a.create_time', 'a.answer_times', 'a.status'])
             ->orderBy('a.create_time', 'desc')
             ->paginate($pageSize > 0 ? $pageSize : 9999999);
         $list = $list ? $list->toArray() : [];
@@ -189,7 +189,7 @@ class ExamService extends BaseService
         $cacheKey = "caches:exams:{$userId}_practice:{$page}_" . md5(json_encode($params));
         $datas = RedisService::get($cacheKey);
         // 每日一练访问次数统计
-        if(empty($sc)){
+        if (empty($sc)) {
             ExamAccessLogModel::saveLog(date('Y-m-d'), $type, 1);
         }
 
@@ -199,7 +199,8 @@ class ExamService extends BaseService
 
         $list = $this->model->from('exam_answers as a')
             ->leftJoin('exam_papers as b', 'b.id', '=', 'a.paper_id')
-            ->where(['b.scene_type' => 1, 'b.status' => 1,'a.status'=>1, 'b.mark' => 1, 'a.mark' => 1])
+            ->where('b.topic_count', '>', 0)
+            ->where(['b.scene_type' => 1, 'b.status' => 1, 'a.status' => 1, 'b.mark' => 1, 'a.mark' => 1])
             ->where(function ($query) use ($params) {
                 $type = isset($params['type']) && $params['type'] ? intval($params['type']) : 1;
                 if ($type > 0) {
@@ -223,7 +224,7 @@ class ExamService extends BaseService
         if ($page == 1 && strtotime($firstTime) < strtotime(date('Y-m-d'))) {
             $type = isset($params['type']) && $params['type'] ? intval($params['type']) : 1;
             $data = PaperService::make()->getRandomPaper($userId, $type, 1);
-            if($data){
+            if ($data) {
                 $rows = array_merge([$data], $rows);
             }
         }
@@ -248,7 +249,7 @@ class ExamService extends BaseService
      */
     public function getRankByType($type, $num = 0)
     {
-        $num = $num ? $num : ConfigService::make()->getConfigByCode('rank_num', 10);
+        $num = $num ? $num : ConfigService::make()->getConfigByCode('rank_num', 50);
         $cacheKey = "caches:exams:ranks:{$type}_{$num}";
         $datas = RedisService::get($cacheKey);
         if ($datas) {
@@ -271,15 +272,16 @@ class ExamService extends BaseService
                     $query->where('a.date', '>=', date('Y-m-01'));
                 }
             })
-            ->select(['a.id', 'a.user_id', 'b.avatar', 'b.nickname', 'a.answer_time', 'a.answer_count', DB::raw("ROUND(sum({$prefix}a.answer_time)/3600,0) as answer_hour"), DB::raw("sum({$prefix}a.answer_count) as count")])
+            ->select(['a.id', 'a.user_id', 'b.avatar', 'b.nickname', 'a.answer_time', 'a.answer_count', DB::raw("sum({$prefix}a.answer_time) as answer_times"), DB::raw("sum({$prefix}a.answer_count) as count")])
             ->groupBy('a.user_id')
-            ->orderByRaw("sum({$prefix}a.answer_time)/3600 desc")
+            ->orderByRaw("sum({$prefix}a.answer_time) desc")
             ->take($num)
             ->get();
         $datas = $datas ? $datas->toArray() : [];
         if ($datas) {
             foreach ($datas as &$item) {
                 $item['avatar'] = $item['avatar'] ? get_image_url($item['avatar']) : '';
+                $item['answer_hour'] = $item['answer_times'] > 3600 ? intval($item['answer_times'] / 3600) . 'h' : ($item['answer_times'] > 60 ? intval($item['answer_times'] / 60) . 'm' : $item['answer_times'] . 's');
             }
 
             RedisService::set($cacheKey, $datas, rand(20, 30));
@@ -294,38 +296,39 @@ class ExamService extends BaseService
      * @param int $rid
      * @return array|mixed
      */
-    public function getCardList($userId, $paperId, $rid=0)
+    public function getCardList($userId, $paperId, $rid = 0)
     {
         $cacheKey = "caches:exams:{$userId}_cardList:{$paperId}_{$rid}";
         $datas = RedisService::get($cacheKey);
-        if($datas){
+        if ($datas) {
             return $datas;
         }
 
         $datas = ExamTopicModel::from('exam_topics as a')
-            ->leftJoin('exam_answers_topics as b',function($join) use($userId, $rid){
-                if($rid){
-                    $join->on('b.topic_id','=','a.id')->where(['b.user_id'=>$userId,'b.answer_log_id'=> $rid,'status'=>1,'mark'=>1]);
-                }
+            ->leftJoin('exam_answers_topics as b', function ($join) use ($userId, $rid) {
+                $join->on('b.topic_id', '=', 'a.id')->where(['b.user_id' => $userId, 'b.answer_log_id' => $rid, 'b.status' => 1, 'b.mark' => 1]);
             })
-            ->where(['a.paper_id'=> $paperId, 'a.status'=>1,'a.mark'=>1])
-            ->select(['a.id','a.topic_name','a.answer','a.score','a.paper_id','a.topic_type','b.id as rid','b.answer as submit_answer','b.accurate','b.score as submit_score'])
-            ->groupBy('a.topic_type')
-            ->orderBy('a.sort','desc')
-            ->orderBy('a.id','asc')
+            ->where(['a.paper_id' => $paperId, 'a.status' => 1, 'a.mark' => 1])
+            ->select(['a.id', 'a.topic_name', 'a.correct_answer', 'b.answer', 'a.score', 'a.paper_id', 'a.topic_type', 'b.answer_log_id as rid', 'b.answer as submit_answer', 'b.accurate', 'b.score as submit_score'])
+            ->orderBy('a.sort', 'desc')
+            ->orderBy('a.id', 'asc')
             ->get();
-        $datas = $datas? $datas->toArray() :[];
-        if($datas){
-            foreach ($datas as &$item){
-                $item['rid'] = !empty($item['rid'])? $item['rid'] : 0;
-                $item['submit_answer'] = isset($item['submit_answer'])? $item['submit_answer'] : '';
-                $item['accurate'] = isset($item['accurate'])? $item['accurate'] : -1;
-                $item['submit_score'] = isset($item['submit_score'])? $item['submit_score'] : 0;
+        $datas = $datas ? $datas->toArray() : [];
+        $list = [];
+        if ($datas) {
+            foreach ($datas as &$item) {
+                $item['rid'] = !empty($item['rid']) && $item['rid'] ? $item['rid'] : $rid;
+                $item['submit_answer'] = isset($item['submit_answer']) ? $item['submit_answer'] : '';
+                $item['accurate'] = isset($item['accurate']) ? $item['accurate'] : -1;
+                $item['submit_score'] = isset($item['submit_score']) ? $item['submit_score'] : 0;
+                if ($item['topic_type']) {
+                    $list[$item['topic_type']][] = $item;
+                }
             }
-            RedisService::set($cacheKey, $datas, rand(5, 10));
+            RedisService::set($cacheKey, $list, rand(5, 10));
         }
 
-        return $datas;
+        return $list;
     }
 
     /**
@@ -333,16 +336,16 @@ class ExamService extends BaseService
      * @param $id
      * @return bool
      */
-    public function reset($id, $userId=0)
+    public function reset($id, $userId = 0)
     {
-        $log = $this->model->where(['id'=> $id,'mark'=>1])->first();
-        if(empty($log)){
+        $log = $this->model->where(['id' => $id, 'mark' => 1])->first();
+        if (empty($log)) {
             return true;
         }
 
-        $updateData = ['score'=>0,'accurate_count'=>0,'answer_count'=>0,'answer_times'=>0,'answer_last_id'=>0,'is_submit'=>0,'status'=>1];
-        $this->model->where(['id'=> $id,'mark'=>1])->update($updateData);
-        ExamAnswerTopicModel::where(['answer_log_id'=> $id,'mark'=>1])->update(['status'=>2,'update_time'=>time()]);
+        $updateData = ['score' => 0, 'accurate_count' => 0, 'answer_count' => 0, 'answer_times' => 0, 'answer_last_id' => 0, 'is_submit' => 0, 'status' => 1];
+        $this->model->where(['id' => $id, 'mark' => 1])->update($updateData);
+        ExamAnswerTopicModel::where(['answer_log_id' => $id, 'mark' => 1])->update(['status' => 2, 'update_time' => time()]);
         RedisService::keyDel("caches:exams:{$userId}*");
         return true;
     }
@@ -355,69 +358,70 @@ class ExamService extends BaseService
      */
     public function answer($userId, $params)
     {
-        $paperId = isset($params['id'])? $params['id'] : 0;
-        $rid = isset($params['rid'])? $params['rid'] : 0;
-        $tid = isset($params['tid'])? $params['tid'] : 0;
-        $isSubmit = isset($params['is_submit'])? $params['is_submit'] : 1;
-        $answer = isset($params['answer'])? $params['answer'] : '';
-        $answerImage = isset($params['answer_image'])? $params['answer_image'] : '';
-        $answerType = isset($params['answer_type']) && $params['answer_type']? $params['answer_type'] : 2;
-        $remainTime = isset($params['remain_time']) && $params['remain_time']? $params['remain_time'] : 0;
-        if($isSubmit<=0 && $answerType==2 && empty($answer)){
+        $paperId = isset($params['id']) ? $params['id'] : 0;
+        $rid = isset($params['rid']) ? $params['rid'] : 0;
+        $tid = isset($params['tid']) ? $params['tid'] : 0;
+        $isSubmit = isset($params['is_submit']) ? $params['is_submit'] : 1;
+        $answer = isset($params['answer']) && $params['answer']? str_replace("\n",'\n', $params['answer']) : '';
+        $answerImage = isset($params['answer_image']) ? $params['answer_image'] : '';
+        $answerType = isset($params['answer_type']) && $params['answer_type'] ? $params['answer_type'] : 2;
+        $remainTime = isset($params['remain_time']) && $params['remain_time'] ? $params['remain_time'] : 0;
+        if ($isSubmit <= 0 && $answerType == 2 && empty($answer)) {
             $this->error = '请先提交答案';
             return false;
         }
 
-        if($isSubmit<=0 && $answerType==1 && empty($answerImage)){
+        if ($isSubmit <= 0 && $answerType == 1 && empty($answerImage)) {
             $this->error = '请先上传图片答案';
             return false;
         }
 
         $cacheKey = "caches:answers:{$userId}_{$paperId}:{$tid}_{$rid}";
-        if(RedisService::get($cacheKey.'_lock')){
+        if (RedisService::get($cacheKey . '_lock')) {
             $this->error = '请不要频繁提交';
             return false;
         }
 
-        RedisService::set($cacheKey.'_lock', $params, rand(2,3));
+        RedisService::set($cacheKey . '_lock', $params, rand(2, 3));
 
         // 试卷数据
-        $paperInfo = ExamPaperModel::where(['id'=> $paperId,'status'=>1,'mark'=>1])
+        $paperInfo = ExamPaperModel::where(['id' => $paperId, 'status' => 1, 'mark' => 1])
             ->first();
-        $type = isset($paperInfo['type']) && $paperInfo['type']? $paperInfo['type'] : 1;
-        $sceneType = isset($paperInfo['scene_type']) && $paperInfo['scene_type']? $paperInfo['scene_type'] : 1;
-        $topicCount = isset($paperInfo['topic_count'])? $paperInfo['topic_count'] : 0;
-        if(empty($paperInfo)){
-            RedisService::clear($cacheKey.'_lock');
+        $sceneType = isset($paperInfo['scene_type']) && $paperInfo['scene_type'] ? $paperInfo['scene_type'] : 1;
+        if (empty($paperInfo)) {
+            RedisService::clear($cacheKey . '_lock');
             $this->error = '试题数据错误,请返回刷新重试';
             return false;
         }
 
         // 题目数据
-        $topicInfo = ExamTopicModel::where(['id'=> $tid,'paper_id'=>$paperId,'status'=>1,'mark'=>1])->first();
-        $topicType = isset($topicInfo['topic_type'])? trim($topicInfo['topic_type']) : '';
-        $topicScore = isset($topicInfo['score'])? intval($topicInfo['score']) : 0;
-        $topicName = isset($topicInfo['topic_name'])? $topicInfo['topic_name'] : '';
-        $topicShowType = isset($topicInfo['show_type'])? $topicInfo['show_type'] : 1;
-        $correctAnswer = isset($topicInfo['correct_answer'])? $topicInfo['correct_answer'] : '';
-        if(empty($topicInfo) || empty($topicType) || empty($topicName)){
-            RedisService::clear($cacheKey.'_lock');
+        $topicInfo = ExamTopicModel::where(['id' => $tid, 'paper_id' => $paperId, 'status' => 1, 'mark' => 1])->first();
+        $topicType = isset($topicInfo['topic_type']) ? trim($topicInfo['topic_type']) : '';
+        $topicScore = isset($topicInfo['score']) ? intval($topicInfo['score']) : 0;
+        $topicName = isset($topicInfo['topic_name']) && $topicInfo['topic_name']? str_replace("\n",'\n', $topicInfo['topic_name']) : '';
+        $topicShowType = isset($topicInfo['show_type']) ? $topicInfo['show_type'] : 1;
+        $correctAnswer = isset($topicInfo['correct_answer']) ? $topicInfo['correct_answer'] : '';
+        if (empty($topicInfo) || empty($topicType) || empty($topicName)) {
+            RedisService::clear($cacheKey . '_lock');
             $this->error = '题库已更新,请返回刷新重试';
             return false;
         }
 
         // 答题记录
         $submit = 0;
+        $answerTimes = 0;
         $answerCount = 0;
-        if($rid){
-            $answerInfo = ExamAnswerModel::where(['id'=>$rid,'status'=>1,'mark'=>1])->first();
-            $submit = isset($answerInfo['is_submit'])? $answerInfo['is_submit'] : 0;
-            $answerCount = isset($answerInfo['answer_count'])? $answerInfo['answer_count'] : 0;
-            if(empty($answerInfo)){
+        if ($rid) {
+            $answerInfo = ExamAnswerModel::where(['id' => $rid, 'user_id' => $userId, 'status' => 1, 'mark' => 1])->first();
+            $submit = isset($answerInfo['is_submit']) ? $answerInfo['is_submit'] : 0;
+            $answerCount = isset($answerInfo['answer_count']) ? $answerInfo['answer_count'] : 0;
+            $answerTimes = isset($answerInfo['answer_times']) ? $answerInfo['answer_times'] : 0;
+            if (empty($answerInfo)) {
                 $rid = 0;
             }
 
-            if($submit){
+            if ($submit) {
+                RedisService::clear($cacheKey . '_lock');
                 $this->error = '您已交卷';
                 return false;
             }
@@ -425,55 +429,50 @@ class ExamService extends BaseService
 
         // 验证答案内容类型和数据
         // 每日一练
-        if($sceneType == 1 && $rid<=0){
+        if ($sceneType == 1 && $rid <= 0) {
             // 今日记录
-            $answerInfo = ExamAnswerModel::where(['paper_id'=>$paperId,'status'=>1,'mark'=>1])->where('create_time','>=', strtotime(date('Y-m-d')))->first();
-            $rid = isset($answerInfo['id'])? $answerInfo['id'] : 0;
-            $submit = isset($answerInfo['is_submit'])? $answerInfo['is_submit'] : 0;
-            $answerCount = isset($answerInfo['answer_count'])? $answerInfo['answer_count'] : 0;
+            $answerInfo = ExamAnswerModel::where(['paper_id' => $paperId, 'status' => 1, 'mark' => 1])->where('create_time', '>=', strtotime(date('Y-m-d')))->first();
+            $rid = isset($answerInfo['id']) ? $answerInfo['id'] : 0;
+            $submit = isset($answerInfo['is_submit']) ? $answerInfo['is_submit'] : 0;
+            $answerCount = isset($answerInfo['answer_count']) ? $answerInfo['answer_count'] : 0;
         }
 
         // 是否已交卷
-        if($submit == 1){
-            RedisService::clear($cacheKey.'_lock');
+        if ($submit == 1) {
+            RedisService::clear($cacheKey . '_lock');
             $this->error = '您已交卷';
             return false;
         }
 
         // 直接交卷
         $totalTime = ConfigService::make()->getConfigByCode('answer_total_time', 1800);
-        $answerTime = $totalTime >$remainTime? $totalTime-$remainTime : $totalTime;
-        if($isSubmit==1 && empty($answer) && empty($answerImage)){
+        $answerTime = $totalTime > $remainTime ? $totalTime - $remainTime : $totalTime;
+        $newAnswerTime = $answerTime > $answerTimes ? $answerTime - $answerTimes : 0;
+        if ($isSubmit == 1 && empty($answer) && empty($answerImage)) {
             // 是否提交过
-            if($answerCount<=0){
+            if ($answerCount <= 0) {
+                RedisService::clear($cacheKey . '_lock');
                 $this->error = '您未提交过答案,请先答题再交卷';
                 return false;
             }
 
-            $this->model->where(['id'=> $rid])->update(['is_submit'=>1,'update_time'=>time()]);
+            $this->model->where(['id' => $rid])->update(['is_submit' => 1, 'update_time' => time()]);
             $this->error = '交卷成功';
 
-            // 答题时间统计
-            $id = MemberAnswerRankModel::where(['user_id'=> $userId,'date'=>date('Y-m-d'),'mark'=>1])->value('id');
-            if($id){
-                MemberAnswerRankModel::where(['id'=> $id])->update(['answer_time'=>DB::raw("answer_time + {$answerTime}"),'answer_count'=>DB::raw("answer_count + {$answerCount}"),'update_time'=>time()]);
-            }else{
-                MemberAnswerRankModel::insert(['user_id'=>$userId,'answer_time'=>$answerTime,'answer_count'=> $answerCount,'date'=>date('Y-m-d'),'create_time'=>time()]);
-            }
-
             RedisService::keyDel("caches:exams:{$userId}*");
-            return ['rid'=> $rid,'paper_id'=>$paperId];
+            RedisService::clear($cacheKey . '_lock');
+            return ['rid' => $rid, 'paper_id' => $paperId];
         }
 
         // 该题是否已提交答案
         $logId = 0;
-        if($rid>0){
-            $answerTopic = ExamAnswerTopicModel::where(['answer_log_id'=>$rid,'topic_id'=> $tid,'mark'=>1])->first();
-            $accurate = isset($answerTopic['accurate'])? $answerTopic['accurate'] : -1;
-            $logId = isset($answerTopic['id'])? $answerTopic['id'] : 0;
-            $status = isset($answerTopic['status'])? $answerTopic['status'] : 0;
-            if($answerTopic && $accurate>=0 && $status == 1){
-                RedisService::clear($cacheKey.'_lock');
+        if ($rid > 0) {
+            $answerTopic = ExamAnswerTopicModel::where(['answer_log_id' => $rid, 'user_id' => $userId, 'topic_id' => $tid, 'mark' => 1])->first();
+            $accurate = isset($answerTopic['accurate']) ? $answerTopic['accurate'] : -1;
+            $logId = isset($answerTopic['id']) ? $answerTopic['id'] : 0;
+            $status = isset($answerTopic['status']) ? $answerTopic['status'] : 0;
+            if ($answerTopic && $accurate >= 0 && $status == 1) {
+                RedisService::clear($cacheKey . '_lock');
                 $this->error = '该题答案已提交';
                 return false;
             }
@@ -482,105 +481,104 @@ class ExamService extends BaseService
         /* TODO 验证答案 */
         $submitType = 1;
         $topicData = [
-            'user_id'=> $userId,
-            'topic_id'=> $tid,
-            'answer'=> $answerType==1? get_image_path($answer) : $answer,
-            'score'=>0,
-            'accurate'=>0,
-            'create_time'=> time(),
-            'status'=> 1,
-            'mark'=> 1,
+            'user_id' => $userId,
+            'topic_id' => $tid,
+            'answer' => $answerType == 1 ? get_image_path($answer) : $answer,
+            'score' => 0,
+            'accurate' => 0,
+            'create_time' => time(),
+            'status' => 1,
+            'mark' => 1,
         ];
-        if(in_array($topicType,['选择题','单选题','多选题','填空题'])){
-            $topicData['answer_analysis'] = isset($topicInfo['answer_analysis'])? $topicInfo['answer_analysis'] : '';
-            if($answer == $correctAnswer){
+        if (in_array($topicType, ['选择题', '单选题', '多选题', '填空题'])) {
+            $topicData['answer_analysis'] = isset($topicInfo['answer_analysis']) ? $topicInfo['answer_analysis'] : '';
+            if ($answer == $correctAnswer) {
                 $topicData['accurate'] = 1;
                 $topicData['score'] = $topicScore;
-            }else{
+            } else {
                 $topicData['accurate'] = 0;
             }
 //      }else if (in_array($topicType, ['简答题','计算题','阅读理解'])){
-        }else {
+        } else {
             // 图片答案AI验证
-            if($answerType == 1){
-                if(empty($answerImage)){
+            if ($answerType == 1) {
+                if (empty($answerImage)) {
                     $this->error = '请上传图片答案~';
                     return false;
                 }
 
                 $submitType = 3;
                 $apiData = [
-                    'answer'=> DeepSeekService::make()->getImageTopicData($answerImage),
-                    'score'=> $topicScore,
-                    'topic'=> $topicShowType == 2 && $topicName? DeepSeekService::make()->getImageTopicData($topicName) : $topicName,
+                    'answer' => DeepSeekService::make()->getImageTopicData($answerImage),
+                    'score' => $topicScore,
+                    'topic' => $topicShowType == 2 && $topicName ? DeepSeekService::make()->getImageTopicData($topicName) : $topicName,
                 ];
 
-                var_dump($apiData);
 
                 $result = DeepSeekService::make()->apiRequest($apiData);
-                RedisService::clear($cacheKey.'_lock');
+                RedisService::clear($cacheKey . '_lock');
                 $this->error = '功能开放中~';
                 return false;
-            }else {
+            } else {
                 $submitType = 2;
                 $apiData = [
-                    'answer'=> $answer,
-                    'score'=> $topicScore,
-                    'topic'=> $topicShowType == 2 && $topicName? DeepSeekService::make()->getImageTopicData($topicName) : $topicName,
+                    'answer' => $answer,
+                    'score' => $topicScore,
+                    'topic' => $topicShowType == 2 && $topicName ? DeepSeekService::make()->getImageTopicData($topicName) : $topicName,
                 ];
 
                 $result = DeepSeekService::make()->apiRequest($apiData);
-                $score = isset($result['score'])?$result['score'] : 0;
-                $analysis = isset($result['analysis'])?$result['analysis'] :  '';
-                if($score>0){
+                $score = isset($result['score']) ? $result['score'] : 0;
+                $analysis = isset($result['analysis']) ? $result['analysis'] : '';
+                if ($score > 0) {
                     $topicData['accurate'] = 1;
                     $topicData['score'] = $score;
                     $topicData['answer_analysis'] = $analysis;
-                }else{
+                } else {
                     $topicData['accurate'] = 0;
                 }
             }
         }
 
         DB::beginTransaction();
-        if($rid){
+        if ($rid) {
             $log = [
-                'answer_times'=> $answerTime,
-                'answer_last_at'=> date('Y-m-d H:i:s'),
-                'answer_count'=> DB::raw("answer_count + 1"),
-                'answer_last_id'=> $tid,
-                'is_submit'=> $isSubmit==1?1:0,
-                'update_time'=> time()
+                'answer_times' => $answerTime,
+                'answer_last_at' => date('Y-m-d H:i:s'),
+                'answer_count' => DB::raw("answer_count + 1"),
+                'answer_last_id' => $tid,
+                'is_submit' => $isSubmit == 1 ? 1 : 0,
+                'update_time' => time()
             ];
 
             // 对题数
-            if($topicData['accurate']==1){
+            if ($topicData['accurate'] == 1) {
                 $log['accurate_count'] = DB::raw("accurate_count + 1");
                 $log['score'] = DB::raw("score + {$topicData['score']}");
             }
 
-            if(!$this->model->where(['id'=> $rid])->update($log)){
+            if (!$this->model->where(['id' => $rid])->update($log)) {
                 DB::rollBack();
-                RedisService::clear($cacheKey.'_lock');
+                RedisService::clear($cacheKey . '_lock');
                 $this->error = '答题失败,请刷新后重新提交';
                 return false;
             }
-        }else{
+        } else {
             $log = [
-                'user_id'=> $userId,
-                'paper_id'=> $paperId,
-                'score'=> $topicData['score'],
-                'answer_count'=> 1,
-                'answer_times'=> $answerTime,
-                'answer_last_at'=> date('Y-m-d H:i:s'),
-                'answer_last_id'=> $tid,
-                'is_submit'=> $isSubmit,
-                'create_time'=> time()
+                'user_id' => $userId,
+                'paper_id' => $paperId,
+                'score' => $topicData['score'],
+                'answer_count' => 1,
+                'answer_times' => $answerTime,
+                'answer_last_at' => date('Y-m-d H:i:s'),
+                'answer_last_id' => $tid,
+                'is_submit' => $isSubmit,
+                'create_time' => time()
             ];
 
-            if(!$rid = $this->model->insertGetId($log)){
+            if (!$rid = $this->model->insertGetId($log)) {
                 DB::rollBack();
-                RedisService::clear($cacheKey.'_lock');
+                RedisService::clear($cacheKey . '_lock');
                 $this->error = '答题失败,请刷新后重新提交';
                 return false;
 
@@ -590,12 +588,12 @@ class ExamService extends BaseService
         $topicData['answer_type'] = $submitType;
         $topicData['answer_log_id'] = $rid;
 
-        if($logId){
+        if ($logId) {
             $topicData['update_time'] = $submitType;
-            ExamAnswerTopicModel::where(['id'=> $logId])->update($topicData);
-        }else if (!ExamAnswerTopicModel::insert($topicData)){
+            ExamAnswerTopicModel::where(['id' => $logId])->update($topicData);
+        } else if (!ExamAnswerTopicModel::insert($topicData)) {
             DB::rollBack();
-            RedisService::clear($cacheKey.'_lock');
+            RedisService::clear($cacheKey . '_lock');
             $this->error = '答题失败,请刷新后重新提交';
             return false;
         }
@@ -603,22 +601,24 @@ class ExamService extends BaseService
         DB::commit();
 
         // 答题时间统计
-        if($isSubmit==1){
-            $id = MemberAnswerRankModel::where(['user_id'=> $userId,'date'=>date('Y-m-d'),'mark'=>1])->value('id');
-            if($id){
-                $answerCount++;
-                MemberAnswerRankModel::where(['id'=> $id])->update(['answer_time'=>DB::raw("answer_time + {$answerTime}"),'answer_count'=>DB::raw("answer_count + {$answerCount}"),'update_time'=>time()]);
-            }else{
-                MemberAnswerRankModel::insert(['user_id'=>$userId,'answer_time'=>$answerTime,'answer_count'=> $answerCount,'date'=>date('Y-m-d'),'create_time'=>time()]);
+        if ($newAnswerTime > 0) {
+            $id = MemberAnswerRankModel::where(['user_id' => $userId, 'date' => date('Y-m-d'), 'mark' => 1])->value('id');
+            if ($id) {
+                MemberAnswerRankModel::where(['id' => $id])->update(['answer_time' => DB::raw("answer_time + {$newAnswerTime}"), 'answer_count' => DB::raw("answer_count + 1"), 'update_time' => time()]);
+            } else {
+                MemberAnswerRankModel::insert(['user_id' => $userId, 'answer_time' => $answerTime, 'answer_count' => 1, 'date' => date('Y-m-d'), 'create_time' => time()]);
             }
         }
 
+
         $this->error = '答题成功';
-        RedisService::clear($cacheKey.'_lock');
-        RedisService::keyDel("caches:exams:{$userId}*");
+        RedisService::clear($cacheKey . '_lock');
         RedisService::clear("caches:exams:info_{$rid}");
+        RedisService::keyDel("caches:exams:ranks*");
+        RedisService::keyDel("caches:exams:{$userId}*");
+        RedisService::keyDel("caches:paper:list*");
         RedisService::keyDel("caches:paper:info_{$userId}:p{$paperId}*");
-        return ['paper_id'=> $paperId,'rid'=>$rid,'tid'=>$tid, 'accurate'=> $topicData['accurate'],'answer'=>$topicData['answer']];
+        return ['paper_id' => $paperId, 'rid' => $rid, 'tid' => $tid, 'accurate' => $topicData['accurate'], 'score' => $topicData['score']];
     }
 
     /**
@@ -631,11 +631,11 @@ class ExamService extends BaseService
     {
         $cacheKey = "caches:answers:topic_{$rid}_{$tid}";
         $data = RedisService::get($cacheKey);
-        if($data){
+        if ($data) {
             return $data;
         }
-        $data = ExamAnswerTopicModel::where(['rid'=> $rid,'topic_id'=>$tid,'mark'=>1])->value('id');
-        if($data){
+        $data = ExamAnswerTopicModel::where(['rid' => $rid, 'topic_id' => $tid, 'mark' => 1])->value('id');
+        if ($data) {
             RedisService::set($cacheKey, $data, rand(5, 10));
         }
 
@@ -651,19 +651,19 @@ class ExamService extends BaseService
     {
         $cacheKey = "caches:exams:info_{$rid}";
         $data = RedisService::get($cacheKey);
-        if($data){
+        if ($data) {
             return $data;
         }
 
         $data = $this->model->from('exam_answers as a')
-            ->leftJoin('exam_papers as b','b.id','=','a.paper_id')
-            ->where(['a.id'=> $rid,'a.status'=>1,'a.mark'=>1])
-            ->select(['a.*','b.topic_count'])
+            ->leftJoin('exam_papers as b', 'b.id', '=', 'a.paper_id')
+            ->where(['a.id' => $rid, 'a.status' => 1, 'a.mark' => 1])
+            ->select(['a.*', 'b.topic_count', 'b.scene_type'])
             ->first();
-        $data = $data? $data->toArray() : [];
-        if($data){
-            $data['accurate_rate'] = round($data['accurate_count']/$data['topic_count'] * 100,2);
-            $data['answer_times_text'] = $data['answer_times']? format_times($data['answer_times']) : '00:00';
+        $data = $data ? $data->toArray() : [];
+        if ($data) {
+            $data['accurate_rate'] = round($data['accurate_count'] / $data['topic_count'] * 100, 2);
+            $data['answer_times_text'] = $data['answer_times'] ? format_times($data['answer_times']) : '00:00';
             RedisService::set($cacheKey, $data, rand(5, 10));
         }
 

+ 91 - 13
app/Services/Api/PaperService.php

@@ -13,6 +13,7 @@ namespace App\Services\Api;
 
 use App\Models\ExamAnswerModel;
 use App\Models\ExamAnswerTopicModel;
+use App\Models\ExamErrorModel;
 use App\Models\ExamPaperModel;
 use App\Models\ExamTopicModel;
 use App\Services\BaseService;
@@ -64,9 +65,11 @@ class PaperService extends BaseService
         if($datas){
             return $datas;
         }
-
+        $userId = isset($params['user_id'])? $params['user_id'] : 0;
         $query = $this->getQuery($params);
-        $list = $query->select(['a.*','b.id as rid','b.score','b.accurate_count','b.answer_times','b.answer_count','b.is_submit'])
+        $list = $query->with(['answer'=>function($query) use($userId){
+            $query->where('user_id', $userId);
+        }])->where('a.topic_count','>', 0)->select(['a.*','b.id as rid', 'b.score', 'b.accurate_count','b.user_id', 'b.answer_times', 'b.answer_count', 'b.is_submit'])
             ->orderBy('a.create_time','desc')
             ->paginate($pageSize > 0 ? $pageSize : 9999999);
         $list = $list? $list->toArray() :[];
@@ -119,11 +122,11 @@ class PaperService extends BaseService
         if($subjectId>0){
             $where['a.subject_id'] = $subjectId;
         }
-
+        $userId = isset($params['user_id'])? $params['user_id'] : 0;
         return $this->model->from('exam_papers as a')
-            ->leftJoin('exam_answers as b', function($join){
+            ->leftJoin('exam_answers as b', function($join) use($userId){
                 // 未完成交卷的答题数据
-                $join->on('b.paper_id','=','a.id')->where(['b.is_submit'=>0]);
+                $join->on('b.paper_id','=','a.id')->where(['b.user_id'=>$userId,'b.status'=>1,'b.mark'=>1]);
             })
             ->where($where)
             ->where(function ($query) use($params){
@@ -222,11 +225,11 @@ class PaperService extends BaseService
         }
         $where = ['a.id'=> $paperId,'a.status'=>1,'a.mark'=>1];
         $info = $this->model->from('exam_papers as a')
-            ->leftJoin('exam_answers as b',function($join) use($rid){
-                $join->on('b.paper_id','=','a.id')->where(['b.id'=>$rid,'b.status'=>1]);
+            ->leftJoin('exam_answers as b',function($join) use($rid,$userId){
+                $join->on('b.paper_id','=','a.id')->where(['b.id'=>$rid,'b.user_id'=>$userId,'b.status'=>1]);
             })
             ->where($where)
-            ->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'])
+            ->select(['a.id as paper_id','b.id as rid','b.score','b.accurate_count','b.is_submit','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'])
             ->first();
         $info = $info? $info->toArray() : [];
 
@@ -239,18 +242,20 @@ class PaperService extends BaseService
             $info['rid'] = isset($info['rid'])? intval($info['rid']) : 0;
 
             // 剩余时间
+            $isSubmit = isset($info['is_submit'])? $info['is_submit'] : 0;
             $totalTime = isset($info['total_time'])? intval($info['total_time']) : 0;
             $totalTime = $totalTime>0? $totalTime : ConfigService::make()->getConfigByCode('answer_total_time', 1800);
             $info['answer_times'] = isset($info['answer_times'])? intval($info['answer_times']) : 0;
             $info['remain_time'] = $totalTime>$info['answer_times']? $totalTime-$info['answer_times'] : 0;
+            $info['remain_time'] = $isSubmit? 0 : $info['remain_time'];
             $info['remain_time_text'] = $info['remain_time']? format_times($info['remain_time']) : '00:00';
-            $info['progress'] = intval($info['answer_count']/$info['topic_count'] * 100);
+            $info['progress'] = $info['topic_count']?intval($info['answer_count']/$info['topic_count'] * 100) : 0;
             // 当前题目
             //$prefix = env('DB_PREFIX','_lev');
             $model = ExamTopicModel::from('exam_topics as a')
-                ->leftJoin('exam_answers_topics as b', function($join) use($rid){
+                ->leftJoin('exam_answers_topics as b', function($join) use($rid, $userId){
                     // 是否有最近答题记录
-                    $join->on('b.topic_id','=',"a.id")->where("b.answer_log_id",'=', $rid)->where(['b.status'=>1,"b.mark"=> 1]);
+                    $join->on('b.topic_id','=',"a.id")->where("b.answer_log_id",'=', $rid)->where(['b.user_id'=>$userId,'b.status'=>1,"b.mark"=> 1]);
                 })
                 ->where(['a.paper_id'=> $paperId,'a.status'=>1,'a.mark'=>1]);
             $model1 = clone  $model;
@@ -268,7 +273,7 @@ class PaperService extends BaseService
             }else{
                 $model = $model1;
             }
-            $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'])
+            $info['topic'] = $model->select(['a.*','b.id as answer_topic_id','b.answer_log_id','b.answer as submit_answer','b.answer_analysis','b.answer_type as submit_answer_type','b.score as submit_score','b.accurate'])
                 ->orderBy('a.sort','desc')
                 ->orderBy('a.id','asc')
                 ->first();
@@ -276,6 +281,9 @@ class PaperService extends BaseService
             $topicId = isset($info['topic']['id'])? $info['topic']['id'] : 0;
             if($info['topic'] && $topicId) {
                 $info['topic']['accurate'] = isset($info['topic']['accurate'])?$info['topic']['accurate'] : -1;
+                $info['topic']['topic_analysis'] = isset($info['topic']['topic_analysis'])?$info['topic']['topic_analysis'] : '';
+                $info['topic']['answer_analysis'] = isset($info['topic']['answer_analysis'])?$info['topic']['answer_analysis'] : '';
+                $info['topic']['topic_analysis'] = $info['topic']['topic_analysis']?$info['topic']['topic_analysis'] : $info['topic']['answer_analysis'];
                 $info['topic']['submit_answer'] = isset($info['topic']['submit_answer'])?$info['topic']['submit_answer'] : '';
                 $info['topic']['submit_answer_type'] = isset($info['topic']['submit_answer_type'])?$info['topic']['submit_answer_type'] : 1;
                 $info['topic']['submit_answers'] = '';
@@ -284,6 +292,8 @@ class PaperService extends BaseService
                     $info['topic']['submit_answer'] = get_image_url($info['topic']['submit_answer']);
                 }else if($info['topic']['submit_answer_type'] == 4){
                     $info['topic']['submit_answer'] = $info['topic']['submit_answer']?json_decode($info['topic']['submit_answer'], true):[];
+                }else{
+                    $info['topic']['submit_answer'] = format_content($info['topic']['submit_answer']);
                 }
 
                 if($info['topic']['show_type'] == 1){
@@ -388,7 +398,6 @@ class PaperService extends BaseService
         return $info;
     }
 
-
     /**
      * 获取随机试卷
      * @param $userId 用户
@@ -422,4 +431,73 @@ class PaperService extends BaseService
 
         return $data;
     }
+
+    /**
+     * 纠错
+     * @param $userId
+     * @param $params
+     * @return bool
+     */
+    public function submitError($userId, $params)
+    {
+        $id = isset($params['id'])? $params['id'] : 0;
+        $type = isset($params['type'])? $params['type'] : 0;
+        $moduleType = isset($params['module_type'])? $params['module_type'] : 1;
+        $description = isset($params['description'])? $params['description'] : '';
+        if($id<=0 || $moduleType<=0){
+            $this->error = '参数错误';
+            return false;
+        }
+
+
+        if($type<=0){
+            $this->error = '请选择错误类型';
+            return false;
+        }
+
+        if(empty($description)){
+            $this->error = '请输入错误描述';
+            return false;
+        }
+
+        $limitTime = ConfigService::make()->getConfigByCode('limit_exam_error_submit', 5);
+        $cacheKey = "caches:paper:errors_{$userId}:{$id}";
+        if(RedisService::get($cacheKey) && $limitTime>0){
+            $this->error = '您近期已提交过该题错误,请稍后再试~';
+            return false;
+        }
+
+        $paperInfo = $this->model->where(['id' => $id, 'mark' => 1])->first();
+        if (empty($paperInfo)) {
+            $this->error = '试题不存在';
+            return false;
+        }
+
+        if($limitTime>0 && $logId = ExamErrorModel::where(['user_id'=> $userId,'paper_id'=> $id,'mark'=>1])->where('create_time','>=', time() - $limitTime* 60)->value('id')){
+            $this->error = '您近期已提交过该题错误,请稍后再试~';
+            RedisService::set($cacheKey, ['id'=> $logId,'paper_id'=>$id], $limitTime* 60);
+            return false;
+        }
+
+        $data = [
+            'user_id'=> $userId,
+            'paper_id'=> $id,
+            'type'=> $type,
+            'module_type'=> $moduleType,
+            'description'=> $description,
+            'image_url'=> isset($params['image_url']) && $params['image_url']? get_image_path($params['image_url']) : '',
+            'create_time'=>time(),
+            'status'=>1,
+            'mark'=>1
+        ];
+
+        if(!$logId = ExamErrorModel::insertGetId($data)){
+            $this->error = '提交失败';
+            return false;
+        }
+
+        $this->error = '提交成功';
+        RedisService::set($cacheKey, $data, $limitTime* 60);
+        return ['id'=> $logId, 'paper_id'=> $id];
+    }
 }

+ 29 - 4
app/Services/DeepSeekService.php

@@ -33,6 +33,7 @@ class DeepSeekService extends BaseService
 
     public function __construct()
     {
+        set_time_limit(0);
         $this->apiUrl = ConfigService::make()->getConfigByCode('dk_base_url');
         $this->apiKey = ConfigService::make()->getConfigByCode('dk_api_key');
         $this->apiName = ConfigService::make()->getConfigByCode('dk_api_name');
@@ -60,6 +61,7 @@ class DeepSeekService extends BaseService
      */
     public function apiRequest($params, $model='deepseek-chat')
     {
+
         if(empty($this->apiUrl) || empty($this->apiKey) || empty($this->apiName)){
             $this->error = 'AI接口参数未配置';
             return false;
@@ -73,8 +75,8 @@ class DeepSeekService extends BaseService
         $answer = isset($params['answer'])? $params['answer'] : '';
         $topic = isset($params['topic'])? $params['topic'] : '';
         $score = isset($params['score'])? $params['score'] : 0;
-        $content = "你是一个答题高手";
-        $message = "题目为【{$topic}】的答案【{$answer}】总分共{$score}分能得多少分?请返回针对该题目的包含score字段评分、topic字段题目内容以及analyze题目解析字段的结果";
+        $content = '你是一个答题高手,请给描述中问题的答案评分,并尝试按 JSON:{"score": 8,"topic":"this is topic content","analysis":"this is topic analysis"}返回';
+        $message = "请给题目【{$topic}】总分{$score}的答案【{$answer}】评分?";
         $data = [
             'model'=> $model,
             'messages'=> [
@@ -89,18 +91,28 @@ class DeepSeekService extends BaseService
             ],
             'stream' => false, //false 非流  true//流返回,需要前端追加和保持长连接
             'response_format'=>[
-                'type'=>'text' //返回格式(text,json_object)
+                'type'=>'json_object' //返回格式(text,json_object)
             ],
             "max_tokens"=>2048, //最大返回token数
         ];
 
 
         $url = $this->apiUrl.$this->apiUrls[$model];
-        $result = aiRequest($url, json_encode($data), 20, $headers);
+        $this->saveLog("caches:dkApi:{$model}:request_1_".date('YmdHis'), ['url'=>$url,'data'=>$data]);
+        $result = aiRequest($url, json_encode($data), 60, $headers);
+        $result = $result? json_decode(trim($result),true) : [];
+        if(empty($result)){
+            $this->error = '答案验证失败';
+            return false;
+        }
+        $this->saveLog("caches:dkApi:{$model}:request_2_".date('YmdHis'), ['url'=>$url,'data'=>$data,'result'=>$result]);
+
         $choices = isset($result['choices'])? $result['choices'] : [];
         $choiceData = isset($choices[0]['message'])? $choices[0]['message'] : [];
         $choiceContent = isset($choiceData['content'])? $choiceData['content'] : '';
         $content = $choiceContent? str_replace('\n','', $choiceContent) : '';
+        $content = $content? preg_replace("/^```json/",'', $content) : '';
+        $content = $content? rtrim($content,'```') : '';
         $content = $content? json_decode($content, true) : [];
         return $content;
     }
@@ -167,4 +179,17 @@ var_dump($result);
 
         return $imageUrl;
     }
+
+    /**
+     * 保存缓存日志
+     * @param $cacheKey
+     * @param $message
+     * @param $expireTime
+     */
+    public function saveLog($cacheKey, $message, $expireTime=0, $open=false)
+    {
+        if(env('APP_DEBUG') || $open){
+            RedisService::set($cacheKey, $message, $expireTime||$this->expireTime);
+        }
+    }
 }

+ 16 - 3
config/platform.php

@@ -23,13 +23,13 @@ return [
             "id" => 98,
             "name" => '智能问答',
             "code" => 'intelligent-answer',
-            'page' => '/pages/custom/intelligent'
+            'page' => '/pages/custom/index'
         ],
         [
             "id" => 5,
             "name" => '复习资料',
             "code" => 'review-materials',
-            'page' => '/pages/exam/index?st=5'
+            'page' => '/pages/exam/material'
         ],
         [
             "id" => 4,
@@ -47,7 +47,20 @@ return [
             "id" => 99,
             "name" => '院校直达',
             "code" => 'institution-entry',
-            'page' => 'http://www.baidu.com'
+            'page' => '/pages/institution/index'
         ],
+    ],
+    // 纠错类型
+    "errors"=>[
+        ['id'=>1,'name'=>'音/视频无法播放'],
+        ['id'=>2,'name'=>'音/视频错误'],
+        ['id'=>3,'name'=>'图片缺少'],
+        ['id'=>4,'name'=>'图片错误'],
+        ['id'=>5,'name'=>'选项错误'],
+        ['id'=>6,'name'=>'有错别字'],
+        ['id'=>7,'name'=>'知识点错误'],
+        ['id'=>8,'name'=>'解析错误'],
+        ['id'=>9,'name'=>'答案错误'],
+        ['id'=>10,'name'=>'题干错误'],
     ]
 ];

+ 3 - 0
routes/api.php

@@ -79,6 +79,7 @@ Route::prefix('v1')->middleware('web.login')->group(function() {
     Route::post('/exam/info', [\App\Http\Controllers\Api\v1\ExamController::class, 'info']);
     Route::post('/exam/cards', [\App\Http\Controllers\Api\v1\ExamController::class, 'cards']);
 
+
     // 每日一练
     Route::post('/practice/index', [\App\Http\Controllers\Api\v1\ExamController::class, 'practice']);
 
@@ -86,11 +87,13 @@ Route::prefix('v1')->middleware('web.login')->group(function() {
     Route::post('/paper/index', [\App\Http\Controllers\Api\v1\PaperController::class, 'index']);
     Route::post('/paper/info', [\App\Http\Controllers\Api\v1\PaperController::class, 'info']);
     Route::post('/paper/subject', [\App\Http\Controllers\Api\v1\PaperController::class, 'subject']);
+    Route::post('/paper/error', [\App\Http\Controllers\Api\v1\PaperController::class, 'error']);
 
     // 视频课
     Route::post('/course/index', [\App\Http\Controllers\Api\v1\CourseController::class, 'index']);
     Route::post('/course/list', [\App\Http\Controllers\Api\v1\CourseController::class, 'list']);
     Route::post('/course/info', [\App\Http\Controllers\Api\v1\CourseController::class, 'info']);
+    Route::post('/course/learn', [\App\Http\Controllers\Api\v1\CourseController::class, 'learn']);
 
 
 });