WorkCamera/client/WorkCameraExport/Services/ApiService.cs
2026-01-05 21:20:55 +08:00

467 lines
15 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.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using WorkCameraExport.Models;
namespace WorkCameraExport.Services
{
/// <summary>
/// API 服务类 - 处理与后端服务器的所有 HTTP 通信
/// </summary>
public class ApiService : IDisposable
{
private readonly HttpClient _httpClient;
private string _baseUrl = "";
private string _token = "";
private bool _disposed;
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
Converters = { new FlexibleDateTimeConverter() }
};
public ApiService()
{
_httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(30)
};
// 设置 User-Agent便于服务器识别客户端
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("WorkCameraExport/1.0 (Windows; .NET)");
}
/// <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);
}
else
{
_httpClient.DefaultRequestHeaders.Authorization = null;
}
}
/// <summary>
/// 获取当前 Token
/// </summary>
public string GetToken() => _token;
/// <summary>
/// 检查是否已登录
/// </summary>
public bool IsLoggedIn => !string.IsNullOrEmpty(_token);
#region
/// <summary>
/// 获取验证码
/// </summary>
public async Task<(bool Success, string Message, CaptchaResponse? Data)> GetCaptchaAsync()
{
try
{
var response = await GetAsync<CaptchaResponse>("/captchaImage");
if (response.IsSuccess && response.Data != null)
{
return (true, "获取成功", response.Data);
}
return (false, response.Msg ?? "获取验证码失败", null);
}
catch (Exception 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
};
// 登录接口直接返回 Token 字符串,不是标准的 ApiResult 格式
var url = $"{_baseUrl}/login";
var json = System.Text.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 = System.Text.Json.JsonSerializer.Deserialize<ApiResult<string>>(responseContent, JsonOptions);
if (response != null && response.IsSuccess && !string.IsNullOrEmpty(response.Data))
{
var token = response.Data;
SetToken(token);
return (true, "登录成功", new LoginResponse { Token = token });
}
return (false, response?.Msg ?? "登录失败", null);
}
catch (Exception ex)
{
return (false, $"登录异常: {ex.Message}", null);
}
}
/// <summary>
/// 退出登录
/// </summary>
public void Logout()
{
SetToken("");
}
#endregion
#region
/// <summary>
/// 查询工作记录(导出用)
/// </summary>
public async Task<(bool Success, string Message, PagedData<WorkRecordExportDto>? Data)>
GetExportListAsync(WorkRecordExportQuery query)
{
try
{
var queryParams = BuildQueryString(query);
var response = await 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)
{
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
{
var queryParams = BuildQueryString(query);
var response = await GetPagedAsync<MigrationRecordDto>(
$"/api/workrecord/migration/list?{queryParams}");
if (response.IsSuccess && response.Data != null)
{
return (true, "查询成功", response.Data);
}
return (false, response.Msg ?? "查询失败", null);
}
catch (Exception ex)
{
return (false, $"查询异常: {ex.Message}", null);
}
}
/// <summary>
/// 更新迁移后的 URL
/// </summary>
public async Task<(bool Success, string Message)> UpdateMigrationUrlsAsync(
MigrationUpdateRequest request)
{
try
{
var response = await PostAsync<object>("/api/workrecord/migration/update", request);
if (response.IsSuccess)
{
return (true, "更新成功");
}
return (false, response.Msg ?? "更新失败");
}
catch (Exception ex)
{
return (false, $"更新异常: {ex.Message}");
}
}
/// <summary>
/// 获取 COS 临时密钥
/// </summary>
public async Task<(bool Success, string Message, CosTempCredentials? Data)>
GetTempCredentialsAsync()
{
try
{
var response = await GetAsync<CosTempCredentials>("/api/cos/getTempCredentials");
if (response.IsSuccess && response.Data != null)
{
return (true, "获取成功", response.Data);
}
return (false, response.Msg ?? "获取临时密钥失败", null);
}
catch (Exception 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)
{
return (false, null, $"下载异常: {ex.Message}");
}
}
#endregion
#region HTTP
private async Task<ApiResult<T>> GetAsync<T>(string endpoint)
{
var url = $"{_baseUrl}{endpoint}";
var response = await _httpClient.GetAsync(url);
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);
var content = await response.Content.ReadAsStringAsync();
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);
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>
/// 灵活的日期时间转换器,支持多种日期格式
/// </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();
}
}
}
}