qinchaoyue 4 meses atrás
pai
commit
5ed87b6c11

+ 7 - 0
Hotline.sln

@@ -59,6 +59,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hotline.Logger", "src\Hotli
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hotline.WeChat", "src\Hotline.WeChat\Hotline.WeChat.csproj", "{75215667-65AF-4B7B-85E7-3140239B30CC}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TianQue.Sdk", "src\TianQue.Sdk\TianQue.Sdk.csproj", "{6CF27647-D0E0-4D17-80FB-3EE57864A2B4}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -153,6 +155,10 @@ Global
 		{75215667-65AF-4B7B-85E7-3140239B30CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{75215667-65AF-4B7B-85E7-3140239B30CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{75215667-65AF-4B7B-85E7-3140239B30CC}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6CF27647-D0E0-4D17-80FB-3EE57864A2B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6CF27647-D0E0-4D17-80FB-3EE57864A2B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6CF27647-D0E0-4D17-80FB-3EE57864A2B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6CF27647-D0E0-4D17-80FB-3EE57864A2B4}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -183,6 +189,7 @@ Global
 		{9F99C272-5BC2-452C-9D97-BC756AF04669} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 		{37784861-ABC0-41F4-87B4-2E08A89A2C42} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 		{75215667-65AF-4B7B-85E7-3140239B30CC} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
+		{6CF27647-D0E0-4D17-80FB-3EE57864A2B4} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {4B8EA790-BD13-4422-8D63-D6DBB77B823F}

+ 15 - 2
src/Hotline.Application.Tests/Application/SnapshotApplicationTest.cs

@@ -38,8 +38,9 @@ public class SnapshotApplicationTest : TestBase
     private readonly IOrderRepository _orderRepository;
     private readonly IOrderSnapshotRepository _orderSnapshotRepository;
     private readonly ISessionContext _sessionContext;
+    private readonly IGuiderSystemService _guiderSystemService;
 
-    public SnapshotApplicationTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor, ISnapshotApplication snapshotApplication, IIdentityAppService identityAppService, IRepository<RedPackRecord> redPackRecordRepository, IIndustryApplication industryApplication, IIndustryRepository industryRepository, IFileRepository fileRepository, OrderServiceMock orderServiceMock, IOrderRepository orderRepository, IOrderSnapshotRepository orderSnapshotRepository, IThirdIdentiyService thirdService, IThirdAccountRepository thirdAccount, ISessionContext sessionContext) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
+    public SnapshotApplicationTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor, ISnapshotApplication snapshotApplication, IIdentityAppService identityAppService, IRepository<RedPackRecord> redPackRecordRepository, IIndustryApplication industryApplication, IIndustryRepository industryRepository, IFileRepository fileRepository, OrderServiceMock orderServiceMock, IOrderRepository orderRepository, IOrderSnapshotRepository orderSnapshotRepository, IThirdIdentiyService thirdService, IThirdAccountRepository thirdAccount, ISessionContext sessionContext, IGuiderSystemService guiderSystemService) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
     {
         _snapshotApplication = snapshotApplication;
         _identityAppService = identityAppService;
@@ -52,6 +53,7 @@ public class SnapshotApplicationTest : TestBase
         _orderSnapshotRepository = orderSnapshotRepository;
         SetWeiXin();
         _sessionContext = sessionContext;
+        _guiderSystemService = guiderSystemService;
     }
 
     [Fact]
