Dobbo微服务项目实战(详细介绍+案例源码) - 5.推荐好友列表/MongoDB集群/动态发布与查看

You are a wizard, Harry!

在这里插入图片描述

系列文章目录

1. 项目介绍及环境配置
2. 短信验证码登录
3. 用户信息
4. MongoDB
5.推荐好友列表/MongoDB集群/动态发布与查看
6. 圈子动态/圈子互动
7. 即时通讯(基于第三方API)
8. 附近的人(百度地图APi)
9. 小视频
10.网关配置
11.后台管理


文章目录


一、推荐好友列表

1. 页面展示

在这里插入图片描述


2. 接口文档

在这里插入图片描述


3. 编码实现

⑴. 控制层 - 获取请求参数

在这里插入图片描述

编辑tanhua-app-server/src/main/java/com/tanhua/server/controller/TanhuaController.java 文件:

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

    @Autowired
    private TanhuaService tanhuaService;

    /**
     * 今日佳人
     */
    @GetMapping("/todayBest")
    public ResponseEntity todayBest() {
        TodayBest vo = tanhuaService.todayBest();
        return ResponseEntity.ok(vo);
    }

    /**
     * 查询分页推荐好友列表
     */
    @GetMapping("recommendation")
    public ResponseEntity recommendation(RecommendUserDto dto) {
        PageResult pr = tanhuaService.recommendation(dto);
        return ResponseEntity.ok(pr);
    }
}

⑵. Service层 - 调用API分页查询

编辑 tanhua-app-server/src/main/java/com/tanhua/server/service/TanhuaService.java 文件:

@Service
public class TanhuaService {

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

    // 查询今日佳人数据
    public TodayBest todayBest() {
        // 1. 获取用户id
        Long userId = UserHolder.getUserId();

        // 2. 调用api查询
        RecommendUser recommendUser = recommendUserApi.queryWithMaxScore(userId);

        // 3. 设置默认值
        if(recommendUser == null) {
            recommendUser = new RecommendUser();
            recommendUser.setUserId(1l);
            recommendUser.setScore(99d);
        }

        // 4. 将recommendUser 转化成 todayBest对象
        UserInfo userInfo = userInfoApi.findById(recommendUser.getUserId());
        TodayBest vo = TodayBest.init(userInfo, recommendUser);

        // 5. 返回
        return vo;
    }

    // 查询分页推荐好友列表
    public PageResult recommendation(RecommendUserDto dto) {
        // 1. 获取用户id
        Long userId = UserHolder.getUserId();

        // 2. 调用RecommendUserApi查询数据列表(PageResult -- RecommendUser)
        PageResult pr = recommendUserApi.queryRecommendUserList(dto.getPage(), dto.getPagesize(), userId);

        // 3. 获取分页中的RecommendUser数据列表
        List<RecommendUser> items = (List<RecommendUser>) pr.getItems();

        // 4. 判断列表数据是否为空
        if (items == null) {
            return pr;
        }

        // 5. 循环RecommendUser推荐列表,根据推荐的用户id查询用户详情
        List<TodayBest> list = new ArrayList<>();
        for (RecommendUser item : items) {
            Long recommendUserId = item.getUserId();
            UserInfo userInfo = userInfoApi.findById(recommendUserId);
            if(userInfo != null) {
                // 条件判断
                if(!StringUtils.isEmpty(dto.getGender()) && !dto.getGender().equals(userInfo.getGender())) {
                    continue;
                }
                if (dto.getAge() != null && dto.getAge() < userInfo.getAge()) {
                    continue;
                }
                TodayBest vo = TodayBest.init(userInfo, item);
                list.add(vo);
            }
        }

        // 6. 构造返回值
        pr.setItems(list);
        return pr;
    }
}

⑶. API接口 - 分页查询推荐好友

编辑 tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/RecommendUserApi.java 文件:

public interface RecommendUserApi {

    // 查询今日佳人
    RecommendUser queryWithMaxScore(Long toUserId);

    // 分页查询推荐好友列表
    PageResult queryRecommendUserList(Integer page, Integer pagesize, Long toUserId);
}

⑷. API实现类 - 完成推荐用户id查询用户详情

编辑tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/api/RecommendUserApiImpl.java 文件:

@DubboService
public class RecommendUserApiImpl implements RecommendUserApi{

    @Autowired
    private MongoTemplate mongoTemplate;

