Browse Source

feat(server): 模考出题

Go 5 years ago
parent
commit
a13cfe59ed
20 changed files with 1084 additions and 153 deletions
  1. 12 0
      front/project/Constant.js
  2. 15 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/QuestionSubject.java
  3. 25 0
      server/data/src/main/java/com/qxgmat/data/constants/enums/QuestionType.java
  4. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/QuestionNo.java
  5. 2 1
      server/data/src/main/java/com/qxgmat/data/dao/mapping/QuestionNoMapper.xml
  6. 14 0
      server/data/src/main/java/com/qxgmat/data/relation/QuestionNoRelationMapper.java
  7. 33 0
      server/data/src/main/java/com/qxgmat/data/relation/entity/QuestionDifficultRelation.java
  8. 61 3
      server/data/src/main/java/com/qxgmat/data/relation/mapping/QuestionNoRelationMapper.xml
  9. 3 3
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/ExaminationController.java
  10. 8 1
      server/gateway-api/src/main/java/com/qxgmat/controller/api/QuestionController.java
  11. 19 1
      server/gateway-api/src/main/java/com/qxgmat/service/UserQuestionService.java
  12. 482 35
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ExaminationService.java
  13. 244 24
      server/gateway-api/src/main/java/com/qxgmat/service/extend/QuestionFlowService.java
  14. 46 12
      server/gateway-api/src/main/java/com/qxgmat/service/extend/ToolsService.java
  15. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/inline/ExaminationPaperService.java
  16. 0 60
      server/gateway-api/src/main/java/com/qxgmat/service/inline/ExaminationStructService.java
  17. 81 6
      server/gateway-api/src/main/java/com/qxgmat/service/inline/QuestionNoService.java
  18. 1 3
      server/gateway-api/src/main/java/com/qxgmat/service/inline/SentencePaperService.java
  19. 1 3
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookPaperService.java
  20. 1 0
      server/tools/src/main/java/com/nuliji/tools/AbstractService.java

+ 12 - 0
front/project/Constant.js

@@ -117,6 +117,18 @@ export const ExaminationOrder = [{ list: ['awa', 'ir', 'quant', 'verbal'] }, { l
 // 模考题目数:/base/examination/number返回
 // const examinationNumber = { verbial: { number: '36', time: '65' }, ir: { number: '12', time: '30' }, awa: { number: '1', time: '30' }, quant: { number: '31', time: '62' } };
 
+// 模考设置
+// const examinationSetting = {
+//   order: [],
+//   disorder: true,
+//   stage: '', // 当前阶段
+//   time: { verbial: 0 }, // 每个阶段时间
+//   number: { verbial: 0 }, // 每个阶段做题数
+//   verbial: { steps: [{ ids: [], level: 0 }] }, // 语文出题逻辑
+//   quant: { steps: [{ ids: [], level: 0 }] }, // 数学出题逻辑
+//   rc: { 5: 8 }, // 随机出题时,rc题的对应序号
+// };
+
 // 模考卷子分数
 // const examinationScore = {
 //   total: 123, totalRank:12, quant: 123, quantRank: 123, verbial: 123, verbialRank: 123, ir: 123, irRank: 123,

+ 15 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/QuestionSubject.java

@@ -45,4 +45,19 @@ public enum QuestionSubject {
                 return null;
         }
     }
+
+    /**
+     * 判断学科是否支持适应性算法
+     * @param subject
+     * @return
+     */
+    public static Boolean SupportAdapt(QuestionSubject subject){
+        switch (subject){
+            case QUANT:
+            case VERBAL:
+                return true;
+            default:
+                return false;
+        }
+    }
 }

+ 25 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/QuestionType.java

@@ -1,5 +1,10 @@
 package com.qxgmat.data.constants.enums;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
 /**
  * Created by gaojie on 2017/11/19.
  */
