美文网首页
.NetCore对接各大财务软件凭证API——用友系列(1)

.NetCore对接各大财务软件凭证API——用友系列(1)

作者: 程序猿贝塔 | 来源:发表于2020-05-21 13:54 被阅读0次

    .NetCore对接各大财务软件凭证API——用友系列(1)

    一.前言

    今天,我们转战用友系列的第一个产品---T+/Tplus。前两篇文章讲解分享的都是金蝶的产品,因为本身公司牵涉的业务有限,后续有金蝶其他产品的API对接业务时,会继续来分享经验。

    T+的API接口,哎,想起来都是心酸泪。关于该接口的对接开发经验,我之前也简单记录了一些,传送门 记录用友T+接口对接的心酸历程。今天我们就来详细解析下这令人头大的财务API接口。

    二.API接口详解

    2.1接口定义和入参

    根据开发者社区API文档的描述我们可以看到,T+版本为12.3以上的API对接,都必须使用V2版本,那v2与v1版本的区别有哪些呢?主要有两点:1. 请求认证方式,增加云企业ID认证方式 ;2.v2版本支持异步请求。OK,因为我们对接的客户财务环境为12.3,那么我们就来处理v2版本的OpenAPI,该版本的API引入了鉴权机制,简单来说就是在请求头增加了授权 Authorization 参数.

    2.1.1 Authorization参数以及签名处理

    那么 Authorization 参数如何才能生成呢?可以看官网首页的描述是 需要appkey、appsecert、私钥的文件全路径。那这三个参数又如何才能获取呢?必须申请ISV认证,即注册ISV,提交开发申请通过审核后,总部会将这三个参数一并发到注册时预留的邮箱里。

    2.1.2 OrgId方式的签名算法处理

    这里,我们在上一步已经拿到签名所需的三个必要参数了,官网给了两种请求Head的处理方式,一种使用OrgId,一种使用用户名、密码、账套号,这两种方式我们都会讲到。先看第一种方式OrgId访问。

    OrgId的获取方式,官网描述的也有,


    1590027798731.png

    即必须开通云企业才能看到


    1590027984042.png

    这样,我们第一种使用OrgId认证方式的所需参数就已全部准备完毕了,接着往下看,首先需要对appKey、orgid、appsecret、私钥全路径做一个签名1的算法加密,这个算法官网给我们提供的也有,这里仅提供C#版本的签名算法1接着做一个Base64位的加密即可得到Authorization参数。

              if (!APIConfig.AuthorizeParameters.ContainsKey("appkey")
                    || !APIConfig.AuthorizeParameters.ContainsKey("orgid")
                    || !APIConfig.AuthorizeParameters.ContainsKey("appsecret")
                    || !APIConfig.AuthorizeParameters.ContainsKey("secerturl"))
                {
                    throw new Exception("鉴权参数不完整");
                }
                var request = new AccessTokenRequest();
                Dictionary<string, object> parm = new Dictionary<string, object>();
                string appkey = APIConfig.AuthorizeParameters["appkey"];
                string orgid = APIConfig.AuthorizeParameters["orgid"];
                string appsecret = APIConfig.AuthorizeParameters["appsecret"];
                string secetrurl = APIConfig.AuthorizeParameters["secerturl"];
    
                parm.Add("appkey", appkey);
                parm.Add("orgid", orgid);
                parm.Add("appsecret", appsecret);
    
                JsonSerializer jsonSerializer = new JsonSerializer();
                string datas = jsonSerializer.Serialize(parm);
                try
                {
                    var signClass = new TokenManage();
                    string signvalue = signClass.CreateSignedToken(datas, secetrurl);
                    string authStr = @"{""appKey"":""" + appkey + @""",""authInfo"":""" + signvalue + @""",""orgId"":" + orgid + @"}";
                    string encode = Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes(authStr));
                    Dictionary<string, string> parms = new Dictionary<string, string>();
                    parms.Add("Authorization", encode);
                    request.SetHeaderParameters(parms);
                    var response = Excute(request);
                    return response;
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.Message);
                }
    

    当你处理完第一步,调试接口正常返回

    {"result":true,"access_token":"03e74889-1457-48cd-970a-ba3742ffcdea","sid":""}  
    

    先不要高兴的太早了,我们还要根据这一步获取到的Token做业务调用。如图所示


    1590028694104.png

    官网也给的有测试的Demo供我们调用调试,这比较方便我们对问题作出反馈。 T+OpenAPI测试工具-包含v2版本-C# 然后大坑就来了...demo中的jose-jwt.dll是 .NET Framework的版本,但是我们的开发环境是.netCore2.2,很遗憾的是该dll在.netCore环境下不支持.详细的解决过程很心酸,就不再多叙述,我在之前的文章里已详细描述,这里我们只说最后的结果就是解决了不支持的问题。

    2.1.3 用户名密码方式的签名算法处理

    这种方式的登录相比第一种使用OrgId认证的方式有什么好处呢?我在实际的开发中得到了验证,OrgId方式的认证在对那些没有开通云企业的客户来说这种方式是行不通的,所以相比较来说,还是用户名密码的认证比较通用,只要客户能提供给我们一个正常可登录财务系统的用户名和密码,我们就可以使用该方式来进行接口的开发对接。下面具体来说一下,如何正确得到该方式的Authoirzation参数。


    1590029362390.png

    这是第一步得到Token的方法,可以看到签名方式和加密方式不变,变得是签名方式的参数不同,orgId为空,在PostBody里要传入用户名、密码和账套号。


    1590029470127.png

    账套号为中括号里的,比如上图的021809... 不要传名称!不要传名称!不要传名称!接着获取到Token后,我们就可以调用业务接口了,这里还是使用用户名密码的方式来调用。


    1590029588592.png

    增加了上一步获取到的Token,详细代码如下

                var signClass = new TokenManage();
                var request = new GetTokenByPwdRequest();
                string appkey = APIConfig.AuthorizeParameters["appkey"];
                string appsecret = APIConfig.AuthorizeParameters["appsecret"];
                string secetrurl = APIConfig.AuthorizeParameters["secerturl"];
                string userName = APIConfig.AuthorizeParameters["userName"];
                string password = APIConfig.AuthorizeParameters["password"];
                string EncodePassword = signClass.GetMd5(password);
                string accNum = APIConfig.AuthorizeParameters["accNum"];
    
                Dictionary<string, object> parm = new Dictionary<string, object>();
                parm.Add("appkey", appkey);
                parm.Add("orgid", "");
                parm.Add("appsecret", appsecret);
    
                JsonSerializer jsonSerializer = new JsonSerializer();
                string datas = jsonSerializer.Serialize(parm);
                try
                {
                    string signvalue = signClass.CreateSignedToken(datas, secetrurl);
                    string authStr = @"{""appKey"":""" + appkey + @""",""authInfo"":""" + signvalue + @""",""orgId"":""""}";
                    string encode = Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes(authStr));
                    Dictionary<string, string> parms = new Dictionary<string, string>();
                    parms.Add("Authorization", encode);
                    request.SetHeaderParameters(parms);
    
                    Dictionary<string, object> postParms = new Dictionary<string, object>();
                    var args = new PwdEntity() { userName = userName, password = EncodePassword, accNum = accNum };
                    var argsJson = jsonSerializer.Serialize(args);
                    postParms.Add("_args", argsJson);
                    request.SetPostParameters(postParms);
    
                    var response = Excute(request);
                    return response;
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.Message);
                }
    

    两种方式都处理完毕了,我们就可以愉快的使用API来做业务的处理啦。

    2.2 业务接口调用

    2.2.1 会计科目查询
    1590029854357.png

    URL的话,v2版本的调用,我们只需将v1改为v2即可,若想一次性获取到所有的会计科目,查询条件传空值即可,即如下:

    { 
      dto:{ 
      } 
    }
    
            public QueryAccountResponse QueryAccountRequest()
            {
                var request = new QueryAccountRequest();
                var parmsDic = new Dictionary<string, object>();
                var parms = new QuertyEntity() { dto = new BasicDto { } };
                parmsDic.Add("_args", JsonConvert.SerializeObject(parms));
                request.SetPostParameters(parmsDic);
                return _Client.Excute(request);
            }
    

    该方法可返回所有会计科目用作基础档案或者前端页面展示使用。

    2.2.2 凭证相关操作

    凭证创建请求实体:

    {
        "dto": {
            "ExternalCode": "1", //外部编码-唯一码,长度小于50
            "DocType": {
                "Code": "记" // 凭证类别编码
            },
            "AttachedVoucherNum": "2",// 附单据数
            "Memo": "无", // 备注 长度小于50
            "VoucherDate": "2014-09-30",// 凭证日期
            "Entrys": [{
                    "Summary": "提现", // 凭证摘要 
                    "Account": {
                        "Code": "1001" // 科目档案
                    },
                    "Currency": {
                        "Code": "RMB" // 币别
                    },
                    "AmountCr": "100" //贷方金额
                },
                {
                    "Summary": "提现",
                    "Account": {
                        "Code": "1002"
                    },
                    "Currency": {
                        "Code": "RMB"
                    },
                    "AmountDr": "100", // 借方金额
                    "AuxInfos": [{ // 辅助核算信息
                        "AuxAccDepartment": { // 部门核算
                            "Code": "001"
                        },
                        "AuxAccInventory": { // 存货核算
                            "Code": "001"
                        },
                        "AuxAccProject": { // 项目核算
                            "Code": "001"
                        },
                        "AuxAccPerson": { // 人员核算
                            "Code": "001"
                        },
                        "AuxAccCustomer": { //往来单位核算
                            "Code": "001"
                        }
                    }]
                }
            ]
        }
    }
    

    URL: TPlus/api/v1/doc/Create || TPlus/api/v2/doc/Create

    构造好对应的凭证实体即可正常传输凭证了。

    凭证查询:官网给的示例是传入一个dtos的数组,但是在实际的开发过程中,真正传入一个起始期间,一个截止时间时,并没有正确的返回对应的数据,所以我到现在也没搞明白这个起始和截止时间该咋用,如果有知道的小伙伴,还请帮忙解答。


    1590030669375.png

    三.结束语

    其实,真正对于T+的业务调用并没有花费很多时间,因为前面的坑已经踩完了,后面基本上也没啥了,就是我很纳闷的是,每个接口的返回值格式是不固定的,这个就很令人费解啊。咱也搞不懂到底为啥这样定义。倒是前面处理签名算法和dll不兼容的问题前前后后大约搞了一个星期才完整解决,这个很是让人头大。

    曾经在T+的开发群了看到这样一句话,每个开发都是一个实施。也确实是这样一种情况,你对接的每个API接口,不可能总会有人给你解答问题,这时候你只能靠自己去摸索,去猜,当然了大部分的开发文档还是很规范的。其实做对接的做多了,你会遇到不同形式的API接口,每家厂商都有自己独特的开发标准,我们能做的就是遵循这套标准,不然如何对接,如何正确处理我们的工作。

    最后的最后,希望我们做API对接或者说是做开发的,要保持一颗良好的心态去面对问题,要相信问题总是会被解决的,只是时间早晚。而且要找对方法,比如社区,或者对应的交流群等等,会有很多大佬帮你解答疑惑,祝你在开发的道路上勇往直前的。

    我是程序猿贝塔,一个分享自己对接过财务系统API经历和生活感悟的程序员。

    相关文章

      网友评论

          本文标题:.NetCore对接各大财务软件凭证API——用友系列(1)

          本文链接:https://www.haomeiwen.com/subject/uopzohtx.html