Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动

读书的意义:在浮华的盛世里,享受清灵;找到一个人给自己讲故事,飘到一片新大陆,不同的规则,不同的人与事,感觉,又活了一辈子。



系列文章目录

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


文章目录

  • 系列文章目录
  • 一、查询动态
    • 1. 查询好友动态
      • ⑴. 页面展示
      • ⑵. 接口文档
      • ⑶. 表结构
      • ⑷. 编码实现
        • ①. Controller层获取请求参数
        • ②. Service层数据封装
        • ③. API接口
        • ④. API实现类 - 根据用户ID查询好友动态详情
      • ⑸. 测试
        • ①. Postman
        • ②. 效果展示
    • 2. 查询推荐动态
      • ⑴. 页面展示
      • ⑵. 需求分析
      • ⑶. 接口文档
      • ⑶. 编码实现
        • ①. Controller层获取请求参数
        • ②. 公共类 - 定义常量
        • ③. Service层获取redis数据
        • ④. API接口
        • ⑤. API实现类 - 获取动态数据
      • ⑷. 测试
        • ①. Postman
        • ②. 页面效果
    • 3. 查询单条动态
      • ⑴. 接口文档
      • ⑵. Controller控制层
      • ⑶. Service层
      • ⑷. API接口
      • ⑸. API接口实现类
      • ⑹. Postman
  • 二、圈子互动
    • 1. 圈子评论
      • ⑴. 页面展示
      • ⑵. 接口文档
      • ⑶. 编码实现
        • ①. 配置提供者环境
          • Ⅰ. 配置实体类
          • Ⅱ. 创建vo对象
          • Ⅲ. 补充Movement对象
          • Ⅳ. 枚举
        • ②. Controller、Service
          • Ⅰ. Controller接收参数
          • Ⅱ. Service调用API
        • ②. API
          • Ⅰ. API接口
          • Ⅱ. 接口实现类
      • ⑷. 测试类
      • ⑸. Postman
    • 2. 评论列表
      • ⑴. 页面效果
      • ⑵. 接口文档
      • ⑶. 编码实现
        • ①. Controller接收请求参数
        • ②. Service层进行vo对象转换
        • ③. API层分页查询数据
          • Ⅰ. API接口
          • Ⅱ. 接口实现类
      • ⑷. Postman
    • 3. 圈子点赞
      • ⑴. 页面展示
      • ⑵. 接口文档
      • ⑶. 编码实现
        • ①. Controller接收请求参数
        • ②. Service将点赞状态保存
        • ③. API判断是否已经点赞
          • Ⅰ. Api接口
          • Ⅱ. Api实现类
        • ④. 添加动态列表点赞状态
      • ⑷. 页面效果
    • 4. 取消点赞
      • ⑴. 页面展示
      • ⑵. 接口文档
      • ⑶. 编码实现
        • ①. Controller 接收请求参数
        • ②. Service 删除点赞状态
        • ③. APi 删除comment数据
          • Ⅰ. API接口
          • Ⅱ. API接口实现类
      • ⑷. 页面效果
    • 5. 圈子喜欢/不喜欢
      • ⑴. 页面展示
      • ⑵. 接口文档
      • ⑶. 编码实现
        • ①. Controller
        • ②. Service - 调用API编辑喜欢状态
        • ③. Service - 修改动态列表喜欢状态
      • ⑷. 页面效果


一、查询动态

1. 查询好友动态

⑴. 页面展示

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第1张图片


⑵. 接口文档

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第2张图片


⑶. 表结构

  • 好友表:记录双向好友关系
  • 动态详情表:完整圈子动态内容
  • 时间线表:记录用户,好友,动态的关联关系

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第3张图片


⑷. 编码实现

①. 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);
    }

    /**
     * 查询我的动态
     */
    @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);
    }

    /**
     * 查询好友动态
     */
    @GetMapping()
    public ResponseEntity movements( @RequestParam(defaultValue = "1") Integer page,
                                     @RequestParam(defaultValue = "10") Integer pagesize) {
        PageResult pr = movementService.findFriendMovements(page, pagesize);
        return ResponseEntity.ok(pr);
    }
}

②. 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;

    @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://img0.baidu.com/it/u=1501084209,93021381&fm=253&fmt=auto&app=138&f=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;
    }

    // 查询好友动态
    public PageResult findFriendMovements(Integer page, Integer pagesize) {
        // 1. 获取当前用户id
        Long userId = UserHolder.getUserId();

        // 2. 调用API查询当前用户好友发布的动态列表
        List<Movement> list = movementApi.findFriendMovements(page, pagesize, userId);

        // 3. 判断列表是否为空
        // if(list == null || list.isEmpty()) {
         if(CollUtil.isEmpty(list)) {
            return new PageResult();
        }

        // 4. 提取动态发布人的id列表
        List<Long> userIds = CollUtil.getFieldValues(list, "userId", Long.class);

        // 5. 根据用户id列表获取用户详情
        Map<Long, UserInfo> map = userInfoApi.findByIds(userIds, null);

        // 6. 一个Movement构造一个vo对象
        List<MovementsVo> vos = new ArrayList<>();
        for (Movement movement : list) {
            UserInfo userInfo = map.get(movement.getUserId());
            if(userInfo != null) {
                MovementsVo vo = MovementsVo.init(userInfo, movement);
                vos.add(vo);
            }
        }
        
        // 7. 构造PageResult并返回
        return new PageResult(page, pagesize, 0l, vos);
    }
}

③. API接口

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

public interface MovementApi {

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

    // 根据用户id,查询此用户发布的动态数据列表
    PageResult findByUserId(Long userId, Integer page, Integer pagesize);

    // 根据用户id,查询用户好友发布的动态数据列表
    List<Movement> findFriendMovements(Integer page, Integer pagesize, Long userId);
}

④. API实现类 - 根据用户ID查询好友动态详情

编辑 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 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();
        }
    }

    // 根据用户id,查询当前用户发布的动态数据列表
    @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);
    }

    /**
     * 查询用户好友发布的动态数据列表
     * @param friendId 当前操作用户id
     * @return
     */
    public List<Movement> findFriendMovements(Integer page, Integer pagesize, Long friendId) {
        // 1. 根据friendId查询时间线表
        Query query = Query.query(Criteria.where("friendId").is(friendId))
                .skip((page - 1) * pagesize).limit(pagesize)
                .with(Sort.by(Sort.Order.desc("created")));
        List<MovementTimeLine> lineList = mongoTemplate.find(query, MovementTimeLine.class);

        // 2. 提取动态id列表
        List<ObjectId> list = CollUtil.getFieldValues(lineList, "movementId", ObjectId.class);

        // 3. 根据动态id查询动态详情
        Query movementQuery = Query.query(Criteria.where("id").is(list));
        List<Movement> movementList = mongoTemplate.find(movementQuery, Movement.class);

        // 4. 返回
        return movementList;
    }
}

⑸. 测试

①. Postman

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第4张图片

②. 效果展示


2. 查询推荐动态

⑴. 页面展示

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第5张图片

⑵. 需求分析

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第6张图片

  • 推荐动态是通过推荐系统,根据个人偏好进行实时计算得出的结果
  • 推荐系统采集用户的行为特征(日常操作行为): 查看、点赞、评论
    • 大数据推荐系统实时计算,统计推荐数据
    • 将结果写入redis
    • 查询推荐动态

⑶. 接口文档

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第7张图片


⑶. 编码实现

①. 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);
    }

    /**
     * 查询我的动态
     */
    @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);
    }

    /**
     * 查询好友动态
     */
    @GetMapping()
    public ResponseEntity movements( @RequestParam(defaultValue = "1") Integer page,
                                     @RequestParam(defaultValue = "10") Integer pagesize) {
        PageResult pr = movementService.findFriendMovements(page, pagesize);
        return ResponseEntity.ok(pr);
    }

    /**
     * 查询推荐动态
     */
    @GetMapping("/recommend")
    public ResponseEntity recommend( @RequestParam(defaultValue = "1") Integer page,
                                     @RequestParam(defaultValue = "10") Integer pagesize) {
        PageResult pr = movementService.findRecommendMovements(page, pagesize);
        return ResponseEntity.ok(pr);
    }
}

②. 公共类 - 定义常量