@@ -242,7 +244,7 @@ public class SnapshotApplicationTest : TestBase
     [Fact]
     public async Task AddVolunteerReport_Test()
     {
-        await _snapshotApplication.AddVolunteerAsync(new AddVolunteerInDto {Name = _sessionContext.UserName, PhoneNumber = _sessionContext.Phone }, CancellationToken.None);
+        await _snapshotApplication.AddVolunteerAsync(new AddVolunteerInDto { Name = _sessionContext.UserName, PhoneNumber = _sessionContext.Phone }, CancellationToken.None);
         var inDto = _fixture.Create<AddVolunteerReportInDto>();
         foreach (var item in inDto.Files)
         {
@@ -262,6 +264,17 @@ public class SnapshotApplicationTest : TestBase
         third.InvitationCode.ShouldBe(code);
     }
 
+    [Fact]
+    public async Task PostOrder_Test()
+    {
+        var orderSnapshot = await _orderSnapshotRepository.Queryable()
+            .OrderByDescending(m => m.CreationTime).FirstAsync();
+        var order = await _orderRepository.GetAsync(orderSnapshot.Id);
+        var token = new ThirdTokenDto { AppId = "TAjYAYuA", Secret = "c01eb299b9d784bf55681af4da86bab6ba428101" };
+        var result = await _guiderSystemService.PostOrder(order, orderSnapshot, token);
+        result.ShouldNotBeNull();
+    }
+
     [Fact]
     public async Task GetPractitionerItems_Test()
     {

+ 1 - 0
src/Hotline.Application.Tests/Hotline.Application.Tests.csproj

@@ -41,6 +41,7 @@
     <ProjectReference Include="..\Hotline.Api\Hotline.Api.csproj" />
     <ProjectReference Include="..\Hotline.Application\Hotline.Application.csproj" />
     <ProjectReference Include="..\Hotline.Repository.SqlSugar\Hotline.Repository.SqlSugar.csproj" />
+    <ProjectReference Include="..\TianQue.Sdk\TianQue.Sdk.csproj" />
     <ProjectReference Include="..\Tr.Sdk\Tr.Sdk.csproj" />
     <ProjectReference Include="..\XF.Domain.Repository\XF.Domain.Repository.csproj" />
     <ProjectReference Include="..\XF.Domain\XF.Domain.csproj" />

+ 36 - 0
src/Hotline.Application.Tests/Infrastructure/TianQueTest.cs

@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TianQue.Sdk;
+
+namespace Hotline.Application.Tests.Infrastructure;
+public class TianQueTest
+{
+    [Fact]
+    public void Test_GenerateNonce()
+    {
+        // Arrange
+        var nonce = SignUtils.GenerateNonce();
+
+        // Assert
+        Assert.NotNull(nonce);
+        Assert.Equal(64, nonce.Length);
+        // b55dfdedba900437d486e70e5fb78ed50afaeb910a2346f16ef03af656f8bb0b
+    }
+
+    [Fact]
+    public async Task PostAcceptInfo_Test()
+    {
+        // Arrange
+        //var tiqnQueService = new TiqnQueService();
+
+        //// Act
+        //var result = await tiqnQueService.PostAcceptInfo();
+
+        //// Assert
+        //Assert.Equal("ok", result);
+    }
+
+}

+ 3 - 0
src/Hotline.Application.Tests/Startup.cs

@@ -59,6 +59,8 @@ using Hotline.Application.Tests.SqlSuger;
 using Microsoft.AspNetCore.Http;
 using Hotline.WeChat;
 using Hotline.Api.Controllers.Snapshot;
+using Hotline.Snapshot.Interfaces;
+using TianQue.Sdk;
 
 namespace Hotline.Application.Tests;
 public class Startup
@@ -181,6 +183,7 @@ public class Startup
             services.AddScoped<KnowledgeServiceMock>();
             services.AddScoped<XingTangCallsSyncJob>();
             services.AddXingTangDb(callCenterConfiguration.XingTang);
+            services.AddScoped<IGuiderSystemService, TiqnQueService>();
             //ServiceLocator.Instance = services.BuildServiceProvider();
         }
 

+ 4 - 4
src/Hotline.Share/Enums/Snapshot/EReadPackUserType.cs

@@ -3,8 +3,8 @@ namespace Hotline.Share.Enums.Snapshot;
 
 /// <summary>
 /// 红包领取人类型;
-/// 0: 市民;
-/// 1: 网格员;
+/// 1: 市民;
+/// 2: 网格员;
 /// </summary>
 public enum EReadPackUserType
 {
@@ -12,11 +12,11 @@ public enum EReadPackUserType
     /// 市民
     /// </summary>
     [Description("市民")]
-    Citizen = 0,
+    Citizen = 1,
 
     /// <summary>
     /// 网络员
     /// </summary>
     [Description("网络员")]
-    Guider = 1
+    Guider = 2
 }

+ 7 - 0
src/Hotline.Share/Tools/DateTimeExtensions.cs

@@ -1,4 +1,5 @@
 using Hotline.Share.Dtos.CallCenter;
+using Novacode;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -30,4 +31,10 @@ public static class DateTimeExtensions
     { 
         return dateTime.ToString("yyyy-MM-dd HH:mm:ss");
     }
+
+    public static long ToUnixTimeMilliseconds(this DateTime value)
+    {
+        return new DateTimeOffset(value).ToUnixTimeMilliseconds();
+    }
+
 }

+ 60 - 2
src/Hotline.Share/Tools/ObjectExtensions.cs

@@ -1,16 +1,74 @@
 using Newtonsoft.Json;
 using System;
+using System.Collections;
 using System.Collections.Generic;
+using System.ComponentModel;
 using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Reflection;
 using System.Text;
+using System.Text.Json;
 using System.Threading.Tasks;
 using static Lucene.Net.Util.Fst.Util;
 
 namespace Hotline.Share.Tools;
 public static class ObjectExtensions
 {
+
+    /// <summary>
+    /// obj 转换成 dictionary
+    /// </summary>
+    /// <param name="source"> The source.  </param>
+    /// <returns> The <see cref="IDictionary"/>.  </returns>
+    public static IDictionary<string, object> ToDictionary(this object source)
+    {
+        return source.ToDictionary<object>();
+    }
+
+    /// <summary>
+    /// obj 转换成 IDictionary
+    /// </summary>
+    /// <param name="source"> The source.  </param>
+    /// <typeparam name="T"> 返回类型 </typeparam>
+    /// <returns> The <see cref="IDictionary"/>.  </returns>
+    public static IDictionary<string, T> ToDictionary<T>(this object source)
+    {
+        if (source == null)
+        {
+            throw new ArgumentNullException("source", "Unable to convert object to a dictionary. The source object is null.");
+        }
+
+        var dictionary = new Dictionary<string, T>();
+        if (source is Dictionary<string, object>)
+        {
+            foreach (var property in (Dictionary<string, object>)source)
+            {
+                dictionary.Add(property.Key, (T)property.Value);
+            }
+        }
+        else
+        {
+            foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(source))
+            {
+                AddPropertyToDictionary<T>(property, source, dictionary);
+            }
+        }
+
+        return dictionary;
+    }
+
+    private static void AddPropertyToDictionary<T>(PropertyDescriptor property, object source, Dictionary<string, T> dictionary)
+    {
+        object value = property.GetValue(source);
+        if (IsOfType<T>(value))
+            dictionary.Add(property.Name, (T)value);
+    }
+
+    private static bool IsOfType<T>(object value)
+    {
+        return value is T;
+    }
+
     /// <summary>
     /// 遍历类属性
     /// </summary>
@@ -30,9 +88,9 @@ public static class ObjectExtensions
         }
     }
 
-    public static string ToJson(this object obj)
+    public static string ToJson(this object obj, JsonSerializerSettings options = null)
     {
-        return obj == null ? default(string) : JsonConvert.SerializeObject(obj);
+        return obj == null ? default(string) : JsonConvert.SerializeObject(obj, options);
     }
 
     public static T FromJson<T>(this string json)

+ 18 - 0
src/Hotline/Snapshot/Interfaces/IGuiderSystemService.cs

@@ -0,0 +1,18 @@
+using Hotline.Orders;
+using Hotline.Share.Dtos.Snapshot;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Snapshot.Interfaces;
+
+/// <summary>
+/// 网格员系统服务
+/// </summary>
+public interface IGuiderSystemService
+{
+   Task<string> PostOrder(Order order, OrderSnapshot orderSnapshot, ThirdTokenDto tokenDto);
+}
+

+ 110 - 0
src/TianQue.Sdk/Models/AcceptInfo.cs

@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TianQue.Sdk.Models;
+public class AcceptInfo
+{
+    /// <summary>
+    /// 唯一标识
+    /// </summary>
+    [Required]
+    [MaxLength(100)]
+    public string ReplyCode { get; set; }
+
+    /// <summary>
+    /// 区域Id
+    /// </summary>
+    [Required]
+    [MaxLength(20)]
+    public long OrgId { get; set; }
+
+    /// <summary>
+    /// 部门编码
+    /// </summary>
+    [MaxLength(20)]
+    public string DepartmentNo { get; set; }
+
+    /// <summary>
+    /// 诉求类型名称
+    /// </summary>
+    [MaxLength(50)]
+    public string TypeName { get; set; }
+
+    /// <summary>
+    /// 事发时间
+    /// </summary>
+    [Required]
+    public DateTime OccurDate { get; set; }
+
+    /// <summary>
+    /// 详细地址
+    /// </summary>
+    [Required]
+    [MaxLength(200)]
+    public string DetailAddress { get; set; }
+
+    /// <summary>
+    /// 线索主题
+    /// </summary>
+    [Required]
+    [MaxLength(200)]
+    public string Topic { get; set; }
+
+    /// <summary>
+    /// 详细内容
+    /// </summary>
+    [Required]
+    [MaxLength(2000)]
+    public string DetailContent { get; set; }
+
+    /// <summary>
+    /// 附件
+    /// </summary>
+    public IList<FileInfo> Files { get; set; }
+
+    /// <summary>
+    /// 反映人信息
+    /// </summary>
+    public IList<PersonInfo> PersonList { get; set; }
+}
+
+public class PersonInfo
+{
+    /// <summary>
+    /// 反映人姓名
+    /// </summary>
+    [MaxLength(20)]
+    public string ReflectUserName { get; set; }
+
+    /// <summary>
+    /// 反映人联系方式
+    /// </summary>
+    [MaxLength(20)]
+    public string ReflectPhone { get; set; }
+
+    /// <summary>
+    /// 反映人身份证号
+    /// </summary>
+    [MaxLength(20)]
+    public string ReflectCardId { get; set; }
+}
+
+public class FileInfo
+{
+    /// <summary>
+    /// 文件名称(带后缀)
+    /// </summary>
+    [Required]
+    [MaxLength(30)]
+    public string FileName { get; set; }
+
+    /// <summary>
+    /// 文件内容(base64编码)
+    /// </summary>
+    [Required]
+    public string FileContent { get; set; }
+}

+ 31 - 0
src/TianQue.Sdk/Models/ApiReponse.cs

@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TianQue.Sdk.Models;
+public class ApiReponse<T>
+{
+    // {"code":400,"success":false,"data":{},"msg":"detailContent:不能为空"}
+
+    /// <summary>
+    /// Code
+    /// </summary>
+    public int Code { get; set; }
+
+    /// <summary>
+    /// 是否成功
+    /// </summary>
+    public bool Success { get; set; }
+
+    /// <summary>
+    /// 数据
+    /// </summary>
+    public T Data { get; set; }
+
+    /// <summary>
+    /// 信息
+    /// </summary>
+    public string Msg { get; set; }
+}

+ 114 - 0
src/TianQue.Sdk/SignUtils.cs

@@ -0,0 +1,114 @@
+using Hotline.Share.Tools;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TianQue.Sdk;
+public class SignUtils
+{
+    private static readonly string SIGN_KEY = "_sign";
+    private static readonly HashSet<string> SKIP_SIGN_PARAMS = new HashSet<string> { SIGN_KEY };
+
+    public static string Sign(string appSecret, Dictionary<string, object> headers, string body)
+    {
+        var sorted = BuildSignSortedMap(headers, body);
+        var stringBuilder = new StringBuilder();
+        stringBuilder.Append(appSecret);
+        string result = string.Join("",
+            sorted.SelectMany(
+                kvp => kvp.Value.Select(value => $"{kvp.Key}{value}")
+            )
+        );
+        stringBuilder.Append(result);
+        stringBuilder.Append(appSecret);
+        var str = stringBuilder.ToString();
+        return MD5Hex(str);
+    }
+
+    public static SortedDictionary<string, SortedSet<string>> BuildSignSortedMap(Dictionary<string, object> keyValueParams, string body)
+    {
+        var sortedMap = new SortedDictionary<string, SortedSet<string>>();
+
+        if (keyValueParams != null && keyValueParams.Count > 0)
+        {
+            foreach (var entry in keyValueParams)
+            {
+                if (entry.Key != SIGN_KEY)
+                {
+                    sortedMap[entry.Key] = new SortedSet<string>
+                    {
+                        entry.Value.ToString()
+                    };
+                }
+            }
+        }
+
+        if (!string.IsNullOrEmpty(body))
+        {
+            var a = new SortedSet<string>
+            {
+                body
+            };
+            sortedMap["_body"] = a;
+        }
+
+        return sortedMap;
+    }
+
+    private static string MD5Hex(string data)
+    {
+        using (var md5 = MD5.Create()) // 使用 MD5 哈希算法
+        {
+            byte[] bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(data)); // 将字符串转换为字节并计算哈希
+            return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant(); // 转换为十六进制字符串(小写)
+        }
+    }
+
+    public static string GenerateNonce()
+    {
+        // 生成随机 UUID 字符串
+        string uuid = Guid.NewGuid().ToString();
+
+        // 生成 10 个随机字符(包含字母和数字)
+        string randomString = GenerateRandomString(10);
+
+        // 使用 HMAC-SHA256 计算哈希值
+        return ComputeHmacSha256(uuid, randomString);
+    }
+
+    private static string GenerateRandomString(int length)
+    {
+        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+        var random = new Random();
+        var result = new StringBuilder(length);
+        for (int i = 0;i < length;i++)
+        {
+            result.Append(chars[random.Next(chars.Length)]);
+        }
+        return result.ToString();
+    }
+
+    private static string ComputeHmacSha256(string key, string data)
+    {
+        // 将密钥和数据转换为字节数组
+        byte[] keyBytes = Encoding.UTF8.GetBytes(key);
+        byte[] dataBytes = Encoding.UTF8.GetBytes(data);
+
+        // 使用 HMACSHA256 生成 HMAC 值
+        using (HMACSHA256 hmac = new HMACSHA256(keyBytes))
+        {
+            byte[] hashBytes = hmac.ComputeHash(dataBytes);
+
+            // 将字节数组转换为十六进制字符串
+            StringBuilder hex = new StringBuilder(hashBytes.Length * 2);
+            foreach (byte b in hashBytes)
+            {
+                hex.AppendFormat("{0:x2}", b);
+            }
+            return hex.ToString();
+        }
+    }
+}

