课程说明

  • 抽取common工程
  • 圈子功能说明
  • 圈子技术实现
  • 圈子技术方案
  • 圈子实现发布动态
  • 圈子实现好友动态
  • 圈子实现推荐动态

    1、抽取common工程

    在项目中一般需要将公用的对象进行抽取放到common工程中,其他的工程依赖此工程即可。下面我们将sso以及server工程中的公用的对象进行抽取。

    1.1、创建my-tanhua-common工程

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    5. <parent>
    6. <artifactId>my-tanhua</artifactId>
    7. <groupId>cn.itcast.tanhua</groupId>
    8. <version>1.0-SNAPSHOT</version>
    9. </parent>
    10. <modelVersion>4.0.0</modelVersion>
    11. <artifactId>my-tanhua-common</artifactId>
    12. <dependencies>
    13. <dependency>
    14. <groupId>org.projectlombok</groupId>
    15. <artifactId>lombok</artifactId>
    16. </dependency>
    17. <dependency>
    18. <groupId>com.baomidou</groupId>
    19. <artifactId>mybatis-plus-boot-starter</artifactId>
    20. </dependency>
    21. <dependency>
    22. <groupId>com.fasterxml.jackson.core</groupId>
    23. <artifactId>jackson-databind</artifactId>
    24. </dependency>
    25. </dependencies>
    26. </project>

    1.2、通用枚举

    将SexEnum枚举移动至common工程,并且后续创建的枚举也要放到次工程中,以达到公用的目的。
    1. package com.tanhua.common.enums;
    2. import com.baomidou.mybatisplus.core.enums.IEnum;
    3. public enum SexEnum implements IEnum<Integer> {
    4. MAN(1,"男"),
    5. WOMAN(2,"女"),
    6. UNKNOWN(3,"未知");
    7. private int value;
    8. private String desc;
    9. SexEnum(int value, String desc) {
    10. this.value = value;
    11. this.desc = desc;
    12. }
    13. @Override
    14. public Integer getValue() {
    15. return this.value;
    16. }
    17. @Override
    18. public String toString() {
    19. return this.desc;
    20. }
    21. }
    需要修改server与sso工程中的application.properties配置:
    1. # 枚举包扫描
    2. mybatis-plus.type-enums-package=com.tanhua.common.enums
    将server与sso工程中的SexEnum对象删除以及将相关的类引用进行修改。

    1.3、抽取mapper

    需要将UserInfoMapper以及UserMapper放置到common工程的com.tanhua.common.mapper包下。
    1. package com.tanhua.common.mapper;
    2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    3. import com.tanhua.common.pojo.User;
    4. public interface UserMapper extends BaseMapper<User> {
    5. }
    1. package com.tanhua.common.mapper;
    2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    3. import com.tanhua.common.pojo.UserInfo;
    4. public interface UserInfoMapper extends BaseMapper<UserInfo> {
    5. }

    说明:抽取完成后,需要将原工程的代码删除以及修改其他代码中引入的依赖。

1.4、抽取pojo

将BasePojo、User、UserInfo移动至common工程:
day04-圈子功能实现 - 图1

1.5、抽取utils

将server工程的utils进行抽取公用,后续的工具类也放置到common工程中。
day04-圈子功能实现 - 图2

抽取完成后进行测试,确保可以正常启动以及功能都正常。

2、圈子功能

2.1、功能说明

探花交友项目中的圈子功能,类似微信的朋友圈,基本的功能为:发布动态、浏览好友动态、浏览推荐动态、点赞、评论、喜欢等功能。
day04-圈子功能实现 - 图3
发布:
day04-圈子功能实现 - 图4

2.2、实现方案分析

对于圈子功能的实现,我们需要对它的功能特点做分析:

  • 数据量会随着用户数增大而增大
  • 读多写少,一般而言,浏览朋友圈动态会多一些,发动态相对就会少一些
  • 非好友看不到其动态内容
  • ……

针对以上特点,我们来分析一下:

  • 对于数据量大而言,显然不能够使用关系型数据库进行存储,我们需要通过MongoDB进行存储
  • 对于读多写少的应用,尽可能的减少读取数据的成本
    • 比如说,一条SQL语句,单张表查询一定比多张表查询要快
    • 条件越多的查询速度将越慢,尽可能的减少条件以提升查询速度

所以对于存储而言,主要是核心的4张表:

  • 发布表:记录了所有用户的发布的东西信息,如图片、视频等。
  • 相册:相册是每个用户独立的,记录了该用户所发布的所有内容。
  • 评论:针对某个具体发布的朋友评论和点赞操作。
  • 时间线:所谓“刷朋友圈”,就是刷时间线,就是一个用户所有的朋友的发布内容。

流程:
day04-圈子功能实现 - 图5
流程说明:

  • 用户发布动态,动态中一般包含了图片和文字,图片上传到阿里云,上传成功后拿到图片地址,将文字和图片地址进行持久化存储
  • 首先,需要将动态数据写入到发布表中,其次,再写入到自己的相册表中,需要注意的是,相册表中只包含了发布id,不会冗余存储发布数据
  • 最后,需要将发布数据异步的写入到好友的时间线表中,之所以考虑异步操作,是因为希望发布能够尽快给用户反馈,发布成功
  • 好友刷朋友圈时,实际上只需要查询自己的时间线表即可,这样最大限度的提升了查询速度,再配合redis的缓存,那速度将是飞快的
  • 用户在对动态内容进行点赞、喜欢、评论操作时,只需要写入到评论表即可,该表中也是只会记录发布id,并不会冗余存储发布数据

    2.3、表结构设计

    发布表:

  1. #表名:quanzi_publish
  2. {
  3. "_id":"5fae53d17e52992e78a3db61",#主键id
  4. "pid":1001, #发布idLong类型)
  5. "userId":1, #用户id
  6. "text":"今天心情很好", #文本内容
  7. "medias":"http://xxxx/x/y/z.jpg", #媒体数据,图片或小视频 url
  8. "seeType":1, #谁可以看,1-公开,2-私密,3-部分可见,4-不给谁看
  9. "seeList":[1,2,3], #部分可见的列表
  10. "notSeeList":[4,5,6],#不给谁看的列表
  11. "longitude":108.840974298098,#经度
  12. "latitude":34.2789316522934,#纬度
  13. "locationName":"上海市浦东区", #位置名称
  14. "created",1568012791171 #发布时间
  15. }

