文章目录
- 前言
- IM介绍
- 简介
- 架构
- 应用场景
- IM服务端接入(spring boot)
- 接口类
- 签名类
- 服务类
前言
最近有一个需求是在小程序上开发一个在线聊天的功能,调研了一下觉得腾讯云的IM服务比较合适。
IM介绍
简介
腾讯云IM与应用之间的交互逻辑如图所示
架构
IM 提供全球接入、单聊、群聊、消息推送、资料关系链托管、账号鉴权等全方位解决方案,并提供完备的 App 接入、后台管理接口。
应用场景
包括了社交沟通,互动直播,智能客服,物联网通信、企业通讯等等。
IM服务端接入(spring boot)
接口类
用的是Feign接入
@FeignClient(name = "tencentIMService", url = "${application.tencent-im.request-url}",configuration = OpenFeignConfig.class, fallback = TencentIMServiceFallback.class)
public interface TencentIMService {/*** 查询账号** @param sdkappid sdkApId* @param identifier 必须为 App 管理员账号* @param contenttype 请求格式固定值为json* @param usersig App 管理员账号生成的签名* @param random 请输入随机的32位无符号整数,取值范围0 - 4294967295* @param accounts 用户列表* @return 响应体*/@PostMapping("/v4/im_open_login_svc/account_check")JSONObject checkAccount(@RequestParam("sdkappid") Long sdkappid, @RequestParam("identifier") String identifier, @RequestParam("contenttype") String contenttype,@RequestParam("usersig") String usersig, @RequestParam("random") Integer random,@RequestBody IMCheckAccountVM accounts);/*** 导入单个账号** @param sdkappid sdkApId* @param identifier 必须为 App 管理员账号* @param contenttype 请求格式固定值为json* @param usersig App 管理员账号生成的签名* @param random 请输入随机的32位无符号整数,取值范围0 - 4294967295* @param account 用户* @return 响应体*/@PostMapping("/v4/im_open_login_svc/account_import")JSONObject importSingleAccount(@RequestParam("sdkappid") Long sdkappid, @RequestParam("identifier") String identifier, @RequestParam("contenttype") String contenttype,@RequestParam("usersig") String usersig, @RequestParam("random") Integer random,@RequestBody IMSingleAccountVM account);/*** 设置账号资料** @param sdkappid sdkApId* @param identifier 必须为 App 管理员账号* @param contenttype 请求格式固定值为json* @param usersig App 管理员账号生成的签名* @param random 请输入随机的32位无符号整数,取值范围0 - 4294967295* @param account 用户* @return 响应体*/@PostMapping("/v4/profile/portrait_set")JSONObject setAccount(@RequestParam("sdkappid") Long sdkappid, @RequestParam("identifier") String identifier, @RequestParam("contenttype") String contenttype,@RequestParam("usersig") String usersig, @RequestParam("random") Integer random,@RequestBody IMProfileVM account);/*** 导入多个账号** @param sdkappid sdkApId* @param identifier 必须为 App 管理员账号* @param contenttype 请求格式固定值为json* @param usersig App 管理员账号生成的签名* @param random 请输入随机的32位无符号整数,取值范围0 - 4294967295* @param accounts 用户账号列表* @return 响应体*/@PostMapping("/v4/im_open_login_svc/multiaccount_import")JSONObject importMultiAccount(@RequestParam("sdkappid") Long sdkappid, @RequestParam("identifier") String identifier, @RequestParam("contenttype") String contenttype,@RequestParam("usersig") String usersig, @RequestParam("random") Integer random,@RequestBody IMMultiAccountVM accounts);/*** 创建群组** @param sdkappid sdkApId* @param identifier 必须为 App 管理员账号* @param contenttype 请求格式固定值为json* @param usersig App 管理员账号生成的签名* @param random 请输入随机的32位无符号整数,取值范围0 - 4294967295* @param group 群组* @return 响应体*/@PostMapping("/v4/group_open_http_svc/create_group")JSONObject createGroup(@RequestParam("sdkappid") Long sdkappid, @RequestParam("identifier") String identifier, @RequestParam("contenttype") String contenttype,@RequestParam("usersig") String usersig, @RequestParam("random") Integer random,@RequestBody IMGroupVM group);/*** 导入群组成员** @param sdkappid sdkApId* @param identifier 必须为 App 管理员账号* @param contenttype 请求格式固定值为json* @param usersig App 管理员账号生成的签名* @param random 请输入随机的32位无符号整数,取值范围0 - 4294967295* @param group 群组* @return 响应体*/@PostMapping("/v4/group_open_http_svc/import_group_member")JSONObject importGroupMember(@RequestParam("sdkappid") Long sdkappid, @RequestParam("identifier") String identifier, @RequestParam("contenttype") String contenttype,@RequestParam("usersig") String usersig, @RequestParam("random") Integer random,@RequestBody IMGroupVM group);/*** 在群组中发送普通消息** @param sdkappid sdkApId* @param identifier 必须为 App 管理员账号* @param contenttype 请求格式固定值为json* @param usersig App 管理员账号生成的签名* @param random 请输入随机的32位无符号整数,取值范围0 - 4294967295* @param msg 普通消息* @return 响应体*/@PostMapping("/v4/group_open_http_svc/send_group_msg")JSONObject sendGroupMsg(@RequestParam("sdkappid") Long sdkappid, @RequestParam("identifier") String identifier, @RequestParam("contenttype") String contenttype,@RequestParam("usersig") String usersig, @RequestParam("random") Integer random,@RequestBody IMGroupMsgVM msg);/*** 在群组中发送系统通知** @param sdkappid sdkApId* @param identifier 必须为 App 管理员账号* @param contenttype 请求格式固定值为json* @param usersig App 管理员账号生成的签名* @param random 请输入随机的32位无符号整数,取值范围0 - 4294967295* @param msg 系统消息* @return 响应体*/@PostMapping("/v4/group_open_http_svc/send_group_system_notification")JSONObject sendGroupSystemNotification(@RequestParam("sdkappid") Long sdkappid, @RequestParam("identifier") String identifier, @RequestParam("contenttype") String contenttype,@RequestParam("usersig") String usersig, @RequestParam("random") Integer random,@RequestBody IMGroupSystemMsgVM msg);/*** 创建机器人账号** @param sdkappid sdkApId* @param identifier 必须为 App 管理员账号* @param contenttype 请求格式固定值为json* @param usersig App 管理员账号生成的签名* @param random 请输入随机的32位无符号整数,取值范围0 - 4294967295* @param msg 系统消息* @return 响应体*/@PostMapping("/v4/openim_robot_http_svc/create_robot")JSONObject createRobot(@RequestParam("sdkappid") Long sdkappid, @RequestParam("identifier") String identifier, @RequestParam("contenttype") String contenttype,@RequestParam("usersig") String usersig, @RequestParam("random") Integer random,@RequestBody IMSingleAccountVM msg);/*** 在群组中发送普通消息** @param sdkappid sdkApId* @param identifier 必须为 App 管理员账号* @param contenttype 请求格式固定值为json* @param usersig App 管理员账号生成的签名* @param random 请输入随机的32位无符号整数,取值范围0 - 4294967295* @param msg 普通消息* @return 响应体*/@PostMapping("/v4/openim/sendmsg")JSONObject sendMsg(@RequestParam("sdkappid") Long sdkappid, @RequestParam("identifier") String identifier, @RequestParam("contenttype") String contenttype,@RequestParam("usersig") String usersig, @RequestParam("random") Integer random,@RequestBody IMMsgVM msg);/*** 在群组中发送普通消息** @param sdkappid sdkApId* @param identifier 必须为 App 管理员账号* @param contenttype 请求格式固定值为json* @param usersig App 管理员账号生成的签名* @param random 请输入随机的32位无符号整数,取值范围0 - 4294967295* @param content 内容* @return 响应体*/@PostMapping("/v4/openim/admin_set_msg_read")JSONObject adminSetMsgRead(@RequestParam("sdkappid") Long sdkappid, @RequestParam("identifier") String identifier, @RequestParam("contenttype") String contenttype,@RequestParam("usersig") String usersig, @RequestParam("random") Integer random,@RequestBody String content);}
签名类
@Component
@Slf4j
@RequiredArgsConstructor
public class IMUserSigService {private final ApplicationProperties applicationProperties;/*** 【功能说明】用于签发 TRTC 和 IM 服务中必须要使用的 UserSig 鉴权票据* <p>* 【参数说明】** @param userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。* @param expire - UserSig 票据的过期时间,单位是秒,比如 86400 代表生成的 UserSig 票据在一天后就无法再使用了。* @return usersig -生成的签名*//*** Function: Used to issue UserSig that is required by the TRTC and IM services.* <p>* Parameter description:** @param userid - User ID. The value can be up to 32 bytes in length and contain letters (a-z and A-Z), digits (0-9), underscores (_), and hyphens (-).* @param expire - UserSig expiration time, in seconds. For example, 86400 indicates that the generated UserSig will expire one day after being generated.* @return usersig - Generated signature.*/public String genUserSig(String userid, long expire) {return genUserSig(userid, expire, null);}/*** 【功能说明】* 用于签发 TRTC 进房参数中可选的 PrivateMapKey 权限票据。* PrivateMapKey 需要跟 UserSig 一起使用,但 PrivateMapKey 比 UserSig 有更强的权限控制能力:* - UserSig 只能控制某个 UserID 有无使用 TRTC 服务的权限,只要 UserSig 正确,其对应的 UserID 可以进出任意房间。* - PrivateMapKey 则是将 UserID 的权限控制的更加严格,包括能不能进入某个房间,能不能在该房间里上行音视频等等。* 如果要开启 PrivateMapKey 严格权限位校验,需要在【实时音视频控制台】/【应用管理】/【应用信息】中打开“启动权限密钥”开关。* <p>* 【参数说明】** @param userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。* @param expire - PrivateMapKey 票据的过期时间,单位是秒,比如 86400 生成的 PrivateMapKey 票据在一天后就无法再使用了。* @param roomid - 房间号,用于指定该 userid 可以进入的房间号* @param privilegeMap - 权限位,使用了一个字节中的 8 个比特位,分别代表八个具体的功能权限开关:* - 第 1 位:0000 0001 = 1,创建房间的权限* - 第 2 位:0000 0010 = 2,加入房间的权限* - 第 3 位:0000 0100 = 4,发送语音的权限* - 第 4 位:0000 1000 = 8,接收语音的权限* - 第 5 位:0001 0000 = 16,发送视频的权限* - 第 6 位:0010 0000 = 32,接收视频的权限* - 第 7 位:0100 0000 = 64,发送辅路(也就是屏幕分享)视频的权限* - 第 8 位:1000 0000 = 200,接收辅路(也就是屏幕分享)视频的权限* - privilegeMap == 1111 1111 == 255 代表该 userid 在该 roomid 房间内的所有功能权限。* - privilegeMap == 0010 1010 == 42 代表该 userid 拥有加入房间和接收音视频数据的权限,但不具备其他权限。* @return usersig - 生成带userbuf的签名*//*** Function:* Used to issue PrivateMapKey that is optional for room entry.* PrivateMapKey must be used together with UserSig but with more powerful permission control capabilities.* - UserSig can only control whether a UserID has permission to use the TRTC service. As long as the UserSig is correct, the user with the corresponding UserID can enter or leave any room.* - PrivateMapKey specifies more stringent permissions for a UserID, including whether the UserID can be used to enter a specific room and perform audio/video upstreaming in the room.* To enable stringent PrivateMapKey permission bit verification, you need to enable permission key in TRTC console > Application Management > Application Info.* <p>* Parameter description:** @param userid - User ID. The value can be up to 32 bytes in length and contain letters (a-z and A-Z), digits (0-9), underscores (_), and hyphens (-).* @param roomid - ID of the room to which the specified UserID can enter.* @param expire - PrivateMapKey expiration time, in seconds. For example, 86400 indicates that the generated PrivateMapKey will expire one day after being generated.* @param privilegeMap - Permission bits. Eight bits in the same byte are used as the permission switches of eight specific features:* - Bit 1: 0000 0001 = 1, permission for room creation* - Bit 2: 0000 0010 = 2, permission for room entry* - Bit 3: 0000 0100 = 4, permission for audio sending* - Bit 4: 0000 1000 = 8, permission for audio receiving* - Bit 5: 0001 0000 = 16, permission for video sending* - Bit 6: 0010 0000 = 32, permission for video receiving* - Bit 7: 0100 0000 = 64, permission for substream video sending (screen sharing)* - Bit 8: 1000 0000 = 200, permission for substream video receiving (screen sharing)* - privilegeMap == 1111 1111 == 255: Indicates that the UserID has all feature permissions of the room specified by roomid.* - privilegeMap == 0010 1010 == 42: Indicates that the UserID has only the permissions to enter the room and receive audio/video data.* @return usersig - Generate signature with userbuf*/public String genPrivateMapKey(String userid, long expire, long roomid, long privilegeMap) {byte[] userbuf = genUserBuf(userid, roomid, expire, privilegeMap, 0, ""); //生成userbufreturn genUserSig(userid, expire, userbuf);}/*** 【功能说明】* 用于签发 TRTC 进房参数中可选的 PrivateMapKey 权限票据。* PrivateMapKey 需要跟 UserSig 一起使用,但 PrivateMapKey 比 UserSig 有更强的权限控制能力:* - UserSig 只能控制某个 UserID 有无使用 TRTC 服务的权限,只要 UserSig 正确,其对应的 UserID 可以进出任意房间。* - PrivateMapKey 则是将 UserID 的权限控制的更加严格,包括能不能进入某个房间,能不能在该房间里上行音视频等等。* 如果要开启 PrivateMapKey 严格权限位校验,需要在【实时音视频控制台】/【应用管理】/【应用信息】中打开“启动权限密钥”开关。* <p>* 【参数说明】** @param userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。* @param expire - PrivateMapKey 票据的过期时间,单位是秒,比如 86400 生成的 PrivateMapKey 票据在一天后就无法再使用了。* @param roomstr - 字符串房间号,用于指定该 userid 可以进入的房间号* @param privilegeMap - 权限位,使用了一个字节中的 8 个比特位,分别代表八个具体的功能权限开关:* - 第 1 位:0000 0001 = 1,创建房间的权限* - 第 2 位:0000 0010 = 2,加入房间的权限* - 第 3 位:0000 0100 = 4,发送语音的权限* - 第 4 位:0000 1000 = 8,接收语音的权限* - 第 5 位:0001 0000 = 16,发送视频的权限* - 第 6 位:0010 0000 = 32,接收视频的权限* - 第 7 位:0100 0000 = 64,发送辅路(也就是屏幕分享)视频的权限* - 第 8 位:1000 0000 = 200,接收辅路(也就是屏幕分享)视频的权限* - privilegeMap == 1111 1111 == 255 代表该 userid 在该 roomid 房间内的所有功能权限。* - privilegeMap == 0010 1010 == 42 代表该 userid 拥有加入房间和接收音视频数据的权限,但不具备其他权限。* @return usersig - 生成带userbuf的签名*//*** Function:* Used to issue PrivateMapKey that is optional for room entry.* PrivateMapKey must be used together with UserSig but with more powerful permission control capabilities.* - UserSig can only control whether a UserID has permission to use the TRTC service. As long as the UserSig is correct, the user with the corresponding UserID can enter or leave any room.* - PrivateMapKey specifies more stringent permissions for a UserID, including whether the UserID can be used to enter a specific room and perform audio/video upstreaming in the room.* To enable stringent PrivateMapKey permission bit verification, you need to enable permission key in TRTC console > Application Management > Application Info.* <p>* Parameter description:** @param userid - User ID. The value can be up to 32 bytes in length and contain letters (a-z and A-Z), digits (0-9), underscores (_), and hyphens (-).* @param roomstr - ID of the room to which the specified UserID can enter.* @param expire - PrivateMapKey expiration time, in seconds. For example, 86400 indicates that the generated PrivateMapKey will expire one day after being generated.* @param privilegeMap - Permission bits. Eight bits in the same byte are used as the permission switches of eight specific features:* - Bit 1: 0000 0001 = 1, permission for room creation* - Bit 2: 0000 0010 = 2, permission for room entry* - Bit 3: 0000 0100 = 4, permission for audio sending* - Bit 4: 0000 1000 = 8, permission for audio receiving* - Bit 5: 0001 0000 = 16, permission for video sending* - Bit 6: 0010 0000 = 32, permission for video receiving* - Bit 7: 0100 0000 = 64, permission for substream video sending (screen sharing)* - Bit 8: 1000 0000 = 200, permission for substream video receiving (screen sharing)* - privilegeMap == 1111 1111 == 255: Indicates that the UserID has all feature permissions of the room specified by roomid.* - privilegeMap == 0010 1010 == 42: Indicates that the UserID has only the permissions to enter the room and receive audio/video data.* @return usersig - Generate signature with userbuf*/public String genPrivateMapKeyWithStringRoomID(String userid, long expire, String roomstr, long privilegeMap) {byte[] userbuf = genUserBuf(userid, 0, expire, privilegeMap, 0, roomstr); //生成userbufreturn genUserSig(userid, expire, userbuf);}private String hmacsha256(String identifier, long currTime, long expire, String base64Userbuf) {ApplicationProperties.TencentIm tencentIm = applicationProperties.getTencentIm();String contentToBeSigned = "TLS.identifier:" + identifier + "\n"+ "TLS.sdkappid:" + tencentIm.getSdkAppId() + "\n"+ "TLS.time:" + currTime + "\n"+ "TLS.expire:" + expire + "\n";if (null != base64Userbuf) {contentToBeSigned += "TLS.userbuf:" + base64Userbuf + "\n";}try {byte[] byteKey = tencentIm.getKey().getBytes(StandardCharsets.UTF_8);Mac hmac = Mac.getInstance("HmacSHA256");SecretKeySpec keySpec = new SecretKeySpec(byteKey, "HmacSHA256");hmac.init(keySpec);byte[] byteSig = hmac.doFinal(contentToBeSigned.getBytes(StandardCharsets.UTF_8));return (Base64.getEncoder().encodeToString(byteSig)).replaceAll("\\s*", "");} catch (NoSuchAlgorithmException | InvalidKeyException e) {return "";}}private String genUserSig(String userid, long expire, byte[] userbuf) {ApplicationProperties.TencentIm tencentIm = applicationProperties.getTencentIm();long currTime = System.currentTimeMillis() / 1000;JSONObject sigDoc = new JSONObject();sigDoc.put("TLS.ver", "2.0");sigDoc.put("TLS.identifier", userid);sigDoc.put("TLS.sdkappid", tencentIm.getSdkAppId());sigDoc.put("TLS.expire", expire);sigDoc.put("TLS.time", currTime);String base64UserBuf = null;if (null != userbuf) {base64UserBuf = Base64.getEncoder().encodeToString(userbuf).replaceAll("\\s*", "");sigDoc.put("TLS.userbuf", base64UserBuf);}String sig = hmacsha256(userid, currTime, expire, base64UserBuf);if (sig.length() == 0) {return "";}sigDoc.put("TLS.sig", sig);Deflater compressor = new Deflater();compressor.setInput(sigDoc.toString().getBytes(StandardCharsets.UTF_8));compressor.finish();byte[] compressedBytes = new byte[2048];int compressedBytesLength = compressor.deflate(compressedBytes);compressor.end();return (new String(base64EncodeUrl(Arrays.copyOfRange(compressedBytes,0, compressedBytesLength)))).replaceAll("\\s*", "");}public byte[] genUserBuf(String account, long dwAuthID, long dwExpTime,long dwPrivilegeMap, long dwAccountType, String RoomStr) {//视频校验位需要用到的字段,按照网络字节序放入buf中/*cVer unsigned char/1 版本号,填0wAccountLen unsigned short /2 第三方自己的帐号长度account wAccountLen 第三方自己的帐号字符dwSdkAppid unsigned int/4 sdkappiddwAuthID unsigned int/4 群组号码dwExpTime unsigned int/4 过期时间 ,直接使用填入的值dwPrivilegeMap unsigned int/4 权限位,主播0xff,观众0xabdwAccountType unsigned int/4 第三方帐号类型*///The fields required for the video check digit are placed in buf according to the network byte order./*cVer unsigned char/1 Version number, fill in 0wAccountLen unsigned short /2 Third party's own account lengthaccount wAccountLen Third party's own account charactersdwSdkAppid unsigned int/4 sdkappiddwAuthID unsigned int/4 group numberdwExpTime unsigned int/4 Expiration time , use the filled value directlydwPrivilegeMap unsigned int/4 Permission bits, host 0xff, audience 0xabdwAccountType unsigned int/4 Third-party account type*/long sdkAppId = applicationProperties.getTencentIm().getSdkAppId();int accountLength = account.length();int roomStrLength = RoomStr.length();int offset = 0;int bufLength = 1 + 2 + accountLength + 20;if (roomStrLength > 0) {bufLength = bufLength + 2 + roomStrLength;}byte[] userbuf = new byte[bufLength];//cVerif (roomStrLength > 0) {userbuf[offset++] = 1;} else {userbuf[offset++] = 0;}//wAccountLenuserbuf[offset++] = (byte) ((accountLength & 0xFF00) >> 8);userbuf[offset++] = (byte) (accountLength & 0x00FF);//accountfor (; offset < 3 + accountLength; ++offset) {userbuf[offset] = (byte) account.charAt(offset - 3);}//dwSdkAppiduserbuf[offset++] = (byte) ((sdkAppId & 0xFF000000) >> 24);userbuf[offset++] = (byte) ((sdkAppId & 0x00FF0000) >> 16);userbuf[offset++] = (byte) ((sdkAppId & 0x0000FF00) >> 8);userbuf[offset++] = (byte) (sdkAppId & 0x000000FF);//dwAuthId,房间号//dwAuthId, room numberuserbuf[offset++] = (byte) ((dwAuthID & 0xFF000000) >> 24);userbuf[offset++] = (byte) ((dwAuthID & 0x00FF0000) >> 16);userbuf[offset++] = (byte) ((dwAuthID & 0x0000FF00) >> 8);userbuf[offset++] = (byte) (dwAuthID & 0x000000FF);//expire,过期时间,当前时间 + 有效期(单位:秒)//expire,Expiration time, current time + validity period (unit: seconds)long currTime = System.currentTimeMillis() / 1000;long expire = currTime + dwExpTime;userbuf[offset++] = (byte) ((expire & 0xFF000000) >> 24);userbuf[offset++] = (byte) ((expire & 0x00FF0000) >> 16);userbuf[offset++] = (byte) ((expire & 0x0000FF00) >> 8);userbuf[offset++] = (byte) (expire & 0x000000FF);//dwPrivilegeMap,权限位//dwPrivilegeMap,Permission bitsuserbuf[offset++] = (byte) ((dwPrivilegeMap & 0xFF000000) >> 24);userbuf[offset++] = (byte) ((dwPrivilegeMap & 0x00FF0000) >> 16);userbuf[offset++] = (byte) ((dwPrivilegeMap & 0x0000FF00) >> 8);userbuf[offset++] = (byte) (dwPrivilegeMap & 0x000000FF);//dwAccountType,账户类型//dwAccountType,account typeuserbuf[offset++] = (byte) ((dwAccountType & 0xFF000000) >> 24);userbuf[offset++] = (byte) ((dwAccountType & 0x00FF0000) >> 16);userbuf[offset++] = (byte) ((dwAccountType & 0x0000FF00) >> 8);userbuf[offset++] = (byte) (dwAccountType & 0x000000FF);if (roomStrLength > 0) {//roomStrLenuserbuf[offset++] = (byte) ((roomStrLength & 0xFF00) >> 8);userbuf[offset++] = (byte) (roomStrLength & 0x00FF);//roomStrfor (; offset < bufLength; ++offset) {userbuf[offset] = (byte) RoomStr.charAt(offset - (bufLength - roomStrLength));}}return userbuf;}public static byte[] base64EncodeUrl(byte[] input) {byte[] base64 = Base64.getEncoder().encode(input);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;}
}
服务类
@Component
@Slf4j
@RequiredArgsConstructor
public class IMService {private final IMUserSigService userSigService;private final TencentIMService tencentIMService;private final ApplicationProperties applicationProperties;/*** 生成腾讯云用户的UserSig** @param userid 腾讯IM用户ID* @return UserSig*/public String genUserSig(String userid) {return userSigService.genUserSig(userid, 30 * 24 * 3600);}/*** 查询用户** @param users 用户列表* @return List 不存在的IM用户*/public List<String> checkAccount(List<String> users) {ApplicationProperties.TencentIm tencentIm = applicationProperties.getTencentIm();String userSig = genUserSig(tencentIm.getIdentifier());List<IMCheckAccountItemVM> accountItems = users.stream().map(u -> {IMCheckAccountItemVM single = new IMCheckAccountItemVM();single.setUserId(u);return single;}).toList();IMCheckAccountVM accounts = IMCheckAccountVM.builder().items(accountItems).build();int random = RandomUtil.randomInt(0, Integer.MAX_VALUE);JSONObject result = tencentIMService.checkAccount(tencentIm.getSdkAppId(), tencentIm.getIdentifier(), tencentIm.getContenttype(), userSig, random,accounts);log.info("im -> account-> import -> result:[{}],[{}]", result.getStr("ErrorCode"), result.getStr("ErrorInfo"));if (!CharSequenceUtil.equals("OK", result.getStr("ActionStatus"))) {throw new SystemProblem(AppStatus.FAILURE, "查询IM账号出现错误!");}List<String> noExistAccounts = new ArrayList<>();JSONArray resultItem = result.getJSONArray("ResultItem");resultItem.forEach(item -> {JSONObject itemObj = JSONUtil.parseObj(item);if (itemObj.getInt("ResultCode") == 0 && CharSequenceUtil.equals("NotImported", itemObj.getStr("AccountStatus"))) {noExistAccounts.add(itemObj.getStr("UserID"));}});return noExistAccounts;}/*** 导入单个用户** @param userId 用户ID* @param nick 名称* @param faceUrl 头像* @return boolean*/public boolean importSingleAccount(String userId, String nick, String faceUrl) {ApplicationProperties.TencentIm tencentIm = applicationProperties.getTencentIm();String userSig = genUserSig(tencentIm.getIdentifier());IMSingleAccountVM singleAccount = IMSingleAccountVM.builder().userId(userId).nick(nick).faceUrl(faceUrl).build();int random = RandomUtil.randomInt(0, Integer.MAX_VALUE);JSONObject result = tencentIMService.importSingleAccount(tencentIm.getSdkAppId(), tencentIm.getIdentifier(), tencentIm.getContenttype(), userSig, random,singleAccount);log.info("im -> account-> import -> result:[{}],[{}]", result.getStr("ErrorCode"), result.getStr("ErrorInfo"));return CharSequenceUtil.equals("OK", result.getStr("ActionStatus"));}/*** 导入多个用户** @param userIds 用户ID列表* @return boolean*/public boolean importMultiAccount(List<IMUserInfo> userIds) {ApplicationProperties.TencentIm tencentIm = applicationProperties.getTencentIm();String userSig = genUserSig(tencentIm.getIdentifier());List<IMSingleAccountVM> accounts = userIds.stream().map(u -> {IMSingleAccountVM single = new IMSingleAccountVM();single.setUserId(u.getUserId());single.setNick(u.getNick());single.setFaceUrl(u.getFaceUrl());return single;}).toList();IMMultiAccountVM multiAccount = IMMultiAccountVM.builder().accounts(accounts).build();int random = RandomUtil.randomInt(0, Integer.MAX_VALUE);JSONObject result = tencentIMService.importMultiAccount(tencentIm.getSdkAppId(), tencentIm.getIdentifier(), tencentIm.getContenttype(), userSig, random,multiAccount);log.info("im -> account-> import -> result:[{}],[{}]", result.getStr("ErrorCode"), result.getStr("ErrorInfo"));return CharSequenceUtil.equals("OK", result.getStr("ActionStatus"));}/*** 创建群组** @param owner 群主* @param groupName 群组名称* @param type 群组类型:Private/Public/ChatRoom/Community(不支持AVChatRoom)(必填)* @param members 成员列表* @return boolean*/public String createGroup(String owner, String groupName, String type, List<String> members) {ApplicationProperties.TencentIm tencentIm = applicationProperties.getTencentIm();String userSig = genUserSig(tencentIm.getIdentifier());//群组信息List<IMGroupMemberVM> memberList = members.stream().map(m -> {IMGroupMemberVM member = new IMGroupMemberVM();member.setMemberAccount(m);return member;}).collect(Collectors.toList());IMGroupVM group = IMGroupVM.builder().ownerAccount(owner).name(groupName).type(type).memberList(memberList).build();int random = RandomUtil.randomInt(0, Integer.MAX_VALUE);JSONObject result = tencentIMService.createGroup(tencentIm.getSdkAppId(), tencentIm.getIdentifier(), tencentIm.getContenttype(), userSig, random,group);String errorInfo = result.getStr("ErrorInfo");log.info("im -> create-> group -> result:[{}],[{}]", result.getStr("ErrorCode"), result.getStr("ErrorInfo"));if (!CharSequenceUtil.equals("OK", result.getStr("ActionStatus"))) {throw new SystemProblem(AppStatus.FAILURE, "create group occurs to exception : " + errorInfo);}return result.getStr("GroupId");}/*** 导入群组成员** @param groupId 群组ID* @param members 群组成员* @return boolean*/public String importGroupMember(String groupId, List<String> members) {ApplicationProperties.TencentIm tencentIm = applicationProperties.getTencentIm();String userSig = genUserSig(tencentIm.getIdentifier());//群组信息List<IMGroupMemberVM> memberList = members.stream().map(m -> {IMGroupMemberVM member = new IMGroupMemberVM();member.setMemberAccount(m);return member;}).collect(Collectors.toList());IMGroupVM group = IMGroupVM.builder().groupId(groupId).memberList(memberList).build();int random = RandomUtil.randomInt(0, Integer.MAX_VALUE);JSONObject result = tencentIMService.importGroupMember(tencentIm.getSdkAppId(), tencentIm.getIdentifier(), tencentIm.getContenttype(), userSig, random,group);String errorInfo = result.getStr("ErrorInfo");log.info("im -> import-> group-member -> result:[{}],[{}]", result.getStr("ErrorCode"), result.getStr("ErrorInfo"));if (!CharSequenceUtil.equals("OK", result.getStr("ActionStatus"))) {throw new SystemProblem(AppStatus.FAILURE, "create group occurs to exception : " + errorInfo);}return result.getStr("GroupId");}/*** 向群组发送普通消息** @param groupId 群组ID* @param fromAccount 指定账号发送* @param msgBodyList 消息体* @return boolean*/public boolean sendGroupMsg(String groupId, String fromAccount, List<IMGroupMsgBodyVM> msgBodyList) {ApplicationProperties.TencentIm tencentIm = applicationProperties.getTencentIm();String userSig = genUserSig(tencentIm.getIdentifier());//群组信息IMGroupMsgVM groupMsg = IMGroupMsgVM.builder().groupId(groupId).fromAccount(fromAccount).msgBody(msgBodyList).build();int random = RandomUtil.randomInt(0, Integer.MAX_VALUE);JSONObject result = tencentIMService.sendGroupMsg(tencentIm.getSdkAppId(), tencentIm.getIdentifier(), tencentIm.getContenttype(), userSig, random,groupMsg);log.info("im -> send-> group-msg -> result:[{}],[{}]", result.getStr("ErrorCode"), result.getStr("ErrorInfo"));return CharSequenceUtil.equals("OK", result.getStr("ActionStatus"));}/*** 向群组发送系统消息** @param groupId 群组ID* @param content 系统消息* @return boolean*/public boolean sendGroupSystemMsg(String groupId, String content) {ApplicationProperties.TencentIm tencentIm = applicationProperties.getTencentIm();String userSig = genUserSig(tencentIm.getIdentifier());//系统信息IMGroupSystemMsgVM groupSystemMsg = IMGroupSystemMsgVM.builder().groupId(groupId).content(content).build();int random = RandomUtil.randomInt(0, Integer.MAX_VALUE);JSONObject result = tencentIMService.sendGroupSystemNotification(tencentIm.getSdkAppId(), tencentIm.getIdentifier(), tencentIm.getContenttype(), userSig, random,groupSystemMsg);log.info("im -> send-> system-msg -> result:[{}],[{}]", result.getStr("ErrorCode"), result.getStr("ErrorInfo"));return CharSequenceUtil.equals("OK", result.getStr("ActionStatus"));}/*** 创建机器人用户** @param userId 用户ID* @param nick 名称* @param faceUrl 头像* @return boolean*/public boolean createRobot(String userId, String nick, String faceUrl) {ApplicationProperties.TencentIm tencentIm = applicationProperties.getTencentIm();String userSig = genUserSig(tencentIm.getIdentifier());IMSingleAccountVM singleAccount = IMSingleAccountVM.builder().userId(userId).nick(nick).faceUrl(faceUrl).build();int random = RandomUtil.randomInt(0, Integer.MAX_VALUE);JSONObject result = tencentIMService.createRobot(tencentIm.getSdkAppId(), tencentIm.getIdentifier(), tencentIm.getContenttype(), userSig, random,singleAccount);log.info("im -> create-> robot -> result:[{}],[{}]", result.getStr("ErrorCode"), result.getStr("ErrorInfo"));return CharSequenceUtil.equals("OK", result.getStr("ActionStatus"));}/*** 发送单聊消息** @param fromAccount 指定账号发送* @param toAccount 指定账号接受* @param msgBodyList 消息体* @return boolean*/public boolean sendMsg(String fromAccount, String toAccount, List<IMGroupMsgBodyVM> msgBodyList) {ApplicationProperties.TencentIm tencentIm = applicationProperties.getTencentIm();String userSig = genUserSig(tencentIm.getIdentifier());int random = RandomUtil.randomInt(0, Integer.MAX_VALUE);//群组信息IMMsgVM msgVM = IMMsgVM.builder().fromAccount(fromAccount).toAccount(toAccount).msgBody(msgBodyList).random(random).build();JSONObject result = tencentIMService.sendMsg(tencentIm.getSdkAppId(), tencentIm.getIdentifier(), tencentIm.getContenttype(), userSig, random,msgVM);log.info("im -> send-> msg -> result:[{}],[{}]", result.getStr("ErrorCode"), result.getStr("ErrorInfo"));return CharSequenceUtil.equals("OK", result.getStr("ActionStatus"));}/*** 设置用户资料** @param userId 用户ID* @param nick 昵称* @param faceUrl 头像*/public void setAccount(Long userId, String nick, String faceUrl) {List<String> accounts = new ArrayList<>();accounts.add("PATIENT_" + userId);//不存在账号List<String> noExists = checkAccount(accounts);accounts.removeAll(noExists);if (CollUtil.isNotEmpty(accounts)) {List<IMProfileItemVM> profileItems = new ArrayList<>();profileItems.add(IMProfileItemVM.builder().tag("Tag_Profile_IM_Nick").value(nick).build());
// profileItems.add(IMProfileItemVM.builder().tag("Tag_Profile_IM_Image").value(faceUrl).build());for (String account : accounts) {ApplicationProperties.TencentIm tencentIm = applicationProperties.getTencentIm();String userSig = genUserSig(tencentIm.getIdentifier());int random = RandomUtil.randomInt(0, Integer.MAX_VALUE);IMProfileVM profileVM = IMProfileVM.builder().fromAccount(account).profileItem(profileItems).build();JSONObject result = tencentIMService.setAccount(tencentIm.getSdkAppId(), tencentIm.getIdentifier(), tencentIm.getContenttype(), userSig, random,profileVM);log.info("im -> send-> msg -> result:[{}],[{}]", result.getStr("ErrorCode"), result.getStr("ErrorInfo"));}}}/*** 管理员指定 reportAccount 将会话 peerAccount 的单聊未读计数清除。** @param reportAccount 进行会话未读计数清理的用户 UserId* @param peerAccount 单聊会话的另一方用户 UserId*/public void setMsgRead(String reportAccount, String peerAccount) {ApplicationProperties.TencentIm tencentIm = applicationProperties.getTencentIm();String userSig = genUserSig(tencentIm.getIdentifier());int random = RandomUtil.randomInt(0, Integer.MAX_VALUE);Map<String, String> content = new HashMap<>();content.put("Report_Account", reportAccount);content.put("Peer_Account", peerAccount);JSONObject result = tencentIMService.adminSetMsgRead(tencentIm.getSdkAppId(), tencentIm.getIdentifier(), tencentIm.getContenttype(), userSig, random,JSONUtil.toJsonStr(content));log.info("im -> send-> msg -> result:[{}],[{}]", result.getStr("ErrorCode"), result.getStr("ErrorInfo"));}
}