package com.qxgmat.controller.api;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.Page;
import com.nuliji.tools.*;
import com.nuliji.tools.exception.AuthException;
import com.nuliji.tools.exception.ParameterException;
import com.nuliji.tools.exception.SystemException;
import com.qxgmat.data.constants.enums.*;
import com.qxgmat.data.constants.enums.module.*;
import com.qxgmat.data.constants.enums.status.AnswerStatus;
import com.qxgmat.data.constants.enums.status.DirectionStatus;
import com.qxgmat.data.constants.enums.user.DataType;
import com.qxgmat.data.dao.entity.*;
import com.qxgmat.data.inline.PaperStat;
import com.qxgmat.data.inline.UserQuestionStat;
import com.qxgmat.data.relation.entity.*;
import com.qxgmat.dto.extend.*;
import com.qxgmat.dto.request.*;
import com.qxgmat.dto.request.CommentDto;
import com.qxgmat.dto.request.FaqDto;
import com.qxgmat.dto.request.UserCollectQuestionDto;
import com.qxgmat.dto.request.UserNoteQuestionDto;
import com.qxgmat.dto.response.*;
import com.qxgmat.help.AiHelp;
import com.qxgmat.help.MailHelp;
import com.qxgmat.help.ShiroHelp;
import com.qxgmat.service.*;
import com.qxgmat.service.extend.*;
import com.qxgmat.service.inline.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.validation.Validator;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by GaoJie on 2017/10/31.
 */
@RestController
@RequestMapping("/api/my")
@Api(tags = "用户接口", description = "获取与操作当前用户信息", produces = MediaType.APPLICATION_JSON_VALUE)
public class MyController {

    @Value("${upload.local_path}")
    private String localPath;

    @Value("${upload.web_url}")
    private String webUrl;

    @Autowired
    private Validator validator;

    @Autowired
    private ShiroHelp shiroHelp;

    @Autowired
    private AiHelp aiHelp;

    @Autowired
    private MailHelp mailHelp;

    @Autowired
    private PreviewService previewService;

    @Autowired
    private SettingService settingService;

    @Autowired
    private ExerciseStructService exerciseStructService;

    @Autowired
    private CourseService courseService;

    @Autowired
    private CourseNoService courseNoService;

    @Autowired
    private QuestionService questionService;

    @Autowired
    private QuestionNoService questionNoService;

    @Autowired
    private SentenceQuestionService sentenceQuestionService;

    @Autowired
    private TextbookQuestionService textbookQuestionService;

    @Autowired
    private TextbookTopicService textbookTopicService;

    @Autowired
    private TextbookLibraryService textbookLibraryService;

    @Autowired
    private CourseDataService courseDataService;

    @Autowired
    private CourseExperienceService courseExperienceService;

    @Autowired
    private CourseDataHistoryService courseDataHistoryService;

    @Autowired
    private CourseTeacherService courseTeacherService;

    @Autowired
    private ExaminationPaperService examinationPaperService;

    @Autowired
    private FaqService faqService;

    @Autowired
    private CommentService commentService;

    @Autowired
    private PreviewAssignService previewAssignService;

    @Autowired
    private UsersService usersService;

    @Autowired
    private UserMessageService userMessageService;

    @Autowired
    private UserCourseRecordService userCourseRecordService;

    @Autowired
    private UserCourseAppointmentService userCourseAppointmentService;

    @Autowired
    private UserCourseAppointmentCommentService userCourseAppointmentCommentService;

    @Autowired
    private UserCourseProgressService userCourseProgressService;

    @Autowired
    private UserCourseDataSubscribeService userCourseDataSubscribeService;

    @Autowired
    private UserSentenceRecordService userSentenceRecordService;

    @Autowired
    private UserServiceService userServiceService;

    @Autowired
    private UserExportService userExportService;

    @Autowired
    private UserCollectQuestionService userCollectQuestionService;

    @Autowired
    private UserCollectExperienceService userCollectExperienceService;

    @Autowired
    private UserNoteQuestionService userNoteQuestionService;

    @Autowired
    private UserNoteCourseService userNoteCourseService;

    @Autowired
    private UserAskQuestionService userAskQuestionService;

    @Autowired
    private UserAskCourseService userAskCourseService;

    @Autowired
    private UserFeedbackErrorService userFeedbackErrorService;

    @Autowired
    private UserTextbookFeedbackService userTextbookFeedbackService;

    @Autowired
    private UserReadyRoomFeedbackService userReadyRoomFeedbackService;

    @Autowired
    private UserQuestionService userQuestionService;

    @Autowired
    private UserReportService userReportService;

    @Autowired
    private UserPaperService userPaperService;

    @Autowired
    private UserPaperQuestionService userPaperQuestionService;

    @Autowired
    private UserOrderService userOrderService;

    @Autowired
    private UserOrderRecordService userOrderRecordService;

    @Autowired
    private UserOrderCheckoutService userOrderCheckoutService;

    @Autowired
    private QuestionFlowService questionFlowService;

    @Autowired
    private SentenceService sentenceService;

    @Autowired
    private CourseExtendService courseExtendService;

    @Autowired
    private OrderFlowService orderFlowService;

    @Autowired
    private MessageExtendService messageExtendService;

    @Autowired
    private ExportService exportService;

    @Autowired
    private UserSearchHistoryService userSearchHistoryService;