相册表:

  1. #表名:quanzi_album_{userId}
  2. {
  3. "_id":"5fae539d7e52992e78a3b684",#主键id
  4. "publishId":"5fae53d17e52992e78a3db61", #发布id
  5. "created":1568012791171 #发布时间
  6. }

时间线表:

  1. #表名:quanzi_time_line_{userId}
  2. {
  3. "_id":"5fae539b7e52992e78a3b4ae",#主键id,
  4. "userId":2, #好友id
  5. "publishId":"5fae53d17e52992e78a3db61", #发布id
  6. "date":1568012791171 #发布时间
  7. }

评论表:

  1. #表名:quanzi_comment
  2. {
  3. "_id":"5fae539d7e52992e78a3b648", #主键id
  4. "publishId":"5fae53d17e52992e78a3db61", #发布id
  5. "commentType":1, #评论类型,1-点赞,2-评论,3-喜欢
  6. "content":"给力!", #评论内容
  7. "userId":2, #评论人
  8. "publishUserId":9, #发布动态的人的id
  9. "isParent":false, #是否为父节点,默认是否
  10. "parentId":1001, #父节点id
  11. "created":1568012791171
  12. }

3、好友关系数据

由于圈子中会涉及的好友关系数据,虽然现在主线是开发圈子功能,但是也需要对于好友关系有所了解,在我们提供的Mongodb数据库中有一些mock数据。
好友关系结构:

  1. package com.tanhua.dubbo.server.pojo;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. import org.bson.types.ObjectId;
  6. import org.springframework.data.mongodb.core.mapping.Document;
  7. @Data
  8. @NoArgsConstructor
  9. @AllArgsConstructor
  10. @Document(collection = "tanhua_users")
  11. public class Users implements java.io.Serializable{
  12. private static final long serialVersionUID = 6003135946820874230L;
  13. private ObjectId id;
  14. private Long userId; //用户id
  15. private Long friendId; //好友id
  16. private Long date; //时间
  17. }

在mock数据中,为每个用户构造了10个好友数据:
day04-圈子功能实现 - 图6

4、查询好友动态

查询好友动态与查询推荐动态显示的结构是一样的,只是其查询数据源不同:
day04-圈子功能实现 - 图7

4.1、基础代码

在my-tanhua-dubbo-interface中编写:

  1. package com.tanhua.dubbo.server.pojo;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. import org.bson.types.ObjectId;
  6. import org.springframework.data.annotation.Id;
  7. import org.springframework.data.mongodb.core.mapping.Document;
  8. import java.util.Date;
  9. import java.util.List;
  10. /**
  11. * 发布表,动态内容
  12. */
  13. @Data
  14. @NoArgsConstructor
  15. @AllArgsConstructor
  16. @Document(collection = "quanzi_publish")
  17. public class Publish implements java.io.Serializable {
  18. private static final long serialVersionUID = 8732308321082804771L;
  19. @Id
  20. private ObjectId id; //主键id
  21. private Long pid; //发布id
  22. private Long userId; //发布用户id
  23. private String text; //文字
  24. private List<String> medias; //媒体数据,图片或小视频 url
  25. private Integer seeType; // 谁可以看,1-公开,2-私密,3-部分可见,4-不给谁看
  26. private List<Long> seeList; //部分可见的列表
  27. private List<Long> notSeeList; //不给谁看的列表
  28. private String longitude; //经度
  29. private String latitude; //纬度
  30. private String locationName; //位置名称
  31. private Long created; //发布时间
  32. }
  1. package com.tanhua.dubbo.server.pojo;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. import org.bson.types.ObjectId;
  6. import org.springframework.data.annotation.Id;
  7. import org.springframework.data.mongodb.core.mapping.Document;
  8. import java.util.Date;
  9. /**
  10. * 相册表,用于存储自己发布的数据,每一个用户一张表进行存储
  11. */
  12. @Data
  13. @NoArgsConstructor
  14. @AllArgsConstructor
  15. @Document(collection = "quanzi_album_{userId}")
  16. public class Album implements java.io.Serializable {
  17. private static final long serialVersionUID = 432183095092216817L;
  18. @Id
  19. private ObjectId id; //主键id
  20. private ObjectId publishId; //发布id
  21. private Long created; //发布时间
  22. }
  1. package com.tanhua.dubbo.server.pojo;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. import org.bson.types.ObjectId;
  6. import org.springframework.data.annotation.Id;
  7. import org.springframework.data.mongodb.core.mapping.Document;
  8. import java.util.Date;
  9. /**
  10. * 时间线表,用于存储发布的数据,每一个用户一张表进行存储
  11. */
  12. @Data
  13. @NoArgsConstructor
  14. @AllArgsConstructor
  15. @Document(collection = "quanzi_time_line_{userId}")
  16. public class TimeLine implements java.io.Serializable {
  17. private static final long serialVersionUID = 9096178416317502524L;
  18. @Id
  19. private ObjectId id;
  20. private Long userId; // 好友id
  21. private ObjectId publishId; //发布id
  22. private Long date; //发布的时间
  23. }
  1. package com.tanhua.dubbo.server.pojo;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. import org.bson.types.ObjectId;
  6. import org.springframework.data.annotation.Id;
  7. import org.springframework.data.mongodb.core.mapping.Document;
  8. import java.util.Date;
  9. /**
  10. * 评论表
  11. */
  12. @Data
  13. @NoArgsConstructor
  14. @AllArgsConstructor
  15. @Document(collection = "quanzi_comment")
  16. public class Comment implements java.io.Serializable{
  17. private static final long serialVersionUID = -291788258125767614L;
  18. @Id
  19. private ObjectId id;
  20. private ObjectId publishId; //发布id
  21. private Integer commentType; //评论类型,1-点赞,2-评论,3-喜欢
  22. private String content; //评论内容
  23. private Long userId; //评论人
  24. private Long publishUserId; //发布动态的用户id
  25. private Boolean isParent = false; //是否为父节点,默认是否
  26. private ObjectId parentId; // 父节点id
  27. private Long created; //发表时间
  28. }

4.2、dubbo服务

圈子的具体业务逻辑的实现需要在dubbo中完成,所以需要开发dubbo服务。

4.2.1、定义接口

在my-tanhua-dubbo-interface工程中:

  1. package com.tanhua.dubbo.server.api;
  2. import com.tanhua.dubbo.server.pojo.Publish;
  3. import com.tanhua.dubbo.server.vo.PageInfo;
  4. public interface QuanZiApi {
  5. /**
  6. * 查询好友动态
  7. *
  8. * @param userId 用户id
  9. * @param page 当前页数
  10. * @param pageSize 每一页查询的数据条数
  11. * @return
  12. */
  13. PageInfo<Publish> queryPublishList(Long userId, Integer page, Integer pageSize);
  14. }

