即时通信 什么是即时通信?
功能说明 在探花交友项目中也提供了类似微信的聊天功能,用户可以和好友或陌生人聊天。如果是陌生人,通过《聊一下》功能进行打招呼,如果对方同意后,就成为了好友,可以进行聊天了。陌生人之间如果相互喜欢,那么就会成为好友,也就可以聊天了。在消息界面中也可以查看:点赞、评论、喜欢、公告等消息信息。
技术方案 对于高并发的即时通讯实现,还是很有挑战的,所需要考虑的点非常多,除了要实现功能,还要考虑并发、流量、负载、服务器、容灾等等。虽然有难度也并不是高不可攀。 对于现实即时通讯往往有两种方案:
方案一:
自主实现,从设计到架构,再到实现。
技术方面可以采用:Netty + WebSocket + RocketMQ + MongoDB + Redis + ZooKeeper + MySQL
方案二:
对接第三方服务完成。
这种方式简单,只需要按照第三方的api进行对接就可以了。
如:环信、网易、容联云通讯等。
如何选择呢? 如果是中大型企业做项目可以选择自主研发,如果是中小型企业研发中小型的项目,选择第二种方案即可。方案一需要有大量的人力、物力的支持,开发周期长,成本高,但可控性强。方案二,成本低,开发周期短,能够快速的集成起来进行功能的开发,只是在可控性方面来说就差了一些。
探花交友项目选择方案二进行实现。
环信 官网:https://www.easemob.com/
稳定健壮,消息必达,亿级并发的即时通讯云
开发简介 平台架构:
集成: 环信和用户体系的集成主要发生在2个地方,服务器端集成和客户端集成。
探花集成 :
探花前端使用AndroidSDK进行集成
文档:http://docs-im.easemob.com/im/server/ready/user
环信Console 需要使用环信平台,那么必须要进行注册,登录之后即可创建应用。环信100以内的用户免费使用,100以上就要注册企业版了。
企业版价格:
创建应用:
创建完成:
用户体系集成 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
环信 ID 数据结构 环信作为一个聊天通道,只需要提供环信 ID (也就是 IM 用户名)和密码就够了。
名称
字段名
数据类型
描述
环信 ID
username
String
在 AppKey 的范围内唯一用户名。
用户密码
password
String
用户登录环信使用的密码。
环信 ID 使用规则 当 APP 和环信集成的时候,需要把 APP 系统内的已有用户和新注册的用户和环信集成,为每个已有用户创建一个环信的账号(环信 ID),并且 APP 有新用户注册的时候,需要同步的在环信中注册。
在注册环信账户的时候,需要注意环信 ID 的规则:
使用英文字母和(或)数字的组合
不能使用中文
不能使用 email 地址
不能使用 UUID
用户ID的长度在255字节以内
中间不能有空格或者井号(#)等特殊字符
允许的用户名正则 “[a-zA-Z0-9_-.]”(a~z大小写字母/数字/下划线/横线/英文句号),其他都不允许 * 如果是大写字母会自动转成小写**
不区分大小写。系统忽略大小写,认为 AA、Aa、aa、aA 都是一样的。如果系统已经存在了环信 ID 为 AA 的用户,再试图使用 aa 作为环信 ID 注册新用户,系统返回用户名重复,以此类推。但是请注意:环信 ID 在数据上的表现形式还是用户最初注册的形式,注册时候使用的大写就保存大写,是小写就保存小写。即:使用 AA 注册,环信保存的 ID 就是 AA;使用 Aa 注册,环信保存的 ID 就是 Aa,以此类推。
另:本文档中可能会交错使用“环信 ID”和“环信用户名”两个术语,但是请注意,这里两个的意思是一样的。
因为一个用户的环信 ID 和他的在 APP 中的用户名并不需要一致,只需要有一个明确的对应关系。例如,用户名是 example@easemob.com ,当这个用户登录到 APP 的时候,可以登录成功之后,再登录环信的服务器,所以这时候,只需要能够从 example@easemob.com 推导出这个用户的环信 ID 即可。
获取管理员权限 环信提供的 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 值
配置 将用户体系集成的逻辑写入到sso系统中。
huanxin.properties
1 2 3 4 5 tanhua.huanxin.url =http://a1.easemob.com/tanhua.huanxin.orgName =1105190515097562 tanhua.huanxin.appName =tanhuatanhua.huanxin.clientId =YXA67ZofwHblEems-_Fh-17 T2gtanhua.huanxin.clientSecret =YXA60r45rNy2Ux5wQ7YYoEPwynHmUZk
说明:这配置在控制台可以找到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.tanhua.sso.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation .Configuration ; import org.springframework.context.annotation .PropertySource ; @Configuration @PropertySource("classpath:huanxin.properties" ) @ConfigurationProperties(prefix = "tanhua.huanxin" ) @Data public class HuanXinConfig { private String url; private String orgName; private String appName; private String clientId; private String clientSecret; }
获取token 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 package com.tanhua.sso.service;import com.fasterxml.jackson.databind.JsonNode;import com.fasterxml.jackson.databind.ObjectMapper;import com.tanhua.sso.config.HuanXinConfig;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Service;import org.springframework.web.client.RestTemplate;import java.util.HashMap;import java.util.Map;import java.util.concurrent.TimeUnit;@Service public class HuanXinTokenService { @Autowired private HuanXinConfig huanXinConfig; @Autowired private RestTemplate restTemplate; private static final ObjectMapper MAPPER = new ObjectMapper(); @Autowired private RedisTemplate<String, String> redisTemplate; private static final String tokenRedisKey = "HUANXIN_TOEKN" ; public String getToken () { String cacheData = this .redisTemplate.opsForValue().get(tokenRedisKey); if (StringUtils.isNotEmpty(cacheData)){ return cacheData; } String url = this .huanXinConfig.getUrl() + this .huanXinConfig.getOrgName() + "/" + this .huanXinConfig.getAppName() + "/token" ; Map<String, Object> param = new HashMap<>(); param.put("grant_type" , "client_credentials" ); param.put("client_id" , this .huanXinConfig.getClientId()); param.put("client_secret" , this .huanXinConfig.getClientSecret()); ResponseEntity<String> responseEntity = this .restTemplate.postForEntity(url, param, String.class ) ; if (responseEntity.getStatusCodeValue() != 200 ) { return null ; } String body = responseEntity.getBody(); try { JsonNode jsonNode = MAPPER.readTree(body); String accessToken = jsonNode.get("access_token" ).asText(); Long expiresIn = jsonNode.get("expires_in" ).asLong() - 86400 ; this .redisTemplate.opsForValue().set(tokenRedisKey, accessToken, expiresIn, TimeUnit.SECONDS); return accessToken; } catch (Exception e) { e.printStackTrace(); } return null ; } }
注册环信用户 注册环信用户分为2种,开放注册、授权注册,区别在于开发注册不需要token,授权注册需要token。
我们使用的授权注册:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.tanhua.sso.vo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data @NoArgsConstructor @AllArgsConstructor public class HuanXinUser { private String username; private String password; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package com.tanhua.sso.service;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;import com.tanhua.sso.config.HuanXinConfig;import com.tanhua.sso.vo.HuanXinUser;import org.apache.commons.codec.digest.DigestUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.HttpEntity;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpRequest;import org.springframework.http.ResponseEntity;import org.springframework.messaging.handler.annotation.Header;import org.springframework.stereotype.Service;import org.springframework.web.client.RestTemplate;import java.util.ArrayList;import java.util.List;@Service public class HuanXinService { @Autowired private HuanXinConfig huanXinConfig; @Autowired private RestTemplate restTemplate; @Autowired private HuanXinTokenService huanXinTokenService; private static final ObjectMapper MAPPER = new ObjectMapper(); public boolean register (Long userId) { String url = this .huanXinConfig.getUrl() + this .huanXinConfig.getOrgName() + "/" + this .huanXinConfig.getAppName() + "/users" ; String token = this .huanXinTokenService.getToken(); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.add("Content-Type" , "application/json" ); httpHeaders.add("Authorization" , "Bearer " + token); List<HuanXinUser> huanXinUsers = new ArrayList<>(); huanXinUsers.add(new HuanXinUser(userId.toString(), DigestUtils.md5Hex(userId + "_itcast_tanhua" ))); try { HttpEntity<String> httpEntity = new HttpEntity(MAPPER.writeValueAsString(huanXinUsers), httpHeaders); ResponseEntity<String> responseEntity = this .restTemplate.postForEntity(url, httpEntity, String.class ) ; return responseEntity.getStatusCodeValue() == 200 ; } catch (Exception e) { e.printStackTrace(); } return false ; }
加入到登录逻辑中:
测试
可以看到已经注册到了环信。
查询环信用户信息 在app中,用户登录后需要根据用户名密码登录环信,由于用户名密码保存在后台,所以需要提供接口进行返回。
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.tanhua.server.controller;import com.tanhua.server.pojo.User;import com.tanhua.server.utils.UserThreadLocal;import com.tanhua.server.vo.HuanXinUser;import org.apache.commons.codec.digest.DigestUtils;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping ("huanxin" )public class HuanXinController { @GetMapping ("user" ) public ResponseEntity<HuanXinUser> queryUser () { User user = UserThreadLocal.get(); HuanXinUser huanXinUser = new HuanXinUser(); huanXinUser.setUsername(user.getId().toString()); huanXinUser.setPassword(DigestUtils.md5Hex(user.getId() + "_itcast_tanhua" )); return ResponseEntity.ok(huanXinUser); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.tanhua.server.vo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data @NoArgsConstructor @AllArgsConstructor public class HuanXinUser { private String username; private String password; }
发送消息给客户端 目前已经完成了用户体系的对接,下面我们进行测试发送消息,场景是这样的:
点击“聊一下”,就会给对方发送一条陌生人信息,这个消息由系统发送完成。
我们暂时通过环信的控制台进行发送:
消息内容:
1 {"userId" : "2" ,"nickname" :"黑马小妹" ,"strangerQuestion" : "你喜欢去看蔚蓝的大海还是去爬巍峨的高山?" ,"reply" : "我喜欢秋天的落叶,夏天的泉水,冬天的雪地,只要有你一切皆可~" }
可以看到已经接收到了消息。
添加联系人 点击“聊一下”,就会成为联系人(好友)。
实现:
将好友写入到MongoDB中
将好友关系注册到环信
定义dubbo服务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.tanhua.dubbo.server.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import org.bson.types.ObjectId;import org.springframework.data.mongodb.core.mapping.Document;import java.util.Date;@Data @NoArgsConstructor @AllArgsConstructor @Document (collection = "tanhua_users" )public class Users implements java .io .Serializable { private static final long serialVersionUID = 6003135946820874230L ; private ObjectId id; private Long userId; private Long friendId; private Long date; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.tanhua.dubbo.server.api;import com.tanhua.dubbo.server.pojo.Users;import com.tanhua.dubbo.server.vo.PageInfo;import java.util.List;public interface UsersApi { String saveUsers (Users users) ; }
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package com.tanhua.dubbo.server.api;import com.alibaba.dubbo.config.annotation.Service;import com.tanhua.dubbo.server.pojo.Users;import com.tanhua.dubbo.server.vo.PageInfo;import org.bson.types.ObjectId;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.domain.PageRequest;import org.springframework.data.domain.Sort;import org.springframework.data.mongodb.core.MongoTemplate;import org.springframework.data.mongodb.core.query.Criteria;import org.springframework.data.mongodb.core.query.Query;import java.util.List;@Service (version = "1.0.0" )public class UsersApiImpl implements UsersApi { @Autowired private MongoTemplate mongoTemplate; @Override public String saveUsers (Users users) { if (users.getUserId() == null || users.getFriendId() == null ) { return null ; } Query query = Query.query(Criteria.where("userId" ) .is(users.getUserId()) .and("friendId" ) .is(users.getFriendId())); Users oldUsers = this .mongoTemplate.findOne(query, Users.class ) ; if (null != oldUsers) { return null ; } users.setId(ObjectId.get()); users.setDate(System.currentTimeMillis()); this .mongoTemplate.save(users); return users.getId().toHexString(); } }
注册好友到环信 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.tanhua.sso.controller;import com.tanhua.sso.service.HuanXinService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping ("user/huanxin" )public class HuanXinController { @Autowired private HuanXinService huanXinService; @PostMapping ("contacts/{owner_username}/{friend_username}" ) public ResponseEntity<Void> contactUsers (@PathVariable("owner_username" ) Long userId, @PathVariable ("friend_username" ) Long friendId) { try { boolean result = this .huanXinService.contactUsers(userId, friendId); if (result) { return ResponseEntity.ok(null ); } } catch (Exception e) { e.printStackTrace(); } return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 package com.tanhua.sso.service;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;import com.tanhua.sso.config.HuanXinConfig;import com.tanhua.sso.vo.HuanXinUser;import org.apache.commons.codec.digest.DigestUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.HttpEntity;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpRequest;import org.springframework.http.ResponseEntity;import org.springframework.messaging.handler.annotation.Header;import org.springframework.stereotype.Service;import org.springframework.web.client.RestTemplate;import java.util.ArrayList;import java.util.List;@Service public class HuanXinService { @Autowired private HuanXinConfig huanXinConfig; @Autowired private RestTemplate restTemplate; @Autowired private HuanXinTokenService huanXinTokenService; private static final ObjectMapper MAPPER = new ObjectMapper(); public boolean contactUsers (Long userId, Long friendId) { String targetUrl = this .huanXinConfig.getUrl() + this .huanXinConfig.getOrgName() + "/" + this .huanXinConfig.getAppName() + "/users/" + userId + "/contacts/users/" + friendId; try { String token = this .huanXinTokenService.getToken(); HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type" , "application/json " ); headers.add("Authorization" , "Bearer " + token); HttpEntity<String> httpEntity = new HttpEntity<>(headers); ResponseEntity<String> responseEntity = this .restTemplate.postForEntity(targetUrl, httpEntity, String.class ) ; return responseEntity.getStatusCodeValue() == 200 ; } catch (Exception e) { e.printStackTrace(); } return false ; } }
编写服务 在itcast-tanhua-server中完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package com.tanhua.server.controller;import com.tanhua.server.service.IMService;import com.tanhua.server.utils.NoAuthorization;import com.tanhua.server.vo.PageResult;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;import java.util.Map;@RestController @RequestMapping ("messages" )public class IMController { private static final Logger LOGGER = LoggerFactory.getLogger(IMController.class ) ; @Autowired private IMService imService; @PostMapping ("contacts" ) public ResponseEntity<Void> contactUser (@RequestBody Map<String, Object> param) { try { Long userId = Long.valueOf(param.get("userId" ).toString()); Boolean result = this .imService.contactUser(userId); if (result) { return ResponseEntity.ok(null ); } } catch (Exception e) { LOGGER.error("添加联系人失败~ param = " + param, e); } return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package com.tanhua.server.service;@Service public class IMService { @Reference (version = "1.0.0" ) private UsersApi usersApi; @Autowired private RestTemplate restTemplate; @Value ("${tanhua.sso.url}" ) private String ssoUrl; public Boolean contactUser (Long userId) { Users users = new Users(); users.setUserId(UserThreadLocal.get().getId()); users.setFriendId(userId); this .usersApi.saveUsers(users); String url = ssoUrl + "user/huanxin/contacts/" + users.getUserId() + "/" + users.getFriendId(); ResponseEntity<Void> responseEntity = this .restTemplate.postForEntity(url, null , Void.class ) ; if (responseEntity.getStatusCodeValue() == 200 ) { return true ; } return false ; } }
测试 可以看到好友已经添加成功。
联系人列表 mock接口
响应数据结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.tanhua.server.vo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data @NoArgsConstructor @AllArgsConstructor public class Contacts { private Long id; private String userId; private String avatar; private String nickname; private String gender; private Integer age; private String city; }
dubbo接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.tanhua.dubbo.server.api;import com.tanhua.dubbo.server.pojo.Users;import com.tanhua.dubbo.server.vo.PageInfo;import java.util.List;public interface UsersApi { String saveUsers (Users users) ; List<Users> queryAllUsersList (Long userId) ; PageInfo<Users> queryUsersList (Long userId, Integer page, Integer pageSize) ; }
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.tanhua.dubbo.server.api;import com.alibaba.dubbo.config.annotation.Service;import com.tanhua.dubbo.server.pojo.Users;import com.tanhua.dubbo.server.vo.PageInfo;import org.bson.types.ObjectId;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.domain.PageRequest;import org.springframework.data.domain.Sort;import org.springframework.data.mongodb.core.MongoTemplate;import org.springframework.data.mongodb.core.query.Criteria;import org.springframework.data.mongodb.core.query.Query;import java.util.List;@Service (version = "1.0.0" )public class UsersApiImpl implements UsersApi { @Autowired private MongoTemplate mongoTemplate; @Override public List<Users> queryAllUsersList (Long userId) { Query query = Query.query(Criteria.where("userId" ).is(userId)); return this .mongoTemplate.find(query, Users.class ) ; } @Override public PageInfo<Users> queryUsersList (Long userId, Integer page, Integer pageSize) { PageRequest pageRequest = PageRequest.of(page - 1 , pageSize, Sort.by(Sort.Order.desc("created" ))); Query query = Query.query(Criteria.where("userId" ).is(userId)).with(pageRequest); List<Users> usersList = this .mongoTemplate.find(query, Users.class ) ; PageInfo<Users> pageInfo = new PageInfo<>(); pageInfo.setPageNum(page); pageInfo.setPageSize(pageSize); pageInfo.setRecords(usersList); pageInfo.setTotal(0 ); return pageInfo; } }
编写接口服务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package com.tanhua.server.controller;import com.tanhua.server.service.IMService;import com.tanhua.server.utils.NoAuthorization;import com.tanhua.server.vo.PageResult;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;import java.util.Map;@RestController @RequestMapping ("messages" )public class IMController { private static final Logger LOGGER = LoggerFactory.getLogger(IMController.class ) ; @Autowired private IMService imService; @PostMapping ("contacts" ) public ResponseEntity<Void> contactUser (@RequestBody Map<String, Object> param) { try { Long userId = Long.valueOf(param.get("userId" ).toString()); Boolean result = this .imService.contactUser(userId); if (result) { return ResponseEntity.ok(null ); } } catch (Exception e) { LOGGER.error("添加联系人失败~ param = " + param, e); } return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } @GetMapping ("contacts" ) public ResponseEntity<PageResult> queryContactsList (@RequestParam(value = "page" , defaultValue = "1" ) Integer page, @RequestParam (value = "pagesize" , defaultValue = "10" ) Integer pageSize, @RequestParam (value = "keyword" , required = false ) String keyword) { PageResult pageResult = this .imService.queryContactsList(page, pageSize, keyword); return ResponseEntity.ok(pageResult); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 package com.tanhua.server.service;@Service public class IMService { @Reference (version = "1.0.0" ) private UsersApi usersApi; @Autowired private RestTemplate restTemplate; @Value ("${tanhua.sso.url}" ) private String ssoUrl; @Autowired private UserInfoService userInfoService; public PageResult queryContactsList (Integer page, Integer pageSize, String keyword) { User user = UserThreadLocal.get(); List<Users> usersList = null ; if (StringUtils.isNotEmpty(keyword)) { usersList = this .usersApi.queryAllUsersList(user.getId()); } else { PageInfo<Users> pageInfo = this .usersApi.queryUsersList(user.getId(), page, pageSize); usersList = pageInfo.getRecords(); } List<Long> fUserIds = new ArrayList<>(); for (Users users : usersList) { fUserIds.add(users.getFriendId()); } QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>(); queryWrapper.in("user_id" , fUserIds); if (StringUtils.isNotEmpty(keyword)) { queryWrapper.like("nick_name" , keyword); } List<UserInfo> userInfoList = this .userInfoService.queryUserInfoList(queryWrapper); List<Contacts> contactsList = new ArrayList<>(); if (StringUtils.isEmpty(keyword)){ for (Users users : usersList) { for (UserInfo userInfo : userInfoList) { if (users.getFriendId().longValue() == userInfo.getId().longValue()){ Contacts contacts = new Contacts(); contacts.setCity(StringUtils.substringBefore(userInfo.getCity(),"-" )); contacts.setUserId(userInfo.getUserId().toString()); contacts.setNickname(userInfo.getNickName()); contacts.setGender(userInfo.getSex().name().toLowerCase()); contacts.setAvatar(userInfo.getLogo()); contacts.setAge(userInfo.getAge()); contactsList.add(contacts); break ; } } } }else { for (UserInfo userInfo : userInfoList) { Contacts contacts = new Contacts(); contacts.setCity(StringUtils.substringBefore(userInfo.getCity(),"-" )); contacts.setUserId(userInfo.getUserId().toString()); contacts.setNickname(userInfo.getNickName()); contacts.setGender(userInfo.getSex().name().toLowerCase()); contacts.setAvatar(userInfo.getLogo()); contacts.setAge(userInfo.getAge()); contactsList.add(contacts); } } PageResult pageResult = new PageResult(); pageResult.setPagesize(pageSize); pageResult.setPage(page); pageResult.setPages(0 ); pageResult.setCounts(0 ); pageResult.setItems(contactsList); return pageResult; } }
测试
点赞列表 mock接口
MessageLike 根据接口定义vo对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.tanhua.server.vo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data @NoArgsConstructor @AllArgsConstructor public class MessageLike { private String id; private String avatar; private String nickname; private String createDate; }
dubbo接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.tanhua.dubbo.server.api;import com.tanhua.dubbo.server.pojo.Comment;import com.tanhua.dubbo.server.pojo.Publish;import com.tanhua.dubbo.server.vo.PageInfo;import java.util.List;public interface QuanZiApi { PageInfo<Comment> queryCommentListByUser (Long userId, Integer type, Integer page, Integer pageSize) ; }
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override public PageInfo<Comment> queryCommentListByUser (Long userId, Integer type, Integer page, Integer pageSize) { PageRequest pageRequest = PageRequest.of(page - 1 , pageSize, Sort.by(Sort.Order.desc("created" ))); Query query = new Query(Criteria .where("publishUserId" ).is(userId) .and("commentType" ).is(type)).with(pageRequest); List<Comment> commentList = this .mongoTemplate.find(query, Comment.class ) ; PageInfo<Comment> pageInfo = new PageInfo<>(); pageInfo.setPageNum(page); pageInfo.setPageSize(pageSize); pageInfo.setRecords(commentList); pageInfo.setTotal(0 ); return pageInfo; }
编写接口服务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.tanhua.server.controller;import com.tanhua.server.service.IMService;import com.tanhua.server.utils.NoAuthorization;import com.tanhua.server.vo.PageResult;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;import java.util.Map;@RestController @RequestMapping ("messages" )public class IMController { private static final Logger LOGGER = LoggerFactory.getLogger(IMController.class ) ; @Autowired private IMService imService; @GetMapping ("likes" ) public ResponseEntity<PageResult> queryMessageLikeList (@RequestParam(value = "page" , defaultValue = "1" ) Integer page, @RequestParam (value = "pagesize" , defaultValue = "10" ) Integer pageSize) { PageResult pageResult = this .imService.queryMessageLikeList(page, pageSize); return ResponseEntity.ok(pageResult); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 package com.tanhua.server.service;import com.alibaba.dubbo.config.annotation.Reference;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.tanhua.dubbo.server.api.QuanZiApi;import com.tanhua.dubbo.server.api.UsersApi;import com.tanhua.dubbo.server.pojo.Comment;import com.tanhua.dubbo.server.pojo.Users;import com.tanhua.dubbo.server.vo.PageInfo;import com.tanhua.server.pojo.User;import com.tanhua.server.pojo.UserInfo;import com.tanhua.server.utils.UserThreadLocal;import com.tanhua.server.vo.Contacts;import com.tanhua.server.vo.MessageLike;import com.tanhua.server.vo.PageResult;import org.apache.commons.lang3.StringUtils;import org.joda.time.DateTime;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Service;import org.springframework.web.client.RestTemplate;import java.util.ArrayList;import java.util.List;@Service public class IMService { @Reference (version = "1.0.0" ) private UsersApi usersApi; @Autowired private RestTemplate restTemplate; @Value ("${tanhua.sso.url}" ) private String url; @Autowired private UserInfoService userInfoService; @Reference (version = "1.0.0" ) private QuanZiApi quanZiApi; public PageResult queryMessageLikeList (Integer page, Integer pageSize) { User user = UserThreadLocal.get(); PageInfo<Comment> pageInfo = this .quanZiApi.queryCommentListByUser(user.getId(), 1 , page, pageSize); PageResult pageResult = new PageResult(); pageResult.setPage(page); pageResult.setPages(0 ); pageResult.setCounts(0 ); pageResult.setPagesize(pageSize); List<Comment> records = pageInfo.getRecords(); List<Long> userIds = new ArrayList<>(); for (Comment comment : records) { userIds.add(comment.getUserId()); } QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>(); queryWrapper.in("user_id" , userIds); List<UserInfo> userInfoList = this .userInfoService.queryUserInfoList(queryWrapper); List<MessageLike> messageLikeList = new ArrayList<>(); for (Comment record : records) { for (UserInfo userInfo : userInfoList) { if (userInfo.getUserId().longValue() == record.getUserId().longValue()){ MessageLike messageLike = new MessageLike(); messageLike.setId(record.getId().toHexString()); messageLike.setAvatar(userInfo.getLogo()); messageLike.setNickname(userInfo.getNickName()); messageLike.setCreateDate(new DateTime(record.getCreated()).toString("yyyy-MM-dd HH:mm" )); messageLikeList.add(messageLike); break ; } } } pageResult.setItems(messageLikeList); return pageResult; } }
测试
评论、喜欢列表 IMController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @GetMapping ("comments" )public ResponseEntity<PageResult> queryMessageCommentList (@RequestParam(value = "page" , defaultValue = "1" ) Integer page, @RequestParam (value = "pagesize" , defaultValue = "10" ) Integer pageSize) { PageResult pageResult = this .imService.queryMessageCommentList(page, pageSize); return ResponseEntity.ok(pageResult); }@GetMapping ("loves" )public ResponseEntity<PageResult> queryMessageLoveList (@RequestParam(value = "page" , defaultValue = "1" ) Integer page, @RequestParam (value = "pagesize" , defaultValue = "10" ) Integer pageSize) { PageResult pageResult = this .imService.queryMessageLoveList(page, pageSize); return ResponseEntity.ok(pageResult); }
IMService 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public PageResult queryMessageLikeList (Integer page, Integer pageSize) { return this .messageCommentList(1 , page, pageSize); }public PageResult queryMessageCommentList (Integer page, Integer pageSize) { return this .messageCommentList(2 , page, pageSize); }public PageResult queryMessageLoveList (Integer page, Integer pageSize) { return this .messageCommentList(3 , page, pageSize); }private PageResult messageCommentList (Integer type, Integer page, Integer pageSize) { User user = UserThreadLocal.get(); PageInfo<Comment> pageInfo = this .quanZiApi.queryCommentListByUser(user.getId(), type, page, pageSize); PageResult pageResult = new PageResult(); pageResult.setPage(page); pageResult.setPages(0 ); pageResult.setCounts(0 ); pageResult.setPagesize(pageSize); List<Comment> records = pageInfo.getRecords(); List<Long> userIds = new ArrayList<>(); for (Comment comment : records) { userIds.add(comment.getUserId()); } QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>(); queryWrapper.in("user_id" , userIds); List<UserInfo> userInfoList = this .userInfoService.queryUserInfoList(queryWrapper); List<MessageLike> messageLikeList = new ArrayList<>(); for (Comment record : records) { for (UserInfo userInfo : userInfoList) { if (userInfo.getUserId().longValue() == record.getUserId().longValue()) { MessageLike messageLike = new MessageLike(); messageLike.setId(record.getId().toHexString()); messageLike.setAvatar(userInfo.getLogo()); messageLike.setNickname(userInfo.getNickName()); messageLike.setCreateDate(new DateTime(record.getCreated()).toString("yyyy-MM-dd HH:mm" )); messageLikeList.add(messageLike); break ; } } } pageResult.setItems(messageLikeList); return pageResult; }
解决bug 点赞、评论、喜欢列表应该是别人对我发布的信息做了操作之后显示的数据,所以查询条件是发布人的id作为查询条件。
第一步:修改Comment对象,增加 publishUserId 字段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.tanhua.dubbo.server.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import org.bson.types.ObjectId;import org.springframework.data.mongodb.core.mapping.Document;import java.util.Date;@Data @NoArgsConstructor @AllArgsConstructor @Document (collection = "quanzi_comment" )public class Comment implements java .io .Serializable { private static final long serialVersionUID = -291788258125767614L ; private ObjectId id; private ObjectId publishId; private Integer commentType; private String content; private Long userId; private Long publishUserId; private Boolean isParent = false ; private ObjectId parentId; private Long created; }
第二步:修改保存Comment逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Override public boolean saveComment (Long userId, String publishId, Integer type, String content) { try { Comment comment = new Comment(); comment.setContent(content); comment.setIsParent(true ); comment.setCommentType(type); comment.setPublishId(new ObjectId(publishId)); comment.setUserId(userId); comment.setId(ObjectId.get()); comment.setCreated(System.currentTimeMillis()); Publish publish = this .mongoTemplate.findById(comment.getPublishId(), Publish.class ) ; if (null != publish) { comment.setPublishUserId(publish.getUserId()); } else { Video video = this .mongoTemplate.findById(comment.getPublishId(), Video.class ) ; if (null != video) { comment.setPublishUserId(video.getUserId()); } } this .mongoTemplate.save(comment); return true ; } catch (Exception e) { e.printStackTrace(); } return false ; }
第三步,修改查询条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override public PageInfo<Comment> queryCommentListByUser (Long userId, Integer type, Integer page, Integer pageSize) { PageRequest pageRequest = PageRequest.of(page - 1 , pageSize, Sort.by(Sort.Order.desc("created" ))); Query query = new Query(Criteria .where("publishUserId" ).is(userId) .and("commentType" ).is(type)).with(pageRequest); List<Comment> commentList = this .mongoTemplate.find(query, Comment.class ) ; PageInfo<Comment> pageInfo = new PageInfo<>(); pageInfo.setPageNum(page); pageInfo.setPageSize(pageSize); pageInfo.setRecords(commentList); pageInfo.setTotal(0 ); return pageInfo; }
公告列表 公告是后台系统对所有用户发布的公告消息。
表结构 1 2 3 4 5 6 7 8 9 CREATE TABLE `tb_announcement` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `title` varchar(200) DEFAULT NULL COMMENT '标题', `description` text COMMENT '描述', `created` datetime DEFAULT NULL, `updated` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `created` (`created`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='公告表';
1 2 3 INSERT INTO `tb_announcement` (`id`, `title`, `description`, `created`, `updated`) VALUES ('1', '探花新版本上线发布啦~,盛夏high趴开始了,赶紧来报名吧!', '探花App2019年7月23日起在苹果商店…,浓情夏日,清爽一聚,探花将吧大家聚…', '2019-10-14 11:06:34', '2019-10-14 11:06:37'); INSERT INTO `tb_announcement` (`id`, `title`, `description`, `created`, `updated`) VALUES ('2', '探花交友的圈子功能正式上线啦~~', '探花交友的圈子功能正式上线啦,欢迎使用~', '2019-10-14 11:09:31', '2019-10-14 11:09:33'); INSERT INTO `tb_announcement` (`id`, `title`, `description`, `created`, `updated`) VALUES ('3', '国庆放假期间,探花交友正常使用~', '国庆放假期间,探花交友正常使用~', '2019-10-14 11:10:01', '2019-10-14 11:10:04');
pojo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.tanhua.server.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data @NoArgsConstructor @AllArgsConstructor public class Announcement extends BasePojo { private Long id; private String title; private String description; }
AnnouncementMapper 1 2 3 4 5 6 7 package com.tanhua.server.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.tanhua.server.pojo.Announcement;public interface AnnouncementMapper extends BaseMapper <Announcement > { }
AnnouncementService 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.tanhua.server.service;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.baomidou.mybatisplus.core.metadata.IPage;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;import com.tanhua.server.mapper.AnnouncementMapper;import com.tanhua.server.pojo.Announcement;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Service public class AnnouncementService { @Autowired private AnnouncementMapper announcementMapper; public IPage<Announcement> queryList (Integer page, Integer pageSize) { QueryWrapper queryWrapper = new QueryWrapper(); queryWrapper.orderByDesc("created" ); return this .announcementMapper.selectPage(new Page<Announcement>(page, pageSize), queryWrapper); } }
定义vo对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.tanhua.server.vo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data @NoArgsConstructor @AllArgsConstructor public class MessageAnnouncement { private String id; private String title; private String description; private String createDate; }
IMController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @GetMapping ("announcements" )@NoAuthorization public ResponseEntity<PageResult> queryMessageAnnouncementList (@RequestParam(value = "page" , defaultValue = "1" ) Integer page, @RequestParam (value = "pagesize" , defaultValue = "10" ) Integer pageSize) { PageResult pageResult = this .imService.queryMessageAnnouncementList(page, pageSize); return ResponseEntity.ok(pageResult); }
IMService 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Autowired private AnnouncementService announcementService;public PageResult queryMessageAnnouncementList (Integer page, Integer pageSize) { IPage<Announcement> announcementPage = this .announcementService.queryList(page, pageSize); List<MessageAnnouncement> messageAnnouncementList = new ArrayList<>(); for (Announcement record : announcementPage.getRecords()) { MessageAnnouncement messageAnnouncement = new MessageAnnouncement(); messageAnnouncement.setId(record.getId().toString()); messageAnnouncement.setTitle(record.getTitle()); messageAnnouncement.setDescription(record.getDescription()); messageAnnouncement.setCreateDate(new DateTime(record.getCreated()).toString("yyyy-MM-dd HH:mm" )); messageAnnouncementList.add(messageAnnouncement); } PageResult pageResult = new PageResult(); pageResult.setPage(page); pageResult.setPages(0 ); pageResult.setCounts(0 ); pageResult.setPagesize(pageSize); pageResult.setItems(messageAnnouncementList); return pageResult; }
测试