新建 tanhua-commons/src/main/java/com/tanhua/commons/utils/Constants.java 文件:

//常量定义
public class Constants {

    //手机APP短信验证码CHECK_CODE_
    public static final String SMS_CODE = "CHECK_CODE_";

	//推荐动态
	public static final String MOVEMENTS_RECOMMEND = "MOVEMENTS_RECOMMEND_";

    //推荐视频
    public static final String VIDEOS_RECOMMEND = "VIDEOS_RECOMMEND_";

	//圈子互动KEY
	public static final String MOVEMENTS_INTERACT_KEY = "MOVEMENTS_INTERACT_";

    //动态点赞用户HashKey
    public static final String MOVEMENT_LIKE_HASHKEY = "MOVEMENT_LIKE_";

    //动态喜欢用户HashKey
    public static final String MOVEMENT_LOVE_HASHKEY = "MOVEMENT_LOVE_";

    //视频点赞用户HashKey
    public static final String VIDEO_LIKE_HASHKEY = "VIDEO_LIKE";

    //访问用户
    public static final String VISITORS = "VISITORS";

    //关注用户
    public static final String FOCUS_USER = "FOCUS_USER_{}_{}";

	//初始化密码
    public static final String INIT_PASSWORD = "123456";

    //环信用户前缀
    public static final String HX_USER_PREFIX = "hx";

    //jwt加密盐
    public static final String JWT_SECRET = "itcast";

    //jwt超时时间
    public static final int JWT_TIME_OUT = 3_600;
}

③. Service层获取redis数据

编辑 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;

     @Autowired
     private RedisTemplate<String, String> redisTemplate;

    /**
     * 发布动态
     */
    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://img0.baidu.com/it/u=1501084209,93021381&fm=253&fmt=auto&app=138&f=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;
    }

    // 查询好友动态
    public PageResult findFriendMovements(Integer page, Integer pagesize) {
        // 1. 获取当前用户id
        Long userId = UserHolder.getUserId();

        // 2. 调用API查询当前用户好友发布的动态列表
        List<Movement> list = movementApi.findFriendMovements(page, pagesize, userId);

        return getPageResult(page, pagesize, list);
    }

    // 公共方法
    private PageResult getPageResult(Integer page, Integer pagesize, List<Movement> list) {
        // 3. 判断列表是否为空
        // if(list == null || list.isEmpty()) {
        if(CollUtil.isEmpty(list)) {
            return new PageResult();
       }

        // 4. 提取动态发布人的id列表
        List<Long> userIds = CollUtil.getFieldValues(list, "userId", Long.class);

        // 5. 根据用户id列表获取用户详情
        Map<Long, UserInfo> map = userInfoApi.findByIds(userIds, null);

        // 6. 一个Movement构造一个vo对象
        List<MovementsVo> vos = new ArrayList<>();
        for (Movement movement : list) {
            UserInfo userInfo = map.get(movement.getUserId());
            if(userInfo != null) {
                MovementsVo vo = MovementsVo.init(userInfo, movement);
                vos.add(vo);
            }
        }

        // 7. 构造PageResult并返回
        return new PageResult(page, pagesize, 0l, vos);
    }

    // 查询推荐动态
    public PageResult findRecommendMovements(Integer page, Integer pagesize) {
        // 1. 从redis从获取推荐数据
        String redisKey = Constants.MOVEMENTS_RECOMMEND + UserHolder.getUserId();
        String redisValue = redisTemplate.opsForValue().get(redisKey);

        // 2. 判断推荐数据是否存在
        List<Movement> list = Collections.EMPTY_LIST;

        if(StringUtils.isEmpty(redisValue)) {
            // 3. 如果不存在, 调用API随机构造10条动态数据
            list = movementApi.randomMovements(pagesize);

        } else {
            // 4. 如果存在, 处理pid数据  "16,17,18,19,20,21,10015,10020,10040,10064,10092,10093,10099,10067"
            String[] values = redisValue.split(",");
            // 4.1 判断当前页的起始条数是否小于数组的总数
            if((page - 1) * pagesize < values.length) {
                List<Long> pids = Arrays.stream(values).skip((page - 1) * pagesize).limit(pagesize)
                        .map(e -> Long.valueOf(e))
                        .collect(Collectors.toList());

                // 5. 调用API根据PID数组查询动态数据
                list = movementApi.findMovementByPids(pids);
            }
        }
        // 6. 调用公共方法构造返回值
        return getPageResult(page, pagesize, list);
    }
}

④. API接口

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

public interface MovementApi {

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

    // 根据用户id,查询此用户发布的动态数据列表
    PageResult findByUserId(Long userId, Integer page, Integer pagesize);

    // 根据用户id,查询用户好友发布的动态数据列表
    List<Movement> findFriendMovements(Integer page, Integer pagesize, Long userId);

    // 随机获取多条动态数据
    List<Movement> randomMovements(Integer counts);

    // 根据pid数组查询动态
    List<Movement> findMovementByPids(List<Long> pids);
}

⑤. 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 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();
        }
    }

    // 根据用户id,查询当前用户发布的动态数据列表
    @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);
    }

    /**
     * 查询用户好友发布的动态数据列表
     * @param friendId 当前操作用户id
     * @return
     */
    public List<Movement> findFriendMovements(Integer page, Integer pagesize, Long friendId) {
        // 1. 根据friendId查询时间线表
        Query query = Query.query(Criteria.where("friendId").is(friendId))
                .skip((page - 1) * pagesize).limit(pagesize)
                .with(Sort.by(Sort.Order.desc("created")));
        List<MovementTimeLine> lineList = mongoTemplate.find(query, MovementTimeLine.class);

        // 2. 提取动态id列表
        List<ObjectId> list = CollUtil.getFieldValues(lineList, "movementId", ObjectId.class);

        // 3. 根据动态id查询动态详情
        Query movementQuery = Query.query(Criteria.where("id").is(list));
        List<Movement> movementList = mongoTemplate.find(movementQuery, Movement.class);

        // 4. 返回
        return movementList;
    }

    // 随机获取多条动态数据
    @Override
    public List<Movement> randomMovements(Integer counts) {
        // 1. 创建统计对象, 设置统计参数
        TypedAggregation aggregation = Aggregation.newAggregation(Movement.class, Aggregation.sample(counts));

        // 2. 调用mongoTemplate方法进行统计
        AggregationResults<Movement> results = mongoTemplate.aggregate(aggregation, Movement.class);

        // 3. 获取统计结果
        return results.getMappedResults();
    }

    // 根据pid数组获取动态数据
    @Override
    public List<Movement> findMovementByPids(List<Long> pids) {
        Query query = Query.query(Criteria.where("pid").in(pids));
        return mongoTemplate.find(query, Movement.class);
    }
}

⑷. 测试

①. Postman

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第8张图片

②. 页面效果

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第9张图片


3. 查询单条动态

⑴. 接口文档

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第10张图片


⑵. 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);
    }

    /**
     * 查询我的动态
     */
    @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);
    }

    /**
     * 查询好友动态
     */
    @GetMapping()
    public ResponseEntity movements( @RequestParam(defaultValue = "1") Integer page,
                                     @RequestParam(defaultValue = "10") Integer pagesize) {
        PageResult pr = movementService.findFriendMovements(page, pagesize);
        return ResponseEntity.ok(pr);
    }

    /**
     * 查询推荐动态
     */
    @GetMapping("/recommend")
    public ResponseEntity recommend( @RequestParam(defaultValue = "1") Integer page,
                                     @RequestParam(defaultValue = "10") Integer pagesize) {
        PageResult pr = movementService.findRecommendMovements(page, pagesize);
        return ResponseEntity.ok(pr);
    }

    /**
     * 查询单条动态
     */
    @GetMapping("/{id}")
    public ResponseEntity findById(@PathVariable("id") String movementId) {
        MovementsVo vo = movementService.findById(movementId);
        return ResponseEntity.ok(vo);
    }
}

