目录
一、需求分析
1.1 功能分析
采用SpringDataMongoDB框架实现评论微服务的持久层。
实现功能:
(1)基本CRUD API
(2)评论点赞
1.2 表结构分析
| 字段名称 | 字段含义 | 字段类型 | 备注 |
|---|---|---|---|
| _id | ID | 文本 |
|
| orderid | 订单id | 文本 | |
| spuid | 商品id | 文本 |
|
| userid | 用户id | 文本 |
|
| nickname | 评论人昵称 | 文本 |
|
| parentid | 评论id | 文本 | 父评论id,顶级评论为0 |
| isparent | 是否是顶级评论 | 布尔型 |
|
| publishtime | 评论日期 | 日期 |
|
| visits | 浏览量 | 整型 |
|
| thumbup | 点赞数 | 整型 |
|
| images | 评论中的图片 | 数组 |
|
| content | 评论内容 | 文本 |
|
| comment | 回复数 | 整型 |
|
| iscomment | 是否允许回复 | 布尔型 |
|
| type | 评价类型 | 整型 | 0:非常差 1:差 2:一般 3:好 4:非常好 -1:表示评论的回复 |
二、服务搭建
2.1 服务搭建
2.1.1 创建module

子模块interface:

子模块service:

目录结构:

2.1.2 添加依赖
在leyou-comments-service中添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!--<parent>-->
<!--<artifactId>leyou-comments</artifactId>-->
<!--<groupId>com.leyou.parent</groupId>-->
<!--<version>1.0.0-SNAPSHOT</version>-->
<!--</parent>-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.leyou.comments</groupId>
<artifactId>leyou-comments-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
<!--配置管理依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.leyou.authentication</groupId>
<artifactId>leyou-authentication-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.leyou.authentication</groupId>
<artifactId>leyou-authentication-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.leyou.common</groupId>
<artifactId>leyou-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.leyou.review</groupId>
<artifactId>leyou-review-interface</artifactId>
<version>1.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.leyou.comments</groupId>
<artifactId>leyou-comments-interface</artifactId>
<version>1.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
在这里面需要说明一下,spring boot在整合mongodb的时候,其配置文件中的parent属性必须是spring boot的依赖:

否则在继承MongoRepository时无法自动加载。这样就破坏了整个工程结构,这个坑怎么填?请大佬赐教!!!!!!!!!
2.1.3 编写配置
server:
port: 8092
tomcat:
max-connections: 5000
spring:
application:
name: comments-service
data:
mongodb:
host: 192.168.19.121
database: spitdb
datasource:
url: jdbc:mysql://127.0.0.1:3306/leyou?characterEncoding=UTF-8&useSSL=false
username: root
password: 123456
hikari:
maximum-pool-size: 20
minimum-idle: 10
rabbitmq:
virtual-host: /leyou
username: /leyou
password: leyou
host: 192.168.19.121
redis:
host: 192.168.19.121
jackson:
default-property-inclusion: non_null # 配置json处理时忽略空值
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
registry-fetch-interval-seconds: 5
instance:
instance-id: ${spring.application.name}:${server.port}
prefer-ip-address: true #当你获取host时,返回的不是主机名,而是ip
ip-address: 127.0.0.1
lease-expiration-duration-in-seconds: 10 #10秒不发送九过期
lease-renewal-interval-in-seconds: 5 #每隔5秒发一次心跳
ribbon:
ConnectTimeout: 4000 # 连接超时时间(ms)
ReadTimeout: 2000 # 通信超时时间(ms)
OkToRetryOnAllOperations: true # 是否对所有操作重试
MaxAutoRetriesNextServer: 1 # 同一服务不同实例的重试次数
MaxAutoRetries: 1 # 同一实例的重试次数
leyou:
worker:
workerId: 2
dataCenterId: 2
jwt:
cookieName: LY_TOKEN
pubKeyPath: G:\\tmp\\rsa\\rsa.pub # 公钥地址
2.1.4 启动类
package com.leyou.comments;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @Author: 98050
* @Time: 2018-11-29 15:41
* @Feature:
*/
@SpringBootApplication
@EnableDiscoveryClient
public class LyCommentsApplication {
public static void main(String[] args) {
SpringApplication.run(LyCommentsApplication.class,args);
}
}
2.2 准备工作
2.2.1 创建实体类
package com.leyou.comments.pojo;
import javax.persistence.Id;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* @Author: 98050
* @Time: 2018-11-26 14:45
* @Feature:
*/
public class Review implements Serializable {
@Id
private String _id;
/**
* 订单id
*/
private String orderid;
/**
* 商品id
*/
private String spuid;
/**
* 评论内容
*/
private String content;
/**
* 评论时间
*/
private Date publishtime;
/**
* 评论用户id
*/
private String userid;
/**
* 评论用户昵称
*/
private String nickname;
/**
* 评论的浏览量
*/
private Integer visits;
/**
* 评论的点赞数
*/
private Integer thumbup;
/**
* 评论中的图片
*/
private List<String> images;
/**
* 评论的回复数
*/
private Integer comment;
/**
* 该评论是否可以被回复
*/
private Boolean iscomment;
/**
* 该评论的上一级id
*/
private String parentid;
/**
* 是否是顶级评论
*/
private Boolean isparent;
/**
* 评论的类型
*/
private Integer type;
/**
* json转换需要
*/
public Review() {
}
public Review(String orderid,String spuid, String content, String userid, String nickname, List<String> images, Boolean iscomment, String parentid, Boolean isparent, Integer type) {
this.orderid = orderid;
this.spuid = spuid;
this.content = content;
this.userid = userid;
this.nickname = nickname;
this.images = images;
this.iscomment = iscomment;
this.parentid = parentid;
this.isparent = isparent;
this.type = type;
}
}
2.2.2 创建数据访问接口
package com.leyou.comments.dao;
import com.leyou.comments.pojo.Review;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @Author: 98050
* @Time: 2018-11-26 20:51
* @Feature:
*/
public interface CommentDao extends MongoRepository<Review,String> {
}
2.2.3 添加路由
在网关中添加路由