4.2.2、实现接口

在my-tanhua-dubbo-service中完成:

  1. package com.tanhua.dubbo.server.api;
  2. import cn.hutool.core.collection.CollUtil;
  3. import com.alibaba.dubbo.config.annotation.Service;
  4. import com.tanhua.dubbo.server.pojo.Publish;
  5. import com.tanhua.dubbo.server.pojo.TimeLine;
  6. import com.tanhua.dubbo.server.vo.PageInfo;
  7. import org.bson.types.ObjectId;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.data.domain.PageRequest;
  10. import org.springframework.data.domain.Pageable;
  11. import org.springframework.data.domain.Sort;
  12. import org.springframework.data.mongodb.core.MongoTemplate;
  13. import org.springframework.data.mongodb.core.query.Criteria;
  14. import org.springframework.data.mongodb.core.query.Query;
  15. import java.util.ArrayList;
  16. import java.util.List;
  17. @Service(version = "1.0.0")
  18. public class QuanZiApiImpl implements QuanZiApi {
  19. @Autowired
  20. private MongoTemplate mongoTemplate;
  21. @Override
  22. public PageInfo<Publish> queryPublishList(Long userId, Integer page, Integer pageSize) {
  23. //分析:查询好友的动态,实际上查询时间线表
  24. PageInfo<Publish> pageInfo = new PageInfo<>();
  25. pageInfo.setPageNum(page);
  26. pageInfo.setPageSize(pageSize);
  27. Pageable pageable = PageRequest.of(page - 1, pageSize,
  28. Sort.by(Sort.Order.desc("date")));
  29. Query query = new Query().with(pageable);
  30. List<TimeLine> timeLineList = this.mongoTemplate.find(query, TimeLine.class, "quanzi_time_line_" + userId);
  31. if(CollUtil.isEmpty(timeLineList)){
  32. //没有查询到数据
  33. return pageInfo;
  34. }
  35. //获取时间线列表中的发布id的列表
  36. List<Object> ids = CollUtil.getFieldValues(timeLineList, "publishId");
  37. //根据动态id查询动态列表
  38. Query queryPublish = Query.query(Criteria.where("id").in(ids))
  39. .with(Sort.by(Sort.Order.desc("created")));
  40. List<Publish> publishList = this.mongoTemplate.find(queryPublish, Publish.class);
  41. pageInfo.setRecords(publishList);
  42. return pageInfo;
  43. }
  44. }

引入Hutool工具包,官方文档:https://www.hutool.cn/docs/#/

  1. <!-- 在my-tanhua工程中定义依赖 -->
  2. <dependency>
  3. <groupId>cn.hutool</groupId>
  4. <artifactId>hutool-all</artifactId>
  5. <version>5.5.2</version>
  6. </dependency>
  7. <!-- 在my-tanhua-dubbo-service中引入使用 -->
  8. <dependency>
  9. <groupId>cn.hutool</groupId>
  10. <artifactId>hutool-all</artifactId>
  11. </dependency>

4.2.3、测试用例

  1. package com.tanhua.dubbo.server.api;
  2. import com.tanhua.dubbo.server.pojo.Publish;
  3. import com.tanhua.dubbo.server.vo.PageInfo;
  4. import org.junit.Test;
  5. import org.junit.runner.RunWith;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.boot.test.context.SpringBootTest;
  8. import org.springframework.test.context.junit4.SpringRunner;
  9. @RunWith(SpringRunner.class)
  10. @SpringBootTest
  11. public class TestQuanZiApi {
  12. @Autowired
  13. private QuanZiApi quanZiApi;
  14. @Test
  15. public void testQueryPublishList(){
  16. this.quanZiApi.queryPublishList(1L, 1, 2)
  17. .getRecords().forEach(publish -> System.out.println(publish));
  18. System.out.println("------------");
  19. this.quanZiApi.queryPublishList(1L, 2, 2)
  20. .getRecords().forEach(publish -> System.out.println(publish));
  21. System.out.println("------------");
  22. this.quanZiApi.queryPublishList(1L, 3, 2)
  23. .getRecords().forEach(publish -> System.out.println(publish));
  24. }
  25. }

测试结果: day04-圈子功能实现 - 图8

4.3、APP接口服务

开发完成dubbo服务后,我们将开发APP端的接口服务,依然是需要按照mock接口的中的接口定义实现。
接口地址:https://mock-java.itheima.net/project/35/interface/api/683

4.3.1、QuanZiVo

根据接口中响应的数据结构进行定义vo对象:(在my-tanhua-server工程中)

  1. package com.tanhua.server.vo;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. @Data
  6. @NoArgsConstructor
  7. @AllArgsConstructor
  8. public class QuanZiVo {
  9. private String id; //动态id
  10. private Long userId; //用户id
  11. private String avatar; //头像
  12. private String nickname; //昵称
  13. private String gender; //性别 man woman
  14. private Integer age; //年龄
  15. private String[] tags; //标签
  16. private String textContent; //文字动态
  17. private String[] imageContent; //图片动态
  18. private String distance; //距离
  19. private String createDate; //发布时间 如: 10分钟前
  20. private Integer likeCount; //点赞数
  21. private Integer commentCount; //评论数
  22. private Integer loveCount; //喜欢数
  23. private Integer hasLiked; //是否点赞(1是,0否)
  24. private Integer hasLoved; //是否喜欢(1是,0否)
  25. }

4.3.2、QuanZiController

根据服务接口编写QuanZiController,其请求方法为GET请求,会传递page、pageSize、token等信息。
代码实现如下:

  1. package com.tanhua.server.controller;
  2. import com.tanhua.server.service.QuanZiService;
  3. import com.tanhua.server.vo.PageResult;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.web.bind.annotation.*;
  6. @RestController
  7. @RequestMapping("movements")
  8. public class QuanZiController {
  9. @Autowired
  10. private QuanZiService quanZiService;
  11. /**
  12. * 查询好友动态
  13. *
  14. * @param page
  15. * @param pageSize
  16. * @return
  17. */
  18. @GetMapping
  19. public PageResult queryPublishList(@RequestParam(value = "page", defaultValue = "1") Integer page,
  20. @RequestParam(value = "pagesize", defaultValue = "10") Integer pageSize,
  21. @RequestHeader("Authorization") String token) {
  22. return this.quanZiService.queryPublishList(page, pageSize, token);
  23. }
  24. }

