文章目录
- day09-MongoDB
- 一、回顾
- 1.1. 行为实战核心要点说明
- 二、评论系统
- 2.1 MongoDB
- 2.1.1 MongoDB简介
- ①简介
- ②体系结构与术语
- 2.1.2 安装与连接
- 2.1.3 Springboot整合MongoDB
- ①引入依赖
- ②添加服务端配置
- ③准备实体类
- ④测试-新增
- ⑤测试-查询
- ⑥测试-更新
- 测试-删除
- 2.2 app端评论-发表评论
- 2.2.1 需求分析
- ①需求分析
- ②对应数据存储结果-集合
- 2.2.2 接口定义
- ①实现步骤
- ②用户远程接口-查询用户-接口定义
- ③长整型数据精度丢失问题
- 2.3 app端评论-点赞评论
- 2.3.1 需求分析
- 2.3.2 思路分析
- 2.3.3 接口定义
- 2.3.4 接口实现
- 2.4 app端评论-评论列表
- 2.4.1 需求分析
- 2.4.2 接口定义
- 2.4.3 需求分析
- 2.4.4 接口实现
- 2.5 app端评论回复-发表回复、点赞回复、回复列表
- 2.6 热点评论
- 2.6.1 需求分析
- 实现思路-计算热点评论
day09-MongoDB
一、回顾
1.1. 行为实战核心要点说明
- 技术方案
Redis+MySQL
Redis:负责对外提供读与写,因为行为对读写的性能很高,不能直接去操作MySQL
MySQL:基于MQ实现异步数据同步,(不使用线程池的原因是因为Threadpool是基于本地内存的,不能把大量的数据放在线程池里,数据量过多),Redis操作完数据,把更改的数据同步到MySQL的行为表中 - 各行为接口
- user服务
- 关注与取消关注接口
- Redis设计:
- ZSET——关注列表
- key:behavior:follow:list:当前用户ID
- score:关注的时间
- value:作者的用户ID
- ZSET——粉丝列表
- key:behavior:follow:list:当前用户ID
- score:被关注的时间
- value:粉丝的用户ID
- ZSET——关注列表
- article服务
- 收藏与取消收藏接口
- Redis设计-HASH类型
- key:behavior:coll:当前用户ID
- value:
- key:文章ID
- value:文章详情
- 文章行为关系数据查询接口
- behavior服务
- 点赞与取消点赞接口
- Redis设计-HASH类型
- key:behavior:likes:文章ID
- value:
- key:用户ID
- value:操作的详情
- 阅读接口
- 不喜欢与取消不喜欢接口
- user服务
二、评论系统
2.1 MongoDB
2.1.1 MongoDB简介
①简介
端口号:27017
默认不支持事务
MongoDB是一个开源、高性能、无模式的文档型数据库
是NoSQL数据库产品中的一种,是最像关系型数据库(MySQL)的非关系性数据库
- 数据存储量较大,甚至巨大
- 对数据读写的响应速度要求非常高
- 某些数据安全性要求不高,可以接收一定范围内的误差
- 数据具有结构型(BSON)
应用场景:
评论、弹幕、观众列表中的一条数据
②体系结构与术语


