feat(ai): 新增会员答题分页及详情查询功能
- 在 AiMemberAnswerMapper 中新增 getAnswerPage 方法及对应 XML 查询语句
- 新增 ApiMemberAnswerPageDto 和 ApiMemberAnswerPageVo 用于分页查询参数和返回结果
- 在 AiMemberAnswerService 及其实现类中添加 getAnswerPage 方法
- 在 AiMemberService 及其实现类中新增 answerPage 和 answerInfo 接口实现
- 新增 ApiMemberAnswerInfoDto 和 ApiMemberAnswerInfoVo 用于答题详情接口参数和响应
- 在 ApiMemberController 中增加 /answerPage 和 /answerInfo两个 POST 接口
- 优化 AiMemberTeamPracticeVo,增加 memberUuid 字段
- 统一导入包路径,简化代码结构
8 files modified
4 files added
254 ■■■■■ changed files
src/main/java/cc/mrbird/febs/ai/controller/member/ApiMemberController.java 28 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/mapper/AiMemberAnswerMapper.java 4 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/req/member/ApiMemberAnswerInfoDto.java 17 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/req/member/ApiMemberAnswerPageDto.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/res/member/ApiMemberAnswerInfoVo.java 37 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/res/member/ApiMemberAnswerPageVo.java 19 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/res/member/ApiMemberTeamPracticeVo.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/service/AiMemberAnswerService.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/service/AiMemberService.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/service/impl/AiMemberAnswerServiceImpl.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/service/impl/AiMemberServiceImpl.java 84 ●●●●● patch | view | raw | blame | history
src/main/resources/mapper/modules/AiMemberAnswerMapper.xml 15 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/controller/member/ApiMemberController.java
@@ -1,11 +1,7 @@
package cc.mrbird.febs.ai.controller.member;
import cc.mrbird.febs.ai.req.member.ApiMemberTeamPageDto;
import cc.mrbird.febs.ai.req.member.ApiMemberTeamPracticeDto;
import cc.mrbird.febs.ai.req.member.ApiMemberTeamStudyDto;
import cc.mrbird.febs.ai.res.member.ApiMemberTeamPageVo;
import cc.mrbird.febs.ai.res.member.ApiMemberTeamPracticeVo;
import cc.mrbird.febs.ai.res.member.ApiMemberTeamStudyVo;
import cc.mrbird.febs.ai.req.member.*;
import cc.mrbird.febs.ai.res.member.*;
import cc.mrbird.febs.ai.service.AiMemberService;
import cc.mrbird.febs.common.entity.FebsResponse;
import io.swagger.annotations.Api;
@@ -53,6 +49,26 @@
        return aiMemberService.practice(dto);
    }
    @ApiOperation(value = "题目练习分页查询", notes = "题目练习分页查询")
    @ApiResponses({
            @ApiResponse(code = 200, message = "success", response = ApiMemberAnswerPageVo.class)
    })
    @PostMapping(value = "/answerPage")
    public FebsResponse answerPage(@RequestBody @Validated ApiMemberAnswerPageDto dto) {
        return aiMemberService.answerPage(dto);
    }
    @ApiOperation(value = "题目练习详情查询", notes = "题目练习详情查询")
    @ApiResponses({
            @ApiResponse(code = 200, message = "success", response = ApiMemberAnswerInfoVo.class)
    })
    @PostMapping(value = "/answerInfo")
    public FebsResponse answerInfo(@RequestBody @Validated ApiMemberAnswerInfoDto dto) {
        return aiMemberService.answerInfo(dto);
    }
    @ApiOperation(value = "学习时长", notes = "学习时长")
    @ApiResponses({
            @ApiResponse(code = 200, message = "success", response = ApiMemberTeamStudyVo.class)
src/main/java/cc/mrbird/febs/ai/mapper/AiMemberAnswerMapper.java
@@ -1,7 +1,9 @@
package cc.mrbird.febs.ai.mapper;
import cc.mrbird.febs.ai.entity.AiMemberAnswer;
import cc.mrbird.febs.ai.req.member.ApiMemberAnswerPageDto;
import cc.mrbird.febs.ai.req.memberAnswer.ApiMemberProductWorkPageDto;
import cc.mrbird.febs.ai.res.member.ApiMemberAnswerPageVo;
import cc.mrbird.febs.ai.res.memberAnswer.ApiMemberProductWorkVo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -16,4 +18,6 @@
public interface AiMemberAnswerMapper extends BaseMapper<AiMemberAnswer> {
    Page<ApiMemberProductWorkVo> getPageMyWorkListByQuery(Page<ApiMemberProductWorkVo> page,  @Param("record")ApiMemberProductWorkPageDto dto);
    Page<ApiMemberAnswerPageVo> getAnswerPage(Page<ApiMemberAnswerPageVo> page, @Param("record")ApiMemberAnswerPageDto dto);
}
src/main/java/cc/mrbird/febs/ai/req/member/ApiMemberAnswerInfoDto.java
New file
@@ -0,0 +1,17 @@
package cc.mrbird.febs.ai.req.member;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
@ApiModel(value = "ApiMemberAnswerInfoDto", description = "参数")
public class ApiMemberAnswerInfoDto {
    @NotBlank(message = "产品ID不能为空")
    @ApiModelProperty(value = "产品ID", example = "10")
    private String answerId;
}
src/main/java/cc/mrbird/febs/ai/req/member/ApiMemberAnswerPageDto.java
New file
@@ -0,0 +1,26 @@
package cc.mrbird.febs.ai.req.member;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
@ApiModel(value = "ApiMemberAnswerPageDto", description = "参数")
public class ApiMemberAnswerPageDto {
    @NotNull(message = "页码不能为空")
    @ApiModelProperty(value = "页码", example = "1")
    private Integer pageNow;
    @NotNull(message = "每页数量不能为空")
    @ApiModelProperty(value = "每页数量", example = "10")
    private Integer pageSize;
    @NotBlank(message = "会员ID不能为空")
    @ApiModelProperty(value = "会员ID", example = "10")
    private String memberUuid;
}
src/main/java/cc/mrbird/febs/ai/res/member/ApiMemberAnswerInfoVo.java
New file
@@ -0,0 +1,37 @@
package cc.mrbird.febs.ai.res.member;
import cc.mrbird.febs.ai.res.memberAnswer.ApiMemberAnswerPreviousItemVo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
@ApiModel(value = "ApiMemberAnswerInfoVo", description = "参数")
public class ApiMemberAnswerInfoVo {
    @ApiModelProperty(value = "题目")
    private String title;
    @ApiModelProperty(value = "难度:1-简单,2-中等,3-困难")
    private Integer difficulty;
    @ApiModelProperty(value = "用户答题答案ID")
    private String memberAnswerQuestionId;
    @ApiModelProperty(value = "答案解析")
    private String answerAnalysis;
    @ApiModelProperty(value = "题目ID")
    private String productQuestionId;
    @ApiModelProperty(value = "时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createdTime;
    @ApiModelProperty(value = "选项")
    private List<ApiMemberAnswerPreviousItemVo> answerList;
}
src/main/java/cc/mrbird/febs/ai/res/member/ApiMemberAnswerPageVo.java
New file
@@ -0,0 +1,19 @@
package cc.mrbird.febs.ai.res.member;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "ApiMemberAnswerPageVo", description = "参数")
public class ApiMemberAnswerPageVo {
    @ApiModelProperty(value = "答题ID")
    private String id;
    @ApiModelProperty(value = "名称")
    private String name;
    @ApiModelProperty(value = "目标")
    private String target;
}
src/main/java/cc/mrbird/febs/ai/res/member/ApiMemberTeamPracticeVo.java
@@ -11,6 +11,9 @@
@ApiModel(value = "ApiMemberTeamPracticeVo", description = "参数")
public class ApiMemberTeamPracticeVo {
    @ApiModelProperty(value = "会员ID")
    private String memberUuid;
    @ApiModelProperty(value = "名称")
    private String name;
src/main/java/cc/mrbird/febs/ai/service/AiMemberAnswerService.java
@@ -1,9 +1,12 @@
package cc.mrbird.febs.ai.service;
import cc.mrbird.febs.ai.entity.AiMemberAnswer;
import cc.mrbird.febs.ai.req.member.ApiMemberAnswerPageDto;
import cc.mrbird.febs.ai.req.memberAnswer.*;
import cc.mrbird.febs.ai.res.member.ApiMemberAnswerPageVo;
import cc.mrbird.febs.common.entity.FebsResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.Date;
@@ -40,4 +43,6 @@
    List<AiMemberAnswer> getListByCompanyId(String companyId);
    List<AiMemberAnswer> getListByCompanyIdAndMemberUuid(String companyId, String memberUuid);
    Page<ApiMemberAnswerPageVo> getAnswerPage(Page<ApiMemberAnswerPageVo> page, ApiMemberAnswerPageDto dto);
}
src/main/java/cc/mrbird/febs/ai/service/AiMemberService.java
@@ -1,9 +1,7 @@
package cc.mrbird.febs.ai.service;
import cc.mrbird.febs.ai.entity.AiMember;
import cc.mrbird.febs.ai.req.member.ApiMemberTeamPageDto;
import cc.mrbird.febs.ai.req.member.ApiMemberTeamPracticeDto;
import cc.mrbird.febs.ai.req.member.ApiMemberTeamStudyDto;
import cc.mrbird.febs.ai.req.member.*;
import cc.mrbird.febs.common.entity.FebsResponse;
import com.baomidou.mybatisplus.extension.service.IService;
@@ -16,4 +14,8 @@
    FebsResponse practice(ApiMemberTeamPracticeDto dto);
    FebsResponse study(ApiMemberTeamStudyDto dto);
    FebsResponse answerPage(ApiMemberAnswerPageDto dto);
    FebsResponse answerInfo(ApiMemberAnswerInfoDto dto);
}
src/main/java/cc/mrbird/febs/ai/service/impl/AiMemberAnswerServiceImpl.java
@@ -3,7 +3,9 @@
import cc.mrbird.febs.ai.entity.*;
import cc.mrbird.febs.ai.enumerates.AiTypeEnum;
import cc.mrbird.febs.ai.mapper.AiMemberAnswerMapper;
import cc.mrbird.febs.ai.req.member.ApiMemberAnswerPageDto;
import cc.mrbird.febs.ai.req.memberAnswer.*;
import cc.mrbird.febs.ai.res.member.ApiMemberAnswerPageVo;
import cc.mrbird.febs.ai.res.memberAnswer.*;
import cc.mrbird.febs.ai.res.product.ApiProductVo;
import cc.mrbird.febs.ai.res.productQuestionItem.ApiMemberAnswerItemVo;
@@ -426,4 +428,10 @@
                        .eq(AiMemberAnswer::getState, AiTypeEnum.AI_MEMBER_ANSWER_STATE_DONE.getCode())
        );
    }
    @Override
    public Page<ApiMemberAnswerPageVo> getAnswerPage(Page<ApiMemberAnswerPageVo> page, ApiMemberAnswerPageDto dto) {
        return aiMemberAnswerMapper.getAnswerPage(page,dto);
    }
}
src/main/java/cc/mrbird/febs/ai/service/impl/AiMemberServiceImpl.java
@@ -3,13 +3,9 @@
import cc.mrbird.febs.ai.entity.*;
import cc.mrbird.febs.ai.enumerates.AiTypeEnum;
import cc.mrbird.febs.ai.mapper.AiMemberMapper;
import cc.mrbird.febs.ai.req.member.ApiMemberTeamPageDto;
import cc.mrbird.febs.ai.req.member.ApiMemberTeamPracticeDto;
import cc.mrbird.febs.ai.req.member.ApiMemberTeamStudyDto;
import cc.mrbird.febs.ai.res.member.ApiMemberTeamPageVo;
import cc.mrbird.febs.ai.res.member.ApiMemberTeamPracticeVo;
import cc.mrbird.febs.ai.res.member.ApiMemberTeamStudyVo;
import cc.mrbird.febs.ai.res.productPoint.ApiProductPointListVo;
import cc.mrbird.febs.ai.req.member.*;
import cc.mrbird.febs.ai.res.member.*;
import cc.mrbird.febs.ai.res.memberAnswer.ApiMemberAnswerPreviousItemVo;
import cc.mrbird.febs.ai.service.*;
import cc.mrbird.febs.common.entity.FebsResponse;
import cc.mrbird.febs.common.exception.FebsException;
@@ -41,9 +37,11 @@
    private final MallMemberMapper mallMemberMapper;
    private final AiMemberTalkService aiMemberTalkService;
    private final AiMemberAnswerService aiMemberAnswerService;
    private final AiMemberAnswerItemService aiMemberAnswerItemService;
    private final AiMemberPointService aiMemberPointService;
    private final AiProductService aiProductService;
    private final AiProductPointService aiProductPointService;
    private final AiProductQuestionItemService aiProductQuestionItemService;
    @Override
    public AiMember getById(String id) {
        return aiMemberMapper.selectById( id);
@@ -207,6 +205,7 @@
            String productId = aiProduct.getId();
            ApiMemberTeamPracticeVo vo = new ApiMemberTeamPracticeVo();
            vo.setMemberUuid(memberUuid);
            vo.setName(aiProduct.getName());
            vo.setTarget(aiProduct.getTarget());
@@ -286,4 +285,75 @@
        return new FebsResponse().success().data(vos);
    }
    @Override
    public FebsResponse answerPage(ApiMemberAnswerPageDto dto) {
        // 创建分页对象,传入当前页和每页大小
        Page<ApiMemberAnswerPageVo> page = new Page<>(dto.getPageNow(), dto.getPageSize());
        Page<ApiMemberAnswerPageVo> aiMemberAnswerPage = aiMemberAnswerService.getAnswerPage(page, dto);
        return new FebsResponse().success().data(aiMemberAnswerPage);
    }
    @Override
    public FebsResponse answerInfo(ApiMemberAnswerInfoDto dto) {
        List<ApiMemberAnswerInfoVo> vos = new ArrayList<>();
        String answerId = dto.getAnswerId();
        AiMemberAnswer memberAnswer = aiMemberAnswerService.getById(answerId);
        if (memberAnswer == null){
            return new FebsResponse().success().data(vos);
        }
        List<AiMemberAnswerItem> records = aiMemberAnswerItemService.getBaseMapper().selectList(
                Wrappers.lambdaQuery(AiMemberAnswerItem.class)
                        .eq(AiMemberAnswerItem::getAnswerId, memberAnswer.getId())
                        .orderByAsc(AiMemberAnswerItem::getCreatedTime)
        );
        if (CollUtil.isNotEmpty(records)){
            Set<String> collect = records.stream().map(AiMemberAnswerItem::getProductQuestionId).collect(Collectors.toSet());
            LambdaQueryWrapper<AiProductQuestionItem> questionItemQuery = Wrappers.lambdaQuery(AiProductQuestionItem.class);
            questionItemQuery.in(AiProductQuestionItem::getProductQuestionId,collect);
            List<AiProductQuestionItem> listByQuery = aiProductQuestionItemService.getListByQuery(questionItemQuery);
            if (CollUtil.isNotEmpty(listByQuery)){
                //利用stream流,操作集合listByQuery,返回一个map对象,key为productQuestionId,value为List<AiProductQuestionItem>对象
                Map<String, List<AiProductQuestionItem>> questionItemMap =
                        listByQuery.stream().collect(Collectors.groupingBy(AiProductQuestionItem::getProductQuestionId));
                for (AiMemberAnswerItem record : records){
                    String productQuestionId = record.getProductQuestionId();
                    ApiMemberAnswerInfoVo vo = new ApiMemberAnswerInfoVo();
                    vo.setTitle(record.getTitle());
                    vo.setDifficulty(record.getDifficulty());
                    vo.setMemberAnswerQuestionId(record.getMemberAnswerQuestionId());
                    vo.setAnswerAnalysis(record.getAnswerAnalysis());
                    vo.setProductQuestionId(record.getProductQuestionId());
                    vo.setCreatedTime(record.getCreatedTime());
                    List<AiProductQuestionItem> questionItemList = questionItemMap.get(productQuestionId);
                    List<ApiMemberAnswerPreviousItemVo> answerList = new ArrayList<>();
                    if (CollUtil.isNotEmpty(questionItemList)){
                        for (AiProductQuestionItem questionItem : questionItemList){
                            ApiMemberAnswerPreviousItemVo itemVo = new ApiMemberAnswerPreviousItemVo();
                            itemVo.setId(questionItem.getId());
                            itemVo.setAnswer(questionItem.getAnswer());
                            itemVo.setAnswerAnalysis(questionItem.getAnswerAnalysis());
                            itemVo.setCorrectAnswer(questionItem.getCorrectAnswer());
                            answerList.add(itemVo);
                        }
                    }
                    vo.setAnswerList(answerList);
                    vos.add( vo);
                }
            }
        }
        return new FebsResponse().success().data(vos);
    }
}
src/main/resources/mapper/modules/AiMemberAnswerMapper.xml
@@ -24,4 +24,19 @@
        group by a.product_id
        order by a.CREATED_TIME desc
    </select>
    <select id="getAnswerPage" resultType="cc.mrbird.febs.ai.res.member.ApiMemberAnswerPageVo">
        select
            a.id as id,
            b.name as name,
            b.target as target
        from ai_member_answer a
        left join ai_product b on a.product_id = b.id and b.state = 1
        where a.member_id = #{record.memberUuid}
          and a.state = 1
        group by a.product_id
        order by a.CREATED_TIME desc
    </select>
</mapper>