【探花交友DAY 04】用户设置、MongoDB使用、今日佳人

1. 用户通用设置模块

1.1 需求分析

1.1.1 功能介绍

在探花交友App上,登录的用户可以设置陌生人问题,通知设置,黑名单设置,以及修改手机号码设置。如下图所示:
【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第1张图片
【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第2张图片

1.1.2 数据库分析

为了完成上述功能,采用了一下几张表

通用设置:用来保存用户的通知信息,和用户是一对一的关系

CREATE TABLE `tb_settings` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) DEFAULT NULL,
  `like_notification` tinyint(4) DEFAULT '1' COMMENT '推送喜欢通知',
  `pinglun_notification` tinyint(4) DEFAULT '1' COMMENT '推送评论通知',
  `gonggao_notification` tinyint(4) DEFAULT '1' COMMENT '推送公告通知',
  `created` datetime DEFAULT NULL,
  `updated` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='设置表';

问题表:保存用户的陌生人问题,一个用户只能有一个陌生人问题,和用户表是一对一的关系

CREATE TABLE `tb_question` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
  `txt` varchar(200) DEFAULT NULL COMMENT '问题内容',
  `created` datetime DEFAULT NULL,
  `updated` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8

黑名单:和用户表是一对多的关系,黑名单表通过user_idblack_user_id唯一确定一条记录

CREATE TABLE `tb_black_list` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) DEFAULT NULL,
  `black_user_id` bigint(20) DEFAULT NULL,
  `created` datetime DEFAULT NULL,
  `updated` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='黑名单';

1.2 准备工作

实体类
Settings

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Settings extends BasePojo {

    private Long id;
    private Long userId;
    private Boolean likeNotification;
    private Boolean pinglunNotification;
    private Boolean gonggaoNotification;

}

Question

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Question extends BasePojo {

    private Long id;
    private Long userId;
    //问题内容
    private String txt;

}

BlackList

@Data
@NoArgsConstructor
@AllArgsConstructor
public class BlackList extends BasePojo {

    private Long id;
    private Long userId;
    private Long blackUserId;
}

Mapper接口

ettingsMapper

public interface SettingsMapper extends BaseMapper<Settings> {}

QuestionMapper

public interface QuestionMapper extends BaseMapper<Question> {}

BlackListMapper

public interface BlackListMapper extends BaseMapper<BlackList> {}

api接口
SettingApi

public interface SettingsApi {
    Settings getSettingsByUserId(Long userId);
    void save(Settings settings);
    void update(Settings settings);
}

QuestionApi

public interface QuestionApi {
    Question getQuestionByUserId(Long userId);
    void save(Question question);
    void update(Question question);
}

BlackListApi

public interface BlackListApi {
    IPage<UserInfo> getBlackList(Long userId, int page, int size);
    void removeUserFromBlackList(Long userId, Long uid);
}

api服务实现类
SettingServiceImpl

@DubboService
public class SettingsApiImpl implements SettingsApi{
    @Resource
    private SettingsMapper settingsMapper;
    @Override
    public Settings getSettingsByUserId(Long userId) {
        LambdaQueryWrapper<Settings> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Settings::getUserId, userId);
        return this.settingsMapper.selectOne(queryWrapper);
    }

    @Override
    public void save(Settings settings) {
        this.settingsMapper.insert(settings);
    }

    @Override
    public void update(Settings settings) {
        this.settingsMapper.updateById(settings);
    }
}

QuestionServiceImpl

@DubboService
public class QuestionApiImpl implements QuestionApi {
    @Resource
    private QuestionMapper questionMapper;

    @Override
    public Question getQuestionByUserId(Long userId) {
        LambdaQueryWrapper<Question> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Question::getUserId, userId);
        return this.questionMapper.selectOne(queryWrapper);
    }

    @Override
    public void save(Question question) {
        this.questionMapper.insert(question);
    }

    @Override
    public void update(Question question) {
        this.questionMapper.updateById(question);
    }
}

BlackListServiceImpl

@DubboService
public class BlackListApiImpl implements BlackListApi{
    @Resource
    private BlackListMapper blackListMapper;

    @Override
    public IPage<UserInfo> getBlackList(Long userId, int page, int size) {
        Page pageInfo = new Page(page, size);

        return this.blackListMapper.getBlackList(userId, pageInfo);
    }