⑶. 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;

    @DubboReference
    private UserInfoApi userInfoApi;

     @Autowired
     private RedisTemplate<String, String> redisTemplate;

    /**
     * 发布动态
     */
    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://img0.baidu.com/it/u=1501084209,93021381&fm=253&fmt=auto&app=138&f=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;
    }

    // 查询好友动态
    public PageResult findFriendMovements(Integer page, Integer pagesize) {
        // 1. 获取当前用户id
        Long userId = UserHolder.getUserId();

        // 2. 调用API查询当前用户好友发布的动态列表
        List<Movement> list = movementApi.findFriendMovements(page, pagesize, userId);

        return getPageResult(page, pagesize, list);
    }

    // 公共方法
    private PageResult getPageResult(Integer page, Integer pagesize, List<Movement> list) {
        // 3. 判断列表是否为空
        // if(list == null || list.isEmpty()) {
        if(CollUtil.isEmpty(list)) {
            return new PageResult();
       }

        // 4. 提取动态发布人的id列表
        List<Long> userIds = CollUtil.getFieldValues(list, "userId", Long.class);

        // 5. 根据用户id列表获取用户详情
        Map<Long, UserInfo> map = userInfoApi.findByIds(userIds, null);

        // 6. 一个Movement构造一个vo对象
        List<MovementsVo> vos = new ArrayList<>();
        for (Movement movement : list) {
            UserInfo userInfo = map.get(movement.getUserId());
            if(userInfo != null) {
                MovementsVo vo = MovementsVo.init(userInfo, movement);
                vos.add(vo);
            }
        }

        // 7. 构造PageResult并返回
        return new PageResult(page, pagesize, 0l, vos);
    }

    // 查询推荐动态
    public PageResult findRecommendMovements(Integer page, Integer pagesize) {
        // 1. 从redis从获取推荐数据
        String redisKey = Constants.MOVEMENTS_RECOMMEND + UserHolder.getUserId();
        String redisValue = redisTemplate.opsForValue().get(redisKey);

        // 2. 判断推荐数据是否存在
        List<Movement> list = Collections.EMPTY_LIST;

        if(StringUtils.isEmpty(redisValue)) {
            // 3. 如果不存在, 调用API随机构造10条动态数据
            list = movementApi.randomMovements(pagesize);

        } else {
            // 4. 如果存在, 处理pid数据  "16,17,18,19,20,21,10015,10020,10040,10064,10092,10093,10099,10067"
            String[] values = redisValue.split(",");
            // 4.1 判断当前页的起始条数是否小于数组的总数
            if((page - 1) * pagesize < values.length) {
                List<Long> pids = Arrays.stream(values).skip((page - 1) * pagesize).limit(pagesize)
                        .map(e -> Long.valueOf(e))
                        .collect(Collectors.toList());

                // 5. 调用API根据PID数组查询动态数据
                list = movementApi.findMovementByPids(pids);
            }
        }
        // 6. 调用公共方法构造返回值
        return getPageResult(page, pagesize, list);
    }

    // 查询单条动态
    public MovementsVo findById(String movementId) {
        // 1. 调用API查询动态详情
        Movement movement = movementApi.findById(movementId);

        // 2. 转换vo对象
        if(movement != null) {
            UserInfo userInfo = userInfoApi.findById(movement.getUserId());
            return MovementsVo.init(userInfo, movement);
        } else {
            return null;
        }
    }
}

⑷. API接口

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

public interface MovementApi {

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

    // 根据用户id,查询此用户发布的动态数据列表
    PageResult findByUserId(Long userId, Integer page, Integer pagesize);

    // 根据用户id,查询用户好友发布的动态数据列表
    List<Movement> findFriendMovements(Integer page, Integer pagesize, Long userId);

    // 随机获取多条动态数据
    List<Movement> randomMovements(Integer counts);

    // 根据pid数组查询动态
    List<Movement> findMovementByPids(List<Long> pids);

    // 查询单条动态
    Movement findById(String movementId);
}

⑸. 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 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();
        }
    }

    // 根据用户id,查询当前用户发布的动态数据列表
    @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);
    }

    /**
     * 查询用户好友发布的动态数据列表
     * @param friendId 当前操作用户id
     * @return
     */
    public List<Movement> findFriendMovements(Integer page, Integer pagesize, Long friendId) {
        // 1. 根据friendId查询时间线表
        Query query = Query.query(Criteria.where("friendId").is(friendId))
                .skip((page - 1) * pagesize).limit(pagesize)
                .with(Sort.by(Sort.Order.desc("created")));
        List<MovementTimeLine> lineList = mongoTemplate.find(query, MovementTimeLine.class);

        // 2. 提取动态id列表
        List<ObjectId> list = CollUtil.getFieldValues(lineList, "movementId", ObjectId.class);

        // 3. 根据动态id查询动态详情
        Query movementQuery = Query.query(Criteria.where("id").is(list));
        List<Movement> movementList = mongoTemplate.find(movementQuery, Movement.class);

        // 4. 返回
        return movementList;
    }

    // 随机获取多条动态数据
    @Override
    public List<Movement> randomMovements(Integer counts) {
        // 1. 创建统计对象, 设置统计参数
        TypedAggregation aggregation = Aggregation.newAggregation(Movement.class, Aggregation.sample(counts));

        // 2. 调用mongoTemplate方法进行统计
        AggregationResults<Movement> results = mongoTemplate.aggregate(aggregation, Movement.class);

        // 3. 获取统计结果
        return results.getMappedResults();
    }

    // 根据pid数组获取动态数据
    @Override
    public List<Movement> findMovementByPids(List<Long> pids) {
        Query query = Query.query(Criteria.where("pid").in(pids));
        return mongoTemplate.find(query, Movement.class);
    }

    // 查询单条动态
    @Override
    public Movement findById(String movementId) {
        return mongoTemplate.findById(movementId, Movement.class);
    }
}

⑹. Postman

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第11张图片



二、圈子互动

1. 圈子评论

⑴. 页面展示


⑵. 接口文档

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第12张图片


⑶. 编码实现

①. 配置提供者环境

Ⅰ. 配置实体类

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

/**
 * 圈子互动表(点赞,评论,喜欢)
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "comment")
public class Comment implements java.io.Serializable{
    
    private ObjectId id;
    private ObjectId publishId;    //发布id
    private Integer commentType;   //评论类型,1-点赞,2-评论,3-喜欢
    private String content;        //评论内容  
    private Long userId;           //评论人   
    private Long publishUserId;    //被评论人ID
    private Long created; 		   //发表时间
    private Integer likeCount = 0; //当前评论的点赞数
    
}

Ⅱ. 创建vo对象

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

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

    private String id; //评论id
    private String avatar; //头像
    private String nickname; //昵称


    private String content; //评论
    private String createDate; //评论时间
    private Integer likeCount; //点赞数
    private Integer hasLiked; //是否点赞(1是,0否)

    public static CommentVo init(UserInfo userInfo, Comment item) {
        CommentVo vo = new CommentVo();
        BeanUtils.copyProperties(userInfo, vo);
        BeanUtils.copyProperties(item, vo);
        vo.setHasLiked(0);
        Date date = new Date(item.getCreated());
        vo.setCreateDate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date));
        vo.setId(item.getId().toHexString());
        return vo;
    }
}

Ⅲ. 补充Movement对象

编辑 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:驳回


    //补充字段
    private Integer likeCount = 0; //点赞数
    private Integer commentCount = 0; //评论数
    private Integer loveCount = 0; //喜欢数

    //根据评论类型,获取对应的互动数量
    public Integer statisCount(Integer commentType) {
        if (commentType == CommentType.LIKE.getType()) {
            return this.likeCount;
        } else if (commentType == CommentType.COMMENT.getType()) {
            return this.commentCount;
        } else {
            return loveCount;
        }
    }
}

Ⅳ. 枚举

新建 tanhua-model/src/main/java/com/tanhua/model/enums/CommentType.java 文件:

/**
 * 评论类型:1-点赞,2-评论,3-喜欢
 */
public enum CommentType {

    LIKE(1), COMMENT(2), LOVE(3);

    int type;

    CommentType(int type) {
        this.type = type;
    }

    public int getType() {
        return type;
    }
}

②. Controller、Service

Ⅰ. Controller接收参数

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

@RestController
@RequestMapping("/comments")
public class CommentsController {

    @Autowired
    private CommentsService commentsService;

    /**
     * 发布评论
     */
    @PostMapping
    public ResponseEntity saveComments(@RequestBody Map map) {
        String movementId = (String) map.get("movementId");
        String comment = (String) map.get("comment");
        commentsService.saveComments(movementId, comment);
        return ResponseEntity.ok(null);
    }
}

