类似于斗鱼直播间的聊天

直播.png

接入第三方IM,大部分功能实现依赖于前端。后端侧重于创建群组的时机,以及考虑群组解散的时机(如果有合理的退群机制和定期清理群人数的机制,当我没说,不用考虑解散群组机制。因为对腾讯IM来说一个人只能同时加入200个群组限制)。如果后端需要对聊天的内容和群组变化记录入库,就需要用到腾讯云IM的回调机制。对于直播间人数获取,可以获取IM群组人数,不过还是要设计好严格的退群加群机制,才能保证人数正确。

对于正确的退群机制,后端要注意IM文档中回调机制:在线状态相关回调

文档中该回调介绍为:

客户端 kill 后台进程,云服务器检测到客户端网络断开后触发下线回调。

客户端心跳超时,包括客户端 Crash、关闭网络 400 秒后,云服务器检测到客户端的心跳超时触发下线回调

通俗讲:也就是客户端用户无法正确执行quitGroup操作时,通过IM回调服务端接口来实现用户退群操作。

本文主要介绍建群和解散群组的实现方式

web直播间群组的创建是和主播进行绑定,一个主播对应一个群组,同时主播也是该群组的管理员,拥有授权,禁言,踢人等操作。当一个普通用户升级为主播的那一刻,后端就创建了一个IM群组,同时授权管理员是该主播。

用户加入IM群组的前提是必须先登录腾讯的IM系统。就好比你要加QQ群聊天,就必须先登录QQ一样,因此对于腾讯IM群组的private/public/chatroom的群组模式是没有不登录这个概念的。这也就解释了,为什么直播发言必须要求用户登录账号(同时也登录了IM系统,同步web的账号信息到IM系统中的账号)。而不登录web的用户要想看到群组的内容,也必须登录IM系统,只是此刻以游客的身份进行登录,也就是随机的账号登录IM系统,只是不能进行发言,这需要前端进行限制操作。

登录IM系统需要账号密码:

对于登录web的用户来说,IM的账号就是web体系的用户ID或者其他唯一标识,而密码则需要调用后端接口getUserSig获取(参考腾讯userSig机制)

在IM文档中可以获取到

Base64URL和GenUserSig,该加密用来生成IM密码

public class Base64URL {

public static byte[] base64EncodeUrl(byte[] input){

byte[] base64 = new BASE64Encoder().encode(input).getBytes();

for (int i = 0; i < base64.length; ++i)

switch (base64[i]) {

case '+':

base64[i] = '*';

break;

case '/':

base64[i] = '-';

break;

case '=':

base64[i] = '_';

break;

default:

break;

}

return base64;

}

public static byte[] base64DecodeUrl(byte[] input) throws IOException {

byte[] base64 = input.clone();

for (int i = 0; i < base64.length; ++i)

switch (base64[i]) {

case '*':

base64[i] = '+';

break;

case '-':

base64[i] = '/';

break;

case '_':

base64[i] = '=';

break;

default:

break;

}

return new BASE64Decoder().decodeBuffer(base64.toString());

}

}