    // 查询今日佳人
    public RecommendUser queryWithMaxScore(Long toUserId) {

        // 根据toUserId查询,根据评分score排序,获取第一条
        // 1. 构建Criteria
        Criteria criteria = Criteria.where("toUserId").is(toUserId);

        // 2. 构建Query
        Query query = Query.query(criteria).with(Sort.by(Sort.Order.desc("score")))
                .limit(1); // 查询第一条(第一页第一条)

        // 3. 调用mongoTemplate查询
        return mongoTemplate.findOne(query, RecommendUser.class);
    }

    // 分页查询推荐好友列表
    public PageResult queryRecommendUserList(Integer page, Integer pagesize, Long toUserId) {
        // 1. 构建Criteria对象
        Criteria criteria = Criteria.where("toUserId").is(toUserId);

        // 2. 构造query对象
        Query query = Query.query(criteria);

        // 3. 查询总数
        long count = mongoTemplate.count(query, RecommendUser.class);

        // 4. 查询数据列表
        query.with(Sort.by(Sort.Order.desc("score"))).limit(pagesize).skip((page - 1) * pagesize);
        List<RecommendUser> list = mongoTemplate.find(query, RecommendUser.class);

        // 5. 构造返回值
        return new PageResult(page, pagesize, count, list);
    }
}

⑸. 构造Dto对象

新建 tanhua-model/src/main/java/com/tanhua/model/dto/RecommendUserDto.java 文件:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class RecommendUserDto {

    private Integer page = 1; //当前页数
    private Integer pagesize = 10; //页尺寸
    private String gender; //性别 man woman
    private String lastLogin; //近期登陆时间
    private Integer age; //年龄
    private String city; //居住地
    private String education; //学历
}

⑹. Postman测试

在这里插入图片描述

4. 代码优化 - 减少UserInfoApi频繁调用

  • 在UserInfoApi中根据条件一次性获取所有用户列表
  • 在Service层进行用户筛选

⑴. API接口 - 获取请求参数

编辑 tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/UserInfoApi.java 文件:

public interface UserInfoApi {
    public void save(UserInfo userInfo);

    public void update(UserInfo userInfo);

    // 根据id查询
    UserInfo findById(Long id);

    /**
     * 批量查詢用戶詳情
     *      返回值:Map<id, UserInfo>
     */
    Map<Long, UserInfo> findByIds(List<Long> userIds, UserInfo info);
}

⑵. API接口实现类 - 批量查询用户详情

编辑 tanhua-dubbo/tanhua-dubbo-db/src/main/java/com/tanhua/dubbo/api/UserInfoApiImpl.java 文件:

@DubboService
public class UserInfoApiImpl implements UserInfoApi{
    @Autowired
    private UserInfoMapper userInfoMapper;

    @Override
    public void save(UserInfo userInfo) {
        userInfoMapper.insert(userInfo);
    }

    @Override
    public void update(UserInfo userInfo) {
        userInfoMapper.updateById(userInfo);
    }

    @Override
    public UserInfo findById(Long id) {
        return userInfoMapper.selectById(id);
    }

    /**
     * 批量查询用戶详情
     * @param userIds 用户id列表
     * @param info 用户详情信息
     * @return
     */
    @Override
    public Map<Long, UserInfo> findByIds(List<Long> userIds, UserInfo info) {
        // 1. 用户id列表
        QueryWrapper qw = new QueryWrapper<>();
        qw.in("id", userIds);

        // 2. 添加筛选条件
        if(info != null) {
            if(info.getAge() != null) {
                qw.lt("age", info.getAge());
            }
            if(!StringUtils.isEmpty(info.getGender())) {
                qw.eq("gender", info.getGender());
            }
        }

        // 3. 获取用户详情列表 转换成map集合
        List<UserInfo> list = userInfoMapper.selectList(qw);
        Map<Long, UserInfo> map = CollUtil.fieldValueMap(list, "id");

        // 4. 返回
        return map;
    }
}

⑶. 测试类

新建 tanhua-app-server/src/test/java/com/tanhua/test/UserInfoApiTest.java 文件:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppServerApplication.class)
public class UserInfoApiTest {

    @DubboReference
    private UserInfoApi userInfoApi;