Ⅱ. Service调用API

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

@Service
@Slf4j
public class CommentsService {

    @DubboReference
    private CommentApi commentApi;

    // 发布评论
    public void saveComments(String movementId, String comment) {
        // 1. 获取操作用户id
        Long userId = UserHolder.getUserId();

        // 2. 构造Comment
        Comment comment1 = new Comment();
        comment1.setPublishId(new ObjectId(movementId));
        comment1.setCommentType(CommentType.COMMENT.getType());
        comment1.setContent(comment);
        comment1.setUserId(userId);
        comment1.setCreated(System.currentTimeMillis());

        // 3. 调用Service保存评论
        Integer commentCount = commentApi.save(comment1);
        log.info("commentCount =" + commentCount);
    }
}

②. API

Ⅰ. API接口

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

public interface CommentApi {

    // 发布评论, 并获得评论数量
    Integer save(Comment comment);
}

Ⅱ. 接口实现类

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

@DubboService
public class CommentApiImpl implements CommentApi{

    @Autowired
    private MongoTemplate mongoTemplate;

    // 发布评论, 获取评论数量
    public Integer save(Comment comment) {
        // 1. 查询动态
        Movement movement = mongoTemplate.findById(comment.getPublishId(), Movement.class);

        // 2. 向Comment对象设置被评论人属性
        if(movement != null) {
            comment.setPublishUserId(movement.getUserId());
        }

        // 3. 保存到数据库
        mongoTemplate.save(comment);

        // 4. 更新动态表中的对应字段
        Query query = Query.query(Criteria.where("id").is(comment.getPublishId()));
        Update update = new Update();
        if(comment.getCommentType() == CommentType.LIKE.getType()) { // 点赞
            update.inc("likeCount", 1);
        } else if (comment.getCommentType() == CommentType.COMMENT.getType()) { // 评论
            update.inc("commentCount", 1);
        } else { // 喜欢
            update.inc("loveCount", 1);
        }
        // 设置更新参数
        FindAndModifyOptions options = new FindAndModifyOptions();
        options.returnNew(true); // 获取更新后的最新数据
        Movement modify = mongoTemplate.findAndModify(query, update, options, Movement.class);

        // 5. 获取最新的评论数量, 并返回
        return modify.statisCount(comment.getCommentType());
    }
}

⑷. 测试类

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

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

    @DubboReference
    private CommentApi commentApi;

    @Test
    public void testSave() {
        Comment comment = new Comment();
        comment.setCommentType(CommentType.COMMENT.getType());
        comment.setUserId(106l);
        comment.setCreated(System.currentTimeMillis());
        comment.setContent("测试评论");
        comment.setPublishId(new ObjectId("5e82dc3e6401952928c211a3"));
        commentApi.save(comment);
    }
}

⑸. Postman

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第13张图片


2. 评论列表

⑴. 页面效果

⑵. 接口文档

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第14张图片

⑶. 编码实现

①. Controller接收请求参数

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

@RestController
@RequestMapping("/comments")
public class CommentsController {

    @Autowired
    private CommentsService commentsService;

    /**
     * 发布评论
     */
    @PostMapping
    public ResponseEntity saveComments(@RequestBody Map map) {
        String movementId = (String) map.get("movementId");
        String comment = (String) map.get("comment");
        commentsService.saveComments(movementId, comment);
        return ResponseEntity.ok(null);
    }

    /**
     * 分页查询评论列表
     */
    @GetMapping
    public ResponseEntity findComments(@RequestParam(defaultValue = "1") Integer page,
                                       @RequestParam(defaultValue = "10") Integer pagesize,
                                       String movementId) {
        PageResult pr = commentsService.findComments(movementId, page, pagesize);
        return ResponseEntity.ok(pr);
    }
}

②. Service层进行vo对象转换

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

@Service
@Slf4j
public class CommentsService {

    @DubboReference
    private CommentApi commentApi;

    @DubboReference
    private UserInfoApi userInfoApi;

    // 发布评论
    public void saveComments(String movementId, String comment) {
        // 1. 获取操作用户id
        Long userId = UserHolder.getUserId();

        // 2. 构造Comment
        Comment comment1 = new Comment();
        comment1.setPublishId(new ObjectId(movementId));
        comment1.setCommentType(CommentType.COMMENT.getType());
        comment1.setContent(comment);
        comment1.setUserId(userId);
        comment1.setCreated(System.currentTimeMillis());

        // 3. 调用Service保存评论
        Integer commentCount = commentApi.save(comment1);
        log.info("commentCount =" + commentCount);
    }

    // 分页查询评论列表
    public PageResult findComments(String movementId, Integer page, Integer pagesize) {
        // 1. 调用API查询分页列表
        List<Comment> list = commentApi.findComments(movementId, CommentType.COMMENT, page, pagesize);

        // 2. 判断list集合是否存在
        if(CollUtil.isEmpty(list)) {
            return new PageResult();
        }

        // 3. 提取所有的用户id, 调用UserInfoApi查询用户详情
        List<Long> userIds = CollUtil.getFieldValues(list, "userId", Long.class);
        Map<Long, UserInfo> map = userInfoApi.findByIds(userIds, null);

        // 4. 构造vo对象
        List<CommentVo> vos = new ArrayList<>();
        for (Comment comment : list) {
            UserInfo userInfo = map.get(comment.getUserId());
            if(userInfo != null) {
                CommentVo vo = CommentVo.init(userInfo, comment);
                vos.add(vo);
            }
        }

        // 5. 构造返回值
        return new PageResult(page, pagesize, 0l, vos);
    }
}

③. API层分页查询数据

Ⅰ. API接口

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

public interface CommentApi {

    // 发布评论, 并获得评论数量
    Integer save(Comment comment);

    // 分页查询评论列表
    List<Comment> findComments(String movementId, CommentType commentType, Integer page, Integer pagesize);
}
Ⅱ. 接口实现类

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

@DubboService
public class CommentApiImpl implements CommentApi{

    @Autowired
    private MongoTemplate mongoTemplate;

    // 发布评论, 获取评论数量
    public Integer save(Comment comment) {
        // 1. 查询动态
        Movement movement = mongoTemplate.findById(comment.getPublishId(), Movement.class);

        // 2. 向Comment对象设置被评论人属性
        if(movement != null) {
            comment.setPublishUserId(movement.getUserId());
        }

        // 3. 保存到数据库
        mongoTemplate.save(comment);

        // 4. 更新动态表中的对应字段
        Query query = Query.query(Criteria.where("id").is(comment.getPublishId()));
        Update update = new Update();
        if(comment.getCommentType() == CommentType.LIKE.getType()) { // 点赞
            update.inc("likeCount", 1);
        } else if (comment.getCommentType() == CommentType.COMMENT.getType()) { // 评论
            update.inc("commentCount", 1);
        } else { // 喜欢
            update.inc("loveCount", 1);
        }
        // 设置更新参数
        FindAndModifyOptions options = new FindAndModifyOptions();
        options.returnNew(true); // 获取更新后的最新数据
        Movement modify = mongoTemplate.findAndModify(query, update, options, Movement.class);

        // 5. 获取最新的评论数量, 并返回
        return modify.statisCount(comment.getCommentType());
    }

    // 分页查询评论列表
    public List<Comment> findComments(String movementId, CommentType commentType, Integer page, Integer pagesize) {
        // 1. 构造查询条件
        Query query = Query.query(Criteria.where("publishId").is(new ObjectId(movementId))
                .and("commentType").is(commentType.getType()))
                .skip(( page - 1) * pagesize)
                .limit(pagesize)
                .with(Sort.by(Sort.Order.desc("created")));

        // 2. 查询并发挥
        return mongoTemplate.find(query, Comment.class);
    }
}

⑷. Postman

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第15张图片


3. 圈子点赞

⑴. 页面展示

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第16张图片

⑵. 接口文档

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第17张图片

⑶. 编码实现

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第18张图片

