最近接一个sdk,也就是蚂蚁金服零号云客服的一个自定义卡片的功能
一、准备
- 官方文档
- 加密解密
- jar依赖
二、代码
2.1、背景
卡片信息有两种展示,这里使用自带的接口方式展示吧
- 客户端窗口
- 客服这边
此时自定义信息那边都是默认的信息,现在我们想要加上玩家id,玩家姓名和等级
步骤:出于安全考虑,阿里云那边采用加密的方式来传输用户信息。首先由后端java对访客的真实信息进行加密,然后将加密后的信息传给阿里云,再进行解密,最后再展示出解密后的访客信息。
相关依赖以及官方文档在 [这里]
https://help.aliyun.com/document_detail/68241.html?spm=a2c4g.11186623.6.585.1471525cV9FlSF
2.2、依赖
<!-- 这里是引入了本地的jar包 这个jar包可以联系云客服索要 -->
<dependency>
<groupId>com.alipay.fc.csplatform</groupId>
<artifactId>fccsplatform-common-crypto</artifactId>
<version>1.0.0.20161108</version>
<scope>system</scope>
<systemPath>${pom.basedir}/lib/fccsplatform-common-crypto-1.0.0.20161108.jar</systemPath>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
pom.xml中再加上这个,免得打包的时候不能同时带上jar包
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/classes/lib</outputDirectory>
<includeScope>compile</includeScope>
</configuration>
</execution>
</executions>
</plugin>
2.3、代码
@CrossOrigin
@Controller
@RequestMapping("/api")
@Slf4j
public class GmExclusiveCustomerServicePopupApiController extends BaseController {
// 公钥
// @Value("${business_card_key}")
private static String pub_key = "MIIBIjANBgkqhkiG9w********************* ";
/**
* 阿里云云客服URL
*/
private static final String HOST_URL = "https://cschat.antcloud.com.cn/index.htm";
/**
* 租户 ID,由零号云客服提供
*/
private static final String tntInstId = "*************";
/**
* 聊天窗编码,由零号云客服提供。(天天)
*/
private static final String scene = "*************";
/**
* 对访客信息进行加密, 聊天窗埋点的时候, 生成埋点URL中的cinfo和key参数 使用此方法.
*
* @param request 用户名、用户信息
* @param response url地址
* @return
*/
@ResponseBody
@RequestMapping(value = "/encryptCard", method = RequestMethod.GET)
public R encryptCard(HttpServletRequest request, HttpServletResponse response) {
try {
String userId = request.getParameter("userId") == null ? "" : request.getParameter("userId");
String userName = request.getParameter("userName") == null ? "游客" : request.getParameter("userName");
String vip = request.getParameter("vip") == null ? "" : request.getParameter("vip");
log.info("userName玩家名称:" + userName);
// 个人信息
JSONObject extInfo = new JSONObject();
extInfo.put("userId", userId);
extInfo.put("userName", userName);
extInfo.put("vip", vip);
JSONObject cinfo = new JSONObject();
cinfo.put("tel", "");
cinfo.put("extInfo", extInfo);
cinfo.put("userId", userId);
cinfo.put("cardId", "");
cinfo.put("timestamp", System.currentTimeMillis());
//生成公钥和私钥,公钥加密,私钥解密
log.info("1cinfo:" + cinfo);
PublicKey publicKey = getPubKey();
log.info("2publicKey:" + publicKey);
Map<String, String> map = CustomerInfoCryptoUtil.encryptByPublicKey(cinfo.toString(), publicKey);
log.info(cinfo.toString() + "\t加密后的字符串为:" + map);
//样例输出, 由于加密含有随机内容, 每次输入不会相同
StringBuilder stringBuilder = new StringBuilder(HOST_URL);
stringBuilder.append("?tntInstId=" + tntInstId);
stringBuilder.append("&scene=" + scene);
stringBuilder.append("&key=" + map.get("key"));
stringBuilder.append("&cinfo=" + map.get("text"));
String url = stringBuilder.toString();
log.info("url地址:" + url);
return R.ok(url);
} catch (Exception e) {
log.error(e.toString());
return R.error("服务器异常:"+e.toString());
}
}
/**
* 通过外部接口方式接入访客名片.
* 工作台显示访客信息, 请使用此方法.
* note : 本接口必须支持跨域访问.
*
* @param request params消息体 和 key 公钥
* @param response
* @return
*/
@ResponseBody
@RequestMapping(value = "/decryptCard")
public JSONObject decryptCard(HttpServletRequest request, HttpServletResponse response) {
try {
//这两个参数是云客服工作台调用该接口 post body带入的参数.
String params = request.getParameter("params");
String key = request.getParameter("key");
log.error("1、回调参数params:"+ params + "key:" + key);
log.error("2、回调参数key:"+ key);
if (org.springframework.util.StringUtils.isEmpty(params) && org.springframework.util.StringUtils.isEmpty(key)) {
log.error("名片参数为空!");
}
//还原出RSA公钥对象
PublicKey publicKey = getPubKey();
String cinfo = CustomerInfoCryptoUtil.decryptByPublicKey(URLEncoder.encode(params, "UTF-8"), URLEncoder.encode(key,"UTF-8"), publicKey);
log.error("3、还原后的字符串为:" + cinfo);
JSONObject jsonObject = JSONObject.parseObject(cinfo);
JSONObject extInfo = jsonObject.getJSONObject("extInfo");
String userId = extInfo.getString("userId");
String userName = extInfo.getString("userName");
String vip = extInfo.getString("vip");
log.error("3、还原后的字符串为:" + cinfo);
//查询到的用户信息按照下面格式拼凑成一个json格式内容, 返回. 请严格按照下面结构形态。note : 自定义参数要在schema中和result下面一一对应.
String mockResult = "{\n" +
" \"message\": \"查询成功\", //成功的消息\n" +
" \"success\": true, //表示查询成功\n" +
" \"result\": { //下面是自定义用户信息内容\n" +
" \"userId\": \""+userName+"\",\n" +
" \"userName\": \""+userId+"\",\n" +
" \"vip\": \""+vip+"\",\n" +
" \"schema\": { //会按照 schema 的结构来展示用户信息\n" +
" \"properties\": {\n" +
" \"userId\": {\n" +
" \"name\": \"userId\",\n" +
" \"type\": \"text\"\n" +
" },\n" +
" \"userName\": {\n" +
" \"name\": \"userName\",\n" +
" \"type\": \"text\"\n" +
" },\n" +
" \"vip\": {\n" +
" \"name\": \"vip\",\n" +
" \"type\": \"text\"\n" +
" },\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
log.error("返回的信息:"+mockResult);
return JSONObject.parseObject(mockResult);
} catch (Exception e) {
//这里建议使用log记录错误. 减少干扰信息, DEMO里不使用LOG
e.printStackTrace();
return null;
}
}
//还原出 RSA 公钥对象
private static PublicKey getPubKey() throws Exception {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64Util.decode(pub_key));
KeyFactory keyFactory;
keyFactory = KeyFactory.getInstance("RSA");
PublicKey key = keyFactory.generatePublic(keySpec);
return key;
}
}
注意:该Controller中的第二个接口必须POST
该接口必须支持跨域请求。
接口协议必须是https。
响应头中 Content-Type 为 application/json; charset=utf-8。
另外:第一个解密信息放在cinfo,然后回调的时候必须用https并且post请求和网址形式。还有在回调的时候你获取的这个key实则他经过转换成私钥了,保证了公钥加密,私钥解密的原则
三、配置回调地址
四、测试
基金网址:xiu.idearyou.cn
后言
本文章来自自己的博客:纯洁的麦田
公众号:纯洁的麦田
百度:纯洁的麦田
新鲜的面试宝典和技术分享和教你玩基金赚钱
欢迎收藏访问