    @Override
    public void removeUserFromBlackList(Long userId, Long uid) {
        LambdaQueryWrapper<BlackList> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(BlackList::getBlackUserId, uid);
        queryWrapper.eq(BlackList::getUserId, userId);
        this.blackListMapper.delete(queryWrapper);
    }
}

1.3 查询用户所有设置信息

1.3.1 功能描述

当用户点击通用设置后,我们需要根据用户的id查询到用户的所有设置信息,包括手机号,陌生人问题和通知设置。接口如下

【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第3张图片
【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第4张图片

1.3.2 代码实现

为了封装返回值的数据,我们需要创建一个VO对象

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SettingsVo implements Serializable {

    private Long id;
    private String strangerQuestion = "";
    private String phone;
    private Boolean likeNotification = true;
    private Boolean pinglunNotification = true;
    private Boolean gonggaoNotification = true;
}

在Controller中添加一个方法处理查询用户通用设置的请求

@GetMapping("/settings")
public ResponseEntity getSettings() {
    // 1. 调用service查询设置信息,返回VO对象
    SettingsVo settingsVo = this.settingsService.getSettings();
    // 2. 返回结果
    return ResponseEntity.ok(settingsVo);
}

在Service中创建方法查询用户通用设置

public SettingsVo getSettings() {
    SettingsVo settingsVo = new SettingsVo();
    // 1. 获取到用户的id
    settingsVo.setId(UserHolder.getUserId());
    // 2. 获取到用户的手机号
    settingsVo.setPhone(UserHolder.getUserPhone());
    // 3. 根据用户id查询陌生人问题
    Question question = this.questionApi.getQuestionByUserId(UserHolder.getUserId());
    String txt = question == null ? "用户没有设置陌生人问题" : question.getTxt();
    settingsVo.setStrangerQuestion(txt);
    // 4. 查询设置开关状态
    Settings settings = this.settingsApi.getSettingsByUserId(UserHolder.getUserId());
    if (settings != null) {
        settingsVo.setLikeNotification(settings.getLikeNotification());
        settingsVo.setPinglunNotification(settings.getPinglunNotification());
        settingsVo.setGonggaoNotification(settings.getGonggaoNotification());
    }
    return settingsVo;
}

1.4 陌生人问题

1.4.1 功能描述

对数据库表进行操作:如果存在数据,更新数据库。如果不存在数据,保存数据库表数据。接口如下:

【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第5张图片

1.4.2 代码实现

在Controller中添加方法

@PostMapping("/questions")
public ResponseEntity saveOrUpdateQuestion(@RequestBody Map map) {
    // 1. 获取参数
    String content = map.get("content").toString();
    // 2. 调用service
    this.settingsService.saveOrUpdateQuestion(content);
    // 3. 返回结果
    return ResponseEntity.ok(null);
}

在Service实现业务逻辑

public void saveOrUpdateQuestion(String content) {
    // 1. 获取用户ID
    Long userId = UserHolder.getUserId();
    // 2. 判断用户是否设置过问题 如果设置过 则为更新 否则为新增
    Question question = this.questionApi.getQuestionByUserId(userId);
    if (question == null) {
        // 新增问题
        question.setUserId(userId);
        question.setTxt(content);
        this.questionApi.save(question);
    } else {
        // 更新问题
        question.setTxt(content);
        this.questionApi.update(question);
    }
}

1.5 通知设置

1.5.1 功能描述

用户可以设置是否有消息提示,如下图所示
【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第6张图片
接口如图所示:
【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第7张图片

1.5.2 代码实现

Controller

/**
 * 更改通知设置
 *
 * @param map
 * @return
 */
@PostMapping("notifications/setting")
public ResponseEntity saveOrUpdateSettings(@RequestBody Map map) {
    // 1. 调用service
    this.settingsService.saveOrUpdateSettings(map);
    // 2. 返回数据
    return ResponseEntity.ok(null);
}

Service

public void saveOrUpdateSettings(Map map) {
    // 0. 解析数据
    Boolean likeNotification = (Boolean) map.get("likeNotification");
    Boolean pinglunNotification = (Boolean) map.get("pinglunNotification");
    Boolean gonggaoNotification = (Boolean) map.get("gonggaoNotification");
    // 1. 获取用户id
    Long userId = UserHolder.getUserId();
    // 2. 查询用户是否有过设置 如果没有 则新增 否则则更新
    Settings settings = this.settingsApi.getSettingsByUserId(userId);
    if (settings == null) {
        settings = new Settings();
        settings.setUserId(userId);
        settings.setLikeNotification(likeNotification);
        settings.setPinglunNotification(pinglunNotification);
        settings.setGonggaoNotification(gonggaoNotification);
        this.settingsApi.save(settings);
    } else {
        settings.setLikeNotification(likeNotification);
        settings.setPinglunNotification(pinglunNotification);
        settings.setGonggaoNotification(gonggaoNotification);
        this.settingsApi.update(settings);
    }
}

