学习目标

  • 能够完成mongodb的环境搭建
  • 能够掌握mongodb的基本使用
  • 能够完成app端评论的查询、发表、点赞等功能
  • 能够完成app端评论回复的查询,发表、点赞功能

Mongodb

Mongodb简介

mysql mongodb Redis

MongoDB是一个开源、高性能、无模式的文档型数据库

应用场景:

  • 支持文本查询
  • 不需要支持事务,不存在复杂的多表查询,不支持join操作
  • 存储数据可持久化
  • 需要TB甚至 PB 级别数据存储
  • 需求变化较快,数据模型无法确认,预计使用快速迭代开发形式
  • 需要至少2000以上的读写QPS【高性能】
  • 能支持快速水平扩展【高扩展】
  • 99.999%高可用【高可用】
  • 适合于数据价值低的数据存储

小结:mongo的特点 1、模式自由 ---- 不需要提前创建表 直接放数据就可以

2、支持高并发 2000以上

3、搭建集群比较方便

4、支持海量数据存储

MongoDB体系结构

MongoDB 的逻辑结构是一种层次结构。主要由:文档(document)、集合(collection)、数据库(database)这三部分组成的。逻辑结构是面向用户的,用户使用 MongoDB 开发应用程序使用的就是逻辑结构。

  1. MongoDB 的文档(document),相当于关系数据库中的一行记录。
  2. 多个文档组成一个集合(collection),相当于关系数据库的表。
  3. 多个集合(collection),逻辑上组织在一起,就是数据库(database)。
  4. 一个 MongoDB 实例支持多个数据库(database)。

文档(document)、集合(collection)、数据库(database)的层次结构如下图:

mongodb实现like mongodb实现评论_数据库

SQL术语/概念

MongoDB术语/概念

解释/说明

database

database

数据库

table

collection

数据库表/集合

row

document

数据记录行/文档

column

field

数据字段/域

index

index

索引

table joins

表连接,MongoDB不支持

primary key

primary key

主键,MongoDB自动将_id字段设置为主键

mongodb实现like mongodb实现评论_mongodb实现like_02

MongoDB数据类型

数据类型

描述

String

字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的。

Integer

整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位。

Boolean

布尔值。用于存储布尔值(真/假)。

Double

双精度浮点值。用于存储浮点值。

Min/Max keys

将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比。

Array

用于将数组或列表或多个值存储为一个键。

Timestamp

时间戳。记录文档修改或添加的具体时间。

Object

用于内嵌文档。

Null

用于创建空值。

Symbol

符号。该数据类型基本上等同于字符串类型,但不同的是,它一般用于采用特殊符号类型的语言。

Date

日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息。

Object ID

对象 ID。用于创建文档的 ID。

Binary Data

二进制数据。用于存储二进制数据。

Code

代码类型。用于在文档中存储 JavaScript 代码。

Regular expression

正则表达式类型。用于存储正则表达式。

ObjectId

ObjectId 类似唯一主键,可以很快的去生成和排序,包含 12 bytes,含义是:

  • 前 4 个字节表示创建 unix 时间戳,格林尼治时间 UTC 时间,比北京时间晚了 8 个小时
  • 接下来的 3 个字节是机器标识码
  • 紧接的两个字节由进程 id 组成 PID
  • 最后三个字节是随机数

mongodb实现like mongodb实现评论_spring_03

MongoDB 中存储的文档必须有一个 _id 键。这个键的值可以是任何类型的,默认是个 ObjectId 对象

MongoDB安装

cd /opt/hmtt/nosql
./close.sh
./start.sh

mongodb实现like mongodb实现评论_mongodb实现like_04

mongo客户端工具安装

mongodb实现like mongodb实现评论_json_05

安装后启动,连接MongoDB

mongodb实现like mongodb实现评论_数据库_06

MongoDB基本操作

数据库以及表的操作

// 查看数据库
show dbs

// 创建数据库 
// use 数据库名称    如果数据库名称存在,那么切换到该数据库,不如不存在,那就新增数据库
use article
use student
use commentdb

// 查看当前所在数据库
db

// 删除数据库
// db.dropDatabase()  删除当前所在的数据库
db.dropDatabase()

// 操作集合
// 查看集合
show tables

// 新增集合
// db.createCollection(集合名)
db.createCollection("student")

// 删除集合
// db.集合名.drop()
db.student.drop()

新增数据

在MongoDB中,存储的文档结构是一种类似于json的结构,称之为bson(全称为:Binary JSON)。

// 新增文档
// db.集合名.insert(文档)   json格式的文档
db.comment.insert({"name":"tom","age":18})
db.comment.insert({"_id":2,"name":"jerry","age":19})


// 查看所有文档
// db.集合名.find()
db.comment.find()

查询数据

MongoDB 查询数据的语法格式如下:

db.表名.find([query],[fields])
  • query :可选,使用查询操作符指定查询条件
  • fields :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需db.省略该参数即可(默认省略)。

如果你需要以易读的方式来读取数据,可以使用 prett() 方法,语法格式如下:

>db.表名.find().pretty()

pretty() 方法以格式化的方式来显示所有文档。

MongoDB 与 RDBMS Where 语句比较

操作

格式

范例

RDBMS中的类似语句

等于

{:}

db.col.find({"userId":"101"})

where userId= '101'

小于

{:{$lt:}}