    @Test
    public void testFindUsers() {
        List ids = new ArrayList<>();
        ids.add(1l);
        ids.add(2l);
        ids.add(3l);
        ids.add(4l);
        // 添加判断条件
        UserInfo userInfo = new UserInfo();
        userInfo.setAge(23);
        // Map map = userInfoApi.findByIds(ids, null);
         Map map = userInfoApi.findByIds(ids, userInfo);
        map.forEach((k, v) -> System.out.println(k + "---" + v));
    }
}

在这里插入图片描述

⑷. Service层优化 - 减少UserInfoApi频繁调用

编辑 tanhua-app-server/src/main/java/com/tanhua/server/service/TanhuaService.java 文件:

@Service
public class TanhuaService {

    @DubboReference
    private RecommendUserApi recommendUserApi;

    @DubboReference
    private UserInfoApi userInfoApi;

    // 查询今日佳人数据
    public TodayBest todayBest() {
        // 1. 获取用户id
        Long userId = UserHolder.getUserId();

        // 2. 调用api查询
        RecommendUser recommendUser = recommendUserApi.queryWithMaxScore(userId);

        // 3. 设置默认值
        if(recommendUser == null) {
            recommendUser = new RecommendUser();
            recommendUser.setUserId(1l);
            recommendUser.setScore(99d);
        }

        // 4. 将recommendUser 转化成 todayBest对象
        UserInfo userInfo = userInfoApi.findById(recommendUser.getUserId());
        TodayBest vo = TodayBest.init(userInfo, recommendUser);

        // 5. 返回
        return vo;
    }

    // 查询分页推荐好友列表
    public PageResult recommendation(RecommendUserDto dto) {
        // 1. 获取用户id
        Long userId = UserHolder.getUserId();

        // 2. 调用RecommendUserApi查询数据列表(PageResult -- RecommendUser)
        PageResult pr = recommendUserApi.queryRecommendUserList(dto.getPage(), dto.getPagesize(), userId);

        // 3. 获取分页中的RecommendUser数据列表
        List<RecommendUser> items = (List<RecommendUser>) pr.getItems();

        // 4. 判断列表数据是否为空
        if (items == null) {
            return pr;
        }

//        // 5. 循环RecommendUser推荐列表,根据推荐的用户id查询用户详情
//        List<TodayBest> list = new ArrayList<>();
//        for (RecommendUser item : items) {
//            Long recommendUserId = item.getUserId();
//            UserInfo userInfo = userInfoApi.findById(recommendUserId);
//            if(userInfo != null) {
//                // 条件判断
//                if(!StringUtils.isEmpty(dto.getGender()) && !dto.getGender().equals(userInfo.getGender())) {
//                    continue;
//                }
//                if (dto.getAge() != null && dto.getAge() < userInfo.getAge()) {
//                    continue;
//                }
//                TodayBest vo = TodayBest.init(userInfo, item);
//                list.add(vo);
//            }
//        }
//


        // 5. 提取所有推荐的用户id列表
        List<Long> ids = CollUtil.getFieldValues(items, "userId", Long.class);
        UserInfo userInfo = new UserInfo();
        userInfo.setAge(dto.getAge());
        userInfo.setGender(dto.getGender());

        // 6. 构建查询条件,批量查询所有的用户详情
        Map<Long, UserInfo> map = userInfoApi.findByIds(ids, userInfo);

        // 7. 循环推荐的数据列表,构建vo对象
        List<TodayBest> list = new ArrayList<>();
        for (RecommendUser item : items) {
            UserInfo info = map.get(item.getUserId());
            if(info != null) {
                TodayBest vo = TodayBest.init(info, item);
                list.add(vo);
            }
        }

        // 6. 构造返回值
        pr.setItems(list);
        return pr;
    }
}

⑸. Postman测试

在这里插入图片描述



二、MongoDB集群

1. 单点问题分析

单机 MongoDB 并不适用于企业场景,存在两个问题亟需解决
在这里插入图片描述

  • 单点故障: 单一MongoDB提供服务,在服务器宕机时造成整体应用崩溃
  • 海量数据存储: 单一MongoDB,并不能支持海量数据存储

2. 解决方案

为了 解决单点故障海量数据存储 问题,MongoDB提供了三种集群形式来支持:

  • Master-Slaver(主从集群): 是一种主从副本的模式,目前已经 不推荐 使用
  • Replica Set (副本集群): 模式取代了 Master-Slaver 模式,是一种互为主从的关系。可以解决 单点故障 问题
  • Sharding (分片集群): 可以解决单点故障和 海量数据存储 问题