+ 109 - 0
src/TianQue.Sdk/TQHttpClient.cs

@@ -0,0 +1,109 @@
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Tools;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using Renci.SshNet;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TianQue.Sdk;
+public class TQHttpClient
+{
+    private readonly string AppSecret;
+    private readonly string AppKey;
+
+    /// <summary>
+    /// ssh服务器ip
+    /// </summary>
+    private readonly string SSHHost;
+
+    /// <summary>
+    /// ssh服务器端口
+    /// </summary>
+    private readonly int SSHPort;
+
+    /// <summary>
+    /// ssh账号
+    /// </summary>
+    private readonly string SSHUserName;
+
+    /// <summary>
+    /// ssh密码
+    /// </summary>
+    private readonly string SSHPassword;
+
+    private readonly SshClient _sshClient;
+
+    private readonly ILogger<TQHttpClient> _logger;
+
+    public TQHttpClient(string appSecret, string appKey, ILogger<TQHttpClient> logger)
+    {
+        AppSecret = appSecret;
+        AppKey = appKey;
+        _logger = logger;
+    }
+
+    public TQHttpClient(string appSecret, string appKey, string sshHost, int sshPort, string sshUserName, string sshPassword, ILogger<TQHttpClient> logger)
+    {
+        AppSecret = appSecret;
+        AppKey = appKey;
+        SSHHost = sshHost;
+        SSHPort = sshPort;
+        SSHUserName = sshUserName;
+        SSHPassword = sshPassword;
+        _logger = logger;
+        _sshClient = new SshClient(SSHHost, SSHPort, SSHUserName, SSHPassword);
+    }
+
+    public async Task<T> PostAsync<T>(Uri uri, object data)
+    {
+        try
+        {
+            var headers = new Dictionary<string, object> { { "_timeStamp", DateTime.Now.ToUnixTimeMilliseconds() }, { "_nonce", SignUtils.GenerateNonce() } };
+            var sortedDic = new SortedDictionary<string, object>(data.ToDictionary());
+            var body = sortedDic.ToJson(new JsonSerializerSettings {ContractResolver = new CamelCasePropertyNamesContractResolver()});
+            var postJsonSign = SignUtils.Sign(AppSecret, headers, body);
+            headers.Add("_sign", postJsonSign);
+            headers.Add("_appKey", AppKey);
+
+            if (SSHHost.NotNullOrEmpty() && SSHPort != 0 && SSHUserName.NotNullOrEmpty() && SSHPassword.NotNullOrEmpty())
+            {
+                _sshClient.Connect();
+                var forwardedPort = new ForwardedPortLocal("127.0.0.1", 8090, uri.Host, (uint)uri.Port);
+                _sshClient.AddForwardedPort(forwardedPort);
+                forwardedPort.Start();
+                uri = new Uri("http://127.0.0.1:8090" + uri.AbsolutePath);
+            }
+            // var url = "http://127.0.0.1:8090/api/v1/test/accept/saveAcceptInfoApi";
+
+            var content = new StringContent(body, Encoding.GetEncoding("UTF-8"));
+            content.Headers.ContentType = new MediaTypeHeaderValue("application/json") { CharSet = "UTF-8" };
+            foreach (var header in headers)
+            {
+                content.Headers.Add(header.Key, header.Value.ToString());
+            }
+            var resp = await (new HttpClient()).PostAsync(uri.ToString(), content);
+            resp.EnsureSuccessStatusCode();
+            var result = await resp.Content.ReadAsStringAsync();
+            _logger.LogInformation($"天阙交互; url: {uri.ToString()}, headers: {headers.ToJson()} request: {body}, response: {result}");
+            return result.FromJson<T>();
+        }
+        catch (Exception e)
+        {
+            _logger.LogError(e.Message);
+            throw;
+        }
+        finally
+        {
+            if (_sshClient.IsConnected)
+            {
+                _sshClient.Disconnect();
+            }
+        }
+    }
+}

