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;
}
}