Просмотр исходного кода

feat: 增加获取创建题目的功能

ChenSiTong 6 лет назад
Родитель
Сommit
69f4477ad4

+ 6 - 0
src/main/java/nju/seec/helper/controller/ControllerAdvice.java

@@ -6,6 +6,7 @@ import nju.seec.helper.util.exception.HelperException;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.validation.BindException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
 import org.springframework.web.bind.MethodArgumentNotValidException;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
@@ -40,6 +41,11 @@ public class ControllerAdvice {
         return ResponseEntity.badRequest().body(ErrorResponse.of(Objects.requireNonNull(e.getFieldError()).getDefaultMessage()));
     }
 
+    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+    public ResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
+        return ResponseEntity.status(405).body(ErrorResponse.of(e.getLocalizedMessage()));
+    }
+
     @ExceptionHandler(HelperException.class)
     public ResponseEntity<ErrorResponse> handleHelperException(HelperException e) {
         return ResponseEntity.status(e.getType().getStatus()).body(ErrorResponse.of(e.getMsg()));

+ 7 - 0
src/main/java/nju/seec/helper/controller/QuestionController.java

@@ -74,4 +74,11 @@ public class QuestionController {
     public void deleteQuestion(LoginUser user, @PathVariable String questionId) {
         questionService.deleteQuestion(user, questionId);
     }
+
+    @Auth(roles = UserType.TEACHER, message = "获取创建的题目")
+    @GetMapping("/created")
+    public PageResponse<BaseQuestionVO> getCreatedQuestions(LoginUser user,
+                                                            @PageableDefault(size = Integer.MAX_VALUE) Pageable pageable) {
+        return PageResponse.of(questionService.getCreatedQuestions(user, pageable));
+    }
 }

+ 21 - 0
src/main/java/nju/seec/helper/dao/QuestionRecordDAO.java

@@ -2,7 +2,10 @@ package nju.seec.helper.dao;
 
 import nju.seec.helper.entity.QuestionRecord;
 import nju.seec.helper.entity.User;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
 
 import java.util.Optional;
 
@@ -18,4 +21,22 @@ public interface QuestionRecordDAO extends JpaRepository<QuestionRecord, Long> {
      * @return
      */
     Optional<QuestionRecord> findByTeacherAndQuestionId(User user, String questionId);
+
+    /**
+     * 查询是否创建该问题ID
+     *
+     * @param questionId
+     * @return
+     */
+    boolean existsByQuestionId(String questionId);
+
+    /**
+     * 查询老师创建的题目
+     *
+     * @param teacher
+     * @param pageable
+     * @return
+     */
+    @Query("select record.questionId from QuestionRecord record where record.teacher=?1")
+    Page<String> findQuestionIdsByTeacher(User teacher, Pageable pageable);
 }

+ 5 - 0
src/main/java/nju/seec/helper/dto/question/BaseQuestionDTO.java

@@ -8,6 +8,7 @@ import org.hibernate.validator.constraints.Length;
 
 import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotNull;
+import java.util.Set;
 
 /**
  * @author cst
@@ -35,4 +36,8 @@ public abstract class BaseQuestionDTO {
     @NotNull(message = "缺少题目类型", groups = Create.class)
     protected String type;
 
+    protected String keyPoints;
+    protected Set<String> tags;
+    protected Set<String> knowledgeId;
+
 }

+ 2 - 12
src/main/java/nju/seec/helper/entity/QuestionRecord.java

@@ -2,11 +2,8 @@ package nju.seec.helper.entity;
 
 import lombok.Data;
 import lombok.experimental.Accessors;
-import org.hibernate.annotations.CreationTimestamp;
-import org.hibernate.annotations.UpdateTimestamp;
 
 import javax.persistence.*;
-import java.time.LocalDateTime;
 
 /**
  * @author cst
@@ -14,7 +11,8 @@ import java.time.LocalDateTime;
 @Data
 @Accessors(chain = true)
 @Entity
-@Table(name = "question_record", uniqueConstraints = @UniqueConstraint(name = "teacher_question_unique", columnNames = {"teacher_id", "question_id"}))
+@Table(name = "question_record", indexes = @Index(name = "question_record_question", columnList = "question_id"),
+        uniqueConstraints = @UniqueConstraint(name = "teacher_question_unique", columnNames = {"teacher_id", "question_id"}))
 public class QuestionRecord {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -26,12 +24,4 @@ public class QuestionRecord {
 
     @Column(name = "question_id", nullable = false)
     private String questionId;
-
-    @Column(name = "create_at", nullable = false, updatable = false)
-    @CreationTimestamp
-    private LocalDateTime createAt;
-
-    @Column(name = "update_at", nullable = false)
-    @UpdateTimestamp
-    private LocalDateTime updateAt;
 }

+ 9 - 0
src/main/java/nju/seec/helper/service/QuestionService.java

@@ -77,4 +77,13 @@ public interface QuestionService {
      * @param questionId
      */
     void deleteQuestion(LoginUser user, String questionId);
+
+    /**
+     * 获取创建的题目
+     *
+     * @param user
+     * @param pageable
+     * @return
+     */
+    Page<BaseQuestionVO> getCreatedQuestions(LoginUser user, Pageable pageable);
 }