1.6 查询黑名单

1.6.1 功能分析

根据用户id在黑名单表中查询用户,需要注意的是,从黑名单表格中查询到的只是userId,我们需要联表查询得到用户信息。此外,在黑名单界面,用户可以点击取消黑名单从而将制定用户从黑名单中移除。接口如下
【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第8张图片
【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第9张图片
【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第10张图片

1.6.2 代码实现

由于前端对分页数据返回值的要求,这里没有直接使用Mybatis Plus的分页对象,而是自己创建了一个VO类用来封装分页后的相关数据

@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {

    private Integer counts = 0;//总记录数
    private Integer pagesize;//页大小
    private Integer pages = 0;//总页数
    private Integer page;//当前页码
    private List<?> items = Collections.emptyList(); //列表

    public PageResult(Integer page,Integer pagesize,
                      int counts,List list) {
        this.page = page;
        this.pagesize = pagesize;
        this.items = list;
        this.counts = counts;
        this.pages = counts % pagesize == 0 ? counts / pagesize : counts / pagesize + 1;
    }
}

Controller

/**
 * 分页查询当前那用户的黑名单
 *
 * @param page
 * @param size
 * @return
 */
@GetMapping("/blacklist")
public ResponseEntity getBlackList(@RequestParam(defaultValue = "1") int page,
                                   @RequestParam(defaultValue = "5") int size) {
    // 1. 调用service查询
    PageResult pageResult = this.settingsService.getBlackList(page, size);
    // 2. 返回结果
    return ResponseEntity.ok(pageResult);
}

/**
 * 移除黑名单
 * @param uid
 * @return
 */
@DeleteMapping("/blacklist/{uid}")
public ResponseEntity removeUserFromBlackList(@PathVariable(name = "uid") Long uid) {
    // 1.调用service方法移除童虎
    this.settingsService.removeUserFromBlackList(uid);
    return ResponseEntity.ok(null);
}

Service

public PageResult getBlackList(int page, int size) {
    // 1. 获取到用户id
    Long userId = UserHolder.getUserId();
    // 2. 调用api查询
    IPage<UserInfo> iPage = this.blackListApi.getBlackList(userId, page, size);
    // 3. 解析page对象成PageResult
    return new PageResult(page, size, (int) iPage.getTotal(), iPage.getRecords());

}

public void removeUserFromBlackList(Long uid) {
    // 1. 获取当前用户id
    Long userId = UserHolder.getUserId();
    // 2. 调用api删除
    this.blackListApi.removeUserFromBlackList(userId, uid);
}

实现联表查询,我们自己写了一个连表查询的SQL语句

public interface BlackListMapper extends BaseMapper<BlackList> {
    @Select("select * from tb_user_info where id in (\n" +
            "  SELECT black_user_id FROM tb_black_list where user_id=#{userId}\n" +
            ")")
    IPage<UserInfo> getBlackList(@Param("userId") Long userId, @Param("pages") Page pageInfo);
}

此外,为了实现分页功能,还需要开启Mybatis Plus的分页查询功能,因此需要在引导类中添加相关配置

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
}

1.6 更换手机号

1.6.1 功能分析

探花交友APP允许修改已注册用户的手机号,修改分为以下三个步骤

  • 用户点击获取验证码向原来的手机号发送验证码
  • 用户输入完毕验证码后进行校验
  • 校验通过后用户输入新的手机号,并将手机号保存到数据库中

接口如下

【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第11张图片
【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第12张图片

【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第13张图片

1.6.2 代码编写

Controller

/**
 * 修改手机号发送验证码
 * @return
 */
@PostMapping("/phone/sendVerificationCode")
public ResponseEntity sendVerificationCode() {
    // 1. 获取登录用户的手机号
    String userPhone = UserHolder.getUserPhone();
    // 2. 调用Service方法
    this.userService.sendMsg(userPhone);
    // 3. 返回结果
    return ResponseEntity.ok(null);
}
/**
 * 判断用户输入的验证码是否正确
 * @param map 验证码
 * @return 返回布尔类型,true - 正确 false - 错误
 */