db.col.find({"thumpUp":{$lt:50}})

where thumpUp < 50

小于或等于

{:{$lte:}}

db.col.find({"thumpUp":{$lte:50}})

where thumpUp <= 50

大于

{:{$gt:}}

db.col.find({"thumpUp":{$gt:50}})

where thumpUp > 50

大于或等于

{:{$gte:}}

db.col.find({"thumpUp":{$gte:50}})

where thumpUp >= 50

不等于

{:{$ne:}}

db.col.find({"thumpUp":{$ne:50}})

where thumpUp != 50

包含使用$in操作符

查询评论集合中userid字段包含101和102的文档:

db.comment.find({userId:{$in:["101","102"]}})

不包含使用$nin操作符

查询评论集合中userid字段不包含101和102的文档:

db.comment.find({userId:{$nin:["101","102"]}})

And 条件

db.col.find({key1:value1, key2:value2})

Or 条件

db.col.find(
   {
      $or: [
         {key1: value1}, {key2:value2}
      ]
   }
)

And 和 Or 联合使用

类似常规 SQL 语句为: 'where thumbUp>1000 AND (userId = '101' OR articleId = '001')'

db.col.find({"thumbUp": {$gt:50}, $or: [{"userId": "101"},{"articleId": "001"}]})

limit()方法

db.COLLECTION_NAME.find().limit(NUMBER)

skip() 方法

db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER)

sort() 方法

db.COLLECTION_NAME.find().sort({KEY:1})

其中 1 为升序排列,而 -1 是用于降序排列。

db.comment.find().sort({"thumbUp":-1})

count() 方法

db.comment.count(query, options)

模糊查询

db.comment.find({content:/理/})
db.comment.find({content:/^理/})
db.comment.find({content:/理$/})

范例

RDBMS中的类似语句

db.comment.find({content:/理/})

where content like '%理%'

db.comment.find({content:/^理/})

where content like '理%'

db.comment.find({content:/理$/})

where content like '%理'

案例

db.comment.drop()

// 插入测试数据
db.comment.insert({_id:"1",articleId:"001",content:"说得很有道理",userId:"101",parentId:"0",publishDate:new Date(),thumbUp:1000});
db.comment.insert({_id:"2",articleId:"001",content:"楼主太有才了",userId:"102",parentId:"0",publishDate:new Date(),thumbUp:1100});
db.comment.insert({_id:"3",articleId:"001",content:"这个可以啊",userId:"103",parentId:"0",publishDate:new Date(),thumbUp:1200});
db.comment.insert({_id:"4",articleId:"002",content:"我怀疑你在开车,可我没有证据",userId:"102",parentId:"0",publishDate:new Date(),thumbUp:1300});
db.comment.insert({_id:"5",articleId:"002",content:"同上",userId:"104",parentId:"0",publishDate:new Date(),thumbUp:1400});
db.comment.insert({_id:"6",articleId:"002",content:"这个有内涵",userId:"101",parentId:"0",publishDate:new Date(),thumbUp:1500});
db.comment.insert({_id:"7",articleId:"003",content:"理论上可行",userId:"103",parentId:"0",publishDate:new Date(),thumbUp:1600});

// 条件查询
// db.集合名.find(query,projection)  query-->json  projection --> 指定返回值 1为返回  0 为不返回
db.comment.find({userId:"101"})

db.comment.find({userId:"101"},{articleId:1,content:1})

db.comment.find({userId:"101"},{articleId:1,content:1,_id:0})

// 多条件查询and  将多个条件写在json中  
// {key1:value1,key2:value2}
db.comment.find({userId:"101",articleId:"001"})

// or 查询
// {$or:[{},{}]}
db.comment.find({$or:[{userId:"101"},{articleId:"002"}]})

// > >= < <= !=  gt gte lt lte ne
// {key:{$gt:value}}
db.comment.find({thumbUp:{$gt:1200}})
db.comment.find({thumbUp:{$gte:1200}})
db.comment.find({thumbUp:{$ne:1200}})

// 需要注意类型不同
db.comment.find({_id:{$lt:2}})
db.comment.find({_id:{$lt:"2"}})

// in nin
// {key:{$in:[value1,value2]}}
db.comment.find({userId:{$in:["101","102"]}})

// 模糊查询  支持正则表达式
// db.集合名.find({key:/value/})	匹配开头 {key:/^value/} 匹配结尾 {key:/value$/}

db.comment.find({content:/理/})
db.comment.find({content:/^理/})
db.comment.find({content:/理$/})

// 统计数量
// db.集合名.count(json)
db.comment.count()
db.comment.count({userId:"101"})


//限制行数 limit
db.comment.find().limit(2)

// 跳过数据 skip
db.comment.find().skip(2)


db.comment.find().skip(2).limit(2)

// 排序
// db.集合名.find().sort({key:value})  value 为1时为升序 -1 为降序
db.comment.find().sort({thumbUp:1})
db.comment.find().sort({thumbUp:-1})

更新数据

update() 方法用于更新已存在的文档。语法格式如下:

db.表名.update(
   <query>,
   <update>,
   [
     upsert: <boolean>,
     multi: <boolean>,
     writeConcern: <document>
   ]
)

参数说明:

  • query : update的查询条件,类似sql update查询内where后面的。
  • update : update的对象和一些更新的操作符(如$,$inc.$set)等,也可以理解为sql update查询内set后面的
  • upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
  • multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