@@ -17,6 +22,11 @@ public enum QuestionType {
 
     final static public String message = "题目类型";
 
+    static private List<String> verbal = Arrays.asList(SC.key, CR.key); // 不包含rc,rc题都是直接查询
+    static private List<String> quant = Arrays.asList(PS.key, DS.key);
+    static private List<String> ir = Collections.singletonList(IR.key);
+    static private List<String> awa = Collections.singletonList(AWA.key);
+
     public String key;
     public String title;
     private QuestionType(String key, String title){
@@ -28,4 +38,19 @@ public enum QuestionType {
         if (name == null) return null;
         return QuestionType.valueOf(name.toUpperCase());
     }
+
+    public static List<String> FromSubject(QuestionSubject subject){
+        switch (subject){
+            case VERBAL:
+                return verbal;
+            case QUANT:
+                return quant;
+            case IR:
+                return ir;
+            case AWA:
+                return awa;
+            default:
+                return null;
+        }
+    }
 }

+ 35 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/QuestionNo.java

@@ -68,6 +68,12 @@ public class QuestionNo implements Serializable {
     @Column(name = "`relation_question`")
     private int[] relationQuestion;
 
+    /**
+     * 关联题目数量
+     */
+    @Column(name = "`relation_number`")
+    private Integer relationNumber;
+
     private static final long serialVersionUID = 1L;
 
     /**
@@ -260,6 +266,24 @@ public class QuestionNo implements Serializable {
         this.relationQuestion = relationQuestion;
     }
 
+    /**
+     * 获取关联题目数量
+     *
+     * @return relation_number - 关联题目数量
+     */
+    public Integer getRelationNumber() {
+        return relationNumber;
+    }
+
+    /**
+     * 设置关联题目数量
+     *
+     * @param relationNumber 关联题目数量
+     */
+    public void setRelationNumber(Integer relationNumber) {
+        this.relationNumber = relationNumber;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -277,6 +301,7 @@ public class QuestionNo implements Serializable {
         sb.append(", totalCorrect=").append(totalCorrect);
         sb.append(", deleteTime=").append(deleteTime);
         sb.append(", relationQuestion=").append(relationQuestion);
+        sb.append(", relationNumber=").append(relationNumber);
         sb.append("]");
         return sb.toString();
     }
@@ -398,6 +423,16 @@ public class QuestionNo implements Serializable {
             return this;
         }
 
+        /**
+         * 设置关联题目数量
+         *
+         * @param relationNumber 关联题目数量
+         */
+        public Builder relationNumber(Integer relationNumber) {
+            obj.setRelationNumber(relationNumber);
+            return this;
+        }
+
         public QuestionNo build() {
             return this.obj;
         }

+ 2 - 1
server/data/src/main/java/com/qxgmat/data/dao/mapping/QuestionNoMapper.xml

@@ -16,12 +16,13 @@
     <result column="total_correct" jdbcType="INTEGER" property="totalCorrect" />
     <result column="delete_time" jdbcType="TIMESTAMP" property="deleteTime" />
     <result column="relation_question" jdbcType="VARCHAR" property="relationQuestion" typeHandler="com.nuliji.tools.mybatis.handler.IntegerArrayHandler" />
+    <result column="relation_number" jdbcType="INTEGER" property="relationNumber" />
   </resultMap>
   <sql id="Base_Column_List">
     <!--
       WARNING - @mbg.generated
     -->
     `id`, `title`, `question_id`, `no`, `module`, `module_struct`, `total_time`, `total_number`, 
-    `total_correct`, `delete_time`, `relation_question`
+    `total_correct`, `delete_time`, `relation_question`, `relation_number`
   </sql>
 </mapper>

+ 14 - 0
server/data/src/main/java/com/qxgmat/data/relation/QuestionNoRelationMapper.java

@@ -2,9 +2,11 @@ package com.qxgmat.data.relation;
 
 import com.qxgmat.data.dao.entity.Question;
 import com.qxgmat.data.dao.entity.QuestionNo;
+import com.qxgmat.data.relation.entity.QuestionDifficultRelation;
 import com.qxgmat.data.relation.entity.QuestionNoRelation;
 import org.apache.ibatis.annotations.Param;
 
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -23,6 +25,18 @@ public interface QuestionNoRelationMapper {
             @Param("stem") String stem
     );
 
+    List<QuestionNo> randomExamination(
+            @Param("structId") Number structId,
+            @Param("targetTypes") Collection targetTypes,
+            @Param("filterIds") Collection filterIds,
+            @Param("size") Number size
+    );
+
+    List<QuestionDifficultRelation> allExaminationByType(
+            @Param("structId") Number structId,
+            @Param("targetTypes") Collection targetTypes
+    );
+
     List<QuestionNoRelation> listExerciseAdmin(
             @Param("questionType") String questionType,
             @Param("structId") Number structId,

+ 33 - 0
server/data/src/main/java/com/qxgmat/data/relation/entity/QuestionDifficultRelation.java

@@ -0,0 +1,33 @@
+package com.qxgmat.data.relation.entity;
+
+public class QuestionDifficultRelation {
+    private Integer id;
+
+    private String difficult;
+
+    private String questionType;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getDifficult() {
+        return difficult;
+    }
+
+    public void setDifficult(String difficult) {
+        this.difficult = difficult;
+    }
+
+    public String getQuestionType() {
+        return questionType;
+    }
+
+    public void setQuestionType(String questionType) {
+        this.questionType = questionType;
+    }
+}

+ 61 - 3
server/data/src/main/java/com/qxgmat/data/relation/mapping/QuestionNoRelationMapper.xml

@@ -7,12 +7,20 @@
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
   </resultMap>
-  <sql id="Id_Column_List">
+  <resultMap id="DifficultMap" type="com.qxgmat.data.relation.entity.QuestionDifficultRelation">
     <!--
       WARNING - @mbg.generated
     -->
+    <id column="id" jdbcType="INTEGER" property="id" />
+    <id column="difficult" jdbcType="VARCHAR" property="difficult" />
+    <id column="question_type" jdbcType="VARCHAR" property="questionType" />
+  </resultMap>
+  <sql id="Id_Column_List">
     qn.`id`
   </sql>
+  <sql id="Difficult_Column_List">
+    qn.`id`, q.`difficult`, q.`question_type`
+  </sql>
 
   <!--累加做题记录-->
   <update id="accumulation">
@@ -39,6 +47,56 @@
   </select>
 
   <!--
+    模考随机出题
+  -->
+  <select id="randomExamination" resultMap="IdMap">
+    select
+    <include refid="Id_Column_List" />
+    from `question_no` qn
+    left join `question` q on q.`id` = qn.`question_id`
+    where
+    qn.`module` = 'examination'
+    <if test="structId != null">
+      and find_in_set(#{structId,jdbcType=VARCHAR}, qn.`module_struct`)
+    </if>
+    <if test="targetTypes != null">
+      and q.`question_type` IN
+      <foreach collection="targetTypes" item="item" index="index" open="(" separator="," close=")">
+        #{item}
+      </foreach>
+    </if>
+    <if test="filterIds != null">
+      and qn.`id` NOT IN
+      <foreach collection="filterIds" item="item" index="index" open="(" separator="," close=")">
+        #{item}
+      </foreach>
+    </if>
+    order by RAND()
+    limit #{size,jdbcType=VARCHAR}
+  </select>
+
+  <!--
+    模考题目列表
+  -->
+  <select id="allExaminationByType" resultMap="DifficultMap">
+    select
+    <include refid="Difficult_Column_List" />
+    from `question_no` qn
+    left join `question` q on q.`id` = qn.`question_id`
+    where
+    qn.`module` = 'examination'
+    <if test="structId != null">
+      and find_in_set(#{structId,jdbcType=VARCHAR}, qn.`module_struct`)
+    </if>
+    <if test="targetTypes != null">
+      and q.`question_type` IN
+      <foreach collection="targetTypes" item="item" index="index" open="(" separator="," close=")">
+        #{item}
+      </foreach>
+    </if>
+  </select>
+
+  <!--
     后台练习题目搜索
   -->
   <select id="listExerciseAdmin" resultMap="IdMap">
@@ -47,7 +105,7 @@
     form `question_no` qn
     left join `question` q on q.`id` = qn.`question_id`
     <if test="paperId != null">
-      left join `exercise_paper` ep on find_in_set(qn.`id`, ep.`question_no_ids`)
+      left join `exercise_paper` ep on find_in_set(qn.`id`, trim(TRAILING ']' from trim(LEADING '[' from eq.`question_no_ids`)))
       and ep.`id` = #{paperId,jdbcType=VARCHAR}
     </if>
     where qn.`module` = "exercise"
@@ -87,7 +145,7 @@
     form `question_no` qn
     left join `question` q on q.`id` = qn.`question_id`
     <if test="paperId != null">
-      left join `examination_paper` ep on find_in_set(qn.`id`, ep.`question_no_ids`)
+      left join `examination_paper` ep on find_in_set(qn.`id`, trim(TRAILING ']' from trim(LEADING '[' from eq.`question_no_ids`)))
       and ep.`id` = #{paperId,jdbcType=VARCHAR}
     </if>
     where qn.`module` = "examination"

+ 3 - 3
server/gateway-api/src/main/java/com/qxgmat/controller/admin/ExaminationController.java

@@ -43,7 +43,7 @@ public class ExaminationController {
     @ApiOperation(value = "添加模考层级", httpMethod = "POST")
     public Response<ExaminationStruct> add(@RequestBody @Validated ExaminationStructDto dto, HttpServletRequest request) {
         ExaminationStruct entity = Transform.dtoToEntity(dto);
-        entity = examinationStructService.addPaper(entity);
+        entity = examinationService.addPaper(entity);
         managerLogService.log(request);
         return ResponseHelp.success(entity);
     }
@@ -51,7 +51,7 @@ public class ExaminationController {
     @ApiOperation(value = "编辑模考层级", httpMethod = "PUT")
     public Response<Boolean> edit(@RequestBody @Validated ExaminationStructDto dto, HttpServletRequest request) {
         ExaminationStruct entity = Transform.dtoToEntity(dto);
-        entity = examinationStructService.editPaper(entity);
+        entity = examinationService.editPaper(entity);
         managerLogService.log(request);
         return ResponseHelp.success(true);
     }
@@ -59,7 +59,7 @@ public class ExaminationController {
     @RequestMapping(value = "/struct/delete", method = RequestMethod.DELETE)
     @ApiOperation(value = "删除模考层级", httpMethod = "DELETE")
     public Response<Boolean> delete(@RequestParam int id, HttpServletRequest request) {
-        boolean result = examinationStructService.deletePaper(id);
+        boolean result = examinationService.deletePaper(id);
         managerLogService.log(request);
         return ResponseHelp.success(result);
     }

+ 8 - 1
server/gateway-api/src/main/java/com/qxgmat/controller/api/QuestionController.java

@@ -382,7 +382,14 @@ public class QuestionController {
     @ApiOperation(value = "提交题目答案", notes = "提交题目", httpMethod = "POST")
     public Response<Boolean> submit(@RequestBody @Validated QuestionSubmitDto dto)  {
         User user = (User) shiroHelp.getLoginUser();
-        Boolean result = questionFlowService.submit(user.getId(), dto.getUserQuestionId(), dto.getTime(), JSONObject.parseObject(JSONObject.toJSONString(dto.getAnswer())), JSONObject.parseObject(JSONObject.toJSONString(dto.getSetting())));
+        UserQuestion userQuestion = userQuestionService.get(dto.getUserQuestionId());
+        if (userQuestion == null){
+            throw new ParameterException("做题不存在");
+        }
+        userQuestion.setUserTime(dto.getTime());
+        userQuestion.setUserAnswer(JSONObject.parseObject(JSONObject.toJSONString(dto.getAnswer())));
+        userQuestion.setSetting(JSONObject.parseObject(JSONObject.toJSONString(dto.getSetting())));
+        Boolean result = questionFlowService.submit(user.getId(), userQuestion);
         return ResponseHelp.success(result);
     }
 

+ 19 - 1
server/gateway-api/src/main/java/com/qxgmat/service/UserQuestionService.java

@@ -66,6 +66,7 @@ public class UserQuestionService extends AbstractService {
         stat.setUserTime(totalTime);
         return stat;
     }
+
     /**
      * 根据试卷分组获取统计信息
      * @param questionIds
@@ -93,6 +94,7 @@ public class UserQuestionService extends AbstractService {
 
         return relationMap;
     }
+
     /**
      * 获取当前报告最后一题
      * @param userId
@@ -148,7 +150,23 @@ public class UserQuestionService extends AbstractService {
         example.and(
                 example.createCriteria()
                         .andEqualTo("userId", userId)
-                        .andEqualTo("paper_id", userReportId)
+                        .andEqualTo("reportId", userReportId)
+        );
+        example.orderBy("id").asc();
+        return select(userQuestionMapper, example);
+    }
+
+    /**
+     * 获取报告固定题目类型做题记录
+     * @return
+     */
+    public List<UserQuestion> listByReportAndType(Integer userId, Integer userReportId, Collection questionTypes){
+        Example example = new Example(UserQuestion.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("userId", userId)
+                        .andEqualTo("reportId", userReportId)
+                        .andIn("questionType", questionTypes)
         );
         example.orderBy("id").asc();
         return select(userQuestionMapper, example);

+ 482 - 35
server/gateway-api/src/main/java/com/qxgmat/service/extend/ExaminationService.java

@@ -1,31 +1,32 @@
 package com.qxgmat.service.extend;
 
+import com.alibaba.fastjson.JSONObject;
 import com.github.pagehelper.Page;
 import com.nuliji.tools.AbstractService;
-import com.nuliji.tools.PageResult;
 import com.nuliji.tools.Transform;
-import com.qxgmat.data.constants.enums.logic.ExerciseLogic;
+import com.qxgmat.data.constants.enums.QuestionDifficult;
+import com.qxgmat.data.constants.enums.QuestionSubject;
+import com.qxgmat.data.constants.enums.QuestionType;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
-import com.qxgmat.data.dao.entity.UserPaper;
-import com.qxgmat.data.dao.entity.UserReport;
+import com.qxgmat.data.dao.entity.ExaminationPaper;
+import com.qxgmat.data.dao.entity.ExaminationStruct;
+import com.qxgmat.data.dao.entity.UserQuestion;
 import com.qxgmat.data.relation.QuestionNoRelationMapper;
 import com.qxgmat.data.relation.UserPaperRelationMapper;
 import com.qxgmat.data.relation.UserReportRelationMapper;
+import com.qxgmat.data.relation.entity.QuestionDifficultRelation;
 import com.qxgmat.data.relation.entity.QuestionNoRelation;
-import com.qxgmat.data.relation.entity.UserExercisePaperRelation;
 import com.qxgmat.service.UserPaperService;
-import com.qxgmat.service.inline.ExercisePaperService;
-import com.qxgmat.service.inline.QuestionNoService;
-import com.qxgmat.service.inline.QuestionService;
-import com.qxgmat.service.inline.UserReportService;
+import com.qxgmat.service.inline.*;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
-import java.util.Collection;
-import java.util.List;
+import java.util.*;
 
 @Service
 public class ExaminationService extends AbstractService {
+    private static Random rand = new Random();
 
     @Resource
     private QuestionService questionService;
@@ -37,6 +38,12 @@ public class ExaminationService extends AbstractService {
     private QuestionNoRelationMapper questionNoRelationMapper;
 
     @Resource
+    private ExaminationStructService examinationStructService;
+
+    @Resource
+    private ExaminationPaperService examinationPaperService;
+
+    @Resource
     private UserPaperRelationMapper userPaperRelationMapper;
 
     @Resource
@@ -47,6 +54,89 @@ public class ExaminationService extends AbstractService {
 
     @Resource
     private UserReportService userReportService;
+
+    /**
+     * verbal考试相关设置
+     */
+    final public Integer verbalMaxLevel = 27;
+    final public Integer verbalMinLevel = 9;
+    final public Integer verbalInitLevel = 13;
+    final public Integer verbalPre = 9;
+    final public Integer verbalSC = 14;
+    final public Integer verbalCR = 9;
+    final public Integer verbalRC = 13;
+    // 出RC阅读题的题号
+    final public Integer[] verbalRCPosition = new Integer[]{5, 15, 24, 33};
+
+    /**
+     * quant考试相关设置
+     */
+    final public Integer[] quantBaseLevel = new Integer[]{8, 8, 8, 7};
+    // 阶段的最大题目数
+    final public Integer[] quantNumber = new Integer[]{0, 8, 16, 24, 31};
+    final public Integer quantMinRatio = 1;
+    final public Integer quantMaxRatio = 3;
+    final public Integer quantInitLevel = 11;
+    final public Integer quantPS = 17;
+    final public Integer quantDS = 14;
+
+    /**
+     * 根据第三层结构建立paper
+     * @param entity
+     * @return
+     */
+    @Transactional
+    public ExaminationStruct addPaper(ExaminationStruct entity){
+        entity = examinationStructService.add(entity);
+        if (entity.getLevel() == 3){
+            ExaminationPaper paper = examinationPaperService.add(ExaminationPaper.builder()
+                    .isAdapt(entity.getIsAdapt())
+                    .structTwo(entity.getParentId())
+                    .structThree(entity.getId())
+                    .title(entity.getTitleZh())
+                    .build()
+            );
+        }
+        return entity;
+    }
+
+    @Transactional
+    public ExaminationStruct editPaper(ExaminationStruct entity){
+        entity = examinationStructService.add(entity);
+        if (entity.getLevel() == 3){
+            ExaminationPaper paper = examinationPaperService.getByThree(entity.getId());
+            if(paper == null){
+                paper = examinationPaperService.add(ExaminationPaper.builder()
+                        .isAdapt(entity.getIsAdapt())
+                        .structTwo(entity.getParentId())
+                        .structThree(entity.getId())
+                        .title(entity.getTitleZh())
+                        .build()
+                );
+            }else{
+                examinationPaperService.edit(ExaminationPaper.builder()
+                        .id(paper.getId())
+                        .isAdapt(entity.getIsAdapt())
+                        .structTwo(entity.getParentId())
+                        .structThree(entity.getId())
+                        .title(entity.getTitleZh())
+                        .build()
+                );
+            }
+        }
+        return entity;
+    }
+
+    @Transactional
+    public Boolean deletePaper(Integer id){
+        Boolean result = examinationStructService.delete(id);
+        ExaminationPaper paper = examinationPaperService.getByThree(id);
+        if(paper != null){
+            examinationPaperService.delete(paper.getId());
+        }
+        return result;
+    }
+
     /**
      *
      * @param page
@@ -57,8 +147,6 @@ public class ExaminationService extends AbstractService {
      * @param paperId
      * @param place
      * @param difficult
-     * @param startTime
-     * @param endTime
      * @param order
      * @param direction
      * @return
@@ -84,38 +172,397 @@ public class ExaminationService extends AbstractService {
         return p;
     }
 
+    /**
+     * 获取下一阶段难度分
+     * @param currentLevel
+     * @param number
+     * @param correct
+     * @return
+     */
+    public Integer verbalNextLevel(Integer currentLevel, Integer number, long correct){
+        long correctRate = correct * 100 / number;
+        if (correctRate < 60){
+            currentLevel -= 5;
+            if (currentLevel < verbalMinLevel) return verbalMinLevel;
+        }else if(correctRate > 80){
+            currentLevel += 5;
+            if (currentLevel > verbalMaxLevel) return verbalMaxLevel;
+        }
+        return currentLevel;
+    }
 
     /**
-     * 查找用户作业, 并关联用户最后一次作业
-     * @param page
-     * @param size
+     * 获取下一阶段难度分
+     * @param currentLevel
+     * @param number
+     * @param correct
+     * @param step
+     * @return
+     */
+    public Integer quantNextLevel(Integer currentLevel, Integer number, long correct, Integer step){
+        long correctRate = correct * 100 / number;
+        Integer max = quantMaxRatio * quantBaseLevel[step];
+        Integer min = quantMinRatio * quantBaseLevel[step];
+        if (correctRate < 60){
+            currentLevel -= 4;
+            if (currentLevel < min) return min;
+        }else if(correctRate > 80){
+            currentLevel += 4;
+            if (currentLevel > max) return max;
+        }
+        return currentLevel;
+    }
+
+    /**
+     * 分组统计不同题型的数量
+     * @param userQuestionList
+     * @return
+     */
+    public Map<String, Integer> statTypeNumber(List<UserQuestion> userQuestionList){
+        Map<String, Integer> result = new HashMap<>();
+        for(UserQuestion question : userQuestionList){
+            String type = question.getQuestionType();
+            if (!result.containsKey(type)){
+                result.put(type, 0);
+            }
+            result.put(type, result.get(type) + 1);
+        }
+        return result;
+    }
+
+    /**
+     * 返回还需的题目类型
+     * @param typeNumber
+     * @param questionTypes
+     * @return
+     */
+    public List<String> needQuestionTypes(Map<String, Integer> typeNumber, List<String> questionTypes){
+        List<String> targetTypes = new ArrayList<>();
+        for(String type : questionTypes){
+            int target = 0;
+            switch(type){
+                case "sc":
+                    target = verbalSC;
+                    break;
+                case "cr":
+                    target = verbalCR;
+                    break;
+                case "rc":
+                    target = verbalRC;
+                    break;
+                case "ps":
+                    target = quantPS;
+                    break;
+                case "ds":
+                    target = quantDS;
+                    break;
+            }
+            if (target == 0 || target > typeNumber.get(type)){
+                targetTypes.add(type);
+            }
+        }
+        return targetTypes;
+    }
+
+    /**
+     * 通过当前完成的题目数,判断是否该抽取阅读题
+     * @param number
+     * @return
+     */
+    public boolean verbalRC(Integer number){
+        for(Integer n : verbalRCPosition){
+            if (n == number) return true;
+        }
+        return false;
+    }
+
+    /**
+     * 通过当前完成的题目数,判断当前阶段
+     * @param number
+     * @return
+     */
+    public Integer quantStep(Integer number) {
+        Integer step = 0;
+        Integer all = 0;
+        for(Integer n : quantBaseLevel){
+            all += n;
+            if (all >= number) return step;
+            step += 1;
+        }
+        return step;
+    }
+
+    /**
+     * 初始化第一阶段题目
+     *  一篇4题的阅读压轴
+     *   { "ids": [], "level": 0 }
      * @param structId
-     * @param userId
-     * @param logic
-     * @param logicExtend
-     * @param times
      * @return
      */
-    public PageResult<UserExercisePaperRelation> list(int page, int size, Number structId, Number userId, ExerciseLogic logic, String logicExtend, Integer times){
-        String logicKey = logic != null ? logic.key : "";
-        Page<UserPaper> p = page(()->{
-            userPaperRelationMapper.listExercisePaper(structId, userId, logicKey, logicExtend, times);
-        },page, size);
+    public JSONObject initVerbal(Integer structId){
+        JSONObject info = new JSONObject();
+        Integer[] rcQ = questionNoService.randomExaminationRc(structId, 4, null);
+        List<QuestionNoRelation> rcRelationList = questionNoService.listWithRelationByIds(rcQ);
+        Integer rcLevel = computeNoLevel(rcRelationList);
+        Integer targetLevel = 0;
+        Collection types = QuestionType.FromSubject(QuestionSubject.VERBAL);
+        List<QuestionDifficultRelation> difficultList = questionNoService.allExaminationByType(structId, types);
+        List<QuestionDifficultRelation> selectedList = null;
+        do{
+            selectedList = randomList(difficultList, verbalPre - 4);
+            targetLevel = rcLevel + computeDifficultLevel(selectedList);
+        }while(targetLevel >= verbalInitLevel);
+        List<Integer> targetIds = new ArrayList<>(verbalPre);
+        for(QuestionDifficultRelation relation : selectedList){
+            targetIds.add(relation.getId());
+        }
+        targetIds.addAll(Arrays.asList(rcQ));
+        info.put("level", targetLevel);
+        info.put("ids", targetIds);
+        return info;
+    }
 
-        Collection ids = Transform.getIds(p, UserPaper.class, "id");
+    /**
+     * 生成下一阶段题目
+     *  一篇3题的阅读压轴
+     *   { "ids": [], "level": 0 }
+     * @param structId
+     * @param level
+     * @param typeNumbers
+     * @param ids
+     * @return
+     */
+    public JSONObject generateVerbal(Integer structId, Integer level, Map<String, Integer> typeNumbers, Collection ids){
+        JSONObject info = new JSONObject();
+        Integer[] rcQ = questionNoService.randomExaminationRc(structId, 3, ids);
+        List<QuestionNoRelation> rcRelationList = questionNoService.listWithRelationByIds(rcQ);
+        Integer rcLevel = computeNoLevel(rcRelationList);
+        Collection types = QuestionType.FromSubject(QuestionSubject.VERBAL);
+        List<QuestionDifficultRelation> difficultList = questionNoService.allExaminationByType(structId, types);
+        List<QuestionDifficultRelation> selectedList = new ArrayList<>(verbalPre - 3);
+        Integer[] levels = generateLevel(level - rcLevel,verbalPre - 3);
+        do{
+            QuestionDifficultRelation r = random(difficultList, ids, levels[selectedList.size()]);
+            int typeNumber = typeNumbers.get(r.getQuestionType()) + 1;
+            switch(r.getQuestionType()){
+                case "sc":
+                    if (typeNumber > verbalSC) {
+                        continue;
+                    }
+                    break;
+                case "cr":
+                    if (typeNumber > verbalCR) {
+                        continue;
+                    }
+                    break;
+            }
+            typeNumbers.put(r.getQuestionType(), typeNumber);
+            selectedList.add(r);
+            ids.add(r.getId());
+        }while(selectedList.size() == verbalPre -3);
+        List<Integer> targetIds = new ArrayList<>(verbalPre);
+        for(QuestionDifficultRelation relation : selectedList){
+            targetIds.add(relation.getId());
+        }
+        targetIds.addAll(Arrays.asList(rcQ));
+        info.put("ids", targetIds);
+        info.put("level", level);
+        return info;
+    }
 
-        // 获取详细数据
-        List<UserPaper> list = userPaperService.select(ids);
-        List<UserExercisePaperRelation> pr = Transform.convert(list, UserExercisePaperRelation.class);
+    /**
+     * 初始化第一阶段题目
+     *  一篇4题的阅读压轴
+     *   { "ids": [], "level": 0 }
+     * @param structId
+     * @return
+     */
+    public JSONObject initQuant(Integer structId){
+        JSONObject info = new JSONObject();
+        Integer targetLevel = 0;
+        Collection types = QuestionType.FromSubject(QuestionSubject.QUANT);
+        Integer number = quantBaseLevel[0];
+        List<QuestionDifficultRelation> difficultList = questionNoService.allExaminationByType(structId, types);
+        List<QuestionDifficultRelation> selectedList = null;
+        do{
+            selectedList = randomList(difficultList, number);
+            targetLevel = computeDifficultLevel(selectedList);
+        }while(targetLevel >= verbalInitLevel);
+        List<Integer> targetIds = new ArrayList<>(verbalPre);
+        for(QuestionDifficultRelation relation : selectedList){
+            targetIds.add(relation.getId());
+        }
+        info.put("level", targetLevel);
+        info.put("ids", targetIds);
+        return info;
+    }
 
-        // 获取最后一次作业结果
-        List<UserReport> reportList = userReportRelationMapper.listLast(ids);
-        Collection reportIds = Transform.getIds(reportList, UserReport.class, "id");
-        Transform.replace(reportList, userReportService.select(reportIds), UserReport.class, "id");
+    /**
+     * 生成下一阶段题目
+     *  一篇3题的阅读压轴
+     *   { "ids": [], "level": 0 }
+     * @param structId
+     * @param level
+     * @param typeNumbers
+     * @param ids
+     * @return
+     */
+    public JSONObject generateQuant(Integer structId, Integer level, Map<String, Integer> typeNumbers, Collection ids, Integer step){
+        JSONObject info = new JSONObject();
+        Collection types = QuestionType.FromSubject(QuestionSubject.QUANT);
+        Integer number = quantBaseLevel[step];
+        List<QuestionDifficultRelation> difficultList = questionNoService.allExaminationByType(structId, types);
+        List<QuestionDifficultRelation> selectedList = null;
+        Integer[] levels = generateLevel(level,number);
+        do{
+            QuestionDifficultRelation r = random(difficultList, ids, levels[selectedList.size()]);
+            int typeNumber = typeNumbers.get(r.getQuestionType()) + 1;
+            switch(r.getQuestionType()){
+                case "ps":
+                    if (typeNumber > quantPS) {
+                        continue;
+                    }
+                    break;
+                case "ds":
+                    if (typeNumber > quantDS) {
+                        continue;
+                    }
+                    break;
+            }
+            typeNumbers.put(r.getQuestionType(), typeNumber);
+            selectedList.add(r);
+            ids.add(r.getId());
+        }while(selectedList.size() == number);
+        List<Integer> targetIds = new ArrayList<>(number);
+        for(QuestionDifficultRelation relation : selectedList){
+            targetIds.add(relation.getId());
+        }
+        info.put("ids", targetIds);
+        info.put("level", level);
+        return info;
+    }
 
-        Transform.combine(p, reportList, UserExercisePaperRelation.class, "id", "report", UserReport.class, "paperId");
+    /**
+     * 随机生成对应的题目列表
+     * @param all
+     * @param size
+     * @return
+     */
+    public List<QuestionDifficultRelation> randomList(List<QuestionDifficultRelation> all, Integer size){
+        List<QuestionDifficultRelation> list = new ArrayList<>(size);
+        do{
+            int index = rand.nextInt(all.size());
+            QuestionDifficultRelation target = all.get(index);
+            list.add(target);
+        }while(list.size() == size);
+        return list;
+    }
 
-        return new PageResult<>(pr, p.getTotal());
+    /**
+     * 随机生成对应的题目
+     * @param all
+     * @param ids
+     * @param level
+     * @return
+     */
+    public QuestionDifficultRelation random(List<QuestionDifficultRelation> all, Collection ids, Integer level){
+        String difficult = levelToDifficult(level);
+        do{
+            int index = rand.nextInt(all.size());
+            QuestionDifficultRelation target = all.get(index);
+            if (ids == null || !ids.contains(target.getId())){
+                if (difficult == null || target.getDifficult().equals(difficult)) return target;
+            }
+        }while(true);
+    }
+
+    /**
+     * 计算考题对应的难度分
+     * @param relationList
+     * @return
+     */
+    public Integer computeNoLevel(List<QuestionNoRelation> relationList){
+        int level = 0;
+        for(QuestionNoRelation relation : relationList){
+            level += difficultToLevel(relation.getQuestion().getDifficult());
+        }
+        return level;
+    }
+
+    public Integer computeDifficultLevel(List<QuestionDifficultRelation> relationList){
+        int level = 0;
+        for(QuestionDifficultRelation relation : relationList){
+            level += difficultToLevel(relation.getDifficult());
+        }
+        return level;
+    }
+
+    public Integer difficultToLevel(String difficult){
+        switch(difficult){
+            case "easy":
+                return 1;
+            case "medium":
+                return 2;
+            case "hard":
+                return 3;
+        }
+        return 0;
+    }
+
+    public String levelToDifficult(Integer level){
+        switch(level){
+            case 1:
+                return "easy";
+            case 2:
+                return "medium";
+            case 3:
+                return "hard";
+        }
+        return null;
+    }
+
+    public Integer[] generateLevel(Integer level, Integer number){
+        Integer[] levels = new Integer[number];
+        int no = 0;
+        // 至少有一个可以3分
+        if (level > number + 1){
+            for(int i = 0; i < number; i++){
+                int n = rand.nextInt(2) + 2; // 随机2-3
+                levels[i] = n;
+                level -= n;
+                no += 1;
+
+                // 只能还有一个2分
+                if (level == number - no + 1){
+                    break;
+                }
+            }
+            number -= no;
+        }
+        if(level == number + 1){
+            // 只有一个2分
+            levels[no] = 2;
+            no += 1;
+        }
+        // 剩余都是1分
+        for (int i = 0; i < number; i++) {
+            levels[i+no] = 1;
+        }
+        shuffle(levels);
+        return levels;
     }
 
+    public static <T> void swap(T[] a, int i, int j){
+        T temp = a[i];
+        a[i] = a[j];
+        a[j] = temp;
+    }
+
+    public static <T> void shuffle(T[] arr) {
+        int length = arr.length;
+        for ( int i = length; i > 0; i-- ){
+            int randInd = rand.nextInt(i);
+            swap(arr, randInd, i - 1);
+        }
+    }
 }

+ 244 - 24
server/gateway-api/src/main/java/com/qxgmat/service/extend/QuestionFlowService.java

@@ -5,10 +5,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.nuliji.tools.Transform;
 import com.nuliji.tools.exception.ParameterException;
 import com.qxgmat.data.constants.enums.*;
-import com.qxgmat.data.constants.enums.module.PaperModule;
-import com.qxgmat.data.constants.enums.module.PaperOrigin;
-import com.qxgmat.data.constants.enums.module.QuestionModule;
-import com.qxgmat.data.constants.enums.module.QuestionOrigin;
+import com.qxgmat.data.constants.enums.module.*;
 import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.data.relation.entity.*;
 import com.qxgmat.service.*;
@@ -67,6 +64,9 @@ public class QuestionFlowService {
     private UserQuestionService userQuestionService;
 
     @Resource
+    private ExaminationService examinationService;
+
+    @Resource
     private ToolsService toolsService;
 
     // 自由组卷初始化试卷callback
@@ -164,6 +164,17 @@ public class QuestionFlowService {
 //                report.setQuestionNoIds(this.randomQuestionNoIds(paper.getQuestionNoIds()));
             }
             JSONArray order = setting.getJSONArray("order");
+            // 初始化第一阶段
+            setting.put("stage", order.getString(0));
+            // 初始化每阶段时间, 以及做题数
+            JSONObject time = new JSONObject();
+            JSONObject number = new JSONObject();
+            for(String stage : order.toJavaList(String.class)){
+                time.put(stage, 0);
+                number.put(stage, 0);
+            }
+            setting.put("time", time);
+            setting.put("number", number);
             toolsService.examinationReportInit(report, order);
         });
 
@@ -183,6 +194,7 @@ public class QuestionFlowService {
             SentenceQuestionRelation relation = sentenceQuestionService.relation(sentenceQuestionService.get(questionNoId));
             question.setQuestionNoId(relation.getId());
             question.setQuestionId(relation.getQuestionId());
+            question.setQuestionType(relation.getQuestion().getQuestionType());
             Integer time = toolsService.computerTime(relation);
             question.setTime(time);
             return true;
@@ -193,6 +205,7 @@ public class QuestionFlowService {
             TextbookQuestionRelation relation = textbookQuestionService.relation(textbookQuestionService.get(questionNoId));
             question.setQuestionNoId(relation.getId());
             question.setQuestionId(relation.getQuestionId());
+            question.setQuestionType(relation.getQuestion().getQuestionType());
             Integer time = toolsService.computerTime(relation);
             question.setTime(time);
             return true;
@@ -200,20 +213,57 @@ public class QuestionFlowService {
         nextCallback.put(PaperModule.EXAMINATION, (question, report, lastQuestion)->{
             JSONObject setting = report.getSetting();
             JSONArray order = setting.getJSONArray("order");
+            String stage = setting.getString("stage");
+            JSONObject time = setting.getJSONObject("time");
+            JSONObject number = setting.getJSONObject("number");
+            time.put(stage, time.getIntValue(stage)+ question.getUserTime());
+            Integer totalNumber = toolsService.examinationSubjectNumber(stage);
             // 判断数量是否已经完成
+            if (number.getIntValue(stage) >= totalNumber){
+                // 进入下一阶段
+                int index = order.indexOf(stage);
+                if (index == order.size() - 1){
+                    // 完成所有阶段,结束考试
+                    return false;
+                }
+                stage = order.getString(index + 1);
+                setting.put("stage", stage);
+            }
+            // 获取本阶段完成数,剩余题目数
+            Integer subnumber =  number.getIntValue(stage);
+            Integer surplus = totalNumber - subnumber;
+            QuestionSubject subject = QuestionSubject.ValueOf(stage);
+            List<String> questionTypes = QuestionType.FromSubject(subject);
+            List<UserQuestion> userQuestionList = userQuestionService.listByReportAndType(report.getUserId(), report.getId(), questionTypes);
+            Collection ids = Transform.getIds(userQuestionList, UserQuestion.class, "questionNoId");
             // 根据设置出题
-            QuestionNoRelation relation = null;
-            // todo 适应性难度判断
+            Integer questionNoId = 0;
             ExaminationPaper paper = examinationPaperService.get(report.getOriginId());
-            if (paper.getIsAdapt() > 0){
-
+            if (paper.getIsAdapt() > 0 && QuestionSubject.SupportAdapt(subject)){
+                switch(subject){
+                    case VERBAL:
+                        questionNoId = verbalCompute(paper, setting, subnumber, ids, userQuestionList);
+                        break;
+                    case QUANT:
+                        questionNoId = quantCompute(paper, setting, subnumber, ids, userQuestionList);
+                        break;
+                    default:
+                        throw new ParameterException("模考出题流程错误:"+subject.key+"不支持适应性判断");
+                }
             }else{
-                // 随机挑题
+                questionNoId = randomCompute(subject, paper, questionTypes, setting, subnumber, ids, userQuestionList);
             }
+            if (questionNoId == 0) {
+                throw new ParameterException("模考出题流程错误:题目生成错误");
+            }
+            QuestionNoRelation relation = questionNoService.getWithRelation(questionNoId);
             question.setQuestionNoId(relation.getId());
             question.setQuestionId(relation.getQuestionId());
-            Integer time = toolsService.computerTime(relation);
-            question.setTime(time);
+            question.setQuestionType(relation.getQuestion().getQuestionType());
+            question.setTime(toolsService.computerTime(relation));
+
+            // 保存report: 更新setting
+            userReportService.edit(UserReport.builder().id(report.getId()).setting(setting).build());
             return true;
         });
 
@@ -421,11 +471,11 @@ public class QuestionFlowService {
         // 查找未完成的questionId
         if (userQuestion==null || userQuestion.getUserTime() >0){
             // 创建新的question
-            UserReport report = userReportService.get(userReportId);
-            if (!report.getUserId().equals(userId)){
+            UserReport userReport = userReportService.get(userReportId);
+            if (!userReport.getUserId().equals(userId)){
                 throw new ParameterException("试卷不存在");
             }
-            userQuestion = userQuestionService.addByReport(report, userQuestion, nextCallback.get(PaperModule.ValueOf(report.getPaperModule())));
+            userQuestion = userQuestionService.addByReport(userReport, userQuestion, nextCallback.get(PaperModule.ValueOf(userReport.getPaperModule())));
         }
 
         return userQuestion;
@@ -434,19 +484,14 @@ public class QuestionFlowService {
     /**
      * 提交试题
      * @param userId
-     * @param userQuestionId
-     * @param answer
+     * @param userQuestion
      * @return
      */
     @Transactional
-    public Boolean submit(Integer userId, Integer userQuestionId, Integer time, JSONObject answer, JSONObject setting){
-        UserQuestion userQuestion = userQuestionService.get(userQuestionId);
+    public Boolean submit(Integer userId, UserQuestion userQuestion){
         if (!userQuestion.getUserId().equals(userId)){
             throw new ParameterException("题目不存在");
         }
-        userQuestion.setTime(time);
-        userQuestion.setUserAnswer(answer);
-        userQuestion.setSetting(setting);
 
         QuestionModule module = QuestionModule.ValueOf(userQuestion.getQuestionModule());
         UserReport userReport = userReportService.get(userQuestion.getReportId());
@@ -473,14 +518,46 @@ public class QuestionFlowService {
     }
 
     /**
-     * 模考进入下一阶段
+     * 模考进入下一阶段:调整出题逻辑,上一阶段都当成未完成
      * @param userId
      * @param userReportId
      * @return
      */
     @Transactional
     public Boolean stage(Integer userId, Integer userReportId){
-
+        UserReport userReport = userReportService.get(userReportId);
+        if (!userReport.getUserId().equals(userId)){
+            throw new ParameterException("试卷不存在");
+        }
+        if (userReport.getDetail() != null && userReport.getFinishTime() != null){
+            throw new ParameterException("做题结束");
+        }
+        PaperOrigin origin = PaperOrigin.ValueOf(userReport.getPaperOrigin());
+        if (origin != PaperOrigin.EXAMINATION){
+            throw new ParameterException("不是模考试卷");
+        }
+        JSONObject setting = userReport.getSetting();
+
+        // 当前stage,补全所有时间,切换成下一个stage
+        String stage = setting.getString("stage");
+        JSONArray order = setting.getJSONArray("order");
+        JSONObject time = setting.getJSONObject("time");
+        JSONObject number = setting.getJSONObject("number");
+        Integer useTime = time.getInteger(stage);
+        Integer totalTime = toolsService.examinationSubjectTime(stage);
+        time.put(stage, totalTime);
+        setting.put("stage", order.getString(order.indexOf(stage) + 1));
+        // 更新setting
+        userReportService.edit(UserReport.builder().id(userReportId).setting(setting).build());
+
+        // 自动提交最后一题
+        UserQuestion userQuestion = userQuestionService.getLastByReport(userId, userReportId);
+        if(userQuestion != null && userQuestion.getUserTime() == 0){
+            userQuestion.setUserTime(totalTime - useTime);
+            userQuestion.setSetting(new JSONObject());
+            userQuestion.setUserAnswer(new JSONObject());
+            this.submit(userId, userQuestion);
+        }
         return true;
     }
 
@@ -526,13 +603,155 @@ public class QuestionFlowService {
     }
 
     /**
+     * 语文出题计算
+     * @param setting
+     * @param subnumber
+     * @param ids
+     * @param subQuestionList
+     * @return
+     */
+    public Integer verbalCompute(ExaminationPaper paper, JSONObject setting, Integer subnumber, Collection ids, List<UserQuestion> subQuestionList){
+        // 一共分为4个阶段:每个阶段9题,包含一题阅读
+        // 其中句改SC有14题,阅读RC有13题(共4篇阅读,每篇题数为3、3、3、4),逻辑CR为9题
+        // verbal: { "steps": [{"ids": [], "level": 0}, {}] }
+        List questionNoIds = new ArrayList<>();
+        JSONObject verbal = setting.getJSONObject("verbal");
+        if (verbal == null) {
+            verbal = new JSONObject();
+            verbal.put("steps", new JSONArray());
+            setting.put("verbal", verbal);
+        }
+        JSONArray steps = verbal.getJSONArray("steps");
+        // 判断当前是第几阶段
+        Integer step = subnumber / examinationService.verbalPre + 1;
+        Integer questionIndex = 0;
+        JSONObject info;
+        if (subnumber == 0){
+            // 初始化
+            info = examinationService.initVerbal(paper.getStructThree());
+            steps.add(info);
+        }else if(subnumber % examinationService.verbalPre == 0){
+            info = steps.getJSONObject(step);
+            List currentIds = info.getJSONArray("ids").toJavaObject(questionNoIds.getClass());
+            // 计算上阶段的做题情况
+            long correct = subQuestionList.stream().filter((x)->currentIds.contains(x.getQuestionNoId()) && x.getIsCorrect() > 0).count();
+            // 下一阶段
+            Integer level = examinationService.verbalNextLevel(info.getInteger("level"), examinationService.verbalPre, correct);
+            Map<String, Integer> typeNumber = examinationService.statTypeNumber(subQuestionList);
+            info = examinationService.generateVerbal(paper.getStructThree(), level, typeNumber, ids);
+            steps.add(info);
+        }else{
+            info = steps.getJSONObject(step);
+            questionIndex = subnumber % examinationService.verbalPre;
+        }
+        //获取下一题
+        questionNoIds = info.getJSONArray("ids").toJavaObject(questionNoIds.getClass());
+        return (Integer) questionNoIds.get(questionIndex);
+    }
+
+    /**
+     * 数学出题计算
+     * @param setting
+     * @param subnumber
+     * @param ids
+     * @param subQuestionList
+     * @return
+     */
+    public Integer quantCompute(ExaminationPaper paper, JSONObject setting, Integer subnumber, Collection ids, List<UserQuestion> subQuestionList){
+        // 一共分为4个阶段:每个阶段题分别为8、8、8、7题,合计31题
+        // 其中数学PS有17题,数学DS有14题
+        // quant: { "steps": [{"ids": [], "level": 0}, {}] }
+        List questionNoIds = new ArrayList<>();
+        JSONObject quant = setting.getJSONObject("quant");
+        if (quant == null) {
+            quant = new JSONObject();
+            quant.put("steps", new JSONArray());
+            setting.put("quant", quant);
+        }
+        JSONArray steps = quant.getJSONArray("steps");
+        // 判断当前是第几阶段
+        Integer step = examinationService.quantStep(subnumber);
+        Integer nextStep = examinationService.quantStep(subnumber+1);
+        Integer questionIndex = 0;
+        JSONObject info;
+        if (subnumber == 0){
+            // 初始化
+            info = examinationService.initQuant(paper.getStructThree());
+            steps.add(info);
+        }else if(step < nextStep){
+            info = steps.getJSONObject(step);
+            List currentIds = info.getJSONArray("ids").toJavaObject(questionNoIds.getClass());
+            // 计算上阶段的做题情况
+            long correct = subQuestionList.stream().filter((x)->currentIds.contains(x.getQuestionNoId()) && x.getIsCorrect() > 0).count();
+            // 下一阶段
+            Integer level = examinationService.quantNextLevel(info.getInteger("level"), examinationService.quantBaseLevel[step], correct, step);
+            Map<String, Integer> typeNumber = examinationService.statTypeNumber(subQuestionList);
+            info = examinationService.generateQuant(paper.getStructThree(), level, typeNumber, ids);
+            steps.add(info);
+        }else{
+            info = steps.getJSONObject(step);
+            questionIndex = subnumber - examinationService.quantNumber[step];
+        }
+        //获取下一题
+        questionNoIds = info.getJSONArray("ids").toJavaObject(questionNoIds.getClass());
+        return (Integer) questionNoIds.get(questionIndex);
+    }
+
+    /**
+     * 随机出题计算
+     * @param subject
+     * @param paper
+     * @param questionTypes
+     * @param setting
+     * @param subnumber
+     * @param ids
+     * @param subQuestionList
+     * @return
+     */
+    public Integer randomCompute(QuestionSubject subject, ExaminationPaper paper, List<String> questionTypes, JSONObject setting, Integer subnumber, Collection ids, List<UserQuestion> subQuestionList){
+        // 随机挑题
+        switch (subject){
+            case VERBAL:
+                // 固定顺序抽取阅读题
+                if(examinationService.verbalRC(subnumber)){
+                    // 第一次抽取4题,后面抽取3题,和适应性一致
+                    JSONObject rc = setting.getJSONObject("rc");
+                    Integer number = 0;
+                    if (rc == null) {
+                        rc = new JSONObject();
+                        setting.put("rc", rc);
+                        number = 4;
+                    }else{
+                        number = 3;
+                    }
+                    Integer[] questions = questionNoService.randomExaminationRc(paper.getStructThree(), number, rc.values());
+                    // 写入后续题目关系
+                    Integer position = subnumber;
+                    for(int q: questions){
+                        rc.put(position.toString(), q);
+                        position += 1;
+                    }
+                }
+                // 判断是否后续阅读题
+                JSONObject rc = setting.getJSONObject("rc");
+                if(rc.getInteger(subnumber.toString()) != null){
+                    return rc.getIntValue(subnumber.toString());
+                }
+                break;
+        }
+        Map<String, Integer> typeNumber = examinationService.statTypeNumber(subQuestionList);
+        List<String> targetTypes = examinationService.needQuestionTypes(typeNumber, questionTypes);
+        // 不主动查询阅读题:targetTypes没有rc
+        return questionNoService.randomExamination(paper.getStructThree(), targetTypes, ids);
+    }
+
+    /**
      * 获取报告题目列表
      * @param userReportId
      * @param userId
      * @return
      */
     public List<UserQuestion> listByReport(Integer userReportId, Integer userId){
-
         List<UserQuestion> userQuestionList = userQuestionService.listByReport(userId, userReportId);
         return userQuestionList;
     }
@@ -583,6 +802,7 @@ public class QuestionFlowService {
         QuestionNoRelation relation = questionNoService.getWithRelation(questionNoId);
         question.setQuestionNoId(relation.getId());
         question.setQuestionId(relation.getQuestionId());
+        question.setQuestionType(relation.getQuestion().getQuestionType());
         Integer time = toolsService.computerTime(relation);
         question.setTime(time);
     }

+ 46 - 12
server/gateway-api/src/main/java/com/qxgmat/service/extend/ToolsService.java

@@ -125,13 +125,18 @@ public class ToolsService {
      * @return
      */
     public Integer computerTime(SentenceQuestionRelation relation){
-        // todo 按人工还是手动
-        Setting setting = settingService.getByKey(SettingKey.SENTENCE_TIME);
-        JSONObject value = setting.getValue();
+        Setting scoreSwitch = settingService.getByKey(SettingKey.SCORE_SWITCH);
+        if (scoreSwitch.getValue().getBooleanValue("all")){
+            // todo 计算全站平均时间
+            return 0;
+        } else {
+            Setting setting = settingService.getByKey(SettingKey.SENTENCE_TIME);
+            JSONObject value = setting.getValue();
 
-        String t = value.getString("time");
-        if (t == null || t.isEmpty()) return 0;
-        return Integer.valueOf(t);
+            String t = value.getString("time");
+            if (t == null || t.isEmpty()) return 0;
+            return Integer.valueOf(t);
+        }
     }
 
     /**
@@ -141,13 +146,18 @@ public class ToolsService {
      * @return
      */
     public Integer computerTime(SentenceQuestionRelation[] relationList){
-        // todo 按人工还是手动
-        Setting setting = settingService.getByKey(SettingKey.SENTENCE_TIME);
-        JSONObject value = setting.getValue();
+        Setting scoreSwitch = settingService.getByKey(SettingKey.SCORE_SWITCH);
+        if (scoreSwitch.getValue().getBooleanValue("all")){
+            // todo 计算全站平均时间
+            return 0;
+        } else {
+            Setting setting = settingService.getByKey(SettingKey.SENTENCE_TIME);
+            JSONObject value = setting.getValue();
 
-        String t = value.getString("time");
-        if (t == null || t.isEmpty()) return 0;
-        return Integer.valueOf(t) * relationList.length;
+            String t = value.getString("time");
+            if (t == null || t.isEmpty()) return 0;
+            return Integer.valueOf(t) * relationList.length;
+        }
     }
 
     /**
@@ -185,6 +195,30 @@ public class ToolsService {
     }
 
     /**
+     * 得到单个科目做题总时间
+     * @param subject
+     * @return
+     */
+    public Integer examinationSubjectTime(String subject){
+        Setting setting = settingService.getByKey(SettingKey.EXAMINATION_TIME);
+        JSONObject value = setting.getValue();
+        JSONObject info = value.getJSONObject(subject);
+        return info.getInteger("time");
+    }
+
+    /**
+     * 得到单个科目做题总题数
+     * @param subject
+     * @return
+     */
+    public Integer examinationSubjectNumber(String subject){
+        Setting setting = settingService.getByKey(SettingKey.EXAMINATION_TIME);
+        JSONObject value = setting.getValue();
+        JSONObject info = value.getJSONObject(subject);
+        return info.getInteger("number");
+    }
+
+    /**
      * 获取模考学科数据:
      *      { time: '', number: '' }
      * @param subject

+ 1 - 1
server/gateway-api/src/main/java/com/qxgmat/service/inline/ExaminationPaperService.java

@@ -32,7 +32,7 @@ public class ExaminationPaperService extends AbstractService {
         Example example = new Example(ExaminationPaper.class);
         example.and(
                 example.createCriteria()
-                .andEqualTo("struct_three", id)
+                .andEqualTo("structThree", id)
         );
         return one(examinationPaperMapper, example);
     }

+ 0 - 60
server/gateway-api/src/main/java/com/qxgmat/service/inline/ExaminationStructService.java

@@ -28,66 +28,6 @@ public class ExaminationStructService extends AbstractService {
     @Resource
     private ExaminationStructMapper examinationStructMapper;
 
-    @Resource
-    private ExaminationPaperService examinationPaperService;
-
-    /**
-     * 根据第三层结构建立paper
-     * @param entity
-     * @return
-     */
-    @Transactional
-    public ExaminationStruct addPaper(ExaminationStruct entity){
-        entity = add(entity);
-        if (entity.getLevel() == 3){
-            ExaminationPaper paper = examinationPaperService.add(ExaminationPaper.builder()
-                    .isAdapt(entity.getIsAdapt())
-                    .structTwo(entity.getParentId())
-                    .structThree(entity.getId())
-                    .title(entity.getTitleZh())
-                    .build()
-            );
-        }
-        return entity;
-    }
-
-    @Transactional
-    public ExaminationStruct editPaper(ExaminationStruct entity){
-        entity = add(entity);
-        if (entity.getLevel() == 3){
-            ExaminationPaper paper = examinationPaperService.getByThree(entity.getId());
-            if(paper == null){
-                paper = examinationPaperService.add(ExaminationPaper.builder()
-                        .isAdapt(entity.getIsAdapt())
-                        .structTwo(entity.getParentId())
-                        .structThree(entity.getId())
-                        .title(entity.getTitleZh())
-                        .build()
-                );
-            }else{
-                examinationPaperService.edit(ExaminationPaper.builder()
-                        .id(paper.getId())
-                        .isAdapt(entity.getIsAdapt())
-                        .structTwo(entity.getParentId())
-                        .structThree(entity.getId())
-                        .title(entity.getTitleZh())
-                        .build()
-                );
-            }
-        }
-        return entity;
-    }
-
-    @Transactional
-    public Boolean deletePaper(Integer id){
-        Boolean result = delete(id);
-        ExaminationPaper paper = examinationPaperService.getByThree(id);
-        if(paper != null){
-            examinationPaperService.delete(paper.getId());
-        }
-        return result;
-    }
-
     @Cacheable(value = "examination_struct/main")
     public List<ExaminationStruct> main(){
         List<ExaminationStruct> list = all();

+ 81 - 6
server/gateway-api/src/main/java/com/qxgmat/service/inline/QuestionNoService.java

@@ -8,13 +8,13 @@ import com.nuliji.tools.exception.ParameterException;
 import com.nuliji.tools.exception.SystemException;
 import com.nuliji.tools.mybatis.Example;
 import com.qxgmat.data.constants.enums.module.StructModule;
-import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.dao.QuestionNoMapper;
 import com.qxgmat.data.dao.entity.Question;
 import com.qxgmat.data.dao.entity.QuestionNo;
 import com.qxgmat.data.dao.entity.UserQuestion;
 import com.qxgmat.data.inline.PaperStat;
 import com.qxgmat.data.relation.QuestionNoRelationMapper;
+import com.qxgmat.data.relation.entity.QuestionDifficultRelation;
 import com.qxgmat.data.relation.entity.QuestionNoRelation;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -29,7 +29,6 @@ import java.util.stream.Collectors;
 public class QuestionNoService extends AbstractService {
     private static final Logger logger = LoggerFactory.getLogger(QuestionNoService.class);
     protected boolean SOFT_FLAG = true;
-    final static String formatSet = "FIND_IN_SET('%d', module_struct)";
 
     @Resource
     private QuestionNoMapper questionNoMapper;
@@ -99,7 +98,7 @@ public class QuestionNoService extends AbstractService {
         example.and(
                 example.createCriteria()
                 .andEqualTo("module", module.key)
-                .andCondition(String.format(formatSet, structId))
+                .andCondition(String.format(formatSet, structId, "module_struct"))
         );
         example.orderBy("no").asc();
         return relation(select(questionNoMapper, example));
@@ -177,6 +176,75 @@ public class QuestionNoService extends AbstractService {
     }
 
     /**
+     * 随机获取对应模块下的试题:排除已做试题
+     * @param structId
+     * @param filterIds
+     * @return
+     */
+    public Integer randomExamination(Integer structId, Collection targetTypes, Collection filterIds){
+        List<QuestionNo> questionNoList = questionNoRelationMapper.randomExamination(structId, targetTypes, filterIds, 1);
+        if (questionNoList.size() > 0){
+            return questionNoList.get(0).getId();
+        }else{
+            return null;
+        }
+    }
+
+    /**
+     * 随机批量获取对应模块下的试题:排除已做试题
+     * @param structId
+     * @param targetTypes
+     * @param filterIds
+     * @param size
+     * @return
+     */
+    public Integer[] randomExaminationList(Integer structId, Collection targetTypes, Collection filterIds, Integer size){
+        List<QuestionNo> questionNoList = questionNoRelationMapper.randomExamination(structId, targetTypes, filterIds, size);
+        if (questionNoList.size() > 0){
+            Integer[] ids = new Integer[questionNoList.size()];
+            for(int i = 0; i< questionNoList.size(); i++){
+                ids[i] = questionNoList.get(i).getId();
+            }
+            return ids;
+        }else{
+            return new Integer[0];
+        }
+    }
+
+    /**
+     * 获取该struct下指定类型的所有题目信息
+     * @param structId
+     * @param targetTypes
+     * @return
+     */
+    public List<QuestionDifficultRelation> allExaminationByType(Integer structId, Collection targetTypes){
+        return questionNoRelationMapper.allExaminationByType(structId, targetTypes);
+    }
+
+    /**
+     * 随机查找模考阅读题
+     * @param structId
+     * @param number
+     * @param filterIds
+     * @return
+     */
+    public Integer[] randomExaminationRc(Integer structId, Integer number, Collection filterIds){
+        Example example = new Example(QuestionNo.class);
+        example.and(
+                example.createCriteria()
+                .andCondition(String.format(formatSet, structId, "module_struct"))
+                .andEqualTo("relationNumber", number)
+                .andNotIn("id", filterIds)
+        );
+        example.setOrderByClause("RAND()");
+        QuestionNo questionNo = one(questionNoMapper, example);
+        if (questionNo == null){
+            throw new ParameterException("阅读题查找失败");
+        }
+        return Arrays.stream(questionNo.getRelationQuestion()).boxed().toArray(Integer[]::new);
+    }
+
+    /**
      * 绑定no和question
      * @param ids
      * @param questionId
@@ -189,12 +257,15 @@ public class QuestionNoService extends AbstractService {
                 example.createCriteria()
                         .andIn("id", Arrays.stream(ids).collect(Collectors.toList()))
         );
-        int result = update(questionNoMapper, example, QuestionNo.builder().questionId(questionId).deleteTime(null).build());
+        int result = update(questionNoMapper, example, QuestionNo.builder()
+                .questionId(questionId)
+                .deleteTime(null)
+                .build());
         return result > 0;
     }
 
     /**
-     * 设定编号关联关系
+     * 设定编号关联关系, 并设定题目数量
      * @param ids
      * @return
      */
@@ -205,7 +276,11 @@ public class QuestionNoService extends AbstractService {
                 example.createCriteria()
                         .andIn("id", Arrays.stream(ids).boxed().collect(Collectors.toList()))
         );
-        int result = update(questionNoMapper, example, QuestionNo.builder().relationQuestion(ids).deleteTime(null).build());
+        int result = update(questionNoMapper, example, QuestionNo.builder()
+                .relationQuestion(ids)
+                .relationNumber(ids.length)
+                .deleteTime(null)
+                .build());
         return result > 0;
     }
 

+ 1 - 3
server/gateway-api/src/main/java/com/qxgmat/service/inline/SentencePaperService.java

@@ -21,8 +21,6 @@ import java.util.List;
 public class SentencePaperService extends AbstractService {
     private static final Logger logger = LoggerFactory.getLogger(SentencePaperService.class);
 
-    final static String formatSet = "FIND_IN_SET('%d', question_no_ids)";
-
     @Resource
     private SentencePaperMapper sentencePaperMapper;
 
@@ -97,7 +95,7 @@ public class SentencePaperService extends AbstractService {
         Example example = new Example(SentencePaper.class);
         example.and(
                 example.createCriteria()
-                        .andCondition(String.format(formatSet, question.getId()))
+                        .andCondition(String.format(formatSet, question.getId(), "question_no_ids"))
         );
         if (logic != null)
             example.and(

+ 1 - 3
server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookPaperService.java

@@ -19,8 +19,6 @@ import java.util.*;
 public class TextbookPaperService extends AbstractService {
     private static final Logger logger = LoggerFactory.getLogger(TextbookPaperService.class);
 
-    final static String formatSet = "FIND_IN_SET('%d', question_no_ids)";
-
     @Resource
     private TextbookPaperMapper textbookPaperMapper;
 
@@ -90,7 +88,7 @@ public class TextbookPaperService extends AbstractService {
         Example example = new Example(SentencePaper.class);
         example.and(
                 example.createCriteria()
-                        .andCondition(String.format(formatSet, question.getId()))
+                        .andCondition(String.format(formatSet, question.getId(), "question_no_ids"))
         );
         return select(textbookPaperMapper, example);
     }

+ 1 - 0
server/tools/src/main/java/com/nuliji/tools/AbstractService.java

@@ -21,6 +21,7 @@ import java.util.stream.Collectors;
  */
 public abstract class AbstractService {
     protected static final Logger logger = LoggerFactory.getLogger(AbstractService.class);
+    final public static String formatSet = "FIND_IN_SET('%d', %s)";
     protected boolean SOFT_FLAG = false;
     protected <T> int insert(Mapper<T> mapper, T record){
         EntityTable entityTable = MapperHelper.getEntityTable(mapper);