课程说明

  • 实现视频点赞、评论、关注功能
  • 了解什么是即时通信
  • 了解探花交友的消息功能
  • 了解即时通信的技术方案
  • 了解环信的即时通讯
  • 实现环信的用户体系集成
  • 实现添加联系人、联系人列表功能

    1、视频点赞

    点赞逻辑与圈子点赞逻辑一致,所以可以复用圈子点赞的逻辑,需要注意的是点赞对象是Video,设置publishUserId的逻辑也需要完善下。

    1.1、dubbo服务

    修改保存Comment逻辑,在原有逻辑中增加对小视频的支持:

    1. //com.tanhua.dubbo.server.api.QuanZiApiImpl
    2. /**
    3. * 保存Comment
    4. *
    5. * @return
    6. */
    7. private Boolean saveComment(Long userId, String publishId,
    8. CommentType commentType, String content) {
    9. try {
    10. Comment comment = new Comment();
    11. comment.setId(ObjectId.get());
    12. comment.setUserId(userId);
    13. comment.setPublishId(new ObjectId(publishId));
    14. // 评论类型
    15. comment.setCommentType(commentType.getType());
    16. // 内容
    17. comment.setContent(content);
    18. comment.setCreated(System.currentTimeMillis());
    19. Publish publish = this.queryPublishById(publishId);
    20. if (ObjectUtil.isNotEmpty(publish)) {
    21. comment.setPublishUserId(publish.getUserId());
    22. } else {
    23. //查询评论
    24. Comment myComment = this.queryCommentById(publishId);
    25. if(ObjectUtil.isNotEmpty(myComment)){
    26. comment.setPublishUserId(myComment.getUserId());
    27. }else{
    28. //查询小视频
    29. Video video = this.videoApi.queryVideoById(publishId);
    30. if(ObjectUtil.isNotEmpty(video)){
    31. comment.setPublishUserId(video.getUserId());
    32. }else{
    33. // 其他情况,直接返回
    34. return false;
    35. }
    36. }
    37. }
    38. this.mongoTemplate.save(comment);
    39. return true;
    40. } catch (Exception e) {
    41. log.error("保存Comment出错~ userId = " + userId + ", publishId = " + publishId + ", commentType = " + commentType, e);
    42. }
    43. return false;
    44. }

    在VideoApi中定义根据id查询Video的方法:

    1. // com.tanhua.dubbo.server.api.VideoApi
    2. /**
    3. * 根据id查询视频对象
    4. *
    5. * @param videoId 小视频id
    6. * @return
    7. */
    8. Video queryVideoById(String videoId);

    编写实现:

    1. // com.tanhua.dubbo.server.api.VideoApiImpl
    2. @Override
    3. public Video queryVideoById(String videoId) {
    4. return this.mongoTemplate.findById(new ObjectId(videoId), Video.class);
    5. }

    1.2、APP接口服务

    接口地址:

  • 点赞 https://mock-java.itheima.net/project/35/interface/api/827

  • 取消点赞:https://mock-java.itheima.net/project/35/interface/api/833

    1.2.1、VideoController

    1. /**
    2. * 视频点赞
    3. *
    4. * @param videoId 视频id
    5. * @return
    6. */
    7. @PostMapping("/{id}/like")
    8. public ResponseEntity<Long> likeComment(@PathVariable("id") String videoId) {
    9. try {
    10. Long likeCount = this.videoService.likeComment(videoId);
    11. if (likeCount != null) {
    12. return ResponseEntity.ok(likeCount);
    13. }
    14. } catch (Exception e) {
    15. e.printStackTrace();
    16. }
    17. return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    18. }
    19. /**
    20. * 取消点赞
    21. *
    22. * @param videoId
    23. * @return
    24. */
    25. @PostMapping("/{id}/dislike")
    26. public ResponseEntity<Long> disLikeComment(@PathVariable("id") String videoId) {
    27. try {
    28. Long likeCount = this.videoService.disLikeComment(videoId);
    29. if (null != likeCount) {
    30. return ResponseEntity.ok(likeCount);
    31. }
    32. } catch (Exception e) {
    33. e.printStackTrace();
    34. }
    35. return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    36. }

    1.2.2、VideoService

    1. //com.tanhua.server.service.VideoService
    2. /**
    3. * 点赞
    4. *
    5. * @param videoId
    6. * @return
    7. */
    8. public Long likeComment(String videoId) {
    9. User user = UserThreadLocal.get();
    10. Boolean result = this.quanZiApi.likeComment(user.getId(), videoId);
    11. if (result) {
    12. return this.quanZiApi.queryLikeCount(videoId);
    13. }
    14. return null;
    15. }
    16. /**
    17. * 取消点赞
    18. *
    19. * @param videoId
    20. * @return
    21. */
    22. public Long disLikeComment(String videoId) {
    23. User user = UserThreadLocal.get();
    24. Boolean result = this.quanZiApi.disLikeComment(user.getId(), videoId);
    25. if (result) {
    26. return this.quanZiApi.queryLikeCount(videoId);
    27. }
    28. return null;
    29. }

    1.2.3、修改点赞数查询

    在查询小视频列表中,需要完善之前TODO的部分。

    1. //com.tanhua.server.service.VideoService
    2. public PageResult queryVideoList(Integer page, Integer pageSize) {
    3. User user = UserThreadLocal.get();
    4. PageResult pageResult = new PageResult();
    5. pageResult.setPage(page);
    6. pageResult.setPagesize(pageSize);
    7. PageInfo<Video> pageInfo = this.videoApi.queryVideoList(user.getId(), page, pageSize);
    8. List<Video> records = pageInfo.getRecords();
    9. if (CollUtil.isEmpty(records)) {
    10. return pageResult;
    11. }
    12. //查询用户信息
    13. List<Object> userIds = CollUtil.getFieldValues(records, "userId");
    14. List<UserInfo> userInfoList = this.userInfoService.queryUserInfoByUserIdList(userIds);
    15. List<VideoVo> videoVoList = new ArrayList<>();
    16. for (Video record : records) {
    17. VideoVo videoVo = new VideoVo();
    18. videoVo.setUserId(record.getUserId());
    19. videoVo.setCover(record.getPicUrl());
    20. videoVo.setVideoUrl(record.getVideoUrl());
    21. videoVo.setId(record.getId().toHexString());
    22. videoVo.setSignature("我就是我~"); //TODO 签名
    23. videoVo.setCommentCount(0); //TODO 评论数
    24. videoVo.setHasFocus(0); //TODO 是否关注
    25. videoVo.setHasLiked(this.quanZiApi.queryUserIsLike(user.getId(), videoVo.getId()) ? 1 : 0); //是否点赞(1是,0否)
    26. videoVo.setLikeCount(Convert.toInt(this.quanZiApi.queryLikeCount(videoVo.getId())));//点赞数
    27. //填充用户信息
    28. for (UserInfo userInfo : userInfoList) {
    29. if (ObjectUtil.equals(videoVo.getUserId(), userInfo.getUserId())) {
    30. videoVo.setNickname(userInfo.getNickName());
    31. videoVo.setAvatar(userInfo.getLogo());
    32. break;
    33. }
    34. }
    35. videoVoList.add(videoVo);
    36. }
    37. pageResult.setItems(videoVoList);
    38. return pageResult;
    39. }

    2、视频评论

    小视频的评论与圈子的评论逻辑类似,所以也可以使用同一套逻辑,所以只需要开发APP接口功能即可。
    评论相关的接口定义:

  • 发布评论:https://mock-java.itheima.net/project/35/interface/api/857

  • 评论列表:https://mock-java.itheima.net/project/35/interface/api/851
  • 评论点赞:https://mock-java.itheima.net/project/35/interface/api/863
  • 评论取消点赞:https://mock-java.itheima.net/project/35/interface/api/869

    2.1、VideoController

    1. //com.tanhua.server.controller.VideoController
    2. /**
    3. * 评论列表
    4. */
    5. @GetMapping("/{id}/comments")
    6. public ResponseEntity<PageResult> queryCommentsList(@PathVariable("id") String videoId,
    7. @RequestParam(value = "page", defaultValue = "1") Integer page,
    8. @RequestParam(value = "pagesize", defaultValue = "10") Integer pageSize) {
    9. try {
    10. PageResult pageResult = this.videoService.queryCommentList(videoId, page, pageSize);
    11. return ResponseEntity.ok(pageResult);
    12. } catch (Exception e) {
    13. e.printStackTrace();
    14. }
    15. return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    16. }
    17. /**
    18. * 提交评论
    19. *
    20. * @param param
    21. * @param videoId
    22. * @return
    23. */
    24. @PostMapping("/{id}/comments")
    25. public ResponseEntity<Void> saveComments(@RequestBody Map<String, String> param,
    26. @PathVariable("id") String videoId) {
    27. try {
    28. String content = param.get("comment");
    29. Boolean result = this.videoService.saveComment(videoId, content);
    30. if (result) {
    31. return ResponseEntity.ok(null);
    32. }
    33. } catch (Exception e) {
    34. e.printStackTrace();
    35. }
    36. return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    37. }
    38. /**
    39. * 评论点赞
    40. *
    41. * @param videoCommentId 视频中的评论id
    42. * @return
    43. */
    44. @PostMapping("/comments/{id}/like")
    45. public ResponseEntity<Long> commentsLikeComment(@PathVariable("id") String videoCommentId) {
    46. try {
    47. Long likeCount = this.videoService.likeComment(videoCommentId);
    48. if (likeCount != null) {
    49. return ResponseEntity.ok(likeCount);
    50. }
    51. } catch (Exception e) {
    52. e.printStackTrace();
    53. }
    54. return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    55. }
    56. /**
    57. * 评论取消点赞
    58. *
    59. * @param videoCommentId 视频中的评论id
    60. * @return
    61. */
    62. @PostMapping("/comments/{id}/dislike")
    63. public ResponseEntity<Long> disCommentsLikeComment(@PathVariable("id") String videoCommentId) {
    64. try {
    65. Long likeCount = this.videoService.disLikeComment(videoCommentId);
    66. if (null != likeCount) {
    67. return ResponseEntity.ok(likeCount);
    68. }
    69. } catch (Exception e) {
    70. e.printStackTrace();
    71. }
    72. return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    73. }

    2.2、VideoService

    1. // com.tanhua.server.service.VideoService
    2. public PageResult queryCommentList(String videoId, Integer page, Integer pageSize) {
    3. return this.quanZiService.queryCommentList(videoId, page, pageSize);
    4. }
    5. public Boolean saveComment(String videoId, String content) {
    6. return this.quanZiService.saveComments(videoId, content);
    7. }

    2.3、查询评论数

    在小视频列表查询结果中,需要返回该视频的评论数据,由于之前在dubbo服务中没有提供查询方法,所以需要先实现查询方法。

    2.3.1、dubbo服务

    2.3.1.1、定义接口
    1. //com.tanhua.dubbo.server.api.QuanZiApi
    2. /**
    3. * 查询评论数
    4. *
    5. * @param publishId
    6. * @return
    7. */
    8. Long queryCommentCount(String publishId);

    2.3.1.2、编写实现
    1. // com.tanhua.dubbo.server.api.QuanZiApiImpl
    2. @Override
    3. public Long queryCommentCount(String publishId) {
    4. return this.queryCommentCount(publishId, CommentType.COMMENT);
    5. }

    2.3.2、查询评论数

    1. //com.tanhua.server.service.VideoService
    2. public PageResult queryVideoList(Integer page, Integer pageSize) {
    3. User user = UserThreadLocal.get();
    4. PageResult pageResult = new PageResult();
    5. pageResult.setPage(page);
    6. pageResult.setPagesize(pageSize);
    7. PageInfo<Video> pageInfo = this.videoApi.queryVideoList(user.getId(), page, pageSize);
    8. List<Video> records = pageInfo.getRecords();
    9. if (CollUtil.isEmpty(records)) {
    10. return pageResult;
    11. }
    12. //查询用户信息
    13. List<Object> userIds = CollUtil.getFieldValues(records, "userId");
    14. List<UserInfo> userInfoList = this.userInfoService.queryUserInfoByUserIdList(userIds);
    15. List<VideoVo> videoVoList = new ArrayList<>();
    16. for (Video record : records) {
    17. VideoVo videoVo = new VideoVo();
    18. videoVo.setUserId(record.getUserId());
    19. videoVo.setCover(record.getPicUrl());
    20. videoVo.setVideoUrl(record.getVideoUrl());
    21. videoVo.setId(record.getId().toHexString());
    22. videoVo.setSignature("我就是我~"); //TODO 签名
    23. videoVo.setCommentCount(Convert.toInt(this.quanZiApi.queryCommentCount(videoVo.getId()))); //评论数
    24. videoVo.setHasFocus(0); //TODO 是否关注
    25. videoVo.setHasLiked(this.quanZiApi.queryUserIsLike(user.getId(), videoVo.getId()) ? 1 : 0); //是否点赞(1是,0否)
    26. videoVo.setLikeCount(Convert.toInt(this.quanZiApi.queryLikeCount(videoVo.getId())));//点赞数
    27. //填充用户信息
    28. for (UserInfo userInfo : userInfoList) {
    29. if (ObjectUtil.equals(videoVo.getUserId(), userInfo.getUserId())) {
    30. videoVo.setNickname(userInfo.getNickName());
    31. videoVo.setAvatar(userInfo.getLogo());
    32. break;
    33. }
    34. }
    35. videoVoList.add(videoVo);
    36. }
    37. pageResult.setItems(videoVoList);
    38. return pageResult;
    39. }

    2.4、测试

    day06-完善小视频功能以及即时通讯 - 图1

    3、关注用户

    关注用户是关注小视频发布的作者,需要在dubbo服务中提供给相关的服务。

    3.1、dubbo服务

    3.1.1、FollowUser

    1. package com.tanhua.dubbo.server.pojo;
    2. import lombok.AllArgsConstructor;
    3. import lombok.Data;
    4. import lombok.NoArgsConstructor;
    5. import org.bson.types.ObjectId;
    6. import org.springframework.data.mongodb.core.mapping.Document;
    7. /**
    8. * 关注用户
    9. */
    10. @Data
    11. @NoArgsConstructor
    12. @AllArgsConstructor
    13. @Document(collection = "follow_user")
    14. public class FollowUser implements java.io.Serializable{
    15. private static final long serialVersionUID = 3148619072405056052L;
    16. private ObjectId id; //主键id
    17. private Long userId; //用户id
    18. private Long followUserId; //关注的用户id
    19. private Long created; //关注时间
    20. }

    3.1.2、定义接口

    1. //com.tanhua.dubbo.server.api.VideoApi
    2. /**
    3. * 关注用户
    4. *
    5. * @param userId 当前用户
    6. * @param followUserId 关注的目标用户
    7. * @return
    8. */
    9. Boolean followUser(Long userId, Long followUserId);
    10. /**
    11. * 取消关注用户
    12. *
    13. * @param userId 当前用户
    14. * @param followUserId 关注的目标用户
    15. * @return
    16. */
    17. Boolean disFollowUser(Long userId, Long followUserId);
    18. /**
    19. * 查询用户是否关注某个用户
    20. *
    21. * @param userId 当前用户
    22. * @param followUserId 关注的目标用户
    23. * @return
    24. */
    25. Boolean isFollowUser(Long userId, Long followUserId);

    3.1.3、编写实现

    1. //com.tanhua.dubbo.server.api.VideoApiImpl
    2. private static final String VIDEO_FOLLOW_USER_KEY_PREFIX = "VIDEO_FOLLOW_USER_";
    3. @Override
    4. public Boolean followUser(Long userId, Long followUserId) {
    5. if (!ObjectUtil.isAllNotEmpty(userId, followUserId)) {
    6. return false;
    7. }
    8. try {
    9. //需要将用户的关注列表,保存到redis中,方便后续的查询
    10. //使用redis的hash结构
    11. if (this.isFollowUser(userId, followUserId)) {
    12. return false;
    13. }
    14. FollowUser followUser = new FollowUser();
    15. followUser.setId(ObjectId.get());
    16. followUser.setUserId(userId);
    17. followUser.setFollowUserId(followUserId);
    18. followUser.setCreated(System.currentTimeMillis());
    19. this.mongoTemplate.save(followUser);
    20. //保存数据到redis
    21. String redisKey = this.getVideoFollowUserKey(userId);
    22. String hashKey = String.valueOf(followUserId);
    23. this.redisTemplate.opsForHash().put(redisKey, hashKey, "1");
    24. return true;
    25. } catch (Exception e) {
    26. e.printStackTrace();
    27. }
    28. return false;
    29. }
    30. @Override
    31. public Boolean disFollowUser(Long userId, Long followUserId) {
    32. if (!ObjectUtil.isAllNotEmpty(userId, followUserId)) {
    33. return false;
    34. }
    35. if (!this.isFollowUser(userId, followUserId)) {
    36. return false;
    37. }
    38. //取消关注,删除关注数据即可
    39. Query query = Query.query(Criteria.where("userId").is(userId)
    40. .and("followUserId").is(followUserId)
    41. );
    42. DeleteResult result = this.mongoTemplate.remove(query, FollowUser.class);
    43. if (result.getDeletedCount() > 0) {
    44. //同时删除redis中的数据
    45. String redisKey = this.getVideoFollowUserKey(userId);
    46. String hashKey = String.valueOf(followUserId);
    47. this.redisTemplate.opsForHash().delete(redisKey, hashKey);
    48. return true;
    49. }
    50. return false;
    51. }
    52. @Override
    53. public Boolean isFollowUser(Long userId, Long followUserId) {
    54. String redisKey = this.getVideoFollowUserKey(userId);
    55. String hashKey = String.valueOf(followUserId);
    56. return this.redisTemplate.opsForHash().hasKey(redisKey, hashKey);
    57. }
    58. private String getVideoFollowUserKey(Long userId) {
    59. return VIDEO_FOLLOW_USER_KEY_PREFIX + userId;
    60. }

    3.2、APP服务

    关注用户:https://mock-java.itheima.net/project/35/interface/api/839
    取消关注:https://mock-java.itheima.net/project/35/interface/api/845

    3.2.1、VideoController

    1. //com.tanhua.server.controller.VideoController
    2. /**
    3. * 视频用户关注
    4. */
    5. @PostMapping("/{id}/userFocus")
    6. public ResponseEntity<Void> saveUserFocusComments(@PathVariable("id") Long userId) {
    7. try {
    8. Boolean bool = this.videoService.followUser(userId);
    9. if (bool) {
    10. return ResponseEntity.ok(null);
    11. }
    12. } catch (Exception e) {
    13. e.printStackTrace();
    14. }
    15. return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    16. }
    17. /**
    18. * 取消视频用户关注
    19. */
    20. @PostMapping("/{id}/userUnFocus")
    21. public ResponseEntity<Void> saveUserUnFocusComments(@PathVariable("id") Long userId) {
    22. try {
    23. Boolean bool = this.videoService.disFollowUser(userId);
    24. if (bool) {
    25. return ResponseEntity.ok(null);
    26. }
    27. } catch (Exception e) {
    28. e.printStackTrace();
    29. }
    30. return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    31. }

    3.2.2、VideoService

    1. //com.tanhua.server.service.VideoService
    2. /**
    3. * 关注用户
    4. *
    5. * @param userId
    6. * @return
    7. */
    8. public Boolean followUser(Long userId) {
    9. User user = UserThreadLocal.get();
    10. return this.videoApi.followUser(user.getId(), userId);
    11. }
    12. /**
    13. * 取消关注
    14. *
    15. * @param userId
    16. * @return
    17. */
    18. public Boolean disFollowUser(Long userId) {
    19. User user = UserThreadLocal.get();
    20. return this.videoApi.disFollowUser(user.getId(), userId);
    21. }

    3.2.3、查询是否关注

    1. //com.tanhua.server.service.VideoService
    2. public PageResult queryVideoList(Integer page, Integer pageSize) {
    3. User user = UserThreadLocal.get();
    4. PageResult pageResult = new PageResult();
    5. pageResult.setPage(page);
    6. pageResult.setPagesize(pageSize);
    7. PageInfo<Video> pageInfo = this.videoApi.queryVideoList(user.getId(), page, pageSize);
    8. List<Video> records = pageInfo.getRecords();
    9. if (CollUtil.isEmpty(records)) {
    10. return pageResult;
    11. }
    12. //查询用户信息
    13. List<Object> userIds = CollUtil.getFieldValues(records, "userId");
    14. List<UserInfo> userInfoList = this.userInfoService.queryUserInfoByUserIdList(userIds);
    15. List<VideoVo> videoVoList = new ArrayList<>();
    16. for (Video record : records) {
    17. VideoVo videoVo = new VideoVo();
    18. videoVo.setUserId(record.getUserId());
    19. videoVo.setCover(record.getPicUrl());
    20. videoVo.setVideoUrl(record.getVideoUrl());
    21. videoVo.setId(record.getId().toHexString());
    22. videoVo.setSignature("我就是我~"); //TODO 签名
    23. videoVo.setCommentCount(Convert.toInt(this.quanZiApi.queryCommentCount(videoVo.getId()))); //评论数
    24. videoVo.setHasFocus(this.videoApi.isFollowUser(user.getId(), videoVo.getUserId()) ? 1 : 0); //是否关注
    25. videoVo.setHasLiked(this.quanZiApi.queryUserIsLike(user.getId(), videoVo.getId()) ? 1 : 0); //是否点赞(1是,0否)
    26. videoVo.setLikeCount(Convert.toInt(this.quanZiApi.queryLikeCount(videoVo.getId())));//点赞数
    27. //填充用户信息
    28. for (UserInfo userInfo : userInfoList) {
    29. if (ObjectUtil.equals(videoVo.getUserId(), userInfo.getUserId())) {
    30. videoVo.setNickname(userInfo.getNickName());
    31. videoVo.setAvatar(userInfo.getLogo());
    32. break;
    33. }
    34. }
    35. videoVoList.add(videoVo);
    36. }
    37. pageResult.setItems(videoVoList);
    38. return pageResult;
    39. }

    3.3、测试

    day06-完善小视频功能以及即时通讯 - 图2
    可以看到,已经完成了关注用户。

    4、即时通信

    4.1、什么是即时通信?

    day06-完善小视频功能以及即时通讯 - 图3

    4.2、功能说明

    在探花交友项目中也提供了类似微信的聊天功能,用户可以和好友或陌生人聊天。
    如果是陌生人,通过《聊一下》功能进行打招呼,如果对方同意后,就成为了好友,可以进行聊天了。
    陌生人之间如果相互喜欢,那么就会成为好友,也就可以聊天了。
    在消息界面中也可以查看:点赞、评论、喜欢、公告等消息信息。
    day06-完善小视频功能以及即时通讯 - 图4
    day06-完善小视频功能以及即时通讯 - 图5
    day06-完善小视频功能以及即时通讯 - 图6

    5、技术方案

    对于高并发的即时通讯实现,还是很有挑战的,所需要考虑的点非常多,除了要实现功能,还要考虑并发、流量、负载、服务器、容灾等等。虽然有难度也并不是高不可攀。
    对于现实即时通讯往往有两种方案:

  • 方案一:

    • 自主实现,从设计到架构,再到实现。
    • 技术方面可以采用:Netty + WebSocket + RocketMQ + MongoDB + Redis + ZooKeeper + MySQL
    • day06-完善小视频功能以及即时通讯 - 图7
  • 方案二:
    • 对接第三方服务完成。
    • 这种方式简单,只需要按照第三方的api进行对接就可以了。
    • 如:环信、网易、容联云通讯等。

