技术标签: Java 第三方OA 项目 华为云WeLink 租户预置 参数解析 响应体签名
2019年12月到 2020年04月,小编参与到公司项目与第三方OA的对接,即华为云WeLink市场的对接工作,华为云市场、WeLink市场两部分对接工作,我负责华为云市场项目对接接口、另一个同事负责WeLink对接。主要经历了四个阶段:熟悉第三方文档需求及相关业务、研发、测试、上线并解决问题。
第三方接口调用、开通第三方租户(租户包括本项目、第三方)
熟悉整体业务流程图
项目分工
小编负责:调试华为云市场生产接口(5个)、开通租户成功后发送消息(同事进行通讯录-组织结构人员同步)
新购商品(5个接口共用一个接口,内部逻辑判断)
商品续费
商品过期
商品资源释放
商品升级
调试过程中,不断熟悉接口定义,以及业务需求。
/**
* 校验通知消息的合法性
*
* @param request http请求通知消息
* @param accessKey 接入码
* @param encryptLength 加密长度
* @return 验证结果
*/
public static ResponseMessage verificateRequestParams(HttpServletRequest request, String accessKey, int encryptLength) {
//解析出url内容
Map<String, String[]> paramsMap = new HashMap<>(request.getParameterMap());
String timeStamp = null;
String authToken = null;
String[] timeStampArray = paramsMap.get("timeStamp");
if (null != timeStampArray && timeStampArray.length > 0) {
timeStamp = timeStampArray[0];
}
String[] authTokenArray = paramsMap.remove("authToken");
if (null != authTokenArray && authTokenArray.length > 0) {
authToken = authTokenArray[0].replace(" ", "+");
}
//对剩下的参数进行排序,拼接成加密内容
Map<String, String[]> sortedMap = new TreeMap<>();
sortedMap.putAll(paramsMap);
StringBuffer strBuffer = new StringBuffer();
Set<String> keySet = sortedMap.keySet();
Iterator<String> iter = keySet.iterator();
while (iter.hasNext()) {
String key = iter.next();
String value = sortedMap.get(key)[0];
if (value.contains(" ")) {
value = value.replace(" ", "+");
}
strBuffer.append("&").append(key).append("=").append(value);
}
//修正消息体,去除第一个参数前面的&
String reqParams = strBuffer.toString().substring(1);
String key = accessKey + timeStamp;
String signature;
try {
signature = generateResponseBodySignature(key, reqParams);
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | UnsupportedEncodingException e) {
// TODO Auto-generated catch block
return ResponseMessage.buildResponse(CodeUtils.OTHER_ERROR, "其它服务内部错误");
}
if (!authToken.equals(signature)) {
return ResponseMessage.buildResponse(CodeUtils.FAIL_AUTH, "鉴权失败");
}
return ResponseMessage.buildResponse(CodeUtils.SUCCESS, "验证消息成功");
}
(2)消息体签名
/**
* 生成消息体签名
*
* @param key 用户在isv console分配的accessKey,请登录后查看
* @param body http响应的报文
* @return 加密结果
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
* @throws IllegalStateException
* @throws UnsupportedEncodingException
*/
public static String generateResponseBodySignature(String key, String body) throws InvalidKeyException, NoSuchAlgorithmException, IllegalStateException, UnsupportedEncodingException {
return base_64(hmacSHA256(key, body));
}
(3)字节数组转字符串
/**
* 字节数组转字符串
*
* @param bytes 字节数组
* @return 字符串
*/
public static String base_64(byte[] bytes) {
return new String(Base64.encodeBase64(bytes));
}
/**
* Http Body签名
*
* @param responseMessage
* @param key
* @param response
* @return
*/
public static void setBodySign(ResponseMessage responseMessage, String key, HttpServletResponse response) throws IOException {
// 消息体签名
String bodySignature = null;
String httpBody = JSONObject.toJSONString(responseMessage);
try {
bodySignature = generateResponseBodySignature(key, httpBody);
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | UnsupportedEncodingException e) {
log.error("error:" + e);
}
// 添加消息体签名
signType = "HMAC-SHA256";
StringBuffer bodySign = new StringBuffer();
bodySign.append("sign_type=").append('"').append(signType).append('"').append(',').append("signature=")
.append('"').append(bodySignature).append('"');
response.setHeader("Body-Sign", bodySign.toString());
log.info("bodySign " + response.getHeader("Body-Sign"));
}
3.一个通用的生产接口
(1)消息验证合法性、响应体消息签名、获取解析不同订单参数
@GetMapping(value = "/activity")
public ResponseMessage Tenancy(HttpServletRequest request, HttpServletResponse response) throws IOException {
ResponseMessage responseMessage = EncDesCode.verificateRequestParams(request, CodeUtils.KEY, CodeUtils.ENCRYPT_LENGTH);
String resultCode = responseMessage.getResultCode();
// 转换订单实体
OrderInfoBean orderInfoBean = EncDesCode.revertRequst(request);
// 区分不同的activity订单行为
if (resultCode.equals(CodeUtils.SUCCESS)) {
switch (orderInfoBean.getActivity()) {
case CodeUtils.ACTIVITY_NEW:
responseMessage = orderService.newInstance(orderInfoBean);
break;
case CodeUtils.ACTIVITY_REFRESH:
responseMessage = orderService.refreshInstance(orderInfoBean);
break;
case CodeUtils.ACTIVITY_EXPIRE:
responseMessage = orderService.expireInstance(orderInfoBean);
break;
case CodeUtils.ACTIVITY_RELEASE:
responseMessage = orderService.releaseInstance(orderInfoBean);
break;
case CodeUtils.ACTIVITY_UPGRADE:
responseMessage = orderService.upgrade(orderInfoBean);
break;
default:
log.info("default");
}
EncDesCode.setBodySign(responseMessage, CodeUtils.KEY, response);
}
return responseMessage;
}
(2)获取request中的参数,保存参数到自定义类OrderInfoBean
/**
* 获取request中的参数
*
* @param request
* @return
*/
public static OrderInfoBean revertRequst(HttpServletRequest request) {
Map<String, String[]> paramMap = request.getParameterMap();
Map<String, String> params = new HashMap<>();
OrderInfoBean orderInfoBean;
for (String key : paramMap.keySet()) {
params.put(key, paramMap.get(key)[0]);
}
for (String param : params.keySet()) {
params.get(param);
}
orderInfoBean = map2Bean(params, OrderInfoBean.class);
//获取request中加密的参数值
return decryptParams(orderInfoBean);
}
/**
* Map转换层Bean,使用泛型免去了类型转换的麻烦。
*
* @param <T>
* @param map
* @param myClass
* @return
*/
private static <T> T map2Bean(Map<String, String> map, Class<T> myClass) {
T bean = null;
try {
bean = myClass.newInstance();
BeanUtils.populate(bean, map);
} catch (InstantiationException e) {
log.error("error:" + e);
} catch (IllegalAccessException e) {
log.error("error:" + e);
} catch (InvocationTargetException e) {
log.error("error:" + e);
}
return bean;
}
/**
* 解密参数
*
* @return
*/
private static OrderInfoBean decryptParams(OrderInfoBean orderInfoBean) {
String saasExtendParams = orderInfoBean.getSaasExtendParams();
String mobilePhone = orderInfoBean.getMobilePhone();
if (StringUtils.isNotEmpty(saasExtendParams)) {
saasExtendParams = saasExtendParams.replace(" ", "+");
saasExtendParams = base_64Decode(saasExtendParams.getBytes());
log.info("DES saasExtendParams:" + saasExtendParams);
String[] params = JSON.parseObject(saasExtendParams, String[].class);
ExtendParam extendParam;
WeLinkTenantInfo weLinkTenantInfo;
log.info("saasExtendParams:{}", params);
for (String paramObject : params) {
extendParam = JSONObject.parseObject(paramObject, ExtendParam.class);
if (extendParam.getName().equals(CodeUtils.CONTACT_NAME)) {
orderInfoBean.setContactName(extendParam.getValue());
orderInfoBean.setTenantType(CodeUtils.TENANT_TYPE_HUAWEIYUN);
}
if (extendParam.getName().equals(CodeUtils.COMPANY)) {
orderInfoBean.setCompanyName(extendParam.getValue());
}
// WeLink开放平台开发的商品所需参数
if (extendParam.getName().equals(CodeUtils.PLATFORM_PARAMS)) {
weLinkTenantInfo = JSONObject.parseObject(extendParam.getValue(), WeLinkTenantInfo.class);
orderInfoBean.setTenantType(CodeUtils.TENANT_TYPE_WELINK);
log.info("weLinkTenantInfo:{}",weLinkTenantInfo);
orderInfoBean.setTenantId(weLinkTenantInfo.getTennantId());
orderInfoBean.setTenantName(weLinkTenantInfo.getTenantName());
orderInfoBean.setWeLinkUserId(weLinkTenantInfo.getUserId());
}
}
log.info("orderInfoBean:" + orderInfoBean);
}
if (StringUtils.isNotEmpty(mobilePhone)) {
mobilePhone = mobilePhone.replace(" ", "+");
orderInfoBean.setAdminAccountAES(mobilePhone);
mobilePhone = decryptMobilePhoneOrEMail(CodeUtils.KEY, mobilePhone, CodeUtils.ENCRYPT_LENGTH);
orderInfoBean.setMobilePhone(mobilePhone);
log.info("DES mobilePhone:" + mobilePhone);
}
return orderInfoBean;
}
开通租户,使用预置租户的思想,租户开通即用预置(不用等待),开通成功发送mq消息(部分代码)
// 获取预置企业
if (cloudEntWelinkPreDone.size() > 0) {
cloudEntWelink = cloudEntWelinkRepository.findByStatusAndTenantIdOrderByCreatedOn(CodeUtils.PRE_DONE, null).get(0);
// 存放redis数据
redisDao.del(CodeUtils.PRE_WELINK_ENT_LIST);
for (CloudEntWelink cloudEntWelinkRedis : cloudEntWelinkPreDone) {
redisDao.lpush(CodeUtils.PRE_WELINK_ENT_LIST, cloudEntWelinkRedis.getEntCode());
}
log.info("redisDao.llen{
{}}", redisDao.llen(CodeUtils.PRE_WELINK_ENT_LIST));
// 重试获取entCode,避免冲突
while (1 != redisDao.setnx(cloudEntWelink.getEntCode(), "-1", 30)) {
if (redisDao.llen(CodeUtils.PRE_WELINK_ENT_LIST) > 0) {
cloudEntWelink.setEntCode(redisDao.rpop(CodeUtils.PRE_WELINK_ENT_LIST));
redisDao.rpop(CodeUtils.PRE_WELINK_ENT_LIST);
log.info("rpop llen{
{}}", redisDao.llen(CodeUtils.PRE_WELINK_ENT_LIST));
} else {
return ResponseMessage.buildResponse(CodeUtils.NO_INSTANCE, "no instance");
}
}
} else {
return ResponseMessage.buildResponse(CodeUtils.NO_INSTANCE, "no instance");
}
小编在实现功能以及上线调试,有一些经验之谈:开发和解决问题的过程中,即时记录开发重点,打印重要参数输出日志、建立思维导图,树立全局思维,能够快速定位并解决问题。
华为云市场文档
zimbra硬件要求,25G硬盘(最好10G),8G内存(4G也可),ubuntu14.04系统,/分区硬盘够大,最好做逻辑卷可以扩容。1.vim /etc/hostnamemail.o8mm.com2.vim /etc/hosts192.168.0.35mail.o8mm.com mail3.vim /etc/dnsmasq.confserver=192.168.0....
【MFC开发(1)】MFC的介绍、与Qt的区别
在线视频[code="java"]http://www.red5.org/[/code]Red5总结 做网站开发已经几年有余了,最近终于忙完了自己的青青豆,以前始终没有抽出时间自己写点什么,偶尔拿起偶尔放下,终究是空,希望自己多多坚持,也希望博友的多多支持,分享自己一些积累的经验! 前一段时间公司鉴于首页不错的流量,希望增加一些格外的收入或者做些视频推广,需...
C语言最重要的知识点总体上必须清楚的:1)程序结构是三种: 顺序结构 、选择结构(分支结构)、循环结构。2)读程序都要从main()入口, 然后从最上面顺序往下读(碰到循环做循环,碰到选择做选择),有且只有一个main函数。3)计算机的数据在电脑中保存是以 二进制的形式. 数据存放的位置就是 他的地址.4)bit是位 是指为0 或者1。 byte 是指字节, 一个字节 = 八个位.概念常考到的:1、编译预处理不是C语言的一部分,不占运行时间,不要加分号。C语言编译的程...
转录组测序和RNA-seq是一样的,他们的关系zhi如下:转录组测序的方法很多,而RNA-seq是当zhuan前转录组测序的一种测序方法,又称为二代测序,包括454,solexa等。RNA-seq即转录组测序技术,就是把mRNA,smallRNA,and NONcoding RNA等或者其中一些用高通量测序技术把它们的序列测出来。反映出它们的表达水平。 数字基因表达谱升级版(RNA-Seq)是用来研究某一生物对象在特定生物过程中基因表达差异的技术。该技术结合了转录组测序建库的实验方法与数字...
/*分析: 第一次写字典树,看了看杭电课件,然后跟着映像写的,差不多,1a哦~,算是半个模板吧。 2012-07-04 */#include"stdio.h"#include"string.h"#include"stdlib
1.为什么需要沙箱机制?默认情况下,一个应用程序是可以访问机器上的所有资源的,比如CPU、内存、文件系统、网络等等。但是这是不安全的,如果随意操作资源,有可能破坏其他应用程序正在使用的资源,或者造成数据泄漏。为了解决这个问题,一般有下面两种解决方案:(1) 为程序分配一个限定权限的账号:利用操作系统的权限管理机制进行限制(2) 为程序提供一个受限的运行环境:这就是沙箱机制2.什么是沙箱机...
此篇文章总结的 基于kmp中的模式串方式关于next图解如下举例:字符串 “ababaa”索引 0 1 2 3 4 5 字符 a b a b a a next 0 0 1 1 2 3 next定义写法从索引1位置开始计算,除去第i位置,从0到 i-1,前后缀中最长重复字符长度next[0]=0 默认0,没有前后缀next[1] =0,字符a...
一、scale实现水平扩展docker-comose scale : Set number of containers for a service,实现水平扩展1.1、用到的三个文件docker-compose.yaml文件version: "3"services: redis: image: redis web: build: ...
计算机原理2010年7月真题及答案解析(02384)计算机原理2010年7月真题及答案解析(02384),该试卷为计算机原理自考历年真题试卷,包含答案及详细解析。一、单项选择题(本大题共 15 小题,每小题 2 分,共 30 分)在每小题列出的四个备选项中只有一个选项是符合题目要求的,请将其代码填写在题后的括号内。错选.多选或未选均无分。1.MTTR 是衡量计算机性能指标中的( )A.运算速度B....
接上一篇文章:https://blog.csdn.net/HNUchaowang/article/details/108311858说一句题外话,就是讨论一下为什么要去学习什么类图呢?要去理解这么多关系呢?我觉得有如下两个作用吧:第一,最实在的,程序员肯定少不了写设计文档,设计文档肯定包含类图;第二,这个理解类与类之间的关系,是学习设计模式的基础。设计模式是什么,那就是相当于“降龙十八掌”和“独孤九剑”的武功秘籍啊,一招一式都是大佬多年来提炼的精髓。话不多说,切入正题。聚合(has-a)和组合(
https://jzoj.net/senior/#main/show/3195Problem给定平面上nnn个点,求有多少对点能被一个平行于矩形包含,且仅仅包含这两个点。Data constraintn≤105n\le 10^5n≤105Solution首先吐槽一下搬题人。你搬题就搬嘛,还非要搞点别的,原题的x,yx,yx,y互不相同,这里可以相同,并且,矩形还可以是就一行...