@PostMapping("/phone/checkVerificationCode")
public ResponseEntity checkVerificationCode(@RequestBody Map map) {
    String code = map.get("verificationCode").toString();
    String userPhone = UserHolder.getUserPhone();
    boolean verification = this.userService.checkVerificationCode(code, userPhone);
    return ResponseEntity.ok(false);
}
/**
 * 更改手机号
 * @param phone 新手机号
 * @return 空
 */
@PostMapping("/phone")
public ResponseEntity updatePhone(String phone) {
    Long userId = UserHolder.getUserId();
    this.userService.updatePhone(phone, userId);
    return ResponseEntity.ok(null);
}

Service

public void sendMsg(String phone) {
    // 1. 生成验证码
    // String code = RandomStringUtils.randomNumeric(6);
    // 2.调用发送验证码的方法
    // emailTemplate.sendCode(phone, code);
    // 3.将验证码存入redis
    String code = "123456"; // 方便测试将验证码写死
    redisTemplate.opsForValue().set(VERIFICATION_CODE_PREFIX + phone, code, Duration.ofMinutes(5));
}
public boolean checkVerificationCode(String code, String userPhone) {
    // 1.从Redis中获取到验证码
    String redisCode = this.redisTemplate.opsForValue().get(VERIFICATION_CODE_PREFIX + userPhone);
    // 2.比较验证码
    if (StringUtils.isEmpty(redisCode) || !redisCode.equals(code)) {
        // 验证码无效或者验证码错误
        throw new BusinessException(ErrorResult.loginError());
    }
    return true;
}

public void updatePhone(String phone, Long userId) {
    this.userApi.updatePhone(phone, userId);
}

Api

@Override
public void updatePhone(String phone, Long userId) {
    User user = new User();
    user.setId(userId);
    user.setMobile(phone);
    this.userMapper.updateById(user);
}

2. MongoDB入门

在探花交友APP中,会涉及很多的数据保存,比如用户的动态,点赞信息,评论信息,位置信息等。这些数据有一下的特点:

  • 海量数据
  • 读多写少
  • 数据价值低
  • 地址位置相关数据
  • 更新十分频繁

针对上述特点,传统的MySQL数据库有些力不从心,因此,我们引入了MongoDB。

2.1 MongoDB简介

MongoDB:是一个高效的非关系型数据库(不支持表关系:只能操作单表)
【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第14张图片
MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。

MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的,它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。

MongoDB最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

2.1.1 MongoDB的存储模型

【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第15张图片
MongoDB采用的是内存和磁盘结合的存储方式,比较常用的数据都保存在内存中,如果内存中找不到数据,再去磁盘中查找,这样就加快了查询的速度。

为了保证数据的安全性,内存中的数据每一分钟都会同步到磁盘中。
此外,为了更加保护数据的安全性,MongoDB在新版本中引入了日志,日志在内存和硬盘中各一份,每10ms同步一次。这样即使断电的时候,内存中的数据没有保存到磁盘,也可以通过日志文件进行恢复,从而保证了数据的安全性。

但是即使这样,也不能MongoDB不能够保证数据100%不丢失。

【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第16张图片
此外,MongoDB支持数据分页技术,可以将保存的数据进行分页,从而可以非常方便的进行存储容量的扩充

2.1.2 MongoDB与其他数据库对比

  • 和Redis对比
    • Redis是纯内存数据库,存储容量有限
    • Redis支持的查询比较简单,而MongoDB支持结构化查询
  • 和MySQL对比
    • MongoDB不支持事务和联表查询
    • MongoDB支持动态字段,MySQL数据表一旦创建,很难修改字段
  • 查询效率
    • Redis > MongoDB > MySQL

2.1.3 MongoDB的应用

  • 游戏装备数据
    • 特征:修改频率很高
  • 物流行业数据
    • 特征:地理位置信息,海量数据
  • 直播打赏数据
    • 特征:数据量很大,修改频繁
  • 日志数据
    • 特征:数据量大,结构多变

2.2 MongoDB的数据和体系结构

下面是MongoDB和MySQL数据库概念上的对比
【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第17张图片
MongoDB中存储BSON数据,类似JSON
【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第18张图片
MongoDB 的逻辑结构是一种层次结构。主要由: 文档(document)、集合(collection)、数据库(database)这三部分组成的。逻辑结构是面 向用户的,用户使用 MongoDB 开发应用程序使用的就是逻辑结构。

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

