MdpApiOutController.cs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. using System.Text.Json;
  2. namespace Admin.NET.Plugin.AiDOP.Controllers;
  3. /// <summary>
  4. /// S9 API_OUT:受控只读 KPI 出站(字段白名单 + 访问日志)。禁止任意 SQL。
  5. /// </summary>
  6. [ApiDescriptionSettings(Order = 330, Description = "MDP API_OUT 受控只读")]
  7. [Route("api/aidop/out")]
  8. [AllowAnonymous]
  9. [NonUnify]
  10. public class MdpApiOutController : IDynamicApiController, ITransient
  11. {
  12. private static readonly Dictionary<string, string> ResourceSql = new(StringComparer.OrdinalIgnoreCase)
  13. {
  14. ["kpi_l1_day"] = "SELECT metric_code, metric_value, biz_date, tenant_id, factory_id FROM ado_s9_kpi_value_l1_day WHERE tenant_id=@tid ORDER BY biz_date DESC LIMIT @limit",
  15. ["kpi_l2_day"] = "SELECT metric_code, metric_value, biz_date, tenant_id, factory_id FROM ado_s9_kpi_value_l2_day WHERE tenant_id=@tid ORDER BY biz_date DESC LIMIT @limit",
  16. ["kpi_l3_day"] = "SELECT metric_code, metric_value, biz_date, tenant_id, factory_id FROM ado_s9_kpi_value_l3_day WHERE tenant_id=@tid ORDER BY biz_date DESC LIMIT @limit",
  17. ["kpi_l4_day"] = "SELECT metric_code, metric_value, biz_date, tenant_id, factory_id FROM ado_s9_kpi_value_l4_day WHERE tenant_id=@tid ORDER BY biz_date DESC LIMIT @limit",
  18. };
  19. private readonly ISqlSugarClient _db;
  20. public MdpApiOutController(ISqlSugarClient db) => _db = db;
  21. [DisplayName("API_OUT 资源列表")]
  22. [HttpGet("resources")]
  23. public object ListResources() => new { resources = ResourceSql.Keys.OrderBy(x => x).ToArray() };
  24. [DisplayName("API_OUT 查询")]
  25. [HttpGet("{resourceCode}")]
  26. public async Task<object> Query(
  27. string resourceCode,
  28. [FromQuery] long tenantId = 0,
  29. [FromQuery] int limit = 100,
  30. [FromQuery] string? caller = null)
  31. {
  32. await EnsureLogTableAsync();
  33. if (!ResourceSql.TryGetValue(resourceCode, out var sql))
  34. {
  35. await WriteLogAsync(tenantId, caller, resourceCode, null, 0, false, "resource not in whitelist");
  36. return new { ok = false, message = "resource not allowed" };
  37. }
  38. limit = Math.Clamp(limit, 1, 500);
  39. try
  40. {
  41. var rows = await _db.Ado.SqlQueryAsync<dynamic>(sql,
  42. new SugarParameter("@tid", tenantId),
  43. new SugarParameter("@limit", limit));
  44. var list = rows?.ToList() ?? [];
  45. await WriteLogAsync(tenantId, caller, resourceCode,
  46. JsonSerializer.Serialize(new { tenantId, limit }), list.Count, true, null);
  47. return new { ok = true, resource = resourceCode, count = list.Count, data = list };
  48. }
  49. catch (Exception ex)
  50. {
  51. await WriteLogAsync(tenantId, caller, resourceCode,
  52. JsonSerializer.Serialize(new { tenantId, limit }), 0, false, ex.Message);
  53. return new { ok = false, message = ex.Message };
  54. }
  55. }
  56. private async Task EnsureLogTableAsync()
  57. {
  58. await _db.Ado.ExecuteCommandAsync("""
  59. CREATE TABLE IF NOT EXISTS mdp_api_out_access_log (
  60. id bigint NOT NULL AUTO_INCREMENT,
  61. tenant_id bigint NOT NULL DEFAULT 0,
  62. caller varchar(100) DEFAULT NULL,
  63. resource_code varchar(100) NOT NULL,
  64. query_json text,
  65. row_count int DEFAULT 0,
  66. success tinyint NOT NULL DEFAULT 1,
  67. message varchar(500) DEFAULT NULL,
  68. create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  69. PRIMARY KEY (id)
  70. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
  71. """);
  72. }
  73. private Task WriteLogAsync(long tenantId, string? caller, string resource, string? queryJson, int rowCount, bool success, string? message) =>
  74. _db.Ado.ExecuteCommandAsync(
  75. """
  76. INSERT INTO mdp_api_out_access_log
  77. (tenant_id, caller, resource_code, query_json, row_count, success, message, create_time)
  78. VALUES (@tid, @caller, @res, @q, @cnt, @ok, @msg, NOW())
  79. """,
  80. new SugarParameter("@tid", tenantId),
  81. new SugarParameter("@caller", caller ?? ""),
  82. new SugarParameter("@res", resource),
  83. new SugarParameter("@q", queryJson),
  84. new SugarParameter("@cnt", rowCount),
  85. new SugarParameter("@ok", success ? 1 : 0),
  86. new SugarParameter("@msg", message != null && message.Length > 480 ? message[..480] : message));
  87. }