如何选择呢?
如果是中大型企业做项目可以选择自主研发,如果是中小型企业研发中小型的项目,选择第二种方案即可。方案一需要有大量的人力、物力的支持,开发周期长,成本高,但可控性强。方案二,成本低,开发周期短,能够快速的集成起来进行功能的开发,只是在可控性方面来说就差了一些。
探花交友项目选择方案二进行实现。

6、环信

官网:https://www.easemob.com/ 稳定健壮,消息必达,亿级并发的即时通讯云
环信平台为黑马学员开设的专用注册地址:https://datayi.cn/w/woVL50vR
day06-完善小视频功能以及即时通讯 - 图8
day06-完善小视频功能以及即时通讯 - 图9

6.1、开发简介

文档地址:http://docs-im.easemob.com/
平台架构:
day06-完善小视频功能以及即时通讯 - 图10
集成:
环信和用户体系的集成主要发生在2个地方,服务器端集成和客户端集成。
day06-完善小视频功能以及即时通讯 - 图11
探花集成:

  • 探花前端使用AndroidSDK进行集成
  • 后端集成用户体系

    • 文档:http://docs-im.easemob.com/im/server/ready/user

      6.2、环信Console

      需要使用环信平台,那么必须要进行注册,登录之后即可创建应用。环信100以内的用户免费使用,100以上就要注册企业版了。
      企业版价格:
      day06-完善小视频功能以及即时通讯 - 图12
      创建应用:
      day06-完善小视频功能以及即时通讯 - 图13
      创建完成:
      day06-完善小视频功能以及即时通讯 - 图14

      6.3、整体流程图

      day06-完善小视频功能以及即时通讯 - 图15
      说明:
  • 在APP端与后端系统,都需要完成与环信的集成。

  • 在APP端,使用Android的SDK与环信进行通信,通信时需要通过后台系统的接口查询当前用户的环信用户名和密码,进行登录环信。
  • 后台系统,在用户注册后,同步注册环信用户到环信平台,在后台系统中保存环信的用户名和密码。
  • APP拿到用户名和密码后,进行登录环信,登录成功后即可向环信发送消息给好友。
  • 后台系统也可以通过管理员的身份给用户发送系统信息。

    7、获取管理员权限

    环信提供的 REST API 需要权限才能访问,权限通过发送 HTTP 请求时携带 token 来体现。
    官方文档:获取管理员权限
    与环信的集成,我们将相关的代码逻辑写入到新的dubbo工程中,名字叫:my-tanhua-dubbo-huanxin。

    7.1、创建dubbo工程

    pom.xml:

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    5. <parent>
    6. <artifactId>my-tanhua-dubbo</artifactId>
    7. <groupId>cn.itcast.tanhua</groupId>
    8. <version>1.0-SNAPSHOT</version>
    9. </parent>
    10. <modelVersion>4.0.0</modelVersion>
    11. <artifactId>my-tanhua-dubbo-huanxin</artifactId>
    12. <dependencies>
    13. <!--引入interface依赖-->
    14. <dependency>
    15. <groupId>cn.itcast.tanhua</groupId>
    16. <artifactId>my-tanhua-dubbo-interface</artifactId>
    17. <version>1.0-SNAPSHOT</version>
    18. </dependency>
    19. <dependency>
    20. <groupId>org.springframework.boot</groupId>
    21. <artifactId>spring-boot-starter</artifactId>
    22. </dependency>
    23. <dependency>
    24. <groupId>org.springframework.boot</groupId>
    25. <artifactId>spring-boot-starter-data-redis</artifactId>
    26. </dependency>
    27. <dependency>
    28. <groupId>org.springframework.boot</groupId>
    29. <artifactId>spring-boot-starter-test</artifactId>
    30. <scope>test</scope>
    31. </dependency>
    32. <dependency>
    33. <groupId>com.baomidou</groupId>
    34. <artifactId>mybatis-plus</artifactId>
    35. </dependency>
    36. <dependency>
    37. <groupId>com.baomidou</groupId>
    38. <artifactId>mybatis-plus-boot-starter</artifactId>
    39. </dependency>
    40. <dependency>
    41. <groupId>mysql</groupId>
    42. <artifactId>mysql-connector-java</artifactId>
    43. </dependency>
    44. <!--dubbo的springboot支持-->
    45. <dependency>
    46. <groupId>com.alibaba.boot</groupId>
    47. <artifactId>dubbo-spring-boot-starter</artifactId>
    48. </dependency>
    49. <!--dubbo框架-->
    50. <dependency>
    51. <groupId>com.alibaba</groupId>
    52. <artifactId>dubbo</artifactId>
    53. </dependency>
    54. <!--zk依赖-->
    55. <dependency>
    56. <groupId>org.apache.zookeeper</groupId>
    57. <artifactId>zookeeper</artifactId>
    58. </dependency>
    59. <dependency>
    60. <groupId>com.github.sgroschupf</groupId>
    61. <artifactId>zkclient</artifactId>
    62. </dependency>
    63. <dependency>
    64. <groupId>io.netty</groupId>
    65. <artifactId>netty-all</artifactId>
    66. </dependency>
    67. <dependency>
    68. <groupId>cn.hutool</groupId>
    69. <artifactId>hutool-all</artifactId>
    70. </dependency>
    71. </dependencies>
    72. </project>

    application.properties:

    1. # Spring boot application
    2. spring.application.name = itcast-tanhua-dubbo-huanxin
    3. # dubbo 扫描包配置
    4. dubbo.scan.basePackages = com.tanhua.dubbo.server
    5. dubbo.application.name = dubbo-provider-huanxin
    6. #dubbo 对外暴露的端口信息
    7. dubbo.protocol.name = dubbo
    8. dubbo.protocol.port = 20881
    9. #dubbo注册中心的配置
    10. dubbo.registry.address = zookeeper://192.168.31.81:2181
    11. dubbo.registry.client = zkclient
    12. dubbo.registry.timeout = 60000
    13. # Redis相关配置
    14. spring.redis.jedis.pool.max-wait = 5000ms
    15. spring.redis.jedis.pool.max-Idle = 100
    16. spring.redis.jedis.pool.min-Idle = 10
    17. spring.redis.timeout = 10s
    18. spring.redis.cluster.nodes = 192.168.31.81:6379,192.168.31.81:6380,192.168.31.81:6381
    19. spring.redis.cluster.max-redirects=5
    20. #数据库连接信息
    21. spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    22. spring.datasource.url=jdbc:mysql://192.168.31.81:3306/mytanhua?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
    23. spring.datasource.username=root
    24. spring.datasource.password=root
    25. # 表名前缀
    26. mybatis-plus.global-config.db-config.table-prefix=tb_
    27. # id策略为自增长
    28. mybatis-plus.global-config.db-config.id-type=auto

    入口启动类:

    1. package com.tanhua.dubbo.server;
    2. import org.springframework.boot.SpringApplication;
    3. import org.springframework.boot.autoconfigure.SpringBootApplication;
    4. import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
    5. import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
    6. @SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}) //排除mongo的自动配置
    7. public class HuanXinDubboApplication {
    8. public static void main(String[] args) {
    9. SpringApplication.run(HuanXinDubboApplication.class, args);
    10. }
    11. }

    7.2、配置

    相关的配置,在环信管理控制台中,可以找到相关的参数。

    1. #huanxin.properties
    2. tanhua.huanxin.url=http://a1.easemob.com/
    3. tanhua.huanxin.orgName=1105190515097562
    4. tanhua.huanxin.appName=tanhua
    5. tanhua.huanxin.clientId=YXA67ZofwHblEems-_Fh-17T2g
    6. tanhua.huanxin.clientSecret=YXA60r45rNy2Ux5wQ7YYoEPwynHmUZk

    编写配置类:

    1. package com.tanhua.dubbo.server.config;
    2. import lombok.Data;
    3. import org.springframework.boot.context.properties.ConfigurationProperties;
    4. import org.springframework.context.annotation.Configuration;
    5. import org.springframework.context.annotation.PropertySource;
    6. @Configuration
    7. @PropertySource("classpath:huanxin.properties")
    8. @ConfigurationProperties(prefix = "tanhua.huanxin")
    9. @Data
    10. public class HuanXinConfig {
    11. private String url;
    12. private String orgName;
    13. private String appName;
    14. private String clientId;
    15. private String clientSecret;
    16. }

    7.3、编写实现

    具体的获取token的业务逻辑在TokenService中完成。实现要点:

  • 分析官方文档中的请求url、参数、响应数据等内容

  • 请求到token需要缓存到redis中,不能频繁的获取token操作,可能会被封号
    1. package com.tanhua.dubbo.server.service;
    2. import cn.hutool.core.util.StrUtil;
    3. import cn.hutool.http.HttpRequest;
    4. import cn.hutool.http.HttpResponse;
    5. import cn.hutool.json.JSONObject;
    6. import cn.hutool.json.JSONUtil;
    7. import com.tanhua.dubbo.server.config.HuanXinConfig;
    8. import lombok.extern.slf4j.Slf4j;
    9. import org.springframework.beans.factory.annotation.Autowired;
    10. import org.springframework.data.redis.core.RedisTemplate;
    11. import org.springframework.stereotype.Service;
    12. import java.util.HashMap;
    13. import java.util.Map;
    14. import java.util.concurrent.TimeUnit;
    15. @Service
    16. @Slf4j
    17. public class TokenService {
    18. @Autowired
    19. private RedisTemplate<String, String> redisTemplate;
    20. private static final String REDIS_KEY = "HX_TOKEN";
    21. @Autowired
    22. private HuanXinConfig huanXinConfig;
    23. /**
    24. * 获取token,先从redis中获取,如果没有,再去环信接口获取
    25. *
    26. * @return
    27. */
    28. public String getToken() {
    29. String token = this.redisTemplate.opsForValue().get(REDIS_KEY);
    30. if (StrUtil.isNotEmpty(token)) {
    31. return token;
    32. }
    33. //访问环信接口获取token
    34. return this.refreshToken();
    35. }
    36. /**
    37. * 刷新token,请求环信接口,将token存储到redis中
    38. *
    39. * @return
    40. */
    41. public String refreshToken() {
    42. String targetUrl = this.huanXinConfig.getUrl() +
    43. this.huanXinConfig.getOrgName() + "/" +
    44. this.huanXinConfig.getAppName() + "/token";
    45. Map<String, Object> param = new HashMap<>();
    46. param.put("grant_type", "client_credentials");
    47. param.put("client_id", this.huanXinConfig.getClientId());
    48. param.put("client_secret", this.huanXinConfig.getClientSecret());
    49. HttpResponse response = HttpRequest.post(targetUrl)
    50. .body(JSONUtil.toJsonStr(param))
    51. .timeout(20000) //请求超时时间
    52. .execute();
    53. if (!response.isOk()) {
    54. log.error("刷新token失败~~~ ");
    55. return null;
    56. }
    57. String jsonBody = response.body();
    58. JSONObject jsonObject = JSONUtil.parseObj(jsonBody);
    59. String token = jsonObject.getStr("access_token");
    60. if (StrUtil.isNotEmpty(token)) {
    61. //将token数据缓存到redis中,缓存时间由expires_in决定
    62. //提前1小时失效
    63. long timeout = jsonObject.getLong("expires_in") - 3600;
    64. this.redisTemplate.opsForValue().set(REDIS_KEY, token, timeout, TimeUnit.SECONDS);
    65. return token;
    66. }
    67. return null;
    68. }
    69. }

    7.4、定义接口

    接口定义在my-tanhua-dubbo-interface工程中。 ```java package com.tanhua.dubbo.server.api; /**

}

  1. <a name="f28b0491"></a>
  2. ### 7.5、实现接口
  3. 在my-tanhua-dubbo-huanxin中完成。
  4. ```java
  5. package com.tanhua.dubbo.server.api;
  6. @Service(version = "1.0.0")
  7. @Slf4j
  8. public class HuanXinApiImpl implements HuanXinApi {
  9. @Autowired
  10. private TokenService tokenService;
  11. @Override
  12. public String getToken() {
  13. return this.tokenService.getToken();
  14. }
  15. }

