【第三方OA对接】01华为云WeLink对接项目总结

34 篇文章 2 订阅

前言

   2019年12月到 2020年04月,小编参与到公司项目与第三方OA的对接,即华为云WeLink市场的对接工作,华为云市场、WeLink市场两部分对接工作,我负责华为云市场项目对接接口、另一个同事负责WeLink对接。主要经历了四个阶段:熟悉第三方文档需求及相关业务、研发、测试、上线并解决问题。

主要工作

    第三方接口调用、开通第三方租户(租户包括本项目、第三方)

第三方接口调用

一、准备工作
  • 熟悉整体业务流程图
    在这里插入图片描述

  • 项目分工
    小编负责:调试华为云市场生产接口(5个)、开通租户成功后发送消息(同事进行通讯录-组织结构人员同步)

    二、接口列表
  • 新购商品(5个接口共用一个接口,内部逻辑判断)
    在这里插入图片描述

  • 商品续费

  • 商品过期

  • 商品资源释放

  • 商品升级

三、接口调试

调试过程中,不断熟悉接口定义,以及业务需求。

开发过程

一、第三方接口开发注意事项
  • 注意接口参数(必填与非必填,以及选取项目有用参数)
  • 接口传参时,使用自定义类,不要传httpServletRequest;
  • 明确参数定义,以及整套流程的参数关联性
二、代码精华
(一)第三方接口,安全校验
  1. 校验消息合法性
    (1)校验通知消息的合法性
    /**
     * 校验通知消息的合法性
     *
     * @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));
    }
  1. 返回消息体签名
    /**
     * 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");
        }

小结

    小编在实现功能以及上线调试,有一些经验之谈:开发和解决问题的过程中,即时记录开发重点,打印重要参数输出日志、建立思维导图,树立全局思维,能够快速定位并解决问题。
华为云市场文档

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值