云通信相关资料下载地址
https://share.weiyun.com/2b3abe0e3f185c440cf455647455f661
Java demo
根据官方提供的demo做了调整,更符合spring-boot的风格
import java.util.Map;
/**
* Created by saleson on 2017/10/24.
*/
public class IMActionResult {
private Map<String, Object> result;
public IMActionResult(Map<String, Object> result) {
this.result = result;
}
public boolean isSuccess() {
return "OK".equals(getActionStatus());
}
public String getActionStatus() {
return (String) result.get("ActionStatus");
}
public String getErrorInfo() {
return (String) result.get("ErrorInfo");
}
public int getErrorCode() {
return (int) result.get("ErrorCode");
}
public Map<String, Object> getResult() {
return result;
}
public Object getObject(String name) {
return result.get(name);
}
public <T> T get(String name) {
return (T) result.get(name);
}
}
/**
* Created by saleson on 2017/10/23.
*/
public class TencentIMConfig {
private String sdkAppid;
private String jnisigcheckLibPath;
private String privateKeyPath;
private String privateKey;
private String defaultImAdminAccount;
public String getSdkAppid() {
return sdkAppid;
}
public void setSdkAppid(String sdkAppid) {
this.sdkAppid = sdkAppid;
}
public String getJnisigcheckLibPath() {
return jnisigcheckLibPath;
}
public void setJnisigcheckLibPath(String jnisigcheckLibPath) {
this.jnisigcheckLibPath = jnisigcheckLibPath;
}
public String getPrivateKeyPath() {
return privateKeyPath;
}
public void setPrivateKeyPath(String privateKeyPath) {
this.privateKeyPath = privateKeyPath;
}
public String getPrivateKey() {
return privateKey;
}
public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
}
public String getDefaultImAdminAccount() {
return defaultImAdminAccount;
}
public void setDefaultImAdminAccount(String defaultImAdminAccount) {
this.defaultImAdminAccount = defaultImAdminAccount;
}
}
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Created by saleson on 2017/10/23.
*/
public class IMActionResponse {
@JsonProperty("ActionStatus")
private String ActionStatus;
@JsonProperty("ErrorInfo")
private String ErrorInfo;
@JsonProperty("ErrorCode")
private int ErrorCode;
public boolean isSuccess() {
return "OK".equals(ActionStatus);
}
public String getActionStatus() {
return ActionStatus;
}
public void setActionStatus(String actionStatus) {
ActionStatus = actionStatus;
}
public String getErrorInfo() {
return ErrorInfo;
}
public void setErrorInfo(String errorInfo) {
ErrorInfo = errorInfo;
}
public int getErrorCode() {
return ErrorCode;
}
public void setErrorCode(int errorCode) {
ErrorCode = errorCode;
}
}
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
/**
* Created by saleson on 2017/10/23.
*/
public class IMMultiAccImportResponse extends IMActionResponse {
@JsonProperty("FailAccounts")
private List<String> FailAccounts;
public List<String> getFailAccounts() {
return FailAccounts;
}
public void setFailAccounts(List<String> failAccounts) {
FailAccounts = failAccounts;
}
}
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.tls.sigcheck.tls_sigcheck;
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* Created by saleson on 2017/10/23.
*/
public class TencentIMHelper {
private static final Logger log = LoggerFactory.getLogger(TencentIMHelper.class);
private TencentIMConfig config;
private RestTemplate rest;
private ObjectMapper objectMapper;
private Joiner.MapJoiner joiner = Joiner.on("&").withKeyValueSeparator("=");
public TencentIMHelper(TencentIMConfig config, RestTemplate rest, ObjectMapper objectMapper) {
this.config = config;
this.rest = rest;
this.objectMapper = objectMapper;
}
/**
* 生成usersig
*
* @param identifier
* @return
*/
public String genUsersig(String identifier) {
tls_sigcheck demo = new tls_sigcheck();
// 使用前请修改动态库的加载路径
demo.loadJniLib(config.getJnisigcheckLibPath());
int ret = demo.tls_gen_signature_ex2(config.getSdkAppid(), identifier, config.getPrivateKey());
if (0 != ret) {
log.error("ret: {}, errMsg:{}", ret, demo.getErrMsg());
throw new ClientException(ResultCode.IM_ACCOUNT_LOGIN_FAILD);
} else {
String usersig = demo.getSig();
System.out.println("sig:\n" + demo.getSig());
log.debug("identifier '{}' take usersig is {}", identifier, usersig);
return usersig;
}
}
/**
* 获取默认设置的im admin 账号的usersig
*
* @return
*/
public String getIMAdminUsersig() {
if (StringUtils.isEmpty(config.getDefaultImAdminAccount())) {
log.error("TencentIMConfig.defaultImAdminAccount不存在");
throw new ServerException(ResultCode.IM_ACCOUNT_LOGIN_FAILD.getResultCode(), "默认的IM admin账号不存在");
}
return getIMAdminUsersig(config.getDefaultImAdminAccount());
}
/**
* 获取im admin 账号的usersig
*
* @param identifier
* @return
*/
public String getIMAdminUsersig(String identifier) {
String usersig = _getIMAdminUsersig(identifier);
if (StringUtils.isEmpty(usersig)) {
usersig = genUsersig(identifier);
cacheIMAdminUsersig(identifier, usersig);
}
return usersig;
}
/**
* 导入账号
*
* @param accountId
*/
public void accountImport(String accountId) {
String url = "https://console.tim.qq.com/v4/im_open_login_svc/account_import?";
String queryString = joiner.join(getDefaultParams());
Map<String, String> requestBody = ImmutableMap.of("Identifier", accountId);
IMActionResponse res = request(url + queryString, requestBody, IMActionResponse.class);
if (!res.isSuccess()) {
log.error("导入'{}'到腾讯云IM失败, response message is: {}", toString(res));
throw new ClientException(ResultCode.IM_ACCOUNT_CREATE_FAILD);
}
}
/**
* 帐号批量导入
*
* @param accountIds 用户名,单个用户名长度不超过 32 字节,单次最多导入100个用户名
* @return 返回导入失败的帐号列表
*/
public List<String> multiAccountImport(String... accountIds) {
String url = "https://console.tim.qq.com/v4/im_open_login_svc/multiaccount_import?";
String queryString = joiner.join(getDefaultParams());
Map<String, List<String>> requestBody = ImmutableMap.of("Accounts", Arrays.asList(accountIds));
IMMultiAccImportResponse res = request(url + queryString, requestBody, IMMultiAccImportResponse.class);
if (!res.isSuccess()) {
log.error("帐号批量导入失败:{}, response message is: {}",
StringUtils.join(accountIds, ", "),
toString(res));
throw new ClientException(ResultCode.IM_ACTION_FAILD);
}
return res.getFailAccounts();
}
/**
* 设置用户资料(IM)
*
* @param userDTO
*/
public void setAccountProfile(UserDTO userDTO) {
String url = "https://console.tim.qq.com/v4/profile/portrait_set?";
String queryString = joiner.join(getDefaultParams());
String nikename = userDTO.getNickName()
int gder = ObjectUtils.defaultIfNull(userDTO.getGender(), 0);
String gender = gder == 1 ? "Gender_Type_Male" : gder == 2 ? "Gender_Type_Female" : "Gender_Type_Unknown";
Map<String, Object> requestBody = ImmutableMap.of("From_Account", userDTO.getLogin(),
"ProfileItem", ImmutableList.of(
ofProPortItem("Tag_Profile_IM_Nick", nikename),
ofProPortItem("Tag_Profile_IM_Gender", gender),
ofProPortItem("Tag_Profile_IM_Image",
ResourceUtils.getFullResourceUrl(userDTO.getAvatar()))
// ofProPortItem("", ""),
// ofProPortItem("", ""),
// ofProPortItem("", ""),
// ofProPortItem("", "")
));
IMActionResult res = request(url + queryString, requestBody);
if (!res.isSuccess()) {
log.error("调用'{}'的账号信息到腾讯云IM失败, response message is: {}",
userDTO.getLogin(), toString(res.getResult()));
throw new ClientException(ResultCode.IM_ACCOUNT_INFO_SET_FAILD);
}
}
/**
* @param tag
* @param value
* @return
*/
private Map<String, Object> ofProPortItem(String tag, Object value) {
return ImmutableMap.of("tag", tag, "value", value);
}
/**
* 默认值
*
* @param obj
* @param def
* @return
*/
private Object defNull(Object obj, Object def) {
return obj == null ? def : obj;
}
/**
* 返回默认的参数
*
* @return
*/
private Map<String, String> getDefaultParams() {
Map<String, String> pathParams = Maps.newHashMap();
pathParams.put("usersig", getIMAdminUsersig());
pathParams.put("identifier", config.getDefaultImAdminAccount());
pathParams.put("sdkappid", config.getSdkAppid());
pathParams.put("random", StringUtils.uuid());
pathParams.put("contenttype", "json");
return pathParams;
}
private void initPrivateKey() {
if (StringUtils.isNotEmpty(config.getPrivateKey())) {
return;
}
if (StringUtils.isEmpty(config.getPrivateKeyPath())) {
throw new ServerException(ResultCode.OPERATION_FAILD.getResultCode(), "腾讯云通讯无法找到privateKey");
}
//todo
}
private <T> T request(String url, Object params, Class<T> cls) {
return toBean(requestInvoke(url, params), cls);
}
private IMActionResult request(String url, Object params) {
Map<String, Object> map = toBean(requestInvoke(url, params),
new TypeReference<Map<String, Object>>() {
});
return new IMActionResult(map);
}
private String requestInvoke(String url, Object params) {
String json = rest.postForObject(url, params, String.class);
try {
log.info("request url {}, the params is: {}, and response: {}", url, objectMapper.writeValueAsString(params), json);
} catch (IOException e) {
}
return json;
}
private <T> T toBean(String json, Class<T> cls) {
try {
return objectMapper.readValue(json, cls);
} catch (IOException e) {
log.error("json:{} 转换类型失败: {} ", json, cls);
log.error(e.getMessage(), e);
throw new ServerException(ResultCode.OPERATION_FAILD.getResultCode(), "转换json失败");
}
}
private <T> T toBean(String json, TypeReference<T> type) {
try {
return objectMapper.readValue(json, type);
} catch (IOException e) {
log.error("json:{} 转换类型失败: {} ", json, type.getType());
log.error(e.getMessage(), e);
throw new ServerException(ResultCode.OPERATION_FAILD.getResultCode(), "转换json失败");
}
}
private String _getIMAdminUsersig(String identifier) {
//todo
return null;
}
private void cacheIMAdminUsersig(String identifier, String usersig) {
//todo
}
private String toString(Object obj) {
if (obj == null) {
return null;
} else if ("".equals(obj)) {
return "";
}
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
return obj.toString();
}
}
}
Test
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tls.sigcheck.tls_sigcheck;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
/**
* Created by saleson on 2017/10/23.
*/
public class TencentIMTest {
@Test
public void testHelper() throws IOException {
TencentIMConfig config = new TencentIMConfig();
config.setDefaultImAdminAccount("testadmin");
config.setSdkAppid("1400046172");
//config.setJnisigcheckLibPath("tencent_im/tls_sig_api-src/src/jnisigcheck.so");
config.setJnisigcheckLibPath("tencent_im/jnisigcheck_mt_x64.so");
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("thirdparty/tencent/im/private_key");
BufferedReader br = new BufferedReader(new InputStreamReader(is));
StringBuilder strBuilder = new StringBuilder();
String s = "";
while ((s = br.readLine()) != null) {
strBuilder.append(s + '\n');
}
br.close();
String priKey = strBuilder.toString();
config.setPrivateKey(priKey);
// config.setPrivateKeyPath("");
// RestTemplate rest = new RestTemplate();
// rest.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter()));
RestTemplate rest = jsonRestTemplate();
TencentIMHelper helper = new TencentIMHelper(config, rest, new ObjectMapper());
String adminUsersig = helper.getIMAdminUsersig();
System.out.println("adminUsersig:\n" + adminUsersig);
helper.accountImport("test10");
List<String> list = helper.multiAccountImport("test2jalsdkfjaoisdufo32jkleoflkjasdkfjasdiufowerquwlkj0", "test21", "test22", "test23");
System.out.println(list);
}
public RestTemplate jsonRestTemplate() {
RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());
restTemplate.getMessageConverters().add(0, new FastJsonHttpMessageConverter());
restTemplate.getInterceptors().add((request, body, execution) -> {
System.out.println(new String(body));
ClientHttpResponse response = execution.execute(request, body);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
return response;
});
return restTemplate;
}
private ClientHttpRequestFactory clientHttpRequestFactory() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setReadTimeout(30000);
factory.setConnectTimeout(30000);
return factory;
}
}
上面的代码在linux环境上是可能正常运行了。
但是要在mac上运行调试, 会报错:
java.lang.UnsatisfiedLinkError: /Users/saleson/Downloads/tencent_im/jnisigcheck_mt_x64.so: dlopen(/Users/saleson/Downloads/tencent_im/jnisigcheck_mt_x64.so, 1): no suitable image found. Did find:
/Users/saleson/Downloads/tencent_im/jnisigcheck_mt_x64.so: unknown file type, first eight bytes: 0x7F 0x45 0x4C 0x46 0x02 0x01 0x01 0x00
/Users/saleson/Downloads/tencent_im/jnisigcheck_mt_x64.so: unknown file type, first eight bytes: 0x7F 0x45 0x4C 0x46 0x02 0x01 0x01 0x00
at java.lang.ClassLoader$NativeLibrary.load(Native Method)
at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1941)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1824)
at java.lang.Runtime.load0(Runtime.java:809)
at java.lang.System.load(System.java:1086)
at com.tls.sigcheck.tls_sigcheck.loadJniLib(tls_sigcheck.java:9)
at com.thirdparty.tencent.im.TencentIMHelper.genUsersig(TencentIMHelper.java:48)
at com.thirdparty.tencent.im.TencentIMHelper.getIMAdminUsersig(TencentIMHelper.java:85)
at com.thirdparty.tencent.im.TencentIMHelper.getIMAdminUsersig(TencentIMHelper.java:72)
at com.thirdparty.TencentIMTest.testHelper(TencentIMTest.java:123)
因此在mac上,需要重新编译生成jnisigcheck.so文件。
编译jnisigcheck.so
下载并解压tls_sig_api-src.tar.gz
进入src目录,查看Makefile
$ tar -zxvf tls_sig_api_src.tar.gz
$ cd tls_sig_api-src/src
$ vim Makefile
Makefile
CPP = g++
CC = gcc
ARCH=$(shell getconf LONG_BIT)
CFLAGS= -g -I../include -I../include/tls_sig_api -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux -Wall -fPIC
LIBS= ../linux/$(ARCH)/lib/jsoncpp/libjsoncpp.a ../openssl-dynamic/lib/libcrypto.a -ldl -lz
TARGETS = libtlsignature.a sigcheck.so signature
all: $(TARGETS)
libtlsignature.a: tls_signature.o multi_thread.o base64.o base64_url.o
ar -rs $@ $^
# jni 库编译前请安装 jdk,并且配置 JAVA_HOME 环境变量
jni: com_tls_sigcheck_tls_sigcheck.o libtlsignature.a
g++ -shared -o jnisigcheck.so $^ ../linux/$(ARCH)/lib/jsoncpp/libjsoncpp.a ../openssl-dynamic/lib/libcrypto.a -lz
signature: signature.o libtlsignature.a
g++ -o $@ $^ ../linux/$(ARCH)/lib/jsoncpp/libjsoncpp.a ../openssl-dynamic/lib/libcrypto.a -ldl -lz
sigcheck.so: sigcheck.o libtlsignature.a sigcheck.h
g++ -shared -fPIC -o $@ $^ ../linux/$(ARCH)/lib/jsoncpp/libjsoncpp.a ../openssl-dynamic/lib/libcrypto.a -ldl -lz
.cpp.o:
$(CPP) $(CFLAGS) -c $*.cpp
.c.o:
$(CC) $(CFLAGS) -c $*.c
clean:
-rm -f *.o *.so *.a tls_licence_tools TAGS $(TARGETS)
可以看到使用make jni生成jnisigcheck.so。执行命令之后,并没有像我们想象那样生成了jnisigcheck.so,需要解决部分依赖库问题:jni_md.h,代码中的../linux/$(ARCH)/lib/jsoncpp/libjsoncpp.a ($(ARCH)=64)和../openssl-dynamic/lib/libcrypto.a文件缺失问题。
解决问题
解决jni_md.h的问题(复制jni_md.h)
$ cd $JAVA_HOME/include
$ sudo cp darwin/jni_md.h .
编译jsoncpp
$ brew install scons
$ cd tls_sig_api-src/jsoncpp-src-0.5.0
$ python /usr/local/bin/scons platform=linux-gcc
执行的结果是在libs目录下生成了
linux-gcc-4.2.1/libjson_linux-gcc-4.2.1_libmt.a
linux-gcc-4.2.1/libjson_linux-gcc-4.2.1_libmt.dylib
编译openssl
// 进入tls_sig_api-src目录
$ tar -zxvf openssl-1.0.2a.tar.gz
$ mv openssl-1.0.2a openssl_x86_64
$ cd openssl_x86_64
$ ./Configure darwin64-x86_64-cc -shared
$ make
这就在当前目录生成了libcrypto.a
创建软链
进入tls_sig_api-src/src
$ mkdir -p ../linux/64/lib/jsoncpp
$ ln -s `cd ..;pwd`/jsoncpp-src-0.5.0/libs/linux-gcc-4.2.1/libjson_linux-gcc-4.2.1_libmt.a ../linux/64/lib/jsoncpp/libjsoncpp.a
$ mkdir -p ../openssl-dynamic/lib/
$ ln -s `cd ..;pwd`/openssl_x86_64/libcrypto.a ../openssl-dynamic/lib/libcrypto.a
创建link的时候,source需要填写绝对路径,所以才有上述cd ..;pwd。
编译jnisigcheck.so
//在src目录下,执行一下命令,则生成`jnisigcheck.so`
$ make clean
$ make jni
这时在src目录下就有jnisigcheck.so文件了,
将jnisigcheckLibPath改为”tls_sig_api-src/src/jnisigcheck.so”,
就可以在mac上运行了。