7.6、测试

  1. package com.tanhua.dubbo.server;
  2. import com.tanhua.dubbo.server.api.HuanXinApi;
  3. import org.junit.Test;
  4. import org.junit.runner.RunWith;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.boot.test.context.SpringBootTest;
  7. import org.springframework.test.context.junit4.SpringRunner;
  8. @SpringBootTest
  9. @RunWith(SpringRunner.class)
  10. public class TestHuanXinApi {
  11. @Autowired
  12. private HuanXinApi huanXinApi;
  13. @Test
  14. public void testGetToken(){
  15. String token = this.huanXinApi.getToken();
  16. System.out.println(token);
  17. }
  18. }

测试结果,已经保存到redis中了:
day06-完善小视频功能以及即时通讯 - 图16

8、用户系统集成

使用环信平台,最重要的就是集成用户体系,基本的逻辑是这样的:新用户在注册后,同时需要注册环信用户。
流程如下:
day06-完善小视频功能以及即时通讯 - 图17
流程说明:

  • 用户在登录时在sso系统中进行判断,如果是新用户,在注册完成后,需要调用dubbo中的环信服务进行注册环信用户。
  • dubbo-huanxin服务在注册环信用户时,需要随机生成密码,携带token请求环信的REST API进行用户注册。
  • 注册成功后,需要将环信的用户信息保存到MySQL中。
  • 用户在APP端使用即时通讯功能时,需要通过环信用户信息登录到环信平台,由于数据存储到服务端,所以需要通过dubbo-huanxin进行查询。
  • 在拿到环信账号信息后,登录环信,登录成功后即可与环信平台进行交互。
  • 需要注意的是,APP端与环信平台交互,是不走后端系统的,是直连操作。

