Springboot 技术整合--笔记17-tencentIM

本文内容


参考资料

Java后台服务端接入腾讯IM
tencentIM官方文档

usesrsig(token)算法参考官方文档

UserSig 是用户登录即时通信 IM 的密码,其本质是对 UserID 等信息加密后得到的密文,本文将指导您如何生成 UserSig
UserSig一般采用web后台提供api方式,增加安全性。
UserSig很多web后台,把其成为token
比如在Android app项目中登录的信息如下

userSig =eJwtjNEKgjAUQP-lPofcuWkq9CgaGgUGRW-m1ritZKiELPr3RH0858D5wrmsvI-qIAHfQ9jMTFK1Az1o1k3jnNaMra2XpraWJCRMIHIeCuYvRY2WOgVJiCJCXNxA78mwIIpEHDN-uz5IT2Mj9q3RB*TdrRhzeerTMrsET*SGO7zzwuWyOr6yq637Hfz*ZxQx1w__
image.png
usersig官方生成demo(android方提供转Java)
package cn.stylefeng.guns.core.util;



import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.qiniu.util.Base64;
import org.apache.commons.lang.StringUtils;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.zip.Deflater;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

/*
 * Module:   GenerateTestUserSig
 *
 * Function: 用于生成测试用的 UserSig,UserSig 是腾讯云为其云服务设计的一种安全保护签名。
 *           其计算方法是对 SDKAppID、UserID 和 EXPIRETIME 进行加密,加密算法为 HMAC-SHA256。
 *
 * Attention: 请不要将如下代码发布到您的线上正式版本的 App 中,原因如下:
 *
 *            本文件中的代码虽然能够正确计算出 UserSig,但仅适合快速调通 SDK 的基本功能,不适合线上产品,
 *            这是因为客户端代码中的 SECRETKEY 很容易被反编译逆向破解,尤其是 Web 端的代码被破解的难度几乎为零。
 *            一旦您的密钥泄露,攻击者就可以计算出正确的 UserSig 来盗用您的腾讯云流量。
 *
 *            正确的做法是将 UserSig 的计算代码和加密密钥放在您的业务服务器上,然后由 App 按需向您的服务器获取实时算出的 UserSig。
 *            由于破解服务器的成本要高于破解客户端 App,所以服务器计算的方案能够更好地保护您的加密密钥。
 *
 * Reference:https://cloud.tencent.com/document/product/269/32688#Server
 */
public class TencentIMGenerateTestUserSig {

    /**
     * 腾讯云 SDKAppId,需要替换为您自己账号下的 SDKAppId。
     *
     * 进入腾讯云云通信[控制台](https://console.cloud.tencent.com/avc ) 创建应用,即可看到 SDKAppId,
     * 它是腾讯云用于区分客户的唯一标识。
     */
    public static final int SDKAPPID = *****在tencentIM控制台中查看;


    /**
     * 签名过期时间,建议不要设置的过短
     * <p>
     * 时间单位:秒
     * 默认时间:7 x 24 x 60 x 60 = 604800 = 7 天
     */
    private static final int EXPIRETIME = 604800;


    /**
     * 计算签名用的加密密钥,获取步骤如下:
     *
     * step1. 进入腾讯云云通信[控制台](https://console.cloud.tencent.com/avc ) ,如果还没有应用就创建一个,
     * step2. 单击“应用配置”进入基础配置页面,并进一步找到“帐号体系集成”部分。
     * step3. 点击“查看密钥”按钮,就可以看到计算 UserSig 使用的加密的密钥了,请将其拷贝并复制到如下的变量中
     *
     * 注意:该方案仅适用于调试Demo,正式上线前请将 UserSig 计算代码和密钥迁移到您的后台服务器上,以避免加密密钥泄露导致的流量盗用。
     * 文档:https://cloud.tencent.com/document/product/269/32688#Server
     */
    private static final String SECRETKEY = "*****在tencentIM控制台中查看";

    /**
     * 计算 UserSig 签名
     *
     * 函数内部使用 HMAC-SHA256 非对称加密算法,对 SDKAPPID、userId 和 EXPIRETIME 进行加密。
     *
     * @note: 请不要将如下代码发布到您的线上正式版本的 App 中,原因如下:
     *
     * 本文件中的代码虽然能够正确计算出 UserSig,但仅适合快速调通 SDK 的基本功能,不适合线上产品,
     * 这是因为客户端代码中的 SECRETKEY 很容易被反编译逆向破解,尤其是 Web 端的代码被破解的难度几乎为零。
     * 一旦您的密钥泄露,攻击者就可以计算出正确的 UserSig 来盗用您的腾讯云流量。
     *
     * 正确的做法是将 UserSig 的计算代码和加密密钥放在您的业务服务器上,然后由 App 按需向您的服务器获取实时算出的 UserSig。
     * 由于破解服务器的成本要高于破解客户端 App,所以服务器计算的方案能够更好地保护您的加密密钥。
     *
     * 文档:https://cloud.tencent.com/document/product/269/32688#Server
     */
    public static String genTestUserSig(String userId) {
        return GenTLSSignature(SDKAPPID, userId, EXPIRETIME, null, SECRETKEY);
    }

