204 lines
6.0 KiB
C#
204 lines
6.0 KiB
C#
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;
|
||
}
|
||
}
|