官方文档:《用户管理》

8.1、通用请求逻辑

在与环信接口通信时,使用的是环信的REST接口,所以我们需要封装一个通用的请求服务,在与所有环信接口对接时使用。
另外,请求接口时都需要携带token,前面我们已经将token存储到redis中,但是,可能存在这样一种情况,token在我们redis中有效,但是在环信平台已经失效,这样环信平台会给我们响应401状态码。
对于这种情况,我们就需要检测状态码是否为401,如果是401的话,就需要重新刷新token,再重新执行此次请求。
也就是要支持请求的重试。

8.1.1、Spring-Retry

Spring提供了重试的功能,使用非常的简单、优雅。

第一步,导入依赖:

  1. <!--Spring重试模块-->
  2. <dependency>
  3. <groupId>org.springframework.retry</groupId>
  4. <artifactId>spring-retry</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.aspectj</groupId>
  8. <artifactId>aspectjweaver</artifactId>
  9. </dependency>

第二步,在启动类中添加@EnableRetry注解来激活重试功能:

  1. package com.tanhua.dubbo.server;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
  5. import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
  6. import org.springframework.retry.annotation.EnableRetry;
  7. @SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}) //排除mongo的自动配置
  8. @EnableRetry
  9. public class HuanXinDubboApplication {
  10. public static void main(String[] args) {
  11. SpringApplication.run(HuanXinDubboApplication.class, args);
  12. }
  13. }

第三步,在需要支持重试操作的Service方法中添加@Retryable注解,demo如下:

  1. //将此类放到test包下测试即可
  2. package com.tanhua.dubbo.server;
  3. import cn.hutool.core.util.RandomUtil;
  4. import org.springframework.retry.annotation.Backoff;
  5. import org.springframework.retry.annotation.Recover;
  6. import org.springframework.retry.annotation.Retryable;
  7. import org.springframework.stereotype.Service;
  8. @Service
  9. public class RetryService {
  10. @Retryable(value = RuntimeException.class, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 2))
  11. public int execute(int max) {
  12. int data = RandomUtil.randomInt(1, 99);
  13. System.out.println("生成:" + data);
  14. if (data < max) {
  15. throw new RuntimeException();
  16. }
  17. return data;
  18. }
  19. @Recover //全部重试失败后执行
  20. public int recover(Exception e) {
  21. System.out.println("全部重试完成。。。。。");
  22. return 88; //返回默认
  23. }
  24. }

@Retryable参数说明:

  • value:抛出指定异常才会重试
  • maxAttempts:最大重试次数,默认3次
  • backoff:重试等待策略,默认使用_@_Backoff
    • _@_Backoff 的value默认为1000L,我们设置为2000L;
    • multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,如果把multiplier设置为2,则第一次重试为2秒,第二次为4秒,第三次为6秒。

@Recover标注的方法,是在所有的重试都失败的情况下,最后执行该方法,该方法有2个要求:

  • 方法的第一个参数必须是 Throwable 类型,最好与 _@_Retryable 中的 value一致。
  • 方法的返回值必须与@Retryable的方法返回值一致,否则该方法不能被执行。

测试类:

  1. package com.tanhua.dubbo.server;
  2. import org.junit.Test;
  3. import org.junit.runner.RunWith;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.boot.test.context.SpringBootTest;
  6. import org.springframework.test.context.junit4.SpringRunner;
  7. @SpringBootTest
  8. @RunWith(SpringRunner.class)
  9. public class TestRetryService {
  10. @Autowired
  11. private RetryService retryService;
  12. @Test
  13. public void testRetry() {
  14. System.out.println(this.retryService.execute(90));
  15. }
  16. }

