第8章 即时通讯功能实现

今日内容介绍

63590012725

学习目标

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

1. 即时通信

【目标】

掌握环信通讯组件使用

【路径】

1:了解即时通讯

2:环信介绍

3:抽取环信组件

【讲解】

1.1. 即时通讯介绍

1.1.1. 什么是即时通信?

1569567156116

1.1.2. 功能说明

在探花交友项目中也提供了类似微信的聊天功能,用户可以和好友或陌生人聊天。

如果是陌生人,通过《聊一下》功能进行打招呼,如果对方同意后,就成为了好友,可以进行聊天了。

陌生人之间如果相互喜欢,那么就会成为好友,也就可以聊天了。

在消息界面中也可以查看:点赞、评论、喜欢、公告等消息信息。

1570760685758

1570760715769

1570760768500

1.1.3. 技术方案

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

对于现实即时通讯往往有两种方案:

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

如何选择呢?

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

探花交友项目选择方案二进行实现。

1.2. 环信介绍

官网:https://www.easemob.com/ 稳定健壮,消息必达,亿级并发的即时通讯云

1570763722654

1.2.1. 开发简介

平台架构:

集成:

环信和用户体系的集成主要发生在2个地方,服务器端集成和客户端集成。

1570776683692

探花集成:

1.2.2. 环信Console

需要使用环信平台,那么必须要进行注册,登录之后即可创建应用。环信100以内的用户免费使用,100以上就要注册企业版了。

企业版价格:

1570778131775

创建应用:

1570778173832

创建完成:

1570778297121

1.2.3. 接口说明

1.2.3.1. Appkey 数据结构

当您申请了 AppKey 后,会得到一个 xxxx#xxxx 格式的字符串,字符串只能由小写字母数字组成,AppKey是环信应用的唯一标识。

前半部分 org_name 是在多租户体系下的唯一租户标识,后半部分 app_name 是租户下的app唯一标识(在环信后台创建一个app时填写的应用 id 即是 app_name )。

下述的 REST API 中,/{org_name}/{app_name}的请求,均是针对一个唯一的appkey进行的。目前环信注册的appkey暂不能由用户自己完成删除操作,如果对 APP 删除需要联系环信操作完成。

Appkey xxxx 分隔符 xxxx 描述
环信应用的唯一标识 org_name # app_name app_name只能是字母、数字、横线组合。长度不能超过32

1.2.3.2. 环信 ID 数据结构

环信作为一个聊天通道,只需要提供环信 ID (也就是 IM 用户名)和密码就够了。

名称 字段名 数据类型 描述
环信 ID username String 在 AppKey 的范围内唯一用户名。
用户密码 password String 用户登录环信使用的密码。

1.2.3.3. 获取管理员权限

环信提供的 REST API 需要权限才能访问,权限通过发送 HTTP 请求时携带 token 来体现,下面描述获取 token 的方式。说明:API 描述的时候使用到的 {APP 的 client_id} 之类的这种参数需要替换成具体的值。

重要提醒:获取 token 时服务器会返回 token 有效期,具体值参考接口返回的 expires_in 字段值。由于网络延迟等原因,系统不保证 token 在此值表示的有效期内绝对有效,如果发现 token 使用异常请重新获取新的 token,比如“http response code”返回 401。另外,请不要频繁向服务器发送获取 token 的请求,同一账号发送此请求超过一定频率会被服务器封号,切记,切记!!

client_id 和 client_secret 可以在环信管理后台的APP 详情页面看到。

HTTP Request

请求方式 请求路径
img /{org_name}/{app_name}/token

Request Headers

参数 说明
Content-Type application/json

Request Body

参数 说明
grant_type client_credentials
client_id App的client_id,可在app详情页找到
client_secret App的client_secret,可在app详情页找到

Response Body

参数 说明
access_token 有效的token字符串
expires_in token 有效时间,以秒为单位,在有效期内不需要重复获取
application 当前 App 的 UUID 值

1570779400739

1.3. 抽取环信组件(了解)

63590156609

url:国内一区地址 url: http://a1.easemob.com/
orgName:1112190901181842
appName:tanhua119
Client ID:YXA6YbMJQfW_Q-miKVQajI4ZJQ
ClientSecret:YXA6oLQ-m3KF1qzbU4YG9FlJ92veUfY

tanhua-commons模块中抽取环信组件

1.3.1. pom文件

tanhua-commons模块pom文件加入依赖如下

<!--springboot基础起步依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

1.3.2. HuanXinProperties

tanhua-commons模块properties包下

package com.tanhua.commons.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "tanhua.huanxin")
@Data
public class HuanXinProperties {

    private String url;
    private String orgName;
    private String appName;
    private String clientId;
    private String clientSecret;