+ 27 - 3
src/main/java/nju/seec/helper/service/impl/QuestionServiceImpl.java

@@ -17,11 +17,13 @@ import nju.seec.helper.vo.question.BaseQuestionVO;
 import nju.seec.helper.vo.question.BokQuestion;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
 import org.springframework.data.domain.Pageable;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
+import java.util.UUID;
 import java.util.stream.Collectors;
 
 /**
@@ -81,12 +83,18 @@ public class QuestionServiceImpl implements QuestionService {
     @Transactional(rollbackFor = Exception.class)
     @Override
     public BaseQuestionVO createQuestion(LoginUser user, BaseQuestionDTO baseQuestionDTO) {
-        BokQuestion bokQuestion = bokUtil.createQuestion(baseQuestionDTO);
+        String questionId;
+        do {
+            // 保证不产生重复QuestionId
+            questionId = UUID.randomUUID().toString().replaceAll("-", "");
+        } while (questionRecordDAO.existsByQuestionId(questionId));
+
+        BokQuestion bokQuestion = bokUtil.createQuestion(questionId, baseQuestionDTO);
         QuestionRecord questionRecord = new QuestionRecord()
                 .setTeacher(userDAO.findUserById(user.getId()))
-                .setQuestionId(bokQuestion.getTqId());
+                .setQuestionId(bokQuestion.getId());
         questionRecordDAO.save(questionRecord);
-        return BaseQuestionVO.convertBokQuestionToVO(bokUtil.createQuestion(baseQuestionDTO), true);
+        return BaseQuestionVO.convertBokQuestionToVO(bokQuestion, true);
     }
 
     @Transactional(readOnly = true)
@@ -107,4 +115,20 @@ public class QuestionServiceImpl implements QuestionService {
         bokUtil.deleteQuestion(questionId);
         questionRecordDAO.delete(questionRecord);
     }
+
+    @Transactional(readOnly = true)
+    @Override
+    public Page<BaseQuestionVO> getCreatedQuestions(LoginUser user, Pageable pageable) {
+        Page<String> questionIdPage = questionRecordDAO
+                .findQuestionIdsByTeacher(userDAO.findUserById(user.getId()), pageable);
+
+        return new PageImpl<>(
+                bokUtil.bokFindByIdIn(questionIdPage.getContent())
+                        .stream()
+                        .map(bokQuestion -> BaseQuestionVO.convertBokQuestionToVO(bokQuestion, true))
+                        .collect(Collectors.toList())
+                , pageable
+                , questionIdPage.getTotalElements()
+        );
+    }
 }

+ 42 - 37
src/main/java/nju/seec/helper/util/BokUtil.java

@@ -2,6 +2,7 @@ package nju.seec.helper.util;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
@@ -18,7 +19,10 @@ import org.springframework.data.domain.PageImpl;
 import org.springframework.data.domain.Pageable;
 import org.springframework.stereotype.Component;
 
-import java.util.*;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
 /**
@@ -31,10 +35,10 @@ import java.util.stream.Collectors;
 public class BokUtil {
     private final RestRequestUtil restRequestUtil;
     private final GuavaCacheUtil cacheUtils;
-    @Value("${bok.url}")
-    private String bokUrl = "http://bok.seecoder.cn";
-    private String searchUrl = bokUrl + "/api/tq/search/";
-    private String tqUrl = bokUrl + "/api/tq/";
+    @Value("${bok.searchUrl}")
+    private String searchUrl;
+    @Value("${bok.tqUrl}")
+    private String tqUrl;
 
     public BokUtil(RestRequestUtil restRequestUtil, GuavaCacheUtil cacheUtils) {
         this.restRequestUtil = restRequestUtil;
@@ -45,48 +49,46 @@ public class BokUtil {
         Map<String, String> urlParams = ImmutableMap.of(
                 "content", stem,
                 "page", String.valueOf(1 + pageable.getPageNumber()),
-                "size", String.valueOf(pageable.getPageSize()),
-                "sort", pageable.getSort().toString().replaceAll(" ", "").replaceAll(":", ",")
+                "size", String.valueOf(pageable.getPageSize())
         );
         BokSearchResult result = restRequestUtil.sendGetRequest(
-                searchUrl + "findByStemLike?content={content}&sort={sort}&size={size}&page={page}", BokSearchResult.class, urlParams);
+                searchUrl + "findByStemLike?content={content}&size={size}&page={page}", BokSearchResult.class, urlParams);
 
-        List<BokQuestion> questions = result.getEmbedded().getChoiceQuestions();
+        List<BokQuestion> bokQuestions = result.getEmbedded().getQuestions();
         cacheUtils.setAll(BOK_CACHE_NAME,
-                questions.parallelStream()
-                        .collect(Collectors.toMap(BokQuestion::getTqId, bokQuestion -> bokQuestion)));
-        return new PageImpl<>(questions, pageable, result.getPage().getTotalElements());
+                bokQuestions.parallelStream()
+                        .collect(Collectors.toMap(BokQuestion::getId, bokQuestion -> bokQuestion))
+        );
+        return new PageImpl<>(bokQuestions, pageable, result.getPage().getTotalElements());
     }
 
     private static String BOK_CACHE_NAME = "BOK";
 
+    @SuppressWarnings({"unchecked", "rawtypes"})
     public List<BokQuestion> bokFindByIdIn(final List<String> ids) {
-        final ImmutableMap<Object, Object> bokQuestionsMap = cacheUtils.multiGet(BOK_CACHE_NAME, ids);
-
-        List<String> requestIds = new ArrayList<>(ids);
-        Map<String, BokQuestion> bokQuestionMap = Maps.newHashMapWithExpectedSize(ids.size());
-
-        bokQuestionsMap
-                .values()
-                .forEach(value -> {
-                    BokQuestion bokQuestion = (BokQuestion) value;
-                    bokQuestionMap.put(bokQuestion.getTqId(), bokQuestion);
-                    requestIds.remove(bokQuestion.getTqId());
-                });
+        Map<String, BokQuestion> bokQuestionsMap = Maps.newHashMapWithExpectedSize(ids.size());
+
+        final Map<String, BokQuestion> cacheBokQuestionsMap = (Map) cacheUtils.multiGet(BOK_CACHE_NAME, ids);
+        bokQuestionsMap.putAll(cacheBokQuestionsMap);
+
+        List<String> requestIds = Lists.newArrayList(ids);
+        requestIds.removeAll(cacheBokQuestionsMap.keySet());
         //未缓存的去这里拿
         if (!requestIds.isEmpty()) {
             Map<String, String> urlParams = ImmutableMap.of("id", requestIds.stream().reduce((a, b) -> a + "," + b).orElse(""));
 
             BokSearchResult result = restRequestUtil.sendGetRequest(searchUrl + "findByIdIn?id={id}", BokSearchResult.class, urlParams);
-            List<BokQuestion> bokQuestions = result.getEmbedded().getChoiceQuestions();
-            bokQuestions.forEach(bokQuestion -> {
-                cacheUtils.set(BOK_CACHE_NAME, bokQuestion.getTqId(), bokQuestion);
-                bokQuestionMap.put(bokQuestion.getTqId(), bokQuestion);
-            });
+            List<BokQuestion> bokQuestions = result.getEmbedded().getQuestions();
+
+            Map<String, BokQuestion> remoteBokQuestionsMap = bokQuestions.parallelStream()
+                    .collect(Collectors.toMap(BokQuestion::getId, bokQuestion -> bokQuestion));
+
+            bokQuestionsMap.putAll(remoteBokQuestionsMap);
+            cacheUtils.setAll(BOK_CACHE_NAME, (Map) remoteBokQuestionsMap);
         }
 
         return ids.stream()
-                .map(bokQuestionMap::get)
+                .map(bokQuestionsMap::get)
                 .collect(Collectors.toList());
     }
 
@@ -96,21 +98,21 @@ public class BokUtil {
             return (BokQuestion) cachedBokQuestion;
         }
         BokQuestion bokQuestion = restRequestUtil.sendGetRequest(tqUrl + questionId, BokQuestion.class, Collections.emptyMap());
-        cacheUtils.set(BOK_CACHE_NAME, bokQuestion.getTqId(), bokQuestion);
+        cacheUtils.set(BOK_CACHE_NAME, bokQuestion.getId(), bokQuestion);
         return bokQuestion;
     }
 
-    public BokQuestion createQuestion(BaseQuestionDTO baseQuestionDTO) {
+    public BokQuestion createQuestion(String questionId, BaseQuestionDTO baseQuestionDTO) {
         BokQuestion bokQuestion = getQuestion(baseQuestionDTO);
-        bokQuestion.setTqId(UUID.randomUUID().toString());
+        bokQuestion.setId(questionId);
         bokQuestion = restRequestUtil.sendPostRequest(tqUrl, bokQuestion, BokQuestion.class);
         return bokQuestion;
     }
 
     public BokQuestion modifyQuestion(String questionId, BaseQuestionDTO baseQuestionDTO) {
         BokQuestion bokQuestion = getQuestion(baseQuestionDTO);
-        bokQuestion.setTqId(questionId);
-        restRequestUtil.sendPutRequest(tqUrl, bokQuestion);
+        bokQuestion.setId(questionId);
+        restRequestUtil.sendPutRequest(tqUrl + questionId, bokQuestion);
         return bokFindById(questionId);
     }
 
@@ -122,6 +124,9 @@ public class BokUtil {
         BokQuestion bokQuestion = new BokQuestion();
         bokQuestion.setType(baseQuestionDTO.getType());
         bokQuestion.setStem(baseQuestionDTO.getStem());
+        bokQuestion.setKeyPoints(baseQuestionDTO.getKeyPoints());
+        bokQuestion.setTags(baseQuestionDTO.getTags());
+        bokQuestion.setKnowledgeId(baseQuestionDTO.getKnowledgeId());
 
         final QuestionType questionType = QuestionType.getQuestionType(baseQuestionDTO.getType());
         switch (Objects.requireNonNull(questionType)) {
@@ -145,12 +150,12 @@ public class BokUtil {
     @Data
     private static class BokSearchResult {
         @JsonProperty("_embedded")
-        private Embedded embedded;
+        private Embedded embedded = new Embedded();
         private Page page;
 
         @Data
         private static class Embedded {
-            private List<BokQuestion> choiceQuestions;
+            private List<BokQuestion> questions = Collections.emptyList();
         }
 
         @Data

+ 3 - 10
src/main/java/nju/seec/helper/util/GuavaCacheUtil.java

@@ -20,16 +20,9 @@ import java.util.stream.Collectors;
 public class GuavaCacheUtil {
     private Cache<Object, Object> cache;
 
-    @Value("${guavaCache.maximumSize}")
-    private Long maximumSize = 1000L;
-
-    @Value("${guavaCache.expireAfterAccessInSeconds}")
-    private Long expireAfterAccessInSeconds = 0L;
-
-    @Value("${guavaCache.expireAfterWriteInSeconds}")
-    private Long expireAfterWriteInSeconds = 600L;
-
-    public GuavaCacheUtil() {
+    public GuavaCacheUtil(@Value("${guavaCache.maximumSize}") Long maximumSize,
+                          @Value("${guavaCache.expireAfterAccessInSeconds}") Long expireAfterAccessInSeconds,
+                          @Value("${guavaCache.expireAfterWriteInSeconds}") Long expireAfterWriteInSeconds) {
         CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
         builder.maximumSize(maximumSize);
         builder.expireAfterAccess(expireAfterAccessInSeconds, TimeUnit.SECONDS);

+ 16 - 0
src/main/java/nju/seec/helper/vo/question/BaseQuestionVO.java

@@ -6,6 +6,9 @@ import nju.seec.helper.util.enums.ExceptionType;
 import nju.seec.helper.util.enums.QuestionType;
 import nju.seec.helper.util.exception.HelperException;
 
+import java.util.Date;
+import java.util.Set;
+
 /**
  * @author XuShengTao
  * <p>
@@ -17,6 +20,10 @@ public abstract class BaseQuestionVO {
     protected QuestionType type;
     protected String stem;
     protected String analysis;
+    protected String keyPoints;
+    protected Set<String> tags;
+    protected Set<String> knowledgeId;
+    private Date lastModified;
 
     public static BaseQuestionVO convertBokQuestionToVO(@NonNull BokQuestion bokQuestion, boolean withAnswer) {
         switch (bokQuestion.getType()) {
@@ -29,6 +36,15 @@ public abstract class BaseQuestionVO {
         }
     }
 
+    public BaseQuestionVO(BokQuestion bokQuestion) {
+        this.id = bokQuestion.getId();
+        this.stem = bokQuestion.getStem();
+        this.keyPoints = bokQuestion.getKeyPoints();
+        this.tags = bokQuestion.getTags();
+        this.knowledgeId = bokQuestion.getKnowledgeId();
+        this.lastModified = bokQuestion.getLastModified();
+    }
+
     /**
      * 检查答案是否正确
      *

+ 1 - 2
src/main/java/nju/seec/helper/vo/question/BokQuestion.java

@@ -15,8 +15,7 @@ import java.util.Set;
  */
 @Data
 public class BokQuestion {
-    @JsonProperty(value = "tq_id")
-    private String tqId;
+    private String id;
     private String type;
     private String stem;
     private Map<String, String> options;

+ 1 - 2
src/main/java/nju/seec/helper/vo/question/ChoiceQuestionVO.java

@@ -19,9 +19,8 @@ public class ChoiceQuestionVO extends BaseQuestionVO {
     private String answer;
 
     public ChoiceQuestionVO(@NonNull BokQuestion bokQuestion, boolean withAnswer) {
-        this.id = bokQuestion.getTqId();
+        super(bokQuestion);
         this.type = QuestionType.CHOICE;
-        this.stem = bokQuestion.getStem();
         this.options = bokQuestion.getOptions();
 
         if (withAnswer) {

+ 1 - 2
src/main/java/nju/seec/helper/vo/question/TrueOrFalseQuestionVO.java

@@ -22,9 +22,8 @@ public class TrueOrFalseQuestionVO extends BaseQuestionVO {
     }
 
     public TrueOrFalseQuestionVO(@NonNull BokQuestion bokQuestion, boolean withAnswer) {
-        this.id = bokQuestion.getTqId();
+        super(bokQuestion);
         this.type = QuestionType.TRUE_FALSE;
-        this.stem = bokQuestion.getStem();
 
         if (withAnswer) {
             this.answer = Boolean.valueOf(bokQuestion.getAnswer());

+ 3 - 1
src/main/resources/application-dev.yml

@@ -51,7 +51,9 @@ aliyun:
     signName: seeccoder
     templateCode: SMS_181555598
 bok:
-  url: http://bok.seecoder.cn
+  url: http://120.78.159.171:6060
+  searchUrl: ${bok.url}/api/question/search/
+  tqUrl: ${bok.url}/api/question/
 guavaCache:
   maximumSize: 1000
   expireAfterAccessInSeconds: 0