2.1.2 安装与连接
启动MongoDB
docker start mongo-service
mongoDB连接工具——studio3t安装
studio3t是MongoDB优秀的客户端工具。官方地址在https://studio3t.com/
2.1.3 Springboot整合MongoDB
①引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
②添加服务端配置
server:port: 9998
spring:data:mongodb:host: 192.168.200.130port: 27017database: leadnews-comment
③准备实体类
/*** APP评论信息*/
@Data
// 使用这个注解来映射实体类和集合的关系
@Document("ap_comment")
public class ApComment {/*** id*/private String id;/*** 用户ID*/private Integer userId;/*** 用户昵称*/private String userName;/*** 文章id或动态id*/private Long objectId;/*** 频道ID*/private Integer channelId;/*** 评论内容类型* 0 文章* 1 动态*/private Integer type;/*** 评论内容*/private String content;/*** 作者头像*/private String image;/*** 点赞数*/private Integer likes;/*** 回复数*/private Integer reply;/*** 文章标记* 0 普通评论* 1 热点评论* 2 推荐评论* 3 置顶评论* 4 精品评论* 5 大V 评论*/private Integer flag;/*** 经度*/private BigDecimal longitude;/*** 维度*/private BigDecimal latitude;/*** 地理位置*/private String address;/*** 评论排列序号*/private Integer ord;/*** 创建时间*/private Date createdTime;/*** 更新时间*/private Date updatedTime;}
④测试-新增
package com.itheima.mongo.test;import com.itheima.mongo.pojo.ApComment;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.test.context.junit4.SpringRunner;import java.util.Date;/*** @author tp* @since 2024/2/19 13:24*/@SpringBootTest
@RunWith(SpringRunner.class)
public class MongoTest {@Autowiredprivate MongoTemplate mongoTemplate;/*** 测试新文档*/@Testpublic void testAddDocument() {for (int i = 1; i <= 10; i++) {ApComment apComment = new ApComment();apComment.setId(String.valueOf(i));// 评论idapComment.setUserId(i);// 评论用户idapComment.setUserName("测试用户"+i);// 评论用户名apComment.setType(0);// 内容类型,0表示文章类型apComment.setObjectId(Long.valueOf(10+i));//文章IDif (i % 2 == 0) {apComment.setFlag(1);// 热点评论} else {apComment.setFlag(0);// 普通评论}apComment.setContent("测试内容"+i);// 评论内容apComment.setLikes(100+i);// 点赞数apComment.setReply(0);// 回复数apComment.setCreatedTime(new Date());// 创建时间apComment.setUpdatedTime(new Date());// 创建时间// mongoTemplate.insert(apComment);// 仅表示新增文档mongoTemplate.save(apComment);// 表示新增文档或跟新文档}}
}
在测试的时候发现,MongoDB可以不创建数据库,可以不创建表,因为在运行的时候,会先读取配置的里database: leadnews-comment的值,发现数据库里没有这个数据库创建,同样的,读取@Document("ap_comment")里的值,发现没有这个集合就创建了这个集合。
⑤测试-查询
/*** 测试查询*/@Testpublic void testQueryDocument() {System.out.println(mongoTemplate.findById("7", ApComment.class));Query query = Query.query(Criteria.where("userName").is("测试用户7"));System.out.println("----------------------------------------------");// 查询一条数据ApComment one = mongoTemplate.findOne(query, ApComment.class);System.out.println(one);System.out.println("-----------------------------------------------");// 查询的多条List<ApComment> all = mongoTemplate.findAll(ApComment.class);all.forEach(x-> System.out.println(x));System.out.println("-----------------------------------------------");// 查询列表数据:单条件条件查询List<ApComment> flag = mongoTemplate.find(Query.query(Criteria.where("flag").is(1)), ApComment.class);flag.forEach(x-> System.out.println(x));System.out.println("-----------------------------------------------");// 查询列表数据:多条件条件查询List<ApComment> flag1 = mongoTemplate.find(Query.query(Criteria.where("flag").is(1).and("likes").gt(102)), ApComment.class);flag1.forEach(x-> System.out.println(x));System.out.println("-----------------------------------------------");//查询列表数据:根据域进行排序和限制查询条数List<ApComment> apComments = mongoTemplate.find(Query.query(Criteria.where("flag").is(1).and("likes").gt(102)).with(Sort.by(Sort.Direction.DESC, "likes")).limit(3), ApComment.class);apComments.forEach(x-> System.out.println(x));}
⑥测试-更新
前两种常用,后一种有数字增减,可用
/*** 测试更新文档*/@Testpublic void testUpdateDocument() {ApComment byId = mongoTemplate.findById("7", ApComment.class);byId.setContent("测试内容007");// 1. savemongoTemplate.save(byId);// 2. updateFirst(非线程安全的方法)mongoTemplate.updateFirst(Query.query(Criteria.where("userId").is(7)), Update.update("content", "修改的测试内容007"), ApComment.class);// 3. findAndModify(线程安全的方法)mongoTemplate.findAndModify(Query.query(Criteria.where("userId").is(7)), Update.update("content", "修改的测试内容007"), ApComment.class);}
测试-删除
/*** 测试删除文档*/@Testpublic void testDeleteDocument() {// 1. 查询并删除// ApComment apComment = mongoTemplate.findById("7", ApComment.class);// mongoTemplate.remove(apComment);// 2. 根据条件删除mongoTemplate.remove(Query.query(Criteria.where("flag").is(1)), ApComment.class);}
2.2 app端评论-发表评论
2.2.1 需求分析
①需求分析

- 文章详情页下方可以查看评论信息,按照点赞数量倒序排列,展示评论内容、评论的作者、点赞数、回复数、时间,默认查看10条评论,如果向查看更多,可以点击加载更多进行分页
- 可以针对当前文章发布评论
- 可以针对于某一条评论进行点赞操作
②对应数据存储结果-集合
APP评论信息

APP评论信息点赞

这两个集合是一对多的关系,表示一条评论可以让多个app用户点赞
2.2.2 接口定义

①实现步骤
1、搭建评论微服务
(1)创建项目heima-leadnews-comment

(2)bootstrap.yml
其中自动配置项去除了关于数据源的配置,因为这个项目不需要查询数据库,查询的mongodb
(3)nacos中添加comment的配置
spring:data:mongodb:host: 192.168.200.130port: 27017database: leadnews-comment
(4)启动类
@SpringBootApplication
@EnableDiscoveryClient
public class CommentApplication {public static void main(String[] args) {SpringApplication.run(CommentApplication.class,args);}
}
(5)添加WebMvcConfig
package com.heima.comment.config;import com.heima.common.interceptor.TokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new TokenInterceptor()).addPathPatterns("/**")//放行swagger和knife4j.excludePathPatterns( "/v2/api-docs","/doc.html","/swagger-resources/configuration/ui","/swagger-resources","/swagger-resources/configuration/security","/swagger-ui.html","/webjars/**","/actuator/**");}
}
(6)接口定义
controller
@RestController
@RequestMapping("/api/v1/comment")
public class ApCommentController {/*** 新增评论* @return*/@PostMapping("/save")public ResponseResult save(@RequestBody CommentSaveDto dto) {return null;}
}
service
public interface ApCommentService{/*** 新增评论* @param dto* @return*/ResponseResult save(CommentSaveDto dto);
}
2、实现思路
判断用户是否存在
判断文章是否存在
判断评论内容是否大于140字
安全过滤
保存评论
serviceImpl
@Service
public class ApCommentServiceImpl implements ApCommentService {@Autowiredprivate IUserClient userClient;@Autowiredprivate IArticleClient articleClient;@Autowiredprivate MongoTemplate mongoTemplate;@Overridepublic ResponseResult save(CommentSaveDto dto) {// 1. 判断参数是否为空if (dto.getArticleId()==null || StringUtils.isBlank(dto.getContent())){return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}// 2. 判断用户是否存在——调用user的feign接口(此feign接口一定要在user服务的webMvcConfig中放行)Integer userId = ThreadLocalUtil.getUserId();ApUser apUser = userClient.findOne(userId);if (apUser == null) {return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"APP用户不存在");}// 3. 判断文章是否存在ApArticle apArticle = articleClient.findOne(dto.getArticleId());if (apArticle == null) {return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"文章不存在");}// 4. 判断评论内容是否大于140字if (dto.getContent().length() > 140) {return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR,"内容长度超过140字");}// 5. 安全过滤(DFA审核和百度云文本审核)// 6. 保存评论到MongoDB中ApComment apComment = new ApComment();apComment.setUserId(userId);apComment.setUserName(apUser.getName());apComment.setImage(apUser.getImage());apComment.setType(0); //评论的内容类型,0表示文章apComment.setObjectId(dto.getArticleId());apComment.setContent(dto.getContent());apComment.setLikes(0);apComment.setReply(0);apComment.setFlag(0);apComment.setCreatedTime(new Date());apComment.setUpdatedTime(new Date());mongoTemplate.save(apComment);return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);}
}
3、配置网关

②用户远程接口-查询用户-接口定义

package com.heima.apis.user;import com.heima.model.user.pojos.ApUser;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;/*** @author tp* @since 2024/2/19 16:17*/@FeignClient("leadnews-user")
public interface IUserClient {/*** 根据id查询用户* @param id* @return*/@GetMapping("/api/v1/user/one/{id}")public ApUser findOne(@PathVariable("id") Integer id);
}
ApUserFeign
@FeignClient("leadnews-user")
public interface IUserClient {/*** 根据id查询用户* @param id* @return*/@GetMapping("/api/v1/user/one/{id}")public ApUser findOne(@PathVariable("id") Integer id);
}
③长整型数据精度丢失问题
前端的问题
一旦遇到服务端响应的数据是长整型的,永远会把后两位,或者后三位变为0,所以服务端根本就不能返回长整型给前端
解决方法:
方案一:将文章的id的由long类型手动改为String类型,可以解决此问题。(需要修改表结构)pass掉
方案二:可以使用jackson进行序列化和反序列化解决

2.3 app端评论-点赞评论
2.3.1 需求分析

- 用户点赞,可以增加点赞数量,点赞后不仅仅要增加点赞数,需要记录当前用户对于当前评论的数据记录
- 用户取消点赞,点赞减一,更新点赞数据

2.3.2 思路分析

2.3.3 接口定义

2.3.4 接口实现
package com.heima.comment.service.impl;import com.heima.apis.article.IArticleClient;
import com.heima.apis.user.IUserClient;
import com.heima.comment.service.ApCommentService;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.comment.dtos.CommentDto;
import com.heima.model.comment.dtos.CommentLikeDto;
import com.heima.model.comment.dtos.CommentSaveDto;
import com.heima.model.comment.pojos.ApComment;
import com.heima.model.comment.pojos.ApCommentLike;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.user.pojos.ApUser;
import com.heima.utils.common.ThreadLocalUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** @author tp* @since 2024/2/19 16:06*/@Service
public class ApCommentServiceImpl implements ApCommentService {@Autowiredprivate IUserClient userClient;@Autowiredprivate IArticleClient articleClient;@Autowiredprivate MongoTemplate mongoTemplate;@Overridepublic ResponseResult save(CommentSaveDto dto) {// 1. 判断参数是否为空if (dto.getArticleId()==null || StringUtils.isBlank(dto.getContent())){return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}// 2. 判断用户是否存在——调用user的feign接口(此feign接口一定要在user服务的webMvcConfig中放行)Integer userId = ThreadLocalUtil.getUserId();ApUser apUser = userClient.findOne(userId);if (apUser == null) {return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"APP用户不存在");}// 3. 判断文章是否存在ApArticle apArticle = articleClient.findOne(dto.getArticleId());if (apArticle == null) {return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"文章不存在");}// 4. 判断评论内容是否大于140字if (dto.getContent().length() > 140) {return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR,"内容长度超过140字");}// 5. 安全过滤(DFA审核和百度云文本审核)// 6. 保存评论到MongoDB中ApComment apComment = new ApComment();apComment.setUserId(userId);apComment.setUserName(apUser.getName());apComment.setImage(apUser.getImage());apComment.setType(0); //评论的内容类型,0表示文章apComment.setObjectId(dto.getArticleId());apComment.setContent(dto.getContent());apComment.setLikes(0);apComment.setReply(0);apComment.setFlag(0);apComment.setCreatedTime(new Date());apComment.setUpdatedTime(new Date());mongoTemplate.save(apComment);return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);}/*** 加载评论* @param dto* @return*/@Overridepublic ResponseResult load(CommentDto dto) {return null;}/*** 点赞评论或者取消点赞* @param dto* @return*/@Overridepublic ResponseResult like(CommentLikeDto dto) {// 1. 判断参数是否为空if (StringUtils.isBlank(dto.getCommentId()) || dto.getOperation() == null) {return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}// 2. 判断评论是否存在ApComment apComment = mongoTemplate.findById(dto.getCommentId(), ApComment.class);if (apComment == null) {return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"评论不存在");}// 3. 查询评论对应的点赞记录// 根据评论id查询点赞记录ApCommentLike apCommentLike = mongoTemplate.findOne(Query.query(Criteria.where("userId").is(ThreadLocalUtil.getUserId()).and("commentId").is(dto.getCommentId())), ApCommentLike.class);// 3.1 点赞记录不存在如何处理?if (apCommentLike == null) {// 就是说还没有点过赞// 3.1.1 判断是否取消点赞if (dto.getOperation() == 1) {return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR,"点赞记录不存在,无法取消点赞");}// 3.1.2 更新评论的点赞数+1ApComment comment = mongoTemplate.findAndModify(Query.query(Criteria.where("id").is(dto.getCommentId())), new Update().inc("likes", 1).set("updateTime", new Date()), ApComment.class);// 3.1.3 保存点赞记录apCommentLike = new ApCommentLike();apCommentLike.setCommentId(dto.getCommentId());apCommentLike.setUserId(ThreadLocalUtil.getUserId());apCommentLike.setOperation(dto.getOperation());apCommentLike.setCreatedTime(new Date());apCommentLike.setUpdatedTime(new Date());mongoTemplate.save(apCommentLike);} else {// 3.2 点赞记录存在如何处理?// 3.2.1 判断是否重复点赞if (dto.getOperation() == 0 && apCommentLike.getOperation() == 0) {return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR,"不能重复点赞");}// 3.2.2 判断是否重复取消点赞if (dto.getOperation() == 1 && apCommentLike.getOperation() == 1) {return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR,"不能重复取消点赞");}// 3.2.3 如果操作的是点赞,则点赞数+1if (dto.getOperation() == 0) {mongoTemplate.findAndModify(Query.query(Criteria.where("id").is(dto.getCommentId())), new Update().inc("likes", 1).set("updateTime", new Date()), ApComment.class);} else {// 3.2.4 如果操作的是取消点赞,则点赞数-1mongoTemplate.findAndModify(Query.query(Criteria.where("id").is(dto.getCommentId())), new Update().inc("likes", -1).set("updateTime", new Date()), ApComment.class);}// 3.2.5 更新点赞记录的操作类型和时间apCommentLike.setOperation(dto.getOperation());apCommentLike.setUpdatedTime(new Date());mongoTemplate.save(apCommentLike);}// 4.查询最新点赞数并返回apComment = mongoTemplate.findById(dto.getCommentId(),ApComment.class);Map result = new HashMap();result.put("likes", apComment.getLikes());return ResponseResult.okResult(result);}
}
2.4 app端评论-评论列表
2.4.1 需求分析
查询评论列表,根据当前文章id进行检索,按照创建时间倒序,分页查询(默认10条数据)

2.4.2 接口定义

2.4.3 需求分析

2.4.4 接口实现
public ResponseResult load(CommentDto dto) {int size = 10; // 默认查询数// 1. 判断参数是否为空if (dto.getArticleId() == null || dto.getMinDate() == null) {return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}// 2. 根据条件查询评论列表(查询条件:type、objectId、createdTime 查询结果:createdTime倒序、限制查询10条)Query query = Query.query(Criteria.where("type").is(0).and("objectId").is(dto.getArticleId()).and("createdTime").lt(dto.getMinDate())).with(Sort.by(Sort.Direction.DESC, "createdTime")).limit(size);List<ApComment> apCommentList = mongoTemplate.find(query, ApComment.class);// 3. 如果当前用户是游客,直接响应评论列表数据Integer userId = ThreadLocalUtil.getUserId();if (userId == 0) {return ResponseResult.okResult(apCommentList);}// 4. 如果当前用户是正常用户,需要标识评论列表中被当前用户点赞过的评论,再响应// 4.1 获取评论列表对应的评论ID列表List<String> commentIdList = apCommentList.stream().map(ApComment::getId).collect(Collectors.toList());// 4.2 查询当前用户针对当前评论列表对应的所有点赞记录(查询条件:userId,operation,commentId)Query queryCommentLike = query = Query.query(Criteria.where("userId").is(userId).and("operation").is(0).and("commentId").in(commentIdList));// 点赞记录列表List<ApCommentLike> apCommentLikes = mongoTemplate.find(queryCommentLike, ApCommentLike.class);// 4.3 在评论列表中找到被点赞过的评论并添加标识表示点赞过List<CommentVo> commentVoList = new ArrayList<>();for (ApComment apComment : apCommentList) {long count = apCommentLikes.stream().filter(x -> x.getCommentId().equals(apComment.getId())).count();CommentVo commentVo = new CommentVo();BeanUtils.copyProperties(apComment,commentVo);if (count > 0) {commentVo.setOperation(0);}commentVoList.add(commentVo);}return ResponseResult.okResult(commentVoList);}
2.5 app端评论回复-发表回复、点赞回复、回复列表
2.6 热点评论
2.6.1 需求分析
- 一个文章最多有5条热点评论
- 热点评论需要按照点赞数倒序排序
- 前5条评论是按照点赞数倒序,其他按照时间倒序查询
实现思路-计算热点评论