①. Controller接收请求参数

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

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

    @Autowired
    private MovementService movementService;

    @Autowired
    private CommentsService commentsService;

    /**
     * 发布动态
     * @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);
    }

    /**
     * 查询好友动态
     */
    @GetMapping()
    public ResponseEntity movements( @RequestParam(defaultValue = "1") Integer page,
                                     @RequestParam(defaultValue = "10") Integer pagesize) {
        PageResult pr = movementService.findFriendMovements(page, pagesize);
        return ResponseEntity.ok(pr);
    }

    /**
     * 查询推荐动态
     */
    @GetMapping("/recommend")
    public ResponseEntity recommend( @RequestParam(defaultValue = "1") Integer page,
                                     @RequestParam(defaultValue = "10") Integer pagesize) {
        PageResult pr = movementService.findRecommendMovements(page, pagesize);
        return ResponseEntity.ok(pr);
    }

    /**
     * 查询单条动态
     */
    @GetMapping("/{id}")
    public ResponseEntity findById(@PathVariable("id") String movementId) {
        MovementsVo vo = movementService.findById(movementId);
        return ResponseEntity.ok(vo);
    }

    /**
     * 点赞
     */
    @GetMapping("/{id}/like")
    public ResponseEntity like(@PathVariable("id") String movementId) {
        Integer likeCount = commentsService.likeComment(movementId);
        return ResponseEntity.ok(likeCount);
    }
}

②. Service将点赞状态保存

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

@Service
@Slf4j
public class CommentsService {

    @DubboReference
    private CommentApi commentApi;

    @DubboReference
    private UserInfoApi userInfoApi;

    @Autowired
    private RedisTemplate redisTemplate;

    // 发布评论
    public void saveComments(String movementId, String comment) {
        // 1. 获取操作用户id
        Long userId = UserHolder.getUserId();

        // 2. 构造Comment
        Comment comment1 = new Comment();
        comment1.setPublishId(new ObjectId(movementId));
        comment1.setCommentType(CommentType.COMMENT.getType());
        comment1.setContent(comment);
        comment1.setUserId(userId);
        comment1.setCreated(System.currentTimeMillis());

        // 3. 调用Service保存评论
        Integer commentCount = commentApi.save(comment1);
        log.info("commentCount =" + commentCount);
    }

    // 分页查询评论列表
    public PageResult findComments(String movementId, Integer page, Integer pagesize) {
        // 1. 调用API查询分页列表
        List<Comment> list = commentApi.findComments(movementId, CommentType.COMMENT, page, pagesize);

        // 2. 判断list集合是否存在
        if(CollUtil.isEmpty(list)) {
            return new PageResult();
        }

        // 3. 提取所有的用户id, 调用UserInfoApi查询用户详情
        List<Long> userIds = CollUtil.getFieldValues(list, "userId", Long.class);
        Map<Long, UserInfo> map = userInfoApi.findByIds(userIds, null);

        // 4. 构造vo对象
        List<CommentVo> vos = new ArrayList<>();
        for (Comment comment : list) {
            UserInfo userInfo = map.get(comment.getUserId());
            if(userInfo != null) {
                CommentVo vo = CommentVo.init(userInfo, comment);
                vos.add(vo);
            }
        }

        // 5. 构造返回值
        return new PageResult(page, pagesize, 0l, vos);
    }

    // 点赞
    public Integer likeComment(String movementId) {
        // 1. 调用API查询用户是否已点赞
        Boolean hasComment = commentApi.hasComment(movementId, UserHolder.getUserId(), CommentType.LIKE);

        // 2. 如果已点赞,抛出异常
        if(hasComment) {
            throw new BusinessException(ErrorResult.likeError());
        }

        // 3. 调用API保存数据到mogonDB
        Comment comment = new Comment();
        comment.setPublishId(new ObjectId(movementId));
        comment.setCommentType(CommentType.LIKE.getType());
        comment.setUserId(UserHolder.getUserId());
        comment.setCreated(System.currentTimeMillis());
        Integer count = commentApi.save(comment);

        // 4. 拼接redis的key, 将用户的点赞状态存入redis
        String key = Constants.MOVEMENTS_INTERACT_KEY + movementId;
        String hashKey = Constants.MOVEMENT_LIKE_HASHKEY + UserHolder.getUserId();
        redisTemplate.opsForHash().put(key, hashKey, "1");
        return count;
    }
}

③. API判断是否已经点赞

Ⅰ. Api接口

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

public interface CommentApi {

    // 发布评论, 并获得评论数量
    Integer save(Comment comment);

    // 分页查询评论列表
    List<Comment> findComments(String movementId, CommentType commentType, Integer page, Integer pagesize);

    // 判断comment数据是否存在
    Boolean hasComment(String movementId, Long userId, CommentType commentType);
}

Ⅱ. Api实现类

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

@DubboService
public class CommentApiImpl implements CommentApi{

    @Autowired
    private MongoTemplate mongoTemplate;

    // 发布评论, 获取评论数量
    public Integer save(Comment comment) {
        // 1. 查询动态
        Movement movement = mongoTemplate.findById(comment.getPublishId(), Movement.class);

        // 2. 向Comment对象设置被评论人属性
        if(movement != null) {
            comment.setPublishUserId(movement.getUserId());
        }

        // 3. 保存到数据库
        mongoTemplate.save(comment);

        // 4. 更新动态表中的对应字段
        Query query = Query.query(Criteria.where("id").is(comment.getPublishId()));
        Update update = new Update();
        if(comment.getCommentType() == CommentType.LIKE.getType()) { // 点赞
            update.inc("likeCount", 1);
        } else if (comment.getCommentType() == CommentType.COMMENT.getType()) { // 评论
            update.inc("commentCount", 1);
        } else { // 喜欢
            update.inc("loveCount", 1);
        }
        // 设置更新参数
        FindAndModifyOptions options = new FindAndModifyOptions();
        options.returnNew(true); // 获取更新后的最新数据
        Movement modify = mongoTemplate.findAndModify(query, update, options, Movement.class);

        // 5. 获取最新的评论数量, 并返回
        return modify.statisCount(comment.getCommentType());
    }

    // 分页查询评论列表
    public List<Comment> findComments(String movementId, CommentType commentType, Integer page, Integer pagesize) {
        // 1. 构造查询条件
        Query query = Query.query(Criteria.where("publishId").is(new ObjectId(movementId))
                .and("commentType").is(commentType.getType()))
                .skip(( page - 1) * pagesize)
                .limit(pagesize)
                .with(Sort.by(Sort.Order.desc("created")));

        // 2. 查询并发挥
        return mongoTemplate.find(query, Comment.class);
    }

    // 判断comment数据是否存在
    public Boolean hasComment(String movementId, Long userId, CommentType commentType) {
        Criteria criteria = Criteria.where("userId").is(userId)
                .and("publishId").is(new ObjectId(movementId))
                .and("commentType").is(commentType.getType());
        Query query = Query.query(criteria);
        return mongoTemplate.exists(query, Comment.class); // 判断数据是否存在
    }
}

④. 添加动态列表点赞状态