// 更新文档
// db.集合名.update(query,update,option)   query --> json   update --> json  option {upsert,multi}
// upsert表示如果没有查到对应的记录,那么新增一项
// multi 表示更新多个查询到的记录,默认情况下只更新一条

// 文档替换
db.comment.update({_id:"5"},{content:"我认为可行"})

// 更新字段 {$set:{key:value}}
db.comment.update({_id:"5"},{$set:{userId:"103"}})

// 更新多个字段 {$set:{key1:value1,key2:value2}}

db.comment.update({_id:"5"},{$set:{articleId:"001",thumbUp:1400}})

db.comment.update({_id:"8"},{content:"我认为可行"},{upsert:1})
// 默认情况下只更新一条
db.comment.update({userId:"101"},{$set:{thumbUp:0}})

db.comment.update({userId:"101"},{$set:{thumbUp:1}},{multi:1})

// 值递增 $inc:{key:value}   value指的是更新的步长
db.comment.update({userId:"101"},{$inc:{thumbUp:10}},{multi:1})

删除数据

通过remove()方法进行删除数据,语法如下:

db.表名.remove(
   <query>,
   [
     justOne: <boolean>,
     writeConcern: <document>
   ]
)

参数说明:

  • query :(可选)删除的文档的条件。
  • justOne : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。
  • writeConcern :(可选)抛出异常的级别。

实例:

// 删除
// db.集合名.remove(json)
db.comment.remove({_id:"5"})
db.comment.remove({userId:"101"})
db.comment.remove({})

SpringDataMongoDB快速入门

创建项目

创建项目:mongodb-demo

pom文件:

<!-- 继承Spring boot -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.8.RELEASE</version>
</parent>

<dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-mongodb</artifactId>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.58</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>org.junit.vintage</groupId>
          <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
  </dependencies>

application.yml

spring:
  data:
    mongodb:
      host: 192.168.85.143
      port: 27017
      database: leadnews-comment

引导类:

package com.heima.mongo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author syl
 */
@SpringBootApplication
public class MongoApp {
    public static void main(String[] args) {
        SpringApplication.run(MongoApp.class, args);
    }
}

实体类准备

实体类准备,添加注解@Document与mongodb中的集合对应上

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.math.BigDecimal;
import java.util.Date;

/**
 * APP评论信息
 */
@Data
@Document("ap_comment")
public class ApComment {

    /**
     * id
     */
    private String id;

    /**
     * 用户ID
     */
    private Integer userId;

    /**
     * 用户昵称
     */
    private String userName;

    /**
     * 文章id或动态id
     */
    private Long entryId; 

    /**
     * 评论内容
     */
    private String content;
 
   

}

基本增删改查

可以使用MongoTemplate来操作

创建测试类:

package com.heima.mongo;

import com.heima.mongo.entity.ApComment;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
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;

@SpringBootTest
// 如果启动类所在的包名和当前测试类的包名不一致,需要手动指定classes到启动类
// @SpringBootTest(classes = MongoApp.class)
public class MongoTests {

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 新增文档
     */
    @Test
    public void addDoc() {
        ApComment comment = new ApComment();
        comment.setId("123456");
        comment.setUserId(10010);
        comment.setUserName("火星联通");
        comment.setContent("欢迎来到火星!!!");
        mongoTemplate.save(comment);
    }

    /**
     * 根据id查询文档
     */
    @Test
    public void getDocById(){
        ApComment apComment = mongoTemplate.findById("123456", ApComment.class);
        System.out.println(apComment);
    }

    /**
     * 更新文档
     */
    @Test
    public void updateDoc(){
        // 构建查询条件
        Query query = new Query();
        // 相当于sql select * from tb where id = '123456'
        query.addCriteria(Criteria.where("_id").is("123456"));
        Update update = new Update();
        update.set("userId","10011");
        UpdateResult result = mongoTemplate.updateFirst(query, update, ApComment.class);
        System.out.println(result);
    }

    /**
     * 删除文档
     */
    @Test
    public void delete(){
        // 构建查询条件
        Query query = new Query();
        // 相当于sql select * from tb where id = '123456'
        query.addCriteria(Criteria.where("_id").is("123456"));
        DeleteResult result = mongoTemplate.remove(query, ApComment.class);
        System.out.println(result);
    }
}

app端评论-发表评论

需求分析

mongodb实现like mongodb实现评论_json_07

  • 文章详情页下方可以查看评论信息,按照点赞数量倒序排列,展示评论内容、评论的作者、点赞数、回复数、时间,默认查看20条评论,如果想查看更多,可以点击加载更多进行分页
  • 登录用户可以进行评论
  • 可以针对当前文章发布评论
  • 可以针对于某一条评论进行点赞操作

接口定义

说明

接口路径

/api/v1/comment/save

请求方式

POST

参数

CommentSaveDto

响应结果

ResponseResult

思路分析

APP评论信息点赞

mongodb实现like mongodb实现评论_mongodb实现like_08

(2)思路分析

  • 评论内容不为空且不超过140字
  • 评论内容需要做文本反垃圾检测
  • 评论内容需要做敏感词过滤
  • 用户登录后可以对文章发表评论
  • 评论数据修改较为频繁,并且数据库比较大,这里最终存储在mongo数据库中