测试结果,会有3次重试机会进行生成随机数,如果3次随机数都小于90,最后返回88。

8.1.2、RequestService

  1. package com.tanhua.dubbo.server.service;
  2. import cn.hutool.http.HttpRequest;
  3. import cn.hutool.http.HttpResponse;
  4. import cn.hutool.http.Method;
  5. import com.tanhua.dubbo.server.exception.UnauthorizedException;
  6. import lombok.extern.slf4j.Slf4j;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.retry.annotation.Backoff;
  9. import org.springframework.retry.annotation.Recover;
  10. import org.springframework.retry.annotation.Retryable;
  11. import org.springframework.stereotype.Service;
  12. /**
  13. * 环信接口通用请求服务
  14. */
  15. @Service
  16. @Slf4j
  17. public class RequestService {
  18. @Autowired
  19. private TokenService tokenService;
  20. /**
  21. * 通用的发送请求方法
  22. *
  23. * @param url 请求地址
  24. * @param body 请求参数
  25. * @param method 请求方法
  26. * @return
  27. */
  28. @Retryable(value = UnauthorizedException.class, maxAttempts = 5, backoff = @Backoff(delay = 2000L, multiplier = 2))
  29. public HttpResponse execute(String url, String body, Method method) {
  30. String token = this.tokenService.getToken();
  31. HttpRequest httpRequest;
  32. switch (method) {
  33. case POST: {
  34. httpRequest = HttpRequest.post(url);
  35. break;
  36. }
  37. case DELETE: {
  38. httpRequest = HttpRequest.delete(url);
  39. break;
  40. }
  41. case PUT: {
  42. httpRequest = HttpRequest.put(url);
  43. break;
  44. }
  45. case GET: {
  46. httpRequest = HttpRequest.get(url);
  47. break;
  48. }
  49. default: {
  50. return null;
  51. }
  52. }
  53. HttpResponse response = httpRequest
  54. .header("Content-Type", "application/json") //设置请求内容类型
  55. .header("Authorization", "Bearer " + token) //设置token
  56. .body(body) // 设置请求数据
  57. .timeout(20000) // 超时时间
  58. .execute(); // 执行请求
  59. if (response.getStatus() == 401) {
  60. //token失效,重新刷新token
  61. this.tokenService.refreshToken();
  62. //抛出异常,需要进行重试
  63. throw new UnauthorizedException(url, body, method);
  64. }
  65. return response;
  66. }
  67. @Recover //全部重试失败后执行
  68. public HttpResponse recover(UnauthorizedException e) {
  69. log.error("获取token失败!url = " + e.getUrl() + ", body = " + e.getBody() + ", method = " + e.getMethod().toString());
  70. //如果重试5次后,依然不能获取到token,说明网络或账号出现了问题,只能返回null了,后续的请求将无法再执行
  71. return null;
  72. }
  73. }
  1. package com.tanhua.dubbo.server.exception;
  2. import cn.hutool.http.Method;
  3. import lombok.AllArgsConstructor;
  4. import lombok.Data;
  5. import lombok.NoArgsConstructor;
  6. @AllArgsConstructor
  7. @NoArgsConstructor
  8. @Data
  9. public class UnauthorizedException extends RuntimeException {
  10. private String url;
  11. private String body;
  12. private Method method;
  13. }

测试用例:

  1. package com.tanhua.dubbo.server;
  2. import cn.hutool.http.HttpResponse;
  3. import cn.hutool.http.Method;
  4. import com.tanhua.dubbo.server.config.HuanXinConfig;
  5. import com.tanhua.dubbo.server.service.RequestService;
  6. import org.junit.Test;
  7. import org.junit.runner.RunWith;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.boot.test.context.SpringBootTest;
  10. import org.springframework.test.context.junit4.SpringRunner;
  11. @SpringBootTest
  12. @RunWith(SpringRunner.class)
  13. public class TestRequestService {
  14. @Autowired
  15. private RequestService requestService;
  16. @Autowired
  17. private HuanXinConfig huanXinConfig;
  18. @Test
  19. public void testQueryHuanXinUser() {
  20. String targetUrl = this.huanXinConfig.getUrl()
  21. + this.huanXinConfig.getOrgName() + "/"
  22. + this.huanXinConfig.getAppName() + "/users/1";
  23. HttpResponse response = this.requestService.execute(targetUrl, null, Method.GET);
  24. System.out.println(response);
  25. }
  26. }

8.2、注册环信用户

注册环信用户分为2种,开放注册、授权注册,区别在于开发注册不需要token,授权注册需要token。我们使用的授权注册。
官方文档:《注册单个用户(授权)》

说明:环信用户数据需要保存到数据中。

8.2.1、HuanXinUser

在my-tanhua-dubbo-interface工程中创建该类:
需要在此工程中添加MybatisPlus依赖:

  1. <dependency>
  2. <groupId>com.baomidou</groupId>
  3. <artifactId>mybatis-plus</artifactId>
  4. </dependency>
  1. package com.tanhua.dubbo.server.pojo;
  2. import com.baomidou.mybatisplus.annotation.TableName;
  3. import lombok.AllArgsConstructor;
  4. import lombok.Data;
  5. import lombok.NoArgsConstructor;
  6. import java.util.Date;
  7. /**
  8. * 环信用户对象
  9. */
  10. @Data
  11. @NoArgsConstructor
  12. @AllArgsConstructor
  13. @TableName("tb_huanxin_user")
  14. public class HuanXinUser implements java.io.Serializable{
  15. private static final long serialVersionUID = -6400630011196593976L;
  16. private Long id; //主键Id
  17. /**
  18. * 环信 ID ;也就是 IM 用户名的唯一登录账号,长度不可超过64个字符长度
  19. */
  20. private String username;
  21. /**
  22. * 登录密码,长度不可超过64个字符长度
  23. */
  24. private String password;
  25. /**
  26. * 昵称(可选),在 iOS Apns 推送时会使用的昵称(仅在推送通知栏内显示的昵称),
  27. * 并不是用户个人信息的昵称,环信是不保存用户昵称,头像等个人信息的,
  28. * 需要自己服务器保存并与给自己用户注册的IM用户名绑定,长度不可超过100个字符
  29. */
  30. private String nickname;
  31. private Long userId; //用户id
  32. private Date created; //创建时间
  33. private Date updated; //更新时间
  34. }

数据库表结构:

  1. CREATE TABLE `tb_huanxin_user` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  3. `user_id` bigint(20) NOT NULL COMMENT '用户id',
  4. `username` varchar(32) NOT NULL COMMENT '环信用户名',
  5. `password` varchar(32) NOT NULL COMMENT '环信密码',
  6. `nickname` varchar(100) DEFAULT NULL COMMENT '昵称',
  7. `created` datetime DEFAULT NULL COMMENT '创建时间',
  8. `updated` datetime DEFAULT NULL COMMENT '更新时间',
  9. PRIMARY KEY (`id`),
  10. KEY `user_id` (`user_id`),
  11. KEY `username` (`username`)
  12. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

8.2.2、定义接口

  1. //com.tanhua.dubbo.server.api.HuanXinApi
  2. /**
  3. * 注册环信用户
  4. * 参见:http://docs-im.easemob.com/im/server/ready/user#%E6%B3%A8%E5%86%8C%E5%8D%95%E4%B8%AA%E7%94%A8%E6%88%B7_%E5%BC%80%E6%94%BE
  5. *
  6. * @param userId 用户id
  7. * @return
  8. */
  9. Boolean register(Long userId);
  10. /**
  11. * 根据用户Id询环信账户信息
  12. *
  13. * @param userId
  14. * @return
  15. */
  16. HuanXinUser queryHuanXinUser(Long userId);

8.2.3、实现接口

  1. //com.tanhua.dubbo.server.api.HuanXinApiImpl
  2. @Override
  3. public Boolean register(Long userId) {
  4. String targetUrl = this.huanXinConfig.getUrl()
  5. + this.huanXinConfig.getOrgName() + "/" +
  6. this.huanXinConfig.getAppName() + "/users";
  7. HuanXinUser huanXinUser = new HuanXinUser();
  8. huanXinUser.setUsername("HX_" + userId); // 用户名
  9. huanXinUser.setPassword(IdUtil.simpleUUID()); //随机生成的密码
  10. HttpResponse response = this.requestService.execute(targetUrl, JSONUtil.toJsonStr(Arrays.asList(huanXinUser)), Method.POST);
  11. if (response.isOk()) {
  12. //将环信的账号信息保存到数据库
  13. huanXinUser.setUserId(userId);
  14. huanXinUser.setCreated(new Date());
  15. huanXinUser.setUpdated(huanXinUser.getCreated());
  16. this.huanXinUserMapper.insert(huanXinUser);
  17. return true;
  18. }
  19. return false;
  20. }
  21. @Override
  22. public HuanXinUser queryHuanXinUser(Long userId) {
  23. QueryWrapper<HuanXinUser> wrapper = new QueryWrapper<>();
  24. wrapper.eq("user_id", userId);
  25. return this.huanXinUserMapper.selectOne(wrapper);
  26. }
  1. package com.tanhua.dubbo.server.mapper;
  2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  3. import com.tanhua.dubbo.server.pojo.HuanXinUser;
  4. import org.apache.ibatis.annotations.Mapper;
  5. @Mapper
  6. public interface HuanXinUserMapper extends BaseMapper<HuanXinUser> {
  7. }

8.2.4、测试用例

  1. package com.tanhua.dubbo.server;
  2. import com.tanhua.dubbo.server.api.HuanXinApi;
  3. import org.junit.Test;
  4. import org.junit.runner.RunWith;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.boot.test.context.SpringBootTest;
  7. import org.springframework.test.context.junit4.SpringRunner;
  8. @SpringBootTest
  9. @RunWith(SpringRunner.class)
  10. public class TestHuanXinApi {
  11. @Autowired
  12. private HuanXinApi huanXinApi;
  13. @Test
  14. public void testRegister(){
  15. //注册用户id为1的用户到环信
  16. System.out.println(this.huanXinApi.register(1L));
  17. }
  18. @Test
  19. public void testQueryHuanXinUser(){
  20. //根据用户id查询环信用户信息
  21. System.out.println(this.huanXinApi.queryHuanXinUser(1L));
  22. }
  23. }