4.3.3、QuanZiService

在QuanZiService中将实现具体的业务逻辑,需要调用quanzi的dubbo服务完成数据的查询,并且要完成用户登录是否有效的校验,最后按照服务接口中定义的结构进行封装数据。

  1. package com.tanhua.server.service;
  2. import cn.hutool.core.bean.BeanUtil;
  3. import cn.hutool.core.collection.CollUtil;
  4. import cn.hutool.core.util.StrUtil;
  5. import com.alibaba.dubbo.config.annotation.Reference;
  6. import com.tanhua.common.pojo.User;
  7. import com.tanhua.common.pojo.UserInfo;
  8. import com.tanhua.common.utils.RelativeDateFormat;
  9. import com.tanhua.dubbo.server.api.QuanZiApi;
  10. import com.tanhua.dubbo.server.pojo.Publish;
  11. import com.tanhua.dubbo.server.vo.PageInfo;
  12. import com.tanhua.server.vo.PageResult;
  13. import com.tanhua.server.vo.QuanZiVo;
  14. import org.apache.commons.lang3.StringUtils;
  15. import org.springframework.beans.factory.annotation.Autowired;
  16. import org.springframework.stereotype.Service;
  17. import java.util.ArrayList;
  18. import java.util.Date;
  19. import java.util.List;
  20. @Service
  21. public class QuanZiService {
  22. @Reference(version = "1.0.0")
  23. private QuanZiApi quanZiApi;
  24. @Autowired
  25. private UserService userService;
  26. @Autowired
  27. private UserInfoService userInfoService;
  28. public PageResult queryPublishList(Integer page, Integer pageSize, String token) {
  29. //分析:通过dubbo中的服务查询用户的好友动态
  30. //通过mysql查询用户的信息,回写到结果对象中(QuanZiVo)
  31. PageResult pageResult = new PageResult();
  32. pageResult.setPage(page);
  33. pageResult.setPagesize(pageSize);
  34. //校验token是否有效
  35. User user = this.userService.queryUserByToken(token);
  36. if (user == null) {
  37. //token已经失效
  38. return pageResult;
  39. }
  40. //通过dubbo查询数据
  41. PageInfo<Publish> pageInfo = this.quanZiApi.queryPublishList(user.getId(), page, pageSize);
  42. List<Publish> records = pageInfo.getRecords();
  43. if (CollUtil.isEmpty(records)) {
  44. return pageResult;
  45. }
  46. List<QuanZiVo> quanZiVoList = new ArrayList<>();
  47. records.forEach(publish -> {
  48. QuanZiVo quanZiVo = new QuanZiVo();
  49. quanZiVo.setId(publish.getId().toHexString());
  50. quanZiVo.setTextContent(publish.getText());
  51. quanZiVo.setImageContent(publish.getMedias().toArray(new String[]{}));
  52. quanZiVo.setUserId(publish.getUserId());
  53. quanZiVo.setCreateDate(RelativeDateFormat.format(new Date(publish.getCreated())));
  54. quanZiVoList.add(quanZiVo);
  55. });
  56. //查询用户信息
  57. List<Object> userIds = CollUtil.getFieldValues(records, "userId");
  58. List<UserInfo> userInfoList = this.userInfoService.queryUserInfoByUserIdList(userIds);
  59. for (QuanZiVo quanZiVo : quanZiVoList) {
  60. //找到对应的用户信息
  61. for (UserInfo userInfo : userInfoList) {
  62. if(quanZiVo.getUserId().longValue() == userInfo.getUserId().longValue()){
  63. this.fillUserInfoToQuanZiVo(userInfo, quanZiVo);
  64. break;
  65. }
  66. }
  67. }
  68. pageResult.setItems(quanZiVoList);
  69. return pageResult;
  70. }
  71. /**
  72. * 填充用户信息
  73. *
  74. * @param userInfo
  75. * @param quanZiVo
  76. */
  77. private void fillUserInfoToQuanZiVo(UserInfo userInfo, QuanZiVo quanZiVo){
  78. BeanUtil.copyProperties(userInfo, quanZiVo, "id");
  79. quanZiVo.setGender(userInfo.getSex().name().toLowerCase());
  80. quanZiVo.setTags(StringUtils.split(userInfo.getTags(), ','));
  81. quanZiVo.setCommentCount(0); //TODO 评论数
  82. quanZiVo.setDistance("1.2公里"); //TODO 距离
  83. quanZiVo.setHasLiked(0); //TODO 是否点赞(1是,0否)
  84. quanZiVo.setLikeCount(0); //TODO 点赞数
  85. quanZiVo.setHasLoved(0); //TODO 是否喜欢(1是,0否)
  86. quanZiVo.setLoveCount(0); //TODO 喜欢数
  87. }
  88. }
  1. // com.tanhua.server.service.UserInfoService
  2. /**
  3. * 根据用户id的集合查询用户列表
  4. *
  5. * @param userIds
  6. * @return
  7. */
  8. public List<UserInfo> queryUserInfoList(Collection<?> userIds) {
  9. QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
  10. queryWrapper.in("user_id", userIds);
  11. return this.queryUserInfoList(queryWrapper);
  12. }

在com.tanhua.server.vo.QuanZiVo中增加字段别名,方便直接拷贝属性数据:

  1. package com.tanhua.server.vo;
  2. import cn.hutool.core.annotation.Alias;
  3. import lombok.AllArgsConstructor;
  4. import lombok.Data;
  5. import lombok.NoArgsConstructor;
  6. @Data
  7. @NoArgsConstructor
  8. @AllArgsConstructor
  9. public class QuanZiVo {
  10. private String id; //动态id
  11. private Long userId; //用户id
  12. @Alias("logo") //别名
  13. private String avatar; //头像
  14. @Alias("nickName") //别名
  15. private String nickname; //昵称
  16. private String gender; //性别 man woman
  17. private Integer age; //年龄
  18. private String[] tags; //标签
  19. private String textContent; //文字动态
  20. private String[] imageContent; //图片动态
  21. private String distance; //距离
  22. private String createDate; //发布时间 如: 10分钟前
  23. private Integer likeCount; //点赞数
  24. private Integer commentCount; //评论数
  25. private Integer loveCount; //喜欢数
  26. private Integer hasLiked; //是否点赞(1是,0否)
  27. private Integer hasLoved; //是否喜欢(1是,0否)
  28. }

4.3.4、测试

day04-圈子功能实现 - 图9

5、统一校验token

