| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- using System.Security.Claims;
- using Admin.NET.Plugin.AiDOP.DataPlatform.Inbound;
- using Microsoft.AspNetCore.Http;
- namespace Admin.NET.Plugin.AiDOP.Controllers;
- /// <summary>第三方标准 API 推数入站。类级仅指定 InboundSignature,不加裸 [Authorize]、不加 [AllowAnonymous]。</summary>
- [ApiController]
- [Route("api/mdp/inbound")]
- [NonUnify]
- [Authorize(AuthenticationSchemes = InboundSignatureDefaults.AuthenticationScheme)]
- [ApiDescriptionSettings(Order = 331, Description = "MDP API_INBOUND 推数入站")]
- public class MdpInboundController : ControllerBase
- {
- private readonly MdpInboundReceiveService _receive;
- private readonly MdpInboundSnapshotService _snapshots;
- public MdpInboundController(MdpInboundReceiveService receive, MdpInboundSnapshotService snapshots)
- {
- _receive = receive;
- _snapshots = snapshots;
- }
- /// <summary>接收推数。签名串 POST&{path}&...(方案 §5.2)。</summary>
- [HttpPost("{entityCode}")]
- public async Task<IActionResult> Receive(string entityCode, CancellationToken ct)
- {
- Request.EnableBuffering();
- using var ms = new MemoryStream();
- await Request.Body.CopyToAsync(ms);
- Request.Body.Position = 0;
- var outcome = await _receive.ReceiveAsync(new MdpInboundReceiveArgs
- {
- EntityCode = entityCode,
- RawBody = ms.ToArray(),
- AccessKey = AccessKey(),
- TenantId = TenantId(),
- IdempotencyKey = Header(InboundSignatureDefaults.IdempotencyKeyHeader),
- ClientIp = ClientIp(),
- Path = Request.Path.Value ?? string.Empty,
- Headers = Request.Headers.ToDictionary(
- h => h.Key,
- h => h.Value.ToString(),
- StringComparer.OrdinalIgnoreCase)
- }, ct);
- return JsonOutcome(outcome);
- }
- /// <summary>对外规范字段 JSON Schema。签名串 GET&{path}&...,须带与 POST 相同的签名头(空 body 摘要)。</summary>
- [HttpGet("{entityCode}/schema")]
- public async Task<IActionResult> Schema(string entityCode, CancellationToken ct)
- {
- var outcome = await _receive.SchemaAsync(entityCode, AccessKey(), TenantId(), ClientIp(), ct);
- return JsonOutcome(outcome);
- }
- /// <summary>按批次查回执。非本 AccessKey 的批次返回 404。</summary>
- [HttpGet("receipts/{syncBatchId}")]
- public async Task<IActionResult> Receipt(string syncBatchId, CancellationToken ct)
- {
- var outcome = await _receive.ReceiptAsync(syncBatchId, AccessKey(), ct);
- return JsonOutcome(outcome);
- }
- /// <summary>开启全量快照。同一 (tenant, access_key, entity) 已有 OPEN → 409。</summary>
- [HttpPost("{entityCode}/snapshots")]
- public async Task<IActionResult> OpenSnapshot(string entityCode, CancellationToken ct)
- {
- var outcome = await _snapshots.OpenAsync(entityCode, AccessKey(), TenantId(), ClientIp(), ct);
- return JsonOutcome(outcome);
- }
- /// <summary>提交快照差集。?force=true 仅当状态为 DIFF_BLOCKED。</summary>
- [HttpPost("{entityCode}/snapshots/{snapshotId}/commit")]
- public async Task<IActionResult> CommitSnapshot(string entityCode, string snapshotId, [FromQuery] bool force, CancellationToken ct)
- {
- try
- {
- var outcome = await _snapshots.CommitAsync(
- entityCode, snapshotId, AccessKey(), TenantId(), ClientIp(), force, ct);
- return JsonOutcome(outcome);
- }
- catch (MdpInboundBatchException ex)
- {
- return JsonOutcome(new MdpInboundOutcome
- {
- HttpStatus = ex.HttpStatus,
- Body = new { code = ex.HttpStatus, message = ex.Message, data = (object)null }
- });
- }
- }
- /// <summary>NDJSON 分块推送。每行独立 idempotencyKey,整包仍走一次入站签名。</summary>
- [HttpPost("{entityCode}/bulk")]
- public async Task<IActionResult> Bulk(string entityCode, CancellationToken ct)
- {
- Request.EnableBuffering();
- using var ms = new MemoryStream();
- await Request.Body.CopyToAsync(ms);
- Request.Body.Position = 0;
- var outcome = await _receive.BulkAsync(new MdpInboundReceiveArgs
- {
- EntityCode = entityCode,
- RawBody = ms.ToArray(),
- AccessKey = AccessKey(),
- TenantId = TenantId(),
- IdempotencyKey = Header(InboundSignatureDefaults.IdempotencyKeyHeader),
- ClientIp = ClientIp(),
- Path = Request.Path.Value ?? string.Empty,
- Headers = Request.Headers.ToDictionary(
- h => h.Key,
- h => h.Value.ToString(),
- StringComparer.OrdinalIgnoreCase)
- }, ct);
- return JsonOutcome(outcome);
- }
- /// <summary>日终对账。query 用 date 或 bizDate(yyyy-MM-dd)。</summary>
- [HttpGet("{entityCode}/digest")]
- public async Task<IActionResult> Digest(string entityCode, [FromQuery] string date, [FromQuery] string bizDate, CancellationToken ct)
- {
- var raw = !string.IsNullOrWhiteSpace(date) ? date : bizDate;
- var outcome = await _receive.DigestAsync(entityCode, AccessKey(), TenantId(), ClientIp(), raw, ct);
- return JsonOutcome(outcome);
- }
- private IActionResult JsonOutcome(MdpInboundOutcome outcome) =>
- new JsonResult(outcome.Body, MdpInboundJson.Options) { StatusCode = outcome.HttpStatus };
- private string AccessKey() =>
- User.FindFirstValue(InboundSignatureDefaults.AccessKeyClaim) ?? string.Empty;
- private long TenantId()
- {
- var raw = User.FindFirstValue(ClaimConst.TenantId);
- return long.TryParse(raw, out var id) ? id : 0;
- }
- private string Header(string name) => Request.Headers[name].FirstOrDefault() ?? string.Empty;
- private string ClientIp()
- {
- var xff = Request.Headers["X-Forwarded-For"].FirstOrDefault();
- if (!string.IsNullOrWhiteSpace(xff))
- return xff.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()
- ?? string.Empty;
- return HttpContext.Connection.RemoteIpAddress?.ToString() ?? string.Empty;
- }
- }
|