    /**
     * 生成 tls 票据
     *
     * @param sdkappid    应用的 appid
     * @param userId      用户 id
     * @param expire      有效期,单位是秒
     * @param userbuf     默认填写null
     * @param priKeyContent 生成 tls 票据使用的私钥内容
     * @return 如果出错,会返回为空,或者有异常打印,成功返回有效的票据
     */
    private static String GenTLSSignature(long sdkappid, String userId, long expire, byte[] userbuf, String priKeyContent) {
        if (StringUtils.isEmpty(priKeyContent)) {
            return "";
        }
        long currTime = System.currentTimeMillis() / 1000;
        JSONObject sigDoc = new JSONObject();
        try {
            sigDoc.put("TLS.ver", "2.0");
            sigDoc.put("TLS.identifier", userId);
            sigDoc.put("TLS.sdkappid", sdkappid);
            sigDoc.put("TLS.expire", expire);
            sigDoc.put("TLS.time", currTime);
        } catch (JSONException e) {
            e.printStackTrace();
        }

        String base64UserBuf = null;
        if (null != userbuf) {
            base64UserBuf = Base64.encodeToString(userbuf, Base64.NO_WRAP);  //使用千牛用库正好有该参数Base64.NO_WRAP
            try {
                sigDoc.put("TLS.userbuf", base64UserBuf);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
        String sig = hmacsha256(sdkappid, userId, currTime, expire, priKeyContent, base64UserBuf);
        if (sig.length() == 0) {
            return "";
        }
        try {
            sigDoc.put("TLS.sig", sig);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        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(base64EncodeUrl(Arrays.copyOfRange(compressedBytes, 0, compressedBytesLength)));
    }


    private static String hmacsha256(long sdkappid, String userId, long currTime, long expire, String priKeyContent, String base64Userbuf) {
        String contentToBeSigned = "TLS.identifier:" + userId + "\n"
                + "TLS.sdkappid:" + sdkappid + "\n"
                + "TLS.time:" + currTime + "\n"
                + "TLS.expire:" + expire + "\n";
        if (null != base64Userbuf) {
            contentToBeSigned += "TLS.userbuf:" + base64Userbuf + "\n";
        }
        try {
            byte[] byteKey = priKeyContent.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 String(Base64.encode(byteSig, Base64.NO_WRAP));
        } catch (UnsupportedEncodingException e) {
            return "";
        } catch (NoSuchAlgorithmException e) {
            return "";
        } catch (InvalidKeyException e) {
            return "";
        }
    }

    private static byte[] base64EncodeUrl(byte[] input) {
        byte[] base64 = new String(Base64.encode(input, Base64.NO_WRAP)).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;
    }

}

controller 提供获取usesrsig(token)

                    //获取tencentIM的Token
                    Map<String, Object> map = new HashMap<>();

                    String mymUserAccount = deviceWxNumber;//"cczzgg11";
                    String tencentIM_userSig = TencentIMGenerateTestUserSig.genTestUserSig(mymUserAccount);
                    map.put("token",tencentIM_userSig);

                    return IJSONResult.ok(map);

为tencentIM 定义TencentIMSendMessageBean(查阅官方文档)

查阅官方文档
消息格式描述

编写controller


/**
 * 参考:SpringBoot(post、get)中获取客户请求信息、消息头、参数;get请求设置参数,使用场景
 * https://blog.csdn.net/qq_41767337/article/details/89144733
 *xuan
 */

@Api(tags = "TencentIM的相关参数调用")
@Controller
@RequestMapping("wechatTask")
public class TencentIMClientController {
    Logger logger = LoggerFactory.getLogger(TencentIMClientController.class);

    @PostMapping("/api_hjzx/doTencentIMTest")
    @ResponseBody
    //@RequestBody  ==采用@RestController 不再需要这个
    public IJSONResult doTencentIMTest(
            @ApiParam(name="content",value="数据内容",required=true) @RequestBody String  content
    ){

        try {

            JSONObject jsonObject = JSON.parseObject(content);
            int wechat_id = jsonObject.getInteger("wechat_id");
            JSONObject jsonObjectTask = jsonObject.getJSONObject("task");
            //int task_task_type = jsonObjectTask.getInteger("task_type");  //null 发送消息;0:好友操作;1:发送朋友圈;99:获取朋友圈数据

            JSONObject jsonObjectTask_task_single_msg = jsonObjectTask.getJSONObject("task_single_msg"); //发送消息txt、img、video、voice、link
            JSONObject jsonObject_task_sns = jsonObjectTask.getJSONObject("task_sns"); //朋友圈数据
            JSONObject jsonObjectTask_task_friends = jsonObjectTask.getJSONObject("task_friends");

            if (jsonObjectTask_task_single_msg !=null){//发送消息
                int task_type = jsonObjectTask_task_single_msg.getInteger("task_type");

                String str_ImMsg = "这是发送的task文本内容";
                switch (task_type){
                    case 0://txt
                        str_ImMsg = str_ImMsg+"==TXT";
                        break;
                    case 1: //图片
                        str_ImMsg = str_ImMsg+"==img";
                        break;
                    case 2: //voice
                        str_ImMsg = str_ImMsg+"==voice";
                        break;
                    case 3: //video
                        str_ImMsg = str_ImMsg+"==video";
                        break;


                }
                TencentIMSendMessageBean sendMessageBean = new TencentIMSendMessageBean();
                sendMessageBean.setMsgLifeTime(600 * 10);
                sendMessageBean.setSyncOtherMachine(2);
                String account =null ;
                if (wechat_id ==69){
                    account = "cczzgg";

                }
                sendMessageBean.setTo_Account(account);
                int intFlag = (int) (Math.random() * 100000000);
                sendMessageBean.setMsgRandom(intFlag);
                sendMessageBean.setMsgTimeStamp((int) (new Date().getTime() / 1000));
                TencentIMSendMessageBean.MsgBodyBean msgBodyBean = new TencentIMSendMessageBean.MsgBodyBean();
                msgBodyBean.setMsgType("TIMTextElem");  //设置为txt


                //设置消息类型 + 消息内容
                TencentIMSendMessageBean.MsgBodyBean.MsgContentBean msgContentBean = new TencentIMSendMessageBean.MsgBodyBean.MsgContentBean();
                msgContentBean.setText(str_ImMsg);
                msgBodyBean.setMsgContent(msgContentBean);



                //添加到消息的List中去
                sendMessageBean.getMsgBody().add(msgBodyBean);


                RestTemplate restTemplate = new RestTemplate();
                //rpc调用
                restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
                String myAdministrator = "administrator";
                String UserSig = TencentIMGenerateTestUserSig.genTestUserSig(myAdministrator);

                String tencentResult = restTemplate.postForObject("https://console.tim.qq.com/v4/openim/sendmsg?usersig=" + UserSig + "&apn=1&identifier="+myAdministrator+"&sdkappid="
                        + TencentIMGenerateTestUserSig.SDKAPPID + "&contenttype=json", JSONObject.toJSONString(sendMessageBean), String.class);

                //如果有测试号,则也发送一份给测试号
                String testAccount = "cczzgg11";  //测试好
                if(StringUtils.isNotEmpty(testAccount)&&!testAccount.equals(myAdministrator)){
                    sendMessageBean.setTo_Account(testAccount);
                    String tencentResult_forTestAccount =restTemplate.postForObject("https://console.tim.qq.com/v4/openim/sendmsg?usersig=" + UserSig + "&apn=1&identifier="+myAdministrator+"&sdkappid="
                            + TencentIMGenerateTestUserSig.SDKAPPID
                            + "&contenttype=json", JSONObject.toJSONString(sendMessageBean), String.class);
                    logger.error(tencentResult_forTestAccount);
                }
                logger.error(tencentResult);




                return IJSONResult.ok("发送完成,其他逻辑正在编写代码中。。。。");


            }

            if (jsonObject_task_sns != null){
                return IJSONResult.ok("正在编写代码中。。。。");

            }
            if (jsonObjectTask_task_friends != null){
                return IJSONResult.ok("正在编写代码中。。。。");

            }


            //可以添加处理逻辑
        } catch (Exception e) {
            logger.error("doTencentIMTest  body 中的参数参数失败");


        }
        return  IJSONResult.errorMsg("还没支撑的任务类型");





    }


}

postman测试

image.png

tencentIM官方demo收到消息

tencentIM官方demo收到消息

tencentIM的回调方式处理--登录状态或发送消息监控

获取签名UserSig后,辅助工具做一下检测,是否生成正确

校验UserSig的生成规则

其他问题

在做app开发是使用wifi碰到tencentIm错误,查阅官方文档
tencentIM :code=9519 ;desc=packet in queue but no available channel
原因1:我用postman对同一个账号重新获取UserSign,造成app缓存的UserSig失效。(遗憾的是,tencentIM给的错误是9519 晕死,搞了老半天)

原因2:TencentIM_SDKAppID错误也是这样提示

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271