3. 副本集群

⑴. 执行原理

在这里插入图片描述

  • 包括主节点和副本节点/从节点
  • 主节点只能有一个,可以完成数据读写操作
  • 副本节点可以有多个,只能完成读操作
  • 多节点间有心跳检测,并进行数据同步
  • 主节点宕机后,副本节点选举新的主节点
  • 当主节点挂掉后,副本节点会进行选举,从中选举出一个新的主节点

⑵. 特点

单点故障、适用于中小型应用(数据量适中)、故障转移、读写分离

4. 分片集群

Sharding (分片集群)该模式适合处理大量数据,它将数据分开存储,不同服务器保存不同的数据,所有服务器数据的总和即为整个数据集。

⑴. 分片服务(Shard Server)

在这里插入图片描述

⑵. 配置服务(Config Server)

在这里插入图片描述

⑶. 路由服务(Route Server)

在这里插入图片描述

⑷. 分片策略

MongoDB 通过分片策略,决定数据存储的分片服务器。mongoDB 有两种分片策略,根据集合字段来指定。

  • 范围指定: 将指定字段的数据按照范围进行划分,根据范围获取分片服务器
  • 数据Hash: 将指定字段的数据进行Hash计算,获取存储的分片服务器

⑸. 特点

支持海量数据存储、分片集群内部结构、分片策略


三、发布动态

1. 页面展示

在这里插入图片描述


2. 表设计方案

需求:

  • 查看个人发布的动态
  • 查看好友发布的动态
  • 指定好友可见/不可见

⑴. 方案一

在这里插入图片描述

最简单的设计思路,包含 好友表动态表

  • 保存动态时,向动态表添加记录即可
  • 查询好友动态时
    • 先查询当前用户的所有好友
    • 根据好友,查询好友发布的所有动态

优点: 开发难度较小、易于理解
缺点: 动态对特定好友可见/不可见,实现难度较大、效率较低

⑵. 方案二

在这里插入图片描述
基于设计1的改造版本,在动态表中添加可见人字段

  • 保存动态
  • 查询当前用户好友
    • 保存动态,将 可见好友存入动态表
    • 查询好友动态

优点: 开发难度较小、可以完成所有业务功能
缺点: 索引空间占用、效率较低

⑶. 方案三

在这里插入图片描述
时间线表保存好友发布的动态(ID)

  • 发布动态
  • 查询好友动态

优点: 可以完成所有业务功能、数据结构清晰
缺点: 开发难度较复杂
分片规则: 动态表(根据用户id)、分片时间线表(根据好友id分片)

⑷. 表结构

好友关系表(friends):

{
    "_id": ObjectId("6018bc055098b2230031e2da"),
    "created": NumberLong("1612233733056"),
    "userId": NumberLong("1"),
    "friendId": NumberLong("106"),
    "_class": "com.itheima.domain.mongo.Friend"
}

动态表(movement):

{
    "_id": ObjectId("5e82dc416401952928c211d8"),
    "pid": NumberLong("10064"),
    "userId": NumberLong("6"),
    "textContent": "最悲伤却又是最痛苦的谎言,就是我还好,没有关系。",
    "medias": [
        "https://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/photo/7/1.jpg",
        "https://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/photo/7/1564567349498.jpg",
        "https://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/photo/7/1564567352977.jpg",
        "https://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/photo/7/1564567360406.jpg"
    ],
    "longitude": "121.588627",
    "latitude": "30.935781",
    "state": NumberInt("0"),
    "locationName": "中国上海市奉贤区人民路445弄",
    "created": NumberLong("1585634369493"),
    "_class": "com.tanhua.dubbo.server.pojo.Publish"
}

时间线表(movement_timeline):

{
    "_id": ObjectId("609cf6538743d448c02c61f0"),
    "movementId": ObjectId("609cf6538743d448c02c61ef"),
    "userId": NumberLong("106"),
    "friendId": NumberLong("1"),
    "created": NumberLong("1620899411043"),
    "_class": "com.tanhua.model.mongo.MovementTimeLine"
}

3. 接口文档

在这里插入图片描述


4. 编码实现

在这里插入图片描述

⑴. 搭建提供者环境

①. 实体类

