feat: add 服务端
This commit is contained in:
64
PetWash.Api/Controllers/OrdersController.cs
Normal file
64
PetWash.Api/Controllers/OrdersController.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PetWash.Api.Models;
|
||||
using PetWash.Api.Services;
|
||||
|
||||
namespace PetWash.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class OrdersController : ControllerBase
|
||||
{
|
||||
private readonly OrderService _orderService;
|
||||
|
||||
public OrdersController(OrderService orderService)
|
||||
{
|
||||
_orderService = orderService;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> CreateOrder([FromBody] CreateOrderRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var order = await _orderService.CreateOrderAsync(request.PackageId);
|
||||
return Ok(order);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("{id}/payment")]
|
||||
public async Task<IActionResult> ConfirmPayment(int id)
|
||||
{
|
||||
var order = await _orderService.ConfirmPaymentAsync(id);
|
||||
if (order == null)
|
||||
return NotFound();
|
||||
|
||||
return Ok(order);
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetOrder(int id)
|
||||
{
|
||||
var order = await _orderService.GetOrderAsync(id);
|
||||
if (order == null)
|
||||
return NotFound();
|
||||
|
||||
return Ok(order);
|
||||
}
|
||||
|
||||
[HttpPut("{id}/status")]
|
||||
public async Task<IActionResult> UpdateStatus(int id, [FromBody] UpdateStatusRequest request)
|
||||
{
|
||||
var order = await _orderService.UpdateOrderStatusAsync(id, request.Status);
|
||||
if (order == null)
|
||||
return NotFound();
|
||||
|
||||
return Ok(order);
|
||||
}
|
||||
}
|
||||
|
||||
public record CreateOrderRequest(int PackageId);
|
||||
public record UpdateStatusRequest(OrderStatus Status);
|
||||
34
PetWash.Api/Controllers/PackagesController.cs
Normal file
34
PetWash.Api/Controllers/PackagesController.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PetWash.Api.Data;
|
||||
|
||||
namespace PetWash.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class PackagesController : ControllerBase
|
||||
{
|
||||
private readonly PetWashDbContext _context;
|
||||
|
||||
public PackagesController(PetWashDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetPackages()
|
||||
{
|
||||
var packages = await _context.Packages.ToListAsync();
|
||||
return Ok(packages);
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetPackage(int id)
|
||||
{
|
||||
var package = await _context.Packages.FindAsync(id);
|
||||
if (package == null)
|
||||
return NotFound();
|
||||
|
||||
return Ok(package);
|
||||
}
|
||||
}
|
||||
24
PetWash.Api/Data/PetWashDbContext.cs
Normal file
24
PetWash.Api/Data/PetWashDbContext.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PetWash.Api.Models;
|
||||
|
||||
namespace PetWash.Api.Data;
|
||||
|
||||
public class PetWashDbContext : DbContext
|
||||
{
|
||||
public PetWashDbContext(DbContextOptions<PetWashDbContext> options) : base(options) { }
|
||||
|
||||
public DbSet<Package> Packages { get; set; }
|
||||
public DbSet<Order> Orders { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
// 初始化套餐数据
|
||||
modelBuilder.Entity<Package>().HasData(
|
||||
new Package { Id = 1, Name = "基础清洗", Price = 50, DurationMinutes = 15, Description = "基础清洗服务,适合小型宠物" },
|
||||
new Package { Id = 2, Name = "标准清洗", Price = 80, DurationMinutes = 25, Description = "标准清洗+烘干,适合中型宠物" },
|
||||
new Package { Id = 3, Name = "豪华清洗", Price = 120, DurationMinutes = 35, Description = "深度清洗+烘干+护理,适合大型宠物" }
|
||||
);
|
||||
}
|
||||
}
|
||||
10
PetWash.Api/Models/DeviceStatus.cs
Normal file
10
PetWash.Api/Models/DeviceStatus.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace PetWash.Api.Models;
|
||||
|
||||
public class DeviceStatus
|
||||
{
|
||||
public string Status { get; set; } = "Idle";
|
||||
public bool DoorOpen { get; set; }
|
||||
public bool IsWashing { get; set; }
|
||||
public int? CurrentOrderId { get; set; }
|
||||
public DateTime LastUpdated { get; set; }
|
||||
}
|
||||
26
PetWash.Api/Models/Order.cs
Normal file
26
PetWash.Api/Models/Order.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace PetWash.Api.Models;
|
||||
|
||||
public class Order
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int PackageId { get; set; }
|
||||
public Package? Package { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public OrderStatus Status { get; set; }
|
||||
public bool IsPaid { get; set; }
|
||||
public DateTime? PaidAt { get; set; }
|
||||
public DateTime? StartedAt { get; set; }
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
}
|
||||
|
||||
public enum OrderStatus
|
||||
{
|
||||
Created,
|
||||
WaitingPayment,
|
||||
Paid,
|
||||
DoorOpened,
|
||||
DoorClosed,
|
||||
Washing,
|
||||
Completed,
|
||||
Cancelled
|
||||
}
|
||||
10
PetWash.Api/Models/Package.cs
Normal file
10
PetWash.Api/Models/Package.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace PetWash.Api.Models;
|
||||
|
||||
public class Package
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public decimal Price { get; set; }
|
||||
public int DurationMinutes { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
}
|
||||
19
PetWash.Api/PetWash.Api.csproj
Normal file
19
PetWash.Api/PetWash.Api.csproj
Normal file
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MQTTnet.AspNetCore" Version="4.3.7.1207" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
6
PetWash.Api/PetWash.Api.http
Normal file
6
PetWash.Api/PetWash.Api.http
Normal file
@@ -0,0 +1,6 @@
|
||||
@PetWash.Api_HostAddress = http://localhost:5274
|
||||
|
||||
GET {{PetWash.Api_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
51
PetWash.Api/Program.cs
Normal file
51
PetWash.Api/Program.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PetWash.Api.Data;
|
||||
using PetWash.Api.Services;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// 添加数据库
|
||||
builder.Services.AddDbContext<PetWashDbContext>(options =>
|
||||
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection") ?? "Data Source=petwash.db"));
|
||||
|
||||
// 添加服务
|
||||
builder.Services.AddSingleton<MqttService>();
|
||||
builder.Services.AddHostedService(provider => provider.GetRequiredService<MqttService>());
|
||||
builder.Services.AddScoped<OrderService>();
|
||||
|
||||
// 添加CORS
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// 初始化数据库
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<PetWashDbContext>();
|
||||
db.Database.EnsureCreated();
|
||||
}
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.UseCors();
|
||||
app.UseHttpsRedirection();
|
||||
app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
41
PetWash.Api/Properties/launchSettings.json
Normal file
41
PetWash.Api/Properties/launchSettings.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:58807",
|
||||
"sslPort": 44392
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5274",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7203;http://localhost:5274",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
68
PetWash.Api/Services/MqttService.cs
Normal file
68
PetWash.Api/Services/MqttService.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using MQTTnet;
|
||||
using MQTTnet.Server;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace PetWash.Api.Services;
|
||||
|
||||
public class MqttService : IHostedService
|
||||
{
|
||||
private readonly MqttServer _mqttServer;
|
||||
private readonly ILogger<MqttService> _logger;
|
||||
|
||||
public MqttService(ILogger<MqttService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
var optionsBuilder = new MqttServerOptionsBuilder()
|
||||
.WithDefaultEndpoint()
|
||||
.WithDefaultEndpointPort(1883);
|
||||
|
||||
_mqttServer = new MqttFactory().CreateMqttServer(optionsBuilder.Build());
|
||||
|
||||
_mqttServer.ClientConnectedAsync += OnClientConnected;
|
||||
_mqttServer.ClientDisconnectedAsync += OnClientDisconnected;
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await _mqttServer.StartAsync();
|
||||
_logger.LogInformation("MQTT Broker started on port 1883");
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await _mqttServer.StopAsync();
|
||||
_logger.LogInformation("MQTT Broker stopped");
|
||||
}
|
||||
|
||||
private Task OnClientConnected(ClientConnectedEventArgs args)
|
||||
{
|
||||
_logger.LogInformation($"Client connected: {args.ClientId}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task OnClientDisconnected(ClientDisconnectedEventArgs args)
|
||||
{
|
||||
_logger.LogInformation($"Client disconnected: {args.ClientId}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task PublishAsync(string topic, object payload)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(payload);
|
||||
var message = new MqttApplicationMessageBuilder()
|
||||
.WithTopic(topic)
|
||||
.WithPayload(json)
|
||||
.WithQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce)
|
||||
.WithRetainFlag(false)
|
||||
.Build();
|
||||
|
||||
await _mqttServer.InjectApplicationMessage(new InjectedMqttApplicationMessage(message)
|
||||
{
|
||||
SenderClientId = "PetWash.Api"
|
||||
});
|
||||
|
||||
_logger.LogInformation($"Published to {topic}: {json}");
|
||||
}
|
||||
}
|
||||
96
PetWash.Api/Services/OrderService.cs
Normal file
96
PetWash.Api/Services/OrderService.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PetWash.Api.Data;
|
||||
using PetWash.Api.Models;
|
||||
|
||||
namespace PetWash.Api.Services;
|
||||
|
||||
public class OrderService
|
||||
{
|
||||
private readonly PetWashDbContext _context;
|
||||
private readonly MqttService _mqttService;
|
||||
private readonly ILogger<OrderService> _logger;
|
||||
|
||||
public OrderService(PetWashDbContext context, MqttService mqttService, ILogger<OrderService> logger)
|
||||
{
|
||||
_context = context;
|
||||
_mqttService = mqttService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<Order> CreateOrderAsync(int packageId)
|
||||
{
|
||||
var package = await _context.Packages.FindAsync(packageId);
|
||||
if (package == null)
|
||||
throw new ArgumentException("套餐不存在");
|
||||
|
||||
var order = new Order
|
||||
{
|
||||
PackageId = packageId,
|
||||
CreatedAt = DateTime.Now,
|
||||
Status = OrderStatus.WaitingPayment,
|
||||
IsPaid = false
|
||||
};
|
||||
|
||||
_context.Orders.Add(order);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation($"订单创建成功: OrderId={order.Id}, PackageId={packageId}");
|
||||
return order;
|
||||
}
|
||||
|
||||
public async Task<Order?> ConfirmPaymentAsync(int orderId)
|
||||
{
|
||||
var order = await _context.Orders.Include(o => o.Package).FirstOrDefaultAsync(o => o.Id == orderId);
|
||||
if (order == null) return null;
|
||||
|
||||
order.IsPaid = true;
|
||||
order.PaidAt = DateTime.Now;
|
||||
order.Status = OrderStatus.Paid;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// 支付成功后,通过MQTT发送开门指令
|
||||
await _mqttService.PublishAsync("device/command", new
|
||||
{
|
||||
command = "open_door",
|
||||
orderId = order.Id,
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
|
||||
_logger.LogInformation($"订单支付成功,已发送开门指令: OrderId={orderId}");
|
||||
return order;
|
||||
}
|
||||
|
||||
public async Task<Order?> UpdateOrderStatusAsync(int orderId, OrderStatus status)
|
||||
{
|
||||
var order = await _context.Orders.FindAsync(orderId);
|
||||
if (order == null) return null;
|
||||
|
||||
order.Status = status;
|
||||
|
||||
if (status == OrderStatus.DoorClosed)
|
||||
{
|
||||
order.StartedAt = DateTime.Now;
|
||||
// 门关闭后,发送开始清洗指令
|
||||
await _mqttService.PublishAsync("device/command", new
|
||||
{
|
||||
command = "start_wash",
|
||||
orderId = order.Id,
|
||||
durationMinutes = order.Package?.DurationMinutes ?? 15,
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
}
|
||||
else if (status == OrderStatus.Completed)
|
||||
{
|
||||
order.CompletedAt = DateTime.Now;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInformation($"订单状态更新: OrderId={orderId}, Status={status}");
|
||||
return order;
|
||||
}
|
||||
|
||||
public async Task<Order?> GetOrderAsync(int orderId)
|
||||
{
|
||||
return await _context.Orders.Include(o => o.Package).FirstOrDefaultAsync(o => o.Id == orderId);
|
||||
}
|
||||
}
|
||||
8
PetWash.Api/appsettings.Development.json
Normal file
8
PetWash.Api/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
12
PetWash.Api/appsettings.json
Normal file
12
PetWash.Api/appsettings.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Data Source=petwash.db"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user