2.3 功能实现
2.3.1 分页查询评论
Controller
请求方式:GET
请求路径:/list
请求参数:商品id、当前页码、页面大小,封装一个请求参数对象:
返回结果:返回评论的分页信息
请求对象,每页大小20,固定参数两个:spuId、page。其中构造函数是用来接收前端传进来的数据,其余都在后端生成。
package com.leyou.comments.bo;
/**
* @Author: 98050
* @Time: 2018-11-26 21:40
* @Feature:
*/
public class CommentRequestParam {
/**
* 商品id
*/
private Long spuId;
/**
* 当前页码
*/
private Integer page;
/**
* 每页大小,不从页面接收,而是固定大小
*/
private static final Integer DEFAULT_SIZE = 20;
/**
* 默认页
*/
private static final Integer DEFAULT_PAGE = 1;
public Long getSpuId() {
return spuId;
}
public void setSpuId(Long spuId) {
this.spuId = spuId;
}
public Integer getPage() {
if (page == null){
return DEFAULT_PAGE;
}
/**
* 获取页码时做一些校验,不能小于1
*/
return Math.max(DEFAULT_PAGE,page);
}
public void setPage(Integer page) {
this.page = page;
}
public Integer getDefaultSize() {
return DEFAULT_SIZE;
}
}
package com.leyou.comments.controller;
import com.leyou.auth.entity.UserInfo;
import com.leyou.comments.bo.CommentRequestParam;
import com.leyou.comments.interceptor.LoginInterceptor;
import com.leyou.comments.pojo.Review;
import com.leyou.comments.service.CommentService;
import com.leyou.common.pojo.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* @Author: 98050
* @Time: 2018-11-26 21:30
* @Feature:
*/
@RequestMapping
@RestController
public class CommentController {
@Autowired
private CommentService commentService;
/**
* 分页查询某一商品下的所有顶级评论
* @param requestParam
* @return
*/
@GetMapping("list")
public ResponseEntity findReviewBySpuId(@RequestBody CommentRequestParam requestParam){
Page<Review> result = commentService.findReviewBySpuId(requestParam);
if (result == null){
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
PageResult pageResult = new PageResult();
pageResult.setTotal(result.getTotalElements());
pageResult.setItems(result.getContent());
pageResult.setTotalPage((long)result.getTotalPages());
return ResponseEntity.ok(pageResult);
}
}
Service
- 接口
package com.leyou.comments.service;
import com.leyou.comments.bo.CommentRequestParam;
import com.leyou.comments.pojo.Review;
import org.springframework.data.domain.Page;
/**
* @Author: 98050
* @Time: 2018-11-26 15:40
* @Feature:
*/
public interface CommentService {
/**
* 查询某一商品下的所有顶级评论
* @param commentRequestParam
* @return
*/
Page<Review> findReviewBySpuId(CommentRequestParam commentRequestParam);
}
- 实现
package com.leyou.comments.service.impl;
import com.leyou.comments.bo.CommentRequestParam;
import com.leyou.comments.dao.CommentDao;
import com.leyou.comments.pojo.Review;
import com.leyou.comments.service.CommentService;
import com.leyou.utils.IdWorker;
import com.mongodb.client.result.UpdateResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
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;
/**
* @Author: 98050
* @Time: 2018-11-26 15:41
* @Feature:
*/
@Service
public class CommentServiceImpl implements CommentService {
@Autowired
private CommentDao commentDao;
@Autowired
private MongoTemplate mongoTemplate;
/**
* 查询某一商品下的所有顶级评论
* @param commentRequestParam
* @return
*/
@Override
public Page<Review> findReviewBySpuId(CommentRequestParam commentRequestParam) {
PageRequest pageRequest = PageRequest.of(commentRequestParam.getPage()-1, commentRequestParam.getDefaultSize());
return this.commentDao.findReviewBySpuid(commentRequestParam.getSpuId()+"",pageRequest);
}
}
Dao
在CommentDao中添加分页查询的方法:
public interface CommentDao extends MongoRepository<Review,String> {
/**
* 分页查询
* @param spuId
* @param pageable
* @return
*/
Page<Review> findReviewBySpuid(String spuId, Pageable pageable);
}
2.3.2 发布评论
Controller
请求方式:POST
请求路径:/
请求参数:review对象
返回结果:201
每个用户对每一个订单只能发表一次顶级评论,可以追评论。
/**
* 增加评论
* @param review
* @return
*/
@PostMapping
public ResponseEntity<Void> addReview(@RequestBody Review review){
boolean result = this.commentService.add(review);
if (!result){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
return ResponseEntity.status(HttpStatus.CREATED).build();
}
Service
添加评论有两种情况,第一种是为商品添加评论,第二种是评论的回复。在这里就体现出表结构设计的技巧,同理参考商品分类管理中的表结构设计。
这里面评论的id需要自己指定,所以要使用IdWorker,参照以前的模块,cv一下
/**
* 新增
* 注意:一个用户只能发表一个顶级评论,可以追评(在一个订单中)
* @param review
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean add(Long orderId,Review review) {
//1.检查用户是否在该商品下发表过顶级评论过
if (review.getIsparent()) {
Query query2 = new Query();
query2.addCriteria(Criteria.where("userid").is(review.getUserid()));
query2.addCriteria(Criteria.where("orderid").is(review.getOrderid()));
query2.addCriteria(Criteria.where("spuid").is(review.getSpuid()));
List<Review> old = this.mongoTemplate.find(query2, Review.class);
if (old.size() > 0 && old.get(0).getIsparent()) {
return false;
}
}
//2.修改订单状态,6代表评价状态
boolean result = this.orderClient.updateOrderStatus(orderId, 6).getBody();
if (!result){
return false;
}
//3.添加评论
/**
* 设置主键
*/
review.set_id(idWorker.nextId() + "");
review.setPublishtime(new Date());
review.setComment(0);
review.setThumbup(0);
review.setVisits(0);
if (review.getParentid() != null && !"".equals(review.getParentid())){
//如果存在上级id,则上级评论数加1,将上级评论的isParent设置为true,浏览量加一
Query query = new Query();
query.addCriteria(Criteria.where("_id").is(review.getParentid()));
Update update = new Update();
update.inc("comment",1);
update.set("isparent",true);
update.inc("visits",1);
this.mongoTemplate.updateFirst(query,update,"review");
}
commentDao.save(review);
return true;
}
2.3.3 删除评论
Controller
请求方式:DELETE
请求路径:/commentId/{id}
请求参数:评论id
返回结果:200
/**
* 根据评论id删除评论
* @param id
* @return
*/
@DeleteMapping("/commentId/{id}")
public ResponseEntity<Void> deleteReview(@PathVariable("id") String id){
this.commentService.deleteById(id);
return ResponseEntity.status(HttpStatus.OK).build();
}
Service
/**
* 删除
*
* @param id
*/
@Override
public void deleteById(String id) {
commentDao.deleteById(id);
}
2.3.4 修改评论
Controller
请求方式:PUT
请求路径:/comment
请求参数:review对象
返回结果:200
/**
* 修改评论
* @param review
* @return
*/
@PutMapping("/comment")
public ResponseEntity<Void> updateReview(@RequestBody Review review){
this.commentService.update(review);
return ResponseEntity.status(HttpStatus.OK).build();
}
Service
/**
* 修改
*
* @param review
*/
@Override
public void update(Review review) {
commentDao.save(review);
}
2.3.5 根据id查询评论
Controller
请求方式:GET
请求路径:/commentId/{id}
请求参数:评论id
返回结果:review对象
/**
* 根据评论id查询评论
* @param id
* @return
*/
@GetMapping("/commentId/{id}")
public ResponseEntity<Review> findReviewById(@PathVariable("id") String id){
Review review = this.commentService.findOne(id);
if (review == null){
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
return ResponseEntity.ok(review);
}
Service
@Override
public Review findOne(String id) {
//判断空
Optional<Review> optional = commentDao.findById(id);
return optional.orElse(null);
}
2.3.6 评论点赞
Controller
请求方式:PUT
请求路径:thumb/{id}
请求参数:评论id
返回结果:true or false
点赞就是让字段thumbup加一,使用MongoTemplate类来实现对某列的操作,不用全部都查询出来,那样效率不高。使用自增函数inc实现thumbup加一。
使用redis控制不能重复点赞
private final String THUMBUP_PREFIX = "thumbup";
/**
* 评论点赞
* @param id
* @return
*/
@PutMapping("thumb/{id}")
public ResponseEntity<Boolean> updateThumbup(@PathVariable String id){
//1.首先判断当前用户是否点过赞
UserInfo userInfo = LoginInterceptor.getLoginUser();
String userId = userInfo.getId()+"";
if (redisTemplate.opsForValue().get(THUMBUP_PREFIX + userId + "_" + id) != null){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
boolean result = this.commentService.updateThumbup(id);
if (!result){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
redisTemplate.opsForValue().set(THUMBUP_PREFIX + userId + "_" + id,"1");
return ResponseEntity.ok(result);
}
Service
/**
* 评论点赞
* @param id
*/
@Override
public boolean updateThumbup(String id) {
Query query = new Query();
query.addCriteria(Criteria.where("_id").is(id));
Update update = new Update();
update.inc("thumbup",1);
UpdateResult result = this.mongoTemplate.updateFirst(query,update,"review");
return result.isModifiedCountAvailable();
}
2.3.7 评论访问量增加
这个功能和评论点赞差不多,但是具体怎么判断用户浏览过? 好吧,我也不知道,这个功能就当测试了。。。。。。。
Controller
请求方式:PUT
请求路径:/visit/{id}
请求参数:评论id
返回结果:200
/**
* 根据评论id访问量加1
* @param id
* @return
*/
@PutMapping("visit/{id}")
public ResponseEntity<Void> updateReviewVisit(@PathVariable("id") String id){
boolean result = this.commentService.updateVisits(id);
if (!result){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
return ResponseEntity.status(HttpStatus.OK).build();
}
Service
/**
* 访问量加一
* @param id
*/
@Override
public boolean updateVisits(String id) {
Query query = new Query();
query.addCriteria(Criteria.where("_id").is(id));
Update update = new Update();
update.inc("visits",1);
UpdateResult result = this.mongoTemplate.updateFirst(query,update,"review");
return result.isModifiedCountAvailable();
}
2.4 测试
2.4.1 增加100条评论数据
创建测试类:
import com.leyou.comments.LyCommentsApplication;
import com.leyou.comments.dao.CommentDao;
import com.leyou.comments.pojo.Review;
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.test.context.junit4.SpringRunner;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: 98050
* @Time: 2018-12-09 20:37
* @Feature:
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LyCommentsApplication.class)
public class CommentsTest {
@Autowired
private CommentDao commentDao;
/**
* 为spuId为2的商品添加100条评顶级论数据
*/
@Test
public void LoadData(){
for (int i = 0; i < 100; i++) {
String spuId = "2";
String content = "手机不错"+i;
String userId = (35 + i) + "";
String nickname = "username"+i;
List<String> images = new ArrayList<>();
boolean iscomment = i % 2 == 0;
String parentId = 0 + "";
boolean isparent = true;
int type = i % 5;
Review review = new Review(spuId, content, userId, nickname, images, iscomment, parentId,isparent,type);
commentDao.insert(review);
}
}
}
同一用户再次发布评论:

2.4.2 分页查询

结果:

2.4.2 根据id查询评论

2.4.3 根据id修改评论
修改id为1071767094657556480的评论

重新查询:

2.4.4 根据id删除评论
删除id为1071767094657556480的评论:

重新查询该评论:

2.4.5 评论点赞

要添加cookie。
查询id为 1071767095416725504的评论:

2.4.6 评论的评论
这个没什么特别,在前端进行数据封装就可以了:

查看id为1071767095433502720的评论:

三、补充
在添加评论的时候,还有一个需要做的工作就是修改订单的状态,为了测试方便,上面就没有给出,熟悉完mongodb后,再把这个坑填上。
3.1 Feign
在leyou-order微服务的接口中新增修改订单状态的方法:

在评论微服务中添加FeignClient:
package com.leyou.comments.client;
import com.leyou.order.api.OrderApi;
import org.springframework.cloud.openfeign.FeignClient;
/**
* @Author: 98050
* @Time: 2018-11-12 15:19
* @Feature: 订单接口
*/
@FeignClient(value = "order-service")
public interface OrderClient extends OrderApi {
}
3.2 添加相关依赖
<dependency>
<groupId>com.leyou.order</groupId>
<artifactId>leyou-order-interface</artifactId>
<version>1.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-core</artifactId>
<version>2.0.2.RELEASE</version>
<scope>compile</scope>
</dependency>
3.3 修改


注意:review对象中的orderid是后期加上去的,当时考虑不周,但是整体没有多大影响,在测试时添加100条数据的时候加上这个字段就可以了。
1797

被折叠的 条评论
为什么被折叠?