新建 tanhua-model/src/main/java/com/tanhua/model/mongo/Friend.java 文件:

/**
 * 好友表:好友关系表
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "friend")
public class Friend implements java.io.Serializable{

    private static final long serialVersionUID = 6003135946820874230L;
    private ObjectId id;
    private Long userId; //用户id
    private Long friendId; //好友id
    private Long created; //时间

}

新建 tanhua-model/src/main/java/com/tanhua/model/mongo/Movement.java 文件:

//动态详情表
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "movement")
public class Movement implements java.io.Serializable {


    private ObjectId id; //主键id
    // redis实现, 或者使用MongoDB实现
    private Long pid; //Long类型,用于推荐系统的模型(自动增长)
    private Long created; //发布时间
    private Long userId;
    private String textContent; //文字
    private List<String> medias; //媒体数据,图片或小视频 url
    private String longitude; //经度
    private String latitude; //纬度
    private String locationName; //位置名称
    private Integer state = 0;//状态 0:未审(默认),1:通过,2:驳回
}

新建 tanhua-model/src/main/java/com/tanhua/model/mongo/MovementTimeLine.java 文件:

/**
 * 好友时间线表,用于存储好友发布的数据
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "movement_timeLine")
public class MovementTimeLine implements java.io.Serializable {

    private static final long serialVersionUID = 9096178416317502524L;
    private ObjectId id;
    private ObjectId movementId;//动态id
    private Long userId;   //发布动态用户id
    private Long friendId; // 可见好友id
    private Long created; //发布的时间
}
②. API接口

新建 tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/MovementApi.java 文件:

public interface MovementApi {
}
③. API实现类

新建 tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/api/MovementApiImpl.java 文件:

@DubboService
public class MovementApiImpl implements MovementApi {

    @Autowired
    private MongoTemplate mongoTemplate;
}

⑵. 自增序列

①. 实体

新建 tanhua-model/src/main/java/com/tanhua/model/mongo/Sequence.java 文件:

@Document(collection = "sequence")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Sequence {

    private ObjectId id;

    private long seqId; //自增序列

    private String collName;  //集合名称
}
②. 工具类

新建 tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/utils/IdWorker.java 文件:

@Component
public class IdWorker {

    @Autowired
    private MongoTemplate mongoTemplate;

    public Long getNextId(String collName) {
        Query query = new Query(Criteria.where("collName").is(collName));

        Update update = new Update();
        update.inc("seqId", 1);

        FindAndModifyOptions options = new FindAndModifyOptions();
        options.upsert(true);
        options.returnNew(true);

        Sequence sequence = mongoTemplate.findAndModify(query, update, options, Sequence.class);
        return sequence.getSeqId();
    }
}

⑶. 测试

新建 tanhua-dubbo/tanhua-dubbo-mongo/src/test/java/com/tanhua/dubbo/IdWorkerTest.java 测试类:

@RunWith(SpringRunner.class)
@SpringBootTest
public class IdWorkerTest {

    @Autowired
    private IdWorker idWorker;

    @Test
    public void test() {
        Long id = idWorker.getNextId("test");
        System.out.println(id);
    }
}

在这里插入图片描述


⑷. 接口文档

在这里插入图片描述


⑸. Controller获取请求参数

新建 tanhua-app-server/src/main/java/com/tanhua/server/controller/MovementController.java 文件:

@RestController
@RequestMapping("/movements")
public class MovementController {

    @Autowired
    private MovementService movementService;

    /**
     * 发布动态
     * @return
     */
    @PostMapping
    public ResponseEntity movements(Movement movement, MultipartFile imageContent[]) throws IOException {
        movementService.publishMovement(movement, imageContent);
        return ResponseEntity.ok(null);
    }
}

⑹. Service完成逻辑处理

新建 tanhua-app-server/src/main/java/com/tanhua/server/service/MovementService.java 文件:

@Service
public class MovementService {

    @Autowired
    private OssTemplate ossTemplate;

    @DubboReference
    private MovementApi movementApi;

