sunpengfei
1 天以前 ab9b84ee42c872cd2277d2e3a863718355bcc6aa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
using Aop.Api.Domain;
using ApiTools.Core.Utils.NongYePayUtils.Models;
using Furion;
using Furion.DatabaseAccessor;
using Furion.DependencyInjection;
using Furion.DistributedIDGenerator;
using Furion.FriendlyException;
using Microsoft.Extensions.Options;
using NetTopologySuite.Algorithm;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
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(
            IOptions<NongYePayOptions> options,
            IRepository<ThreeResourceLog, LogDbContextLocator> repThreeResourceLog
        ) : ITransient
    {
        private readonly IOptions<NongYePayOptions> options = options;
        private readonly IRepository<ThreeResourceLog, LogDbContextLocator> repThreeResourceLog = repThreeResourceLog;
 
        /// <summary>
        /// 查询账户余额
        /// </summary>
        /// <returns>余额信息</returns>
        public Task<NongYePayGetBalanceResponse> GetBalance(NongYePayGetBalanceRequest request)
        {
            return Send<NongYePayGetBalanceRequest, NongYePayGetBalanceResponse>(request);
        }
 
        private async Task<TResponse> Send<TRequest, TResponse>(TRequest request)
            where TRequest : NongYePayBaseRequest
            where TResponse : NongYePayBaseResponse
        {
            var logier = JwtUtils.GetCurrentLogier();
            var now = DateTime.Now;
            var random = StringUtils.GenerateRandomString(6);
            request.CorpNo = options.Value.CorpNo;
            request.ReqDate = now.ToString("yyyyMMdd");
            request.ReqTime = now.ToString("HHmmss");
            request.ReqSeqNo = $"{now:yyyyMMddHHmmssfff}{random}";
            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.TransCode,
                CreatedUserId = logier?.Id,
                CreatedChannelId = logier?.ChannelId,
                Request = request.ToJson(),
            };
            await repThreeResourceLog.InsertNowAsync(log);
            var stopwatch = Stopwatch.StartNew();
            var responseXml = await SendRequest(requestXml);
            stopwatch.Stop();
            var response = DeserializeResponse<TResponse>(responseXml);
            log.UpdatedTime = DateTimeOffset.Now;
            log.Response = responseXml;
            log.IsSuccess = response.RespSource == "0";
            log.ElapsedMilliseconds = stopwatch.ElapsedMilliseconds;
            await repThreeResourceLog.UpdateNowAsync(log);
 
            if (!log.IsSuccess) throw Oops.Oh(EnumErrorCodeType.s510, $"农行接口异常:{response.RespInfo}");
 
            return response;
        }
 
        /// <summary>
        /// 泛型请求类转XML
        /// </summary>
        private string SerializeRequest<T>(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));
        }
 
        /// <summary>
        /// XML转泛型应答类
        /// </summary>
        private T DeserializeResponse<T>(string gbkXml)
        {
            var serializer = new XmlSerializer(typeof(T));
            using var stream = new MemoryStream(Encoding.UTF8.GetBytes(gbkXml));
            return (T)serializer.Deserialize(stream);
        }
 
        /// <summary>
        /// 发送请求并接收应答
        /// </summary>
        private async Task<string> 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();
            }
        }
    }
}