8.2.5、在sso中注册环信用户

需要在sso系统中使用dubbo服务进行注册环信用户。

第一步,导入依赖:

  1. <dependency>
  2. <groupId>cn.itcast.tanhua</groupId>
  3. <artifactId>my-tanhua-dubbo-interface</artifactId>
  4. <version>1.0-SNAPSHOT</version>
  5. </dependency>
  6. <!--dubbo的springboot支持-->
  7. <dependency>
  8. <groupId>com.alibaba.boot</groupId>
  9. <artifactId>dubbo-spring-boot-starter</artifactId>
  10. </dependency>
  11. <!--dubbo框架-->
  12. <dependency>
  13. <groupId>com.alibaba</groupId>
  14. <artifactId>dubbo</artifactId>
  15. </dependency>
  16. <!--zk依赖-->
  17. <dependency>
  18. <groupId>org.apache.zookeeper</groupId>
  19. <artifactId>zookeeper</artifactId>
  20. </dependency>
  21. <dependency>
  22. <groupId>com.github.sgroschupf</groupId>
  23. <artifactId>zkclient</artifactId>
  24. </dependency>

第二步,增加dubbo注册中心配置

application.properties:

  1. #dubbo注册中心配置
  2. dubbo.application.name = itcast-tanhua-server
  3. dubbo.registry.address = zookeeper://192.168.31.81:2181
  4. dubbo.registry.client = zkclient
  5. dubbo.registry.timeout = 60000
  6. dubbo.consumer.timeout = 60000

第三步,在UserService中增加相应的逻辑:

  1. @Reference(version = "1.0.0")
  2. private HuanXinApi huanXinApi;
  3. /**
  4. * 用户登录
  5. *
  6. * @param phone 手机号
  7. * @param code 验证码
  8. * @return
  9. */
  10. public String login(String phone, String code) {
  11. String redisKey = "CHECK_CODE_" + phone;
  12. boolean isNew = false;
  13. //校验验证码
  14. String redisData = this.redisTemplate.opsForValue().get(redisKey);
  15. if (!StringUtils.equals(code, redisData)) {
  16. return null; //验证码错误
  17. }
  18. //验证码在校验完成后,需要废弃
  19. this.redisTemplate.delete(redisKey);
  20. QueryWrapper<User> queryWrapper = new QueryWrapper<>();
  21. queryWrapper.eq("mobile", phone);
  22. User user = this.userMapper.selectOne(queryWrapper);
  23. if (null == user) {
  24. //需要注册该用户
  25. user = new User();
  26. user.setMobile(phone);
  27. user.setPassword(DigestUtils.md5Hex("123456"));
  28. //注册新用户
  29. this.userMapper.insert(user);
  30. isNew = true;
  31. //注册环信用户
  32. Boolean result = this.huanXinApi.register(user.getId());
  33. if (!result) {
  34. //注册环信失败,记录日志
  35. log.error("注册环信用户失败~ userId = " + user.getId());
  36. }
  37. }
  38. //生成token
  39. Map<String, Object> claims = new HashMap<String, Object>();
  40. claims.put("id", user.getId());
  41. // 生成token
  42. String token = Jwts.builder()
  43. .setClaims(claims) //payload,存放数据的位置,不能放置敏感数据,如:密码等
  44. .signWith(SignatureAlgorithm.HS256, secret) //设置加密方法和加密盐
  45. .setExpiration(new DateTime().plusHours(12).toDate()) //设置过期时间,12小时后过期
  46. .compact();
  47. try {
  48. //发送用户登录成功的消息
  49. Map<String, Object> msg = new HashMap<>();
  50. msg.put("id", user.getId());
  51. msg.put("date", System.currentTimeMillis());
  52. this.rocketMQTemplate.convertAndSend("tanhua-sso-login", msg);
  53. } catch (MessagingException e) {
  54. log.error("发送消息失败!", e);
  55. }
  56. return token + "|" + isNew;
  57. }

8.2.6、测试

将服务全部跑起来,使用APP进行测试,使用新手机号进行登录测试。
新注册的用户:
day06-完善小视频功能以及即时通讯 - 图18
所对应的环信用户:
day06-完善小视频功能以及即时通讯 - 图19
环信平台:
day06-完善小视频功能以及即时通讯 - 图20
可以看到已经注册到了环信。

8.3、查询环信用户信息

在app中,用户登录后需要根据用户名密码登录环信,由于用户名密码保存在后台,所以需要提供接口进行返回。
mock地址: https://mock-java.itheima.net/project/35/interface/api/563
day06-完善小视频功能以及即时通讯 - 图21

8.3.1、HuanXinUserVo

  1. package com.tanhua.server.vo;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. @Data
  6. @NoArgsConstructor
  7. @AllArgsConstructor
  8. public class HuanXinUserVo {
  9. private String username;
  10. private String password;
  11. }

8.3.2、HuanXinController

  1. package com.tanhua.server.controller;
  2. import com.tanhua.server.service.HuanXinService;
  3. import com.tanhua.server.vo.HuanXinUserVo;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.http.ResponseEntity;
  6. import org.springframework.web.bind.annotation.GetMapping;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. import org.springframework.web.bind.annotation.RestController;
  9. @RestController
  10. @RequestMapping("huanxin")
  11. public class HuanXinController {
  12. @Autowired
  13. private HuanXinService huanXinService;
  14. @GetMapping("user")
  15. public HuanXinUserVo queryHuanXinUser(){
  16. return this.huanXinService.queryHuanXinUser();
  17. }
  18. }

8.3.3、HuanXinService

  1. package com.tanhua.server.service;
  2. import cn.hutool.core.util.ObjectUtil;
  3. import com.alibaba.dubbo.config.annotation.Reference;
  4. import com.tanhua.common.pojo.User;
  5. import com.tanhua.common.utils.UserThreadLocal;
  6. import com.tanhua.dubbo.server.api.HuanXinApi;
  7. import com.tanhua.dubbo.server.pojo.HuanXinUser;
  8. import com.tanhua.server.vo.HuanXinUserVo;
  9. import org.springframework.stereotype.Service;
  10. @Service
  11. public class HuanXinService {
  12. @Reference(version = "1.0.0")
  13. private HuanXinApi huanXinApi;
  14. public HuanXinUserVo queryHuanXinUser() {
  15. User user = UserThreadLocal.get();
  16. //通过dubbo服务查询环信用户
  17. HuanXinUser huanXinUser = this.huanXinApi.queryHuanXinUser(user.getId());
  18. if (ObjectUtil.isNotEmpty(huanXinUser)) {
  19. return new HuanXinUserVo(huanXinUser.getUsername(), huanXinUser.getPassword());
  20. }
  21. return null;
  22. }
  23. }

8.3.4、测试

day06-完善小视频功能以及即时通讯 - 图22

8.4、查询个人信息

在消息模块中,需要实现根据环信用户名查询个人的用户信息。
接口文档:https://mock-java.itheima.net/project/35/interface/api/2921

8.4.1、dubbo服务

8.4.1.1、定义接口
  1. //com.tanhua.dubbo.server.api.HuanXinApi
  2. /**
  3. * 根据环信用户名查询用户信息
  4. *
  5. * @param userName
  6. * @return
  7. */
  8. HuanXinUser queryUserByUserName(String userName);

8.4.1.2、编写实现
  1. //com.tanhua.dubbo.server.api.HuanXinApiImpl
  2. @Override
  3. public HuanXinUser queryUserByUserName(String userName) {
  4. QueryWrapper<HuanXinUser> wrapper = new QueryWrapper<>();
  5. wrapper.eq("username", userName);
  6. return this.huanXinUserMapper.selectOne(wrapper);
  7. }

8.4.2、APP接口服务

8.4.2.1、UserInfoVo
  1. package com.tanhua.server.vo;
  2. import cn.hutool.core.annotation.Alias;
  3. import lombok.AllArgsConstructor;
  4. import lombok.Data;
  5. import lombok.NoArgsConstructor;
  6. @Data
  7. @AllArgsConstructor
  8. @NoArgsConstructor
  9. public class UserInfoVo {
  10. @Alias("userId")
  11. private Long id; //用户id
  12. @Alias("logo")
  13. private String avatar; //头像
  14. @Alias("nickName")
  15. private String nickname; //昵称
  16. private String birthday; //生日 2019-09-11
  17. private String age; //年龄
  18. private String gender; //性别 man woman
  19. private String city; //城市
  20. @Alias("edu")
  21. private String education; //学历
  22. private String income; //月收入
  23. @Alias("industry")
  24. private String profession; //行业
  25. private Integer marriage; //婚姻状态(0未婚,1已婚)
  26. }

8.4.2.2、IMController
  1. //com.tanhua.server.controller.IMController
  2. package com.tanhua.server.controller;
  3. import cn.hutool.core.util.ObjectUtil;
  4. import com.tanhua.server.service.IMService;
  5. import com.tanhua.server.vo.UserInfoVo;
  6. import lombok.extern.slf4j.Slf4j;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.http.HttpStatus;
  9. import org.springframework.http.ResponseEntity;
  10. import org.springframework.web.bind.annotation.GetMapping;
  11. import org.springframework.web.bind.annotation.RequestMapping;
  12. import org.springframework.web.bind.annotation.RequestParam;
  13. import org.springframework.web.bind.annotation.RestController;
  14. @RequestMapping("messages")
  15. @RestController
  16. @Slf4j
  17. public class IMController {
  18. @Autowired
  19. private IMService imService;
  20. /**
  21. * 根据环信用户名查询用户信息
  22. *
  23. * @param userName 环信用户
  24. * @return
  25. */
  26. @GetMapping("userinfo")
  27. public ResponseEntity<UserInfoVo> queryUserInfoByUserName(@RequestParam("huanxinId") String userName) {
  28. try {
  29. UserInfoVo userInfoVo = this.imService.queryUserInfoByUserName(userName);
  30. if (ObjectUtil.isNotEmpty(userInfoVo)) {
  31. return ResponseEntity.ok(userInfoVo);
  32. }
  33. } catch (Exception e) {
  34. log.error("根据环信id查询用户信息! userName = " + userName, e);
  35. }
  36. return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
  37. }
  38. }

