using System.Data; using System.Text.Json; using Admin.NET.Plugin.AiDOP.Entity.S8; using Microsoft.Extensions.Logging; using SqlSugar; namespace Admin.NET.Plugin.AiDOP.Service.S8.Rules; /// /// S8 规则取数:SQL 走 SqlSugarScope;API 走 HTTP GET(endpoint=完整 URL)。 /// public class S8DataSourceRowLoader : ITransient { public const string SqlType = "SQL"; public const string ApiType = "API"; private readonly S8SqlSugarScopeFactory _scopeFactory; private readonly ILogger _logger; public S8DataSourceRowLoader(S8SqlSugarScopeFactory scopeFactory, ILogger logger) { _scopeFactory = scopeFactory; _logger = logger; } public async Task LoadAsync( AdoS8DataSource dataSource, string expression, SqlSugar.DbType dbType, int timeoutSeconds, CancellationToken cancellationToken = default) { var type = dataSource.Type?.Trim() ?? SqlType; if (string.Equals(type, SqlType, StringComparison.OrdinalIgnoreCase)) { using var db = _scopeFactory.CreateScope(dataSource.Endpoint!, dbType, timeoutSeconds); return await db.Ado.GetDataTableAsync(expression); } if (string.Equals(type, ApiType, StringComparison.OrdinalIgnoreCase)) return await LoadFromApiAsync(dataSource, expression, timeoutSeconds, cancellationToken); throw new InvalidOperationException($"不支持的数据源类型:{type}"); } public static bool IsSupportedType(string? type) => string.Equals(type?.Trim(), SqlType, StringComparison.OrdinalIgnoreCase) || string.Equals(type?.Trim(), ApiType, StringComparison.OrdinalIgnoreCase); private async Task LoadFromApiAsync( AdoS8DataSource dataSource, string expression, int timeoutSeconds, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(dataSource.Endpoint)) throw new InvalidOperationException("API 数据源 endpoint 为空"); var url = dataSource.Endpoint.Trim(); if (!string.IsNullOrWhiteSpace(expression) && !expression.TrimStart().StartsWith("SELECT", StringComparison.OrdinalIgnoreCase)) { if (expression.StartsWith('?')) url = url.Contains('?') ? url + "&" + expression.TrimStart('?') : url + expression; else if (expression.StartsWith('/')) url = url.TrimEnd('/') + expression; } _logger.LogDebug("S8 API 取数 url={Url} authType={AuthType}", url, dataSource.AuthType); using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(Math.Max(5, timeoutSeconds)) }; using var resp = await http.GetAsync(url, cancellationToken); resp.EnsureSuccessStatusCode(); var json = await resp.Content.ReadAsStringAsync(cancellationToken); return JsonToDataTable(json); } /// 支持 {data:{list:[...]}} / {list:[...]} / [...] 。 internal static DataTable JsonToDataTable(string json) { using var doc = JsonDocument.Parse(json); JsonElement arr; var root = doc.RootElement; if (root.ValueKind == JsonValueKind.Array) arr = root; else if (root.TryGetProperty("data", out var data) && data.TryGetProperty("list", out var list) && list.ValueKind == JsonValueKind.Array) arr = list; else if (root.TryGetProperty("list", out var list2) && list2.ValueKind == JsonValueKind.Array) arr = list2; else throw new InvalidOperationException("API 响应不是可识别的行数组(期望 data.list / list / [])"); var table = new DataTable(); foreach (var item in arr.EnumerateArray()) { if (item.ValueKind != JsonValueKind.Object) continue; foreach (var prop in item.EnumerateObject()) { if (!table.Columns.Contains(prop.Name)) table.Columns.Add(prop.Name, typeof(string)); } } foreach (var item in arr.EnumerateArray()) { if (item.ValueKind != JsonValueKind.Object) continue; var row = table.NewRow(); foreach (DataColumn col in table.Columns) { if (item.TryGetProperty(col.ColumnName, out var v)) row[col.ColumnName] = v.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined ? DBNull.Value : v.ToString(); } table.Rows.Add(row); } return table; } }