功能实现

搭建评论微服务

(1)创建项目heima-leadnews-comment

mongodb实现like mongodb实现评论_数据库_09

(2)pom依赖

<dependencies>
	<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb</artifactId>
    </dependency>
</dependencies>

(3) 启动类

package com.heima.comment;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author mcm
 */
@SpringBootApplication
public class CommentApp {
    public static void main(String[] args) {
        SpringApplication.run(CommentApp.class, args);
    }
}

(4)application.yml

server:
  port: 9007
spring:
  application:
    name: leadnews-comment
  cloud:
    nacos:
      discovery:
        server-addr: http://192.168.85.143:8848
  data:
    mongodb:
      host: 192.168.85.143
      port: 27017
      database: leadnews_comment

配置

(1)需要使用阿里云安全、接口knife4j、通用异常

如下配置:

package com.heima.comment.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(value = {"com.heima.common.exception","com.heima.common.knife4j"})
public class InitConfig {
}

(2)获取当前登录用户信息

评论必须在登录的情况下才能发布,所以需要验证用户获取用户信息,添加拦截器:

package com.heima.comment.interceptor;

import com.heima.common.dto.User;
import com.heima.common.util.UserThreadLocalUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class UserInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//        从请求头中获取userId和name
        String userId = request.getHeader("userId");
        String name = request.getHeader("name");
        if(StringUtils.isNotBlank(userId)){
            User user = new User(Integer.parseInt(userId),name);
            UserThreadLocalUtil.set(user);
        }
        return true; //一直是放行
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        UserThreadLocalUtil.remove();
    }

}

拦截器配置

package com.heima.comment.config;

import com.heima.comment.interceptor.UserInterceptor;
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 MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInterceptor()).addPathPatterns("/**");
    }
}

实体

实体类ApComment

package com.heima.comment.entity;

import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;

import java.math.BigDecimal;
import java.util.Date;

/**
 * APP评论信息
 */
@Data
@Document("ap_comment")
public class ApComment {

    /**
     * id
     */
    private String id;

    /**
     * 用户ID
     */
    private Integer userId;

    /**
     * 用户昵称
     */
    private String userName;

    /**
     * 用户头像
     */
    private String image;

    /**
     * 文章id
     */
    private Long articleId;

    /**
     * 评论内容
     */
    private String content;

    /**
     * 点赞数
     */
    private Integer likes = 0;

    /**
     * 回复数
     */
    private Integer repay = 0;

    /**
     * 创建时间
     */
    private Date createdTime;

}

ApCommentLike

package com.heima.comment.entity;

import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;

/**
 * APP评论信息点赞
 */
@Data
@Document("ap_comment_like")
public class ApCommentLike {

    /**
     * id
     */
    private String id;

    /**
     * 用户ID
     */
    private Integer userId;

    /**
     * 评论id
     */
    private String commentId;

    /**
     * 0:点赞
     * 1:取消点赞
     */
    private Integer operation;
    
    private Date createTime;
       
}

远程调用接口

评论中需要查询登录的用户信息,所以需要定义远程feign接口根据用户id获取用户信息

1、在user用户微服务中修改ApUserController ,添加根据id查询用户方法

@RestController
@RequestMapping("/api/v1/user")
@CrossOrigin
public class ApUserController {

    @Autowired
    private IApUserService apUserService;

    @GetMapping("/{id}")
    public ResponseResult<ApUser> getById(@PathVariable("id") Integer id) {
        ApUser apUser = apUserService.getById(id);
        return ResponseResult.okResult(apUser);
    }
}

2、在comment微服务中定义feign接口

先添加feign的启动器

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

再添加对Feign的注解支持

mongodb实现like mongodb实现评论_spring_10

3、把user微服务的实体类拷贝过来,去掉表相关的注解

package com.heima.comment.dto;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.io.Serializable;

/**
 * <p>
 * APP用户信息表
 * </p>
 *
 * @author syl
 * @since 2022-08-22
 */
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(description="APP用户信息表")
public class ApUser implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @ApiModelProperty(value = "主键")
    private Integer id;
  
    /**
     * 用户名
     */
    @ApiModelProperty(value = "用户名")
    private String name;

    

    /**
     * 头像
     */
    @ApiModelProperty(value = "头像")
    private String image;

}

4、在comment微服务中定义feign接口

package com.heima.comment.feign;

import com.heima.comment.entity.ApUser;
import com.heima.common.dto.ResponseResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient("leadnews-user")
public interface UserFeign {

    /**
     * 根据id查询用户
     * @param id
     * @return
     */
    @GetMapping("/api/v1/user/{id}")
    public ResponseResult<ApUser> getUserById(@PathVariable("id") Integer id);
}

接口定义

说明

接口路径

/api/v1/comment/save

请求方式

POST

参数

CommentSaveDto

响应结果

ResponseResult

保存评论

package com.heima.comment.controller;

import com.heima.comment.dto.CommentSaveDto;
import com.heima.comment.service.ICommentService;
import com.heima.common.dto.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/comment")
public class CommentController {

    @Autowired
    private ICommentService commentService;

    /**
     * 发表评论
     * @param dto
     * @return
     */
    @PostMapping("/save")
    public ResponseResult saveComment(@RequestBody CommentSaveDto dto) {
        return commentService.saveComment(dto);
    }
}

