第8章 即时通讯功能实现
今日内容介绍
学习目标
- 了解什么是即时通信
- 了解探花交友的消息功能
- 了解即时通信的技术方案
- 了解环信的即时通讯
- 实现环信的用户体系集成
- 实现添加联系人、联系人列表功能
- 实现点赞、评论、喜欢列表查询
1. 即时通信
【目标】
掌握环信通讯组件使用
【路径】
1:了解即时通讯
2:环信介绍
3:抽取环信组件
【讲解】
1.1. 即时通讯介绍
1.1.1. 什么是即时通信?
1.1.2. 功能说明
在探花交友项目中也提供了类似微信的聊天功能,用户可以和好友或陌生人聊天。
如果是陌生人,通过《聊一下》功能进行打招呼,如果对方同意后,就成为了好友,可以进行聊天了。
陌生人之间如果相互喜欢,那么就会成为好友,也就可以聊天了。
在消息界面中也可以查看:点赞、评论、喜欢、公告等消息信息。
1.1.3. 技术方案
对于高并发的即时通讯实现,还是很有挑战的,所需要考虑的点非常多,除了要实现功能,还要考虑并发、流量、负载、服务器、容灾等等。虽然有难度也并不是高不可攀。
对于现实即时通讯往往有两种方案:
- 方案一:
- 自主实现,从设计到架构,再到实现。
- 技术方面可以采用:Netty + WebSocket + RocketMQ + MongoDB + Redis + ZooKeeper + MySQL
- 方案二:
- 对接第三方服务完成。
- 这种方式简单,只需要按照第三方的api进行对接就可以了。
- 如:环信、网易、容联云通讯等。
如何选择呢?
如果是中大型企业做项目可以选择自主研发,如果是中小型企业研发中小型的项目,选择第二种方案即可。方案一需要有大量的人力、物力的支持,开发周期长,成本高,但可控性强。方案二,成本低,开发周期短,能够快速的集成起来进行功能的开发,只是在可控性方面来说就差了一些。
探花交友项目选择方案二进行实现。
1.2. 环信介绍
官网:https://www.easemob.com/ 稳定健壮,消息必达,亿级并发的即时通讯云
1.2.1. 开发简介
平台架构:
集成:
环信和用户体系的集成主要发生在2个地方,服务器端集成和客户端集成。
探花集成:
- 探花前端使用AndroidSDK进行集成
- 后端集成用户体系
1.2.2. 环信Console
需要使用环信平台,那么必须要进行注册,登录之后即可创建应用。环信100以内的用户免费使用,100以上就要注册企业版了。
企业版价格:
创建应用:
创建完成:
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
请求方式 | 请求路径 |
---|---|
![]() |
/{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 值 |
1.3. 抽取环信组件(了解)
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:发送消息给客户端
【讲解】
将用户体系集成的逻辑写入到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中,用户登录后需要根据用户名密码登录环信,由于用户名密码保存在后台,所以需要提供接口进行返回。
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. 发送消息给客户端
目前已经完成了用户体系的对接,下面我们进行测试发送消息,场景是这样的:
点击“聊一下”,就会给对方发送一条陌生人信息,这个消息由系统发送完成。
我们暂时通过环信的控制台进行发送:
消息内容:
{"userId": "1","nickname":"黑马小妹","strangerQuestion": "你喜欢去看蔚蓝的大海还是去爬巍峨的高山?","reply": "我喜欢秋天的落叶,夏天的泉水,冬天的雪地,只要有你一切皆可~"}
可以看到已经接收到了消息。
2.4. 测试注意
1.保证用户一定是已经注册的,否则不能使用环信云
2.如果用户之间需要发送消息(即时通讯),一定先登录(由app来实现)
3.清除数据,重新进行配置
4.配置环信
5.消息模块-显示加载失败,请联系管理员
6.如果再次打开失败,重新登录一次
7.app如果使用了a用户登录了,只能使用a用户操作。
【小结】
掌握环信通讯集成使用
3. 消息管理(重点)
【目标】
掌握消息管理实现
【路径】
1:查看用户详情
2:查看陌生人消息
3:回复陌生人消息
4:添加联系人
5:联系人列表
【讲解】
客户端完成消息管理共有三步
- 查看用户详情,点击聊一下查看陌生人问题
- 填写内容,发送陌生人消息
- 如果对方感兴趣,可以回复陌生人消息,双方成为好友
3.1. 服务消费者-查看用户详情
在首页可以查看感兴趣人的详细资料。点击“聊一下”,可以查看对方的问题
3.1.1. mock接口
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")));
3.3. 服务消费者-查询陌生人问题
点击“聊一下”,可以查看对方的问题
3.3.1. mock接口
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. 测试
3.3.服务消费者-回复陌生人消息
需求:
- 通过服务器端,给目标用户发送一条陌生人消息
3.3.1. mock接口
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. 测试
发送消息测试
接收消息测试
postman:
3.4. 服务消费者-添加联系人
用户获取陌生人消息后,点击“聊一下”,就会成为联系人(好友)。
实现:
- 将好友写入到MongoDB中
- 将好友关系注册到环信
3.4.1. mock接口
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测试:
可以看到好友已经添加成功。
app测试:注册新用户测试
3.6. 服务消费者-联系人列表
3.6.1. mock接口
响应数据结构:
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. 测试
【小结】
掌握消息管理功能
4. 点赞评论喜欢列表查询
【目标】
掌握点赞评论喜欢功能
【路径】
1:点赞评论喜欢功能分析
2:点赞评论喜欢功能实现
【讲解】
4.1. 服务消费者-点赞评论喜欢
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. 测试
【小结】
掌握点赞评论喜欢列表查询功能
总结
-
了解环信云使用
-
应用集成(探花项目 用上 环信云)--面试 整个流程-重点掌握
a.注册
b.登录
c.即时通讯
注意:测试注意严格按照课上说的7点
-
聊一下-查询用户详情 查询陌生人问题 回复陌生人问题(首页推荐用户-聊一下-输入回答的内容,点击聊一下)-重点掌握
注意:消息 key value都是string类型 发消息的用户id特别注意下
-
添加联系人
-
联系人列表
-
点赞评论喜欢列表查询
注意:comment表中新增一个被评论人用户id