    /**
     * 发布动态
     */
    public void publishMovement(Movement movement, MultipartFile[] imageContent) throws IOException {
        // 1. 判断发布动态的内容是否存在
        if(StringUtils.isEmpty(movement.getTextContent())) {
            throw new BusinessException(ErrorResult.contentError());
        }

        // 2. 获取当前登录的用户id
        Long userId = UserHolder.getUserId();

        // 3. 将文件内容上传到阿里云OSS, 获取请求地址
        List<String> medias = new ArrayList<>();
        for (MultipartFile multipartFile : imageContent) {
            String upload = ossTemplate.upload(multipartFile.getOriginalFilename(), multipartFile.getInputStream());
            medias.add(upload);
        }

        // 4. 将数据封装到movement对象
        movement.setUserId(userId);
        movement.setMedias(medias);

         //5. 调用API完成动态发布
        movementApi.publish(movement);
    }
}

⑺. API接口

编辑 tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/MovementApi.java 文件:

public interface MovementApi {

    // 发布动态
    void publish(Movement movement);
}

⑻. API实现类

编辑 tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/api/MovementApiImpl.java 文件:

@DubboService
public class MovementApiImpl implements MovementApi {

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private IdWorker idWorker;

    // 发布动态
    public void publish(Movement movement) {
        try {
            // 1. 保存动态详情
            // 1.1 设置PID(同名时序列自增)
            movement.setPid(idWorker.getNextId("movement"));
            // 1.2 设置时间
            movement.setCreated(System.currentTimeMillis());
            // 1.3 保存动态
            mongoTemplate.save(movement);

            // 2. 查询当前用户的好友数据
            Criteria criteria = Criteria.where("userId").is(movement.getUserId());
            Query query = Query.query(criteria);
            List<Friend> friends = mongoTemplate.find(query, Friend.class);

            // 3. 循环好友数据, 构建时间线数据存入数据库
            for (Friend friend : friends) {
                MovementTimeLine timeLine = new MovementTimeLine();
                timeLine.setMovementId(movement.getId());
                timeLine.setUserId(friend.getUserId());
                timeLine.setFriendId(friend.getFriendId());
                timeLine.setCreated(System.currentTimeMillis());
                mongoTemplate.save(timeLine);
            }
        } catch (Exception e) {
            // 忽略事物处理
            e.printStackTrace();
        }
    }
}

⑼. 测试类

新建 tanhua-app-server/src/test/java/com/tanhua/test/MovementApiTest.java 文件:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppServerApplication.class)
public class MovementApiTest {

    @DubboReference
    private MovementApi movementApi;

    @Test
    public void testPublish() {
        Movement movement = new Movement();
        movement.setUserId(106l);
        movement.setTextContent("你的酒窝没有酒,我却醉的像条狗");
        List<String> list = new ArrayList<>();
        list.add("https://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/images/tanhua/avatar_1.png");
        list.add("https://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/images/tanhua/avatar_2.png");
        movement.setMedias(list);
        movement.setLatitude("40.066355");
        movement.setLongitude("116.350426");
        movement.setLocationName("中国北京市昌平区建材城西路16号");
        movementApi.publish(movement);
    }
}

在这里插入图片描述


5. 代码优化

大量的时间线数据同步写入的问题如何解决 ?
@Async: Spring提供的异步处理注解,被此注解标注的方法会在新的线程中执行,其实就相当于我们自己new Thread。

⑴. 方法抽取

新建 tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/utils/TimeLineService.java 文件:

@Component
public class TimeLineService {

    @Autowired
    private MongoTemplate mongoTemplate;

    @Async // 异步多线程调用
    public void saveTimeLine(Long userId, ObjectId movementId) {
        // 2. 查询当前用户的好友数据
        Criteria criteria = Criteria.where("userId").is(userId);
        Query query = Query.query(criteria);
        List<Friend> friends = mongoTemplate.find(query, Friend.class);

        // 睡眠时间
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        // 3. 循环好友数据, 构建时间线数据存入数据库
        for (Friend friend : friends) {
            MovementTimeLine timeLine = new MovementTimeLine();
            timeLine.setMovementId(movementId);
            timeLine.setUserId(friend.getUserId());
            timeLine.setFriendId(friend.getFriendId());
            timeLine.setCreated(System.currentTimeMillis());
            mongoTemplate.save(timeLine);
        }
    }
}

⑵. 引导类开启异步调用

编辑 tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/DubboMongoApplication.java 文件:

@SpringBootApplication
@EnableAsync // 开启Spring @Async支持
public class DubboMongoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DubboMongoApplication.class,args);
    }
}

⑶. 调用异步方法

编辑 tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/api/MovementApiImpl.java 文件:

@DubboService
public class MovementApiImpl implements MovementApi {

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private IdWorker idWorker;

    @Autowired
    private TimeLineService timeLineService;

    // 发布动态
    public void publish(Movement movement) {
        try {
            // 1. 保存动态详情
            // 1.1 设置PID(同名时序列自增)
            movement.setPid(idWorker.getNextId("movement"));
            // 1.2 设置时间
            movement.setCreated(System.currentTimeMillis());
            // 1.3 保存动态
            mongoTemplate.save(movement);

//            // 2. 查询当前用户的好友数据
//            Criteria criteria = Criteria.where("userId").is(movement.getUserId());
//            Query query = Query.query(criteria);
//            List<Friend> friends = mongoTemplate.find(query, Friend.class);
//
//            // 3. 循环好友数据, 构建时间线数据存入数据库
//            for (Friend friend : friends) {
//                MovementTimeLine timeLine = new MovementTimeLine();
//                timeLine.setMovementId(movement.getId());
//                timeLine.setUserId(friend.getUserId());
//                timeLine.setFriendId(friend.getFriendId());
//                timeLine.setCreated(System.currentTimeMillis());
//                mongoTemplate.save(timeLine);
//            }

            // 异步多线程调用
            timeLineService.saveTimeLine(movement.getUserId(), movement.getId());
        } catch (Exception e) {
            // 忽略事物处理
            e.printStackTrace();
        }
    }
}

6. 页面效果

在这里插入图片描述



四、查询我的动态

1. 页面展示

在这里插入图片描述


2. 接口文档

在这里插入图片描述


3. 编码实现

⑴. 构建vo对象

新建 tanhua-model/src/main/java/com/tanhua/model/vo/MovementsVo.java 文件:

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

    private String id; //动态id

    private Long userId; //用户id
    private String avatar; //头像
    private String nickname; //昵称
    private String gender; //性别 man woman
    private Integer age; //年龄
    private String[] tags; //标签


    private String textContent; //文字动态
    private String[] imageContent; //图片动态
    private String distance; //距离
    private String createDate; //发布时间 如: 10分钟前
    private Integer likeCount; //点赞数
    private Integer commentCount; //评论数
    private Integer loveCount; //喜欢数


    private Integer hasLiked; //是否点赞(1是,0否)
    private Integer hasLoved; //是否喜欢(1是,0否)


    public static MovementsVo init(UserInfo userInfo, Movement item) {
        MovementsVo vo = new MovementsVo();
        //设置动态数据
        BeanUtils.copyProperties(item, vo);
        vo.setId(item.getId().toHexString());
        //设置用户数据
        BeanUtils.copyProperties(userInfo, vo);
        if(!StringUtils.isEmpty(userInfo.getTags())) {
            vo.setTags(userInfo.getTags().split(","));
        }
        //图片列表
        vo.setImageContent(item.getMedias().toArray(new String[]{}));
        //距离
        vo.setDistance("500米");
        Date date = new Date(item.getCreated());
        vo.setCreateDate(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(date));
        //设置是否点赞(后续处理)
        vo.setHasLoved(0);
        vo.setHasLiked(0);
        return vo;
    }
}

⑵. 控制层获取请求参数

编辑 tanhua-app-server/src/main/java/com/tanhua/server/controller/MovementController.java 文件:

@RestController
@RequestMapping("/movements")
public class MovementController {

    @Autowired
    private MovementService movementService;

    /**
     * 发布动态
     * @return
     */
    @PostMapping
    public ResponseEntity movements(Movement movement, MultipartFile imageContent[]) throws IOException {
        movementService.publishMovement(movement, imageContent);
        return ResponseEntity.ok(null);
    }

    /**
     * 查询我的动态
     */
    @GetMapping("/all")
    public ResponseEntity findByUserId(Long userId,
                                       @RequestParam(defaultValue = "1") Integer page,
                                       @RequestParam(defaultValue = "10") Integer pagesize) {
        PageResult pr = movementService.findByUserId(userId, page, pagesize);
        return ResponseEntity.ok(pr);
    }
}

⑶. Service层封装vo对象

编辑 tanhua-app-server/src/main/java/com/tanhua/server/service/MovementService.java 文件:

@Service
public class MovementService {

    @Autowired
    private OssTemplate ossTemplate;

    @DubboReference
    private MovementApi movementApi;

    @DubboReference
    private UserInfoApi userInfoApi;