在之前的开发中,我们会在每一个Service中对token做处理,相同的逻辑一定是要进行统一处理的,该如何处理呢?
由于程序是运行在web容器中,每一个HTTP请求都是一个独立线程,也就是可以理解成我们编写的应用程序运行在一个多线程的环境中,那么我们就可以使用ThreadLocal在HTTP请求的生命周期内进行存值、取值操作。
如下图:
day04-圈子功能实现 - 图10
说明:

  • 用户的每一个请求,都是一个独立的线程
  • 图中的TL就是ThreadLocal,一旦将数据绑定到ThreadLocal中,那么在整个请求的生命周期内都可以随时拿到ThreadLocal中当前线程的数据。

根据上面的分析,我们只需要在Controller请求之前进行对token做校验,如果token有效,则会拿到User对象,然后将该User对象保存到ThreadLocal中即可,最后放行请求,在后续的各个环节中都可以获取到该数据了。
如果token无效,给客户端响应401状态码,拦截请求,不再放行到Controller中。
由此可见,这个校验的逻辑是比较适合放在拦截器中完成的。

5.1、编写UserThreadLocal

在my-tanhua-common工程中,编写UserThreadLocal。

  1. package com.tanhua.common.utils;
  2. import com.tanhua.common.pojo.User;
  3. public class UserThreadLocal {
  4. private static final ThreadLocal<User> LOCAL = new ThreadLocal<>();
  5. private UserThreadLocal(){
  6. }
  7. /**
  8. * 将对象放入到ThreadLocal
  9. *
  10. * @param user
  11. */
  12. public static void set(User user){
  13. LOCAL.set(user);
  14. }
  15. /**
  16. * 返回当前线程中的User对象
  17. *
  18. * @return
  19. */
  20. public static User get(){
  21. return LOCAL.get();
  22. }
  23. /**
  24. * 删除当前线程中的User对象
  25. */
  26. public static void remove(){
  27. LOCAL.remove();
  28. }
  29. }

5.2、编写TokenInterceptor

  1. package com.tanhua.server.interceptor;
  2. import cn.hutool.core.util.StrUtil;
  3. import com.tanhua.common.pojo.User;
  4. import com.tanhua.common.utils.NoAuthorization;
  5. import com.tanhua.common.utils.UserThreadLocal;
  6. import com.tanhua.server.service.UserService;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.stereotype.Component;
  9. import org.springframework.web.bind.annotation.GetMapping;
  10. import org.springframework.web.method.HandlerMethod;
  11. import org.springframework.web.servlet.HandlerInterceptor;
  12. import javax.servlet.http.HttpServletRequest;
  13. import javax.servlet.http.HttpServletResponse;
  14. @Component
  15. public class UserTokenInterceptor implements HandlerInterceptor {
  16. @Autowired
  17. private UserService userService;
  18. @Override
  19. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  20. //校验handler是否是HandlerMethod
  21. if (!(handler instanceof HandlerMethod)) {
  22. return true;
  23. }
  24. //判断是否包含@NoAuthorization注解,如果包含,直接放行
  25. if (((HandlerMethod) handler).hasMethodAnnotation(NoAuthorization.class)) {
  26. return true;
  27. }
  28. //从请求头中获取token
  29. String token = request.getHeader("Authorization");
  30. if(StrUtil.isNotEmpty(token)){
  31. User user = this.userService.queryUserByToken(token);
  32. if(user != null){
  33. //token有效
  34. //将User对象放入到ThreadLocal中
  35. UserThreadLocal.set(user);
  36. return true;
  37. }
  38. }
  39. //token无效,响应状态为401
  40. response.setStatus(401); //无权限
  41. return false;
  42. }
  43. @Override
  44. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  45. //从ThreadLocal中移除User对象
  46. UserThreadLocal.remove();
  47. }
  48. }

5.3、编写注解NoAuthorization

  1. package com.tanhua.common.utils;
  2. import java.lang.annotation.*;
  3. @Target(ElementType.METHOD)
  4. @Retention(RetentionPolicy.RUNTIME)
  5. @Documented //标记注解
  6. public @interface NoAuthorization {
  7. }

5.4、注册拦截器

  1. package com.tanhua.server.config;
  2. import com.tanhua.server.interceptor.RedisCacheInterceptor;
  3. import com.tanhua.server.interceptor.UserTokenInterceptor;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  7. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  8. @Configuration
  9. public class WebConfig implements WebMvcConfigurer {
  10. @Autowired
  11. private RedisCacheInterceptor redisCacheInterceptor;
  12. @Autowired
  13. private UserTokenInterceptor userTokenInterceptor;
  14. @Override
  15. public void addInterceptors(InterceptorRegistry registry) {
  16. //考虑拦截器的顺序
  17. registry.addInterceptor(this.userTokenInterceptor).addPathPatterns("/**");
  18. registry.addInterceptor(this.redisCacheInterceptor).addPathPatterns("/**");
  19. }
  20. }

5.5、使用ThreadLocal

在所有的Service中,如果需要获取User对象的,直接从UserThreadLocal获取即可,同时在Controller中也无需进行获取token操作。
例如:

  1. //com.tanhua.server.service.QuanZiService
  2. public PageResult queryPublishList(Integer page, Integer pageSize) {
  3. PageResult pageResult = new PageResult();
  4. pageResult.setPage(page);
  5. pageResult.setPagesize(pageSize);
  6. //获取User对象,无需对User对象校验,其一定不为null
  7. User user = UserThreadLocal.get();
  8. PageInfo<Publish> pageInfo = this.quanZiApi.queryPublishList(user.getId(), page, pageSize);
  9. //。。。。代码略。。。。。
  10. return pageResult;
  11. }

需要注意的是,在APP中,如果请求响应401,会跳转到登录页面。

6、发布动态

用户可以在圈子中发布动态,动态内容中可以有文字和图片。如下图:
day04-圈子功能实现 - 图11

6.1、dubbo服务

6.1.1、定义接口

  1. package com.tanhua.dubbo.server.api;
  2. import com.tanhua.dubbo.server.pojo.Publish;
  3. import com.tanhua.dubbo.server.vo.PageInfo;
  4. public interface QuanZiApi {
  5. /**
  6. * 查询好友动态
  7. *
  8. * @param userId 用户id
  9. * @param page 当前页数
  10. * @param pageSize 每一页查询的数据条数
  11. * @return
  12. */
  13. PageInfo<Publish> queryPublishList(Long userId, Integer page, Integer pageSize);
  14. /**
  15. * 发布动态
  16. *
  17. * @param publish
  18. * @return 发布成功返回动态id
  19. */
  20. String savePublish(Publish publish);
  21. }