发表评论:CommentSaveDto

package com.heima.comment.dto;

import lombok.Data;

@Data
public class CommentSaveDto {

    /**
     * 文章id
     */
    private Long articleId;
    
    /**
     * 频道id
     */
    private Integer channelId;

    /**
     * 评论内容
     */
    private String content;
}

业务层

业务层接口:

package com.heima.comment.service;

import com.heima.comment.dto.CommentSaveDto;
import com.heima.common.dto.ResponseResult;

public interface ICommentService {
    ResponseResult saveComment(CommentSaveDto dto);
}

实现类:

package com.heima.comment.service.impl;

import com.heima.comment.dto.ApUser;
import com.heima.comment.dto.CommentSaveDto;
import com.heima.comment.entity.ApComment;
import com.heima.comment.feign.UserFeign;
import com.heima.comment.service.ICommentService;
import com.heima.common.dto.ResponseResult;
import com.heima.common.dto.User;
import com.heima.common.enums.AppHttpCodeEnum;
import com.heima.common.util.AppThreadLocalUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class CommentServiceImpl implements ICommentService {
 
    @Autowired
    private MongoTemplate mongoTemplate;

      @Autowired
    private UserFeign userFeign;


    @Override
    public ResponseResult saveComment(CommentSaveDto dto) {

        User user = UserThreadLocalUtil.get();
//        用户登录后可以对文章发表评论
        if(user==null||user.getUserId()==0){
            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
        }

//        评论内容不为空且不超过140字
        if(StringUtils.isBlank(dto.getContent()) || dto.getContent().length()>140){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }


        ResponseResult<ApUser> responseResult = userFeign.getById(user.getUserId());
        ApUser apUser = responseResult.getData();

        ApComment apComment = new ApComment();
//        apComment.setId();  //不需要 mongodb会自动生成
        apComment.setUserId(user.getUserId());
        apComment.setUserName(apUser.getName());
        apComment.setImage(apUser.getImage());
        apComment.setArticleId(dto.getArticleId());
        apComment.setContent(dto.getContent());
        apComment.setLikes(0); //当前这个评论收到的点赞数
        apComment.setRepay(0); //当前这个评论收到的回复数
        apComment.setCreatedTime(new Date());
        mongoTemplate.save(apComment);
        return ResponseResult.okResult();
    }

}

在APP网关中添加评论服务

- id: comment
          uri: lb://leadnews-comment
          predicates:
            - Path=/comment/**
          filters:
            - StripPrefix= 1

测试

打开前端页面进行测试

mongodb实现like mongodb实现评论_数据库_11

app端评论-评论列表

需求分析

mongodb实现like mongodb实现评论_spring_12

  • 根据文章id查询评论列表
  • 按照评论发布时间倒序排列 createTime desc
  • 分页查询
  • 如果用户登录了,需要判断当前用户点赞了列表中哪些评论(这个等做完对评论点赞后再说)

功能实现

接口定义

说明

接口路径

/api/v1/comment/load

请求方式

POST

参数

CommentDto

响应结果

ResponseResult

mongodb实现like mongodb实现评论_spring_13

CommentController中添加方法

/**
     * 加载评论列表
     * @param dto
     * @return
     */
@PostMapping("/load")
public ResponseResult loadComment(@RequestBody CommentDto dto) {
    return commentService.loadComment(dto);
}

参数 CommentDto

import lombok.Data;

import java.util.Date;

@Data
public class CommentDto {

    /**
     * 文章id
     */
    private Long articleId;
    /**
     * 显示条数
     */
    private Integer size;
    /**
     * 最小时间
     */
    private Date minDate;
    /**
     * 是否首页
     */
    private Integer index;
}

业务层

业务层接口:

修改CommentService业务层接口,添加方法

/**
     * 根据文章id查询评论列表
     * @return
     */
ResponseResult loadComment(CommentDto dto);

实现类:

@Override
    public ResponseResult loadComment(CommentDto dto) {
        if (dto.getSize() == null || dto.getSize() <= 0 || dto.getSize() >= 50) {
            dto.setSize(10);
        }
//        - 根据文章id查询评论列表
//        - 按照评论发布时间倒序排列  createTime  desc
//        - 分页查询  limit  skip
        Query query = new Query(Criteria.where("articleId").is(dto.getArticleId()));
        query.with(Sort.by(Sort.Direction.DESC,"createdTime"));
        query.limit(dto.getSize());
        List<ApComment> commentList = mongoTemplate.find(query, ApComment.class);
        return ResponseResult.okResult(commentList);
    }

app端评论-点赞评论

需求分析

mongodb实现like mongodb实现评论_数据库_14

评论与点赞对应关系如下:

mongodb实现like mongodb实现评论_mongodb实现like_08

  • 用户登录后可以对评论点赞
  • 根据当前用户id和评论id查询是否已有点赞记录
  • 没有点赞记录,新增点赞记录,更新评论的点赞数+1
  • 已有点赞记录,判断点赞记录中的点赞状态是否与当前操作一致,不一致进行点赞数更新和点赞记录更新
  • 点赞操作,评论的点赞数+1
  • 取消点赞操作,评论的点赞数-1

接口定义

说明

接口路径

/api/v1/comment/like

请求方式

POST

参数

CommentLikeDto

响应结果

ResponseResult

功能实现

CommentControll中添加方法

