Merge branch 'chore/comment-fix' into dev

This commit is contained in:
2025-12-04 14:43:02 +08:00
271 changed files with 1978 additions and 868 deletions

View File

@@ -6,7 +6,7 @@
public static class DatabaseConstants
{
/// <summary>
/// 默认业务库AppDatabase
/// 默认业务库AppDatabase.
/// </summary>
public const string AppDataSource = "AppDatabase";

View File

@@ -5,15 +5,43 @@ namespace TakeoutSaaS.Shared.Abstractions.Constants;
/// </summary>
public static class ErrorCodes
{
/// <summary>
/// 请求参数错误。
/// </summary>
public const int BadRequest = 400;
/// <summary>
/// 未授权访问。
/// </summary>
public const int Unauthorized = 401;
/// <summary>
/// 权限不足。
/// </summary>
public const int Forbidden = 403;
/// <summary>
/// 资源未找到。
/// </summary>
public const int NotFound = 404;
/// <summary>
/// 资源冲突。
/// </summary>
public const int Conflict = 409;
/// <summary>
/// 校验失败。
/// </summary>
public const int ValidationFailed = 422;
/// <summary>
/// 服务器内部错误。
/// </summary>
public const int InternalServerError = 500;
// 业务自定义区间10000+
/// <summary>
/// 业务自定义错误10000+)。
/// </summary>
public const int BusinessError = 10001;
}

View File

@@ -29,6 +29,7 @@ public interface IDapperExecutor
/// <param name="role">连接角色(读/写)。</param>
/// <param name="command">命令委托,提供已打开的连接和取消标记。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步执行任务。</returns>
Task ExecuteAsync(
string dataSourceName,
DatabaseConnectionRole role,

View File

@@ -1,5 +1,3 @@
using System.Threading;
namespace TakeoutSaaS.Shared.Abstractions.Diagnostics;
/// <summary>

View File

@@ -1,6 +1,3 @@
using System;
using System.Collections.Generic;
namespace TakeoutSaaS.Shared.Abstractions.Exceptions;
/// <summary>

View File

@@ -8,24 +8,36 @@ public static class ApiResponse
/// <summary>
/// 仅返回成功消息(无数据)。
/// </summary>
/// <param name="message">提示信息。</param>
/// <returns>封装后的成功响应。</returns>
public static ApiResponse<object> Success(string? message = "操作成功")
=> ApiResponse<object>.Ok(message: message);
/// <summary>
/// 成功且携带数据。
/// </summary>
/// <param name="data">业务数据。</param>
/// <param name="message">提示信息。</param>
/// <returns>封装后的成功响应。</returns>
public static ApiResponse<object> Ok(object? data, string? message = "操作成功")
=> data is null ? ApiResponse<object>.Ok(message: message) : ApiResponse<object>.Ok(data, message);
/// <summary>
/// 错误返回。
/// </summary>
/// <param name="code">错误码。</param>
/// <param name="message">错误提示。</param>
/// <returns>封装后的失败响应。</returns>
public static ApiResponse<object> Failure(int code, string message)
=> ApiResponse<object>.Error(code, message);
/// <summary>
/// 错误返回(附带详情)。
/// </summary>
/// <param name="code">错误码。</param>
/// <param name="message">错误提示。</param>
/// <param name="errors">错误详情。</param>
/// <returns>封装后的失败响应。</returns>
public static ApiResponse<object> Error(int code, string message, object? errors = null)
=> ApiResponse<object>.Error(code, message, errors);
}

View File

@@ -6,7 +6,7 @@ namespace TakeoutSaaS.Shared.Abstractions.Results;
/// <summary>
/// 统一的 API 返回结果包装。
/// </summary>
/// <typeparam name="T">数据载荷类型</typeparam>
/// <typeparam name="T">数据载荷类型</typeparam>
public sealed record ApiResponse<T>
{
/// <summary>
@@ -47,36 +47,53 @@ public sealed record ApiResponse<T>
/// <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 };
@@ -92,6 +109,10 @@ public sealed record ApiResponse<T>
Timestamp = DateTime.UtcNow
};
/// <summary>
/// 解析当前 TraceId。
/// </summary>
/// <returns>当前有效的 TraceId。</returns>
private static string ResolveTraceId()
{
if (!string.IsNullOrWhiteSpace(TraceContext.TraceId))
@@ -113,9 +134,19 @@ public sealed record ApiResponse<T>
}
}
/// <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();
@@ -126,6 +157,10 @@ internal sealed class IdFallbackGenerator
{
}
/// <summary>
/// 生成雪花风格的本地备用 ID。
/// </summary>
/// <returns>本地生成的雪花 ID。</returns>
public long NextId()
{
lock (_sync)
@@ -149,6 +184,11 @@ internal sealed class IdFallbackGenerator
}
}
/// <summary>
/// 等待到下一个毫秒以避免序列冲突。
/// </summary>
/// <param name="lastTimestamp">上一毫秒的时间戳。</param>
/// <returns>下一个时间戳(毫秒)。</returns>
private static long WaitNextMillis(long lastTimestamp)
{
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();

View File

@@ -1,5 +1,3 @@
using System.Collections.Generic;
namespace TakeoutSaaS.Shared.Abstractions.Results;
/// <summary>

View File

@@ -6,7 +6,8 @@ namespace TakeoutSaaS.Shared.Abstractions.Tenancy;
public interface ITenantProvider
{
/// <summary>
/// 获取当前租户 ID未解析时返回 Guid.Empty
/// 获取当前租户 ID未解析时返回 0
/// </summary>
/// <returns>当前请求绑定的租户 ID未解析时为 0。</returns>
long GetCurrentTenantId();
}

View File

@@ -1,6 +1,5 @@
using System.Diagnostics;
using System.Security.Cryptography;
using System.Threading;
using TakeoutSaaS.Shared.Abstractions.Ids;
namespace TakeoutSaaS.Shared.Kernel.Ids;

View File

@@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.Extensions.DependencyInjection;
using TakeoutSaaS.Shared.Abstractions.Security;
using TakeoutSaaS.Shared.Web.Filters;

View File

@@ -1,4 +1,3 @@
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using TakeoutSaaS.Shared.Abstractions.Constants;

View File

@@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using TakeoutSaaS.Shared.Abstractions.Diagnostics;
using TakeoutSaaS.Shared.Abstractions.Ids;
@@ -49,10 +46,10 @@ public sealed class CorrelationIdMiddleware(RequestDelegate next, ILogger<Correl
});
using (logger.BeginScope(new Dictionary<string, object>
{
["TraceId"] = traceId,
["SpanId"] = spanId
}))
{
["TraceId"] = traceId,
["SpanId"] = spanId
}))
{
try
{

View File

@@ -1,10 +1,8 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Text.Json;
using System.Text.Json.Serialization;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Results;

View File

@@ -1,7 +1,6 @@
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using TakeoutSaaS.Shared.Abstractions.Diagnostics;
namespace TakeoutSaaS.Shared.Web.Middleware;
@@ -11,7 +10,6 @@ namespace TakeoutSaaS.Shared.Web.Middleware;
/// </summary>
public sealed class RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
public async Task InvokeAsync(HttpContext context)
{
var stopwatch = Stopwatch.StartNew();

View File

@@ -1,4 +1,3 @@
using System;
using System.Security.Claims;
namespace TakeoutSaaS.Shared.Web.Security;

View File

@@ -1,5 +1,5 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using TakeoutSaaS.Shared.Abstractions.Security;
namespace TakeoutSaaS.Shared.Web.Security;
@@ -12,8 +12,6 @@ namespace TakeoutSaaS.Shared.Web.Security;
/// </remarks>
public sealed class HttpContextCurrentUserAccessor(IHttpContextAccessor httpContextAccessor) : ICurrentUserAccessor
{
/// <inheritdoc />
public long UserId
{

View File

@@ -1,6 +1,4 @@
using System;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi;