From e7be0ce07e78c05e3ce3384eb5b2db88228ed383 Mon Sep 17 00:00:00 2001 From: sunkaixuan <610262374@qq.com> Date: Thu, 21 Mar 2024 02:21:52 +0800 Subject: [PATCH] Add QuestDb rest api --- .../HttpExtensions.cs | 15 ++ .../QuestDbRestAPHelper.cs | 45 +++++ .../QuestDbRestAPI.cs | 166 ++++++++++++++++++ .../SqlSugar.QuestDb.RestAPI.csproj | 16 ++ Src/Asp.NetCore2/SqlSugarCore.sln | 17 +- 5 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 Src/Asp.NetCore2/SqlSugar.QuestDb.RestApi/HttpExtensions.cs create mode 100644 Src/Asp.NetCore2/SqlSugar.QuestDb.RestApi/QuestDbRestAPHelper.cs create mode 100644 Src/Asp.NetCore2/SqlSugar.QuestDb.RestApi/QuestDbRestAPI.cs create mode 100644 Src/Asp.NetCore2/SqlSugar.QuestDb.RestApi/SqlSugar.QuestDb.RestAPI.csproj diff --git a/Src/Asp.NetCore2/SqlSugar.QuestDb.RestApi/HttpExtensions.cs b/Src/Asp.NetCore2/SqlSugar.QuestDb.RestApi/HttpExtensions.cs new file mode 100644 index 000000000..1643d0a5e --- /dev/null +++ b/Src/Asp.NetCore2/SqlSugar.QuestDb.RestApi/HttpExtensions.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace SqlSugar +{ + public static class ISqlSugarClientExtensions + { + public static QuestDbRestAPI RestApi(this ISqlSugarClient db) + { + return new QuestDbRestAPI(db); + } + } +} diff --git a/Src/Asp.NetCore2/SqlSugar.QuestDb.RestApi/QuestDbRestAPHelper.cs b/Src/Asp.NetCore2/SqlSugar.QuestDb.RestApi/QuestDbRestAPHelper.cs new file mode 100644 index 000000000..89c8477ab --- /dev/null +++ b/Src/Asp.NetCore2/SqlSugar.QuestDb.RestApi/QuestDbRestAPHelper.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Collections; +using System.Globalization; +using System.IO; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using Newtonsoft.Json; +using CsvHelper; + +namespace SqlSugar +{ + internal class QuestDbRestAPHelper + { + + /// + /// 逐行读取,包含空行 + /// + /// + /// + public static List SplitByLine(string text) + { + List lines = new List(); + byte[] array = Encoding.UTF8.GetBytes(text); + using (MemoryStream stream = new MemoryStream(array)) + { + using (var sr = new StreamReader(stream)) + { + string line = sr.ReadLine(); + while (line != null) + { + lines.Add(line); + line = sr.ReadLine(); + } + } + } + + return lines; + } + + + } +} diff --git a/Src/Asp.NetCore2/SqlSugar.QuestDb.RestApi/QuestDbRestAPI.cs b/Src/Asp.NetCore2/SqlSugar.QuestDb.RestApi/QuestDbRestAPI.cs new file mode 100644 index 000000000..46ece2d1b --- /dev/null +++ b/Src/Asp.NetCore2/SqlSugar.QuestDb.RestApi/QuestDbRestAPI.cs @@ -0,0 +1,166 @@ +using CsvHelper; +using Newtonsoft.Json; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using System.Web; + +namespace SqlSugar +{ + + public class QuestDbRestAPI + { + internal string url = string.Empty; + internal string authorization = string.Empty; + ISqlSugarClient db; + public QuestDbRestAPI(ISqlSugarClient db) + { + this.db = db; + string host = ""; + string username = ""; + string password = ""; + + url = host; + if (url.EndsWith("/")) + url = url.Remove(url.Length - 1); + + if (!url.ToLower().StartsWith("http")) + url = $"http://{url}"; + //生成TOKEN + if (!string.IsNullOrWhiteSpace(username) && !string.IsNullOrWhiteSpace(password)) + { + var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}")); + authorization = $"Basic {base64}"; + } + } + + + public async Task ExecuteCommandAsync(string sql) + { + //HTTP GET 执行SQL + var result = string.Empty; + var client = new HttpClient(); + var url = $"{this.url}/exec?query={HttpUtility.UrlEncode(sql)}"; + if (!string.IsNullOrWhiteSpace(authorization)) + client.DefaultRequestHeaders.Add("Authorization", authorization); + var httpResponseMessage = await client.GetAsync(url); + result = await httpResponseMessage.Content.ReadAsStringAsync(); + return result; + } + + public string ExecuteCommand(string sql) + { + return ExecuteCommandAsync(sql).GetAwaiter().GetResult(); + } + /// + /// 批量快速插入 + /// + /// + /// + /// 导入时,时间格式 默认:yyyy/M/d H:mm:ss + /// + public async Task BulkCopyAsync(List insertList, string dateFormat = "yyyy/M/d H:mm:ss") where T : class + { + + if (string.IsNullOrWhiteSpace(url)) + { + throw new Exception("BulkCopy功能需要启用RestAPI,程序启动时执行:RestAPIExtension.UseQuestDbRestAPI(\"localhost:9000\", \"username\", \"password\")"); + } + var result = 0; + var fileName = $"{Guid.NewGuid()}.csv"; + var filePath = Path.Combine(AppContext.BaseDirectory, fileName); + try + { + var client = new HttpClient(); + var boundary = "---------------" + DateTime.Now.Ticks.ToString("x"); + var list = new List(); + var name = db.EntityMaintenance.GetEntityInfo().DbTableName; //获取表名 + db.DbMaintenance.GetColumnInfosByTableName(name).ForEach(d => + { + if (d.DataType == "TIMESTAMP") + { + list.Add(new Hashtable() + { + { "name", d.DbColumnName }, + { "type", d.DataType }, + { "pattern", dateFormat} + }); + } + else + { + list.Add(new Hashtable() + { + { "name", d.DbColumnName }, + { "type", d.DataType } + }); + } + }); + var schema = JsonConvert.SerializeObject(list); + //写入CSV文件 + using (var writer = new StreamWriter(filePath)) + using (var csv = new CsvWriter(writer, CultureInfo.CurrentCulture)) + { + await csv.WriteRecordsAsync(insertList); + } + + var httpContent = new MultipartFormDataContent(boundary); + if (!string.IsNullOrWhiteSpace(this.authorization)) + client.DefaultRequestHeaders.Add("Authorization", this.authorization); + httpContent.Add(new StringContent(schema), "schema"); + httpContent.Add(new ByteArrayContent(File.ReadAllBytes(filePath)), "data"); + //boundary带双引号 可能导致服务器错误情况 + httpContent.Headers.Remove("Content-Type"); + httpContent.Headers.TryAddWithoutValidation("Content-Type", + "multipart/form-data; boundary=" + boundary); + var httpResponseMessage = + await client.PostAsync($"{this.url}/imp?name={name}", httpContent); + var readAsStringAsync = await httpResponseMessage.Content.ReadAsStringAsync(); + var splitByLine = QuestDbRestAPHelper.SplitByLine(readAsStringAsync); + foreach (var s in splitByLine) + { + if (s.Contains("Rows")) + { + var strings = s.Split('|'); + if (strings[1].Trim() == "Rows imported") + { + result = Convert.ToInt32(strings[2].Trim()); + } + } + } + } + catch (Exception) + { + throw; + } + finally + { + try + { + File.Delete(filePath); + } + catch + { + // ignored + } + } + return result; + } + + /// + /// 批量快速插入 + /// + /// + /// + /// 导入时,时间格式 默认:yyyy/M/d H:mm:ss + /// + public int BulkCopy(List insertList, string dateFormat = "yyyy/M/d H:mm:ss") where T : class + { + return BulkCopyAsync(insertList, dateFormat).GetAwaiter().GetResult(); + } + } +} diff --git a/Src/Asp.NetCore2/SqlSugar.QuestDb.RestApi/SqlSugar.QuestDb.RestAPI.csproj b/Src/Asp.NetCore2/SqlSugar.QuestDb.RestApi/SqlSugar.QuestDb.RestAPI.csproj new file mode 100644 index 000000000..4c2167ecf --- /dev/null +++ b/Src/Asp.NetCore2/SqlSugar.QuestDb.RestApi/SqlSugar.QuestDb.RestAPI.csproj @@ -0,0 +1,16 @@ + + + + netstandard2.1 + enable + + + + + + + + + + + diff --git a/Src/Asp.NetCore2/SqlSugarCore.sln b/Src/Asp.NetCore2/SqlSugarCore.sln index b68e20c2c..018501a90 100644 --- a/Src/Asp.NetCore2/SqlSugarCore.sln +++ b/Src/Asp.NetCore2/SqlSugarCore.sln @@ -68,7 +68,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SqlSugar.TDengineCore", "Sq EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1TDengineTest", "TDengineTest\1TDengineTest.csproj", "{AFBF6813-DA87-4621-9659-5D123234CDDF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "2VastbaseTest", "VastbaseTest\2VastbaseTest.csproj", "{FA0F2233-7BD5-49A0-8333-BA471509F2D7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "2VastbaseTest", "VastbaseTest\2VastbaseTest.csproj", "{FA0F2233-7BD5-49A0-8333-BA471509F2D7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SqlSugar.QuestDb.RestAPI", "SqlSugar.QuestDb.RestApi\SqlSugar.QuestDb.RestAPI.csproj", "{6DED25D0-660B-468A-AD61-7646345145F5}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -440,6 +442,18 @@ Global {FA0F2233-7BD5-49A0-8333-BA471509F2D7}.Release|ARM32.Build.0 = Release|Any CPU {FA0F2233-7BD5-49A0-8333-BA471509F2D7}.Release|x86.ActiveCfg = Release|Any CPU {FA0F2233-7BD5-49A0-8333-BA471509F2D7}.Release|x86.Build.0 = Release|Any CPU + {6DED25D0-660B-468A-AD61-7646345145F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6DED25D0-660B-468A-AD61-7646345145F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6DED25D0-660B-468A-AD61-7646345145F5}.Debug|ARM32.ActiveCfg = Debug|Any CPU + {6DED25D0-660B-468A-AD61-7646345145F5}.Debug|ARM32.Build.0 = Debug|Any CPU + {6DED25D0-660B-468A-AD61-7646345145F5}.Debug|x86.ActiveCfg = Debug|Any CPU + {6DED25D0-660B-468A-AD61-7646345145F5}.Debug|x86.Build.0 = Debug|Any CPU + {6DED25D0-660B-468A-AD61-7646345145F5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6DED25D0-660B-468A-AD61-7646345145F5}.Release|Any CPU.Build.0 = Release|Any CPU + {6DED25D0-660B-468A-AD61-7646345145F5}.Release|ARM32.ActiveCfg = Release|Any CPU + {6DED25D0-660B-468A-AD61-7646345145F5}.Release|ARM32.Build.0 = Release|Any CPU + {6DED25D0-660B-468A-AD61-7646345145F5}.Release|x86.ActiveCfg = Release|Any CPU + {6DED25D0-660B-468A-AD61-7646345145F5}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -452,6 +466,7 @@ Global {517D3B1E-EECB-43C3-83B9-33613F45908A} = {88992AAF-146B-4253-9AD7-493E8F415B57} {7CEB3DFE-8337-4B83-AE6A-D159D73E3CD3} = {88992AAF-146B-4253-9AD7-493E8F415B57} {A8FDDB0E-835A-4042-A955-66A2DB98207D} = {88992AAF-146B-4253-9AD7-493E8F415B57} + {6DED25D0-660B-468A-AD61-7646345145F5} = {88992AAF-146B-4253-9AD7-493E8F415B57} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {230A85B9-54F1-41B1-B1DA-80086581B2B4}