    /**
     * 发布动态
     */
    public void publishMovement(Movement movement, MultipartFile[] imageContent) throws IOException {
        // 1. 判断发布动态的内容是否存在
        if(StringUtils.isEmpty(movement.getTextContent())) {
            throw new BusinessException(ErrorResult.contentError());
        }

        // 2. 获取当前登录的用户id
        Long userId = UserHolder.getUserId();

        // 3. 将文件内容上传到阿里云OSS, 获取请求地址
        List<String> medias = new ArrayList<>();
        for (MultipartFile multipartFile : imageContent) {
            // String upload = ossTemplate.upload(multipartFile.getOriginalFilename(), multipartFile.getInputStream());
            // !!! 阿里云OSS收费, 这里暂时跳过
            String upload = "https://img-blog.csdnimg.cn/bb419b491ec1445d84046aa1852946bd.jpeg";
            medias.add(upload);
        }

        // 4. 将数据封装到movement对象
        movement.setUserId(userId);
        movement.setMedias(medias);

         //5. 调用API完成动态发布
        movementApi.publish(movement);
    }

    // 查询我的动态

    public PageResult findByUserId(Long userId, Integer page, Integer pagesize) {
        // 1. 根据用户id, 调用API查询个人动态内容(PageResult -- Movement)
        PageResult pr = movementApi.findByUserId(userId, page, pagesize);

        // 2. 获取PageResult中item列表对象
        List<Movement> items = (List<Movement>) pr.getItems();

        // 3. 非空判断
        if(items == null) {
            return pr;
        }

        // 4. 循环数据列表
        UserInfo userInfo = userInfoApi.findById(userId);
        List<MovementsVo> vos = new ArrayList<>();
        for (Movement item : items) {
            // 5. 一个Movement构建一个VO对象
            MovementsVo vo = MovementsVo.init(userInfo, item);
            vos.add(vo);
        }

        // 6. 构建返回值
        pr.setItems(vos);
        return pr;
    }
}

⑷. API接口

编辑 tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/MovementApi.java 文件:

public interface MovementApi {

    // 发布动态
    void publish(Movement movement);

    // 查询我的动态
    PageResult findByUserId(Long userId, Integer page, Integer pagesize);
}

⑸. API实现类查询动态数据

编辑 tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/api/MovementApiImpl.java 文件:

@DubboService
public class MovementApiImpl implements MovementApi {

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private IdWorker idWorker;

    @Autowired
    private TimeLineService timeLineService;

    // 发布动态
    public void publish(Movement movement) {
        try {
            // 1. 保存动态详情
            // 1.1 设置PID(同名时序列自增)
            movement.setPid(idWorker.getNextId("movement"));
            // 1.2 设置时间
            movement.setCreated(System.currentTimeMillis());
            // 1.3 保存动态
            mongoTemplate.save(movement);

//            // 2. 查询当前用户的好友数据
//            Criteria criteria = Criteria.where("userId").is(movement.getUserId());
//            Query query = Query.query(criteria);
//            List<Friend> friends = mongoTemplate.find(query, Friend.class);
//
//            // 3. 循环好友数据, 构建时间线数据存入数据库
//            for (Friend friend : friends) {
//                MovementTimeLine timeLine = new MovementTimeLine();
//                timeLine.setMovementId(movement.getId());
//                timeLine.setUserId(friend.getUserId());
//                timeLine.setFriendId(friend.getFriendId());
//                timeLine.setCreated(System.currentTimeMillis());
//                mongoTemplate.save(timeLine);
//            }

            // 异步多线程调用
            timeLineService.saveTimeLine(movement.getUserId(), movement.getId());
        } catch (Exception e) {
            // 忽略事物处理
            e.printStackTrace();
        }
    }

    // 查询我的动态
    @Override
    public PageResult findByUserId(Long userId, Integer page, Integer pagesize) {
        Criteria criteria = Criteria.where("userId").is(userId);
        Query query = Query.query(criteria).skip((page  - 1) * pagesize).limit(pagesize)
                .with(Sort.by(Sort.Order.desc("created")));
        List<Movement> movements = mongoTemplate.find(query, Movement.class);
        return new PageResult(page, pagesize, 0l, movements);
    }
}

4. 测试

⑴. postman

在这里插入图片描述

⑵. 页面效果

在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后海 0_o

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值