8.4.2.3、IMService
  1. //com.tanhua.server.service.IMService
  2. package com.tanhua.server.service;
  3. import cn.hutool.core.bean.BeanUtil;
  4. import cn.hutool.core.util.ObjectUtil;
  5. import cn.hutool.core.util.StrUtil;
  6. import com.alibaba.dubbo.config.annotation.Reference;
  7. import com.tanhua.common.pojo.UserInfo;
  8. import com.tanhua.dubbo.server.api.HuanXinApi;
  9. import com.tanhua.dubbo.server.pojo.HuanXinUser;
  10. import com.tanhua.server.vo.UserInfoVo;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.stereotype.Service;
  13. @Service
  14. public class IMService {
  15. @Reference(version = "1.0.0")
  16. private HuanXinApi huanXinApi;
  17. @Autowired
  18. private UserInfoService userInfoService;
  19. public UserInfoVo queryUserInfoByUserName(String userName) {
  20. //查询环信账户
  21. HuanXinUser huanXinUser = this.huanXinApi.queryUserByUserName(userName);
  22. if (ObjectUtil.isEmpty(huanXinUser)) {
  23. return null;
  24. }
  25. //查询用户信息
  26. UserInfo userInfo = this.userInfoService.queryUserInfoByUserId(huanXinUser.getUserId());
  27. if (ObjectUtil.isEmpty(userInfo)) {
  28. return null;
  29. }
  30. UserInfoVo userInfoVo = BeanUtil.copyProperties(userInfo, UserInfoVo.class, "marriage");
  31. userInfoVo.setGender(userInfo.getSex().toString().toLowerCase());
  32. userInfoVo.setMarriage(StrUtil.equals("已婚", userInfo.getMarriage()) ? 1 : 0);
  33. return userInfoVo;
  34. }
  35. }

8.4.2.4、测试

day06-完善小视频功能以及即时通讯 - 图23

8.5、根据用户id查询个人信息

在消息模块与我的模块中,需要根据用户id查询个人信息,其响应的数据结构与上面一致,均为:UserInfoVo对象。
接口地址:https://mock-java.itheima.net/project/35/interface/api/875

8.5.1、MyCenterController

  1. package com.tanhua.server.controller;
  2. import cn.hutool.core.util.ObjectUtil;
  3. import com.tanhua.server.service.MyCenterService;
  4. import com.tanhua.server.vo.UserInfoVo;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.http.HttpStatus;
  8. import org.springframework.http.ResponseEntity;
  9. import org.springframework.web.bind.annotation.GetMapping;
  10. import org.springframework.web.bind.annotation.RequestMapping;
  11. import org.springframework.web.bind.annotation.RequestParam;
  12. import org.springframework.web.bind.annotation.RestController;
  13. @RequestMapping("users")
  14. @RestController
  15. @Slf4j
  16. public class MyCenterController {
  17. @Autowired
  18. private MyCenterService myCenterService;
  19. /**
  20. * 根据用户id查询用户信息
  21. *
  22. * @param userId 用户id,如果为空,表示查询当前登录人的信息
  23. * @return
  24. */
  25. @GetMapping
  26. public ResponseEntity<UserInfoVo> queryUserInfoByUserId(@RequestParam(value = "userID", required = false) Long userId) {
  27. try {
  28. UserInfoVo userInfoVo = this.myCenterService.queryUserInfoByUserId(userId);
  29. if (ObjectUtil.isNotEmpty(userInfoVo)) {
  30. return ResponseEntity.ok(userInfoVo);
  31. }
  32. } catch (Exception e) {
  33. log.error("根据用户id查询用户信息出错~ userId = " + userId, e);
  34. }
  35. return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
  36. }
  37. }

8.5.2、MyCenterService

  1. package com.tanhua.server.service;
  2. import cn.hutool.core.bean.BeanUtil;
  3. import cn.hutool.core.util.ObjectUtil;
  4. import cn.hutool.core.util.StrUtil;
  5. import com.tanhua.common.pojo.UserInfo;
  6. import com.tanhua.common.utils.UserThreadLocal;
  7. import com.tanhua.server.vo.UserInfoVo;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.stereotype.Service;
  10. @Service
  11. public class MyCenterService {
  12. @Autowired
  13. private UserInfoService userInfoService;
  14. public UserInfoVo queryUserInfoByUserId(Long userId) {
  15. if (ObjectUtil.isEmpty(userId)) {
  16. //如果查询id为null,就表示查询当前用户信息
  17. userId = UserThreadLocal.get().getId();
  18. }
  19. //查询用户信息
  20. UserInfo userInfo = this.userInfoService.queryUserInfoByUserId(userId);
  21. if (ObjectUtil.isEmpty(userInfo)) {
  22. return null;
  23. }
  24. UserInfoVo userInfoVo = BeanUtil.copyProperties(userInfo, UserInfoVo.class, "marriage");
  25. userInfoVo.setGender(userInfo.getSex().toString().toLowerCase());
  26. userInfoVo.setMarriage(StrUtil.equals("已婚", userInfo.getMarriage()) ? 1 : 0);
  27. return userInfoVo;
  28. }
  29. }

8.5.3、测试

day06-完善小视频功能以及即时通讯 - 图24

8.6、发送消息给客户端

目前已经完成了用户体系的对接,下面我们进行测试发送消息,场景是这样的:
day06-完善小视频功能以及即时通讯 - 图25
点击“聊一下”,就会给对方发送一条陌生人信息,这个消息由系统发送完成。
我们暂时通过环信的控制台进行发送: day06-完善小视频功能以及即时通讯 - 图26
消息内容:

  1. {"userId":1,"huanXinId":"HX_1","nickname":"黑马小妹","strangerQuestion":"你喜欢去看蔚蓝的大海还是去爬巍峨的高山?","reply":"我喜欢秋天的落叶,夏天的泉水,冬天的雪地,只要有你一切皆可~"}

day06-完善小视频功能以及即时通讯 - 图27
day06-完善小视频功能以及即时通讯 - 图28
day06-完善小视频功能以及即时通讯 - 图29
可以看到已经接收到了消息。

8.7、将用户数据同步到环信

需要将1~99用户注册到环信,因为我们提供的数据都是这些用户的数据。

  1. //com.tanhua.dubbo.server.TestHuanXinApi
  2. @Test
  3. public void testRegisterAllUser(){
  4. for (int i = 1; i < 100; i++) {
  5. this.huanXinApi.register(Long.valueOf(i));
  6. }
  7. }

day06-完善小视频功能以及即时通讯 - 图30
环信:
day06-完善小视频功能以及即时通讯 - 图31

9、 添加联系人

点击“聊一下”,就会成为联系人(好友)。
实现:

  • 将好友写入到MongoDB中
  • 将好友关系注册到环信

具体的流程如下:
day06-完善小视频功能以及即时通讯 - 图32

9.1、好友dubbo服务

9.1.1、定义接口

  1. package com.tanhua.dubbo.server.api;
  2. public interface UsersApi {
  3. /**
  4. * 保存好友关系
  5. *
  6. * @param userId 用户id
  7. * @param friendId 好友id
  8. * @return
  9. */
  10. String saveUsers(Long userId, Long friendId);
  11. /**
  12. * 删除好友数据
  13. *
  14. * @param userId 用户id
  15. * @param friendId 好友id
  16. * @return
  17. */
  18. Boolean removeUsers(Long userId, Long friendId);
  19. }

9.1.2、编写实现

  1. package com.tanhua.dubbo.server.api;
  2. import cn.hutool.core.util.ObjectUtil;
  3. import com.alibaba.dubbo.config.annotation.Service;
  4. import com.tanhua.dubbo.server.pojo.Users;
  5. import org.bson.types.ObjectId;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.data.mongodb.core.MongoTemplate;
  8. import org.springframework.data.mongodb.core.query.Criteria;
  9. import org.springframework.data.mongodb.core.query.Query;
  10. @Service(version = "1.0.0")
  11. public class UsersApiImpl implements UsersApi {
  12. @Autowired
  13. private MongoTemplate mongoTemplate;
  14. @Override
  15. public String saveUsers(Long userId, Long friendId) {
  16. if (!ObjectUtil.isAllNotEmpty(userId, friendId)) {
  17. return null;
  18. }
  19. // 检测是否该好友关系是否存在
  20. Query query = Query.query(Criteria
  21. .where("userId").is(userId)
  22. .and("friendId").is(friendId));
  23. long count = this.mongoTemplate.count(query, Users.class);
  24. if (count > 0) {
  25. return null;
  26. }
  27. Users users = new Users();
  28. users.setId(ObjectId.get());
  29. users.setDate(System.currentTimeMillis());
  30. users.setUserId(userId);
  31. users.setFriendId(friendId);
  32. //注册我与好友的关系
  33. this.mongoTemplate.save(users);
  34. //注册好友与我的关系
  35. users.setId(ObjectId.get());
  36. users.setUserId(friendId);
  37. users.setFriendId(userId);
  38. this.mongoTemplate.save(users);
  39. return users.getId().toHexString();
  40. }
  41. @Override
  42. public Boolean removeUsers(Long userId, Long friendId) {
  43. Query query1 = Query.query(Criteria.where("userId").is(userId)
  44. .and("friendId").is(friendId));
  45. //删除我与好友的关系数据
  46. long count1 = this.mongoTemplate.remove(query1, Users.class).getDeletedCount();
  47. Query query2 = Query.query(Criteria.where("userId").is(friendId)
  48. .and("friendId").is(userId));
  49. //删除好友与我的关系数据
  50. long count2 = this.mongoTemplate.remove(query2, Users.class).getDeletedCount();
  51. return count1 > 0 && count2 > 0;
  52. }
  53. }

9.2、环信dubbo服务

9.2.1、定义接口

在my-tanhua-dubbo-interface中定义。

  1. //com.tanhua.dubbo.server.api.HuanXinApi
  2. /**
  3. * 添加好友(双向好友关系)
  4. * 参见:http://docs-im.easemob.com/im/server/ready/user#%E6%B7%BB%E5%8A%A0%E5%A5%BD%E5%8F%8B
  5. *
  6. * @param userId 自己的id
  7. * @param friendId 好友的id
  8. * @return
  9. */
  10. Boolean addUserFriend(Long userId, Long friendId);
  11. /**
  12. * 删除好友关系(双向删除)
  13. * 参见:http://docs-im.easemob.com/im/server/ready/user#%E7%A7%BB%E9%99%A4%E5%A5%BD%E5%8F%8B
  14. *
  15. * @param userId 自己的id
  16. * @param friendId 好友的id
  17. * @return
  18. */
  19. Boolean removeUserFriend(Long userId, Long friendId);

