【第三方OA对接】01华为云WeLink对接项目总结_冯浩月的博客-程序员秘密

技术标签: Java  第三方OA  项目  华为云WeLink  租户预置  参数解析  响应体签名  

前言

   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");
        }

小结

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m18633778874/article/details/107249668

智能推荐

zimbra邮箱服务器搭建及错误_weixin_34259232的博客-程序员秘密

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....

在线视频_onlyzhaozhen的博客-程序员秘密

在线视频[code=&quot;java&quot;]http://www.red5.org/[/code]Red5总结 做网站开发已经几年有余了,最近终于忙完了自己的青青豆,以前始终没有抽出时间自己写点什么,偶尔拿起偶尔放下,终究是空,希望自己多多坚持,也希望博友的多多支持,分享自己一些积累的经验! 前一段时间公司鉴于首页不错的流量,希望增加一些格外的收入或者做些视频推广,需...

C语言最重要的知识点【入门干货】_c语言重要内容_yzcn的博客-程序员秘密

C语言最重要的知识点总体上必须清楚的:1)程序结构是三种: 顺序结构 、选择结构(分支结构)、循环结构。2)读程序都要从main()入口, 然后从最上面顺序往下读(碰到循环做循环,碰到选择做选择),有且只有一个main函数。3)计算机的数据在电脑中保存是以 二进制的形式. 数据存放的位置就是 他的地址.4)bit是位 是指为0 或者1。 byte 是指字节, 一个字节 = 八个位.概念常考到的:1、编译预处理不是C语言的一部分,不占运行时间,不要加分号。C语言编译的程...

转录组测序和RNA-seq_rna-seq是二代测序吗_wangchuang2017的博客-程序员秘密

转录组测序和RNA-seq是一样的,他们的关系zhi如下:转录组测序的方法很多,而RNA-seq是当zhuan前转录组测序的一种测序方法,又称为二代测序,包括454,solexa等。RNA-seq即转录组测序技术,就是把mRNA,smallRNA,and NONcoding RNA等或者其中一些用高通量测序技术把它们的序列测出来。反映出它们的表达水平。 数字基因表达谱升级版(RNA-Seq)是用来研究某一生物对象在特定生物过程中基因表达差异的技术。该技术结合了转录组测序建库的实验方法与数字...

hdu1251_hdu - 1251_Ice_Crazy的博客-程序员秘密

/*分析:    第一次写字典树,看了看杭电课件,然后跟着映像写的,差不多,1a哦~,算是半个模板吧。                                                2012-07-04   */#include"stdio.h"#include"string.h"#include"stdlib

随便推点

什么是沙箱机制_沙箱机制什么意思_如山似水0_0的博客-程序员秘密

1.为什么需要沙箱机制?默认情况下,一个应用程序是可以访问机器上的所有资源的,比如CPU、内存、文件系统、网络等等。但是这是不安全的,如果随意操作资源,有可能破坏其他应用程序正在使用的资源,或者造成数据泄漏。为了解决这个问题,一般有下面两种解决方案:(1) 为程序分配一个限定权限的账号:利用操作系统的权限管理机制进行限制(2) 为程序提供一个受限的运行环境:这就是沙箱机制2.什么是沙箱机...

kmp算法中next和nextval计算方法和代码总结_赵秋然的博客-程序员秘密

此篇文章总结的 基于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...

Docker Compose水平扩展和负载均衡_Jeremy_Lee123的博客-程序员秘密

一、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)计算机原理2010年7月真题及答案解析(02384),该试卷为计算机原理自考历年真题试卷,包含答案及详细解析。一、单项选择题(本大题共 15 小题,每小题 2 分,共 30 分)在每小题列出的四个备选项中只有一个选项是符合题目要求的,请将其代码填写在题后的括号内。错选.多选或未选均无分。1.MTTR 是衡量计算机性能指标中的( )A.运算速度B....

UML类图中聚合和组合的区别详解(含代码)_描述聚合和组合的异同,画图举例说明怎么写_HNUchaowang的博客-程序员秘密

接上一篇文章:https://blog.csdn.net/HNUchaowang/article/details/108311858说一句题外话,就是讨论一下为什么要去学习什么类图呢?要去理解这么多关系呢?我觉得有如下两个作用吧:第一,最实在的,程序员肯定少不了写设计文档,设计文档肯定包含类图;第二,这个理解类与类之间的关系,是学习设计模式的基础。设计模式是什么,那就是相当于“降龙十八掌”和“独孤九剑”的武功秘籍啊,一招一式都是大佬多年来提炼的精髓。话不多说,切入正题。聚合(has-a)和组合(

3195. 【HNOI模拟题】数学(分治 + 单调栈二分)_Algor_pro_king_John的博客-程序员秘密

https://jzoj.net/senior/#main/show/3195Problem给定平面上nnn个点,求有多少对点能被一个平行于矩形包含,且仅仅包含这两个点。Data constraintn≤105n\le 10^5n≤105Solution首先吐槽一下搬题人。你搬题就搬嘛,还非要搞点别的,原题的x,yx,yx,y互不相同,这里可以相同,并且,矩形还可以是就一行...

推荐文章

热门文章

相关标签