WorkCamera/client/WorkCameraExport/Services/ApiService.cs
2026-01-06 22:23:29 +08:00

977 lines
34 KiB
C#
Raw Permalink 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.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using WorkCameraExport.Models;
using WorkCameraExport.Services.Interfaces;
namespace WorkCameraExport.Services
{
/// <summary>
/// API 服务类 - 处理与后端服务器的所有 HTTP 通信
/// 实现 Token 认证、请求重试、Token 自动刷新
/// </summary>
public class ApiService : IApiService
{
private readonly HttpClient _httpClient;
private readonly ILogService? _logService;
private string _baseUrl = "";
private string _token = "";
private DateTime _tokenExpireTime = DateTime.MinValue;
private string _refreshToken = "";
private bool _disposed;
// 配置常量
private const int MaxRetryCount = 3;
private const int RetryDelayMs = 1000;
private const int TokenRefreshThresholdMinutes = 5;
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
Converters = { new FlexibleDateTimeConverter() }
};
public ApiService(ILogService? logService = null)
{
_logService = logService;
_httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(30)
};
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("WorkCameraExport/2.0 (Windows; .NET)");
}
/// <summary>
/// 用于测试的构造函数,允许注入 HttpClient
/// </summary>
internal ApiService(HttpClient httpClient, ILogService? logService = null)
{
_httpClient = httpClient;
_logService = logService;
}
#region
/// <summary>
/// 设置服务器地址
/// </summary>
public void SetBaseUrl(string baseUrl)
{
_baseUrl = baseUrl.TrimEnd('/');
}
/// <summary>
/// 设置认证 Token
/// </summary>
public void SetToken(string token)
{
_token = token;
if (!string.IsNullOrEmpty(token))
{
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
// 默认设置 Token 有效期为 2 小时
_tokenExpireTime = DateTime.Now.AddHours(2);
}
else
{
_httpClient.DefaultRequestHeaders.Authorization = null;
_tokenExpireTime = DateTime.MinValue;
}
}
/// <summary>
/// 设置 Token 及其过期时间
/// </summary>
public void SetToken(string token, DateTime expireTime)
{
_token = token;
_tokenExpireTime = expireTime;
if (!string.IsNullOrEmpty(token))
{
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
}
else
{
_httpClient.DefaultRequestHeaders.Authorization = null;
}
}
/// <summary>
/// 设置刷新 Token
/// </summary>
public void SetRefreshToken(string refreshToken)
{
_refreshToken = refreshToken;
}
/// <summary>
/// 获取当前 Token
/// </summary>
public string GetToken() => _token;
/// <summary>
/// 获取 Token 过期时间
/// </summary>
public DateTime GetTokenExpireTime() => _tokenExpireTime;
/// <summary>
/// 检查是否已登录
/// </summary>
public bool IsLoggedIn => !string.IsNullOrEmpty(_token);
/// <summary>
/// 检查 Token 是否即将过期
/// </summary>
public bool IsTokenExpiringSoon =>
_tokenExpireTime != DateTime.MinValue &&
_tokenExpireTime.Subtract(DateTime.Now).TotalMinutes < TokenRefreshThresholdMinutes;
#endregion
#region
/// <summary>
/// 获取验证码
/// </summary>
public async Task<(bool Success, string Message, CaptchaResponse? Data)> GetCaptchaAsync()
{
try
{
var response = await ExecuteWithRetryAsync(() => GetAsync<CaptchaResponse>("/captchaImage"));
if (response.IsSuccess && response.Data != null)
{
return (true, "获取成功", response.Data);
}
return (false, response.Msg ?? "获取验证码失败", null);
}
catch (Exception ex)
{
_logService?.Error($"获取验证码异常: {ex.Message}", ex);
return (false, $"获取验证码异常: {ex.Message}", null);
}
}
/// <summary>
/// 用户登录
/// </summary>
public async Task<(bool Success, string Message, LoginResponse? Data)> LoginAsync(
string username, string password, string code = "", string uuid = "")
{
try
{
var request = new LoginRequest
{
Username = username,
Password = password,
Code = code,
Uuid = uuid
};
var url = $"{_baseUrl}/login";
var json = JsonSerializer.Serialize(request, JsonOptions);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var httpResponse = await _httpClient.PostAsync(url, content);
var responseContent = await httpResponse.Content.ReadAsStringAsync();
var response = JsonSerializer.Deserialize<ApiResult<string>>(responseContent, JsonOptions);
if (response != null && response.IsSuccess && !string.IsNullOrEmpty(response.Data))
{
var token = response.Data;
SetToken(token);
_logService?.Info($"用户 {username} 登录成功");
return (true, "登录成功", new LoginResponse { Token = token });
}
_logService?.Warn($"用户 {username} 登录失败: {response?.Msg}");
return (false, response?.Msg ?? "登录失败", null);
}
catch (Exception ex)
{
_logService?.Error($"登录异常: {ex.Message}", ex);
return (false, $"登录异常: {ex.Message}", null);
}
}
/// <summary>
/// 刷新 Token
/// </summary>
public async Task<bool> RefreshTokenAsync()
{
try
{
if (string.IsNullOrEmpty(_refreshToken))
{
_logService?.Warn("无刷新 Token无法刷新");
return false;
}
var response = await PostAsync<TokenRefreshResponse>("/refreshToken", new { refreshToken = _refreshToken });
if (response.IsSuccess && response.Data != null)
{
SetToken(response.Data.Token, response.Data.ExpireTime);
if (!string.IsNullOrEmpty(response.Data.RefreshToken))
{
_refreshToken = response.Data.RefreshToken;
}
_logService?.Info("Token 刷新成功");
return true;
}
_logService?.Warn($"Token 刷新失败: {response.Msg}");
return false;
}
catch (Exception ex)
{
_logService?.Error($"Token 刷新异常: {ex.Message}", ex);
return false;
}
}
/// <summary>
/// 退出登录
/// </summary>
public void Logout()
{
_logService?.Info("用户退出登录");
SetToken("");
_refreshToken = "";
}
/// <summary>
/// 获取当前用户信息(用于验证 Token 有效性)
/// </summary>
public async Task<(bool Success, string Message, UserInfo? Data)> GetCurrentUserAsync()
{
try
{
var response = await GetAsync<UserProfileResponse>("/system/user/profile");
if (response.IsSuccess && response.Data?.User != null)
{
return (true, "获取成功", new UserInfo
{
UserId = response.Data.User.UserId,
UserName = response.Data.User.UserName ?? "",
NickName = response.Data.User.NickName ?? ""
});
}
return (false, response.Msg ?? "获取用户信息失败", null);
}
catch (Exception ex)
{
_logService?.Error($"获取用户信息异常: {ex.Message}", ex);
return (false, $"获取用户信息异常: {ex.Message}", null);
}
}
#endregion
#region
/// <summary>
/// 获取统计信息
/// </summary>
public async Task<(bool Success, string Message, StatisticsDto? Data)> GetStatisticsAsync()
{
try
{
await EnsureTokenValidAsync();
var response = await ExecuteWithRetryAsync(() => GetAsync<StatisticsDto>("/api/workrecord/statistics"));
if (response.IsSuccess && response.Data != null)
{
return (true, "获取成功", response.Data);
}
return (false, response.Msg ?? "获取统计信息失败", null);
}
catch (Exception ex)
{
_logService?.Error($"获取统计信息异常: {ex.Message}", ex);
return (false, $"获取统计信息异常: {ex.Message}", null);
}
}
#endregion
#region CRUD
/// <summary>
/// 查询工作记录列表
/// </summary>
public async Task<(bool Success, string Message, PagedData<WorkRecordDto>? Data)> GetWorkRecordsAsync(
WorkRecordQueryDto query)
{
try
{
await EnsureTokenValidAsync();
var queryParams = BuildQueryString(query);
var response = await ExecuteWithRetryAsync(() =>
GetPagedAsync<WorkRecordDto>($"/business/CamWorkrecord/list?{queryParams}"));
if (response.IsSuccess && response.Data != null)
{
// 调试日志:记录反序列化后的 Workers 数据
foreach (var record in response.Data.Result.Take(3)) // 只记录前3条
{
var workersInfo = record.Workers != null
? $"Workers数量={record.Workers.Count}, 名称=[{string.Join(", ", record.Workers.Select(w => w.WorkerName ?? "null"))}]"
: "Workers=null";
_logService?.Info($"[反序列化] 记录ID={record.Id}, {workersInfo}");
}
return (true, "查询成功", response.Data);
}
return (false, response.Msg ?? "查询失败", null);
}
catch (Exception ex)
{
_logService?.Error($"查询工作记录异常: {ex.Message}", ex);
return (false, $"查询异常: {ex.Message}", null);
}
}
/// <summary>
/// 获取单条工作记录
/// </summary>
public async Task<(bool Success, string Message, WorkRecordDto? Data)> GetWorkRecordAsync(int id)
{
try
{
await EnsureTokenValidAsync();
var response = await ExecuteWithRetryAsync(() =>
GetAsync<WorkRecordDto>($"/business/CamWorkrecord/{id}"));
if (response.IsSuccess && response.Data != null)
{
return (true, "获取成功", response.Data);
}
return (false, response.Msg ?? "获取失败", null);
}
catch (Exception ex)
{
_logService?.Error($"获取工作记录异常: {ex.Message}", ex);
return (false, $"获取异常: {ex.Message}", null);
}
}
/// <summary>
/// 新增工作记录
/// </summary>
public async Task<(bool Success, string Message, int? Id)> AddWorkRecordAsync(WorkRecordSaveDto record)
{
try
{
await EnsureTokenValidAsync();
var response = await ExecuteWithRetryAsync(() =>
PostAsync<int>("/business/CamWorkrecord", record));
if (response.IsSuccess)
{
_logService?.Info($"新增工作记录成功ID: {response.Data}");
return (true, "新增成功", response.Data);
}
return (false, response.Msg ?? "新增失败", null);
}
catch (Exception ex)
{
_logService?.Error($"新增工作记录异常: {ex.Message}", ex);
return (false, $"新增异常: {ex.Message}", null);
}
}
/// <summary>
/// 更新工作记录
/// </summary>
public async Task<(bool Success, string Message)> UpdateWorkRecordAsync(WorkRecordSaveDto record)
{
try
{
await EnsureTokenValidAsync();
var response = await ExecuteWithRetryAsync(() =>
PutAsync<object>("/business/CamWorkrecord", record));
if (response.IsSuccess)
{
_logService?.Info($"更新工作记录成功ID: {record.Id}");
return (true, "更新成功");
}
return (false, response.Msg ?? "更新失败");
}
catch (Exception ex)
{
_logService?.Error($"更新工作记录异常: {ex.Message}", ex);
return (false, $"更新异常: {ex.Message}");
}
}
/// <summary>
/// 删除工作记录
/// </summary>
public async Task<(bool Success, string Message)> DeleteWorkRecordAsync(int id)
{
try
{
await EnsureTokenValidAsync();
var response = await ExecuteWithRetryAsync(() =>
DeleteAsync<object>($"/business/CamWorkrecord/{id}"));
if (response.IsSuccess)
{
_logService?.Info($"删除工作记录成功ID: {id}");
return (true, "删除成功");
}
return (false, response.Msg ?? "删除失败");
}
catch (Exception ex)
{
_logService?.Error($"删除工作记录异常: {ex.Message}", ex);
return (false, $"删除异常: {ex.Message}");
}
}
#endregion
#region
/// <summary>
/// 获取月报表数据
/// </summary>
public async Task<(bool Success, string Message, List<MonthlyReportDto>? Data)> GetMonthlyReportAsync(
MonthReportQueryDto query)
{
try
{
await EnsureTokenValidAsync();
var queryParams = BuildQueryString(query);
var response = await ExecuteWithRetryAsync(() =>
GetAsync<List<MonthlyReportDto>>($"/business/CamWorkers/list?{queryParams}"));
if (response.IsSuccess && response.Data != null)
{
return (true, "查询成功", response.Data);
}
return (false, response.Msg ?? "查询失败", null);
}
catch (Exception ex)
{
_logService?.Error($"获取月报表异常: {ex.Message}", ex);
return (false, $"查询异常: {ex.Message}", null);
}
}
/// <summary>
/// 获取指定月份的所有图片
/// </summary>
public async Task<(bool Success, string Message, List<MonthImageDto>? Data)> GetMonthImagesAsync(
string yearMonth)
{
try
{
await EnsureTokenValidAsync();
var response = await ExecuteWithRetryAsync(() =>
GetAsync<List<MonthImageDto>>($"/api/workrecord/monthImages?yearMonth={Uri.EscapeDataString(yearMonth)}"));
if (response.IsSuccess && response.Data != null)
{
return (true, "获取成功", response.Data);
}
return (false, response.Msg ?? "获取失败", null);
}
catch (Exception ex)
{
_logService?.Error($"获取月份图片异常: {ex.Message}", ex);
return (false, $"获取异常: {ex.Message}", null);
}
}
#endregion
#region
/// <summary>
/// 查询工作记录(导出用)
/// </summary>
public async Task<(bool Success, string Message, PagedData<WorkRecordExportDto>? Data)>
GetExportListAsync(WorkRecordExportQuery query)
{
try
{
await EnsureTokenValidAsync();
var queryParams = BuildQueryString(query);
var response = await ExecuteWithRetryAsync(() =>
GetPagedAsync<WorkRecordExportDto>($"/api/workrecord/export/list?{queryParams}"));
if (response.IsSuccess && response.Data != null)
{
return (true, "查询成功", response.Data);
}
return (false, response.Msg ?? "查询失败", null);
}
catch (Exception ex)
{
_logService?.Error($"查询导出列表异常: {ex.Message}", ex);
return (false, $"查询异常: {ex.Message}", null);
}
}
/// <summary>
/// 获取导出记录总数
/// </summary>
public async Task<(bool Success, int TotalCount, int TotalImages)>
GetExportCountAsync(WorkRecordExportQuery query)
{
var result = await GetExportListAsync(new WorkRecordExportQuery
{
PageNum = 1,
PageSize = 1,
StartDate = query.StartDate,
EndDate = query.EndDate,
DeptName = query.DeptName,
WorkerName = query.WorkerName,
Content = query.Content
});
if (result.Success && result.Data != null)
{
return (true, result.Data.TotalNum, 0);
}
return (false, 0, 0);
}
#endregion
#region
/// <summary>
/// 查询待迁移记录
/// </summary>
public async Task<(bool Success, string Message, PagedData<MigrationRecordDto>? Data)>
GetMigrationListAsync(MigrationQuery query)
{
try
{
_logService?.Info($"[迁移] 开始查询迁移列表, 页码={query.PageNum}, 状态={query.Status}");
await EnsureTokenValidAsync();
var queryParams = BuildQueryString(query);
var response = await ExecuteWithRetryAsync(() =>
GetPagedAsync<MigrationRecordDto>($"/api/workrecord/migration/list?{queryParams}"));
if (response.IsSuccess && response.Data != null)
{
_logService?.Info($"[迁移] 查询成功, 共 {response.Data.TotalNum} 条记录");
return (true, "查询成功", response.Data);
}
_logService?.Warn($"[迁移] 查询失败: {response.Msg}, Code={response.Code}");
return (false, response.Msg ?? "查询失败", null);
}
catch (Exception ex)
{
_logService?.Error($"[迁移] 查询迁移列表异常: {ex.Message}", ex);
return (false, $"查询异常: {ex.Message}", null);
}
}
/// <summary>
/// 更新迁移后的 URL
/// </summary>
public async Task<(bool Success, string Message)> UpdateMigrationUrlsAsync(
MigrationUpdateRequest request)
{
try
{
_logService?.Info($"[迁移] 开始更新URL, 记录ID={request.RecordId}, 图片数={request.ImageUrls?.Count ?? 0}");
await EnsureTokenValidAsync();
var response = await ExecuteWithRetryAsync(() =>
PostAsync<object>("/api/workrecord/migration/update", request));
if (response.IsSuccess)
{
_logService?.Info($"[迁移] 更新URL成功, 记录ID={request.RecordId}");
return (true, "更新成功");
}
_logService?.Warn($"[迁移] 更新URL失败: {response.Msg}, Code={response.Code}");
return (false, response.Msg ?? "更新失败");
}
catch (Exception ex)
{
_logService?.Error($"[迁移] 更新迁移URL异常: {ex.Message}", ex);
return (false, $"更新异常: {ex.Message}");
}
}
/// <summary>
/// 获取 COS 临时密钥
/// </summary>
public async Task<(bool Success, string Message, CosTempCredentials? Data)>
GetTempCredentialsAsync()
{
try
{
await EnsureTokenValidAsync();
var response = await ExecuteWithRetryAsync(() =>
GetAsync<CosTempCredentials>("/api/cos/getTempCredentials"));
if (response.IsSuccess && response.Data != null)
{
return (true, "获取成功", response.Data);
}
return (false, response.Msg ?? "获取临时密钥失败", null);
}
catch (Exception ex)
{
_logService?.Error($"获取临时密钥异常: {ex.Message}", ex);
return (false, $"获取临时密钥异常: {ex.Message}", null);
}
}
#endregion
#region
/// <summary>
/// 下载图片
/// </summary>
public async Task<(bool Success, byte[]? Data, string Message)> DownloadImageAsync(
string imageUrl, CancellationToken cancellationToken = default)
{
try
{
var fullUrl = imageUrl.StartsWith("http") ? imageUrl : $"{_baseUrl}{imageUrl}";
using var response = await _httpClient.GetAsync(fullUrl, cancellationToken);
if (response.IsSuccessStatusCode)
{
var data = await response.Content.ReadAsByteArrayAsync(cancellationToken);
return (true, data, "下载成功");
}
return (false, null, $"下载失败: {response.StatusCode}");
}
catch (OperationCanceledException)
{
return (false, null, "下载已取消");
}
catch (Exception ex)
{
_logService?.Error($"下载图片异常: {ex.Message}", ex);
return (false, null, $"下载异常: {ex.Message}");
}
}
#endregion
#region HTTP Token
/// <summary>
/// 确保 Token 有效,如果即将过期则自动刷新
/// </summary>
private async Task EnsureTokenValidAsync()
{
if (IsTokenExpiringSoon && !string.IsNullOrEmpty(_refreshToken))
{
_logService?.Info("Token 即将过期,尝试自动刷新");
await RefreshTokenAsync();
}
}
/// <summary>
/// 带重试机制的请求执行
/// </summary>
private async Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> action, int maxRetries = MaxRetryCount)
where T : class
{
Exception? lastException = null;
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
return await action();
}
catch (HttpRequestException ex) when (IsRetryableException(ex))
{
lastException = ex;
_logService?.Warn($"请求失败,第 {attempt}/{maxRetries} 次重试: {ex.Message}");
if (attempt < maxRetries)
{
await Task.Delay(RetryDelayMs * attempt);
}
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
lastException = ex;
_logService?.Warn($"请求超时,第 {attempt}/{maxRetries} 次重试");
if (attempt < maxRetries)
{
await Task.Delay(RetryDelayMs * attempt);
}
}
}
_logService?.Error($"请求失败,已达最大重试次数: {lastException?.Message}", lastException);
throw lastException ?? new Exception("请求失败");
}
/// <summary>
/// 判断异常是否可重试
/// </summary>
private static bool IsRetryableException(HttpRequestException ex)
{
// 网络错误、服务器错误5xx可重试
if (ex.StatusCode.HasValue)
{
var statusCode = (int)ex.StatusCode.Value;
return statusCode >= 500 && statusCode < 600;
}
// 无状态码的网络错误也可重试
return true;
}
private async Task<ApiResult<T>> GetAsync<T>(string endpoint)
{
var url = $"{_baseUrl}{endpoint}";
var response = await _httpClient.GetAsync(url);
// 处理 401 未授权
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
return new ApiResult<T> { Code = 401, Msg = "登录已失效,请重新登录" };
}
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<ApiResult<T>>(content, JsonOptions)
?? new ApiResult<T> { Code = 500, Msg = "解析响应失败" };
}
private async Task<PagedResult<T>> GetPagedAsync<T>(string endpoint)
{
var url = $"{_baseUrl}{endpoint}";
var response = await _httpClient.GetAsync(url);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
return new PagedResult<T> { Code = 401, Msg = "登录已失效,请重新登录" };
}
var content = await response.Content.ReadAsStringAsync();
// 调试日志:记录原始 JSON 响应(完整内容)
_logService?.Info($"[API响应] {endpoint} 原始JSON: {content}");
return JsonSerializer.Deserialize<PagedResult<T>>(content, JsonOptions)
?? new PagedResult<T> { Code = 500, Msg = "解析响应失败" };
}
private async Task<ApiResult<T>> PostAsync<T>(string endpoint, object data)
{
var url = $"{_baseUrl}{endpoint}";
var json = JsonSerializer.Serialize(data, JsonOptions);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(url, content);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
return new ApiResult<T> { Code = 401, Msg = "登录已失效,请重新登录" };
}
var responseContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<ApiResult<T>>(responseContent, JsonOptions)
?? new ApiResult<T> { Code = 500, Msg = "解析响应失败" };
}
private async Task<ApiResult<T>> PutAsync<T>(string endpoint, object data)
{
var url = $"{_baseUrl}{endpoint}";
var json = JsonSerializer.Serialize(data, JsonOptions);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PutAsync(url, content);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
return new ApiResult<T> { Code = 401, Msg = "登录已失效,请重新登录" };
}
var responseContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<ApiResult<T>>(responseContent, JsonOptions)
?? new ApiResult<T> { Code = 500, Msg = "解析响应失败" };
}
private async Task<ApiResult<T>> DeleteAsync<T>(string endpoint)
{
var url = $"{_baseUrl}{endpoint}";
var response = await _httpClient.DeleteAsync(url);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
return new ApiResult<T> { Code = 401, Msg = "登录已失效,请重新登录" };
}
var responseContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<ApiResult<T>>(responseContent, JsonOptions)
?? new ApiResult<T> { Code = 500, Msg = "解析响应失败" };
}
private static string BuildQueryString(object obj)
{
var properties = obj.GetType().GetProperties();
var queryParams = new List<string>();
foreach (var prop in properties)
{
var value = prop.GetValue(obj);
if (value == null) continue;
string stringValue;
if (value is DateTime dt)
{
stringValue = dt.ToString("yyyy-MM-dd");
}
else
{
stringValue = value.ToString() ?? "";
}
if (!string.IsNullOrEmpty(stringValue))
{
var name = char.ToLower(prop.Name[0]) + prop.Name[1..];
queryParams.Add($"{name}={Uri.EscapeDataString(stringValue)}");
}
}
return string.Join("&", queryParams);
}
#endregion
#region IDisposable
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_httpClient.Dispose();
}
_disposed = true;
}
}
#endregion
}
/// <summary>
/// Token 刷新响应
/// </summary>
public class TokenRefreshResponse
{
public string Token { get; set; } = "";
public string RefreshToken { get; set; } = "";
public DateTime ExpireTime { get; set; }
}
/// <summary>
/// 灵活的日期时间转换器,支持多种日期格式
/// </summary>
public class FlexibleDateTimeConverter : JsonConverter<DateTime?>
{
private static readonly string[] DateFormats = new[]
{
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-ddTHH:mm:ss",
"yyyy-MM-ddTHH:mm:ss.fff",
"yyyy-MM-ddTHH:mm:ss.fffffffZ",
"yyyy-MM-ddTHH:mm:ssZ",
"yyyy-MM-dd",
"yyyy/MM/dd HH:mm:ss",
"yyyy/MM/dd"
};
public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
{
return null;
}
if (reader.TokenType == JsonTokenType.String)
{
var dateString = reader.GetString();
if (string.IsNullOrWhiteSpace(dateString))
{
return null;
}
foreach (var format in DateFormats)
{
if (DateTime.TryParseExact(dateString, format,
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.None, out var result))
{
return result;
}
}
if (DateTime.TryParse(dateString, out var defaultResult))
{
return defaultResult;
}
}
return null;
}
public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options)
{
if (value.HasValue)
{
writer.WriteStringValue(value.Value.ToString("yyyy-MM-dd HH:mm:ss"));
}
else
{
writer.WriteNullValue();
}
}
}
}