6.1.2、实现接口

  1. /**
  2. * 发布动态
  3. *
  4. * @param publish
  5. * @return 发布成功返回动态id
  6. */
  7. public String savePublish(Publish publish) {
  8. //对publish对象校验
  9. if (!ObjectUtil.isAllNotEmpty(publish.getText(), publish.getUserId())) {
  10. //发布失败
  11. return null;
  12. }
  13. //设置主键id
  14. publish.setId(ObjectId.get());
  15. try {
  16. //设置自增长的pid
  17. publish.setPid(this.idService.createId(IdType.PUBLISH));
  18. publish.setCreated(System.currentTimeMillis());
  19. //写入到publish表中
  20. this.mongoTemplate.save(publish);
  21. //写入相册表
  22. Album album = new Album();
  23. album.setId(ObjectId.get());
  24. album.setCreated(System.currentTimeMillis());
  25. album.setPublishId(publish.getId());
  26. this.mongoTemplate.save(album, "quanzi_album_" + publish.getUserId());
  27. //写入好友的时间线表(异步写入)
  28. this.timeLineService.saveTimeLine(publish.getUserId(), publish.getId());
  29. } catch (Exception e) {
  30. //TODO 需要做事务的回滚,Mongodb的单节点服务,不支持事务,对于回滚我们暂时不实现了
  31. log.error("发布动态失败~ publish = " + publish, e);
  32. }
  33. return publish.getId().toHexString();
  34. }
  1. package com.tanhua.dubbo.server.service;
  2. import com.tanhua.dubbo.server.enums.IdType;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.data.redis.core.RedisTemplate;
  5. import org.springframework.stereotype.Service;
  6. //生成自增长的id,原理:使用redis的自增长值
  7. @Service
  8. public class IdService {
  9. @Autowired
  10. private RedisTemplate<String, String> redisTemplate;
  11. public Long createId(IdType idType) {
  12. String idKey = "TANHUA_ID_" + idType.toString();
  13. return this.redisTemplate.opsForValue().increment(idKey);
  14. }
  15. }
  1. package com.tanhua.dubbo.server.enums;
  2. public enum IdType {
  3. PUBLISH, VIDEO;
  4. }

6.1.3、好友时间线数据

好友的时间线数据需要异步执行。这里使用Spring的@Async注解实现异步执行,其底层是通过启动独立线程来执行,从而可以异步执行。通过返回的CompletableFuture来判断是否执行成功以及是否存在异常。同时需要在启动类中添加_@_EnableAsync 开启异步的支持。

  1. package com.tanhua.dubbo.server.service;
  2. import cn.hutool.core.collection.CollUtil;
  3. import com.tanhua.dubbo.server.pojo.TimeLine;
  4. import com.tanhua.dubbo.server.pojo.Users;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.bson.types.ObjectId;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.data.mongodb.core.MongoTemplate;
  9. import org.springframework.data.mongodb.core.query.Criteria;
  10. import org.springframework.data.mongodb.core.query.Query;
  11. import org.springframework.scheduling.annotation.Async;
  12. import org.springframework.stereotype.Service;
  13. import java.util.List;
  14. import java.util.concurrent.CompletableFuture;
  15. @Service
  16. @Slf4j
  17. public class TimeLineService {
  18. @Autowired
  19. private MongoTemplate mongoTemplate;
  20. @Async //异步执行,原理:底层开一个线程去执行该方法
  21. public CompletableFuture<String> saveTimeLine(Long userId, ObjectId publishId) {
  22. //写入好友的时间线表
  23. try {
  24. //查询好友列表
  25. Query query = Query.query(Criteria.where("userId").is(userId));
  26. List<Users> usersList = this.mongoTemplate.find(query, Users.class);
  27. if (CollUtil.isEmpty(usersList)) {
  28. //返回成功
  29. return CompletableFuture.completedFuture("ok");
  30. }
  31. //依次写入到好友的时间线表中
  32. for (Users users : usersList) {
  33. TimeLine timeLine = new TimeLine();
  34. timeLine.setId(ObjectId.get());
  35. timeLine.setDate(System.currentTimeMillis());
  36. timeLine.setPublishId(publishId);
  37. timeLine.setUserId(userId);
  38. //写入数据
  39. this.mongoTemplate.save(timeLine, "quanzi_time_line_" + users.getFriendId());
  40. }
  41. } catch (Exception e) {
  42. log.error("写入好友时间线表失败~ userId = " + userId + ", publishId = " + publishId, e);
  43. //TODO 事务回滚问题
  44. return CompletableFuture.completedFuture("error");
  45. }
  46. return CompletableFuture.completedFuture("ok");
  47. }
  48. }

开启异步执行:

  1. package com.tanhua.dubbo.server;
  2. import cn.hutool.core.util.StrUtil;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import org.springframework.scheduling.annotation.EnableAsync;
  6. @SpringBootApplication
  7. @EnableAsync //开启异步执行的支持
  8. public class DubboApplication {
  9. public static void main(String[] args) {
  10. SpringApplication.run(DubboApplication.class, args);
  11. }
  12. }