编辑 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;

     @Autowired
     private RedisTemplate<String, String> redisTemplate;

    /**
     * 发布动态
     */
    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://img0.baidu.com/it/u=1501084209,93021381&fm=253&fmt=auto&app=138&f=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;
    }

    // 查询好友动态
    public PageResult findFriendMovements(Integer page, Integer pagesize) {
        // 1. 获取当前用户id
        Long userId = UserHolder.getUserId();

        // 2. 调用API查询当前用户好友发布的动态列表
        List<Movement> list = movementApi.findFriendMovements(page, pagesize, userId);

        return getPageResult(page, pagesize, list);
    }

    // 公共方法
    private PageResult getPageResult(Integer page, Integer pagesize, List<Movement> list) {
        // 3. 判断列表是否为空
        // if(list == null || list.isEmpty()) {
        if(CollUtil.isEmpty(list)) {
            return new PageResult();
       }

        // 4. 提取动态发布人的id列表
        List<Long> userIds = CollUtil.getFieldValues(list, "userId", Long.class);

        // 5. 根据用户id列表获取用户详情
        Map<Long, UserInfo> map = userInfoApi.findByIds(userIds, null);

        // 6. 一个Movement构造一个vo对象
        List<MovementsVo> vos = new ArrayList<>();
        for (Movement movement : list) {
            UserInfo userInfo = map.get(movement.getUserId());
            if(userInfo != null) {
                MovementsVo vo = MovementsVo.init(userInfo, movement);
                // 添加点赞状态,判断hashKey是否存在
                String key = Constants.MOVEMENTS_INTERACT_KEY + movement.getId().toHexString();
                String hashKey = Constants.MOVEMENT_LIKE_HASHKEY + UserHolder.getUserId();
                if(redisTemplate.opsForHash().hasKey(key, hashKey)) {
                    vo.setHasLiked(1);
                }
                vos.add(vo);
            }
        }

        // 7. 构造PageResult并返回
        return new PageResult(page, pagesize, 0l, vos);
    }

    // 查询推荐动态
    public PageResult findRecommendMovements(Integer page, Integer pagesize) {
        // 1. 从redis从获取推荐数据
        String redisKey = Constants.MOVEMENTS_RECOMMEND + UserHolder.getUserId();
        String redisValue = redisTemplate.opsForValue().get(redisKey);

        // 2. 判断推荐数据是否存在
        List<Movement> list = Collections.EMPTY_LIST;

        if(StringUtils.isEmpty(redisValue)) {
            // 3. 如果不存在, 调用API随机构造10条动态数据
            list = movementApi.randomMovements(pagesize);

        } else {
            // 4. 如果存在, 处理pid数据  "16,17,18,19,20,21,10015,10020,10040,10064,10092,10093,10099,10067"
            String[] values = redisValue.split(",");
            // 4.1 判断当前页的起始条数是否小于数组的总数
            if((page - 1) * pagesize < values.length) {
                List<Long> pids = Arrays.stream(values).skip((page - 1) * pagesize).limit(pagesize)
                        .map(e -> Long.valueOf(e))
                        .collect(Collectors.toList());

                // 5. 调用API根据PID数组查询动态数据
                list = movementApi.findMovementByPids(pids);
            }
        }
        // 6. 调用公共方法构造返回值
        return getPageResult(page, pagesize, list);
    }

    // 查询单条动态
    public MovementsVo findById(String movementId) {
        // 1. 调用API查询动态详情
        Movement movement = movementApi.findById(movementId);

        // 2. 转换vo对象
        if(movement != null) {
            UserInfo userInfo = userInfoApi.findById(movement.getUserId());
            return MovementsVo.init(userInfo, movement);
        } else {
            return null;
        }
    }
}

⑷. 页面效果

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第19张图片


4. 取消点赞

⑴. 页面展示

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第20张图片

⑵. 接口文档

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第21张图片

⑶. 编码实现

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第22张图片

①. Controller 接收请求参数

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

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

    @Autowired
    private MovementService movementService;

    @Autowired
    private CommentsService commentsService;

    /**
     * 发布动态
     * @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);
    }

    /**
     * 查询好友动态
     */
    @GetMapping()
    public ResponseEntity movements( @RequestParam(defaultValue = "1") Integer page,
                                     @RequestParam(defaultValue = "10") Integer pagesize) {
        PageResult pr = movementService.findFriendMovements(page, pagesize);
        return ResponseEntity.ok(pr);
    }

    /**
     * 查询推荐动态
     */
    @GetMapping("/recommend")
    public ResponseEntity recommend( @RequestParam(defaultValue = "1") Integer page,
                                     @RequestParam(defaultValue = "10") Integer pagesize) {
        PageResult pr = movementService.findRecommendMovements(page, pagesize);
        return ResponseEntity.ok(pr);
    }

    /**
     * 查询单条动态
     */
    @GetMapping("/{id}")
    public ResponseEntity findById(@PathVariable("id") String movementId) {
        MovementsVo vo = movementService.findById(movementId);
        return ResponseEntity.ok(vo);
    }

    /**
     * 点赞
     */
    @GetMapping("/{id}/like")
    public ResponseEntity like(@PathVariable("id") String movementId) {
        Integer likeCount = commentsService.likeComment(movementId);
        return ResponseEntity.ok(likeCount);
    }

    /**
     * 取消点赞
     */
    @GetMapping("/{id}/dislike")
    public ResponseEntity dislike(@PathVariable("id") String movementId) {
        Integer likeCount = commentsService.dislikeComment(movementId);
        return ResponseEntity.ok(likeCount);
    }
}

②. Service 删除点赞状态

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

@Service
@Slf4j
public class CommentsService {

    @DubboReference
    private CommentApi commentApi;

    @DubboReference
    private UserInfoApi userInfoApi;

    @Autowired
    private RedisTemplate redisTemplate;

    // 发布评论
    public void saveComments(String movementId, String comment) {
        // 1. 获取操作用户id
        Long userId = UserHolder.getUserId();

        // 2. 构造Comment
        Comment comment1 = new Comment();
        comment1.setPublishId(new ObjectId(movementId));
        comment1.setCommentType(CommentType.COMMENT.getType());
        comment1.setContent(comment);
        comment1.setUserId(userId);
        comment1.setCreated(System.currentTimeMillis());

        // 3. 调用Service保存评论
        Integer commentCount = commentApi.save(comment1);
        log.info("commentCount =" + commentCount);
    }

    // 分页查询评论列表
    public PageResult findComments(String movementId, Integer page, Integer pagesize) {
        // 1. 调用API查询分页列表
        List<Comment> list = commentApi.findComments(movementId, CommentType.COMMENT, page, pagesize);

        // 2. 判断list集合是否存在
        if(CollUtil.isEmpty(list)) {
            return new PageResult();
        }

        // 3. 提取所有的用户id, 调用UserInfoApi查询用户详情
        List<Long> userIds = CollUtil.getFieldValues(list, "userId", Long.class);
        Map<Long, UserInfo> map = userInfoApi.findByIds(userIds, null);

        // 4. 构造vo对象
        List<CommentVo> vos = new ArrayList<>();
        for (Comment comment : list) {
            UserInfo userInfo = map.get(comment.getUserId());
            if(userInfo != null) {
                CommentVo vo = CommentVo.init(userInfo, comment);
                vos.add(vo);
            }
        }

        // 5. 构造返回值
        return new PageResult(page, pagesize, 0l, vos);
    }

    // 点赞
    public Integer likeComment(String movementId) {
        // 1. 调用API查询用户是否已点赞
        Boolean hasComment = commentApi.hasComment(movementId, UserHolder.getUserId(), CommentType.LIKE);

        // 2. 如果已点赞,抛出异常
        if(hasComment) {
            throw new BusinessException(ErrorResult.likeError());
        }

        // 3. 调用API保存数据到mogonDB
        Comment comment = new Comment();
        comment.setPublishId(new ObjectId(movementId));
        comment.setCommentType(CommentType.LIKE.getType());
        comment.setUserId(UserHolder.getUserId());
        comment.setCreated(System.currentTimeMillis());
        Integer count = commentApi.save(comment);

        // 4. 拼接redis的key, 将用户的点赞状态存入redis
        String key = Constants.MOVEMENTS_INTERACT_KEY + movementId;
        String hashKey = Constants.MOVEMENT_LIKE_HASHKEY + UserHolder.getUserId();
        redisTemplate.opsForHash().put(key, hashKey, "1");

        // 5. 返回点赞数量
        return count;
    }

    // 取消点赞
    public Integer dislikeComment(String movementId) {
        // 1. 调用API查询用户是否已点赞
        Boolean hasComment = commentApi.hasComment(movementId, UserHolder.getUserId(), CommentType.LIKE);

        // 2. 如果未点赞,抛出异常
        if(!hasComment) {
            throw new BusinessException(ErrorResult.disLikeError());
        }

        // 3. 调用APi删除数据
        Comment comment = new Comment();
        comment.setPublishId(new ObjectId(movementId));
        comment.setCommentType(CommentType.LIKE.getType());
        comment.setUserId(UserHolder.getUserId());
        Integer count = commentApi.delete(comment);

        // 4. 拼接redis的key, 删除点赞状态
        String key = Constants.MOVEMENTS_INTERACT_KEY + movementId;
        String hashKey = Constants.MOVEMENT_LIKE_HASHKEY + UserHolder.getUserId();
        redisTemplate.opsForHash().delete(key, hashKey);

        // 5. 返回点赞数量
        return count;
    }
}

③. APi 删除comment数据

Ⅰ. API接口

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

public interface CommentApi {

    // 发布评论, 并获得评论数量
    Integer save(Comment comment);

