《使用BSN一天学会区块链开发》系列文章可以帮助具有一定开发经验的开发者,在不需要学习区块链开发语言的情况下,在区块链服务网络(BSN)上完成区块链应用的开发。
《使用BSN一天学会区块链开发》系列文章可以帮助具有一定开发经验的开发者,在不需要学习区块链开发语言的情况下,在区块链服务网络(BSN)上完成区块链应用的开发。
本文主要介绍用户如何通过链下业务系统使用C#语言与链上数据进行交互而进行的相关操作流程说明,但在说明链下业务系统与链上进行数据交互之前,首先对用户如何在区块链服务门户注册、应用发布服务、应用服务参与以及应用管理进行一一说明。
注册和登录
打开区块链服务网络网址:www.bsnbase.com, 点击登录页面中的【内测申请】按钮进入内测申请页:
根据页面中的提示填写内测申请信息并提交内测申请,内测申请用户分为个人用户和企业用户两种,申请人应根据自己的用户类型任选一种进行申请, 内测申请提交后需等待系统审核,待系统审核通过后将向申请者发送激活邮件,申请用户可根据【激活邮件】中的激活链接来激活账, 帐户激活后即可登录系统使用。
应用服务发布
应用服务是指在区块链服务网络中已经发布并运行的区块链应用,用户可以通过服务网络门户发布自己的区块链应用服务,应用服务分为公开和非公开两种(内测期间发布的服务默认为非公开,如需公开服务需提交公开申请,后台运营人员进行审核,只有审核通过后服务才可在服务门户的应用商店中进行查看),此处就不一一截图了,就概述一下应用服务发布整体流程:
● 登录区块链服务门户以后,进入我发布的应用->我发布的服务页面;
● 点击创建新服务按钮,进入创建新服务页面,根据提示输入相应的信息;
● 点击下一步按钮开始上传链码包(上传链码包可以使用自己开发的链码包或直接使用服务网络提供的预制链码包),再定义服务功能及角色(表示链下业务系统调用链码所对应的权限)、选择发布的城市节点(表示当前发布的服务所对应的链码部署在哪些城市节点)、付费周期以及是否使用云服务等信息;
● 继续点击下一步按钮,进入到设置接入方式页面,输入网站地址、移动终端接入以及API服务接入等信息(接入方式均为链下接入的相关信息),如果暂时不需要设置接入方式,可跳过此页面继续点击下一步按钮,进入服务账单页面进行确定(服务网络内测其间,所有资源均免费使用);
● 点击确定后,在我发布的应用->我发布的服务列表页面可以看到该条新创建的服务,且该服务的状态为“待初审”状态,发布者需等待运营和运维人员进行审核和发布,服务发布后发布者默认不参与服务。
应用服务参与
在服务网络内测期间,所有发布的服务均为非公开,如果自己或其他用户想要参与已经发布的服务,可通过在我发布的应用->我发布的服务列表中邀请参与者,将邀请参与者的链接地址发给需要参与服务的用户,应用参与服务整体流程如下:
● 根据邀请的链接地址,进入服务信息页面,点击申请参与应用按钮,进入服务角色及城市节点选择页面;
● 选择需要使用的服务角色和城市节点(角色是指用户参与服务后链下业务系统访问链上所对应的权限;城市节点是指用户的链下业务系统通过该城市节点与链上进行数据交互)。用户选择服务角色以后,系统会显示角色对应的功能使用费,选择城市节点的时候申请新证书或选择已有的证书。
● 点击确定按钮,提示接入城市节点、接入方式以及费用信息:
● 确认参与服务的接入城市节点、接入方式以及费用信息(内测期间,所有资源均免费)后,在我参与的应用->我参与的列表页面可以查看到所参与的应用服务,该应用服务的状态为待发布者审核,等待应用服务的发布者对参与者进行审核。
● 服务网络内测期间只要发布者对服务参与申请审核通过后,服务参与信息的状态更新为已确认。至此,参与应用服务成功。
● 应用服务参与成功以后,链下业务系统就可以调用城市节点所对应的节点网关,将数据推送上链和从获取上链数据以及链上区块信息,调用节点网关所对应的参数可进入我参与的服务->我参与的->查看->接入的城市节点和服务接入配置参数部分进行查看,接入的城市节点用于接入的城市节点证书进行下载以及城市节点网关的调用地址进行展示,应用服务接入配置参数用于对链下业务系统调用城市节点网关的相关参数进行展示,此部分的细节描述参考“数据交互”部分;
应用管理
应用服务发布以后,可进入我发布的应用->我发布的服务管理列表中对已经发布的服务进行统一管理,管理列表具体包含以下功能:
● 邀请参与者:主要用于将应用参与的邀请链接地址发给其它用户,其它用于可以通过此邀请链接地址参与此服务;
● 申请公开:主要是内测期间,所有发布的服务默认均为非公开,如果需要将服务公开,则可以通过此功能提交公开申请;
● 服务升级:主要用于对应用服务的链码包进行升级;
● 配置升级:主要用于对应用服务的配置资源进行升级,如部署的城市节点以及节点的资源的配置信息;
● 历史版本:主要用于对服务升级以后的历史版本进行查询;
● 运行信息:主要用于对应用服务的部署节点以及节点资源信息和链上数据(来源于链下业务系统通过调用节点网关接口将数据推送上链)进行监控;
● 设置接入方式:主要用于对应用服务的链下业务系统的接入进行配置;
● 查看:主要用于对应用服务的基本信息、链码及部署信息、服务角色信息、审批记录信息以及接入方式等信息进行查看。
应用链码开发
链码(ChainCode)又称为智能合约,是用计算机语言描述合约条款、交易的条件、交易的业务逻辑等,通过调用智能合约实现交易的自动执行和对账本数据的操作。一个区块链应用可以部署多个链码,每个链码包含多个方法。
链码支持多种语言编写,包括golang、java、node.js。每个链码程序都必须实现Chaincode接口,链码包含:Init ,Invoke,Query三个基本操作:
● Init :链码初始化的方法,在链码实例化或者升级的时候调用一次,以便链码可以执行任何必要的 初始化,包括应用程序状态的初始化。
● Invoke:接收和处理链下业务系统调用事务处理提案,其参数包含调用的链码程序中函数的名称和具体业务处理数据参数。即在Invoke中根据不同的方法参数调用其他分支处理响应的业务。Invoke可以简单的理解为链码方法的入口。
● Query:提供查询链码数据的方法,该方法只作为查询使用,不提供操作链上数据的操作。可在Query操作时调用,亦可在Invoke方法中作为某些方法的分支被调用。该方法可以不实现。
下面以通用数据链码包为例详细说明一下。
通用数据链码包是我们为应用开发者提供对业务数据进行增删改查基本操作的链码(Golang语言编写)。应用开发者可以在此链码包的基础上根据应用业务需求进一步拓展链码功能。此链码支持存储的数据类型有字符串、整型、浮点型、集合(map、list)等等。
链码包下载地址: www.bsnbase.com/static/base/BaseChainCode.zip
1.链码包功能如下:
1.1.增加数据(set)
输入参数说明
baseKey:需要保存的唯一的主键标识,baseValue:保存的数据信息
例:{“baseKey”:”str”,”baseValue”:”this is string”}
注:其中baseKey是不能为空的字符串,baseValue可以是任意类型的数据。如果baseKey已经存在,则直接返回已经存在,不能添加;如果不存在,则添加数据。
1.2.修改数据(update)
输入参数说明
baseKey:需要修改的唯一的主键标,baseValue:保存的数据信息
例:{“baseKey”:”str”,”baseValue”:”this is string”}
注:其中baseKey是不能为空的字符串,baseValue可以是任意类型的数据。如果baseKey不存在,则无法更新,如果已经存在,则修改数据。
1.3.删除数据(delete)
输入参数说明
baseKey:需要删除的唯一的主键标识的值
例:”str”
注:其中baseKey的值不能为空,且必须存在,否则将无法删除。
1.4.获取数据(get)
输入参数说明
baseKey:需要获取的唯一的主键标识的值
例:”str”
注:其中baseKey的值不能为空,且必须存在,否则将无法获取到相应的信息。
2.链码介绍
2.1.Init方法
func (t *BaseChainCode) Init(stub shim.ChaincodeStubInterface) peer.Response {setLogger("bsnchaincode Init start......") // 日志输出defer setLogger("bsnchaincode Init end......") // 方法处理完成后日志输出// 初始化基本信息dbBaseModel := DBBaseModel{BaseKey: "key_", BaseInfo: "hello chaincode"}reqJsonValue, err := json.Marshal(&dbBaseModel)//将对象转成byteif err != nil { return shim.Error(fmt.Sprintf("数据转换失败:%s", err.Error()))}err = stub.PutState(dbBaseModel.BaseKey, reqJsonValue)// 提交数据if err != nil {//结果判断return shim.Error(err.Error())}return shim.Success(nil)//响应}
这个就是最简单的链码初始化功能,写日志、初始化一条数据、保存到数据库、响应。
建议在链码初始化(Init)的时候,不要有太多的业务操作。
2.2. Invoke
func (t *BaseChainCode) Invoke(stub shim.ChaincodeStubInterface) peer.Response { function, args := stub.GetFunctionAndParameters()// 获取方法名称和参数信息 switch function { case "set": // 保存 return t.set(stub, args)//调用保存的方法 case "update": // 修改 return t.update(stub, args)//调用修改的方法 case "delete": // 删除 return t.delete(stub, args)//调用删除的方法 case "get": // 获取 return t.get(stub, args)//调用获取的方法 default: setLogger("无效的方法, 只能是set,update,delete,get!") break } return shim.Error("无效的请求")}
stub.GetFunctionAndParameters():获取请求的方法名称(string)和参数信息([]string)
return t.set(stub, args)//调用保存的方法,具体的业务处理
节点网关
节点网关是部署在各个城市节点,接收应用系统的请求,使用托管的用户身份信息,向相应的应用链码发起访问并返回链码的执行结果。节点网关的调用是通过向区块链服务的各个城市节点的网关服务发送HTTP请求来实现。节点网关负责验证用户身份信息、应用信息,通过用户身份信息和应用信息以及需要访问的链码、链码方法来传递链码参数、获取链码执行结果的服务通道。
业务系统需要按照接口说明在请求中加入相应的请求参数,调用节点网关以后,节点网关会返回链码的执行结果。
接口地址:https://节点网关地址/api/node/reqChainCode
注:用户参与服务成功后可以在服务详情页面查看并下载应用链下业务系统开发所需要的应用服务配置参数、节点网关地址和应用身份证书,如下图:
通讯方式:POST
签名算法:
1、将userCode+ appCode+ chainCode+ funcName的值以及args中每一项数据拼接成字符串A;
2、对字符串A使用用户证书的私钥进行SHA256WITHECDSA签名。
请求参数
响应参数
数据交互
应用服务参与审核通过之后,链下业务系统就可以通过节点网关与链上数据进行数据交互,调用节点网关需要节点网关接入地址、节点网关请求参数以及证书等信息,此部分数据可以通过我参与的应用->我参与的->查看明细页面进行获取,下面对这三方面的参数进行一一说明;
● 节点网关接入地址:为链下业务系统调用链上所对应的城市节点入口,所有与链上的数据交互都是通过此地址进行访问,如下图所示:
● 节点网关请求参数:如果需要通过节点网关接入地址与链上进行数据交互,肯定需要按照节点网关接口调用规范,节点网关接口调用需要userCode、appCode、tid、chainCode、funcName、agrs等参数信息,下面对相关参数进行概要说明。
▶ userCode:应用发布者或者参与者的登录名(也就是登录门户的用户名)。
▶ appCode:参与应用的唯一标识。应用创建时,系统自动生成的唯一标识。
▶ tid:用户与参与的应用之间关联的唯一标识。
▶ chainCode:区块链服务网络中,运维部署的链码的名称。需要注意的是,这里的链码名称不是服务发布时的链码名称。
▶ funcName:调用链码的方法名称。
▶ agrs:调用链码方法的参数集合。字符串类型的数组,参与者需要与应用发布者联系,获取方法对应的参数。
参数获取页面如下图所示:
● 节点用户证书:在请求节点网关过程中需要https证书、请求报文签名证书(即用户身份证书)和响应结果验签证书。
▶ https请求证书:为保障数据传输层的安全,需要使用https请求。
▶ 请求报文签名证书:用户私钥证书。
▶ 响应结果验签证书:网关公钥证书。
用户身份证书的下载可以通过我参与的应用->我参与的->查看->接入的城市节点部分进行下载,也可以通过进入我的身份证书-证书查看列表中找到对应的应用信息,进行证书下载。下载的证书文件包含https证书、用户证书(私钥证书、公钥证书(网关对报文的验签))、网关证书(网关公钥证书)。证书下载页面如下图所示
业务系统开发
业务系统就是链下业务系统,需要与链上进行数据交互的系统,下面根据预置链码包的C#开发实例(可从门户下载)着重说一下与网关交互的说明。
项目环境准备:
1、Visual Studio2019/VS Code(可以使用您习惯的IDE)
2、.NET Core 2.2
3、BouncyCastle.Crypto.dll
项目介绍:
上图为项目的目录结构:
该项目使用.net core 2.2框架,直接调用服务网关api接口,实现数据交互:
1、Library文件夹下文件说明:
▷ ECDSAHelper.cs(ECDSA椭圆曲线算法服务类)
▷ X9ECParametersExtends.cs(X9E参数扩展服务类)
▷ SignerExtends.cs(签名扩展服务类)
▷ ConfigurationHelper.cs(配置服务类)
▷ SendHelper.cs(发送服务帮助类)
2、Certs文件夹下文件说明(下载用户证书zip包获取):
▷ bsn_https.crt(https请求的公钥证书)
▷ gateway_public_cert.pem(网关公钥证书)
▷ private_key.pem(用户私钥证书)
▷ public_cert.pem(用户公钥证书)
▷ instruction_for_use.txt(秘钥证书说明)
3、packages 文件夹下文件说明 :
▷ BouncyCastle.Crypto.dll (椭圆曲线加密第三方类库 )
4、Models文件夹下文件说明
▷ NodeApiReqContent.cs (节点网关请求公共实体 )
▷ NodeApiResContent.cs(节点网关返回公共实体)
流程说明:
1、用户在客户端(web页面)填写上链信息
2、进入相应的方法,获取用户填写的上链信息,并且判断输入信息不可为空
3、拼接待签名的字符串,对字符串使用用户私钥证书进行 SHA256WITHECDSA 签名加密(调用ECDSAHelper下的SignData方法生成字节后转成base64字符串)
4、发起post请求,并且附加HTTPS证书
5、获取返回报文中的mac值,对返回报文中的mac值,使用网关的公钥证书进行验签,验签内容与传参时签名字符串相同
6、若验签成功,则将链上返回报文处理后,显示到web页面中
详细说明
数据签名、验签
/// <summary>
/// ECDSA椭圆曲线算法服务类
/// </summary>
public class ECDSAHelper
{
/// <summary>
/// 使用私钥签名
/// </summary>
/// <param name="macdata">待签名数据</param>
/// <param name="pkUrl">签名证书地址</param>
/// <returns>返回待签名结果</returns>
public static byte[] SignData(string macdata, string pkUrl)
{
byte[] data = Encoding.UTF8.GetBytes(macdata);
string privateKey = ReadPK(pkUrl);
TextReader ptr = new StringReader(privateKey);
Org.BouncyCastle.OpenSsl.PemReader pem = new Org.BouncyCastle.OpenSsl.PemReader(ptr);
Org.BouncyCastle.Crypto.Parameters.ECPrivateKeyParameters ecdsaPrivateKey = (Org.BouncyCastle.Crypto.Parameters.ECPrivateKeyParameters)pem.ReadObject();
string curveName = "P-256";
var nistCurve = Org.BouncyCastle.Asn1.Nist.NistNamedCurves.GetByName(curveName);
var sign = nistCurve.Sign(ecdsaPrivateKey, data);
return sign;
}
/// <summary>
/// 使用公钥证书对待验签数据进行验签
/// </summary>
/// <param name="macdata">参与验签字符</param>
/// <param name="mac">要验签的签名串</param>
/// <param name="pkUrl">公钥证书地址</param>
/// <returns>返回验签结果</returns>
public static bool VerifyData(string macdata, string mac, string pkUrl)
{
byte[] data = Encoding.UTF8.GetBytes(macdata);
byte[] signature = Convert.FromBase64String(mac);
string publicCert = ReadPK(pkUrl);
Org.BouncyCastle.X509.X509Certificate cert = new Org.BouncyCastle.X509.X509CertificateParser().ReadCertificate(Encoding.UTF8.GetBytes(publicCert));
Org.BouncyCastle.Crypto.Parameters.ECPublicKeyParameters ecdsaPublicKey = (Org.BouncyCastle.Crypto.Parameters.ECPublicKeyParameters)cert.GetPublicKey();
string curveName = "P-256";
var nistCurve = Org.BouncyCastle.Asn1.Nist.NistNamedCurves.GetByName(curveName);
return nistCurve.Verify(ecdsaPublicKey, data, signature);
}
/// <summary>
/// 读取密钥文件
/// </summary>
/// <param name="keyUrl">密钥文件路径</param>
/// <returns>返回读取的密钥文件内容</returns>
private static string ReadPK(string keyUrl)
{
string pk = string.Empty;
FileStream fileStream = new FileStream(keyUrl, FileMode.Open);
using (StreamReader reader = new StreamReader(fileStream))
{
pk = reader.ReadToEnd();
}
return pk;
}
}
/// <summary>
/// X9E参数扩展服务类
/// </summary>
public static class X9ECParametersExtends
{
/// <summary>
/// 获取EC域参数
/// </summary>
/// <param name="ec"></param>
/// <returns></returns>
public static ECDomainParameters GetDomainParams(this X9ECParameters ec)
{
return new ECDomainParameters(ec.Curve, ec.G, ec.N, ec.H);
}
/// <summary>
///
/// </summary>
/// <param name="ec"></param>
/// <returns></returns>
public static AsymmetricCipherKeyPair GenerateKeyPair(this X9ECParameters ec)
{
IAsymmetricCipherKeyPairGenerator g = GeneratorUtilities.GetKeyPairGenerator("ECDSA");
g.Init(new ECKeyGenerationParameters(ec.GetDomainParams(), new SecureRandom()));
return g.GenerateKeyPair();
}
/// <summary>
/// 获取签名接口
/// </summary>
/// <param name="ec">X9E参数对象</param>
/// <returns>返回获取的签名接口</returns>
public static ISigner GetSigner(this X9ECParameters ec)
{
int bits = (ec.Curve.FieldSize == 521) ? 512 : ec.Curve.FieldSize;
return SignerUtilities.GetSigner(string.Format("SHA-{0}withECDSA", bits));
}
/// <summary>
/// 数据签名
/// </summary>
/// <param name="ec">X9E参数对象</param>
/// <param name="key">EC私钥参数</param>
/// <param name="src">待签名数据</param>
/// <returns>返回待签名结果</returns>
public static byte[] Sign(this X9ECParameters ec, ECPrivateKeyParameters key, byte[] src)
{
return ec.GetSigner().Sign(key, src);
}
/// <summary>
/// 数据验签
/// </summary>
/// <param name="ec">X9E参数对象</param>
/// <param name="key">EC公钥参数</param>
/// <param name="src">原签名字节数组</param>
/// <param name="signature">签名后的字节数组</param>
/// <returns></returns>
public static bool Verify(this X9ECParameters ec, ECPublicKeyParameters key, byte[] src, byte[] signature)
{
return ec.GetSigner().Verify(key, src, signature);
}
}
/// <summary>
/// 签名扩展服务类
/// </summary>
public static class SignerExtends
{
/// <summary>
/// 数据签名
/// </summary>
/// <param name="signer">签名接口</param>
/// <param name="key">EC私钥参数</param>
/// <param name="src">待签名字节数组</param>
/// <returns>返回签名结果</returns>
public static byte[] Sign(this ISigner signer, ECPrivateKeyParameters key, byte[] src)
{
signer.Init(true, key);
signer.BlockUpdate(src, 0, src.Length);
byte[] sign = signer.GenerateSignature();
Asn1Sequence sequence = Asn1Sequence.GetInstance(sign);
DerInteger r = (DerInteger)sequence[0];
DerInteger s = (DerInteger)sequence[1];
BigInteger[] bigs = new BigInteger[] { r.Value, s.Value };
var N = key.Parameters.N;
bigs = preventMalleability(bigs, N);
byte[] bs;
using (MemoryStream ms = new MemoryStream())
{
DerSequenceGenerator seq = new DerSequenceGenerator(ms);
seq.AddObject(new DerInteger(bigs[0]));
seq.AddObject(new DerInteger(bigs[1]));
seq.Close();
bs = ms.ToArray();
}
return bs;
}
/// <summary>
/// 数据验签
/// </summary>
/// <param name="signer">签名接口</param>
/// <param name="key">EC公钥参数</param>
/// <param name="src">原签名字节数组</param>
/// <param name="signature">签名后的字节数组</param>
/// <returns></returns>
public static bool Verify(this ISigner signer, ECPublicKeyParameters key, byte[] src, byte[] signature)
{
signer.Init(false, key);
signer.BlockUpdate(src, 0, src.Length);
return signer.VerifySignature(signature);
}
/// <summary>
/// 检查签名的S与私钥参数N椭圆处理后的结果是否相等,不相等则使用椭圆处理后的结果替换调原S
/// </summary>
/// <param name="sigs">签名数据</param>
/// <param name="curveN">私钥参数N</param>
/// <returns>返回处理后的签名数据</returns>
private static BigInteger[] preventMalleability(BigInteger[] sigs, BigInteger curveN)
{
BigInteger cmpVal = curveN.Divide(BigInteger.ValueOf(2L));
BigInteger sval = sigs[1];
if (sval.CompareTo(cmpVal) == 1)
{
sigs[1] = curveN.Subtract(sval);
}
return sigs;
}
}
发起请求(以添加数据Create为例)
/// <summary>
/// 提交数据上链
/// </summary>
/// <param name="model">待上链的数据</param>
/// <returns>返回数据上链结果</returns>
[HttpPost]
public JsonResult Create([FromBody]BaseCCModel model)
{
return this.Submit("set", new List<string> { JsonConvert.SerializeObject(model) });
}
/// <summary>
/// 数据提交
/// </summary>
/// <param name="functionName">方法名</param>
/// <param name="args">参数</param>
/// <returns></returns>
private JsonResult Submit(string functionName, List<string> args)
{
//构建请求数据
NodeApiReqContent req = new NodeApiReqContent()
{
header = new ReqHeader()
{
appCode = AppSettingHelper.AppCode,
userCode = AppSettingHelper.UserCode,
tId = AppSettingHelper.TId
},
body = new ReqBody
{
args = args,
funcName = functionName,
chainCode = AppSettingHelper.ChainCode
}
};
//拼装待签名原字符串
StringBuilder strBuilder = new StringBuilder();
strBuilder.Append(AppSettingHelper.UserCode)
.Append(AppSettingHelper.AppCode)
.Append(AppSettingHelper.ChainCode)
.Append(functionName)
.Append(string.Join("", args));
//数据签名
byte[] singData = ECDSAHelper.SignData(strBuilder.ToString(), AppSettingHelper.PkUrl);
//数据进行Base64编码
req.mac = Convert.ToBase64String(singData);
NodeApiResContent res = SendHelper.SendPost(AppSettingHelper.ReqUrl, JsonConvert.SerializeObject(req), AppSettingHelper.HttpsCert);
if (res != null)
{
//依次检查状态码
if (res.header.code != 0) return Json(new { code = 1, msg = string.Format("处理结果:{0}!", res.header.msg) });
if (res.body.ccRes.ccCode!= 200) return Json(new { code = 1, msg = string.Format("处理结果:{0}!", res.body.ccRes.ccData) });
if (res.body.blockInfo.status != 0) return Json(new { code = 1, msg = "链上交易处理状态失败!" });
//数据验签
if (ECDSAHelper.VerifyData(strBuilder.ToString(), res.mac, AppSettingHelper.GatewayCert))
{
switch (functionName)
{
case "get":
return Json(new { code = 0, msg = string.Format("链上查询结果:{0}【交易ID:{1}】!", res.body.ccRes.ccData, res.body.blockInfo.txId) });
case "set":
return Json(new { code = 0, msg = string.Format("数据上链结果:{0}【交易ID:{1}】!", res.body.ccRes.ccData, res.body.blockInfo.txId) });
case "update":
return Json(new { code = 0, msg = string.Format("链上数据更新结果:{0}【交易ID:{1}】!", res.body.ccRes.ccData, res.body.blockInfo.txId) });
case "delete":
return Json(new { code = 0, msg = string.Format("链上数据删除结果:{0}【交易ID:{1}】!", res.body.ccRes.ccData, res.body.blockInfo.txId) });
}
}
else
return Json(new { code = 1, msg = "对节点网关返回的MAC验签失败!" });
}
return Json(new { code = 1, msg = "调用节点网关API失败!" });
}
/// <summary>
/// 发送服务帮助类
/// </summary>
public static class SendHelper
{
/// <summary>
///
/// </summary>
private static readonly NLog.Logger log = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetLogger("SST");
/// <summary>
/// 指定Post地址使用Get 方式获取全部字符串
/// </summary>
/// <param name="url">请求后台地址</param>
/// <param name="content">Post提交数据内容(utf-8编码的)</param>
/// <param name="certPath">证书地址</param>
/// <returns></returns>
public static NodeApiResContent SendPost(string url, string content, string certPath)
{
log.Debug(string.Format("请求地址:{0},请求报文:{1}",url,content));
try
{
//构建Http客户端处理器
var handler = new HttpClientHandler();
//设置客户端证书选项为手动
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
//添加添加X509Certificate2格式的公钥证书
handler.ClientCertificates.Add(new X509Certificate2(certPath, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet));
//设置SSL协议
handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls;
//服务器证书验证回调
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
//构建Http客户端
using (HttpClient client= new HttpClient(handler))
{
//构建内存流对象
using (MemoryStream ms = new MemoryStream())
{
//将请求内容转换成字节数组
byte[] bytes = Encoding.UTF8.GetBytes(content);
ms.Write(bytes, 0, bytes.Length);
ms.Seek(0, SeekOrigin.Begin);//设置指针读取位置,否则发送无效
HttpContent hc = new StreamContent(ms);
//异步发送
var response = client.PostAsync(url, hc).Result;
//获取服务端响应结果
string result = response.Content.ReadAsStringAsync().Result;
//检查响应状态是否为200
if (response.StatusCode == HttpStatusCode.OK)
{
log.Info(string.Format("响应报文:{0}", result));
return JsonConvert.DeserializeObject<NodeApiResContent>(result);
}
else
{
string httpStatus = Enum.GetName(typeof(HttpStatusCode), response.StatusCode);
log.Info("非200HTTP状态,httpStatus=" + httpStatus + ",data=[" + result + "]");
return null;
}
}
}
}
catch (Exception ex)
{
log.Error("post失败,异常:" + ex.Message);
return null;
}
}
}
原创文章,作者:BlockCNN,如若转载,请注明出处:http://www.blockcnn.top/tnews/36146.html