feat: add 客户端

This commit is contained in:
GukSang.Jin
2026-02-25 15:43:47 +08:00
parent 833031864b
commit aac0dd8fec
16 changed files with 888 additions and 39 deletions

View File

@@ -0,0 +1,88 @@
using System.Net.Http;
using System.Net.Http.Json;
using PetWashControl.Models;
namespace PetWashControl.Services;
public class ApiService
{
private readonly HttpClient _httpClient;
private readonly ConfigurationService _config;
public ApiService(ConfigurationService? config = null)
{
_config = config ?? new ConfigurationService();
_httpClient = new HttpClient
{
BaseAddress = new Uri(_config.ApiBaseUrl),
Timeout = TimeSpan.FromSeconds(30)
};
}
public async Task<List<Package>> GetPackagesAsync()
{
try
{
return await _httpClient.GetFromJsonAsync<List<Package>>("api/packages")
?? new List<Package>();
}
catch (HttpRequestException ex)
{
throw new Exception($"无法连接到服务器: {ex.Message}", ex);
}
}
public async Task<Order?> CreateOrderAsync(int packageId)
{
try
{
var response = await _httpClient.PostAsJsonAsync("api/orders", new { packageId });
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<Order>();
}
catch (HttpRequestException ex)
{
throw new Exception($"创建订单失败: {ex.Message}", ex);
}
}
public async Task<Order?> ConfirmPaymentAsync(int orderId)
{
try
{
var response = await _httpClient.PostAsync($"api/orders/{orderId}/payment", null);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<Order>();
}
catch (HttpRequestException ex)
{
throw new Exception($"确认支付失败: {ex.Message}", ex);
}
}
public async Task<Order?> GetOrderAsync(int orderId)
{
try
{
return await _httpClient.GetFromJsonAsync<Order>($"api/orders/{orderId}");
}
catch (HttpRequestException ex)
{
throw new Exception($"获取订单失败: {ex.Message}", ex);
}
}
public async Task<Order?> UpdateOrderStatusAsync(int orderId, OrderStatus status)
{
try
{
var response = await _httpClient.PutAsJsonAsync($"api/orders/{orderId}/status", new { status });
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<Order>();
}
catch (HttpRequestException ex)
{
throw new Exception($"更新订单状态失败: {ex.Message}", ex);
}
}
}

View File

@@ -0,0 +1,12 @@
namespace PetWashControl.Services;
public class ConfigurationService
{
public string ApiBaseUrl { get; set; } = "https://localhost:7001/";
public string MqttBrokerHost { get; set; } = "localhost";
public int MqttBrokerPort { get; set; } = 1883;
public string MqttClientId { get; set; } = "PetWashControl";
public int PaymentCheckIntervalSeconds { get; set; } = 2;
public int WashSimulationSeconds { get; set; } = 10;
}

View File

@@ -0,0 +1,50 @@
using System.Diagnostics;
using System.IO;
namespace PetWashControl.Services;
public class LogService
{
private readonly string _logFilePath;
public LogService()
{
var logDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
Directory.CreateDirectory(logDirectory);
_logFilePath = Path.Combine(logDirectory, $"log_{DateTime.Now:yyyyMMdd}.txt");
}
public void LogInfo(string message)
{
Log("INFO", message);
}
public void LogWarning(string message)
{
Log("WARN", message);
}
public void LogError(string message, Exception? ex = null)
{
var fullMessage = ex != null ? $"{message}\n{ex}" : message;
Log("ERROR", fullMessage);
}
private void Log(string level, string message)
{
var logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{level}] {message}";
// 输出到调试窗口
Debug.WriteLine(logMessage);
// 写入文件
try
{
File.AppendAllText(_logFilePath, logMessage + Environment.NewLine);
}
catch
{
// 忽略日志写入失败
}
}
}

View File

@@ -0,0 +1,109 @@
using MQTTnet;
using MQTTnet.Client;
using System.Text;
using System.Text.Json;
namespace PetWashControl.Services;
public class MqttClientService
{
private readonly IMqttClient _mqttClient;
private readonly MqttClientOptions _options;
private readonly ConfigurationService _config;
public event Action<string, string>? MessageReceived;
public bool IsConnected => _mqttClient.IsConnected;
public MqttClientService(ConfigurationService? config = null)
{
_config = config ?? new ConfigurationService();
var factory = new MqttFactory();
_mqttClient = factory.CreateMqttClient();
_options = new MqttClientOptionsBuilder()
.WithTcpServer(_config.MqttBrokerHost, _config.MqttBrokerPort)
.WithClientId(_config.MqttClientId)
.WithCleanSession()
.Build();
_mqttClient.ApplicationMessageReceivedAsync += OnMessageReceived;
_mqttClient.DisconnectedAsync += OnDisconnected;
}
public async Task ConnectAsync()
{
if (_mqttClient.IsConnected)
return;
try
{
await _mqttClient.ConnectAsync(_options, CancellationToken.None);
// 订阅设备状态和命令主题
var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
.WithTopicFilter("device/status")
.WithTopicFilter("device/command")
.Build();
await _mqttClient.SubscribeAsync(subscribeOptions, CancellationToken.None);
}
catch (Exception ex)
{
throw new Exception($"MQTT连接失败: {ex.Message}", ex);
}
}
public async Task DisconnectAsync()
{
if (_mqttClient.IsConnected)
{
await _mqttClient.DisconnectAsync();
}
}
public async Task PublishAsync(string topic, object payload)
{
if (!_mqttClient.IsConnected)
{
throw new InvalidOperationException("MQTT客户端未连接");
}
var json = JsonSerializer.Serialize(payload);
var message = new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithPayload(json)
.WithQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce)
.Build();
await _mqttClient.PublishAsync(message, CancellationToken.None);
}
private Task OnMessageReceived(MqttApplicationMessageReceivedEventArgs args)
{
var topic = args.ApplicationMessage.Topic;
var payloadBytes = args.ApplicationMessage.PayloadSegment.ToArray();
var payload = Encoding.UTF8.GetString(payloadBytes);
MessageReceived?.Invoke(topic, payload);
return Task.CompletedTask;
}
private async Task OnDisconnected(MqttClientDisconnectedEventArgs args)
{
// 自动重连
if (!args.ClientWasConnected)
return;
await Task.Delay(TimeSpan.FromSeconds(5));
try
{
await ConnectAsync();
}
catch
{
// 重连失败,等待下次重试
}
}
}