public class TXGenUserSig {

private long sdkappid;

private String key;

public TXGenUserSig(long sdkappid, String key) {

this.sdkappid = sdkappid;

this.key = key;

}

private String hmacsha256(String identifier, long currTime, long expire, String base64Userbuf) {

String contentToBeSigned = "TLS.identifier:" + identifier + "\n"

+ "TLS.sdkappid:" + sdkappid + "\n"

+ "TLS.time:" + currTime + "\n"

+ "TLS.expire:" + expire + "\n";

if (null != base64Userbuf) {

contentToBeSigned += "TLS.userbuf:" + base64Userbuf + "\n";

}

try {

byte[] byteKey = key.getBytes("UTF-8");

Mac hmac = Mac.getInstance("HmacSHA256");

SecretKeySpec keySpec = new SecretKeySpec(byteKey, "HmacSHA256");

hmac.init(keySpec);

byte[] byteSig = hmac.doFinal(contentToBeSigned.getBytes("UTF-8"));

return (new BASE64Encoder().encode(byteSig)).replaceAll("\\s*", "");

} catch (UnsupportedEncodingException e) {

return "";

} catch (NoSuchAlgorithmException e) {

return "";

} catch (InvalidKeyException e) {

return "";

}

}

private String genSig(String identifier, long expire, byte[] userbuf) {

long currTime = System.currentTimeMillis()/1000;

JSONObject sigDoc = new JSONObject();

sigDoc.put("TLS.ver", "2.0");

sigDoc.put("TLS.identifier", identifier);

sigDoc.put("TLS.sdkappid", sdkappid);

sigDoc.put("TLS.expire", expire);

sigDoc.put("TLS.time", currTime);

String base64UserBuf = null;

if (null != userbuf) {

base64UserBuf = new BASE64Encoder().encode(userbuf);

sigDoc.put("TLS.userbuf", base64UserBuf);

}

String sig = hmacsha256(identifier, currTime, expire, base64UserBuf);

if (sig.length() == 0) {

return "";

}

sigDoc.put("TLS.sig", sig);

Deflater compressor = new Deflater();

compressor.setInput(sigDoc.toString().getBytes(Charset.forName("UTF-8")));

compressor.finish();

byte [] compressedBytes = new byte[2048];

int compressedBytesLength = compressor.deflate(compressedBytes);

compressor.end();

return (new String(Base64URL.base64EncodeUrl(Arrays.copyOfRange(compressedBytes,

0, compressedBytesLength)))).replaceAll("\\s*", "");

}

public String genSig(String identifier, long expire) {

return genSig(identifier, expire, null);

}

public String genSigWithUserBuf(String identifier, long expire, byte[] userbuf) {

return genSig(identifier, expire, userbuf);

}

}

腾讯IM侧重前端,对java web后端没有好的sdk支持

自定义一个IM的配置类

@Configuration

public class TXIMConfiguration {

private static long sdkappid;

private static String key;

private static String identifier;

//userSig 有效期7天

private static final long EXPIRE_TIME=7*24*60*60;

private static TXGenUserSig txGenUserSig=null;

@Value("${txim.sdkappid}")

public void setSdkappid(long sdkappid) {

TXIMConfiguration.sdkappid = sdkappid;

}

@Value("${txim.key}")

public void setKey(String key) {

TXIMConfiguration.key = key;

}

@Value("${txim.identifier}")

public void setIdentifier(String identifier) {

TXIMConfiguration.identifier = identifier;

}

@Bean

public Object services(){

txGenUserSig=new TXGenUserSig(sdkappid,key);

return Boolean.TRUE;

}

public static String getUserSig(String identifier){

return txGenUserSig.genSig(identifier, EXPIRE_TIME);

}

/**

* 创建IM群组API URL

* @return

*/

public static String getCreateGroupURL(){

return "https://console.tim.qq.com/v4/group_open_http_svc/create_group?"+"sdkappid="+sdkappid+"&identifier="+identifier+"&usersig="+getUserSig(identifier)+

"&random="+ CodeUtil.getRandomNumber(32)+"&contenttype=json";

}

/**

* 解散IM群组API URL

* @return

*/

public static String getDestoryGroupURL(){

return "https://console.tim.qq.com/v4/group_open_http_svc/destroy_group?"+"sdkappid="+sdkappid+"&identifier="+identifier+"&usersig="+getUserSig(identifier)+

"&random="+CodeUtil.getRandomNumber(32)+"&contenttype=json";

}

/**

* 检测账号 API URL

* @return

*/

public static String getCheckAccountURL(){

return "https://console.tim.qq.com/v4/im_open_login_svc/account_check?"+"sdkappid="+sdkappid+"&identifier="+identifier+"&usersig="+getUserSig(identifier)+

"&random="+CodeUtil.getRandomNumber(32)+"&contenttype=json";

}

/**

* 单个账号导入 API URL

* @return

*/

public static String getAccountImportURL(){

return "https://console.tim.qq.com/v4/im_open_login_svc/account_import?"+"sdkappid="+sdkappid+"&identifier="+identifier+"&usersig="+getUserSig(identifier)+

"&random="+CodeUtil.getRandomNumber(32)+"&contenttype=json";

}

}

