using Aop.Api.Domain; using ApiTools.Core.Utils.NongYePayUtils.Models; using Azure; using Azure.Core; using Furion; using Furion.DatabaseAccessor; using Furion.DataEncryption.Extensions; using Furion.DependencyInjection; using Furion.DistributedIDGenerator; using Furion.FriendlyException; using Furion.HttpRemote; using Microsoft.Extensions.Options; using NetTopologySuite.Algorithm; using Org.BouncyCastle.Asn1.Ocsp; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using System.Xml; using System.Xml.Serialization; namespace ApiTools.Core { public class NongYePayUtils( IHttpRemoteService httpRemoteService, IOptions options, IRepository repThreeResourceLog ) : ITransient { private readonly IHttpRemoteService httpRemoteService = httpRemoteService; private readonly IOptions options = options; private readonly IRepository repThreeResourceLog = repThreeResourceLog; /// /// 查询账户余额 /// /// 余额信息 public Task GetBalance(NongYePayGetBalanceRequest request) { return Send(request); } /// /// 单笔对公转账 /// /// /// public Task SingleCorporateTransfer(NongYePaySingleCorporateTransferRequest request) { return Send(request); } /// /// 查询转账状态 /// /// /// public Task GetTransferStatus(NongYePayGetTransferStatusRequest request) { return Send(request); } /// /// 查询转账详情 /// /// /// public Task GetTransferDetails(NongYePayGetTransferDetailsRequest request) { return SendWithFile(request); } /// /// 获取电子回单 /// /// /// public Task GetEreceipts(NongYePayGetEreceiptsRequest request) { return SendWithFile(request); } /// /// 下载电子回单 /// /// /// public async Task DownloadEreceipt(NongYePayDownloadEreceiptRequest request) { return await SendWithZip(request); } /// /// 实时下载电子回单 /// /// /// public async Task RealTimeDownloadEreceipt(NongYePayRealTimeDownloadEreceiptRequest request) { return await SendWithZip(request); } /// /// 获取银行地区 /// /// public Task>> GetBankAreas() { var list = new List> { new SelectOption("02", "天津市"), new SelectOption("03", "上海"), new SelectOption("04", "山西省"), new SelectOption("05", "内蒙古"), new SelectOption("06", "辽宁省"), new SelectOption("07", "吉林省"), new SelectOption("08", "黑龙江"), new SelectOption("10", "江苏省"), new SelectOption("11", "北京市"), new SelectOption("12", "安徽省"), new SelectOption("13", "福建省"), new SelectOption("14", "江西省"), new SelectOption("15", "山东省"), new SelectOption("16", "河南省"), new SelectOption("17", "湖北省"), new SelectOption("18", "湖南省"), new SelectOption("19", "浙江省"), new SelectOption("20", "广西区"), new SelectOption("21", "海南省"), new SelectOption("22", "四川省"), new SelectOption("23", "贵州省"), new SelectOption("24", "云南省"), new SelectOption("25", "西藏区"), new SelectOption("26", "陕西省"), new SelectOption("27", "甘肃省"), new SelectOption("28", "青海省"), new SelectOption("29", "宁夏区"), new SelectOption("30", "新疆区"), new SelectOption("31", "重庆市"), new SelectOption("34", "大连市"), new SelectOption("38", "青岛市"), new SelectOption("39", "宁波市"), new SelectOption("40", "厦门市"), new SelectOption("41", "深圳市"), new SelectOption("44", "广东省"), new SelectOption("50", "河北省"), new SelectOption("71", "台湾省"), new SelectOption("81", "营业部"), new SelectOption("97", "香港"), new SelectOption("98", "澳门"), new SelectOption("99", "总行"), }; return Task.FromResult(list); } /// /// 获取银行 /// /// /// public Task GetBanks(NongYePayGetBanksRequest request) { return SendWithFile(request); } public string GetSeqNo() { var now = DateTime.Now; var random = StringUtils.GenerateRandomString(6); return $"{now:yyyyMMddHHmmssfff}{random}"; } private async Task> GetZipFiles(NongYePayZipResponse response) { var list = new List(); if (response != null && response.RespSource == "0" && response.Cmp != null && response.Cmp.BatchFileName.IsNotNull()) { var memoryStream = new MemoryStream(); if (options.Value.RemoteFileUrl.IsNotNull()) { response.ZipFileName = response.Cmp.BatchFileName; var url = $"{options.Value.RemoteFileUrl}/api/file/download"; var command = new DownloadFileCommand { Scene = "NongYePay", Path = response.Cmp.BatchFileName }; var timestamp = DateTime.Now.ToTimeStamp(true); var sign = $"POST|/api/file/download|{command.ToJson()}|{options.Value.RemoteFilePrivateKey}|{timestamp}".ToMD5Encrypt(); using var stream = await httpRemoteService.PostAsStreamAsync(url, builder => builder .SetJsonContent(command) .WithHeader("x-timestamp", timestamp, replace: true) .WithHeader("x-sign", sign, replace: true)); stream.CopyTo(memoryStream); memoryStream.Position = 0; } else if (options.Value.FilePath.IsNotNull()) { response.ZipFileName = $"{options.Value.FilePath}{response.Cmp.BatchFileName}"; if (File.Exists(response.ZipFileName)) { using var stream = File.OpenRead(response.ZipFileName); stream.CopyTo(memoryStream); memoryStream.Position = 0; } } using (var archive = new ZipArchive(memoryStream)) { foreach (var entry in archive.Entries) { if (entry.FullName.EndsWith(".pdf")) { using (var stream = entry.Open()) { var ms = new MemoryStream(); stream.CopyTo(ms); ms.Position = 0; list.Add(new NongYePayResponseZipFile { FileName = entry.FullName, Stream = ms }); } } } } } return list; } private async Task> GetList(NongYePayBaseResponse response, ThreeResourceLog log) where T : class, new() { var list = new List(); if (response.RespSource == "0" && response.FileFlag == "1" && response.Cmp.BatchFileName.IsNotNull()) { var lines = new List(); if (options.Value.RemoteFileUrl.IsNotNull()) { var url = $"{options.Value.RemoteFileUrl}/api/file/download"; var command = new DownloadFileCommand { Scene = "NongYePay", Path = response.Cmp.BatchFileName }; var timestamp = DateTime.Now.ToTimeStamp(true); var sign = $"POST|/api/file/download|{command.ToJson()}|{options.Value.RemoteFilePrivateKey}|{timestamp}".ToMD5Encrypt(); var buffer = await httpRemoteService.PostAsByteArrayAsync(url, builder => builder .SetJsonContent(command) .WithHeader("x-timestamp", timestamp, replace: true) .WithHeader("x-sign", sign, replace: true)); var text = Encoding.GetEncoding("GBK").GetString(buffer); lines = text.Split("\n").Where(it => it.IsNotNull()).ToList(); } else if (options.Value.FilePath.IsNotNull()) { var fileName = $"{options.Value.FilePath}{response.Cmp.BatchFileName}"; if (File.Exists(fileName)) { var array = await File.ReadAllLinesAsync(fileName, Encoding.GetEncoding("GBK")); lines = array.ToList(); } } if (lines.IsNotNull()) { var props = typeof(T).GetProperties(); foreach (var line in lines) { var item = new T(); var columns = line.Split("|"); for (int i = 0; i < columns.Count() - 1; i++) { props[i].SetValue(item, columns[i]); } list.Add(item); } } log.UpdatedTime = DateTimeOffset.Now; var json = list.ToJson(); log.Response += $"\n{json}"; await repThreeResourceLog.UpdateNowAsync(log); } return list; } private async Task SendWithFile(TRequest request) where TRequest : NongYePayBaseRequest where TResponse : NongYePayBaseResponse, new() where TResponseItem : class, new() { var response = await SendWithLog(request); response.response.Items = await GetList(response.response, response.log); return response.response; } private async Task SendWithZip(TRequest request) where TRequest : NongYePayBaseRequest where TResponse : NongYePayZipResponse, new() { var response = await SendWithLog(request); response.response.Items = await GetZipFiles(response.response); return response.response; } private async Task Send(TRequest request) where TRequest : NongYePayBaseRequest where TResponse : NongYePayBaseResponse, new() { var response = await SendWithLog(request); return response.response; } private async Task<(TResponse response, ThreeResourceLog log)> SendWithLog(TRequest request) where TRequest : NongYePayBaseRequest where TResponse : NongYePayBaseResponse, new() { try { var logier = JwtUtils.GetCurrentLogier(); var now = DateTime.Now; request.CorpNo = options.Value.CorpNo; request.OpNo = options.Value.OpNo; request.AuthNo = ""; request.Sign = ""; request.ReqDate = now.ToString("yyyyMMdd"); request.ReqTime = now.ToString("HHmmss"); request.ReqSeqNo = request.ReqSeqNo ?? GetSeqNo(); request.ChannelType = "ERP"; request.ProductID = "ICC"; string requestXml = SerializeRequest(request); var log = new ThreeResourceLog { CreatedTime = DateTimeOffset.Now, Id = IDGen.NextID(), TraceId = App.GetTraceId(), Method = EnumResourceMethod.Post, Domain = $"{options.Value.Ip}:{options.Value.Port}", Path = request.CCTransCode, CreatedUserId = logier?.Id, CreatedChannelId = logier?.ChannelId, Request = requestXml, }; await repThreeResourceLog.InsertNowAsync(log); var stopwatch = Stopwatch.StartNew(); var responseXml = await SendRequest(requestXml); stopwatch.Stop(); var response = DeserializeResponse(responseXml); log.UpdatedTime = DateTimeOffset.Now; log.Response = responseXml; log.IsSuccess = response.RespSource == "0"; log.ElapsedMilliseconds = stopwatch.ElapsedMilliseconds; await repThreeResourceLog.UpdateNowAsync(log); return (response, log); } catch (Exception ex) { return (new TResponse { RespSource = "500", RespInfo = ex.Message }, null); } } /// /// 泛型请求类转XML /// private string SerializeRequest(T request) { var serializer = new XmlSerializer(typeof(T)); var ns = new XmlSerializerNamespaces(); ns.Add("", ""); var settings = new XmlWriterSettings { OmitXmlDeclaration = true, Indent = false, Encoding = Encoding.UTF8 }; using var stream = new MemoryStream(); using var writer = XmlWriter.Create(stream, settings); serializer.Serialize(writer, request, ns); // 转换为GBK编码 stream.Position = 0; using var reader = new StreamReader(stream, Encoding.UTF8); string utf8Xml = reader.ReadToEnd(); return Encoding.GetEncoding("GBK").GetString(Encoding.GetEncoding("GBK").GetBytes(utf8Xml)); } /// /// XML转泛型应答类 /// private T DeserializeResponse(string gbkXml) { var buffer = Encoding.GetEncoding("GBK").GetBytes(gbkXml); using var ms = new MemoryStream(buffer); using var sr = new StreamReader(ms, Encoding.GetEncoding("GBK")); var serializer = new XmlSerializer(typeof(T)); using var xr = XmlReader.Create(sr); return (T)serializer.Deserialize(xr); } /// /// 发送请求并接收应答 /// private async Task SendRequest(string requestXml) { byte[] requestBytes = Encoding.GetEncoding("GBK").GetBytes(requestXml); // 2. 构建7字节包头(加密标志0 + 数据长度,右补空格) byte[] header = new byte[7]; header[0] = 0x30; // 加密标志:0(ASCII码) string requestLengthStr = requestBytes.Length.ToString().PadRight(6, ' '); // 后6字节:长度右补空格 Buffer.BlockCopy(Encoding.ASCII.GetBytes(requestLengthStr), 0, header, 1, 6); // 3. 合并包头+数据包 byte[] sendData = new byte[header.Length + requestBytes.Length]; Buffer.BlockCopy(header, 0, sendData, 0, header.Length); Buffer.BlockCopy(requestBytes, 0, sendData, header.Length, requestBytes.Length); // 4. Socket发送(带超时) using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.SendTimeout = (int)TimeSpan.FromMinutes(5).TotalMilliseconds; socket.ReceiveTimeout = (int)TimeSpan.FromMinutes(5).TotalMilliseconds; try { await socket.ConnectAsync(options.Value.Ip, options.Value.Port); await socket.SendAsync(sendData); // 接收包头 byte[] responseHeader = new byte[7]; int headerLen = socket.Receive(responseHeader); if (headerLen != 7) throw Oops.Oh(EnumErrorCodeType.s510, "接收包头失败"); // 解析数据长度 string lengthStr = Encoding.ASCII.GetString(responseHeader, 1, 6).Trim(); if (!int.TryParse(lengthStr, out int dataLength)) throw Oops.Oh(EnumErrorCodeType.s510, $"解析数据长度失败:{lengthStr}"); // 接收数据包 byte[] responseBytes = new byte[dataLength]; int receivedLen = 0; while (receivedLen < dataLength) { int len = socket.Receive(responseBytes, receivedLen, dataLength - receivedLen, SocketFlags.None); receivedLen += len; } // 转换为GBK XML var responseXml = Encoding.GetEncoding("GBK").GetString(responseBytes); if (responseXml.IsNull()) { throw Oops.Oh(EnumErrorCodeType.s510, "未收到应答"); } return responseXml; } finally { socket.Dispose(); } } } }