天机学堂 第五天 问答系统

发布于:2024-08-08 ⋅ 阅读:(81) ⋅ 点赞:(0)

返回数据思路

先去vo里面查看,有哪些字段,得出哪些数据可以直接返回,哪些是需要去别的数据库去查

以分页查看回答或评论为例,在这个vo中后面这几项都是需要单独去调用才可以查询到的

提出问题页面

 回答问题及评论实现效果

 

 

  • 也没渲染只分两层:

    • 对问题的一级回复,称为回答

    • 对回答的回复、对回复的回复,作为第二级,称为评论

 

 提出问题(根据多个原型图得出ER图推断出数据库字典)

原型图

 根据上图得出字段:课程id,章节id, 问题标题,问题描述,是否匿名

隐藏字段:提出用户id

 

 最新回答的id(这个时候需要另一个表,根据提出的问题进行回答的表),分页查询最新回答比较慢,效率低,所以在表中添加会好一些

 

 回答数量,用户端是否显示,管理员是否查看

得出ER图

 

回答、评论

回答和评论的属性基本一致,差别就是:

  • 回答的对象是问题

  • 评论的对象是其它回答或评论

 

字段:回答内容 是否匿名,对哪一个问题进行回答的id(对哪一个评论进行回答的id), 点赞数

隐藏的字段:回答人的id

 

 代码开发

新增问题

 修改问题

 分页查询问题

@Override
    public PageDTO<QuestionVO> queryQuestionPage(QuestionPageQuery query) {
        // 1.参数校验,课程id 不能为空
        if (query.getCourseId() == null) {
            throw new BadRequestException("课程id不能为空");
        }
        // 2.获取当前登录用户id
        Long userid = UserContext.getUser();
        // 3.  分页查询提出的问题  课程号 olnyMine为true才会加userid 没有隐藏
        Page<InteractionQuestion> page = this.lambdaQuery()
                .select(InteractionQuestion.class, tableFieldInfo -> !tableFieldInfo.getProperty().equals("description"))
                .eq(InteractionQuestion::getCourseId, query.getCourseId())
                .eq(query.getOnlyMine(), InteractionQuestion::getUserId, userid)
                .eq(query.getSectionId() != null, InteractionQuestion::getSectionId, query.getSectionId())
                .eq(InteractionQuestion::getHidden, false)
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        List<InteractionQuestion> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        // 每一个提出的问题的 最新回答id集合
        Set<Long> latestAnswerIdsSet = records.stream().filter(c -> c.getLatestAnswerId() != null)
                .map(InteractionQuestion::getLatestAnswerId).collect(Collectors.toSet());
        // 5.1 获取没有匿名的用户信息,包括回复的不宁名用户
        Set<Long> userSet = records.stream().filter(c -> c.getAnonymity() != true).map(InteractionQuestion::getUserId).collect(Collectors.toSet());
        // 4.根据id查询提问者id和最近一次回答的信息
        Map<Long, InteractionReply> replyMap = new HashMap<>();
        if (CollUtils.isNotEmpty(latestAnswerIdsSet)) {     // 可能每个问题都没有最新的回答
            List<InteractionReply> replies = replyService.listByIds(latestAnswerIdsSet);
            for (InteractionReply reply : replies) {
                if (!reply.getAnonymity()) {
                    userSet.add(reply.getUserId());
                }
                replyMap.put(reply.getId(), reply);
            }
//            replyMap = replies.stream().collect(Collectors.toMap(InteractionReply::getId, c -> c));
        }
        // 5. 远程调用用户服务 获取用户信息 批量
        List<UserDTO> userDTOS = userClient.queryUserByIds(userSet);
        Map<Long, UserDTO> userDTOMap = userDTOS.stream().collect(Collectors.toMap(UserDTO::getId, c -> c));
        // 6.封装vo返回
        ArrayList<QuestionVO> voList = new ArrayList<>();
        for (InteractionQuestion record : records) {
            QuestionVO vo = BeanUtils.copyBean(record, QuestionVO.class);
            if (!vo.getAnonymity()) {
                UserDTO userDTO = userDTOMap.get(vo.getUserId());
                if (userDTO != null) {
                    vo.setUserName(userDTO.getName());
                    vo.setUserIcon(userDTO.getIcon());
                }
            }

            InteractionReply interactionReply = replyMap.get(record.getLatestAnswerId());
            if (interactionReply != null) {
                if (!interactionReply.getAnonymity()) {// 不是匿名设置名称
                    UserDTO userDTO1 = userDTOMap.get(interactionReply.getUserId());
                    if (userDTO1 != null) {
                        vo.setLatestReplyUser(userDTO1.getName());
                    }
                }
                vo.setLatestReplyContent(interactionReply.getContent());
            }

            voList.add(vo);
        }
        return PageDTO.of(page, voList);
    }

 管理端分页查询问题