【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第19张图片
为了更好的理解,下面与SQL中的概念进行对比:

SQL术语/概念 MongoDB术语/概念 解释/说明
database database 数据库
table collection 数据库表/集合
row document 表中的一条数据
column field 数据字段/域
index index 索引
table joins 表连接,MongoDB不支持
primary key primary key 主键,MongoDB自动将_id字段设置为主键

MongoDB中常用的数据结构如下:

MongoDB中常见的数据类型有:

  • 数据格式:BSON {aa:bb}
  • null:用于表示空值或者不存在的字段,{“x”:null}
  • 布尔型:布尔类型有两个值true和false,{“x”:true}
  • 数值:shell默认使用64为浮点型数值。{“x”:3.14}或{“x”:3}。对于整型值,可以使用 NumberInt(4字节符号整数)或NumberLong(8字节符号整数), {“x”:NumberInt(“3”)}{“x”:NumberLong(“3”)}
  • 字符串:UTF-8字符串都可以表示为字符串类型的数据,{“x”:“呵呵”}
  • 日期:日期被存储为自新纪元依赖经过的毫秒数,不存储时区,{“x”:new Date()}
  • 正则表达式:查询时,使用正则表达式作为限定条件,语法与JavaScript的正则表达式相 同,{“x” : /[abc]/}
  • 数组:数据列表或数据集可以表示为数组,{“x”: [“a“,“b”,”c”]}
  • 内嵌文档:文档可以嵌套其他文档,被嵌套的文档作为值来处理,{“x”:{“y”:3 }}
  • 对象Id:对象id是一个12字节的字符串,是文档的唯一标识,{“x”: objectId() }
  • 二进制数据:二进制数据是一个任意字节的字符串。它不能直接在shell中使用。如果要 将非utf-字符保存到数据库中,二进制数据是唯一的方式。

2.3 MongDB的简单操作操作

2.3.1 新增数据

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

#插入数据
#语法:db.表名.insert(json字符串)
db.user.insert({id:1,name:'zhangsan'})  #插入数据

2.3.2 更新数据

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

db.collection.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,就把按条件查出来多条记录全部更新。
  • writeConcern :可选,抛出异常的级别。
#更新数据
> db.user.update({id:1},{$set:{age:22}}) 

#注意:如果这样写,会删除掉其他的字段
> db.user.update({id:1},{age:25})

#更新不存在的字段,会新增字段
> db.user.update({id:2},{$set:{sex:1}}) #更新数据

#更新不存在的数据,默认不会新增数据
> db.user.update({id:3},{$set:{sex:1}})

#如果设置第一个参数为true,就是新增数据
> db.user.update({id:3},{$set:{sex:1}},true)

2.3.3 删除数据

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

db.collection.remove(
   <query>,
   {
     justOne: <boolean>,
     writeConcern: <document>
   }
)

参数说明:

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

实例:

#删除数据
> db.user.remove({})

#插入4条测试数据
db.user.insert({id:1,username:'zhangsan',age:20})
db.user.insert({id:2,username:'lisi',age:21})
db.user.insert({id:3,username:'wangwu',age:22})
db.user.insert({id:4,username:'zhaoliu',age:22})

> db.user.remove({age:22},true)

#删除所有数据
> db.user.remove({})

2.3.4 查询数据

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

db.user.find([query],[fields])
  • query :可选,使用查询操作符指定查询条件
  • fields :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。
    条件查询:
操作 格式 范例 RDBMS中的类似语句
等于 {:} db.col.find({"by":"Jack"}).pretty() where by = 'Jack'
小于 {:{$lt:}} db.col.find({"likes":{$lt:50}}).pretty() where likes < 50
小于或等于 {:{$lte:}} db.col.find({"likes":{$lte:50}}).pretty() where likes <= 50
大于 {:{$gt:}} db.col.find({"likes":{$gt:50}}).pretty() where likes > 50
大于或等于 {:{$gte:}} db.col.find({"likes":{$gte:50}}).pretty() where likes >= 50
不等于 {:{$ne:}} db.col.find({"likes":{$ne:50}}).pretty() where likes != 50

实例:

#插入测试数据
db.user.insert({id:1,username:'zhangsan',age:20})
db.user.insert({id:2,username:'lisi',age:21})
db.user.insert({id:3,username:'wangwu',age:22})
db.user.insert({id:4,username:'zhaoliu',age:22})