/**
 * 点赞某一条评论
 * @param dto
 * @return
 */

@PostMapping("/like") //对评论点赞或取消点赞
public ResponseResult commentLike(@RequestBody CommentLikeDto dto){
    return commentService.commentLike(dto);
}

用户点赞:CommentLikeDto

import lombok.Data;

@Data
public class CommentLikeDto {

    /**
     * 评论id
     */
    private String commentId;

    /**
     * 0:点赞
     * 1:取消点赞
     */
    private Integer operation;
}

业务层

业务层接口:ICommentService

/**
     * 点赞评论
     * @param dto
     * @return
     */
public ResponseResult commentLike(CommentLikeDto dto);

实现类:

@Override
    public ResponseResult commentLike(CommentLikeDto dto) {
        User user = UserThreadLocalUtil.get();
//        用户登录后可以对评论点赞
        if(user==null||user.getUserId()==0){
            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
        }

//        向点赞表中插入数据
        ApCommentLike apCommentLike = new ApCommentLike();
        apCommentLike.setUserId(user.getUserId());
        apCommentLike.setCommentId(dto.getCommentId());
        apCommentLike.setOperation(dto.getOperation());
        apCommentLike.setCreateTime(new Date());
        mongoTemplate.save(apCommentLike);

//      对评论数修改   如果是SQL update ap_commet set likes=like+_1 where _id=??
        Query query = new Query(Criteria.where("_id").is(dto.getCommentId()));
        Update update = new Update();
//        update.set("likes",);
        if(dto.getOperation()==0){   //0:点赞
            update.inc("likes",1);
        }else{
            update.inc("likes",-1);
        }
        mongoTemplate.updateFirst(query,update,ApComment.class);
        return ResponseResult.okResult();
    }

测试 //10.11为什么拿postman测试

使用postman来测试

mongodb实现like mongodb实现评论_json_16

评论列表中显示是否点赞

需求分析

mongodb实现like mongodb实现评论_json_17

mongodb实现like mongodb实现评论_json_18

实现思路:

当前登录人是否对这个评论点赞需求记录起来,为了提高效率,这种数据标记可以记录到Redis中

在点赞和取消点赞那里就应该存储和删除Redis中的数据

功能实现

定义返回vo类

package com.heima.comment.vo;

import com.heima.comment.entity.ApComment;
import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper = true)
public class ApCommentVo extends ApComment {

    /**
     * 0:点赞
     * 1:取消点赞
     */
    private Integer operation;
}

添加Redis配置

在leadnews-comment模块中引入redis

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置文件中添加redis地址

spring:
  redis:
    host: 192.168.85.143

点赞时记录数据

mongodb实现like mongodb实现评论_mongodb实现like_19

列表回显时赋值

完整的load方法代码:

@Override
    public ResponseResult load(CommentDto dto) {
        // 根据文章id查询评论列表        
        // 按照创建时间倒序排列        
        // 分页判断数据小于前端传递的最小时间
        // 每页默认显示10条评论数据
        if (dto.getSize() == null || dto.getSize() <= 0 || dto.getSize() >= 50) {
            dto.setSize(10);
        }
        Query query = new Query();
        query.addCriteria(Criteria.where("entryId").is(dto.getArticleId()).and("createdTime").lt(dto.getMinDate()));
        // 分页 和 排序
        Pageable page = PageRequest.of(0, dto.getSize(), Sort.Direction.DESC, "createdTime");
        query.with(page);
        List<ApComment> apComments = mongoTemplate.find(query, ApComment.class);
        // TODO 登录用户需要查看当前返回的列表中是否已经有点赞记录
        User user = UserThreadLocalUtil.get();
        if(user!=null&&user.getUserId()!=0){
            List<ApCommentVo> commentVoList = new ArrayList<>();
            for (ApComment comment : apComments) {
                ApCommentVo commentVo = new ApCommentVo();
                BeanUtils.copyProperties(comment,commentVo);
                Boolean hasKey = redisTemplate.hasKey("comment_like_" + user.getUserId() + "_" + comment.getId());
                commentVo.setOperation(hasKey?0:1); //0代表当前登录人对这个评论点赞
                commentVoList.add(commentVo);
            }
            return ResponseResult.okResult(commentVoList);
        }
        return ResponseResult.okResult(apComments);
    }

app端评论回复(学员实现)

包含的功能有:发表回复、点赞回复、回复列表

需求分析

mongodb实现like mongodb实现评论_数据库_20

  • 当用户点击了评论中的回复就可以查看所有评论回复内容
  • 可以针对当前评论进行回复,需要更新评论的回复数量
  • 可以对当前评论回复列表进行点赞操作,同时记录当前回复评论点赞信息

思路分析

(1)数据实体

操作数据实体为mongodb中的集合,评论回复集合是ap_comment_repay

对应实体类为:

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.math.BigDecimal;
import java.util.Date;
/**
 * APP评论回复信息
 */
@Data
@Document("ap_comment_repay")
public class ApCommentRepay {
    /**
     * id
     */
    @Id
    private String id;

    /**
     * 用户ID
     */
    private Integer userId;

    /**
     * 用户昵称
     */
    private String userName;

    /**
     * 评论id
     */
    private String commentId;

    /**
     * 回复内容
     */
    private String content;
    
    /**
     * 点赞数
     */
    private Integer likes = 0;