    // 分页查询评论列表
    List<Comment> findComments(String movementId, CommentType commentType, Integer page, Integer pagesize);

    // 判断comment数据是否存在
    Boolean hasComment(String movementId, Long userId, CommentType commentType);

    // 删除comment数据
    Integer delete(Comment comment);
}

Ⅱ. API接口实现类

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

@DubboService
public class CommentApiImpl implements CommentApi{

    @Autowired
    private MongoTemplate mongoTemplate;

    // 发布评论, 获取评论数量
    public Integer save(Comment comment) {
        // 1. 查询动态
        Movement movement = mongoTemplate.findById(comment.getPublishId(), Movement.class);

        // 2. 向Comment对象设置被评论人属性
        if(movement != null) {
            comment.setPublishUserId(movement.getUserId());
        }

        // 3. 保存到数据库
        mongoTemplate.save(comment);

        // 4. 更新动态表中的对应字段
        Query query = Query.query(Criteria.where("id").is(comment.getPublishId()));
        Update update = new Update();
        if(comment.getCommentType() == CommentType.LIKE.getType()) { // 点赞
            update.inc("likeCount", 1);
        } else if (comment.getCommentType() == CommentType.COMMENT.getType()) { // 评论
            update.inc("commentCount", 1);
        } else { // 喜欢
            update.inc("loveCount", 1);
        }
        // 设置更新参数
        FindAndModifyOptions options = new FindAndModifyOptions();
        options.returnNew(true); // 获取更新后的最新数据
        Movement modify = mongoTemplate.findAndModify(query, update, options, Movement.class);

        // 5. 获取最新的评论数量, 并返回
        return modify.statisCount(comment.getCommentType());
    }

    // 分页查询评论列表
    public List<Comment> findComments(String movementId, CommentType commentType, Integer page, Integer pagesize) {
        // 1. 构造查询条件
        Query query = Query.query(Criteria.where("publishId").is(new ObjectId(movementId))
                .and("commentType").is(commentType.getType()))
                .skip(( page - 1) * pagesize)
                .limit(pagesize)
                .with(Sort.by(Sort.Order.desc("created")));

        // 2. 查询并发挥
        return mongoTemplate.find(query, Comment.class);
    }

    // 判断comment数据是否存在
    public Boolean hasComment(String movementId, Long userId, CommentType commentType) {
        Criteria criteria = Criteria.where("userId").is(userId)
                .and("publishId").is(new ObjectId(movementId))
                .and("commentType").is(commentType.getType());
        Query query = Query.query(criteria);
        return mongoTemplate.exists(query, Comment.class); // 判断数据是否存在
    }

    // 删除comment数据
    public Integer delete(Comment comment) {
        // 1. 删除comment表数据
        Criteria criteria = Criteria.where("userId").is(comment.getUserId())
                .and("publishId").is(comment.getPublishId())
                .and("commentType").is(comment.getCommentType());
        Query query = Query.query(criteria);
        mongoTemplate.remove(query, Comment.class);

        // 2. 修改动态表中的总数量
        Query movementQuery = Query.query(Criteria.where("id").is(comment.getPublishId()));
        Update update = new Update();
        if(comment.getCommentType() == CommentType.LIKE.getType()) { // 点赞
            update.inc("likeCount", -1);
        } else if (comment.getCommentType() == CommentType.COMMENT.getType()) { // 评论
            update.inc("commentCount", -1);
        } else { // 喜欢
            update.inc("loveCount", -1);
        }

        // 3. 设置更新参数
        FindAndModifyOptions options = new FindAndModifyOptions();
        options.returnNew(true); // 获取更新后的最新数据
        Movement modify = mongoTemplate.findAndModify(movementQuery, update, options, Movement.class);

        // 4. 获取最新的评论数量, 并返回
        return modify.statisCount(comment.getCommentType());
    }
}

⑷. 页面效果


5. 圈子喜欢/不喜欢

⑴. 页面展示

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第23张图片


⑵. 接口文档

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第24张图片
Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第25张图片


⑶. 编码实现

①. Controller

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

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

    @Autowired
    private MovementService movementService;

    @Autowired
    private CommentsService commentsService;

    /**
     * 发布动态
     * @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);
    }

    /**
     * 查询好友动态
     */
    @GetMapping()
    public ResponseEntity movements( @RequestParam(defaultValue = "1") Integer page,
                                     @RequestParam(defaultValue = "10") Integer pagesize) {
        PageResult pr = movementService.findFriendMovements(page, pagesize);
        return ResponseEntity.ok(pr);
    }

    /**
     * 查询推荐动态
     */
    @GetMapping("/recommend")
    public ResponseEntity recommend( @RequestParam(defaultValue = "1") Integer page,
                                     @RequestParam(defaultValue = "10") Integer pagesize) {
        PageResult pr = movementService.findRecommendMovements(page, pagesize);
        return ResponseEntity.ok(pr);
    }

    /**
     * 查询单条动态
     */
    @GetMapping("/{id}")
    public ResponseEntity findById(@PathVariable("id") String movementId) {
        MovementsVo vo = movementService.findById(movementId);
        return ResponseEntity.ok(vo);
    }

    /**
     * 点赞
     */
    @GetMapping("/{id}/like")
    public ResponseEntity like(@PathVariable("id") String movementId) {
        Integer likeCount = commentsService.likeComment(movementId);
        return ResponseEntity.ok(likeCount);
    }

    /**
     * 取消点赞
     */
    @GetMapping("/{id}/dislike")
    public ResponseEntity dislike(@PathVariable("id") String movementId) {
        Integer likeCount = commentsService.dislikeComment(movementId);
        return ResponseEntity.ok(likeCount);
    }

    /**
     * 喜欢
     */
    @GetMapping("/{id}/love")
    public ResponseEntity love(@PathVariable("id") String movementId) {
        Integer likeCount = commentsService.loveComment(movementId);
        return ResponseEntity.ok(likeCount);
    }

    /**
     * 取消喜欢
     */
    @GetMapping("/{id}/unlove")
    public ResponseEntity unlove(@PathVariable("id") String movementId) {
        Integer likeCount = commentsService.disloveComment(movementId);
        return ResponseEntity.ok(likeCount);
    }
}

②. Service - 调用API编辑喜欢状态

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

@Service
@Slf4j
public class CommentsService {

    @DubboReference
    private CommentApi commentApi;

    @DubboReference
    private UserInfoApi userInfoApi;

    @Autowired
    private RedisTemplate redisTemplate;

    // 发布评论
    public void saveComments(String movementId, String comment) {
        // 1. 获取操作用户id
        Long userId = UserHolder.getUserId();

        // 2. 构造Comment
        Comment comment1 = new Comment();
        comment1.setPublishId(new ObjectId(movementId));
        comment1.setCommentType(CommentType.COMMENT.getType());
        comment1.setContent(comment);
        comment1.setUserId(userId);
        comment1.setCreated(System.currentTimeMillis());

        // 3. 调用Service保存评论
        Integer commentCount = commentApi.save(comment1);
        log.info("commentCount =" + commentCount);
    }

    // 分页查询评论列表
    public PageResult findComments(String movementId, Integer page, Integer pagesize) {
        // 1. 调用API查询分页列表
        List<Comment> list = commentApi.findComments(movementId, CommentType.COMMENT, page, pagesize);

        // 2. 判断list集合是否存在
        if(CollUtil.isEmpty(list)) {
            return new PageResult();
        }

        // 3. 提取所有的用户id, 调用UserInfoApi查询用户详情
        List<Long> userIds = CollUtil.getFieldValues(list, "userId", Long.class);
        Map<Long, UserInfo> map = userInfoApi.findByIds(userIds, null);

        // 4. 构造vo对象
        List<CommentVo> vos = new ArrayList<>();
        for (Comment comment : list) {
            UserInfo userInfo = map.get(comment.getUserId());
            if(userInfo != null) {
                CommentVo vo = CommentVo.init(userInfo, comment);
                vos.add(vo);
            }
        }

        // 5. 构造返回值
        return new PageResult(page, pagesize, 0l, vos);
    }