db.user.find()  #查询全部数据
db.user.find({},{id:1,username:1})  #只查询id与username字段
db.user.find().count()  #查询数据条数
db.user.find({id:1}) #查询id为1的数据
db.user.find({age:{$lte:21}}) #查询小于等于21的数据
db.user.find({$or:[{id:1},{id:2}]}) #查询id=1 or id=2

#分页查询:Skip()跳过几条,limit()查询条数
db.user.find().limit(2).skip(1)  #跳过1条数据,查询2条数据
db.user.find().sort({id:-1}) #按照id倒序排序,-1为倒序,1为正序

2.6、索引

索引能够极大提高查询的效率。类似于MySQL,MongoDB中叶提供了索引支持。在没有索引的时候,MongoDB需要便利整个集合,查询效率很低。
为MongoDB创建索引的命令如下,为age域设置一个递增的索引

#创建索引
#说明:1表示升序创建索引,-1表示降序创建索引。
> db.user.createIndex({'age':1})

3. Springboot整合MongoDB

Spring-data对MongoDB做了支持,使用spring-data-mongodb可以简化MongoDB的操作,封装了底层的mongodb-driver。在Springboot中使用MongoDB的步骤如下:

  • 导入依赖
  • 编写配置信息
  • 编写集合实体类
  • 注入MongoTemplate对象,完成CRUD操作

导入依赖

<dependency>
     <groupId>org.springframework.bootgroupId>
     <artifactId>spring-boot-starter-data-mongodbartifactId>
dependency>
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-testartifactId>
    <scope>testscope>
dependency>

编写application.yml

spring:
  data:
    mongodb:
      uri: mongodb://192.168.136.160:27017/test

编写集合对应的实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(value="tb_person")
public class Person {
    @Id
    private ObjectId id;
    @Field("myname")
    private String name;
    private int age;
    private String address;
}
  • 只要是MongoDB的集合实体类,都需要添加@Document(value="XXX")注解,在MongoDB中,集合的名称就为XXX
  • 在实体类中,需要指定MongoDB的文档的ID,这个ID由MongoDB自动生成,不会重复。在ID字段上使用@Id指定。ID的类型应为ObjectId
  • 可以通过@Field("myname")来指定保存集合的域的名称,如果不指定,默认就以变量面作为域名

引入MongoTemplate

@Resource
private MongoTemplate mongoTemplate;

下面编写了一个测试类,来以Java代码的方式对MongoDB进行CRUD操作

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MongoApplication.class)
public class MongoTest {

    @Resource
    private MongoTemplate mongoTemplate;

    @Test
    public void testInsert() {
        // 插入一条数据
        Person person = new Person();
        person.setAddress("上海");
        person.setAge(23);
        person.setName("Robert");
        this.mongoTemplate.save(person);
    }

    @Test
    public void testFindAll() {
        // 查询所有数据
        List<Person> all = this.mongoTemplate.findAll(Person.class);
        all.forEach(System.out::println);
    }

    @Test
    public void testFindByCondition() {
        // 根据条件查询
        Criteria criteria = Criteria.where("age")
                .is(23).and("myname").is("Robert");
        Query query = new Query(criteria);
        List<Person> people = this.mongoTemplate.find(query, Person.class);
        people.forEach(System.out::println);
    }

    @Test
    public void testFindPage() {
        int page = 2;
        int size = 5;
        // 条件分页
        Criteria criteria = Criteria.where("age").lt(55);
        Query query = new Query(criteria);
        query.skip((page - 1) * size).limit(size)
                .with(Sort.by(Sort.Order.desc("age")));
        List<Person> people = this.mongoTemplate.find(query, Person.class);
        people.forEach(System.out::println);
    }

    @Test
    public void testUpdate() {
        Query query = new Query(Criteria.where("id").is("63a431e998e6c17444113fc9"));
        // updateFirst是更新满足条件的第一个记录
        // 设置要更新的内容
        Update update = new Update();
        update.set("myname", "Jack");
        this.mongoTemplate.updateFirst(query, update, Person.class);
    }

    @Test
    public void testDelete() {
        // 根据条件删除
        Query query = new Query(Criteria.where("myname").is("Jack"));
        this.mongoTemplate.remove(query, Person.class);
    }
}

4. 今日佳人功能