    /**
     * 创建时间
     */
    private Date createdTime;

  }

APP评论回复点赞信息,对回复点赞的集合是ap_comment_repay_like

import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;

/**
 * APP评论回复信息点赞信息
 */
@Data
@Document("ap_comment_repay_like")
public class ApCommentRepayLike {

    /**
     * id
     */
    private String id;

    /**
     * 用户ID
     */
    private Integer userId;

    /**
     * 评论id
     */
    private String commentRepayId;

    /**
     * 0:点赞
     * 1:取消点赞
     */
    private Integer operation;
}

(2)思路分析:

1 评论回复

  • 回复内容不为空且不超过140字
  • 回复内容需要做文本反垃圾检测
  • 回复内容需要做敏感词过滤
  • 用户登录后可以对评论发表回复

2 回复点赞

  • 用户登录后可以对回复点赞
  • 根据当前用户id和回复id查询是否已有点赞记录
  • 没有点赞记录,新增点赞记录,更新回复的点赞数+1
  • 已有点赞记录,判断点赞记录中的点赞状态是否与当前操作一致,不一致进行点赞数更新和点赞记录更新
  • 点赞操作,回复的点赞数+1
  • 取消点赞操作,回复的点赞数-1

3 回复列表

  • 根据评论id查询回复列表
  • 按照点赞数量倒序排列
  • 分页查询
  • 如果用户未登录,返回回复列表
  • 如果用户登录了,需要判断当前用户点赞了列表中哪些回复

以上描述是不是非常眼熟,和评论文章功能除了表名不一样以外其他的一模一样啊~~~~

接下来就根据接口文档自己实现吧

接口定义

某评论回复列表

说明

接口路径

/api/v1/comment_repay/load

请求方式

POST

参数

CommentRepayDto

响应结果

ResponseResult

对某评论发表回复

说明

接口路径

/api/v1/comment_repay/save

请求方式

POST

参数

CommentRepaySaveDto

响应结果

ResponseResult

点赞某条回复

说明

接口路径

/api/v1/comment_repay/like

请求方式

POST

参数

CommentRepayLikeDto

响应结果

ResponseResult

功能实现

(1)接口定义

package com.heima.comment.controller;

import com.heima.comment.dto.CommentRepayDto;
import com.heima.comment.dto.CommentRepayLikeDto;
import com.heima.comment.dto.CommentRepaySaveDto;
import com.heima.comment.service.ICommentRepayService;
import com.heima.common.dto.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/comment_repay")
public class CommentRepayController {

    @Autowired
    private ICommentRepayService repayService;

    /**
     * 发表回复
     * @param dto
     * @return
     */
    @PostMapping("/save")
    public ResponseResult saveRepay(@RequestBody CommentRepaySaveDto dto) {
        return repayService.saveRepay(dto);
    }

    /**
     * 点赞回复
     * @param dto
     * @return
     */
    @PostMapping("/like")
    public ResponseResult like(@RequestBody CommentRepayLikeDto dto){
        return repayService.like(dto);
    }

    /**
     * 加载回复列表
     * @param dto
     * @return
     */
    @PostMapping("/load")
    public ResponseResult load(@RequestBody CommentRepayDto dto){
        return repayService.load(dto);
    }
}

加载评论回复列表参数:CommentRepayDto

import lombok.Data;
import java.util.Date;

@Data
public class CommentRepayDto {

    /**
     * 评论id
     */
    private String commentId;
    /**
     * 显示条数
     */
    private Integer size;
    /**
     * 最小时间
     */
    private Date minDate;
}

保存回复内容参数:CommentRepaySaveDto

import lombok.Data;

@Data
public class CommentRepaySaveDto {

    /**
     * 评论id
     */
    private String commentId;

    /**
     * 回复内容
     */
    private String content;
}

点赞回复内容参数:CommentRepayLikeDto

import lombok.Data;

@Data
public class CommentRepayLikeDto {

    /**
     * 回复id
     */
    private String commentRepayId;

    /**
     * 0:点赞
     * 1:取消点赞
     */
    private Integer operation;
}

(2) 业务层

评论回复业务层接口

package com.heima.comment.service;

import com.heima.comment.dto.CommentRepayDto;
import com.heima.comment.dto.CommentRepayLikeDto;
import com.heima.comment.dto.CommentRepaySaveDto;
import com.heima.common.dto.ResponseResult;

public interface ICommentRepayService {
    
    ResponseResult load(CommentRepayDto dto);
    
    ResponseResult saveRepay(CommentRepaySaveDto dto);

    ResponseResult like(CommentRepayLikeDto dto);

}

加载评论回复列表数据封装类

package com.heima.comment.vo;

import com.heima.comment.entity.ApCommentRepay;
import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper = true)
public class ApCommentRepayVo extends ApCommentRepay {

    /**
     * 0:点赞
     * 1:取消点赞
     */
    private Integer operation;
}

实现类

package com.heima.comment.service.impl;