    // 点赞
    public Integer likeComment(String movementId) {
        // 1. 调用API查询用户是否已点赞
        Boolean hasComment = commentApi.hasComment(movementId, UserHolder.getUserId(), CommentType.LIKE);

        // 2. 如果已点赞,抛出异常
        if(hasComment) {
            throw new BusinessException(ErrorResult.likeError());
        }

        // 3. 调用API保存数据到mogonDB
        Comment comment = new Comment();
        comment.setPublishId(new ObjectId(movementId));
        comment.setCommentType(CommentType.LIKE.getType());
        comment.setUserId(UserHolder.getUserId());
        comment.setCreated(System.currentTimeMillis());
        Integer count = commentApi.save(comment);

        // 4. 拼接redis的key, 将用户的点赞状态存入redis
        String key = Constants.MOVEMENTS_INTERACT_KEY + movementId;
        String hashKey = Constants.MOVEMENT_LIKE_HASHKEY + UserHolder.getUserId();
        redisTemplate.opsForHash().put(key, hashKey, "1");

        // 5. 返回点赞数量
        return count;
    }

    // 取消点赞
    public Integer dislikeComment(String movementId) {
        // 1. 调用API查询用户是否已点赞
        Boolean hasComment = commentApi.hasComment(movementId, UserHolder.getUserId(), CommentType.LIKE);

        // 2. 如果未点赞,抛出异常
        if(!hasComment) {
            throw new BusinessException(ErrorResult.disLikeError());
        }

        // 3. 调用APi删除数据
        Comment comment = new Comment();
        comment.setPublishId(new ObjectId(movementId));
        comment.setCommentType(CommentType.LIKE.getType());
        comment.setUserId(UserHolder.getUserId());
        Integer count = commentApi.delete(comment);

        // 4. 拼接redis的key, 删除点赞状态
        String key = Constants.MOVEMENTS_INTERACT_KEY + movementId;
        String hashKey = Constants.MOVEMENT_LIKE_HASHKEY + UserHolder.getUserId();
        redisTemplate.opsForHash().delete(key, hashKey);

        // 5. 返回点赞数量
        return count;
    }

    // 喜欢
    public Integer loveComment(String movementId) {
        // 1. 调用API查询用户是否已喜欢
        Boolean hasComment = commentApi.hasComment(movementId, UserHolder.getUserId(), CommentType.LOVE);

        // 2. 如果已喜欢,抛出异常
        if(hasComment) {
            throw new BusinessException(ErrorResult.loveError());
        }

        // 3. 调用API保存数据到mogonDB
        Comment comment = new Comment();
        comment.setPublishId(new ObjectId(movementId));
        comment.setCommentType(CommentType.LOVE.getType());
        comment.setUserId(UserHolder.getUserId());
        comment.setCreated(System.currentTimeMillis());
        Integer count = commentApi.save(comment);

        // 4. 拼接redis的key, 将用户的喜欢状态存入redis
        String key = Constants.MOVEMENTS_INTERACT_KEY + movementId;
        String hashKey = Constants.MOVEMENT_LOVE_HASHKEY + UserHolder.getUserId();
        redisTemplate.opsForHash().put(key, hashKey, "1");

        // 5. 返回喜欢数量
        return count;
    }

    // 取消喜欢
    public Integer disloveComment(String movementId) {
        // 1. 调用API查询用户是否已点赞
        Boolean hasComment = commentApi.hasComment(movementId, UserHolder.getUserId(), CommentType.LOVE);

        // 2. 如果未点赞,抛出异常
        if(!hasComment) {
            throw new BusinessException(ErrorResult.disloveError());
        }

        // 3. 调用APi删除数据
        Comment comment = new Comment();
        comment.setPublishId(new ObjectId(movementId));
        comment.setCommentType(CommentType.LOVE.getType());
        comment.setUserId(UserHolder.getUserId());
        Integer count = commentApi.delete(comment);

        // 4. 拼接redis的key, 删除点赞状态
        String key = Constants.MOVEMENTS_INTERACT_KEY + movementId;
        String hashKey = Constants.MOVEMENT_LOVE_HASHKEY + UserHolder.getUserId();
        redisTemplate.opsForHash().delete(key, hashKey);

        // 5. 返回点赞数量
        return count;
    }
}

③. 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;

    @DubboReference
    private UserInfoApi userInfoApi;

     @Autowired
     private RedisTemplate<String, String> redisTemplate;

    /**
     * 发布动态
     */
    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://img0.baidu.com/it/u=1501084209,93021381&fm=253&fmt=auto&app=138&f=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;
    }

    // 查询好友动态
    public PageResult findFriendMovements(Integer page, Integer pagesize) {
        // 1. 获取当前用户id
        Long userId = UserHolder.getUserId();

        // 2. 调用API查询当前用户好友发布的动态列表
        List<Movement> list = movementApi.findFriendMovements(page, pagesize, userId);

        return getPageResult(page, pagesize, list);
    }

    // 公共方法
    private PageResult getPageResult(Integer page, Integer pagesize, List<Movement> list) {
        // 3. 判断列表是否为空
        // if(list == null || list.isEmpty()) {
        if(CollUtil.isEmpty(list)) {
            return new PageResult();
       }

        // 4. 提取动态发布人的id列表
        List<Long> userIds = CollUtil.getFieldValues(list, "userId", Long.class);

        // 5. 根据用户id列表获取用户详情
        Map<Long, UserInfo> map = userInfoApi.findByIds(userIds, null);

        // 6. 一个Movement构造一个vo对象
        List<MovementsVo> vos = new ArrayList<>();
        for (Movement movement : list) {
            UserInfo userInfo = map.get(movement.getUserId());
            if(userInfo != null) {
                MovementsVo vo = MovementsVo.init(userInfo, movement);

                // 添加点赞状态,判断hashKey是否存在
                String key = Constants.MOVEMENTS_INTERACT_KEY + movement.getId().toHexString();
                String hashKey = Constants.MOVEMENT_LIKE_HASHKEY + UserHolder.getUserId();
                if(redisTemplate.opsForHash().hasKey(key, hashKey)) {
                    vo.setHasLiked(1);
                }

                // 添加喜欢状态,判断hashKey是否存在
                String loveHashKey = Constants.MOVEMENT_LOVE_HASHKEY + UserHolder.getUserId();
                if(redisTemplate.opsForHash().hasKey(key, loveHashKey)) {
                    vo.setHasLoved(1);
                }

                vos.add(vo);
            }
        }

        // 7. 构造PageResult并返回
        return new PageResult(page, pagesize, 0l, vos);
    }

    // 查询推荐动态
    public PageResult findRecommendMovements(Integer page, Integer pagesize) {
        // 1. 从redis从获取推荐数据
        String redisKey = Constants.MOVEMENTS_RECOMMEND + UserHolder.getUserId();
        String redisValue = redisTemplate.opsForValue().get(redisKey);

        // 2. 判断推荐数据是否存在
        List<Movement> list = Collections.EMPTY_LIST;

        if(StringUtils.isEmpty(redisValue)) {
            // 3. 如果不存在, 调用API随机构造10条动态数据
            list = movementApi.randomMovements(pagesize);

        } else {
            // 4. 如果存在, 处理pid数据  "16,17,18,19,20,21,10015,10020,10040,10064,10092,10093,10099,10067"
            String[] values = redisValue.split(",");
            // 4.1 判断当前页的起始条数是否小于数组的总数
            if((page - 1) * pagesize < values.length) {
                List<Long> pids = Arrays.stream(values).skip((page - 1) * pagesize).limit(pagesize)
                        .map(e -> Long.valueOf(e))
                        .collect(Collectors.toList());

                // 5. 调用API根据PID数组查询动态数据
                list = movementApi.findMovementByPids(pids);
            }
        }
        // 6. 调用公共方法构造返回值
        return getPageResult(page, pagesize, list);
    }

    // 查询单条动态
    public MovementsVo findById(String movementId) {
        // 1. 调用API查询动态详情
        Movement movement = movementApi.findById(movementId);

        // 2. 转换vo对象
        if(movement != null) {
            UserInfo userInfo = userInfoApi.findById(movement.getUserId());
            return MovementsVo.init(userInfo, movement);
        } else {
            return null;
        }
    }
}

⑷. 页面效果

Dobbo微服务项目实战(详细介绍+案例源码) - 6.圈子动态/圈子互动_第26张图片


你可能感兴趣的:(Java,微服务,java,mongodb,1024程序员节)