Files
TakeoutSaaS.TenantApi/src/Core/TakeoutSaaS.Shared.Abstractions/Results/ApiResponse.cs

204 lines
6.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Diagnostics;
using TakeoutSaaS.Shared.Abstractions.Diagnostics;
namespace TakeoutSaaS.Shared.Abstractions.Results;
/// <summary>
/// 统一的 API 返回结果包装。
/// </summary>
/// <typeparam name="T">数据载荷类型。</typeparam>
public sealed record ApiResponse<T>
{
/// <summary>
/// 是否成功。
/// </summary>
public bool Success { get; init; }
/// <summary>
/// 状态/错误码(默认 200
/// </summary>
public int Code { get; init; } = 200;
/// <summary>
/// 提示信息。
/// </summary>
public string? Message { get; init; }
/// <summary>
/// 业务数据。
/// </summary>
public T? Data { get; init; }
/// <summary>
/// 错误详情(如字段验证错误)。
/// </summary>
public object? Errors { get; init; }
/// <summary>
/// TraceId便于链路追踪。
/// </summary>
public string TraceId { get; init; } = string.Empty;
/// <summary>
/// 时间戳UTC
/// </summary>
public DateTime Timestamp { get; init; } = DateTime.UtcNow;
/// <summary>
/// 成功返回。
/// </summary>
/// <param name="data">业务数据。</param>
/// <param name="message">提示信息。</param>
/// <returns>封装后的成功响应。</returns>
public static ApiResponse<T> Ok(T data, string? message = "操作成功")
=> Create(true, 200, message, data);
/// <summary>
/// 无数据的成功返回。
/// </summary>
/// <param name="message">提示信息。</param>
/// <returns>封装后的成功响应。</returns>
public static ApiResponse<T> Ok(string? message = "操作成功")
=> Create(true, 200, message, default);
/// <summary>
/// 兼容旧名称:成功结果。
/// </summary>
/// <param name="data">业务数据。</param>
/// <param name="message">提示信息。</param>
/// <returns>封装后的成功响应。</returns>
public static ApiResponse<T> SuccessResult(T data, string? message = "操作成功")
=> Ok(data, message);
/// <summary>
/// 错误返回。
/// </summary>
/// <param name="code">错误码。</param>
/// <param name="message">错误提示。</param>
/// <param name="errors">错误详情。</param>
/// <returns>封装后的失败响应。</returns>
public static ApiResponse<T> Error(int code, string message, object? errors = null)
=> Create(false, code, message, default, errors);
/// <summary>
/// 兼容旧名称:失败结果。
/// </summary>
/// <param name="code">错误码。</param>
/// <param name="message">错误提示。</param>
/// <returns>封装后的失败响应。</returns>
public static ApiResponse<T> Failure(int code, string message)
=> Error(code, message);
/// <summary>
/// 附加错误详情。
/// </summary>
/// <param name="errors">错误详情。</param>
/// <returns>包含错误详情的新响应。</returns>
public ApiResponse<T> WithErrors(object? errors)
=> this with { Errors = errors };
private static ApiResponse<T> Create(bool success, int code, string? message, T? data, object? errors = null)
=> new()
{
Success = success,
Code = code,
Message = message,
Data = data,
Errors = errors,
TraceId = ResolveTraceId(),
Timestamp = DateTime.UtcNow
};
/// <summary>
/// 解析当前 TraceId。
/// </summary>
/// <returns>当前有效的 TraceId。</returns>
private static string ResolveTraceId()
{
if (!string.IsNullOrWhiteSpace(TraceContext.TraceId))
{
return TraceContext.TraceId;
}
if (!string.IsNullOrWhiteSpace(TraceContext.TraceId))
{
return TraceContext.TraceId;
}
if (Activity.Current?.Id is { } id && !string.IsNullOrWhiteSpace(id))
{
return id;
}
return IdFallbackGenerator.Instance.NextId().ToString();
}
}
/// <summary>
/// 作为 TraceId 缺失时的本地雪花 ID 备用生成器。
/// </summary>
internal sealed class IdFallbackGenerator
{
/// <summary>
/// 延迟初始化的单例实例承载。
/// </summary>
private static readonly Lazy<IdFallbackGenerator> Lazy = new(() => new IdFallbackGenerator());
/// <summary>
/// 获取备用雪花生成器单例。
/// </summary>
public static IdFallbackGenerator Instance => Lazy.Value;
private readonly object _sync = new();
private long _lastTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
private long _sequence;
private IdFallbackGenerator()
{
}
/// <summary>
/// 生成雪花风格的本地备用 ID。
/// </summary>
/// <returns>本地生成的雪花 ID。</returns>
public long NextId()
{
lock (_sync)
{
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
if (timestamp == _lastTimestamp)
{
_sequence = (_sequence + 1) & 4095;
if (_sequence == 0)
{
timestamp = WaitNextMillis(_lastTimestamp);
}
}
else
{
_sequence = 0;
}
_lastTimestamp = timestamp;
return ((timestamp - 1577836800000L) << 22) | _sequence;
}
}
/// <summary>
/// 等待到下一个毫秒以避免序列冲突。
/// </summary>
/// <param name="lastTimestamp">上一毫秒的时间戳。</param>
/// <returns>下一个时间戳(毫秒)。</returns>
private static long WaitNextMillis(long lastTimestamp)
{
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
while (timestamp <= lastTimestamp)
{
Thread.SpinWait(100);
timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
}
return timestamp;
}
}