    @RequestMapping(value = "/email", method = RequestMethod.POST)
    @ApiOperation(value = "绑定邮箱", httpMethod = "POST")
    public Response<Boolean> email(@RequestBody @Validated UserEmailDto dto, HttpSession session, HttpServletRequest request) {
        User user = (User) shiroHelp.getLoginUser();
        User in = usersService.get(user.getId());
        if (in.getEmail().equals(dto.getEmail())) {
            return ResponseHelp.success(true);
        }
        User other = usersService.getByEmail(dto.getEmail());
        if (other != null){
            throw new ParameterException("该邮箱已绑定其他账号,请更换邮箱地址。");
        }
        usersService.edit(User.builder()
                .id(user.getId())
                .email(dto.getEmail())
                .build());
        messageExtendService.sendEmailChange(in, dto.getEmail());
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/mobile", method = RequestMethod.POST)
    @ApiOperation(value = "绑定手机", httpMethod = "POST")
    public Response<Boolean> mobile(@RequestBody @Validated UserMobileDto dto, HttpSession session, HttpServletRequest request) {
        User user = (User) shiroHelp.getLoginUser();
        User in = usersService.get(user.getId());
        if (in.getArea().equals(dto.getArea()) && in.getMobile().equals(dto.getMobile())) {
            return ResponseHelp.success(true);
        }
        User other = usersService.getByMobile(dto.getArea(), dto.getMobile());
        if (other != null){
            throw new ParameterException("该手机已绑定其他账号,请更换手机号码");
        }
        usersService.edit(User.builder()
                .id(user.getId())
                .area(dto.getArea())
                .mobile(dto.getMobile())
                .build());
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/info", method = RequestMethod.POST)
    @ApiOperation(value = "修改用户信息", httpMethod = "POST")
    public Response<Boolean> info(@RequestBody @Validated UserInfoDto dto){
        User user = (User) shiroHelp.getLoginUser();
        usersService.edit(User.builder()
                .id(user.getId())
                .nickname(dto.getNickname())
                .avatar(dto.getAvatar())
                .build());
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/real/front", method = RequestMethod.POST)
    @ApiOperation(value = "实名认证", notes = "保存用户实名信息", httpMethod = "POST")
    public Response<UserRealDto> realFront(@RequestParam("file") MultipartFile file) throws IOException {
        if (file.isEmpty()) {
            throw new ParameterException("上传文件为空");
        }
        User user = (User) shiroHelp.getLoginUser();
        UserRealDto dto = new UserRealDto();

        Map<String, String> map = aiHelp.orcIdcardFront(file.getBytes());
        dto.setName(map.get("name"));
        dto.setAddress(map.get("address"));
        dto.setIdentity(map.get("identity"));

        User in = usersService.getByIdentity(map.get("identity"));
        if (in != null && !in.getId().equals(user.getId())){
            throw new ParameterException("该身份证已被其他账号认证");
        }

        String filename = file.getOriginalFilename();
        String suffix = filename.substring(filename.lastIndexOf(".")+1);
        String frontName = UUID.randomUUID().toString()+"."+suffix;
        try {
            File dir = new File(localPath);
            File dest = new File(dir.getAbsolutePath() + File.separator+frontName);
            file.transferTo(dest);
            dto.setPhotoFront(webUrl+frontName);
            usersService.edit(User.builder()
                    .id(user.getId())
                    .realAddress(dto.getAddress())
                    .realName(dto.getName())
                    .realIdentity(dto.getIdentity())
                    .realPhotoFront(dto.getPhotoFront())
                    .build());
            return ResponseHelp.success(dto);
        } catch (IOException e) {
            e.printStackTrace();
            return ResponseHelp.exception(new SystemException("图片上传失败"));
        }
    }

    @RequestMapping(value = "/real/back", method = RequestMethod.POST)
    @ApiOperation(value = "实名认证", notes = "保存用户实名信息", httpMethod = "POST")
    public Response<UserRealDto> realBack(@RequestParam("file") MultipartFile file) throws IOException {
        if (file.isEmpty()) {
            throw new ParameterException("上传文件为空");
        }
        User user = (User) shiroHelp.getLoginUser();
        UserRealDto dto = new UserRealDto();

        aiHelp.orcIdcardBack(file.getBytes());
        String filename = file.getOriginalFilename();
        String suffix = filename.substring(filename.lastIndexOf(".")+1);
        String backName = UUID.randomUUID().toString()+"."+suffix;
        try {
            File dir = new File(localPath);
            File dest = new File(dir.getAbsolutePath() + File.separator+backName);
            file.transferTo(dest);
            dto.setPhotoBack(webUrl+backName);
            usersService.edit(User.builder()
                    .id(user.getId())
                    .realPhotoBack(dto.getPhotoBack())
                    .build());
            return ResponseHelp.success(dto);
        } catch (IOException e) {
            e.printStackTrace();
            return ResponseHelp.exception(new SystemException("图片上传失败"));
        }
    }

    @RequestMapping(value = "/real/finish", method = RequestMethod.POST)
    @ApiOperation(value = "实名认证", notes = "保存用户实名信息", httpMethod = "POST")
    public Response<UserRealDto> realFinish() {
        User user = (User) shiroHelp.getLoginUser();
        UserRealDto dto = new UserRealDto();

        User in = usersService.get(user.getId());
        if (in.getRealAddress() == null || in.getRealAddress().equals("")){
            throw new ParameterException("实名认证流程错误");
        }
        if (in.getRealIdentity() == null || in.getRealIdentity().equals("")){
            throw new ParameterException("实名认证流程错误");
        }
        if (in.getRealName() == null || in.getRealName().equals("")){
            throw new ParameterException("实名认证流程错误");
        }
        if (in.getRealPhotoFront() == null || in.getRealPhotoFront().equals("")){
            throw new ParameterException("实名认证流程错误");
        }
        if (in.getRealPhotoBack() == null || in.getRealPhotoBack().equals("")){
            throw new ParameterException("实名认证流程错误");
        }

        usersService.edit(User.builder()
                .id(user.getId())
                .realStatus(1)
                .realTime(new Date())
                .build());
        UserOrderRecord record = orderFlowService.giveReal(in);
        dto.setRecord(Transform.convert(record, UserOrderRecordExtendDto.class));
        return ResponseHelp.success(dto);
    }

    @RequestMapping(value = "/invite/email", method = RequestMethod.POST)
    @ApiOperation(value = "发送邮件邀请", httpMethod = "POST")
    public Response<Boolean> inviteEmail(@RequestBody @Validated InviteEmailDto dto)  {
        User user = (User) shiroHelp.getLoginUser();
        messageExtendService.sendInviteEmail(user, dto.getEmails(), user.getInviteCode());
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/vip/info", method = RequestMethod.GET)
    @ApiOperation(value = "vip信息", httpMethod = "GET")
    public Response<UserVipInfoDto> info(HttpSession session) {
        User user = (User) shiroHelp.getLoginUser();
        UserVipInfoDto dto = new UserVipInfoDto();

        if (user != null){
            UserService userService = userServiceService.getService(user.getId(), ServiceKey.VIP);
            dto.setHasService(userService != null);
            UserOrderRecord record = userOrderRecordService.getUnUseService(user.getId(), ServiceKey.VIP);
            dto.setUnUseRecord(Transform.convert(record, UserServiceRecordExtendDto.class));

            dto.setStartTime(userService!=null ? userService.getStartTime() : null);
            dto.setExpireTime(userService != null ? userService.getExpireTime() : null);
        }

        return ResponseHelp.success(dto);
    }

    @RequestMapping(value = "/message", method = RequestMethod.GET)
    @ApiOperation(value = "用户站内信", notes = "用户消息列表", httpMethod = "GET")
    public Response<PageMessage<UserMessage>> message(
            @RequestParam(required = false, defaultValue = "1") int page,
            @RequestParam(required = false, defaultValue = "100") int size,
            @RequestParam(required = false) String messageType,
            @RequestParam(required = false) Integer read
    )  {
        User user = (User) shiroHelp.getLoginUser();
        Page<UserMessage> p = userMessageService.list(page, size, user.getId(), MessageType.ValueOf(messageType), read);
        messageExtendService.refreshMessage(p);
        return ResponseHelp.success(p, page, size, p.getTotal());
    }

    @RequestMapping(value = "/message/read", method = RequestMethod.PUT)
    @ApiOperation(value = "读取消息", notes = "读取用户消息/全部", httpMethod = "PUT")
    public Response<Boolean> readMessage(@RequestBody @Validated MessageReadDto dto)  {
        User user = (User) shiroHelp.getLoginUser();

        if (dto.getAll()){
            userMessageService.clearAll(user.getId());
        }else{
            userMessageService.clear(user.getId(), dto.getId());
        }

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/clear/exercise/latest", method = RequestMethod.PUT)
    @ApiOperation(value = "清除最后一次做题记录", notes = "清除最后一次做题记录", httpMethod = "PUT")
    public Response<Boolean> clearLatestExercise()  {
        User user = (User) shiroHelp.getLoginUser();
        usersService.edit(User.builder().id(user.getId()).latestExercise(0).build());

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/clear/error/latest", method = RequestMethod.PUT)
    @ApiOperation(value = "清除最后一次错题组卷做题记录", notes = "清除最后一次错题组卷做题记录", httpMethod = "PUT")
    public Response<Boolean> clearLatestError()  {
        User user = (User) shiroHelp.getLoginUser();
        usersService.edit(User.builder().id(user.getId()).latestError(0).build());

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/clear/collect/latest", method = RequestMethod.PUT)
    @ApiOperation(value = "清除最后一次收藏组卷做题记录", notes = "清除最后一次收藏组卷做题记录", httpMethod = "PUT")
    public Response<Boolean> clearLatestCollect()  {
        User user = (User) shiroHelp.getLoginUser();
        usersService.edit(User.builder().id(user.getId()).latestCollect(0).build());

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/prepare", method = RequestMethod.PUT)
    @ApiOperation(value = "修改备考信息", notes = "修改用户备考信息", httpMethod = "PUT")
    public Response<Boolean> editPrepare(@RequestBody @Validated UserPrepareDto dto)  {
        User entity = Transform.dtoToEntity(dto);
        User user = (User) shiroHelp.getLoginUser();
        entity.setId(user.getId());
        if (user.getPrepareTime() == null){
            // 邀请奖励
            orderFlowService.givePrepare(user);
        }
        entity.setPrepareTime(new Date());
        usersService.edit(entity);

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/prepare", method = RequestMethod.GET)
    @ApiOperation(value = "获取备考信息", notes = "获取备考信息及分布", httpMethod = "GET")
    public Response<UserPrepareDetailDto> getPrepare()  {
        User user = (User) shiroHelp.getLoginUser();
        User entity = usersService.get(user.getId());
        UserPrepareDetailDto dto = Transform.convert(entity, UserPrepareDetailDto.class);

        Setting settingStat = settingService.getByKey(SettingKey.PREPARE_STAT);
        JSONObject valueStat = settingStat.getValue();
        dto.setStat(valueStat);

        Setting settingInfo = settingService.getByKey(SettingKey.PREPARE_INFO);
        JSONObject valueInfo = settingInfo.getValue();
        dto.setInfo(valueInfo);
        return ResponseHelp.success(dto);
    }

    @RequestMapping(value = "/study", method = RequestMethod.GET)
    @ApiOperation(value = "获取学习记录", notes = "获取选择那天的做题信息", httpMethod = "GET")
    public Response<UserStudyDayDto> studyTime(
            @RequestParam(required = false) String date
    )  {
        User user = (User) shiroHelp.getLoginUser();
        Date start;
        try {
            start = DateFormat.getDateInstance().parse(date);
        } catch (ParseException e) {
            throw new ParameterException("日期格式错误");
        }
        Date end = Tools.addDate(start, 1);
        String startTime = start.toString();
        String endTime = end.toString();
        UserStudyDayDto dto = new UserStudyDayDto();

        List<ExerciseStruct> p = exerciseStructService.main();
        Map<String, String> m = new HashMap<>();
        for (ExerciseStruct struct : p){
            if (struct.getExtend() == null || struct.getExtend().isEmpty()) continue;
            m.put(struct.getExtend(), struct.getTitleZh() + (struct.getTitleEn().isEmpty() ? "":" "+struct.getTitleEn()));
        }

        // 获取总用户数
        Integer total = usersService.count();

        // 获取练习统计 - 按题型进行分组统计
        Integer exerciseTime = 0;
        Integer exerciseQuestion = 0;
        List<UserExerciseExtendDto> exerciseList = new ArrayList<>();
        List<UserStudyStatRelation> typeList = userReportService.statGroupExerciseType(user.getId(), start, end);
        for(UserStudyStatRelation type:typeList){
            exerciseTime += type.getUserTime();
            exerciseQuestion += type.getUserNumber();
            exerciseList.add(new UserExerciseExtendDto(m.get(type.getModule()), type.getUserNumber(), type.getUserTime(), type.getUserCorrect()));
        }
        // todo 练习统计排行
        UserRankStatRelation exerciseRank = userReportService.rankExerciseByTime(user.getId(), start, end);
        if (exerciseRank != null)
            exerciseRank.setTotal(total);

        dto.setExerciseTime(exerciseTime);
        dto.setExerciseQuestion(exerciseQuestion);
        dto.setExerciseList(exerciseList);
        dto.setExerciseExceed(exerciseRank);

        // 获取模考统计 - 按卷子
        Integer examinationTime = 0;
        Integer examinationPaper = 0;
        List<UserReport> userReportList = userReportService.getByModule(user.getId(), PaperModule.EXAMINATION, start, end);
        Collection paperIds = Transform.getIds(userReportList, UserReport.class, "paperId");
        List<UserPaper> userPaperList = userPaperService.select(paperIds);
        Map userPaper = Transform.getMap(userPaperList, UserPaper.class, "id");
        List<UserPaperDetailExtendDto> examinationPaperList = new ArrayList<>(userReportList.size());
        for(UserReport report: userReportList){
            examinationTime += report.getUserTime();
            examinationPaper += 1;
            UserPaperDetailExtendDto d = Transform.convert(userPaper.get(report.getPaperId()), UserPaperDetailExtendDto.class);
            d.setReport(Transform.convert(report, UserReportExtendDto.class));
            examinationPaperList.add(d);
        }
        // todo 模考统计排行
        UserRankStatRelation examinationRank = userReportService.rankExaminationByTime(user.getId(), start, end);
        if (examinationRank != null)
            examinationRank.setTotal(total);

        dto.setExaminationTime(examinationTime);
        dto.setExaminationPaper(examinationPaper);
        dto.setExaminationList(examinationPaperList);
        dto.setExaminationExceed(examinationRank);

        // 获取课程访问记录 - 按课时
        Integer courseTime = 0;
        Integer courseNumber = 0;
        List<UserCourseRecord> userCourseRecordList = userCourseRecordService.getByTime(user.getId(), start, end);
        Collection courseIds = Transform.getIds(userCourseRecordList, UserCourseRecord.class, "courseId");
        Collection courseNoIds = Transform.getIds(userCourseRecordList, UserCourseRecord.class, "noId");
        List<Course> courseList = courseService.select(courseIds);
        Map courseMap = Transform.getMap(courseList, Course.class, "id");
        List<CourseNo> courseNoList = courseNoService.select(courseNoIds);
        Map courseNoMap = Transform.getMap(courseNoList, CourseNo.class, "id");
        List<UserCourseResultExtendDto> courseResultList = new ArrayList<>(userCourseRecordList.size());
        for(UserCourseRecord record:userCourseRecordList){
            courseTime += record.getUserTime();
            courseNumber += 1;
            UserCourseResultExtendDto d = Transform.convert(record, UserCourseResultExtendDto.class);
            d.setExtend(((Course)courseMap.get(record.getCourseId())).getExtend());
            d.setTitle(((CourseNo)courseNoMap.get(record.getCourseNoId())).getTitle());
            d.setNo(((CourseNo)courseNoMap.get(record.getCourseNoId())).getNo());
            courseResultList.add(d);
        }
        // todo 听课统计排行
        UserRankStatRelation courseRank = userCourseRecordService.rankByTime(user.getId(), start, end);
        if (courseRank != null)
            courseRank.setTotal(total);

        dto.setCourseTime(courseTime);
        // 课时数量:不按学完的课时计算
        dto.setCourseNumber(courseNoIds.size());
        dto.setCourseList(courseResultList);
        dto.setCourseExceed(courseRank);
        dto.setCourse(user.getIsCourse()!=null && user.getIsCourse() > 0);

        return ResponseHelp.success(dto);
    }

    @RequestMapping(value = "/study/week", method = RequestMethod.GET)
    @ApiOperation(value = "获取本周记录", notes = "获取本周学习记录", httpMethod = "GET")
    public Response<UserStudyDetailDto> studyWeekTime(
            @RequestParam(required = false) Integer week
    )  {
        User user = (User) shiroHelp.getLoginUser();
        UserStudyDetailDto dto = new UserStudyDetailDto();
        dto.setCreateTime(user.getCreateTime());
        dto.setDays((int)((new Date().getTime() - user.getCreateTime().getTime()) / (1000*3600*24)));

        Date now = Tools.today();
        int day = Tools.getDayOfWeek(now);
        Date start = Tools.addDate(now, -1 * (day + week * 7));
        Date end = Tools.addDate(start, 7);

        Integer time = 0;
        time += courseExtendService.studyTime(user.getId(), start, end);
        time += sentenceService.studyTime(user.getId(), start, end);
        time += questionFlowService.studyTime(user.getId(), start, end);
        dto.setTime(time);

        Integer avgTime = 0;
        avgTime += courseExtendService.studyAvgTime(start, end);
        avgTime += sentenceService.studyAvgTime(start, end);
        avgTime += questionFlowService.studyAvgTime(start, end);
        dto.setAvgTime(avgTime);

        return ResponseHelp.success(dto);
    }

    @RequestMapping(value = "/study/total", method = RequestMethod.GET)
    @ApiOperation(value = "获取总学习记录", notes = "获取总学习记录", httpMethod = "GET")
    public Response<UserStudyDetailDto> studyTotalTime()  {
        User user = (User) shiroHelp.getLoginUser();
        UserStudyDetailDto dto = new UserStudyDetailDto();
        dto.setCreateTime(user.getCreateTime());
        dto.setDays((int)((new Date().getTime() - user.getCreateTime().getTime()) / (1000*3600*24)));
        Integer totalTime = 0;
        Map<String, Integer> categoryMap = new HashMap<>();
        // 按模块来源分组查询: module=> sentence, examination, collect+error, 忽略exercise,preview
        List<UserStudyStatRelation> originList = userReportService.statGroupOrigin(user.getId());
        for(UserStudyStatRelation relation:originList){
            // 练习时间过滤
            if (relation.getModule().equals(PaperOrigin.EXERCISE.key) || relation.getModule().equals(PaperOrigin.PREVIEW.key)){
                continue;
            }
            Integer time = relation.getUserTime();
            String key = relation.getModule();
            totalTime += time;
            // 收藏及错误组卷合并
            if (relation.getModule().equals(PaperOrigin.COLLECT.key)
                    || relation.getModule().equals(PaperOrigin.ERROR.key)){
                key = "freedom";
                time += categoryMap.getOrDefault(key, 0);
            }
            categoryMap.put(key, time);
        }
        // 按题型统计练习
        List<UserStudyStatRelation> exerciseList = userReportService.statGroupExerciseType(user.getId(), null, null);
        for(UserStudyStatRelation type:exerciseList){
            totalTime += type.getUserTime();
            Integer time = categoryMap.getOrDefault(type.getModule(), 0);
            categoryMap.put(type.getModule(), time + type.getUserTime());
        }
        // 按题型统计预习作业
        List<UserStudyStatRelation> previewList = userReportService.statGroupPreviewType(user.getId(), null, null);
        for(UserStudyStatRelation type:previewList){
            totalTime += type.getUserTime();
            Integer time = categoryMap.getOrDefault(type.getModule(), 0);
            categoryMap.put(type.getModule(), time + type.getUserTime());
        }
        // 按题型统计课程
        List<UserModuleRecordStatRelation> recordList = userCourseRecordService.statGroupType(user.getId(), null, null);
        for (UserModuleRecordStatRelation record : recordList){
            totalTime += record.getUserTime();
            // 累加同类型时间
            Integer time = categoryMap.getOrDefault(record.getModule(), 0);
            categoryMap.put(record.getModule(), time + record.getUserTime());
        }
        // 获取长难句阅读统计
        UserRecordStatRelation sentenceStatRelation = userSentenceRecordService.stat(user.getId(), null, null);
        if (sentenceStatRelation != null){
            totalTime += sentenceStatRelation.getUserTime();
            Integer sentenceTime = categoryMap.getOrDefault(PaperModule.SENTENCE.key, 0);
            categoryMap.put(PaperModule.SENTENCE.key, sentenceTime + sentenceStatRelation.getUserTime());
        }

        List<ExerciseStruct> p = exerciseStructService.main();
        Map<String, String> m = new HashMap<>();
        for (ExerciseStruct struct : p){
            if (struct.getExtend() == null || struct.getExtend().isEmpty()) continue;
            m.put(struct.getExtend(), struct.getTitleZh() + (struct.getTitleEn().isEmpty() ? "":" "+struct.getTitleEn()));
        }

        // 组装数据
        List<UserStudyExtendDto> categorys = new ArrayList<>();
        if (categoryMap.containsKey(QuestionType.SENTENCE.key)) categorys.add(new UserStudyExtendDto(m.get(QuestionType.SENTENCE.key), categoryMap.get(PaperModule.SENTENCE.key)));
        if (categoryMap.containsKey(QuestionType.SC.key)) categorys.add(new UserStudyExtendDto(m.get(QuestionType.SC.key), categoryMap.get(QuestionType.SC.key)));
        if (categoryMap.containsKey(QuestionType.RC.key)) categorys.add(new UserStudyExtendDto(m.get(QuestionType.RC.key), categoryMap.get(QuestionType.RC.key)));
        if (categoryMap.containsKey(QuestionType.CR.key)) categorys.add(new UserStudyExtendDto(m.get(QuestionType.CR.key), categoryMap.get(QuestionType.CR.key)));
        if (categoryMap.containsKey(QuestionType.PS.key)){
            // 累加数学
            Integer time = categoryMap.getOrDefault(QuestionSubject.QUANT.key, 0);
            categoryMap.put(QuestionSubject.QUANT.key, time + categoryMap.get(QuestionType.PS.key));
        }
        if (categoryMap.containsKey(QuestionType.DS.key)){
            // 累加数学
            Integer time = categoryMap.getOrDefault(QuestionSubject.QUANT.key, 0);
            categoryMap.put(QuestionSubject.QUANT.key, time + categoryMap.get(QuestionType.DS.key));
        }
        if (categoryMap.containsKey(QuestionSubject.QUANT.key)) categorys.add(new UserStudyExtendDto(m.get(QuestionSubject.QUANT.key), categoryMap.get(QuestionSubject.QUANT.key)));

        if (categoryMap.containsKey(QuestionType.IR.key)) categorys.add(new UserStudyExtendDto(m.get(QuestionType.IR.key), categoryMap.get(QuestionType.IR.key)));
        if (categoryMap.containsKey(QuestionType.AWA.key)) categorys.add(new UserStudyExtendDto(m.get(QuestionType.AWA.key), categoryMap.get(QuestionType.AWA.key)));
        if (categoryMap.containsKey(PaperModule.EXAMINATION.key)) categorys.add(new UserStudyExtendDto("模考", categoryMap.get(PaperModule.EXAMINATION.key)));
        if (categoryMap.containsKey("freedom")) categorys.add(new UserStudyExtendDto("自由组卷", categoryMap.get("freedom")));

        dto.setTime(totalTime);
        dto.setCategorys(categorys);
        return ResponseHelp.success(dto);
    }

    @RequestMapping(value = "/data", method = RequestMethod.GET)
    @ApiOperation(value = "获取做题数据", notes = "获取做题数据", httpMethod = "GET")
    public Response<Map<String, UserDataDto>> questionData(
            @RequestParam(required = true) String module,
            @RequestParam(required = true) String subject,
            @RequestParam(required = false) Integer[] structIds,
            @RequestParam(required = false) String startTime,
            @RequestParam(required = false) String endTime
    )  {
        User user = (User) shiroHelp.getLoginUser();

        QuestionSubject questionSubject = QuestionSubject.ValueOf(subject);
        StructModule structModule = StructModule.ValueOf(module);
        List<QuestionNo> questionNoList = questionNoService.listByStruct(structModule, structIds);
        List<QuestionNoRelation> relationList = questionNoService.relation(questionNoList);
        Map<Number, QuestionNoRelation> relationMap = new HashMap<>();
        for(QuestionNoRelation relation : relationList){
            relationMap.put(relation.getId(), relation);
        }
        List<String> questionTypes = QuestionType.FromSubject(questionSubject);
        Map<String, UserDataDto> dtoMap = new HashMap<>();
        for(String questionType : questionTypes){
            UserDataDto dto = new UserDataDto();
            dto.setQuestionType(questionType);
            JSONObject placeMap = new JSONObject();
            JSONObject difficultMap = new JSONObject();

            Integer correctTime = 0;
            Integer incorrectTime = 0;

            List<QuestionNo> list = relationList.stream().filter((row)->row.getQuestion().getQuestionType().equals(questionType)).collect(Collectors.toList());
            dto.setQuestionNumber(list.size());

            PaperStat stat = questionNoService.statPaper(list);
            dto.setTotalCorrect(stat.getTotalCorrect());
            dto.setTotalNumber(stat.getTotalNumber());
            dto.setTotalTime(stat.getTotalTime());

            Collection questionNoIds = Transform.getIds(list, QuestionNo.class, "id");
            List<UserQuestion> userQuestionList = userQuestionService.listByQuestionWithTime(user.getId(), QuestionModule.BASE, questionNoIds, Tools.baseTime(startTime), Tools.baseTime(endTime));
            Map userQuestionMap = Transform.getMap(userQuestionList, UserQuestion.class, "questionNoId");
            dto.setUserQuestion(userQuestionMap.size());

            UserQuestionStat userQuestionStat = userQuestionService.statQuestion(userQuestionList);
            dto.setUserCorrect(userQuestionStat.getUserCorrect());
            dto.setUserNumber(userQuestionStat.getUserNumber());
            dto.setUserTime(userQuestionStat.getUserTime());

            for (UserQuestion userQuestion:userQuestionList){
                QuestionNoRelation relation = relationMap.get(userQuestion.getQuestionNoId());

                // 考点用时,以及正确度
                String placeKey = relation.getQuestion().getPlace();
                JSONObject place = placeMap.getJSONObject(placeKey);
                if (place == null){
                    place = new JSONObject();
                    place.put("key", placeKey);
                    place.put("userNumber", 1);
                    place.put("userCorrect", userQuestion.getIsCorrect());
                    place.put("userTime", userQuestion.getUserTime());
                    placeMap.put(placeKey, place);
                }else{
                    place.put("userNumber", place.getInteger("userNumber") + 1);
                    place.put("userCorrect", place.getInteger("userCorrect") + userQuestion.getIsCorrect());
                    place.put("userTime", place.getInteger("userTime") + userQuestion.getUserTime());
                }

                // 难度正确度
                String difficultKey = relation.getQuestion().getDifficult();
                JSONObject difficult = difficultMap.getJSONObject(difficultKey);
                if (difficult == null){
                    difficult = new JSONObject();
                    difficult.put("key", difficultKey);
                    difficult.put("userNumber", 1);
                    difficult.put("userCorrect", userQuestion.getIsCorrect());
                    difficult.put("totalNumber", relation.getTotalNumber());
                    difficult.put("totalCorrect", relation.getTotalCorrect());
                    difficultMap.put(difficultKey, difficult);
                }else{
                    difficult.put("userNumber", difficult.getInteger("userNumber") + 1);
                    difficult.put("userCorrect", difficult.getInteger("userCorrect") + userQuestion.getIsCorrect());
                    difficult.put("totalNumber", difficult.getInteger("totalNumber") + relation.getTotalNumber());
                    difficult.put("totalCorrect", difficult.getInteger("totalCorrect") + relation.getTotalCorrect());
                }

                if (userQuestion.getIsCorrect() > 0){
                    correctTime += userQuestion.getUserTime();
                }else{
                    incorrectTime += userQuestion.getUserTime();
                }
            }
            JSONArray difficult = new JSONArray();
            JSONArray place = new JSONArray();
            difficult.addAll(difficultMap.values());
            place.addAll(placeMap.values());
            dto.setDifficult(difficult);
            dto.setPlace(place);

            dto.setCorrectTime(correctTime);
            dto.setIncorrectTime(incorrectTime);
            dtoMap.put(questionType, dto);
        }

        return ResponseHelp.success(dtoMap);
    }

    @RequestMapping(value = "/collect/experience/add", method = RequestMethod.PUT)
    @ApiOperation(value = "添加心经收藏", notes = "添加心经收藏", httpMethod = "PUT")
    public Response<Boolean> addExperienceCollect(@RequestBody @Validated UserCollectExperienceDto dto)  {
        UserCollectExperience entity = Transform.dtoToEntity(dto);
        User user = (User) shiroHelp.getLoginUser();
        entity.setUserId(user.getId());
        userCollectExperienceService.addExperience(entity);

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/collect/experience/delete", method = RequestMethod.DELETE)
    @ApiOperation(value = "移除心经收藏", notes = "移除心经收藏", httpMethod = "DELETE")
    public Response<Boolean> deleteExperienceCollect(Integer experienceId)  {
        User user = (User) shiroHelp.getLoginUser();
        Boolean result = userCollectExperienceService.deleteExperience(user.getId(), experienceId);

        return ResponseHelp.success(result);
    }

    @RequestMapping(value = "/collect/experience/list", method = RequestMethod.GET)
    @ApiOperation(value = "获取收藏心经列表", notes = "获取收藏心经列表", httpMethod = "GET")
    public Response<PageMessage<CourseExperience>> listExperienceCollect(
            @RequestParam(required = false, defaultValue = "1") int page,
            @RequestParam(required = false, defaultValue = "100") int size,
            @RequestParam(required = false) String startTime,
            @RequestParam(required = false) String endTime,
            @RequestParam(required = false, defaultValue = "id") String order, // collect_time, update_time
            @RequestParam(required = false, defaultValue = "desc") String direction,
            HttpSession session)  {
        User user = (User) shiroHelp.getLoginUser();
        Page<CourseExperience> p = courseExperienceService.listWithUser(page, size, user.getId(), Tools.baseTime(startTime), Tools.baseTime(endTime), order, DirectionStatus.ValueOf(direction));

        return ResponseHelp.success(p, page, size, p.getTotal());
    }

    @RequestMapping(value = "/collect/question/add", method = RequestMethod.PUT)
    @ApiOperation(value = "添加题目收藏", notes = "添加题目收藏", httpMethod = "PUT")
    public Response<Boolean> addQuestionCollect(@RequestBody @Validated UserCollectQuestionDto dto)  {
        UserCollectQuestion entity = Transform.dtoToEntity(dto);
        User user = (User) shiroHelp.getLoginUser();

        QuestionNo questionNo = questionNoService.get(dto.getQuestionNoId());
        QuestionNoModule questionNoModule = QuestionNoModule.ValueOf(questionNo.getModule());
        QuestionModule questionModule = QuestionModule.WithQuestionNo(questionNoModule);
        entity.setQuestionModule(questionModule.key);
        entity.setQuestionId(questionNo.getQuestionId());
        entity.setQuestionNoId(questionNo.getId());
        entity.setUserId(user.getId());
        userCollectQuestionService.addQuestion(entity);

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/collect/question/delete", method = RequestMethod.DELETE)
    @ApiOperation(value = "移除题目收藏", notes = "移除题目收藏", httpMethod = "DELETE")
    public Response<Boolean> deleteQuestionCollect(Integer questionNoId)  {
        User user = (User) shiroHelp.getLoginUser();
        QuestionNo questionNo = questionNoService.get(questionNoId);
        Integer questionId = questionNo.getQuestionId();
        Boolean result = userCollectQuestionService.deleteQuestion(user.getId(), questionId);

        return ResponseHelp.success(result);
    }

    @RequestMapping(value = "/collect/question/clear", method = RequestMethod.DELETE)
    @ApiOperation(value = "移除题目收藏", notes = "移除题目收藏", httpMethod = "DELETE")
    public Response<Boolean> clearQuestionCollect(@RequestBody @Validated UserQuestionNoIdsDto dto)  {
        User user = (User) shiroHelp.getLoginUser();
        List<QuestionNo> questionNoList = questionNoService.select(dto.getQuestionNoIds());
        for(QuestionNo questionNo : questionNoList){
            userCollectQuestionService.deleteQuestion(user.getId(), questionNo.getQuestionId());
        }

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/collect/question/group", method = RequestMethod.POST)
    @ApiOperation(value = "收藏题目组卷", notes = "收藏题目组卷", httpMethod = "POST")
    public Response<UserPaper> bindQuestionCollect(@RequestBody @Validated UserCustomGroupDto dto)  {
        User user = (User) shiroHelp.getLoginUser();
        QuestionModule questionModule = questionFlowService.validGroup(dto.getQuestionNoIds());
        UserPaper userPaper = questionFlowService.makePaper(
                user.getId(),
                questionModule,
                PaperOrigin.COLLECT,
                Arrays.stream(dto.getQuestionNoIds()).collect(Collectors.toList()),
                dto.getFilterTimes()
        );

        return ResponseHelp.success(userPaper);
    }

    @RequestMapping(value = "/collect/question/remove", method = RequestMethod.POST)
    @ApiOperation(value = "移除正确题", notes = "移除正确题", httpMethod = "POST")
    public Response<Boolean> removeCollect(@RequestBody @Validated ErrorReportDto dto)  {
        User user = (User) shiroHelp.getLoginUser();

        UserReport report = userReportService.get(dto.getUserReportId());
        if (report.getIsFinish() == 0){
            throw new ParameterException("试卷未完成");
        }
        List<UserQuestion> questionList = userQuestionService.listByReport(user.getId(), dto.getUserReportId());
        Collection questionNoIds = Transform.getIds(questionList, UserQuestion.class, "questionNoId");

        List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
        List<QuestionNoRelation> relationList = questionNoService.relation(questionNoList);
        userPaperQuestionService.addRemoveError(user.getId(), relationList);

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/collect/question/list", method = RequestMethod.GET)
    @ApiOperation(value = "获取收藏题目列表", notes = "获取收藏题目列表", httpMethod = "GET")
    public Response<PageMessage<UserCollectQuestionInfoDto>> listQuestionCollect(
            @RequestParam(required = false, defaultValue = "1") int page,
            @RequestParam(required = false, defaultValue = "100") int size,
            @RequestParam(required = false) String keyword,
            @RequestParam(required = false) String module,
            @RequestParam(required = false) String[] questionTypes,
            @RequestParam(required = false) Integer[] structIds,
            @RequestParam(required = false) String startTime,
            @RequestParam(required = false) String endTime,
            @RequestParam(required = false) Boolean latest,
            @RequestParam(required = false) String year,
            @RequestParam(required = false) String order, // (pid asc, no asc), time, correct, question_type, latest_time
            HttpSession session)  {
        User user = (User) shiroHelp.getLoginUser();
        QuestionNoModule questionNoModule = QuestionNoModule.ValueOf(module);
        Page<UserCollectQuestion> p = null;
        if(questionNoModule == QuestionNoModule.EXERCISE){
            p = userCollectQuestionService.listExercise(page, size, user.getId(), keyword, questionTypes, structIds, Tools.baseTime(startTime), Tools.baseTime(endTime), order != null ? order.replace("|", " ") : null);
        }else if (questionNoModule == QuestionNoModule.EXAMINATION){
            Integer libraryId = null;
            if (latest != null){
                if (latest) {
                    TextbookLibrary textbookLibrary = textbookLibraryService.getLatest();
                    libraryId = textbookLibrary.getId();
                    year = null;
                }else{
                    libraryId = 0;
                }
            }
            p = userCollectQuestionService.listExamination(page, size, user.getId(), keyword, questionTypes, structIds, libraryId, year, Tools.baseTime(startTime), Tools.baseTime(endTime), order != null ? order.replace("|", " ") : null);
        }else{
            throw new ParameterException("参数逻辑错误");
        }
        List<UserCollectQuestionInfoDto> pr = Transform.convert(p, UserCollectQuestionInfoDto.class);

        // 获取题目信息
        Collection questionIds = Transform.getIds(pr, UserCollectQuestionInfoDto.class, "questionId");
        List<Question> questionList = questionService.select(questionIds);
        Transform.combine(pr, questionList, UserCollectQuestionInfoDto.class, "questionId", "question", Question.class, "id", QuestionExtendDto.class);

        Collection questionNoIds = Transform.getIds(pr, UserCollectQuestionInfoDto.class, "questionNoId");
        List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
        Transform.combine(pr, questionNoList, UserCollectQuestionInfoDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);

        // 绑定题目统计
        List<UserQuestion> userQuestionList = userQuestionService.listByQuestion(user.getId(), questionIds);
        Map<Object, UserQuestionStat> stats = userQuestionService.statQuestionMap(userQuestionList);
        Transform.combine(pr, stats, UserCollectQuestionInfoDto.class, "questionId", "stat");

        // 最近做题
        List<UserQuestion> lastList = userQuestionService.listWithLast(user.getId(), questionIds);
        Map lastMap = Transform.getMap(lastList, UserQuestion.class, "id", "createTime");
        Transform.combine(pr, lastMap, UserCollectQuestionInfoDto.class, "questionId", "latestTime");

        // 收藏、笔记
        List<UserCollectQuestion> userCollectQuestionList = userCollectQuestionService.listByUserAndQuestions(user.getId(), questionIds);
        Map collectMap = Transform.getMap(userCollectQuestionList, UserCollectQuestion.class, "questionId", "id");

        List<UserNoteQuestion> userNoteQuestionList = userNoteQuestionService.listByUserAndQuestions(user.getId(), questionIds);
        Map noteMap = Transform.getMap(userNoteQuestionList, UserNoteQuestion.class, "questionId", "id");

        for(UserCollectQuestionInfoDto dto : pr){
            dto.setCollect(collectMap.containsKey(dto.getQuestionId()));
            dto.setNote(noteMap.containsKey(dto.getQuestionId()));
        }
        return ResponseHelp.success(pr, page, size, p.getTotal());
    }

    @RequestMapping(value = "/error/list", method = RequestMethod.GET)
    @ApiOperation(value = "获取错题列表", notes = "获取错题列表", httpMethod = "GET")
    public Response<PageMessage<UserQuestionErrorInfoDto>> listError(
            @RequestParam(required = false, defaultValue = "1") int page,
            @RequestParam(required = false, defaultValue = "100") int size,
            @RequestParam(required = false) String keyword,
            @RequestParam(required = false) String module,
            @RequestParam(required = false) String[] questionTypes,
            @RequestParam(required = false) Integer[] structIds,
            @RequestParam(required = false) String startTime,
            @RequestParam(required = false) String endTime,
            @RequestParam(required = false) Boolean latest,
            @RequestParam(required = false) String year,
            @RequestParam(required = false) String order // (pid asc, no asc), time, correct, question_type, latest_time
    )  {
        User user = (User) shiroHelp.getLoginUser();
        QuestionNoModule questionNoModule = QuestionNoModule.ValueOf(module);
        Page<UserQuestion> p = null;
        if(questionNoModule == QuestionNoModule.EXERCISE){
            p = userQuestionService.listExerciseError(page, size, user.getId(), keyword, questionTypes, structIds, Tools.baseTime(startTime), Tools.baseTime(endTime), order != null ? order.replace("|", " ") : null);
        }else if (questionNoModule == QuestionNoModule.EXAMINATION){
            Integer libraryId = null;
            if (latest != null){
                if (latest) {
                    TextbookLibrary textbookLibrary = textbookLibraryService.getLatest();
                    libraryId = textbookLibrary.getId();
                    year = null;
                }else{
                    libraryId = 0;
                }
            }
            p = userQuestionService.listExaminationError(page, size, user.getId(), keyword, questionTypes, structIds, libraryId, year, Tools.baseTime(startTime), Tools.baseTime(endTime), order != null ? order.replace("|", " ") : null);
        }else{
            throw new ParameterException("参数逻辑错误");
        }
        List<UserQuestionErrorInfoDto> pr = Transform.convert(p, UserQuestionErrorInfoDto.class);

        // 获取题目信息
        Collection questionIds = Transform.getIds(pr, UserQuestionErrorInfoDto.class, "questionId");
        List<Question> questionList = questionService.select(questionIds);
        Transform.combine(pr, questionList, UserQuestionErrorInfoDto.class, "questionId", "question", Question.class, "id", QuestionExtendDto.class);

        Collection questionNoIds = Transform.getIds(pr, UserQuestionErrorInfoDto.class, "questionId");
        List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
        Transform.combine(pr, questionNoList, UserQuestionErrorInfoDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);

        // 绑定题目统计
        List<UserQuestion> userQuestionList = userQuestionService.listByQuestion(user.getId(), questionIds);
        Map<Object, UserQuestionStat> stats = userQuestionService.statQuestionMap(userQuestionList);
        Transform.combine(pr, stats, UserQuestionErrorInfoDto.class, "questionId", "stat");

        // 最近做题
        List<UserQuestion> lastList = userQuestionService.listWithLast(user.getId(), questionIds);
        Map lastMap = Transform.getMap(lastList, UserQuestion.class, "id", "createTime");
        Transform.combine(pr, lastMap, UserQuestionErrorInfoDto.class, "questionId", "latestTime");

        // 收藏、笔记
        List<UserCollectQuestion> userCollectQuestionList = userCollectQuestionService.listByUserAndQuestions(user.getId(), questionIds);
        Map collectMap = Transform.getMap(userCollectQuestionList, UserCollectQuestion.class, "questionId", "id");

        List<UserNoteQuestion> userNoteQuestionList = userNoteQuestionService.listByUserAndQuestions(user.getId(), questionIds);
        Map noteMap = Transform.getMap(userNoteQuestionList, UserNoteQuestion.class, "questionId", "id");

        for(UserQuestionErrorInfoDto dto : pr){
            dto.setCollect(collectMap.containsKey(dto.getQuestionId()));
            dto.setNote(noteMap.containsKey(dto.getQuestionId()));
        }
        return ResponseHelp.success(pr, page, size, p.getTotal());
    }

    @RequestMapping(value = "/error/group", method = RequestMethod.POST)
    @ApiOperation(value = "错题组卷", notes = "错题组卷", httpMethod = "POST")
    public Response<UserPaper> bindError(@RequestBody @Validated UserCustomGroupDto dto)  {
        User user = (User) shiroHelp.getLoginUser();
        QuestionModule questionModule = questionFlowService.validGroup(dto.getQuestionNoIds());
        UserPaper userPaper = questionFlowService.makePaper(
                user.getId(),
                questionModule,
                PaperOrigin.ERROR,
                Arrays.stream(dto.getQuestionNoIds()).collect(Collectors.toList()),
                dto.getFilterTimes()
        );

        return ResponseHelp.success(userPaper);
    }

    @RequestMapping(value = "/error/clear", method = RequestMethod.POST)
    @ApiOperation(value = "错题移除", notes = "错题移除", httpMethod = "POST")
    public Response<Boolean> clearError(@RequestBody @Validated UserQuestionNoIdsDto dto)  {
        User user = (User) shiroHelp.getLoginUser();

        List<QuestionNo> questionNoList = questionNoService.select(dto.getQuestionNoIds());
        List<QuestionNoRelation> relationList = questionNoService.relation(questionNoList);
        userPaperQuestionService.addRemoveError(user.getId(), relationList);

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/error/remove", method = RequestMethod.POST)
    @ApiOperation(value = "移除正确题", notes = "移除正确题", httpMethod = "POST")
    public Response<Boolean> removeError(@RequestBody @Validated ErrorReportDto dto)  {
        User user = (User) shiroHelp.getLoginUser();

        UserReport report = userReportService.get(dto.getUserReportId());
        if (report.getIsFinish() == 0){
            throw new ParameterException("试卷未完成");
        }
        List<UserQuestion> questionList = userQuestionService.listByReport(user.getId(), dto.getUserReportId());
        Collection questionNoIds = Transform.getIds(questionList, UserQuestion.class, "questionNoId");

        List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
        List<QuestionNoRelation> relationList = questionNoService.relation(questionNoList);
        userPaperQuestionService.addRemoveError(user.getId(), relationList);

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/note/question", method = RequestMethod.PUT)
    @ApiOperation(value = "更新题目笔记", notes = "更新题目笔记", httpMethod = "PUT")
    public Response<Boolean> updateNoteQuestion(@RequestBody @Validated UserNoteQuestionDto dto)  {
        UserNoteQuestion entity = Transform.dtoToEntity(dto);
        User user = (User) shiroHelp.getLoginUser();
        entity.setUserId(user.getId());

        QuestionNo questionNo = questionNoService.get(dto.getQuestionNoId());
        QuestionNoModule questionNoModule = QuestionNoModule.ValueOf(questionNo.getModule());
        QuestionModule questionModule = QuestionModule.WithQuestionNo(questionNoModule);
        entity.setQuestionModule(questionModule.key);
        entity.setQuestionId(questionNo.getQuestionId());
        entity.setQuestionNoId(questionNo.getId());

        userNoteQuestionService.updateNote(entity);

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/note/question/clear", method = RequestMethod.POST)
    @ApiOperation(value = "笔记移除", notes = "笔记移除", httpMethod = "POST")
    public Response<Boolean> clearNoteQuestion(@RequestBody @Validated UserQuestionNoIdsDto dto)  {
        User user = (User) shiroHelp.getLoginUser();
        List<QuestionNo> questionNoList = questionNoService.select(dto.getQuestionNoIds());
        for(QuestionNo questionNo : questionNoList){
            userNoteQuestionService.deleteNote(user.getId(), questionNo.getQuestionId());
        }
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/note/question/list", method = RequestMethod.GET)
    @ApiOperation(value = "获取题目笔记列表", notes = "获取笔记列表", httpMethod = "GET")
    public Response<PageMessage<UserNoteQuestionInfoDto>> listNoteQuestion(
            @RequestParam(required = false, defaultValue = "1") int page,
            @RequestParam(required = false, defaultValue = "100") int size,
            @RequestParam(required = false) String keyword,
            @RequestParam(required = false) String module,
            @RequestParam(required = false) String[] questionTypes,
            @RequestParam(required = false) Integer[] structIds,
            @RequestParam(required = false) String startTime,
            @RequestParam(required = false) String endTime,
            @RequestParam(required = false) Boolean latest,
            @RequestParam(required = false) String year,
            @RequestParam(required = false) String order, // update_time
            HttpSession session)  {
        User user = (User) shiroHelp.getLoginUser();
        QuestionNoModule questionNoModule = QuestionNoModule.ValueOf(module);
        Page<UserNoteQuestion> p = null;
        if(questionNoModule == QuestionNoModule.EXERCISE){
            p = userNoteQuestionService.listExercise(page, size, user.getId(), keyword, questionTypes, structIds, Tools.baseTime(startTime), Tools.baseTime(endTime), order != null ? order.replace("|", " ") : null);
        }else if (questionNoModule == QuestionNoModule.EXAMINATION){
            Integer libraryId = null;
            if (latest != null){
                if (latest) {
                    TextbookLibrary textbookLibrary = textbookLibraryService.getLatest();
                    libraryId = textbookLibrary.getId();
                    year = null;
                }else{
                    libraryId = 0;
                }
            }
            p = userNoteQuestionService.listExamination(page, size, user.getId(), keyword, questionTypes, structIds, libraryId, year, Tools.baseTime(startTime), Tools.baseTime(endTime), order != null ? order.replace("|", " ") : null);
        }else{
            throw new ParameterException("参数逻辑错误");
        }
        List<UserNoteQuestionInfoDto> pr = Transform.convert(p, UserNoteQuestionInfoDto.class);

        // 获取题目信息
        Collection questionIds = Transform.getIds(pr, UserNoteQuestionInfoDto.class, "questionId");
        List<Question> questionList = questionService.select(questionIds);
        Transform.combine(pr, questionList, UserNoteQuestionInfoDto.class, "questionId", "question", Question.class, "id", QuestionExtendDto.class);

        Collection questionNoIds = Transform.getIds(pr, UserNoteQuestionInfoDto.class, "questionNoId");
        List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
        Transform.combine(pr, questionNoList, UserNoteQuestionInfoDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);

        return ResponseHelp.success(pr, page, size, p.getTotal());
    }

    @RequestMapping(value = "/note/course", method = RequestMethod.PUT)
    @ApiOperation(value = "更新课程笔记", notes = "更新课程笔记", httpMethod = "PUT")
    public Response<Boolean> updateNoteCourse(@RequestBody @Validated UserNoteCourseDto dto)  {
        UserNoteCourse entity = Transform.dtoToEntity(dto);
        User user = (User) shiroHelp.getLoginUser();
        entity.setUserId(user.getId());
        userNoteCourseService.update(entity);

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/note/course/clear", method = RequestMethod.POST)
    @ApiOperation(value = "笔记移除", notes = "笔记移除", httpMethod = "POST")
    public Response<Boolean> clearNoteCourse(@RequestBody @Validated UserCourseNoIdsDto dto)  {
        User user = (User) shiroHelp.getLoginUser();
        List<CourseNo> courseNoList = courseNoService.select(dto.getCourseNoIds());
        for(CourseNo courseNo : courseNoList){
            userNoteCourseService.deleteNote(user.getId(), courseNo.getId());
        }
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/note/course/list", method = RequestMethod.GET)
    @ApiOperation(value = "获取课程笔记列表", notes = "获取笔记列表", httpMethod = "GET")
    public Response<PageMessage<UserNoteCourse>> listNoteCourse(
            @RequestParam(required = false, defaultValue = "1") int page,
            @RequestParam(required = false, defaultValue = "100") int size,
            @RequestParam(required = false) String keyword,
            @RequestParam(required = false) Integer courseId,
            @RequestParam(required = false, defaultValue = "") String order, // update_time, no
            @RequestParam(required = false, defaultValue = "desc") String direction,
            HttpSession session)  {
        User user = (User) shiroHelp.getLoginUser();
        Page<UserNoteCourse> p = userNoteCourseService.listByCourse(page, size, keyword, user.getId(), courseId, order, DirectionStatus.ValueOf(direction));

        return ResponseHelp.success(p, page, size, p.getTotal());
    }

    @RequestMapping(value = "/report/list", method = RequestMethod.GET)
    @ApiOperation(value = "获取报告列表", notes = "获取报告列表", httpMethod = "GET")
    public Response<PageMessage<UserPaperDto>> listReport(
            @RequestParam(required = false, defaultValue = "1") int page,
            @RequestParam(required = false, defaultValue = "100") int size,
            @RequestParam(required = false) String keyword,
            @RequestParam(required = false) String module,
            @RequestParam(required = false) String origin,
            @RequestParam(required = false) String[] questionTypes,
            @RequestParam(required = false) Integer[] structIds,
            @RequestParam(required = false) String startTime,
            @RequestParam(required = false) String endTime,
            @RequestParam(required = false) Boolean latest,
            @RequestParam(required = false) String year,
            @RequestParam(required = false) String[] courseModules,
            @RequestParam(required = false) String order, // title, latest_time,correct,time
            HttpSession session)  {
        User user = (User) shiroHelp.getLoginUser();
        PaperOrigin paperOrigin = PaperOrigin.ValueOf(origin);
        QuestionNoModule questionNoModule = QuestionNoModule.ValueOf(module);
        Page<UserPaper> p = null;
        if (paperOrigin == PaperOrigin.COLLECT || paperOrigin == PaperOrigin.ERROR){
            p = userPaperService.list(page, size, user.getId(), keyword, paperOrigin, Tools.baseTime(startTime), Tools.baseTime(endTime), order != null ? order.replace("|", " ") : null);
        }else if(questionNoModule == QuestionNoModule.EXERCISE){
            p = userPaperService.listExercise(page, size, user.getId(), keyword, questionTypes, structIds, courseModules, Tools.baseTime(startTime), Tools.baseTime(endTime), order != null ? order.replace("|", " ") : null);
        }else if (questionNoModule == QuestionNoModule.EXAMINATION){
            Integer libraryId = null;
            if (latest != null){
                paperOrigin = PaperOrigin.TEXTBOOK;
                if (latest) {
                    TextbookLibrary textbookLibrary = textbookLibraryService.getLatest();
                    libraryId = textbookLibrary.getId();
                    year = null;
                }else{
                    libraryId = 0;
                }
            }
            p = userPaperService.listExamination(page, size, user.getId(), keyword, structIds, libraryId, year, Tools.baseTime(startTime), Tools.baseTime(endTime), order != null ? order.replace("|", " ") : null);
        }else{
            throw new ParameterException("参数逻辑错误");
        }
        List<UserPaperDto> pr = Transform.convert(p, UserPaperDto.class);

        if (questionNoModule == QuestionNoModule.EXAMINATION){
            Collection originIds = Transform.getIds(p, UserPaper.class, "originId");
            List<ExaminationPaper> examinationPapers = examinationPaperService.select(originIds);
            Transform.combine(pr, examinationPapers, UserPaperDto.class, "originId", "origin", ExaminationPaper.class, "id", PaperExtendDto.class);
        }

        Collection paperIds = Transform.getIds(p, UserPaper.class, "id");
        // 绑定用户报告
        Map<Object, Collection<UserReport>> reportByPaper = userReportService.mapByPaper(paperIds);
        Transform.combine(pr, reportByPaper, UserPaperDto.class, "id", "reports", UserReportExtendDto.class);

        // 获取试卷统计信息
        Map<Integer, Integer[]> idsMap = new HashMap<>();
        for(UserPaperDto paper : pr){
            if (paper.getQuestionNoIds() == null) continue;
            idsMap.put(paper.getId(), paper.getQuestionNoIds());
        }
        Map statMap = questionNoService.statPaperMap(idsMap);
        Transform.combine(pr, statMap, UserPaperDto.class, "id", "stat");

        // 获取试卷题型
        Map questionTypMap = questionNoService.questionTypeMap(idsMap);
        Transform.combine(pr, questionTypMap, UserPaperDto.class, "id", "questionTypes");


        return ResponseHelp.success(pr, page, size, p.getTotal());
    }

    @RequestMapping(value = "/ask/question", method = RequestMethod.POST)
    @ApiOperation(value = "添加题目提问", notes = "添加题目提问", httpMethod = "POST")
    public Response<Boolean> addAskQuestion(@RequestBody @Validated UserAskQuestionDto dto)  {
        UserAskQuestion entity = Transform.dtoToEntity(dto);
        User user = (User) shiroHelp.getLoginUser();
        entity.setUserId(user.getId());

        QuestionNo questionNo = questionNoService.get(dto.getQuestionNoId());
        QuestionNoModule questionNoModule = QuestionNoModule.ValueOf(questionNo.getModule());
        QuestionModule questionModule = QuestionModule.WithQuestionNo(questionNoModule);
        entity.setQuestionModule(questionModule.key);
        entity.setQuestionId(questionNo.getQuestionId());
        entity.setQuestionNoId(questionNo.getId());
        Question question = questionService.get(questionNo.getQuestionId());

        Integer assignId = null;
        PaperModule paperModule = null;
        if (dto.getUserQuestionId() != null && dto.getUserQuestionId() > 0){
            UserQuestion userQuestion = userQuestionService.get(dto.getUserQuestionId());
            UserReport userReport = userReportService.get(userQuestion.getReportId());
            UserPaper userPaper = userPaperService.get(userReport.getPaperId());
            if(userPaper != null && userPaper.getPaperOrigin().equals(PaperOrigin.PREVIEW.key)){
                assignId = userPaper.getOriginId();
            }
            paperModule = PaperModule.ValueOf(userPaper.getPaperModule());
        }else{
            paperModule = PaperModule.WithQuestionNo(questionNoModule);
        }
        entity.setAskModule(AskModule.WithPaper(paperModule).key);
        Integer recordId = questionFlowService.questionRelationCourse(user.getId(), assignId, QuestionType.ValueOf(question.getQuestionType()));

        if (recordId != null){
            // 绑定提问权限
            entity.setRecordId(recordId);
            UserOrderRecord record = userOrderRecordService.get(recordId);
            entity.setAskTime(record.getAskTime());
            Date now = new Date();
            entity.setExpireTime(Tools.addHour(now, record.getAskTime() / 3600));
        }else{
            // todo 判断题目是否有提问权限
        }
        userAskQuestionService.add(entity);

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/ask/question/delete", method = RequestMethod.DELETE)
    @ApiOperation(value = "提问删除", httpMethod = "DELETE")
    public Response<Boolean> deleteAskQuestion(@RequestParam int id, HttpServletRequest request) {
        UserAskQuestion in = userAskQuestionService.get(id);
        User user = (User) shiroHelp.getLoginUser();
        if(in == null){
            throw new ParameterException("提问不存在");
        }
        if (!in.getUserId().equals(user.getId())){
            throw new ParameterException("提问不存在");
        }
        if (in.getAnswerStatus()== AnswerStatus.ANSWER.index){
            throw new ParameterException("提问已回答");
        }
        userAskQuestionService.delete(id);
        // 如果
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/ask/question/list", method = RequestMethod.GET)
    @ApiOperation(value = "获取题目提问列表", notes = "获取题目提问列表", httpMethod = "GET")
    public Response<PageMessage<UserAskQuestionInfoDto>> listAskQuestion(
            @RequestParam(required = false, defaultValue = "1") int page,
            @RequestParam(required = false, defaultValue = "100") int size,
            @RequestParam(required = false) String keyword,
            @RequestParam(required = false) String module,
            @RequestParam(required = false) String[] questionTypes,
            @RequestParam(required = false) Integer[] structIds,
            @RequestParam(required = false) String startTime,
            @RequestParam(required = false) String endTime,
            @RequestParam(required = false) Integer answerStatus,
            @RequestParam(required = false) Boolean latest,
            @RequestParam(required = false) String year,
            @RequestParam(required = false) String order, // create_time, answer_time
            HttpSession session)  {
        User user = (User) shiroHelp.getLoginUser();
        QuestionNoModule questionNoModule = QuestionNoModule.ValueOf(module);
        Page<UserAskQuestion> p = null;
        if(questionNoModule == QuestionNoModule.EXERCISE){
            p = userAskQuestionService.listExercise(page, size, user.getId(), keyword, questionTypes, structIds, AnswerStatus.ValueOf(answerStatus), Tools.baseTime(startTime), Tools.baseTime(endTime), order != null ? order.replace("|", " ") : null);
        }else if (questionNoModule == QuestionNoModule.EXAMINATION){
            Integer libraryId = null;
            if (latest != null){
                if (latest) {
                    TextbookLibrary textbookLibrary = textbookLibraryService.getLatest();
                    libraryId = textbookLibrary.getId();
                    year = null;
                }else{
                    libraryId = 0;
                }
            }
            p = userAskQuestionService.listExamination(page, size, user.getId(), keyword, questionTypes, structIds, libraryId, year, AnswerStatus.ValueOf(answerStatus), Tools.baseTime(startTime), Tools.baseTime(endTime), order != null ? order.replace("|", " ") : null);
        }else{
            throw new ParameterException("参数逻辑错误");
        }
        List<UserAskQuestionInfoDto> pr = Transform.convert(p, UserAskQuestionInfoDto.class);

        // 获取题目信息
        Collection questionIds = Transform.getIds(pr, UserAskQuestionInfoDto.class, "questionId");
        List<Question> questionList = questionService.select(questionIds);
        Transform.combine(pr, questionList, UserAskQuestionInfoDto.class, "questionId", "question", Question.class, "id", QuestionExtendDto.class);

        Collection questionNoIds = Transform.getIds(pr, UserAskQuestionInfoDto.class, "questionNoId");
        List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
        Transform.combine(pr, questionNoList, UserAskQuestionInfoDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);

        return ResponseHelp.success(pr, page, size, p.getTotal());
    }

    @RequestMapping(value = "/ask/course", method = RequestMethod.POST)
    @ApiOperation(value = "添加课程提问", notes = "添加课程提问", httpMethod = "POST")
    public Response<Boolean> addAskCourse(@RequestBody @Validated UserAskCourseDto dto)  {
        UserAskCourse entity = Transform.dtoToEntity(dto);
        User user = (User) shiroHelp.getLoginUser();
        entity.setUserId(user.getId());

        UserCourse userCourse = courseExtendService.userCourse(user.getId(), dto.getCourseId());
        if (userCourse != null){
            // 绑定提问权限
            entity.setRecordId(userCourse.getRecordId());
            UserOrderRecord record = userOrderRecordService.get(userCourse.getRecordId());
            entity.setAskTime(record.getAskTime());
            Date now = new Date();
            entity.setExpireTime(Tools.addHour(now, record.getAskTime() / 3600));
        }else{
            throw new ParameterException("课程需开通后才能提问");
        }
        userAskCourseService.add(entity);

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/ask/course/delete", method = RequestMethod.DELETE)
    @ApiOperation(value = "提问课程提问", httpMethod = "DELETE")
    public Response<Boolean> deleteAskCourse(@RequestParam int id, HttpServletRequest request) {
        UserAskCourse in = userAskCourseService.get(id);
        User user = (User) shiroHelp.getLoginUser();
        if(in == null){
            throw new ParameterException("提问不存在");
        }
        if (!in.getUserId().equals(user.getId())){
            throw new ParameterException("提问不存在");
        }
        if (in.getAnswerStatus()== AnswerStatus.ANSWER.index){
            throw new ParameterException("提问已回答");
        }
        userAskCourseService.delete(id);
        // 如果
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/ask/course/list", method = RequestMethod.GET)
    @ApiOperation(value = "获取课程提问列表", notes = "获取课程提问列表", httpMethod = "GET")
    public Response<PageMessage<UserAskCourse>> listAskCourse(
            @RequestParam(required = false, defaultValue = "1") int page,
            @RequestParam(required = false, defaultValue = "100") int size,
            @RequestParam(required = false) String keyword,
            @RequestParam(required = false) Integer courseId,
            @RequestParam(required = false) Integer courseNoId,
            @RequestParam(required = false) Integer answerStatus,
            @RequestParam(required = false) Integer position,
            @RequestParam(required = false) String order, // create_time, answer_time
            @RequestParam(required = false,defaultValue = "desc") String direction,
            HttpSession session)  {
        User user = (User) shiroHelp.getLoginUser();
        Page<UserAskCourse> p = userAskCourseService.listByUser(page, size, keyword, user.getId(), courseId, courseNoId, position, AnswerStatus.ValueOf(answerStatus), order, DirectionStatus.ValueOf(direction));

        return ResponseHelp.success(p, page, size, p.getTotal());
    }

    @RequestMapping(value = "/feedback/error/question", method = RequestMethod.POST)
    @ApiOperation(value = "添加题目勘误", notes = "添加勘误", httpMethod = "POST")
    public Response<Boolean> addFeedbackErrorQuestion(@RequestBody @Validated UserFeedbackErrorQuestionDto dto)  {
        UserFeedbackError entity = Transform.dtoToEntity(dto);
        User user = (User) shiroHelp.getLoginUser();
        entity.setUserId(user.getId());
        entity.setStatus(0);
        entity.setModule(FeedbackModule.QUESTION.key);

        QuestionNo questionNo = questionNoService.get(dto.getQuestionNoId());
        QuestionNoModule questionNoModule = QuestionNoModule.ValueOf(questionNo.getModule());
        QuestionModule questionModule = QuestionModule.WithQuestionNo(questionNoModule);
        entity.setQuestionModule(questionModule.key);
        entity.setQuestionNoId(questionNo.getId());
        entity.setModuleId(questionNo.getQuestionId());

        Question question = questionService.get(questionNo.getQuestionId());

        entity.setQuestionType(question.getQuestionType());
        userFeedbackErrorService.add(entity);

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/feedback/error/data", method = RequestMethod.POST)
    @ApiOperation(value = "添加资料勘误", notes = "添加勘误", httpMethod = "POST")
    public Response<Boolean> addFeedbackError(@RequestBody @Validated UserFeedbackErrorDataDto dto)  {
        UserFeedbackError entity = Transform.dtoToEntity(dto);
        User user = (User) shiroHelp.getLoginUser();
        entity.setUserId(user.getId());
        entity.setModule(FeedbackModule.DATA.key);
        entity.setModuleId(dto.getDataId());
        entity.setStatus(0);
        userFeedbackErrorService.add(entity);

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/feedback/textbook", method = RequestMethod.POST)
    @ApiOperation(value = "添加机经反馈", notes = "添加反馈", httpMethod = "POST")
    public Response<Boolean> addFeedbackTextbook(@RequestBody @Validated UserTextbookFeedbackDto dto)  {
        UserTextbookFeedback entity = Transform.dtoToEntity(dto);
        User user = (User) shiroHelp.getLoginUser();

        TextbookLibrary latest = textbookLibraryService.getLatest();
        entity.setLibraryId(latest.getId());
        entity.setUserId(user.getId());
        entity.setStatus(0);
        if (entity.getNo() != null && entity.getNo() > 0){
            TextbookTopic textbookTopic = textbookTopicService.getByNo(entity.getLibraryId(), entity.getTextbookSubject(), entity.getNo());
            if (textbookTopic == null){
                throw new ParameterException("题目不存在");
            }
            if (textbookTopic != null){
                entity.setTopicId(textbookTopic.getId());
            }
        }
        userTextbookFeedbackService.add(entity);

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/feedback/ready/room", method = RequestMethod.POST)
    @ApiOperation(value = "添加考场反馈", notes = "添加考场反馈", httpMethod = "POST")
    public Response<Boolean> addFeedbackTextbook(@RequestBody @Validated UserReadyRoomFeedbackDto dto)  {
        UserReadyRoomFeedback entity = Transform.dtoToEntity(dto);
        User user = (User) shiroHelp.getLoginUser();
        entity.setUserId(user.getId());
        userReadyRoomFeedbackService.add(entity);

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/faq", method = RequestMethod.POST)
    @ApiOperation(value = "添加faq", notes = "添加faq", httpMethod = "POST")
    public Response<Boolean> addFaq(@RequestBody @Validated FaqDto dto)  {
        Faq entity = Transform.dtoToEntity(dto);
        User user = (User) shiroHelp.getLoginUser();
        entity.setUserId(user.getId());
        entity.setMessage(1);
        // 取消邮箱发送
//        entity.setEmail(user.getEmail());
        faqService.add(entity);

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/comment", method = RequestMethod.POST)
    @ApiOperation(value = "添加评论", notes = "添加评论", httpMethod = "POST")
    public Response<Boolean> addComment(@RequestBody @Validated CommentDto dto)  {
        Comment entity = Transform.dtoToEntity(dto);
        User user = (User) shiroHelp.getLoginUser();
        entity.setUserId(user.getId());
        commentService.add(entity);

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/data/email/subscribe", method = RequestMethod.POST)
    @ApiOperation(value = "资料订阅", notes = "资料订阅", httpMethod = "POST")
    public Response<Boolean> subscribeDataEmail(@RequestBody @Validated DataEmailSubscribeDto dto)  {
        User user = (User) shiroHelp.getLoginUser();
        if (user == null){
            throw new AuthException("请先登录");
        }
        usersService.edit(User.builder()
                .id(user.getId())
                .dataEmailSubscribe(dto.getSubscribe() ? 1 : 0)
                .build());
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/data/subscribe", method = RequestMethod.POST)
    @ApiOperation(value = "资料订阅", httpMethod = "POST")
    public Response<Boolean> subscribeData(@RequestBody @Validated DataSubscribeDto dto) {
        User user = (User) shiroHelp.getLoginUser();

        UserCourseDataSubscribe subscribe = userCourseDataSubscribeService.getByData(user.getId(), dto.getDataId());
        if (dto.getSubscribe()){
            if (subscribe == null){
                userCourseDataSubscribeService.add(UserCourseDataSubscribe.builder()
                        .userId(user.getId())
                        .dataId(dto.getDataId())
                        .build());
            }
        }else{
            if (subscribe!=null){
                userCourseDataSubscribeService.delete(subscribe.getId());
            }
        }

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/data/history", method = RequestMethod.GET)
    @ApiOperation(value = "资料更新记录", httpMethod = "GET")
    public Response<PageMessage<CourseDataHistoryInfoDto>> listDataHistory(
            @RequestParam(required = false, defaultValue = "1") int page,
            @RequestParam(required = false, defaultValue = "100") int size,
            @RequestParam(required = false) Integer dataId,
            HttpSession session) {
        User user = (User) shiroHelp.getLoginUser();

        Page<CourseDataHistory> p = courseDataHistoryService.listByUser(page, size, dataId, user.getId());
        List<CourseDataHistoryInfoDto> pr = Transform.convert(p, CourseDataHistoryInfoDto.class);

        // 绑定资料
        Collection dataIds = Transform.getIds(p, CourseDataHistory.class, "dataId");
        List<CourseData> dataList = courseDataService.select(dataIds);
        Transform.combine(pr, dataList, CourseDataHistoryInfoDto.class, "dataId", "data", CourseData.class, "id", CourseDataExtendDto.class);
        return ResponseHelp.success(pr, page, size, p.getTotal());
    }

    @RequestMapping(value = "/data/list", method = RequestMethod.GET)
    @ApiOperation(value = "订阅资料记录", httpMethod = "GET")
    public Response<PageMessage<CourseDataListDto>> listData(
            @RequestParam(required = false, defaultValue = "1") int page,
            @RequestParam(required = false, defaultValue = "100") int size,
            @RequestParam(required = false) Integer structId,
            @RequestParam(required = false) String dataType,
            @RequestParam(required = false, defaultValue = "id") String order, // latest_time, sale_number
            @RequestParam(required = false, defaultValue = "desc") String direction,
            HttpSession session) {
        User user = (User) shiroHelp.getLoginUser();

        Page<CourseData> p = courseDataService.listByUser(page, size, user.getId(), structId, DataType.ValueOf(dataType),order, DirectionStatus.ValueOf(direction));
        courseExtendService.refreshDataResource(user, p);
        List<CourseDataListDto> pr = Transform.convert(p, CourseDataListDto.class);

        Collection ids = Transform.getIds(p, CourseData.class, "id");

        // 已购买: 查看当前服务
        List<UserOrderRecord> userOrderRecordList = userOrderRecordService.listWithUserData(user.getId(), ids);
        Map userOrderRecordMap = Transform.getMap(userOrderRecordList, UserOrderRecord.class, "productId");

        // 添加购物车
        List<UserOrderCheckout> userOrderCheckoutList = userOrderCheckoutService.listWithProduct(user.getId(), ProductType.DATA, ids);
        Map userOrderCheckoutMap = Transform.getMap(userOrderCheckoutList, UserOrderCheckout.class, "productId");

        for(CourseDataListDto dto : pr){
            dto.setHave(userOrderRecordMap.containsKey(dto.getId()));
            dto.setAdd(userOrderCheckoutMap.containsKey(dto.getId()));
        }

        return ResponseHelp.success(pr, page, size, p.getTotal());
    }

    @RequestMapping(value = "/course/list", method = RequestMethod.GET)
    @ApiOperation(value = "购买的课程记录", httpMethod = "GET")
    public Response<PageMessage<UserCourseDetailDto>> listCourse(
            @RequestParam(required = false, defaultValue = "1") int page,
            @RequestParam(required = false, defaultValue = "100") int size,
            @RequestParam(required = false) String courseModule,
            @RequestParam(required = false) Boolean isUsed,
            @RequestParam(required = false) Boolean isEnd,
            @RequestParam(required = false, defaultValue = "id") String order, // useEndTime desc
            @RequestParam(required = false, defaultValue = "desc") String direction,
            HttpSession session) {
        User user = (User) shiroHelp.getLoginUser();

        CourseModule module = CourseModule.ValueOf(courseModule);
        Page<UserOrderRecord> p;
        if (module == CourseModule.ONLINE){
            // 在线课程包含:视频课程、小班课程
            p = userOrderRecordService.listWithCourse(page, size, user.getId(), new String[]{CourseModule.VIDEO.key}, isUsed,isEnd, order, DirectionStatus.ValueOf(direction));
        } else if (module == CourseModule.VS){
            // 1v1课程:只有系统授课有作业
            p = userOrderRecordService.listWithCourse(page, size, user.getId(), new String[]{CourseModule.VS.key}, isUsed,isEnd, order, DirectionStatus.ValueOf(direction));
        }else{
            throw new ParameterException("课程类型错误");
        }
        List<UserCourseDetailDto> pr = Transform.convert(p, UserCourseDetailDto.class);
        Map<Integer, UserOrderRecord> map = new HashMap<>();
        for(UserOrderRecord record : p){
            map.put(record.getId(), record);
        }

        Collection recordIds = Transform.getIds(p, UserOrderRecord.class,"id");

        // 绑定课程
        Collection courseIds = Transform.getIds(p, UserOrderRecord.class, "productId");
        List<Course> courseList = courseService.select(courseIds);
        Map courseMap = Transform.getMap(courseList, Course.class, "id");
        Transform.combine(pr, courseList, UserCourseDetailDto.class, "productId", "course", Course.class, "id", CourseExtendDto.class);

        // 绑定课时、预约、进度
        Map<Object, Collection<CourseNo>> courseNoMap = courseNoService.groupByCourseId(courseIds);
        Transform.combine(pr, courseNoMap, UserCourseDetailDto.class, "productId", "courseNos", CourseNoExtendDto.class);

        Map<Object, Collection<UserCourseAppointment>> appointmentMap = userCourseAppointmentService.groupByRecordId(recordIds);
        Transform.combine(pr, appointmentMap, UserCourseDetailDto.class, "id", "appointments", UserCourseAppointmentExtendDto.class);

        Map<Object, Collection<UserCourseProgress>> progressMap = userCourseProgressService.groupByRecordId(recordIds);
        Transform.combine(pr, progressMap, UserCourseDetailDto.class, "id", "progress", UserCourseProgressExtendDto.class);
        Map<Object, Collection<UserCourseRecord>> recordMap = userCourseRecordService.groupByRecordId(recordIds);
        for(UserCourseDetailDto dto : pr){
            dto.setTotalDays(courseExtendService.computeCourseDay(map.get(dto.getId())));
            Collection<CourseNo> courseNos = courseNoMap.get(dto.getProductId());
            if (courseNos == null || courseNos.size() == 0) continue;
            Collection<UserCourseProgress> list = progressMap.get(dto.getId());
            if (list == null || list.size() == 0) continue;
            dto.setCurrentNo(courseExtendService.computeCourseNoCurrent(courseNos, list));
            Collection<UserCourseRecord> userCourseRecords = recordMap.get(dto.getId());
            dto.setTotalTime(courseExtendService.computeCourseTime(userCourseRecords));
        }

        // 获取每个科目的所有作业
        Map<Object, Collection<UserPreviewPaperRelation>> previewMap = previewService.groupByRecordId(user.getId(), recordIds, 1000);
        Transform.combine(pr, previewMap, UserCourseDetailDto.class, "id", "papers", BasePaperExtendDto.class);
        for(UserCourseDetailDto dto : pr){
            Collection<UserPreviewPaperRelation> list = previewMap.get(dto.getId());
            if (list == null || list.size() == 0) continue;
            int finish = 0;
            for(UserPreviewPaperRelation relation : list){
                if (relation.getPaper() == null) continue;
                UserPaper paper = relation.getPaper();
                if (paper.getTimes() > 0){
                    finish += 1;
                }
            }
            dto.setPreviewProgress(list.size()> 0 ? finish * 100 / list.size(): 0);
        }

        // 绑定老师
        Collection teacherIds = Transform.getIds(p, UserOrderRecord.class, "teacherId");
        List<CourseTeacher> teacherList = courseTeacherService.select(teacherIds);
        Transform.combine(pr, teacherList, UserCourseDetailDto.class, "teacherId", "teacher", CourseTeacher.class, "id", CourseTeacherExtendDto.class);

        // 提问数、笔记数
        Map<Object, Collection<UserAskCourse>> askMap = userAskCourseService.groupByRecordId(recordIds);
        Map<Object, Collection<UserNoteCourse>> noteMap = userNoteCourseService.groupByCourse(user.getId(), courseIds);
        for(UserCourseDetailDto dto : pr){
            Collection<CourseNoExtendDto> courseNos = dto.getCourseNos();
            if (courseNos == null) continue;
            Collection<UserAskCourse> askList = askMap.get(dto.getId());
            Map<Object, List<UserAskCourse>> askListMap = Transform.getMapList(askList, UserAskCourse.class, "courseNoId");
            Collection<UserNoteCourse> noteList = noteMap.get(dto.getProductId());
            Map notes = Transform.getMap(noteList, UserNoteCourse.class, "courseNoId");
            int noteNumber = 0;
            int askNumber = askList == null ? 0: askList.size();
            int answerNumber = askList == null ? 0 : (int)askList.stream().filter(r->r.getAnswerStatus()== AnswerStatus.ANSWER.index).count();
            for(CourseNoExtendDto courseNo : courseNos){
                if (notes.get(courseNo.getId()) != null){
                    courseNo.setNote(true);
                    noteNumber += 1;
                }
                List<UserAskCourse> askListNo = askListMap.get(courseNo.getId());
                if (askListNo != null){
                    courseNo.setAskNumber(askListNo.size());
                    courseNo.setAnswerNumber((int)askListNo.stream().filter(r->r.getAnswerStatus()== AnswerStatus.ANSWER.index).count());
                }
            }
            dto.setNoteNumber(noteNumber);
            dto.setAskNumber(askNumber);
            dto.setAnswerNumber(answerNumber);
        }

        // vs预约comment
        Map<Object, Collection<UserCourseAppointmentComment>> commentMap = userCourseAppointmentCommentService.groupByRecordId(recordIds);
        Transform.combine(pr, commentMap, UserCourseDetailDto.class, "id", "comments", UserCourseAppointmentComment.class);


        return ResponseHelp.success(pr, page, size, p.getTotal());
    }

    @RequestMapping(value = "/course/detail", method = RequestMethod.GET)
    @ApiOperation(value = "购买的课程记录", httpMethod = "GET")
    public Response<UserCourseDetailDto> detailCourse(int recordId,
            HttpSession session) {
        User user = (User) shiroHelp.getLoginUser();

        UserOrderRecord record = userOrderRecordService.get(recordId);
        UserCourseDetailDto dto = Transform.convert(record, UserCourseDetailDto.class);

        // 绑定课程
        Course course = courseService.get(record.getProductId());
        dto.setCourse(Transform.convert(course, CourseExtendDto.class));

        // 绑定课时、预约、进度
        List<CourseNo> courseNoList = courseNoService.allCourse(course.getId());
        dto.setCourseNos(Transform.convert(courseNoList, CourseNoExtendDto.class));

        List<UserCourseAppointment> appointmentList = userCourseAppointmentService.listByRecord(recordId);
        dto.setAppointments(Transform.convert(appointmentList, UserCourseAppointmentExtendDto.class));

        List<UserCourseProgress> progressList = userCourseProgressService.listCourse(recordId, course.getId());
        List<UserCourseRecord> recordList = userCourseRecordService.allWithRecord(recordId);
        if(progressList != null)dto.setCurrentNo(courseExtendService.computeCourseNoCurrent(courseNoList, progressList));
        if(recordList != null)dto.setTotalTime(courseExtendService.computeCourseTime(recordList));
        dto.setTotalDays(courseExtendService.computeCourseDay(record));

        // 获取每个科目的所有作业
        List<UserPreviewPaperRelation> previewList = previewService.list(1, 1000, recordId, user.getId(), null,null);
        dto.setPapers(Transform.convert(previewList, BasePaperExtendDto.class));
        int finish = 0;
        for(UserPreviewPaperRelation relation : previewList){
            if (relation.getPaper() == null) continue;
            UserPaper paper = relation.getPaper();
            if (paper.getTimes() > 0){
                finish += 1;
            }
        }
        dto.setPreviewProgress(previewList.size() > 0 ? finish * 100 / previewList.size(): 0);

        // 绑定老师
        CourseTeacher teacher = courseTeacherService.get(record.getTeacherId());
        dto.setTeacher(Transform.convert(teacher, CourseTeacherExtendDto.class));

        // 提问数、笔记数
        Collection<CourseNoExtendDto> courseNos = dto.getCourseNos();
        if (courseNos != null && courseNos.size() > 0) {
            Collection<UserAskCourse> askList = userAskCourseService.listByRecord(recordId);
            Map<Object, List<UserAskCourse>> askListMap = Transform.getMapList(askList, UserAskCourse.class, "courseNoId");
            Collection<UserNoteCourse> noteList = userNoteCourseService.listByCourse(course.getId());
            Map notes = Transform.getMap(noteList, UserNoteCourse.class, "courseNoId");
            int noteNumber = 0;
            int askNumber = askList.size();
            int answerNumber = (int)askList.stream().filter(r->r.getAnswerStatus()== AnswerStatus.ANSWER.index).count();
            for(CourseNoExtendDto courseNo : courseNos){
                if (notes.get(courseNo.getId()) != null){
                    courseNo.setNote(true);
                    noteNumber += 1;
                }
                List<UserAskCourse> askListNo = askListMap.get(courseNo.getId());
                if (askListNo != null){
                    courseNo.setAskNumber(askListNo.size());
                    courseNo.setAnswerNumber((int)askListNo.stream().filter(r->r.getAnswerStatus()== AnswerStatus.ANSWER.index).count());
                }
            }
            dto.setNoteNumber(noteNumber);
            dto.setAskNumber(askNumber);
            dto.setAnswerNumber(answerNumber);
        }

        // vs预约comment
        List<UserCourseAppointmentComment> commentList = userCourseAppointmentCommentService.listByRecord(recordId);
        dto.setComments(commentList);

        return ResponseHelp.success(dto);
    }

    @RequestMapping(value = "/course/cctalk_name", method = RequestMethod.POST)
    @ApiOperation(value = "设置课程cctalk", notes = "设置课程cctalk", httpMethod = "POST")
    public Response<Boolean> setCourseCCTalkName(@RequestBody @Validated UserCourseCCTalkNameDto dto)  {
        User user = (User) shiroHelp.getLoginUser();
        UserOrderRecord entity = Transform.dtoToEntity(dto);
        UserOrderRecord in = userOrderRecordService.get(dto.getId());
        if (!in.getUserId().equals(user.getId())){
            throw new ParameterException("记录不存在");
        }
        entity = userOrderRecordService.edit(entity);
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/course/appointment/question", method = RequestMethod.POST)
    @ApiOperation(value = "预约提交答疑文档", notes = "预约提交答疑文档", httpMethod = "POST")
    public Response<Boolean> uploadAppointmentQuestion(@RequestBody @Validated UserCourseAppointmentQuestionDto dto)  {
        User user = (User) shiroHelp.getLoginUser();
        UserCourseAppointment entity = Transform.dtoToEntity(dto);
        UserCourseAppointment in = userCourseAppointmentService.get(dto.getId());
        if (!in.getUserId().equals(user.getId())){
            throw new ParameterException("记录不存在");
        }
        entity = userCourseAppointmentService.edit(entity);
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/course/appointment/comment/add", method = RequestMethod.POST)
    @ApiOperation(value = "预约评论添加", notes = "预约评论添加", httpMethod = "POST")
    public Response<Boolean> addAppointmentComment(@RequestBody @Validated UserCourseAppointmentCommentDto dto)  {
        User user = (User) shiroHelp.getLoginUser();
        UserCourseAppointmentComment entity = Transform.dtoToEntity(dto);
        UserCourseAppointment appointment = userCourseAppointmentService.get(entity.getAppointmentId());
        entity.setUserId(user.getId());
        entity.setRecordId(appointment.getRecordId());
        if (entity.getParentId() > 0){
            UserCourseAppointmentComment comment = userCourseAppointmentCommentService.get(entity.getParentId());
            if (comment != null){
                entity.setReply(comment.getContent());
            }
        }
        entity = userCourseAppointmentCommentService.add(entity);
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/course/appointment/comment/edit", method = RequestMethod.POST)
    @ApiOperation(value = "预约评论编辑", notes = "预约评论编辑", httpMethod = "POST")
    public Response<Boolean> editAppointmentComment(@RequestBody @Validated UserCourseAppointmentCommentDto dto)  {
        User user = (User) shiroHelp.getLoginUser();
        UserCourseAppointmentComment entity = Transform.dtoToEntity(dto);

        UserCourseAppointmentComment in = userCourseAppointmentCommentService.get(dto.getId());
        if (!in.getUserId().equals(user.getId())){
            throw new ParameterException("记录不存在");
        }
        entity = userCourseAppointmentCommentService.edit(entity);
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/course/appointment/comment/delete", method = RequestMethod.DELETE)
    @ApiOperation(value = "预约评论删除", notes = "预约评论删除", httpMethod = "DELETE")
    public Response<Boolean> deleteAppointmentComment(int id)  {
        User user = (User) shiroHelp.getLoginUser();

        UserCourseAppointmentComment in = userCourseAppointmentCommentService.get(id);
        if (!in.getUserId().equals(user.getId())){
            throw new ParameterException("记录不存在");
        }
        userCourseAppointmentCommentService.delete(id);
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/course/suspend", method = RequestMethod.POST)
    @ApiOperation(value = "申请停课", notes = "申请停课", httpMethod = "POST")
    public Response<Boolean> suspendCourse(@RequestBody @Validated CourseSuspendDto dto)  {
        User user = (User) shiroHelp.getLoginUser();

        courseExtendService.suspendCourse(user.getId(), dto.getRecordId());

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/course/restore", method = RequestMethod.POST)
    @ApiOperation(value = "恢复停课", notes = "恢复停课", httpMethod = "POST")
    public Response<Boolean> restoreCourse(@RequestBody @Validated CourseRestoreDto dto)  {
        User user = (User) shiroHelp.getLoginUser();

        courseExtendService.restoreCourse(user.getId(), dto.getRecordId());

        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/course/time", method = RequestMethod.GET)
    @ApiOperation(value = "时间表", notes = "时间表", httpMethod = "GET")
    public Response<List<UserCourseTimeDto>> timeCourse(int recordId)  {
        User user = (User) shiroHelp.getLoginUser();
        List<UserCourseTimeDto> dtos = new ArrayList<>();

        UserOrderRecord record = userOrderRecordService.get(recordId);
        if (record == null){
            throw new ParameterException("记录不存在");
        }
        if (!record.getUserId().equals(user.getId())){
            throw new ParameterException("记录不存在");
        }
        Integer courseId = record.getProductId();

        // 获取停课记录
        Date suspend = record.getSuspendTime();
        if (suspend != null){
            Date restore = record.getRestoreTime();
            if (restore == null) restore = new Date();

            while(suspend.before(restore)){
                UserCourseTimeDto dto = new UserCourseTimeDto();
                dto.setType("stop");
                dto.setDay(Tools.day(suspend));
                dtos.add(dto);
                suspend = Tools.addDate(suspend, 1);
            }
        }
        List<Long> tmpList = new ArrayList<>();

        // 获取听课记录
        List<UserCourseRecord> userCourseRecordList = userCourseRecordService.allWithRecord(recordId);
        tmpList.clear();
        for(UserCourseRecord userCourseRecord:userCourseRecordList){
            Date day = Tools.day(userCourseRecord.getCreateTime());
            if (!tmpList.contains(day.getTime())){
                tmpList.add(day.getTime());

                UserCourseTimeDto dto = new UserCourseTimeDto();
                dto.setType("course");
                dto.setDay(day);
                dtos.add(dto);
            }
        }

        // 预习作业
        List<CourseNo> courseNoList = courseNoService.allCourse(courseId);
        Collection courseNoIds = Transform.getIds(courseNoList, CourseNo.class, "id");
        List<PreviewAssign> previewAssignList = previewAssignService.listByCourseNos(courseId, courseNoIds);
        Collection assignIds = Transform.getIds(previewAssignList, PreviewAssign.class, "id");
        List<UserPaper> userPaperList = userPaperService.listWithCourse(user.getId(), assignIds, recordId);
        Collection paperIds = Transform.getIds(userPaperList, UserPaper.class, "id");
        List<UserReport> userReportList = userReportService.listByPaper(paperIds);
        tmpList.clear();
        for(UserReport userReport:userReportList){
            Date day = Tools.day(userReport.getCreateTime());
            if (!tmpList.contains(day.getTime())){
                tmpList.add(day.getTime());

                UserCourseTimeDto dto = new UserCourseTimeDto();
                dto.setType("preview");
                dto.setDay(day);
                dtos.add(dto);
            }
        }

        return ResponseHelp.success(dtos);
    }

    @RequestMapping(value = "/export/question/collect", method = RequestMethod.POST)
    @ApiOperation(value = "导出题目", notes = "导出题目", httpMethod = "POST")
    public Response<Integer> exportQuestionCollect(@RequestBody @Validated UserExportDto dto)  {
        User user = (User) shiroHelp.getLoginUser();
        UserExport entity = exportService.addQuestionCollect(user.getId(), JSONObject.parseObject(JSONObject.toJSONString(dto.getSetting())));
        return ResponseHelp.success(entity.getId());
    }

    @RequestMapping(value = "/export/question/error", method = RequestMethod.POST)
    @ApiOperation(value = "导出题目", notes = "导出题目", httpMethod = "POST")
    public Response<Integer> exportQuestionError(@RequestBody @Validated UserExportDto dto)  {
        User user = (User) shiroHelp.getLoginUser();
        UserExport entity = exportService.addQuestionError(user.getId(), JSONObject.parseObject(JSONObject.toJSONString(dto.getSetting())));
        return ResponseHelp.success(entity.getId());
    }

    @RequestMapping(value = "/export/note/question", method = RequestMethod.POST)
    @ApiOperation(value = "导出题目笔记", notes = "导出题目笔记", httpMethod = "POST")
    public Response<Integer> exportNoteQuestion(@RequestBody @Validated UserExportDto dto)  {
        User user = (User) shiroHelp.getLoginUser();
        UserExport entity = exportService.addQuestionNote(user.getId(), JSONObject.parseObject(JSONObject.toJSONString(dto.getSetting())));
        return ResponseHelp.success(entity.getId());
    }

    @RequestMapping(value = "/export/note/course", method = RequestMethod.POST)
    @ApiOperation(value = "导出课程笔记", notes = "导出课程笔记", httpMethod = "POST")
    public Response<Integer> exportNoteCourse(@RequestBody @Validated UserExportDto dto)  {
        User user = (User) shiroHelp.getLoginUser();
        UserExport entity = exportService.addCourseNote(user.getId(), JSONObject.parseObject(JSONObject.toJSONString(dto.getSetting())));
        return ResponseHelp.success(entity.getId());
    }

    @RequestMapping(value = "/export/detail", method = RequestMethod.GET)
    @ApiOperation(value = "导出详情", notes = "导出详情", httpMethod = "GET")
    public Response<UserExport> exportDetail(int id)  {
        User user = (User) shiroHelp.getLoginUser();
        UserExport entity = userExportService.get(id);
        if (!user.getId().equals(entity.getUserId())){
            throw new ParameterException("记录不存在");
        }
        return ResponseHelp.success(entity);
    }

    @RequestMapping(value = "/export/tips", method = RequestMethod.POST)
    @ApiOperation(value = "关闭提示", notes = "关闭提示", httpMethod = "POST")
    public Response<Boolean> exportTips()  {
        User user = (User) shiroHelp.getLoginUser();

        User in = usersService.get(user.getId());
        usersService.edit(User.builder()
                .id(user.getId())
                .exportTips(1)
                .build());
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/textbook/tips", method = RequestMethod.POST)
    @ApiOperation(value = "关闭提示", notes = "关闭提示", httpMethod = "POST")
    public Response<Boolean> textbookTips()  {
        User user = (User) shiroHelp.getLoginUser();

        User in = usersService.get(user.getId());
        usersService.edit(User.builder()
                .id(user.getId())
                .textbookTips(1)
                .build());
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/course/comment/tips", method = RequestMethod.POST)
    @ApiOperation(value = "关闭评论提示提示", notes = "关闭评论提示提示", httpMethod = "POST")
    public Response<Boolean> closeCommentTips(@RequestBody @Validated RecordCommentTipsDto dto)  {
        User user = (User) shiroHelp.getLoginUser();
        UserOrderRecord record = userOrderRecordService.get(dto.getRecordId());
        userOrderRecordService.edit(UserOrderRecord.builder()
                .id(dto.getRecordId())
                .commentTips(1)
                .build());
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/search/history", method = RequestMethod.POST)
    @ApiOperation(value = "添加搜索记录", notes = "添加搜索记录", httpMethod = "POST")
    public Response<Boolean> addSearchHistory(@RequestBody @Validated SearchHistoryDto dto)  {
        User user = (User) shiroHelp.getLoginUser();
        QuestionNo questionNo = questionNoService.get(dto.getQuestionNoId());
        userSearchHistoryService.add(UserSearchHistory.builder()
                .userId(user.getId())
                .questionNoId(questionNo.getId())
                .questionId(questionNo.getQuestionId())
                .build());
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/search/history/clear", method = RequestMethod.POST)
    @ApiOperation(value = "清除搜索记录", notes = "清除搜索记录", httpMethod = "POST")
    public Response<Boolean> addSearchHistory(@RequestBody @Validated SearchHistoryClearDto dto)  {
        User user = (User) shiroHelp.getLoginUser();

        Date end;
        Date start;
        try{
            SimpleDateFormat sdf =   new SimpleDateFormat("yyyy-MM-dd");
            start = sdf.parse(dto.getDate());
            end = Tools.addDate(start, 1);
        }catch (Exception e){
            throw new ParameterException("日期格式错误");
        }
        userSearchHistoryService.clearDate(user.getId(), start, end);
        return ResponseHelp.success(true);
    }

    @RequestMapping(value = "/search/history/list", method = RequestMethod.GET)
    @ApiOperation(value = "搜索历史记录", httpMethod = "GET")
    public Response<List<UserSearchHistoryDto>> listSearchHistory(
            HttpSession session) {
        User user = (User) shiroHelp.getLoginUser();

        int week = 0;

        Date now = Tools.today();
        int day = Tools.getDayOfWeek(now);
        Date start = Tools.addDate(now, -6);
        Date end = Tools.addDate(start, 7);

        List<UserSearchHistory> p = userSearchHistoryService.listByUser(user.getId(), start, end);
        List<UserSearchHistoryDto> pr = Transform.convert(p, UserSearchHistoryDto.class);

        Collection questionIds = Transform.getIds(p, UserSearchHistory.class, "questionId");
        List<Question> questionList = questionService.select(questionIds);
        Transform.combine(pr, questionList, UserSearchHistoryDto.class, "questionId", "question", Question.class, "id", QuestionExtendDto.class);

        Collection questionNoIds = Transform.getIds(p, UserSearchHistory.class, "questionNoId");
        List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);
        Transform.combine(pr, questionNoList, UserSearchHistoryDto.class, "questionNoId", "questionNo", QuestionNo.class, "id", QuestionNoExtendDto.class);

        return ResponseHelp.success(pr);
    }
}