import com.heima.comment.dto.CommentRepayDto;
import com.heima.comment.dto.CommentRepayLikeDto;
import com.heima.comment.dto.CommentRepaySaveDto;
import com.heima.comment.entity.ApComment;
import com.heima.comment.entity.ApCommentRepay;
import com.heima.comment.entity.ApCommentRepayLike;
import com.heima.comment.entity.ApUser;
import com.heima.comment.feign.UserFeign;
import com.heima.comment.service.ICommentRepayService;
import com.heima.comment.vo.ApCommentRepayVo;
import com.heima.comment.vo.ApCommentVo;
import com.heima.common.dto.ResponseResult;
import com.heima.common.dto.User;
import com.heima.common.enums.AppHttpCodeEnum;
import com.heima.common.util.UserThreadLocalUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
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.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Service
public class CommentRepayServiceImpl implements ICommentRepayService {

    @Autowired
    private UserFeign userFeign;

    @Autowired
    private MongoTemplate mongoTemplate;


    @Override
    public ResponseResult load(CommentRepayDto dto) {
        // 根据文章id查询评论列表
        // 按照创建时间倒序排列
        // 分页判断数据小于前端传递的最小时间
        // 每页默认显示10条评论数据
        if (dto.getSize() == null || dto.getSize() <= 0 || dto.getSize() >= 50) {
            dto.setSize(10);
        }
        Query query = new Query();
        query.addCriteria(Criteria.where("commentId").is(dto.getCommentId()).and("createdTime").lt(dto.getMinDate()));
        // 分页 和 排序        
        query.limit(dto.getSize());
        query.with(Sort.by(Sort.Direction.DESC,"createdTime")); 
        List<ApCommentRepay> apCommentRepayList = mongoTemplate.find(query, ApCommentRepay.class);
        // 判断当前用户是否登录
        User user = UserThreadLocalUtil.get();
        // TODO 登录用户需要查看当前返回的列表中是否已经有点赞记录
        if(user!=null&&user.getUserId()!=0){
            List<ApCommentRepayVo> commentRepayVoList = new ArrayList<>();
            for (ApCommentRepay commentRepay : apCommentRepayList) {
                ApCommentRepayVo commentRepayVo = new ApCommentRepayVo();
                BeanUtils.copyProperties(commentRepay,commentRepayVo);
                Boolean hasKey = redisTemplate.hasKey("comment_repay_like_" + user.getUserId() + "_" + commentRepay.getId());
                commentRepayVo.setOperation(hasKey?0:1); //0代表当前登录人对这个回复点赞
                commentRepayVoList.add(commentRepayVo);
            }
            return ResponseResult.okResult(commentRepayVoList);
        }
        return ResponseResult.okResult(apCommentRepayList);
    }

    @Override
    public ResponseResult saveRepay(CommentRepaySaveDto dto) {
        // 判断内容是否大于140字
        if (dto == null || dto.getContent().length() > 140) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        // 判断用户是否登录(要求使用手机号和密码登录的用户)
        User user = UserThreadLocalUtil.get();
        if (user == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
        }
        // 安全过滤--阿里云文本审核  todo 自行实现
        ApCommentRepay comment = new ApCommentRepay();
        comment.setUserId(user.getUserId());
        // 根据用户id查询用户,调用用户微服务提供的远程接口
        ResponseResult<ApUser> userResponseResult = userFeign.getUserById(user.getUserId());
        if (userResponseResult.getCode().equals(AppHttpCodeEnum.SUCCESS.getCode())) {
            ApUser apUser = userResponseResult.getData();
            comment.setUserName(apUser.getName());
        }
        comment.setCommentId(dto.getCommentId());
        comment.setContent(dto.getContent());
        comment.setLikes(0);
        comment.setCreatedTime(new Date());
        // 保存评论
        mongoTemplate.save(comment);
        // 将评论表的回复数量+1
        Query commentQuery = new Query();
        commentQuery.addCriteria(Criteria.where("_id").is(dto.getCommentId()));
        // 更新条件
        Update update = new Update();
        update.inc("repay");
        mongoTemplate.updateFirst(commentQuery,update, ApComment.class);
        return ResponseResult.okResult();
    }



    @Override
    public ResponseResult like(CommentRepayLikeDto dto) {
        // 获取当前的用户
        User user = UserThreadLocalUtil.get();
        if (user == null||user.getUserId()==0) {
            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
        }
        // 构建点赞记录
        ApCommentRepayLike like = new ApCommentRepayLike();
        like.setUserId(user.getUserId());
        like.setCommentRepayId(dto.getCommentRepayId());
        like.setOperation(dto.getOperation());
        // 保存点赞或取消点赞记录
        mongoTemplate.save(like);

        // 更新回复的点赞数量+1或者-1
        Query commentQuery = new Query();
        commentQuery.addCriteria(Criteria.where("_id").is(dto.getCommentRepayId()));
        Update update = new Update();
        if (dto.getOperation() == 1) { //取消点赞
            update.inc("likes",-1);
            //删除在Redis中做的标记
            redisTemplate.delete("comment_repay_like_"+user.getUserId()+"_"+dto.getCommentRepayId());
        }else{  //点赞
            update.inc("likes",1);
            //在Redis中做标记
            redisTemplate.boundValueOps("comment_repay_like_"+user.getUserId()+"_"+dto.getCommentRepayId())
                    .set("");//这里放什么值无所谓,只要判断存在这个key就可以了
        }
        mongoTemplate.updateFirst(commentQuery, update, ApCommentRepay.class);

        return ResponseResult.okResult();
    }
    @Autowired
    private StringRedisTemplate redisTemplate;
}

(3) 测试

打开前端页面进行测试

mongodb实现like mongodb实现评论_spring_21