在管理端后台存在问答管理列表页,与用户端类似都是分页查询,但是请求参数和返回值有较大差别:

课程分类数据查询

 课程分类字段

course字段里面的分类

分类字段里面有parent_id,第一级菜单父id为0

本地缓存由于无需网络查询,速度非常快。不过由于上述缺点,本地缓存往往适用于数据量小更新不频繁的数据。而课程分类恰好符合。

Caffeine

适用于数据量少,长时间不会发生变化的

对于常见缓存类型而言,可以分为本地缓存以及分布式缓存两种,Caffeine就是一种优秀的本地缓存,而Redis可以用来做分布式缓存

caffeine是一个命中率搞得本地缓存

 

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <!--https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeinez 找最新版-->
    <version>3.0.5</version>
</dependency>

 

@Test
void testBasicOps() {
    // 构建cache对象
    Cache<String, String> cache = Caffeine.newBuilder().build();

    // 存数据
    cache.put("gf", "迪丽热巴");

    // 取数据
    String gf = cache.getIfPresent("gf");
    System.out.println("gf = " + gf);

    // 取数据,包含两个参数:
    // 参数一:缓存的key
    // 参数二:Lambda表达式,表达式参数就是缓存的key,方法体是查询数据库的逻辑
    // 优先根据key查询JVM缓存,如果未命中,则执行参数二的Lambda表达式
    String defaultGF = cache.get("defaultGF", key -> {
        // 根据key去数据库查询数据
        return "柳岩";
    });
    System.out.println("defaultGF = " + defaultGF);
}

 分类的缓存:

    private final Cache<String, Map<Long, CategoryBasicDTO>> categoryCaches;

    private final CategoryClient categoryClient;

    public Map<Long, CategoryBasicDTO> getCategoryMap() {
        return categoryCaches.get("CATEGORY", key -> {
 // 这里面必须要有return语句
            // 1.从CategoryClient查询
            List<CategoryBasicDTO> list = categoryClient.getAllOfOneLevel();
            if (list == null || list.isEmpty()) {
                return CollUtils.emptyMap();
            }
            // 2.转换数据
            return list.stream().collect(Collectors.toMap(CategoryBasicDTO::getId, Function.identity()));
        });
    }
    public String getCategoryNames(List<Long> ids) {
        if (ids == null || ids.size() == 0) {
            return "";
        }
        // 1.读取分类缓存
        Map<Long, CategoryBasicDTO> map = getCategoryMap();
        // 2.根据id查询分类名称并组装
        StringBuilder sb = new StringBuilder();
        for (Long id : ids) {
            sb.append(map.get(id).getName()).append("/");
        }
        // 3.返回结果
        return sb.deleteCharAt(sb.length() - 1).toString();
    }

其中CategoryCacheConfig是Caffeine的缓存配置:

public class CategoryCacheConfig {
    /**
     * 课程分类的caffeine缓存
     */
    @Bean
    public Cache<String, Map<Long, CategoryBasicDTO>> categoryCaches(){
        return Caffeine.newBuilder()
                .initialCapacity(1) // 容量限制
                .maximumSize(10_000) // 最大内存限制
                .expireAfterWrite(Duration.ofMinutes(30)) // 有效期
                .build();
    }
    /**
     * 课程分类的缓存工具类
     */
    @Bean
    public CategoryCache categoryCache(
            Cache<String, Map<Long, CategoryBasicDTO>> categoryCaches, CategoryClient categoryClient){
        return new CategoryCache(categoryCaches, categoryClient);
    }
}

新增回答或评论

 基本属性:内容,是否匿名、对哪一个问题做的回答,当前用户id

如果是针对某个回答发表的评论,则有新的关联属性:

  • 回答id:评论是在哪个回答下面的

  • 目标评论id:当前评论是针对哪一条评论的

  • 目标用户id:当前评论是针对哪一个用户的

 评论或回答表结构

如果是回答:answer_id 为空,question_id就是对哪个问题做的回答

如果评论:回复的目标回复id就是另一条数据的主键id

 

 

 

分页查询回答

 

管理员端查看回答