    public String getHuanXinUrl() {
        return this.url
                + this.orgName + "/"
                + this.appName;
    }
}

1.3.3. HuanXinUser

tanhua-commons模块vo包下

package com.tanhua.commons.vo;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.Serializable;

@Data
@AllArgsConstructor
public class HuanXinUser implements Serializable {

    private String username;
    private String password;
    private String nickname;
}

1.3.4.TanHuaException

package com.tanhua.commons.exception;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 自定义异常
 */
@Data
@NoArgsConstructor
public class TanHuaException extends RuntimeException {
    private Object errData;

    public TanHuaException(String errMessage){
        super(errMessage);
    }

    public TanHuaException(Object data){
        super();
        this.errData = data;
    }

}

1.3.4. HuanXinTemplate

tanhua-commons模块templates包下

package com.tanhua.commons.templates;

import com.alibaba.fastjson.JSON;
import com.tanhua.commons.exception.TanHuaException;
import com.tanhua.commons.properties.HuanXinProperties;
import com.tanhua.commons.vo.HuanXinUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import java.util.*;

/**
 * 即时通讯模板类
 */
@Slf4j
public class HuanXinTemplate {

    @Autowired
    private RestTemplate restTemplate;

    private HuanXinProperties props;

    private long tokenDuration;

    private String token;

    public HuanXinTemplate(HuanXinProperties properties){
        this.props = properties;
    }

    /**
     * 发送消息
     * @param target 接收方
     * @param msg
     */
    public void sendMsg(String target, String msg) {
        String url = props.getHuanXinUrl();
        url+="/messages";

        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type","application/json");
        headers.add("Authorization","Bearer " + getToken());

        Map<String,Object> requestBody = new HashMap<String,Object>();
        requestBody.put("target_type","users");
        requestBody.put("target", Arrays.asList(target));

        Map<String, Object> msgParam = new HashMap<String, Object>();
        msgParam.put("msg", msg);
        msgParam.put("type", "txt");

        requestBody.put("msg", msgParam);

        HttpEntity<Map<String,Object>> httpEntity = new HttpEntity<Map<String,Object>>(requestBody,headers);
        ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, httpEntity, String.class);
        try {
            int statusCode = responseEntity.getStatusCode().value();
            log.info("发送消息*****url:{}******sendMsg:{}******statusCode{}*****",url,msg,statusCode);
            if(200 != statusCode){
                throw new TanHuaException("发送信息失败!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 交友
     * @param userId
     * @param friendId
     */
    public void makeFriends(Long userId, Long friendId){
        String token = getToken();
        String url = props.getHuanXinUrl();
        url+="/users/"+ userId.toString()+"/contacts/users/" + friendId.toString();

        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type","application/json");
        headers.add("Authorization","Bearer " + token);

        HttpEntity httpEntity = new HttpEntity(headers);
        ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, httpEntity, String.class);
        int statusCode = responseEntity.getStatusCode().value();
        log.info("交友*****url:{}******userId:{}******friendId{}*****",url,userId,friendId);
        if(200 != statusCode){
            throw new TanHuaException("添加好友失败!");
        }
    }

    /**
     * 注册用户
     * @param userId
     */
    public void register(Long userId){
        // 获取授权令牌
        String token = getToken();
        // 注册用户rest api地址
        String url = props.getHuanXinUrl();
        url+="/users";
        // 请求体内容
        HuanXinUser user = new HuanXinUser(userId.toString(),"123456","1");
        // 请求头信息
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type","application/json");
        headers.add("Authorization","Bearer " + token);
        // 发送请求
        ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, new HttpEntity<HuanXinUser>(user, headers), String.class);
        int statusCode = responseEntity.getStatusCode().value();
        log.info("注册用户*****url:{}******userId:{}*****",url,userId);
        if(200 != statusCode){
            throw new TanHuaException("用户注册失败,HuanXin httpCode:"+statusCode);
        }
    }

    /**
     * 注册用户
     */
    public void registerBatch(){
        // 获取授权令牌
        String token = getToken();
        // 注册用户rest api地址
        String url = props.getHuanXinUrl();
        url+="/users";
        // 请求体内容
        HuanXinUser user = new HuanXinUser("1","123456","1");
        List<HuanXinUser> list = new ArrayList<HuanXinUser>();
        for (int i = 10; i < 20 ; i++) {
            list.add(new HuanXinUser(i+"",i+"",String.format("今晚打老虎_%05d",i)));
        }
        // 请求头信息
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type","application/json");
        headers.add("Authorization","Bearer " + token);
        // 发送请求
        ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, new HttpEntity<List>(list, headers), String.class);
        int statusCode = responseEntity.getStatusCode().value();
        log.info("注册用户*****url:{}******userId:{}*****",url,list);
        if(200 != statusCode){
            throw new TanHuaException("用户注册失败,HuanXin httpCode:"+statusCode);
        }
    }