获取userSig的接口

/**

* 获取登录IM聊天室的账号密码

* @param

* @return

*/

@RequestMapping("/getUserSig")

public ResultBean getUserSig(String account){

try {

String userSig = TXIMConfiguration.getUserSig(account);

return ResultBean.setOk(0, "认证成功",userSig);

}catch (Exception e){

logger.error("腾讯SDK认证失败,检查秘钥 "+e);

return ResultBean.setError(1,"认证失败");

}

}

创建IM群组和解散IM群组 这里以Springboot线程池异步方式执行

准备好实体参数实体类

CheckItem

public class CheckItem {

private String UserID;

public CheckItem(String userID) {

UserID = userID;

}

@JSONField(name = "UserID")

public String getUserID() {

return UserID;

}

public void setUserID(String userID) {

UserID = userID;

}

}

GroupInfo

public class GroupInfo {

//群主UserId

private String Owner_Account;

//群组类型 Private/Public/ChatRoom/

private String Type;

//自定义群组ID

private String GroupId;

//群名称

private String Name;

//最大群成员数量

private int MaxMemberCount;

//申请加群方式

private String ApplyJoinOption;

public GroupInfo(String Owner_Account, String Type, String GroupId, String Name, int MaxMemberCount,String ApplyJoinOption) {

this.Owner_Account = Owner_Account;

this.Type = Type;

this.GroupId = GroupId;

this.Name = Name;

this.MaxMemberCount = MaxMemberCount;

this.ApplyJoinOption = ApplyJoinOption;

}

public GroupInfo(String Type, String GroupId, String Name, int MaxMemberCount,String ApplyJoinOption) {

this.Type = Type;

this.GroupId = GroupId;

this.Name = Name;

this.MaxMemberCount = MaxMemberCount;

this.ApplyJoinOption = ApplyJoinOption;

}

@JSONField(name="Owner_Account")

public String getOwner_Account() {

return Owner_Account;

}

public void setOwner_Account(String owner_Account) {

Owner_Account = owner_Account;

}

@JSONField(name="Type")

public String getType() {

return Type;

}

public void setType(String type) {

Type = type;

}

@JSONField(name="GroupId")

public String getGroupId() {

return GroupId;

}

public void setGroupId(String groupId) {

GroupId = groupId;

}

@JSONField(name="Name")

public String getName() {

return Name;

}

public void setName(String name) {

Name = name;

}

@JSONField(name="MaxMemberCount")

public int getMaxMemberCount() {

return MaxMemberCount;

}

public void setMaxMemberCount(int maxMemberCount) {

MaxMemberCount = maxMemberCount;

}

@JSONField(name="ApplyJoinOption")

public String getApplyJoinOption() {

return ApplyJoinOption;

}

public void setApplyJoinOption(String applyJoinOption) {

ApplyJoinOption = applyJoinOption;

}

}

业务实现,根据自己需要来

@Service