6.1.4、测试好友时间线

  1. package com.tanhua.dubbo.server.api;
  2. import com.tanhua.dubbo.server.service.TimeLineService;
  3. import org.bson.types.ObjectId;
  4. import org.junit.Test;
  5. import org.junit.runner.RunWith;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.boot.test.context.SpringBootTest;
  8. import org.springframework.test.context.junit4.SpringRunner;
  9. import java.util.concurrent.CompletableFuture;
  10. import java.util.concurrent.ExecutionException;
  11. @RunWith(SpringRunner.class)
  12. @SpringBootTest
  13. public class TestTimeLineService {
  14. @Autowired
  15. private TimeLineService timeLineService;
  16. @Test
  17. public void testSaveTimeLine() {
  18. ObjectId objectId = ObjectId.get();
  19. System.out.println("生成的id为:" + objectId.toHexString());
  20. CompletableFuture<String> future = this.timeLineService.saveTimeLine(1L, objectId);
  21. future.whenComplete((s, throwable) -> {
  22. System.out.println("执行完成:" + s);
  23. });
  24. System.out.println("异步方法执行完成");
  25. try {
  26. future.get(); //阻塞当前的主线程,等待异步执行的结束
  27. } catch (Exception e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }

6.1.5、测试发布动态

将dubbo服务启动起来,在my-tanhua-server工程中进行功能的测试:

  1. package com.tanhua.server;
  2. import cn.hutool.core.collection.ListUtil;
  3. import com.alibaba.dubbo.config.annotation.Reference;
  4. import com.tanhua.dubbo.server.api.QuanZiApi;
  5. import com.tanhua.dubbo.server.pojo.Publish;
  6. import org.junit.Test;
  7. import org.junit.runner.RunWith;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.boot.test.context.SpringBootTest;
  10. import org.springframework.test.context.junit4.SpringRunner;
  11. @RunWith(SpringRunner.class)
  12. @SpringBootTest
  13. public class TestQuanZiApi {
  14. @Reference(version = "1.0.0")
  15. private QuanZiApi quanZiApi;
  16. @Test
  17. public void testSavePublish(){
  18. Publish publish = new Publish();
  19. publish.setText("人生不如意事十之八九,真正有格局的人,既能享受最好的,也能承受最坏的。");
  20. publish.setMedias(ListUtil.toList("https://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/photo/6/1.jpg", "https://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/photo/6/CL-3.jpg"));
  21. publish.setUserId(1L);
  22. publish.setSeeType(1);
  23. publish.setLongitude("116.350426");
  24. publish.setLatitude("40.066355");
  25. publish.setLocationName("中国北京市昌平区建材城西路16号");
  26. this.quanZiApi.savePublish(publish);
  27. }
  28. }

6.2、APP接口服务

接口地址:https://mock-java.itheima.net/project/35/interface/api/701
day04-圈子功能实现 - 图12
从接口中可以看出,主要的参数有:文字、图片、位置等内容。

6.2.1、图片上传

图片上传功能原来是在sso中完成的,为了能公用该功能,所以需要将图片上传的Service以及配置移动至common工程中。
day04-圈子功能实现 - 图13
pom.xml:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>my-tanhua</artifactId>
  7. <groupId>cn.itcast.tanhua</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>my-tanhua-common</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-web</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.projectlombok</groupId>
  19. <artifactId>lombok</artifactId>
  20. </dependency>
  21. <dependency>
  22. <groupId>com.baomidou</groupId>
  23. <artifactId>mybatis-plus-boot-starter</artifactId>
  24. </dependency>
  25. <dependency>
  26. <groupId>com.fasterxml.jackson.core</groupId>
  27. <artifactId>jackson-databind</artifactId>
  28. </dependency>
  29. <dependency>
  30. <groupId>com.aliyun.oss</groupId>
  31. <artifactId>aliyun-sdk-oss</artifactId>
  32. </dependency>
  33. <dependency>
  34. <groupId>org.apache.commons</groupId>
  35. <artifactId>commons-lang3</artifactId>
  36. </dependency>
  37. <dependency>
  38. <groupId>joda-time</groupId>
  39. <artifactId>joda-time</artifactId>
  40. </dependency>
  41. </dependencies>
  42. </project>

需要注意3点:

  • 将sso系统中的相关代码删除
  • 将aliyun.properties复制到my-tanhua-server中
  • 启动类中需要将包扫描范围扩大到comm.tanhua,因为相关类被移动到com.tanhua.common下,默认扫描不能被扫描到。
    • sso与server系统都需要设置:
      1. package com.tanhua.server;
      2. @MapperScan("com.tanhua.common.mapper") //设置mapper接口的扫描包
      3. @SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}) //排除mongo的自动配置
      4. @ComponentScan(basePackages={"com.tanhua"}) //设置扫描包范围
      5. public class ServerApplication {
      6. public static void main(String[] args) {
      7. SpringApplication.run(ServerApplication.class, args);
      8. }
      9. }

      6.2.2、接口服务

      需要注意的是,文字是必须提交的,图片是非必须的。
      1. //com.tanhua.server.controller.QuanZiController
      2. /**
      3. * 发送动态
      4. *
      5. * @param textContent
      6. * @param location
      7. * @param multipartFile
      8. * @return
      9. */
      10. @PostMapping
      11. public ResponseEntity<Void> savePublish(@RequestParam("textContent") String textContent,
      12. @RequestParam(value = "location", required = false) String location,
      13. @RequestParam(value = "latitude", required = false) String latitude,
      14. @RequestParam(value = "longitude", required = false) String longitude,
      15. @RequestParam(value = "imageContent", required = false) MultipartFile[] multipartFile) {
      16. try {
      17. String publishId = this.quanZiService.savePublish(textContent, location, latitude, longitude, multipartFile);
      18. if (StrUtil.isNotEmpty(publishId)) {
      19. return ResponseEntity.ok(null);
      20. }
      21. } catch (Exception e) {
      22. e.printStackTrace();
      23. }
      24. return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
      25. }

      6.2.3、QuanZiService实现

      1. // com.tanhua.server.service.QuanZiService
      2. @Autowired
      3. private PicUploadService picUploadService;
      4. public String savePublish(String textContent,
      5. String location,
      6. String latitude,
      7. String longitude,
      8. MultipartFile[] multipartFile) {
      9. //查询当前的登录信息
      10. User user = UserThreadLocal.get();
      11. Publish publish = new Publish();
      12. publish.setUserId(user.getId());
      13. publish.setText(textContent);
      14. publish.setLocationName(location);
      15. publish.setLatitude(latitude);
      16. publish.setLongitude(longitude);
      17. publish.setSeeType(1);
      18. List<String> picUrls = new ArrayList<>();
      19. //图片上传
      20. for (MultipartFile file : multipartFile) {
      21. PicUploadResult picUploadResult = this.picUploadService.upload(file);
      22. picUrls.add(picUploadResult.getName());
      23. }
      24. publish.setMedias(picUrls);
      25. return this.quanZiApi.savePublish(publish);
      26. }

      7、查询推荐动态

      推荐动态是通过推荐系统计算出的结果,现在我们只需要实现查询即可,推荐系统在后面的课程中完成。
      推荐系统计算完成后,会将结果数据写入到Redis中,数据如下:
      1. 192.168.31.81:6379> get QUANZI_PUBLISH_RECOMMEND_1
      2. "2562,3639,2063,3448,2128,2597,2893,2333,3330,2642,2541,3002,3561,3649,2384,2504,3397,2843,2341,2249"
      可以看到,在Redis中的数据是有多个发布id组成(pid)由逗号分隔。所以实现中需要自己对这些数据做分页处理。

      7.1、dubbo服务

      7.1.1、定义接口

      1. //com.tanhua.dubbo.server.api.QuanZiApi
      2. /**
      3. * 查询推荐动态
      4. *
      5. * @param userId 用户id
      6. * @param page 当前页数
      7. * @param pageSize 每一页查询的数据条数
      8. * @return
      9. */
      10. PageInfo<Publish> queryRecommendPublishList(Long userId, Integer page, Integer pageSize);

      7.1.2、编写实现

      1. //com.tanhua.dubbo.server.api.QuanZiApiImpl
      2. @Autowired
      3. private RedisTemplate<String, String> redisTemplate;
      4. public PageInfo<Publish> queryRecommendPublishList(Long userId, Integer page, Integer pageSize) {
      5. PageInfo<Publish> pageInfo = new PageInfo<>();
      6. pageInfo.setPageNum(page);
      7. pageInfo.setPageSize(pageSize);
      8. // 查询推荐结果数据
      9. String key = "QUANZI_PUBLISH_RECOMMEND_" + userId;
      10. String data = this.redisTemplate.opsForValue().get(key);
      11. if (StrUtil.isEmpty(data)) {
      12. return pageInfo;
      13. }
      14. //查询到的pid进行分页处理
      15. List<String> pids = StrUtil.split(data, ',');
      16. //计算分页
      17. //[0, 10]
      18. int[] startEnd = PageUtil.transToStartEnd(page - 1, pageSize);
      19. int startIndex = startEnd[0]; //开始
      20. int endIndex = Math.min(startEnd[1], pids.size()); //结束
      21. List<Long> pidLongList = new ArrayList<>();
      22. for (int i = startIndex; i < endIndex; i++) {
      23. pidLongList.add(Long.valueOf(pids.get(i)));
      24. }
      25. if (CollUtil.isEmpty(pidLongList)) {
      26. //没有查询到数据
      27. return pageInfo;
      28. }
      29. //根据pid查询publish
      30. Query query = Query.query(Criteria.where("pid").in(pidLongList))
      31. .with(Sort.by(Sort.Order.desc("created")));
      32. List<Publish> publishList = this.mongoTemplate.find(query, Publish.class);
      33. if (CollUtil.isEmpty(publishList)) {
      34. //没有查询到数据
      35. return pageInfo;
      36. }
      37. pageInfo.setRecords(publishList);
      38. return pageInfo;
      39. }

      7.2、APP服务

      地址:https://mock-java.itheima.net/project/35/interface/api/677
      通过接口的定义可以看出,其响应的数据结构与好友动态结构一样,所以可以复用QuanZiVo对象。

      7.2.1、QuanZiController

      1. //com.tanhua.server.controller.QuanZiController
      2. /**
      3. * 查询推荐动态
      4. *
      5. * @param page
      6. * @param pageSize
      7. * @return
      8. */
      9. @GetMapping("recommend")
      10. public PageResult queryRecommendPublishList(@RequestParam(value = "page", defaultValue = "1") Integer page,
      11. @RequestParam(value = "pagesize", defaultValue = "10") Integer pageSize) {
      12. return this.quanZiService.queryRecommendPublishList(page, pageSize);
      13. }

      7.2.2、QuanZiService

      在实现中,将查询好友动态的方法中公共的内容,进行抽取,具体如下:
      1. //com.tanhua.server.service.QuanZiService
      2. public PageResult queryRecommendPublishList(Integer page, Integer pageSize) {
      3. //分析:通过dubbo中的服务查询系统推荐动态
      4. //通过mysql查询用户的信息,回写到结果对象中(QuanZiVo)
      5. PageResult pageResult = new PageResult();
      6. pageResult.setPage(page);
      7. pageResult.setPagesize(pageSize);
      8. //直接从ThreadLocal中获取对象
      9. User user = UserThreadLocal.get();
      10. //通过dubbo查询数据
      11. PageInfo<Publish> pageInfo = this.quanZiApi.queryRecommendPublishList(user.getId(), page, pageSize);
      12. List<Publish> records = pageInfo.getRecords();
      13. if (CollUtil.isEmpty(records)) {
      14. return pageResult;
      15. }
      16. pageResult.setItems(this.fillQuanZiVo(records));
      17. return pageResult;
      18. }
      1. /**
      2. * 填充用户信息
      3. *
      4. * @param userInfo
      5. * @param quanZiVo
      6. */
      7. private void fillUserInfoToQuanZiVo(UserInfo userInfo, QuanZiVo quanZiVo){
      8. BeanUtil.copyProperties(userInfo, quanZiVo, "id");
      9. quanZiVo.setGender(userInfo.getSex().name().toLowerCase());
      10. quanZiVo.setTags(StringUtils.split(userInfo.getTags(), ','));
      11. quanZiVo.setCommentCount(0); //TODO 评论数
      12. quanZiVo.setDistance("1.2公里"); //TODO 距离
      13. quanZiVo.setHasLiked(0); //TODO 是否点赞(1是,0否)
      14. quanZiVo.setLikeCount(0); //TODO 点赞数
      15. quanZiVo.setHasLoved(0); //TODO 是否喜欢(1是,0否)
      16. quanZiVo.setLoveCount(0); //TODO 喜欢数
      17. }
      18. /**
      19. * 根据查询到的publish集合填充QuanZiVo对象
      20. *
      21. * @param records
      22. * @return
      23. */
      24. private List<QuanZiVo> fillQuanZiVo(List<Publish> records){
      25. List<QuanZiVo> quanZiVoList = new ArrayList<>();
      26. records.forEach(publish -> {
      27. QuanZiVo quanZiVo = new QuanZiVo();
      28. quanZiVo.setId(publish.getId().toHexString());
      29. quanZiVo.setTextContent(publish.getText());
      30. quanZiVo.setImageContent(publish.getMedias().toArray(new String[]{}));
      31. quanZiVo.setUserId(publish.getUserId());
      32. quanZiVo.setCreateDate(RelativeDateFormat.format(new Date(publish.getCreated())));
      33. quanZiVoList.add(quanZiVo);
      34. });
      35. //查询用户信息
      36. List<Object> userIds = CollUtil.getFieldValues(records, "userId");
      37. List<UserInfo> userInfoList = this.userInfoService.queryUserInfoByUserIdList(userIds);
      38. for (QuanZiVo quanZiVo : quanZiVoList) {
      39. //找到对应的用户信息
      40. for (UserInfo userInfo : userInfoList) {
      41. if(quanZiVo.getUserId().longValue() == userInfo.getUserId().longValue()){
      42. this.fillUserInfoToQuanZiVo(userInfo, quanZiVo);
      43. break;
      44. }
      45. }
      46. }
      47. return quanZiVoList;
      48. }

      7.3、测试

      day04-圈子功能实现 - 图14