学习完MongoDB的使用以后,我们来实现一个功能用来巩固MongoDB的使用。
在用户登录成功后,就会进入首页,首页中有今日佳人、推荐好友、探花、搜附近等功能。

今日佳人,会推荐缘分值最大的用户,进行展现出来。缘分值的计算是由用户的行为进行打分,如:点击、点赞、评论、学历、婚姻状态等信息组合而成的。

实现:我们先不考虑推荐的逻辑,假设现在已经有推荐的结果,我们只需要从结果中查询到缘分值最高的用户就可以了。至于推荐的逻辑以及实现,我们将后面的课程中讲解。

流程:
【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第20张图片

4.1 表结构设计

#表结构  recommend_user
{
  "userId":1001,  #推荐的用户id
  "toUserId":1002, #用户id
  "score":90,  #推荐得分
  "date":"2019/1/1" #日期
}

在MongoDB中只存储用户的id数据,其他的数据需要通过接口查询。
其中score为缘分分数,我们要选择缘分分数最大的。此外,我们是将userId推荐给toUserId
假设userId=1001,toUserId=106,那么应该是将1001推荐给106。

4.2 接口

今日佳人功能的接口如下:

【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第21张图片
【探花交友DAY 04】用户设置、MongoDB使用、今日佳人_第22张图片

4.3 代码实现

编写实体类

@AllArgsConstructor
@NoArgsConstructor
@Data
@Document(collection = "recommend_user")
public class RecommendUser implements Serializable {
    @Id
    private ObjectId id; //主键id
    private Long userId; //推荐的用户id
    private Long toUserId; //用户id
    private Double score =0d; //推荐得分
    private String date; //日期
}

编写API

public interface RecommendUserApi {
    RecommendUser getTodayBest(Long userId);
}
@DubboService
public class RecommendUserApiImpl implements RecommendUserApi {
    @Resource
    private MongoTemplate mongoTemplate;
    @Override
    public RecommendUser getTodayBest(Long userId) {
        // 根据toUserId字段查询,然后根据分数排序,取分数最高的一个
        Criteria criteria = Criteria.where("toUserId").is(userId);
        Query query = new Query().with(Sort.by(Sort.Order.desc("score"))).limit(1);
        return mongoTemplate.findOne(query, RecommendUser.class);
    }
}

注意:这里我们值选择分数最高的一个可以利用分页查询,pagesize为1,然后获取第一页

Controller

@RestController
@RequestMapping("/tanhua")
public class TanhuaController {

    @Resource
    private TanhuaService tanhuaService;

    /**
     * 查询今日佳人
     *
     * @return
     */
    @GetMapping("/todayBest")
    public ResponseEntity getTodayBest() {
        // 1. 调用service方法查询今日佳人
        TodayBest vo = this.tanhuaService.getTodayBest();
        // 2. 将结果返回
        return ResponseEntity.ok(vo);
    }
}

Service

@Service
public class TanhuaService {

    @DubboReference
    private RecommendUserApi recommendUserApi;
    @DubboReference
    private UserInfoApi userInfoApi;

    public TodayBest getTodayBest() {
        // 1. 获取当前用户
        Long userId = UserHolder.getUserId();
        // 2. 调用api查询
        RecommendUser recommendUser = this.recommendUserApi.getTodayBest(userId);
        // 如果佳人不存在,则设置一个默认值
        if (recommendUser == null) {
            recommendUser = new RecommendUser();
            recommendUser.setUserId(1L);
            recommendUser.setScore(100.0);
        }
        // 3. 将返回数据封装成vo对象
        UserInfo userInfo = this.userInfoApi.getUserInfoById(recommendUser.getUserId());
        return TodayBest.init(userInfo, recommendUser);

    }
}

注意这里有可能查不到有缘人,因此需要判断一下并设置一个默认值

4.4 解决MongoDB启动bug

在项目中,添加了mongo的依赖的话,springboot就会自动去连接本地的mongo,由于他连接不上会导致出错。

解决有2种方案:

  • 排除掉mongo的依赖

  • springboot中添加排除自动配置的注解

@SpringBootApplication(exclude = {
        MongoAutoConfiguration.class,
        MongoDataAutoConfiguration.class
}) //排除mongo的自动配置
public class TanhuaServerApplication {
    public static void main(String[] args) {
       SpringApplication.run(TanhuaServerApplication.class,args);
    }
}

你可能感兴趣的:(探花交友项目,MongoDB,交友,数据库)