This commit is contained in:
2026-05-21 15:52:36 +08:00
commit e3fe965f10
138 changed files with 15087 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>Avalonia_Common</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
</ItemGroup>
</Project>
+205
View File
@@ -0,0 +1,205 @@
using System.Text.Json.Serialization;
namespace Avalonia_Common.Core
{
/// <summary>
/// 统一 API 返回格式。
/// 所有接口的返回都包装为此格式,确保前端收到一致的数据结构。
/// </summary>
/// <typeparam name="T">业务数据类型</typeparam>
public class ApiResponse<T>
{
/// <summary>是否成功</summary>
[JsonPropertyName("success")]
public bool Success { get; set; }
/// <summary>HTTP 状态码</summary>
[JsonPropertyName("code")]
public int Code { get; set; }
/// <summary>消息(成功时可为 null,失败时包含错误描述)</summary>
[JsonPropertyName("message")]
public string? Message { get; set; }
/// <summary>业务数据</summary>
[JsonPropertyName("data")]
public T? Data { get; set; }
/// <summary>时间戳</summary>
[JsonPropertyName("timestamp")]
public DateTime Timestamp { get; set; } = DateTime.Now;
/// <summary>请求追踪 ID(用于排查问题)</summary>
[JsonPropertyName("traceId")]
public string? TraceId { get; set; }
// ---- 快捷工厂方法 ----
/// <summary>成功返回(有数据)</summary>
public static ApiResponse<T> Ok(T data, string? message = null)
{
return new ApiResponse<T>
{
Success = true,
Code = 200,
Message = message,
Data = data,
};
}
/// <summary>失败返回</summary>
public static ApiResponse<T> Fail(int code, string message, T? data = default)
{
return new ApiResponse<T>
{
Success = false,
Code = code,
Message = message,
Data = data,
};
}
/// <summary>400 参数错误</summary>
public static ApiResponse<T> BadRequest(string message = "参数错误")
=> Fail(400, message);
/// <summary>401 未授权</summary>
public static ApiResponse<T> Unauthorized(string message = "未授权")
=> Fail(401, message);
/// <summary>403 无权限</summary>
public static ApiResponse<T> Forbidden(string message = "无权限")
=> Fail(403, message);
/// <summary>404 未找到</summary>
public static ApiResponse<T> NotFound(string message = "资源不存在")
=> Fail(404, message);
/// <summary>500 服务器内部错误</summary>
public static ApiResponse<T> ServerError(string message = "服务器内部错误")
=> Fail(500, message);
}
/// <summary>
/// 无数据的统一返回格式(object? 版本)。
/// </summary>
public class ApiResponse : ApiResponse<object?>
{
/// <summary>成功返回(无数据)</summary>
public static ApiResponse Succeed(string? message = null)
{
return new ApiResponse
{
Success = true,
Code = 200,
Message = message,
Data = null,
};
}
/// <summary>失败返回</summary>
public static ApiResponse Failure(int code, string message)
{
return new ApiResponse
{
Success = false,
Code = code,
Message = message,
Data = null,
};
}
}
/// <summary>
/// 分页返回格式
/// </summary>
public class PagedResponse<T>
{
/// <summary>
/// 获取或设置操作是否成功。
/// </summary>
[JsonPropertyName("success")]
public bool Success { get; set; } = true;
/// <summary>
/// 获取或设置业务状态码,默认 200。
/// </summary>
[JsonPropertyName("code")]
public int Code { get; set; } = 200;
/// <summary>
/// 获取或设置分页数据项列表。
/// </summary>
[JsonPropertyName("items")]
public List<T> Items { get; set; } = new();
/// <summary>
/// 获取或设置数据总条数。
/// </summary>
[JsonPropertyName("total")]
public int Total { get; set; }
/// <summary>
/// 获取或设置当前页码,从 1 开始。
/// </summary>
[JsonPropertyName("page")]
public int Page { get; set; } = 1;
/// <summary>
/// 获取或设置每页条数,默认 20。
/// </summary>
[JsonPropertyName("pageSize")]
public int PageSize { get; set; } = 20;
/// <summary>
/// 获取总页数(根据 Total 和 PageSize 自动计算)。
/// </summary>
[JsonPropertyName("totalPages")]
public int TotalPages => PageSize > 0 ? (int)Math.Ceiling((double)Total / PageSize) : 0;
/// <summary>
/// 从数据列表和分页参数创建分页响应。
/// </summary>
/// <param name="items">当前页数据项。</param>
/// <param name="total">数据总条数。</param>
/// <param name="page">当前页码。</param>
/// <param name="pageSize">每页条数。</param>
/// <returns>分页响应实例。</returns>
public static PagedResponse<T> From(List<T> items, int total, int page, int pageSize)
{
return new PagedResponse<T>
{
Items = items,
Total = total,
Page = page,
PageSize = pageSize,
};
}
}
/// <summary>
/// 端点返回辅助方法 —— 在 AppEndpoints 中快捷构建统一响应。
/// </summary>
public static class ResponseHelper
{
/// <summary>成功返回</summary>
public static ApiResponse<T> Ok<T>(T data, string? message = null)
=> ApiResponse<T>.Ok(data, message);
/// <summary>成功返回(无数据)</summary>
public static ApiResponse Succeed(string? message = null)
=> ApiResponse.Succeed(message);
/// <summary>失败返回</summary>
public static ApiResponse<T> Fail<T>(int code, string message, T? data = default)
=> ApiResponse<T>.Fail(code, message, data);
/// <summary>失败返回(无数据)</summary>
public static ApiResponse Failure(int code, string message)
=> ApiResponse.Failure(code, message);
/// <summary>分页返回</summary>
public static PagedResponse<T> Paged<T>(List<T> items, int total, int page, int pageSize)
=> PagedResponse<T>.From(items, total, page, pageSize);
}
}
@@ -0,0 +1,167 @@
using Serilog;
using Serilog.Events;
namespace Avalonia_Common.Infrastructure
{
/// <summary>
/// Serilog 日志配置 —— 可在 Avalonia-API 和 Avalonia-PC 中共享。
/// </summary>
public static class LoggingConfiguration
{
/// <summary>
/// 默认日志目录
/// </summary>
private static readonly string DefaultLogDir = Path.Combine(AppContext.BaseDirectory, "logs");
/// <summary>
/// 创建控制台日志记录器(开发环境)。
/// </summary>
public static ILogger CreateConsoleLogger(
LogEventLevel minimumLevel = LogEventLevel.Debug)
{
return new LoggerConfiguration()
.MinimumLevel.Is(minimumLevel)
.Enrich.FromLogContext()
.WriteTo.Console(
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
.CreateLogger();
}
/// <summary>
/// 创建控制台 + 文件日志记录器。
/// </summary>
/// <param name="minimumLevel">最低日志级别</param>
/// <param name="logDir">日志目录,默认 ./logs</param>
/// <param name="retainedDays">保留天数</param>
public static ILogger CreateDefaultLogger(
LogEventLevel minimumLevel = LogEventLevel.Information,
string? logDir = null,
int retainedDays = 30)
{
logDir ??= DefaultLogDir;
return new LoggerConfiguration()
.MinimumLevel.Is(minimumLevel)
//.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
//.MinimumLevel.Override("System", LogEventLevel.Warning)
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.Enrich.WithThreadId()
.WriteTo.Console(
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
.WriteTo.File(
path: Path.Combine(logDir, "log-.txt"),
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: retainedDays,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}",
encoding: System.Text.Encoding.UTF8)
.CreateLogger();
}
/// <summary>
/// 创建只写文件的日志记录器(桌面应用静默模式)。
/// </summary>
public static ILogger CreateFileOnlyLogger(
LogEventLevel minimumLevel = LogEventLevel.Information,
string? logDir = null,
int retainedDays = 30)
{
logDir ??= DefaultLogDir;
return new LoggerConfiguration()
.MinimumLevel.Is(minimumLevel)
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.WriteTo.File(
path: Path.Combine(logDir, "app-.txt"),
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: retainedDays,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}",
encoding: System.Text.Encoding.UTF8)
.CreateLogger();
}
}
/// <summary>
/// 静态日志访问器 —— 全局静态入口,方便在没有 DI 的场景下使用。
/// </summary>
public static class AppLog
{
/// <summary>
/// 保存全局日志记录器实例。
/// </summary>
private static ILogger? _logger;
/// <summary>
/// 初始化全局日志记录器。
/// </summary>
public static void Initialize(ILogger logger)
{
_logger = logger;
Log.Logger = logger;
}
/// <summary>
/// 获取全局日志记录器。若未初始化则回退到 Serilog.Log.Logger。
/// </summary>
public static ILogger Logger => _logger ?? Log.Logger;
/// <summary>
/// 写入 Debug 级别日志。
/// </summary>
/// <param name="messageTemplate">消息模板。</param>
/// <param name="propertyValues">属性值。</param>
public static void Debug(string messageTemplate, params object?[] propertyValues)
=> Logger.Debug(messageTemplate, propertyValues);
/// <summary>
/// 写入 Information 级别日志。
/// </summary>
/// <param name="messageTemplate">消息模板。</param>
/// <param name="propertyValues">属性值。</param>
public static void Information(string messageTemplate, params object?[] propertyValues)
=> Logger.Information(messageTemplate, propertyValues);
/// <summary>
/// 写入 Warning 级别日志。
/// </summary>
/// <param name="messageTemplate">消息模板。</param>
/// <param name="propertyValues">属性值。</param>
public static void Warning(string messageTemplate, params object?[] propertyValues)
=> Logger.Warning(messageTemplate, propertyValues);
/// <summary>
/// 写入 Error 级别日志。
/// </summary>
/// <param name="messageTemplate">消息模板。</param>
/// <param name="propertyValues">属性值。</param>
public static void Error(string messageTemplate, params object?[] propertyValues)
=> Logger.Error(messageTemplate, propertyValues);
/// <summary>
/// 写入 Error 级别日志,并附带异常信息。
/// </summary>
/// <param name="exception">异常对象。</param>
/// <param name="messageTemplate">消息模板。</param>
/// <param name="propertyValues">属性值。</param>
public static void Error(Exception exception, string messageTemplate, params object?[] propertyValues)
=> Logger.Error(exception, messageTemplate, propertyValues);
/// <summary>
/// 写入 Fatal 级别日志。
/// </summary>
/// <param name="messageTemplate">消息模板。</param>
/// <param name="propertyValues">属性值。</param>
public static void Fatal(string messageTemplate, params object?[] propertyValues)
=> Logger.Fatal(messageTemplate, propertyValues);
/// <summary>
/// 写入 Fatal 级别日志,并附带异常信息。
/// </summary>
/// <param name="exception">异常对象。</param>
/// <param name="messageTemplate">消息模板。</param>
/// <param name="propertyValues">属性值。</param>
public static void Fatal(Exception exception, string messageTemplate, params object?[] propertyValues)
=> Logger.Fatal(exception, messageTemplate, propertyValues);
}
}