    /**
     * 获取授权
     * @return
     */
    private String getToken(){
        if(tokenDuration < System.currentTimeMillis()){
            return applyNewToken();
        }
        return token;
    }

    /**
     * 获取管理员的授权令牌
     * @return
     */
    private String applyNewToken(){
        String url = props.getHuanXinUrl();
        url+="/token";

        Map<String,String> paramMap = new HashMap<String,String>();
        paramMap.put("grant_type","client_credentials");
        paramMap.put("client_id",props.getClientId());
        paramMap.put("client_secret",props.getClientSecret());

        ResponseEntity<String> resEntity = restTemplate.postForEntity(url, paramMap, String.class);
        int statusCode = resEntity.getStatusCode().value();
        log.info("获取管理员的授权令牌*****url:{}******statusCode:{}*****",url,statusCode);
        if(200 != statusCode){
            throw new TanHuaException("获取环信token失败");
        }
        Map<String,Object> resultMap = JSON.parseObject(resEntity.getBody(),Map.class);
        long expiresInSeconds = (int)resultMap.get("expires_in");
        tokenDuration = System.currentTimeMillis()-10*60*1000 + expiresInSeconds*1000;
        token = (String) resultMap.get("access_token");
        return token;
    }
}

1.3.5. CommonsAutoConfiguration

tanhua-commons模块修改CommonsAutoConfiguration自动装配类中

package com.tanhua.commons;
... ...
/**
 * 自动配置类
 */
@Configuration
@EnableConfigurationProperties({SmsProperties.class, OssProperties.class, FaceProperties.class, HuanXinProperties.class})
public class CommonsAutoConfiguration {

    ... ...

    @Bean
    public HuanXinTemplate huanXinTemplate(HuanXinProperties huanXinProperties){
        return new HuanXinTemplate(huanXinProperties);
    }
    
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder){
        return builder.build();
    }
}

1.3.6. application.yml

tanhua-server模块的application.yml文件加入配置如下

 tanhua:
 ...
 # im
  huanxin:
    url: http://a1.easemob.com/
    orgName: 1112190901181842
    appName: tanhua
    clientId: YXA6fVCzHj7rQjySx-6Ffsq2PA
    clientSecret: YXA62etJXe7EZloCx-SjHByJ5uc4HQQ

1.3.7. 测试

tanhua-server模块的test包下

package com.tanhua.server.test;

import com.alibaba.fastjson.JSON;
import com.tanhua.commons.templates.HuanXinTemplate;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.HashMap;
import java.util.Map;

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

    @Autowired
    private HuanXinTemplate huanXinTemplate;

    @Test
    public void testRegister(){
        huanXinTemplate.register(1l);
        huanXinTemplate.register(2l);
    }

    @Test
    public void registerBatch(){
        huanXinTemplate.registerBatch();
    }

    @Test
    public void makeFriend(){
        huanXinTemplate.makeFriends(1l,2l);
    }

    @Test
    public void sendMsg() {
        String ab = "{\"userId\": \"1\",\"nickname\":\"黑马小妹\",\"strangerQuestion\": \"你喜欢去看蔚蓝的大海还是去爬巍峨的高山?\",\"reply\": \"我喜欢秋天的落叶,夏天的泉水,冬天的雪地,只要有你一切皆可~\"}";
        Map<String, Object> map = new HashMap<>();
        map.put("userId", "1");
        map.put("nickname", "小师妹");
        map.put("strangerQuestion", "你喜欢去看蔚蓝的大海还是去爬巍峨的高山");
        map.put("reply", "我喜欢秋天的落叶,夏天的泉水,冬天的雪地,只要有你一切皆可~");
        String msg = JSON.toJSONString(map);
        System.out.println(msg);
        huanXinTemplate.sendMsg("2",msg);
    }
}

【小结】

掌握环信组件使用

2. 应用集成(重点)

【目标】

掌握环信通讯集成使用

【路径】

1:注册环信用户

2:查询环信用户信息

3:发送消息给客户端

【讲解】

61432878229

将用户体系集成的逻辑写入到tanhua-server系统中。

  • 探花用户注册时需要将用户信息注册到环信系统中
    • 对于老数据:编写单元测试方法批量的注册到环信
    • 对于新数据:loginVerification注册到环信
  • APP从服务端获取当前用户的环信用户密码,自动登入环信系统
  • APP自动获取环信服务器发送的信息数据

2.1. 注册环信用户

在用户登录逻辑中,当第一次注册时,将用户信息注册到环信

tanhua-server模块的service包下

 @Autowired
 private HuanXinTemplate huanXinTemplate; 
 /**
   * 注册登录-第一步:验证码校验(登录)
   */
 public Map<String, Object> loginVerification(String phone, String verificationCode) {
 	......
 	//用户不存在,自动注册用户
    if (user == null) {
        ......
        //注册环信通讯
        huanXinTemplate.register(user.getId());
    }
 }

2.2. 查询环信用户信息

在app中,用户登录后需要根据用户名密码登录环信,由于用户名密码保存在后台,所以需要提供接口进行返回。

1570852095822

tanhua-server模块的controller包下编写HuanXinController实现

package com.tanhua.server.controller;

import com.tanhua.commons.vo.HuanXinUser;
import com.tanhua.domain.db.User;
import com.tanhua.server.interceptor.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * 登录环信云-由app来实现
 * 后台主要返回当前登录的用户信息
 */
@RestController
@RequestMapping("/huanxin")
@Slf4j
public class HuanxinUserController {


    /**
     * 返回app需要当前用户的账号 密码
     */
    @RequestMapping(value = "/user",method = RequestMethod.GET)
    public ResponseEntity huanxinUser(){
        log.debug("************环信登录成功了**************");
        User user = UserHolder.getUser();
        //用户id //密码 //昵称
        HuanXinUser huanXinUser = new HuanXinUser(user.getId().toString(),"123456","小红");
        return ResponseEntity.ok(huanXinUser);
    }
}

2.3. 发送消息给客户端

目前已经完成了用户体系的对接,下面我们进行测试发送消息,场景是这样的:

image-20201108104329308

1570853314493

点击“聊一下”,就会给对方发送一条陌生人信息,这个消息由系统发送完成。

我们暂时通过环信的控制台进行发送: 1570853400508

消息内容:

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

1570853459084

1570853651822

1570853668888

可以看到已经接收到了消息。

2.4. 测试注意

1.保证用户一定是已经注册的,否则不能使用环信云

2.如果用户之间需要发送消息(即时通讯),一定先登录(由app来实现)

3.清除数据,重新进行配置

63590574736

63590591339

4.配置环信

63590565183

5.消息模块-显示加载失败,请联系管理员

63590560002

6.如果再次打开失败,重新登录一次

7.app如果使用了a用户登录了,只能使用a用户操作。

【小结】

掌握环信通讯集成使用

3. 消息管理(重点)

【目标】

掌握消息管理实现

【路径】

1:查看用户详情

2:查看陌生人消息

3:回复陌生人消息

4:添加联系人

5:联系人列表

【讲解】

客户端完成消息管理共有三步

  • 查看用户详情,点击聊一下查看陌生人问题
  • 填写内容,发送陌生人消息
  • 如果对方感兴趣,可以回复陌生人消息,双方成为好友

3.1. 服务消费者-查看用户详情

在首页可以查看感兴趣人的详细资料。点击“聊一下”,可以查看对方的问题

61433244399

3.1.1. mock接口

image-20201107213518294

3.1.2. TodayBestController

    /**
     * 佳人信息
     */
    @RequestMapping(value = "/{id}/personalInfo",method = RequestMethod.GET)
    public ResponseEntity personalInfo(@PathVariable("id") Long personId){
        TodayBestVo todayBestVo=todayBestService.personalInfo(personId);
        return ResponseEntity.ok(todayBestVo);
    }

3.1.3. TodayBestService

  /**
     * 佳人信息
     */
    public TodayBestVo personalInfo(Long personId) {
        Long userId = UserHolder.getUserId();//当前用户id
        //1 根据当前用户id 推荐的用户id查询缘分值
        RecommendUser recommendUser = recommendUserApi.queryScoreById(userId,personId);
        //2 调用getTodayBestVo得到佳人信息
        TodayBestVo todayBestVo = getTodayBestVo(recommendUser);
        //3 返回佳人信息
        return todayBestVo;
    }

3.2. 服务提供者-查看用户详情

3.2.1. RecommendUserApi

tanhua-dubbo-interface模块编写查询用户缘分值方法

   /**
     * 根据当前用户id 和 佳人用户id 查询缘分值
     * @param userId
     * @param personId
     * @return
     */
    RecommendUser queryScoreById(Long userId, Long personId);

3.2.2. RecommendUserApiImpl

tanhua-dubbo-service模块编写查询用户缘分值方法

    /**
     * 根据当前用户id 和 佳人用户id 查询缘分值
     * @param userId
     * @param personId
     * @return
     */
    @Override
    public RecommendUser queryScoreById(Long userId, Long personId) {
        Query query = new Query();
        query.addCriteria(Criteria.where("userId").is(personId).and("toUserId").is(userId));
        RecommendUser recommendUser = mongoTemplate.findOne(query, RecommendUser.class);
        if(recommendUser == null || recommendUser.getScore() == null){
            recommendUser = new RecommendUser();
            recommendUser.setUserId(personId);//推荐的用户id
            recommendUser.setToUserId(userId);//给谁推荐的用户
            recommendUser.setScore(66.6);//缘分值
        }
        return recommendUser;
    }

3.2.3 测试

为方便测试可以修改RecommendUserApiImpl类中findPage方法

query.with(Sort.by(Sort.Order.asc("userId")));

61346987427

3.3. 服务消费者-查询陌生人问题

点击“聊一下”,可以查看对方的问题

61433276236

3.3.1. mock接口

image-20201107213537641

3.3.2. TodayBestController

    /**
     * 查询陌生人问题
     */
    @RequestMapping(value = "/strangerQuestions",method = RequestMethod.GET)
    public ResponseEntity strangerQuestions(Long userId){
        String content =todayBestService.strangerQuestions(userId);
        return ResponseEntity.ok(content);
    }

3.3.3. TodayBestService

    /**
     * 查询陌生人问题
     */
    public String strangerQuestions(Long userId) {
        Question question = questionApi.findByUserId(userId);
        String strangerQuestion = "约吗?";//默认值
        if (question != null && !StringUtils.isEmpty(question.getTxt())) {
            strangerQuestion = question.getTxt();
        }
        return strangerQuestion;
    }

3.3.4. 测试

61347028435

3.3.服务消费者-回复陌生人消息

需求:

  • 通过服务器端,给目标用户发送一条陌生人消息

61433323933

3.3.1. mock接口

image-20201107213558481

3.3.2. TodayBestController

    /**
     * 回复陌生人问题
     */
    @RequestMapping(value = "/strangerQuestions",method = RequestMethod.POST)
    public ResponseEntity replyQuestions(@RequestBody Map params){
        Long userId = Long.parseLong(params.get("userId").toString());
        String reply = (String)params.get("reply");
        todayBestService.replyQuestions(userId,reply);
        return ResponseEntity.ok(null);
    }

3.3.3. TodayBestService

TodayBestService编写方法,完成回复陌生人消息功能

   /**
     * 回复陌生人问题
     */
    public void replyQuestions(Long personId, String reply) {
        Long currentUserId = UserHolder.getUserId();
        //1.根据当前登录id查询用户信息
        UserInfo userInfo = userInfoApi.findUserInfoByUserId(currentUserId);
        //2根据陌生人用户id查询陌生人问题
        Question question = questionApi.findByUserId(personId);
        String strangerQuestion = "约吗?";//默认值
        if (question != null && !StringUtils.isEmpty(question.getTxt())) {
            strangerQuestion = question.getTxt();
        }
        //3构造消息调用环信发送即时通讯消息
        //{"userId": "1","nickname":"黑马小妹",
        // "strangerQuestion": "你喜欢去看蔚蓝的大海还是去爬巍峨的高山?",
        // "reply": "我喜欢秋天的落叶,夏天的泉水,冬天的雪地,只要有你一切皆可~"}
        Map<String,String> msgMap = new HashMap<>();
        msgMap.put("userId",currentUserId.toString());//当前用户的id
        msgMap.put("nickname",userInfo.getNickname());//当前用户的昵称
        msgMap.put("strangerQuestion",strangerQuestion);//佳人的问题
        msgMap.put("reply",reply);//当前用户的回复的内容
        //将map转为string
        String msg = JSON.toJSONString(msgMap);
        huanXinTemplate.sendMsg(personId.toString(),msg);
    }

3.3.4. 测试

发送消息测试

61347611387

接收消息测试

61347617708

postman:

63591190129

3.4. 服务消费者-添加联系人

用户获取陌生人消息后,点击“聊一下”,就会成为联系人(好友)。

实现:

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

61448373742

3.4.1. mock接口

1570886419453

3.4.2. IMController

package com.tanhua.server.controller;

import com.tanhua.server.service.IMService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

/**
 * 消息管理-控制层
 */
@RestController
@RequestMapping("/messages")
@Slf4j
public class IMController {

    @Autowired
    private IMService imService;

    /**
     * 联系人添加
     */
    @RequestMapping(value = "/contacts",method = RequestMethod.POST)
    public ResponseEntity saveContacts(@RequestBody Map params){
        Long personId = Long.parseLong(params.get("userId").toString());
        imService.saveContacts(personId);
        return ResponseEntity.ok(null);
    }
}

3.4.3. IMService

IMService补充添加联系人方法

package com.tanhua.server.service;

import com.tanhua.commons.templates.HuanXinTemplate;
import com.tanhua.dubbo.api.mongo.FriendApi;
import com.tanhua.server.interceptor.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 消息管理-业务逻辑处理层
 */
@Service
@Transactional
@Slf4j //日志注解
public class IMService {

    @Reference
    private FriendApi friendApi;

    @Autowired
    private HuanXinTemplate huanXinTemplate;

    /**
     * 联系人添加
     */
    public void saveContacts(Long personId) {
        Long userId = UserHolder.getUserId();
        //1 调用好友服务方法 往好友表插入2条记录
        friendApi.saveContacts(personId,userId);
        //2 调用环信云 成为好友
        huanXinTemplate.makeFriends(userId,personId);
    }
}

3.5. 服务提供者-添加联系人

3.5.1. FriendApi

创建FriendApi添加好友的方法

package com.tanhua.dubbo.api.mongo;

/**
 * 好友管理服务接口
 */
public interface FriendApi {
    /**
     * 联系人添加
     * @param personId
     * @param userId
     */
    void saveContacts(Long personId, Long userId);
}

3.5.2. FriendApiImpl

创建FriendApiImpl添加好友的方法

package com.tanhua.dubbo.api.mongo;

import com.tanhua.domain.mongo.Friend;
import org.apache.dubbo.config.annotation.Service;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

/**
 * 联系人服务接口实现类
 */
@Service
public class FriendApiImpl implements FriendApi{

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 联系人添加
     * @param personId
     * @param userId
     */
    @Override
    public void saveContacts(Long personId, Long userId) {
        //1.先查询personId userId记录是否存在
        Query query1 = new Query();
        query1.addCriteria(Criteria.where("userId").is(personId).and("friendId").is(userId));
        if(!mongoTemplate.exists(query1, Friend.class)) {
            //2.如果不存在则保存personId userId
            Friend friend = new Friend();
            friend.setId(ObjectId.get());//主键id
            friend.setUserId(personId);//
            friend.setFriendId(userId);//
            friend.setCreated(System.currentTimeMillis());
            mongoTemplate.insert(friend);
        }
        Query query2 = new Query();
        query2.addCriteria(Criteria.where("userId").is(userId).and("friendId").is(personId));
        //3.先查询userId personId 记录是否存在
        if(!mongoTemplate.exists(query2, Friend.class)) {
            //4.如果不存在则保存 userId personId
            Friend friend = new Friend();
            friend.setId(ObjectId.get());//主键id
            friend.setUserId(userId);//
            friend.setFriendId(personId);//
            friend.setCreated(System.currentTimeMillis());
            mongoTemplate.insert(friend);
        }
    }
}

3.5.3. 测试

postman测试:

61347778405

61347781745

可以看到好友已经添加成功。

app测试:注册新用户测试

61347773035

3.6. 服务消费者-联系人列表

61434595052

3.6.1. mock接口

1570938475069

响应数据结构:

1570938948137

3.6.2. ContactVo

package com.tanhua.domain.vo;
import lombok.Data;
@Data
public class ContactVo {
    private Long id;
    private String userId;
    private String avatar;
    private String nickname;
    private String gender;
    private Integer age;
    private String city;
}

3.6.3. IMController

IMController中添加查询联系人列表方法

    /**
     * 联系人列表
     */
    @RequestMapping(value = "/contacts",method = RequestMethod.GET)
    public ResponseEntity findPageContacts(@RequestParam(defaultValue = "1") int page,
                                           @RequestParam(defaultValue = "10") int pagesize, String keyword){
        PageResult<ContactVo> pageResult = imService.findPageContacts(page,pagesize,keyword);
        return ResponseEntity.ok(pageResult);
    }

3.6.4. IMService

IMService中添加查询联系人方法

    /**
     * 联系人列表
     */
    public PageResult<ContactVo> findPageContacts(int page, int pagesize, String keyword) {
        Long userId = UserHolder.getUserId();
        //1.查询好友表
        PageResult<Friend> friendPageResult = friendApi.findPageContacts(page,pagesize,userId);
        if(friendPageResult == null || CollectionUtils.isEmpty(friendPageResult.getItems())){
            return new PageResult<>(0l,10l,0l,1l,null);
        }
        //2查询 tb_userInfo
        List<ContactVo> listContactVo = new ArrayList<>();
        long id = 1l;
        for (Friend friend : friendPageResult.getItems()) {
            ContactVo contactVo = new ContactVo();
            Long friendId = friend.getFriendId();//根据好友id查询好友信息
            UserInfo userInfo = userInfoApi.findUserInfoByUserId(friendId);
            BeanUtils.copyProperties(userInfo,contactVo);//avatar  nickname   gender age  city
            contactVo.setId(id);//编号
            contactVo.setUserId(friendId.toString());//好友用户id
            listContactVo.add(contactVo);
            id++;
        }
        //3 构造vo返回
        PageResult<ContactVo> voPageResult = new PageResult<>();
        BeanUtils.copyProperties(friendPageResult,voPageResult);
        voPageResult.setItems(listContactVo);
        return voPageResult;
    }

3.7. 服务提供者-联系人列表

3.7.1. FriendApi

在FriendApi中添加分页查询方法

    /**
     * 好友联系人分页查询
     * @param page
     * @param pagesize
     * @param userId
     * @return
     */
    PageResult<Friend> findPageContacts(int page, int pagesize, Long userId);

3.7.2. FriendApiImpl

在FriendApiImpl中添加分页查询方法

   /**
     * 好友联系人分页查询
     * @param page
     * @param pagesize
     * @param userId
     * @return
     */
    @Override
    public PageResult<Friend> findPageContacts(int page, int pagesize, Long userId) {
        Query query = new Query();
        query.addCriteria(Criteria.where("userId").is(userId));//当前用户id查询好友ids
        query.limit(pagesize);
        query.skip((page-1)*pagesize);
        long counts = mongoTemplate.count(query, Friend.class);
        //2,查询当前页面数据
        List<Friend> friendList = mongoTemplate.find(query, Friend.class);
        //3.封装PageResult对象返回
        //pages //总页数
        long pages =  counts/pagesize + (counts%pagesize > 0 ?1:0);
        return new PageResult<>(counts,(long)pagesize,pages,(long)page,friendList);
    }

3.7.3. 测试

61348025496

61348027608

61348029315

【小结】

掌握消息管理功能

4. 点赞评论喜欢列表查询

【目标】

掌握点赞评论喜欢功能

【路径】

1:点赞评论喜欢功能分析

2:点赞评论喜欢功能实现

【讲解】

4.1. 服务消费者-点赞评论喜欢

63592327684

61434753158

4.1.1. MessageVo

根据接口定义vo对象。

package com.tanhua.domain.vo;

import lombok.Data;

@Data
public class MessageVo {
    private String id;
    private String avatar;
    private String nickname;
    private String createDate;
}

4.1.2. IMController

    /**
     * 点赞列表  //评论类型,1-点赞,2-评论,3-喜欢
     */
    @RequestMapping(value = "/likes",method = RequestMethod.GET)
    public ResponseEntity likes(@RequestParam(defaultValue = "1") int page,
                                @RequestParam(defaultValue = "10") int pagesize){
        PageResult<MessageVo> pageResult = imService.findPageComments(page,pagesize,1);
        return ResponseEntity.ok(pageResult);
    }

    /**
     * 评论列表  //评论类型,1-点赞,2-评论,3-喜欢
     */
    @RequestMapping(value = "/comments",method = RequestMethod.GET)
    public ResponseEntity comments(@RequestParam(defaultValue = "1") int page,
                                   @RequestParam(defaultValue = "10") int pagesize){
        PageResult<MessageVo> pageResult = imService.findPageComments(page,pagesize,2);
        return ResponseEntity.ok(pageResult);
    }

    /**
     * 喜欢列表  //评论类型,1-点赞,2-评论,3-喜欢
     */
    @RequestMapping(value = "/loves",method = RequestMethod.GET)
    public ResponseEntity loves(@RequestParam(defaultValue = "1") int page,
                                           @RequestParam(defaultValue = "10") int pagesize){
        PageResult<MessageVo> pageResult = imService.findPageComments(page,pagesize,3);
        return ResponseEntity.ok(pageResult);
    }

4.1.3. IMService

    /**
     * 点赞列表  //评论类型,1-点赞,2-评论,3-喜欢
     */
    public PageResult<MessageVo> findPageComments(int page, int pagesize, int type) {
        Long userId = UserHolder.getUserId();
        //1.根据publishUserId = 当前用户id and  type=1 2 3来进行分页查询
        PageResult<Comment>  pageCommentsMessages = commentApi.findPageCommentsMessages(page,pagesize,type,userId);
        if(pageCommentsMessages == null || CollectionUtils.isEmpty(pageCommentsMessages.getItems())){
            return new PageResult<>(0l,10l,0l,1l,null);
        }
        //2.查询tb_userInfo
        List<MessageVo> messageVoList = new ArrayList<>();
        for (Comment comment : pageCommentsMessages.getItems()) {
            MessageVo messageVo= new MessageVo();
            Long commentUserId = comment.getUserId();//评论人的用户id
            UserInfo userInfo = userInfoApi.findUserInfoByUserId(commentUserId);
            BeanUtils.copyProperties(userInfo,messageVo);//头像 昵称
            messageVo.setId(userInfo.getId().toString());//评论人用户id
            String createDate = new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date(comment.getCreated()));
            messageVo.setCreateDate(createDate);//评论时间
            messageVoList.add(messageVo);
        }
        //3.构造vo返回
        PageResult<MessageVo> voPageResult = new PageResult<>();
        BeanUtils.copyProperties(pageCommentsMessages,voPageResult);
        voPageResult.setItems(messageVoList);
        return voPageResult;
    }

