2019年12月到 2020年04月,小编参与到公司项目与第三方OA的对接,即华为云WeLink市场的对接工作,华为云市场、WeLink市场两部分对接工作,我负责华为云市场项目对接接口、另一个同事负责WeLink对接。主要经历了四个阶段:熟悉第三方文档需求及相关业务、研发、测试、上线并解决问题。
第三方接口调用、开通第三方租户(租户包括本项目、第三方)
项目分工
小编负责:调试华为云市场生产接口(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
* @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");
}
小编在实现功能以及上线调试,有一些经验之谈:开发和解决问题的过程中,即时记录开发重点,打印重要参数输出日志、建立思维导图,树立全局思维,能够快速定位并解决问题。
华为云市场文档