public class TXIMAsynServiceImpl implements TXIMAsynService {

private Logger logger = LoggerFactory.getLogger(TXIMAsynServiceImpl.class);

/**

* 异步创建群组

* @param lessonId

* @param periodIds

*/

@Override

@Async("asynTxImServiceExecutor")

public void createIMGroup(String account) {

logger.info("异步线程执行创建群组操作开始...userId=" + account);

//该账号未注册到IM系统中,注册后才可以将该账号指定为IM群主

if(account!=null) {

String accountStr = String.valueOf(account);

String checkRes = checkSingleAccount(accountStr);

if (checkRes == null) {

account = null;

} else if ("Not Imported".equals(checkRes)) {

//注册账号到IM体系

if ("OK".equals(importSingleAccount(accountStr))) {

logger.info("注册账号到IM成功,账号:{}", account);

} else {

logger.error("注册账号到IM失败,账号:{}", account);

}

}

}

//开始创建群组,如果账号为空创建无群主群组,否则有群主群组,这里创建群组类型为Public

GroupInfo groupInfo = account == null ?

new GroupInfo("Public", account, "用户" + account, 200, "FreeAccess")

: new GroupInfo(account, "Public", account, "用户" + account, 200, "FreeAccess");

String contentType = JSON.toJSONString(groupInfo);

//获取第三方API地址

String urlAddParam = TXIMConfiguration.getCreateGroupURL();

String res = HttpUtil.doPostJson(urlAddParam, contentType);

String actionStatus = JSONObject.parseObject(res).getString("ActionStatus");

if ("OK".equals(actionStatus)) {

logger.info("调用腾讯IM创建群组成功,参数:" + contentType + "结果:{}", res);

//创建完群组后,执行自己业务逻辑

} else {

logger.error("调用腾讯IM创建群组失败,{}", res);

}

}

/**

* 解散群组。

* @param periodIds

*/

@Override

@Async("asynTxImServiceExecutor")

public void destoryIMGroup(String account) {

for (Integer periodId : periodIds) {

String destoryGroupURL = TXIMConfiguration.getDestoryGroupURL();

String contentType = JSON.toJSONString(new HashMap(1) {

{

put("GroupId", account);

}

});

String res = HttpUtil.doPostJson(destoryGroupURL, contentType);

String actionStatus = JSONObject.parseObject(res).getString("ActionStatus");

if ("OK".equals(actionStatus)) {

logger.info("调用腾讯IM删除群组成功,参数:" + contentType + "结果:{}", res);

} else {

logger.error("调用腾讯IM删除群组失败,{}", res);

}

}

}

/**

* 检查单个账号

* 指定群组的群主时需要先检查群主是否注册到IM系统中,否则指定不成功

* @param account

* @return

*/

private String checkSingleAccount(String account) {

String checkContent = JSON.toJSONString(new HashMap(1) {

{

put("CheckItem", Arrays.asList(new CheckItem(account)));

}

});

String accountCheckRes = HttpUtil.doPostJson(TXIMConfiguration.getCheckAccountURL(), checkContent);

String accountCheckStatus = JSONObject.parseObject(accountCheckRes).getString("ActionStatus");

if ("OK".equals(accountCheckStatus)) {

logger.info("调用腾讯IM账号检查接口成功,账号:" + account + "结果:{}", accountCheckRes);

String resultItem = JSONObject.parseObject(accountCheckRes).getJSONArray("ResultItem").getString(0);

//NotImported Imported

return (String) JSON.parseObject(resultItem, Map.class).get("AccountStatus");

} else {

logger.error("调用腾讯IM账号检查接口失败,账号:" + account + "参数:" + checkContent + "结果:{}", accountCheckRes);

return null;

}

}

/**

* 注册单个账号

*

* @param account

* @return

*/

private String importSingleAccount(String account) {

//json参数

String checkContent = JSON.toJSONString(new HashMap(1) {

{

put("Identifier", account);

}

});

String importAccountRes = HttpUtil.doPostJson(TXIMConfiguration.getAccountImportURL(), checkContent);

logger.info("调用腾讯IM单个账号导入接口," + "参数:" + checkContent + "结果:{}", importAccountRes);

return JSON.parseObject(importAccountRes).getString("ActionStatus");

}

}

注意,腾讯IM参数首字母是大写,小写就无法识别,转换成JSON时候,会将参数首字母变成小写,需要注意这个问题,我这里用@JSONField来解决