4.2. 服务提供者-点赞评论喜欢

4.2.1. Comment

点赞、评论、喜欢列表应该是别人对我发布的信息做了操作之后显示的数据,所以查询条件是发布人的id作为查询条件。

修改Comment对象,增加 publishUserId 字段。

package com.tanhua.domain.mongo;

import lombok.Data;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.mapping.Document;

import java.io.Serializable;

@Data
@Document(collection = "quanzi_comment")
public class Comment implements Serializable {

    private ObjectId id;

    private ObjectId publishId;    //发布id
    private Integer commentType;   //评论类型,1-点赞,2-评论,3-喜欢
    private Integer pubType;       //评论内容类型: 1-对动态操作 2-对视频操作 3-对评论操作
    private String content;        //评论内容
    private Long userId;           //评论人
    private Integer likeCount = 0; //点赞数
    private Long created; //发表时间
    private Long publishUserId; //被评论人ID

    //动态选择更新的字段
    public String getCol() {
        return this.commentType == 1 ? "likeCount" : commentType==2? "commentCount"
            : "loveCount";
    }
}

4.2.2. CommentApi

/**
* 通过发布者id和评论的类型分页查询
*
* @param userId
* @param commentType
* @param page
* @param pageSize
* @return
*/
PageResult findByUserId(Integer page, Integer pageSize,Integer commentType,Long userId);

4.2.3. CommentApiImpl

    /**
     * 点赞列表  //评论类型,1-点赞,2-评论,3-喜欢
     */
    @Override
    public PageResult<Comment> findPageCommentsMessages(int page, int pagesize, int type, Long userId) {
        //1.根据publishUserId=userId and type = 1 2 3  分页查询评论表
        Query query = new Query();
        query.addCriteria(
                Criteria.where("publishUserId").is(userId)
                        .and("commentType").is(type)
        );
        query.limit(pagesize);
        query.skip((page - 1) * pagesize);
        long counts = mongoTemplate.count(query, Comment.class);
        //2,查询当前页面数据
        List<Comment> commentList = mongoTemplate.find(query, Comment.class);
        //3.封装PageResult对象返回
        //pages //总页数
        long pages = counts / pagesize + (counts % pagesize > 0 ? 1 : 0);
        return new PageResult<>(counts, (long) pagesize, pages, (long) page, commentList);
    }

4.2.4. CommentApiImpl

修改点赞 评论 喜欢

   /**
     * 动态点赞
     * 动态喜欢
     *
     * @param comment
     * @return
     */
    @Override
    public int saveComment(Comment comment) {
        //1保存点赞记录到评论表 、
        comment.setId(ObjectId.get());//主键id
        comment.setCreated(System.currentTimeMillis());//点赞时间
        //设置被评论的用户id
        Publish publish = mongoTemplate.findById(comment.getPublishId(), Publish.class);
        Long publishUserId = publish.getUserId();
        comment.setPublishUserId(publishUserId);
        mongoTemplate.insert(comment);
        //2更新发布表点赞数量+1 、喜欢数量+1 评论数量+1 更新评论表 点赞数量+1
        updateCount(comment, 1);
        //3查询发布表点赞 喜欢 评论数量返回  查询评论表点赞数量返回
        return findCount(comment);
    }

4.2.5. 测试

61348453122

61348455023

61348456651

61348457962

61348459356

【小结】

掌握点赞评论喜欢列表查询功能

总结

  1. 了解环信云使用

  2. 应用集成(探花项目 用上 环信云)--面试 整个流程-重点掌握

    a.注册

    b.登录

    c.即时通讯

    注意:测试注意严格按照课上说的7点

  3. 聊一下-查询用户详情 查询陌生人问题 回复陌生人问题(首页推荐用户-聊一下-输入回答的内容,点击聊一下)-重点掌握

    注意:消息 key value都是string类型 发消息的用户id特别注意下


  4. 添加联系人

  5. 联系人列表

  6. 点赞评论喜欢列表查询

    注意:comment表中新增一个被评论人用户id

posted on 2022-04-23 15:22  ofanimon  阅读(128)  评论(0编辑  收藏  举报
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css