这次解决两个技术点,一个是如何对下载的资金账单GZIP文件来进行hash校验,另外一个就是如何解压缩GZIP的文件.对hash校验走了点弯路,还好及时发现,没有浪费时间.
1. 通过接口获取微信支付资金数据
微信支付按天提供微信支付账户的资金流水账单文件,当日账单在次日上午9点开始生成,建议商户在上午10点以后获取;资金账单中涉及金额的字段单位为“元”。标记红色是都需要注意的.
接口地址: https://api.mch.weixin.qq.com/v3/bill/fundflowbill
请求的参数: Header 头必须带上 Authorization , Accept 设置为 application/json
Query 参数有
bill_date : 账单日期
account_type 资金账户类型 BASIC: 基本账户 OPERATION
: 运营账户 FEES
: 手续费账户
tar_type: GZIP 压缩格式,不填则以不压缩的方式返回数据流可选取值:
注意: 这里提交参数的方式是GET, 我习惯性的使用了POST,一直报405错误, 后面错误码里面又没找到405, 折腾了一会才发现这个问题.
接口返回数据格式如下:
{
"hash_type": "SHA1",
"hash_value": "79bb0f45fc4c42234a918000b2668d689e2bde04",
"download_url": " https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx"
}
核心就是拿到download_url 数据, 注意这个链接的有效期只有5分钟.
string url = "https://api.mch.weixin.qq.com/v3/bill/fundflowbill";
HttpClient client = new HttpClient(new HttpHandler(WxPayTool.mchId, WxPayTool.serialNO));
string queryString = $"bill_date={riqi}&account_type=BASIC&tar_type=GZIP";
var response = client.GetAsync(url + "?" + queryString);
var rep = response.Result;
var respStr = rep.Content.ReadAsStringAsync().Result;
JObject downObject = JObject.Parse(respStr);
2. 下载账单数据:
这里我也想当然的以为直接读取链接然后保存文件就可以了. 结果失败.看文档才知道一样的需要对downurl 进行签名,然后发起请求方可以下载.
string filename = Path.Combine(hostEnvironment.WebRootPath,"bill", Guid.NewGuid().ToString()+".gzip"); //定义下载文件名
string csvFileName = Path.Combine(hostEnvironment.WebRootPath, "bill", riqi +".csv" ); //解压缩后的文件名
string downurl = downObject["download_url"].ToString();
string fileHashCode = downObject["hash_value"].ToString();
var downresp = client.GetAsync(downurl);
var downrep = downresp.Result;
using (Stream reader = downresp.Result.Content.ReadAsStream()) {
using (FileStream writer = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Write))
{
byte[] buffer = new byte[1024];
int c = 0;
while ((c = reader.Read(buffer, 0, buffer.Length)) > 0)
{
writer.Write(buffer, 0, c);
}
}
}
3. 解压缩gzip数据
直接在网上找到的解压缩文件代码
public static void DeCompressFile(String compressPath, String DecompressPath)
{
int fileszie = (int) new FileInfo(compressPath).Length;
byte[] buffer = new byte[fileszie];
using (FileStream fileStream = new FileStream(compressPath, FileMode.Open, FileAccess.Read)) {
using(BinaryReader binaryReader = new BinaryReader(fileStream))
{
binaryReader.BaseStream.Seek(0, SeekOrigin.Begin);
buffer = binaryReader.ReadBytes(fileszie);
binaryReader.Close();
}
fileStream.Close();
}
using(MemoryStream memoryStream = new MemoryStream(buffer)) {
using(GZipStream zipStream = new GZipStream(memoryStream, CompressionMode.Decompress)) {
using(MemoryStream resutlStream = new MemoryStream())
{
int readLength = 1024;
byte[] buf = new byte[readLength];
int len = 0;
while ((len = zipStream.Read(buf, 0, readLength)) > 0) {
resutlStream.Write(buf, 0, len);
}
using(FileStream fs = new FileStream(DecompressPath,FileMode.Create, FileAccess.ReadWrite)) {
using(BinaryWriter bw = new BinaryWriter(fs)) {
bw.Write(resutlStream.ToArray());
bw.Flush();
bw.Close();
}
fs.Close();
}
}
}
}
}
4. 进行HASH校验
为什么在这里才写hash校验, 因为我之前也是想当然得也为hash值码是下载的GZIP文件的校验码,然而并不是, 校验的是解压缩后的文件. 同样在网上找到的校验代码了. 到此账单就是下载完成了,后续如何读取就很简单了,字段含义都很清晰.
public static class HashHelper
{
///<summary>
/// 计算指定文件的SHA1值
///</summary>
/// <paramname="fileName">指定文件的完全限定名称</param>
///<returns>返回值的字符串形式</returns>
public static string ComputeSHA1(String fileName)
{
String hashSHA1 = String.Empty;
//检查文件是否存在,如果文件存在则进行计算,否则返回空值
if (System.IO.File.Exists(fileName))
{
using (System.IO.FileStream fs = new System.IO.FileStream(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
//计算文件的SHA1值
System.Security.Cryptography.SHA1 calculator = System.Security.Cryptography.SHA1.Create();
Byte[] buffer = calculator.ComputeHash(fs);
calculator.Clear();
//将字节数组转换成十六进制的字符串形式
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < buffer.Length; i++)
{
stringBuilder.Append(buffer[i].ToString("x2"));
}
hashSHA1 = stringBuilder.ToString();
}//关闭文件流
}
return hashSHA1;
}
}