+ 18 - 0
src/TianQue.Sdk/TianQue.Sdk.csproj

@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="SSH.NET" Version="2024.2.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Hotline.Share\Hotline.Share.csproj" />
+    <ProjectReference Include="..\Hotline\Hotline.csproj" />
+  </ItemGroup>
+
+</Project>

+ 72 - 0
src/TianQue.Sdk/TiqnQueService.cs

@@ -0,0 +1,72 @@
+using Hotline.Orders;
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Tools;
+using Hotline.Snapshot;
+using Hotline.Snapshot.Interfaces;
+using Mapster;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using Renci.SshNet;
+using System.Net.Http.Headers;
+using System.Text;
+using TianQue.Sdk.Models;
+using XF.Domain.Dependency;
+
+namespace TianQue.Sdk;
+
+public class TiqnQueService : IGuiderSystemService, IScopeDependency
+{
+    private readonly string appSecret = "c01eb299b9d784bf55681af4da86bab6ba428101";
+    private readonly string appKey = "TAjYAYuA";
+    private readonly ILogger<TQHttpClient> _logger;
+    private readonly string _url = "http://10.0.188.11:6090/api/v1/test/accept/saveAcceptInfoApi";
+
+    public TiqnQueService(ILogger<TQHttpClient> logger)
+    {
+        _logger = logger;
+    }
+
+    public async Task<string> PostAcceptInfo()
+    {
+
+        var bodyDic = new SortedDictionary<string, object>
+        {
+            { "p2", "p2"},
+            { "p1", "p1"}
+        };
+
+
+        return "ok";
+    }
+
+    public async Task<string> PostOrder(Order order, OrderSnapshot orderSnapshot, ThirdTokenDto tokenDto)
+    {
+        TQHttpClient httpClient;
+#if DEBUG
+         httpClient = new TQHttpClient(appSecret, appKey, "171.94.154.2", 22, "root" , "ZGbyy@2024!", _logger);
+#else
+         httpClient = new TQHttpClient(appSecret, appKey, _logger);
+#endif
+        var acceptInfo = order.Adapt<AcceptInfo>();
+        acceptInfo.ReplyCode = order.No!; // 唯一标识
+        acceptInfo.OrgId = order.ActualHandleOrgAreaCode.NotNullOrEmpty() ? long.Parse(order.ActualHandleOrgAreaCode) : 0; // 区域Id
+        acceptInfo.DepartmentNo = order.ActualHandleOrgCode!; // 部门编码
+        acceptInfo.TypeName = order.AcceptType!; // 诉求类型名称
+        acceptInfo.OccurDate = order.CreationTime; // 事发时间
+        acceptInfo.DetailAddress = order.FullAddress!; // 详细地址
+        acceptInfo.Topic = order.Title!; // 线索主题
+        acceptInfo.DetailContent = order.Content!; // 详细内容
+
+        // 反映人信息
+        acceptInfo.PersonList = new List<PersonInfo>
+        {
+            new() {
+                // ReflectCardId = // 反映人身份证号
+                ReflectPhone = order.Contact!,
+                ReflectUserName = order.FromName!,
+            } 
+        };
+        var result = await httpClient.PostAsync<ApiReponse<string>>(new Uri(_url), acceptInfo);
+        return result.ToJson();
+    }
+}