Преглед изворни кода

feat: 增加获取做过题目,获取收藏评价题目的功能

ChenSiTong пре 6 година
родитељ
комит
cc6267a88f

+ 1 - 1
pom.xml

@@ -10,7 +10,7 @@
     </parent>
     <groupId>cn.seecoder</groupId>
     <artifactId>helper-backend</artifactId>
-    <version>5.2.0</version>
+    <version>5.3.0</version>
     <packaging>jar</packaging>
     <name>helper</name>
     <description>backend</description>

+ 4 - 8
src/main/java/nju/seec/helper/api/bok/QuestionApi.java

@@ -1,6 +1,5 @@
 package nju.seec.helper.api.bok;
 
-import cn.hutool.core.util.StrUtil;
 import com.google.common.collect.ImmutableMap;
 import lombok.Data;
 import nju.seec.helper.api.bok.constant.QuestionType;
@@ -13,6 +12,7 @@ import nju.seec.helper.enums.ExceptionType;
 import nju.seec.helper.exception.HelperException;
 import nju.seec.helper.util.Consts;
 import nju.seec.helper.util.RestRequestUtil;
+import nju.seec.helper.util.StringUtils;
 import nju.seec.helper.util.cache.CaffeineCacheUtils;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.data.domain.Page;
@@ -51,13 +51,9 @@ public class QuestionApi {
                 .put("stem", stem)
                 .put("tag", tag)
                 .put("knowledgeId", knowledgeId)
-                .put("page", String.valueOf(1 + pageable.getPageNumber()))
-                .put("size", String.valueOf(pageable.getPageSize()))
-                .put("sort", pageable.getSort().stream()
-                        .map(order -> StrUtil.concat(true, "sort=", order.getProperty(), ",", order.getDirection().toString()))
-                        .reduce((o1, o2) -> StrUtil.join("&", o1, o2))
-                        .orElse("")
-                )
+                .put("page", 1 + pageable.getPageNumber())
+                .put("size", pageable.getPageSize())
+                .put("sort", StringUtils.getSortString(pageable.getSort()))
                 .build();
         PageableQuestionListVO result = restRequestUtil.sendGetRequest(searchUrl, PageableQuestionListVO.class, urlParams);
 

+ 21 - 3
src/main/java/nju/seec/helper/api/bok/RecordApi.java

@@ -1,14 +1,21 @@
 package nju.seec.helper.api.bok;
 
+import com.google.common.collect.ImmutableMap;
 import lombok.Data;
 import nju.seec.helper.api.bok.dto.RecordDTO;
+import nju.seec.helper.api.bok.vo.PageableQuestionListVO;
+import nju.seec.helper.api.bok.vo.QuestionVO;
 import nju.seec.helper.util.RestRequestUtil;
+import nju.seec.helper.util.StringUtils;
 import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.scheduling.annotation.Async;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.Pageable;
 import org.springframework.stereotype.Component;
 
 import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Map;
 
 /**
  * @author cst
@@ -18,12 +25,12 @@ import java.util.List;
 @Data
 public class RecordApi {
     private String url;
+    private String questionUrl;
     private final RestRequestUtil restRequestUtil;
 
-    @Async
     public void reportUserRecord(List<RecordDTO> records) {
         System.out.println(records);
-        restRequestUtil.sendPostRequest(url, records, String.class);
+//        restRequestUtil.sendPostRequest(url, records, String.class);
     }
 
     public RecordDTO createRecordDTO(String studentId, String questionId, Object answer, Boolean pass, LocalDateTime startAt, LocalDateTime finishAt) {
@@ -36,4 +43,15 @@ public class RecordApi {
                 .finishAt(finishAt)
                 .build();
     }
+
+    public Page<QuestionVO> getCompletedQuestions(String userId, Pageable pageable) {
+        Map<String, ?> uriParams = ImmutableMap.of(
+                "userId", userId,
+                "page", pageable.getPageNumber() + 1,
+                "size", pageable.getPageSize(),
+                "sort", StringUtils.getSortString(pageable.getSort())
+        );
+        PageableQuestionListVO questionListVO = restRequestUtil.sendGetRequest(questionUrl, PageableQuestionListVO.class, uriParams);
+        return new PageImpl<>(questionListVO.getQuestions(), pageable, questionListVO.getPage().getTotalElements());
+    }
 }

+ 22 - 0
src/main/java/nju/seec/helper/api/bok/UserInteractiveApi.java

@@ -4,12 +4,20 @@ import com.google.common.collect.ImmutableMap;
 import lombok.Data;
 import nju.seec.helper.api.bok.dto.CollectDTO;
 import nju.seec.helper.api.bok.dto.RateDTO;
+import nju.seec.helper.api.bok.vo.PageableQuestionListVO;
+import nju.seec.helper.api.bok.vo.QuestionVO;
 import nju.seec.helper.util.Consts;
 import nju.seec.helper.util.RestRequestUtil;
+import nju.seec.helper.util.StringUtils;
 import nju.seec.helper.util.cache.CaffeineCacheUtils;
 import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.Pageable;
 import org.springframework.stereotype.Component;
 
+import java.util.Map;
+
 /**
  * @author cst
  */
@@ -38,4 +46,18 @@ public class UserInteractiveApi {
     public void rateQuestion(String userId, String questionId, RateDTO rateDTO) {
         restRequestUtil.sendPostRequest(rateUrl, rateDTO, Object.class, ImmutableMap.of("userId", userId, "questionId", questionId));
     }
+
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public Page<QuestionVO> getCollectAndRateQuestions(String userId, Boolean collect, Boolean rate, Pageable pageable) {
+        Map<String, ?> uriParams = (Map) ImmutableMap.builder()
+                .put("userId", userId)
+                .put("collection", collect)
+                .put("rate", rate)
+                .put("page", pageable.getPageNumber() + 1)
+                .put("size", pageable.getPageSize())
+                .put("sort", StringUtils.getSortString(pageable.getSort()))
+                .build();
+        PageableQuestionListVO pageableQuestionListVO = restRequestUtil.sendGetRequest(questionUrl, PageableQuestionListVO.class, uriParams);
+        return new PageImpl<>(pageableQuestionListVO.getQuestions(), pageable, pageableQuestionListVO.getPage().getTotalElements());
+    }
 }

+ 1 - 4
src/main/java/nju/seec/helper/api/dataanalysis/EventApi.java

@@ -8,9 +8,7 @@ import nju.seec.helper.api.dataanalysis.dto.EventDTO;
 import nju.seec.helper.entity.Course;
 import nju.seec.helper.entity.Quiz;
 import nju.seec.helper.util.RestRequestUtil;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 
 import java.time.LocalDateTime;
@@ -28,8 +26,7 @@ public class EventApi {
     public EventApi(RestRequestUtil restRequestUtil) {
         this.restRequestUtil = restRequestUtil;
     }
-
-    @Async
+    
     public void reportEvent(EventDTO eventDTO) {
         System.out.println(eventDTO);
 //            restRequestUtil.sendPostRequest(eventReportUrl, event, Boolean.class);

+ 0 - 2
src/main/java/nju/seec/helper/api/dataanalysis/ExerciseApi.java

@@ -4,7 +4,6 @@ import lombok.Data;
 import nju.seec.helper.api.dataanalysis.dto.ExerciseRecordDTO;
 import nju.seec.helper.util.RestRequestUtil;
 import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 
 import java.time.Duration;
@@ -25,7 +24,6 @@ public class ExerciseApi {
         this.restRequestUtil = restRequestUtil;
     }
 
-    @Async
     public void reportExercise(List<ExerciseRecordDTO> exerciseRecords) {
         System.out.println(exerciseRecords);
 //        restRequestUtil.sendPostRequest(exerciseReportUrl, exerciseRecords, Boolean.class);

+ 2 - 2
src/main/java/nju/seec/helper/aspect/event/EventAspect.java

@@ -12,7 +12,7 @@ import nju.seec.helper.dto.comment.CommentDTO;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.annotation.AfterReturning;
 import org.aspectj.lang.annotation.Aspect;
-import org.springframework.core.annotation.Order;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -27,7 +27,6 @@ import java.util.stream.Stream;
  */
 @Aspect
 @Component
-@Order(3)
 public class EventAspect {
     private final Map<EventType, Map<EventAction, EventHandler>> eventHandleMap;
 
@@ -84,6 +83,7 @@ public class EventAspect {
     private final EventHandler doNothing = (joinPoint, user) -> {
     };
 
+    @Async
     @SneakyThrows
     @AfterReturning("@annotation(event) && args(user,..)")
     public void reportEvent(JoinPoint joinPoint, Event event, LoginUser user) {

+ 2 - 1
src/main/java/nju/seec/helper/aspect/message/MessageAspect.java

@@ -17,6 +17,7 @@ import nju.seec.helper.vo.quiz.QuizVO;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.annotation.AfterReturning;
 import org.aspectj.lang.annotation.Aspect;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 
 import java.util.Collections;
@@ -79,7 +80,6 @@ public class MessageAspect {
                 .put(MessageType.QUIZ_NEW, new MessageCreator() {
                     @Override
                     public void createMessage(JoinPoint joinPoint, Object object) {
-
                         if (object instanceof QuizVO) {
                             sendMessages(quizDAO.findQuizById(((QuizVO) object).getId()));
                         } else {
@@ -107,6 +107,7 @@ public class MessageAspect {
                 .build();
     }
 
+    @Async
     @AfterReturning(value = "@annotation(message)", returning = "object")
     public void createMessage(JoinPoint joinPoint, Message message, Object object) {
         messageCreatorMap.getOrDefault(message.value(), doNothing).createMessage(joinPoint, object);

+ 8 - 7
src/main/java/nju/seec/helper/aspect/record/ExerciseRecordAspect.java

@@ -10,7 +10,7 @@ import nju.seec.helper.dto.quiz.QuizStudentAnswerDTO;
 import nju.seec.helper.vo.quiz.QuizStudentAnswerVO;
 import org.aspectj.lang.annotation.AfterReturning;
 import org.aspectj.lang.annotation.Aspect;
-import org.springframework.core.annotation.Order;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 
 import java.util.List;
@@ -21,7 +21,6 @@ import java.util.Map;
  */
 @Aspect
 @Component
-@Order(2)
 public class ExerciseRecordAspect {
     private final ExerciseApi exerciseApi;
     private final RecordApi recordApi;
@@ -31,6 +30,7 @@ public class ExerciseRecordAspect {
         this.recordApi = recordApi;
     }
 
+    @Async
     @AfterReturning(value = "@annotation(exerciseRecord) && args(user,..,quizStudentAnswerDTO)", returning = "quizStudentAnswerVO", argNames = "exerciseRecord,user,quizStudentAnswerDTO,quizStudentAnswerVO")
     public void reportExerciseRecordEvent(ExerciseRecord exerciseRecord, LoginUser user, QuizStudentAnswerDTO quizStudentAnswerDTO, QuizStudentAnswerVO quizStudentAnswerVO) {
         String userId = String.valueOf(user.getPid());
@@ -38,15 +38,16 @@ public class ExerciseRecordAspect {
         int size = quizStudentAnswerDTO.getRecords().size();
         List<ExerciseRecordDTO> exerciseRecords = Lists.newArrayListWithExpectedSize(size);
         List<RecordDTO> records = Lists.newArrayListWithExpectedSize(size);
-
         quizStudentAnswerDTO
                 .getRecords()
                 .forEach((key, value) -> {
                     QuizStudentAnswerVO.QuestionStudentAnswerInfo studentAnswerInfo = questionStudentAnswerInfoMap.get(key);
-                    Boolean pass = studentAnswerInfo.getCorrect();
-                    Object answer = studentAnswerInfo.getAnswer();
-                    exerciseRecords.add(exerciseApi.createExerciseRecordDTO(userId, key, value.getStartAt(), value.getEndAt(), pass));
-                    records.add(recordApi.createRecordDTO(userId, key, answer, pass, value.getStartAt(), value.getEndAt()));
+                    if (studentAnswerInfo != null) {
+                        Boolean pass = studentAnswerInfo.getCorrect();
+                        Object answer = studentAnswerInfo.getAnswer();
+                        exerciseRecords.add(exerciseApi.createExerciseRecordDTO(userId, key, value.getStartAt(), value.getEndAt(), pass));
+                        records.add(recordApi.createRecordDTO(userId, key, answer, pass, value.getStartAt(), value.getEndAt()));
+                    }
                 });
         exerciseApi.reportExercise(exerciseRecords);
         recordApi.reportUserRecord(records);

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

@@ -129,4 +129,25 @@ public class QuestionController {
     public void rateQuestion(LoginUser user, @PathVariable String questionId, @Validated @RequestBody RateDTO rateDTO) {
         questionService.rateQuestion(user, questionId, rateDTO);
     }
+
+    /**
+     * 取得收藏评价的题目
+     */
+    @Auth(roles = UserRole.STUDENT, message = "取得收藏评价的题目")
+    @GetMapping("/collect-rate")
+    public PageResponse<BaseQuestionVO> getCollectAndRateQuestions(LoginUser user,
+                                                                   @RequestParam(required = false, defaultValue = "true") Boolean collect,
+                                                                   @RequestParam(required = false, defaultValue = "true") Boolean rate,
+                                                                   @PageableDefault(size = Integer.MAX_VALUE) Pageable pageable) {
+        return PageResponse.of(questionService.getCollectAndRateQuestions(user, collect, rate, pageable));
+    }
+
+    /**
+     * 取得完成的题目
+     */
+    @Auth(roles = UserRole.STUDENT, message = "取得完成的题目")
+    @GetMapping("/completed")
+    public PageResponse<BaseQuestionVO> getCompletedQuestions(LoginUser user, @PageableDefault(size = Integer.MAX_VALUE) Pageable pageable) {
+        return PageResponse.of(questionService.getCompletedQuestions(user, pageable));
+    }
 }

+ 16 - 1
src/main/java/nju/seec/helper/dao/QuizDAO.java

@@ -64,7 +64,7 @@ public interface QuizDAO extends JpaRepository<Quiz, Long>, JpaSpecificationExec
      * @param pageable
      * @return
      */
-    default Page<Quiz> findByCourseIdsAndStateAndNameContains(Set<Long> courseIds, QuizState quizState, String name, Pageable pageable) {
+    default Page<Quiz> findByCourseIdInAndStateAndNameContains(Set<Long> courseIds, QuizState quizState, String name, Pageable pageable) {
         return this.findAll(
                 (Specification<Quiz>) (root, query, cb) -> cb.and(
                         root.get("course").get("id").in(courseIds)
@@ -94,4 +94,19 @@ public interface QuizDAO extends JpaRepository<Quiz, Long>, JpaSpecificationExec
      * @return
      */
     Page<Quiz> findByTeacherAndStateAndNameContains(User teacher, QuizState quizState, String name, Pageable pageable);
+
+    /**
+     * 根据课程ID和状态获取测试
+     *
+     * @param courseIds
+     * @param states
+     * @return
+     */
+    default List<Quiz> findByCourseIdInAndStateIn(Set<Long> courseIds, Set<QuizState> states) {
+        return this.findAll(
+                (Specification<Quiz>) (root, query, cb) -> cb.and(
+                        root.get("course").get("id").in(courseIds)
+                        , root.get("state").in(states)
+                ));
+    }
 }

+ 3 - 0
src/main/java/nju/seec/helper/dto/quiz/QuizStudentAnswerDTO.java

@@ -5,6 +5,7 @@ import lombok.Data;
 import javax.validation.Valid;
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Past;
 import java.io.Serializable;
 import java.time.LocalDateTime;
 import java.util.Map;
@@ -25,9 +26,11 @@ public class QuizStudentAnswerDTO implements Serializable {
     @Data
     public static class AnswerRecordDTO {
         @NotNull(message = "缺少做题开始时间")
+        @Past(message = "开始时间错误")
         private LocalDateTime startAt;
 
         @NotNull(message = "缺少做题结束时间")
+        @Past(message = "结束时间错误")
         private LocalDateTime endAt;
     }
 }

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

@@ -116,4 +116,24 @@ public interface QuestionService {
      * @param rateDTO
      */
     void rateQuestion(LoginUser user, String questionId, RateDTO rateDTO);
+
+    /**
+     * 取得收藏评价的题目
+     *
+     * @param user
+     * @param collect
+     * @param rate
+     * @param pageable
+     * @return
+     */
+    Page<BaseQuestionVO> getCollectAndRateQuestions(LoginUser user, Boolean collect, Boolean rate, Pageable pageable);
+
+    /**
+     * 取得完成的题目
+     *
+     * @param user
+     * @param pageable
+     * @return
+     */
+    Page<BaseQuestionVO> getCompletedQuestions(LoginUser user, Pageable pageable);
 }

+ 1 - 3
src/main/java/nju/seec/helper/service/impl/MessageServiceImpl.java

@@ -8,7 +8,6 @@ import nju.seec.helper.enums.MessageType;
 import nju.seec.helper.service.MessageService;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
-import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -25,8 +24,7 @@ public class MessageServiceImpl implements MessageService {
     public MessageServiceImpl(MessageDAO messageDAO) {
         this.messageDAO = messageDAO;
     }
-
-    @Async
+    
     @Override
     public void createMessage(Set<Long> toUserIds, MessageType type, String content, Long refId) {
         messageDAO.saveAll(

+ 34 - 1
src/main/java/nju/seec/helper/service/impl/QuestionServiceImpl.java

@@ -1,11 +1,15 @@
 package nju.seec.helper.service.impl;
 
 import cn.hutool.core.util.IdUtil;
-import nju.seec.helper.api.bok.*;
+import nju.seec.helper.api.bok.QuestionApi;
+import nju.seec.helper.api.bok.QuestionQueryApi;
+import nju.seec.helper.api.bok.RecordApi;
+import nju.seec.helper.api.bok.UserInteractiveApi;
 import nju.seec.helper.api.bok.dto.CollectDTO;
 import nju.seec.helper.api.bok.dto.RateDTO;
 import nju.seec.helper.api.bok.vo.QuestionVO;
 import nju.seec.helper.aspect.auth.LoginUser;
+import nju.seec.helper.dao.ChooseDAO;
 import nju.seec.helper.dao.QuestionRecordDAO;
 import nju.seec.helper.dao.QuizDAO;
 import nju.seec.helper.dao.UserDAO;
@@ -25,6 +29,7 @@ import org.springframework.data.domain.Pageable;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -39,25 +44,31 @@ public class QuestionServiceImpl implements QuestionService {
     private final QuizDAO quizDAO;
     private final UserDAO userDAO;
     private final QuestionRecordDAO questionRecordDAO;
+    private final ChooseDAO chooseDAO;
 
     private final QuestionApi questionApi;
     private final QuestionQueryApi questionQueryApi;
     private final UserInteractiveApi userInteractiveApi;
+    private final RecordApi recordApi;
 
     @Autowired
     public QuestionServiceImpl(QuizDAO quizDAO
             , UserDAO userDAO
             , QuestionRecordDAO questionRecordDAO
+            , ChooseDAO chooseDAO
             , QuestionApi questionApi
             , QuestionQueryApi questionQueryApi
             , UserInteractiveApi userInteractiveApi
+            , RecordApi recordApi
     ) {
         this.quizDAO = quizDAO;
         this.userDAO = userDAO;
         this.questionRecordDAO = questionRecordDAO;
+        this.chooseDAO = chooseDAO;
         this.questionApi = questionApi;
         this.questionQueryApi = questionQueryApi;
         this.userInteractiveApi = userInteractiveApi;
+        this.recordApi = recordApi;
     }
 
     @Override
@@ -156,4 +167,26 @@ public class QuestionServiceImpl implements QuestionService {
     public void rateQuestion(LoginUser user, String questionId, RateDTO rateDTO) {
         userInteractiveApi.rateQuestion(String.valueOf(user.getPid()), questionId, rateDTO);
     }
+
+    @Transactional(readOnly = true)
+    @Override
+    public Page<BaseQuestionVO> getCollectAndRateQuestions(LoginUser user, Boolean collect, Boolean rate, Pageable pageable) {
+        return convert(user, userInteractiveApi.getCollectAndRateQuestions(String.valueOf(user.getPid()), collect, rate, pageable));
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public Page<BaseQuestionVO> getCompletedQuestions(LoginUser user, Pageable pageable) {
+        return convert(user, recordApi.getCompletedQuestions(String.valueOf(user.getPid()), pageable));
+    }
+
+    private static final Set<QuizState> HIDE_QUESTION_ANSWER_STATES = Collections.singleton(QuizState.ONGOING);
+
+    private Page<BaseQuestionVO> convert(LoginUser user, Page<QuestionVO> questionVOList) {
+        List<String> hideQuestionIds = quizDAO.findByCourseIdInAndStateIn(chooseDAO.findCourseIdsByStudentId(user.getId()), HIDE_QUESTION_ANSWER_STATES)
+                .stream()
+                .flatMap(quiz -> quiz.getQuestions().stream())
+                .collect(Collectors.toList());
+        return questionVOList.map(questionVO -> BaseQuestionVO.convertBokQuestionToVO(questionVO, !hideQuestionIds.contains(questionVO.getId())));
+    }
 }

+ 1 - 1
src/main/java/nju/seec/helper/service/impl/QuizServiceImpl.java

@@ -171,7 +171,7 @@ public class QuizServiceImpl implements QuizService {
             throw HelperException.of(ExceptionType.FORBIDDEN, "您暂时无法获取该状态的测试");
         }
         Set<Long> courseIds = chooseDAO.findCourseIdsByStudentId(user.getId());
-        return quizDAO.findByCourseIdsAndStateAndNameContains(courseIds, state, key, pageable).map(QuizVO::new);
+        return quizDAO.findByCourseIdInAndStateAndNameContains(courseIds, state, key, pageable).map(QuizVO::new);
     }
 
     @Transactional(readOnly = true)

+ 9 - 0
src/main/java/nju/seec/helper/util/StringUtils.java

@@ -1,6 +1,8 @@
 package nju.seec.helper.util;
 
+import cn.hutool.core.util.StrUtil;
 import lombok.experimental.UtilityClass;
+import org.springframework.data.domain.Sort;
 
 /**
  * @author cst
@@ -10,4 +12,11 @@ public class StringUtils {
     public String keyPattern(String key) {
         return '%' + key + '%';
     }
+
+    public String getSortString(Sort sort) {
+        return sort.stream()
+                .map(order -> StrUtil.concat(true, "sort=", order.getProperty(), ",", order.getDirection().toString()))
+                .reduce((o1, o2) -> StrUtil.join("&", o1, o2))
+                .orElse("");
+    }
 }

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

@@ -71,6 +71,7 @@ bok:
     question-url: ${bok.user.url}/{userId}/question?collection={collection}&rate={rate}
   record:
     url: ${bok.api-url}/record
+    question-url: ${bok.record.url}/{userId}/question?page={page}&size={size}&{sort}
 #  tqUrl: ${bok.url}/api/question
 #  tqSearchUrl: ${bok.tqUrl}
 #  tqStemSearchUrl: ${bok.tqSearchUrl}/findByStemLike?content={content}&size={size}&page={page}