java web 怎么实现直播_Java web后端直播接入腾讯IM聊天相关推荐

  1. java web学什么软件_java web开发是什么?该怎么学习?

    Java中有前端这个开发方向,上回我们说到了javaweb是什么?你们应该对javaweb有了一定的了解了,那你们知道该如何学习web吗?小编这里整理了一些相关知识,快来看看吧. 一.javaweb是 ...

  2. java web初级面试题_Java Web应用程序初学者教程

    java web初级面试题 Java Web Application is used to create dynamic websites. Java provides support for web ...

  3. java目前开发的背景_Java web 开发构想[一] 背景和形势

    [Java web 开发构想系列 转自JavaEye社区,在此申明] 1.背景.形势 能够进行Web开发的编程语言和技术很多 (1) 动态解释语言 PHP; Perl; Python (Zope, P ...

  4. java项目使用过滤器实例_Java web开发--过滤器篇(详细介绍)

    一. web过滤器的介绍 1.过滤器 在生活中,过滤这种我们时常可见:比如水资源的处理,化学药剂的提取等等.所谓过滤,就是指对某事物的处理进行一定的处理获取相应的结果的一个过程.它可以总结为下: 过滤 ...

  5. web图书销售管理系统_Java Web实现简易的图书管理系统

    Java Web实现简易的图书管理系统 这是一个使用Java Web相关的知识做出来的网页图书管理系统,使用的数据库为mysql. 程序中实现了登录功能和对图书表.图书类别表的增删查改功能. 因为我对 ...

  6. java web 怎么实现直播_java web开发直播平台可以实现但有缺陷

    java web开发直播平台可以做到,现在已经有很多开源Java直播工具了.Java可以实现全部功能,而且开发成本可能还略低.但是易用性,运营成本等各方面,都是不行的. java web开发直播平台的 ...

  7. java web是前端吗_Java Web 是前端还是后端?

    Java Web 是前端还是后端? Java Web是属于后端,Java Web就是用Java技术开发的Web应用,而Java是一种可以编写跨平台应用软件.完全面向对象的高级程序设计语言,一般常用于后 ...

  8. java web编码详解_java web 开发 编码问题详解

    java web 开发 编码问题详解 浏览器 IE/FireFox ------------->Servlet容器-------------------------->显示页面 编码   ...

  9. java web jsp相对路径_Java Web中的相对路径与绝对路径总结

    1.基本概念的理解 绝对路径:绝对路径就是你的主页上的文件或目录在硬盘上真正的路径,(URL和物理路径)例如:C:\xyz\test.txt 代表了test.txt文件的绝对路径.http://www ...

最新文章

  1. [多级联动下拉选择框]和[Tree to Tree]续——让他们可以设置默认值
  2. 膨胀卷积(Dilated convolution)
  3. GDCM:gdcm::String的测试程序
  4. Scala的存在类型
  5. css 字体字体图标_CSS基础知识:了解字体
  6. 三年经验前端社招——腾讯微保
  7. 包含内部类的.java文件编译后生成几个.class文件
  8. air中wav转mp3
  9. 最新增值税商品税目编码表_姓名:增值税,税率:13%,9%,6%,这是我最新最全税率表!...
  10. Python调整图片透明度
  11. [C语言]——打印素数(质数)
  12. 解决ecshop模板兼容jquery问题
  13. 计算机的扩展模式,Win7双屏复制/双屏扩展设置教程
  14. 怎么有python画五角星_Python的画五角星
  15. calamari架构与分析
  16. UEFI启动-GPT分区,Windows 7+ 系统引导修复
  17. LeetCode337打家劫社Ⅲ(树形动态规划)
  18. 图片和文字对齐的方法
  19. 清除composite里的子控件
  20. 苹果手机如何用短信信息服务器,Iphone双卡双待如何发信息? 苹果双卡手机发短信的方法...

热门文章

  1. 图论--DFS-SPFA求负环
  2. 希捷硬盘读取固件区数据
  3. liun5实现DHCP地址分配笔记
  4. 升级失败?用TFTP修复Linksys无线路由器固件
  5. 单片机怎么跳出循环_自学单片机第二十七篇:矩阵按键的硬件测试
  6. python中sample是什么意思_基于Python中random.sample()的替代方案
  7. android 线程信号量,iOS开发 多线程的高级应用-信号量semaphore
  8. dw6能编译asp吗,让Adobe Dreamweaver CC支持ASP
  9. b区计算机考研招不满的大学,B区又一所院校招收大量调剂!一志愿不满!
  10. android webview 填充,从Android使用WebView自动填充表格