9.2.2、编写实现

在my-tanhua-dubbo-huanxin中实现。

  1. //com.tanhua.dubbo.server.api.HuanXinApiImpl
  2. @Override
  3. public Boolean addUserFriend(Long userId, Long friendId) {
  4. String targetUrl = this.huanXinConfig.getUrl()
  5. + this.huanXinConfig.getOrgName() + "/"
  6. + this.huanXinConfig.getAppName() + "/users/HX_" +
  7. userId + "/contacts/users/HX_" + friendId;
  8. try {
  9. // 404 -> 对方未在环信注册
  10. return this.requestService.execute(targetUrl, null, Method.POST).isOk();
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. }
  14. // 添加失败
  15. return false;
  16. }
  17. @Override
  18. public Boolean removeUserFriend(Long userId, Long friendId) {
  19. String targetUrl = this.huanXinConfig.getUrl()
  20. + this.huanXinConfig.getOrgName() + "/"
  21. + this.huanXinConfig.getAppName() + "/users/HX_" +
  22. userId + "/contacts/users/HX_" + friendId;
  23. try {
  24. // 404 -> 对方未在环信注册
  25. return this.requestService.execute(targetUrl, null, Method.DELETE).isOk();
  26. } catch (Exception e) {
  27. e.printStackTrace();
  28. }
  29. // 添加失败
  30. return false;
  31. }

9.3、APP接口服务

接口地址:https://mock-java.itheima.net/project/35/interface/api/809
在my-tanhua-server中完成。

  1. //com.tanhua.server.controller.IMController
  2. /**
  3. * 添加好友
  4. *
  5. * @param param
  6. * @return
  7. */
  8. @PostMapping("contacts")
  9. public ResponseEntity<Void> contactUser(@RequestBody Map<String, Object> param) {
  10. try {
  11. Long friendId = Long.valueOf(param.get("userId").toString());
  12. boolean result = this.imService.contactUser(friendId);
  13. if (result) {
  14. return ResponseEntity.ok(null);
  15. }
  16. } catch (Exception e) {
  17. log.error("添加联系人失败! param = " + param, e);
  18. }
  19. return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
  20. }
  1. package com.tanhua.server.service;
  2. import cn.hutool.core.util.StrUtil;
  3. import com.alibaba.dubbo.config.annotation.Reference;
  4. import com.tanhua.common.pojo.User;
  5. import com.tanhua.common.utils.UserThreadLocal;
  6. import com.tanhua.dubbo.server.api.HuanXinApi;
  7. import com.tanhua.dubbo.server.api.UsersApi;
  8. import org.springframework.stereotype.Service;
  9. @Service
  10. public class IMService {
  11. @Reference(version = "1.0.0")
  12. private UsersApi usersApi;
  13. @Reference(version = "1.0.0")
  14. private HuanXinApi huanXinApi;
  15. /**
  16. * 添加好友
  17. *
  18. * @param friendId 好友id
  19. */
  20. public boolean contactUser(Long friendId) {
  21. User user = UserThreadLocal.get();
  22. String id = this.usersApi.saveUsers(user.getId(), friendId);
  23. if (StrUtil.isNotEmpty(id)) {
  24. //注册好友关系到环信
  25. return this.huanXinApi.addUserFriend(user.getId(), friendId);
  26. }
  27. return false;
  28. }
  29. }

9.4、测试

接口测试:
day06-完善小视频功能以及即时通讯 - 图33
Monodb数据:
day06-完善小视频功能以及即时通讯 - 图34
环信平台好友数据:
day06-完善小视频功能以及即时通讯 - 图35

9.5、重新生成好友关系数据

由于之前的数据并没有完整的双向数据,所以需要重新生成,如下:

  1. package com.tanhua.server;
  2. import cn.hutool.core.convert.Convert;
  3. import cn.hutool.core.util.RandomUtil;
  4. import com.tanhua.common.pojo.User;
  5. import com.tanhua.common.utils.UserThreadLocal;
  6. import com.tanhua.server.service.IMService;
  7. import org.junit.Test;
  8. import org.junit.runner.RunWith;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.boot.test.context.SpringBootTest;
  11. import org.springframework.test.context.junit4.SpringRunner;
  12. @RunWith(SpringRunner.class)
  13. @SpringBootTest
  14. public class TestIMService {
  15. @Autowired
  16. private IMService imService;
  17. /**
  18. * 构造好友数据,为1~99用户构造10个好友
  19. */
  20. @Test
  21. public void testUsers() {
  22. for (int i = 1; i <= 99; i++) {
  23. for (int j = 0; j < 10; j++) {
  24. User user = new User();
  25. user.setId(Convert.toLong(i));
  26. UserThreadLocal.set(user);
  27. this.imService.contactUser(this.getFriendId(user.getId()));
  28. }
  29. }
  30. }
  31. private Long getFriendId(Long userId) {
  32. Long friendId = RandomUtil.randomLong(1, 100);
  33. if (friendId.intValue() == userId.intValue()) {
  34. getFriendId(userId);
  35. }
  36. return friendId;
  37. }
  38. }

10、联系人列表

用户在消息模块中,可以查看联系人列表(好友列表)。
接口文档地址:https://mock-java.itheima.net/project/35/interface/api/803

10.1、dubbo服务

10.1.1、定义接口

  1. //com.tanhua.dubbo.server.api.UsersApi
  2. /**
  3. * 根据用户id查询全部Users列表
  4. *
  5. * @param userId
  6. * @return
  7. */
  8. List<Users> queryAllUsersList(Long userId);
  9. /**
  10. * 根据用户id查询Users列表(分页查询)
  11. *
  12. * @param userId
  13. * @return
  14. */
  15. PageInfo<Users> queryUsersList(Long userId, Integer page, Integer pageSize);

10.1.2、接口实现

  1. //com.tanhua.dubbo.server.api.UsersApiImpl
  2. @Override
  3. public List<Users> queryAllUsersList(Long userId) {
  4. Query query = Query.query(Criteria.where("userId").is(userId));
  5. return this.mongoTemplate.find(query, Users.class);
  6. }
  7. @Override
  8. public PageInfo<Users> queryUsersList(Long userId, Integer page, Integer pageSize) {
  9. PageRequest pageRequest = PageRequest.of(page - 1, pageSize, Sort.by(Sort.Order.desc("created")));
  10. Query query = Query.query(Criteria.where("userId").is(userId)).with(pageRequest);
  11. List<Users> usersList = this.mongoTemplate.find(query, Users.class);
  12. PageInfo<Users> pageInfo = new PageInfo<>();
  13. pageInfo.setPageNum(page);
  14. pageInfo.setPageSize(pageSize);
  15. pageInfo.setRecords(usersList);
  16. return pageInfo;
  17. }

10.2、APP接口服务

10.2.1、UsersVo

  1. package com.tanhua.server.vo;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. @Data
  6. @NoArgsConstructor
  7. @AllArgsConstructor
  8. public class UsersVo {
  9. private Long id;
  10. private String userId;
  11. private String avatar;
  12. private String nickname;
  13. private String gender;
  14. private Integer age;
  15. private String city;
  16. }

10.2.2、IMController

  1. //com.tanhua.server.controller.IMController
  2. /**
  3. * 查询联系人列表
  4. *
  5. * @param page
  6. * @param pageSize
  7. * @param keyword
  8. * @return
  9. */
  10. @GetMapping("contacts")
  11. public ResponseEntity<PageResult> queryContactsList(@RequestParam(value = "page", defaultValue = "1") Integer page,
  12. @RequestParam(value = "pagesize", defaultValue = "10") Integer pageSize,
  13. @RequestParam(value = "keyword", required = false) String keyword) {
  14. PageResult pageResult = this.imService.queryContactsList(page, pageSize, keyword);
  15. return ResponseEntity.ok(pageResult);
  16. }

10.2.3、IMService

  1. //com.tanhua.server.service.IMService
  2. public PageResult queryContactsList(Integer page, Integer pageSize, String keyword) {
  3. PageResult pageResult = new PageResult();
  4. pageResult.setPage(page);
  5. pageResult.setPagesize(pageSize);
  6. User user = UserThreadLocal.get();
  7. List<Users> usersList;
  8. if (StringUtils.isNotEmpty(keyword)) {
  9. //关键不为空,查询所有的好友,在后面进行关键字过滤
  10. usersList = this.usersApi.queryAllUsersList(user.getId());
  11. } else {
  12. //关键字为空,进行分页查询
  13. PageInfo<Users> usersPageInfo = this.usersApi.queryUsersList(user.getId(), page, pageSize);
  14. usersList = usersPageInfo.getRecords();
  15. }
  16. if (CollUtil.isEmpty(usersList)) {
  17. return pageResult;
  18. }
  19. List<Object> userIds = CollUtil.getFieldValues(usersList, "friendId");
  20. QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
  21. queryWrapper.in("user_id", userIds);
  22. if (StringUtils.isNotEmpty(keyword)) {
  23. queryWrapper.like("nick_name", keyword);
  24. }
  25. List<UserInfo> userInfoList = this.userInfoService.queryUserInfoList(queryWrapper);
  26. List<UsersVo> contactsList = new ArrayList<>();
  27. //填充用户信息
  28. for (UserInfo userInfo : userInfoList) {
  29. UsersVo usersVo = new UsersVo();
  30. usersVo.setId(userInfo.getUserId());
  31. usersVo.setAge(userInfo.getAge());
  32. usersVo.setAvatar(userInfo.getLogo());
  33. usersVo.setGender(userInfo.getSex().name().toLowerCase());
  34. usersVo.setNickname(userInfo.getNickName());
  35. //环信用户账号
  36. usersVo.setUserId("HX_" + String.valueOf(userInfo.getUserId()));
  37. usersVo.setCity(StringUtils.substringBefore(userInfo.getCity(), "-"));
  38. contactsList.add(usersVo);
  39. }
  40. pageResult.setItems(contactsList);
  41. return pageResult;
  42. }

10.3、测试

day06-完善小视频功能以及即时通讯 - 图36
day06-完善小视频功能以及即时通讯 - 图37