using NCC.Common.Core.Manager; using NCC.Common.Enum; using NCC.Common.Extension; using NCC.Common.Filter; using NCC.Dependency; using NCC.DynamicApiController; using NCC.FriendlyException; using NCC.Extend.Interfaces.UavOrder; using Mapster; using Microsoft.AspNetCore.Mvc; using SqlSugar; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using NCC.Extend.Entitys; using NCC.Extend.Entitys.Dto.UavOrder; using Yitter.IdGenerator; using NCC.Common.Helper; using NCC.JsonSerialization; using Microsoft.AspNetCore.Authorization; using NCC.Extend.Entitys.Enums; using NCC.Code; using System.Text.Json; using AlipaySDKNet.OpenAPI.Util.Model; using Alipay.AopSdk.Core.Util; using Microsoft.AspNetCore.Http; using Serilog; using System.IO; using System.Text; using NCC.Extend.Interfaces.MqttPublisher; using NCC.Extend.Entitys.Dto.UavDevice; using NCC.Extend.Interfaces.AliPay; using NCC.Extend.Entitys.Dto.UavFeeRule; using NCC.UnifyResult; using NCC.Localization; using Spire.Presentation; using NCC.System.Entitys; using NCC.System.Entitys.Permission; using SqlSugar; namespace NCC.Extend.UavOrder { /// /// 订单管理服务 /// [ApiDescriptionSettings(Tag = "订单管理服务", Name = "UavOrder", Order = 200)] [Route("api/Extend/[controller]")] public class UavOrderService : IUavOrderService, IDynamicApiController, ITransient { private readonly ISqlSugarRepository _uavOrderRepository; private readonly SqlSugarScope _db; private readonly IUserManager _userManager; private readonly IHttpContextAccessor _httpContextAccessor; private readonly IMqttPublisherService _mqttService; private readonly IAliPayService _aliPayService; /// /// 初始化一个类型的新实例 /// public UavOrderService(ISqlSugarRepository uavOrderRepository, IUserManager userManager, IHttpContextAccessor httpContextAccessor, IMqttPublisherService mqttService, IAliPayService aliPayService) { _uavOrderRepository = uavOrderRepository; _db = _uavOrderRepository.Context; _userManager = userManager; _httpContextAccessor = httpContextAccessor; _mqttService = mqttService; _aliPayService = aliPayService; } #region 获取支付宝配置 [NonAction] private static AlipayConfig GetAlipayConfig() { return new AlipayConfig { ServerUrl = "https://openapi.alipay.com", // AppId = "2021005150691832", AppId = App.Configuration["PaymentSettings:AlipayConfig:APPLETE_APPID"], PrivateKey = App.Configuration["PaymentSettings:AlipayConfig:ALIPay_RSA_PRIVATEKEY"], AlipayPublicKey = App.Configuration["PaymentSettings:AlipayConfig:ALIPay_RSA_ALI_PUBLICKEY"], }; } #endregion #region 获取订单详情 /// /// 获取订单详情 /// /// 订单ID /// [HttpGet("{id}")] public async Task GetInfo(string id) { // 查询订单实体 var entity = await _db.Queryable().FirstAsync(p => p.Id == id); if (entity == null) return null; // 查询相关信息(设备、站点、套餐、货道)一次性批量查询,减少数据库访问次数 var deviceTask = _db.Queryable().Where(p => p.Id == entity.DeviceId).FirstAsync(); var siteTask = _db.Queryable().Where(p => p.Id == entity.SiteId).FirstAsync(); var feeRuleTask = string.IsNullOrEmpty(entity.FeeRuleId) ? Task.FromResult(null) : _db.Queryable().Where(p => p.Id == entity.FeeRuleId).FirstAsync(); var cellTask = _db.Queryable().Where(p => p.Id == entity.RentCellId).FirstAsync(); await Task.WhenAll(deviceTask, siteTask, feeRuleTask, cellTask); var output = entity.Adapt(); output.startTime = entity.StartTime; output.endTime = entity.EndTime; output.statusString = global::System.Enum.GetName(typeof(UavOrderStatusEnum), output.status); output.feeRuleName = feeRuleTask.Result?.RuleName ?? "未选择套餐"; output.deviceName = deviceTask.Result?.DeviceName ?? ""; output.deviceCode = deviceTask.Result?.DeviceCode ?? ""; output.siteName = siteTask.Result?.SiteName ?? ""; output.rentCellCode = cellTask.Result?.CellCode ?? ""; return output; } #endregion #region 获取订单管理列表 /// /// 获取订单管理列表 /// /// 请求参数 /// [HttpGet("")] public async Task GetList([FromQuery] UavOrderListQueryInput input) { var sidx = input.sidx == null ? "id" : input.sidx; var data = await _db.Queryable() .WhereIF(!string.IsNullOrEmpty(input.orderNo), p => p.OrderNo.Contains(input.orderNo)) .WhereIF(!string.IsNullOrEmpty(input.deviceName), p => SqlFunc.Subqueryable().Where(d => d.Id == p.DeviceId).Select(d => d.DeviceName).Contains(input.deviceName)) .WhereIF(!string.IsNullOrEmpty(input.siteName), p => SqlFunc.Subqueryable().Where(s => s.Id == p.SiteId).Select(s => s.SiteName).Contains(input.siteName)) .WhereIF(!string.IsNullOrEmpty(input.aircraftName), p => SqlFunc.Subqueryable().Where(a => a.Id == p.AircraftId).Select(a => a.Model).Contains(input.aircraftName)) .WhereIF(!string.IsNullOrEmpty(input.agentName), p => SqlFunc.Subqueryable().Where(u => u.Id == p.AgentId).Select(u => u.RealName).Contains(input.agentName)) .WhereIF(!string.IsNullOrEmpty(input.rentCellName), p => SqlFunc.Subqueryable().Where(c => c.Id == p.RentCellId).Select(c => c.CellCode).Contains(input.rentCellName)) .WhereIF(!string.IsNullOrEmpty(input.returnCellName), p => SqlFunc.Subqueryable().Where(c => c.Id == p.ReturnCellId).Select(c => c.CellCode).Contains(input.returnCellName)) .WhereIF(!string.IsNullOrEmpty(input.userPhone), p => p.UserPhone.Contains(input.userPhone)) .Select(it => new UavOrderListOutput { id = it.Id, orderNo = it.OrderNo, deviceId = it.DeviceId, deviceName = SqlFunc.Subqueryable().Where(u => u.Id == it.DeviceId).Select(u => u.DeviceName), siteId = it.SiteId, siteName = SqlFunc.Subqueryable().Where(u => u.Id == it.SiteId).Select(u => u.SiteName), aircraftId = it.AircraftId, aircraftName = SqlFunc.Subqueryable().Where(u => u.Id == it.AircraftId).Select(u => u.Model), agentId = it.AgentId, agentName = SqlFunc.Subqueryable().Where(u => u.Id == it.AgentId).Select(u => u.RealName), userAccount = it.UserAccount, userPhone = it.UserPhone, userName = it.UserName, discountAmount = it.DiscountAmount, actualAmount = it.ActualAmount, shareRatio = it.ShareRatio, shareAmount = it.ShareAmount, finalAmount = it.FinalAmount, rentCellId = it.RentCellId, rentCellName = SqlFunc.Subqueryable().Where(u => u.Id == it.RentCellId).Select(u => u.CellCode), returnCellId = it.ReturnCellId, returnCellName = SqlFunc.Subqueryable().Where(u => u.Id == it.ReturnCellId).Select(u => u.CellCode), status = it.Status, remark = it.Remark, createTime = it.CreateTime, startTime = it.StartTime, endTime = it.EndTime, feeRuleId = it.FeeRuleId, aliAuthNo = it.AliAuthNo, returnDeviceId = it.ReturnDeviceId, returnDeviceName = SqlFunc.Subqueryable().Where(u => u.Id == it.ReturnDeviceId).Select(u => u.DeviceName), returnSiteId = it.ReturnSiteId, returnSiteName = SqlFunc.Subqueryable().Where(u => u.Id == it.ReturnSiteId).Select(u => u.SiteName), rfid1 = it.RFID1, rfid2 = it.RFID2 }).MergeTable().Mapper(p => { p.statusString = global::System.Enum.GetName(typeof(UavOrderStatusEnum), p.status); }).OrderBy(sidx + " " + input.sort).ToPagedListAsync(input.currentPage, input.pageSize); return PageResult.SqlSugarPageResult(data); } #endregion #region 获取当前代理商相关的订单 /// /// 获取当前代理商相关的订单 /// /// 请求参数 /// [HttpGet("GetListToAgent")] public async Task GetListToAgent([FromQuery] UavOrderListQueryInput input) { var sidx = input.sidx == null ? "id" : input.sidx; var data = await _db.Queryable() .Where(p => p.AgentId == _userManager.UserId) .WhereIF(!string.IsNullOrEmpty(input.orderNo), p => p.OrderNo.Contains(input.orderNo)) .WhereIF(!string.IsNullOrEmpty(input.deviceName), p => SqlFunc.Subqueryable().Where(d => d.Id == p.DeviceId).Select(d => d.DeviceName).Contains(input.deviceName)) .WhereIF(!string.IsNullOrEmpty(input.siteName), p => SqlFunc.Subqueryable().Where(s => s.Id == p.SiteId).Select(s => s.SiteName).Contains(input.siteName)) .WhereIF(!string.IsNullOrEmpty(input.aircraftName), p => SqlFunc.Subqueryable().Where(a => a.Id == p.AircraftId).Select(a => a.Model).Contains(input.aircraftName)) .WhereIF(!string.IsNullOrEmpty(input.rentCellName), p => SqlFunc.Subqueryable().Where(c => c.Id == p.RentCellId).Select(c => c.CellCode).Contains(input.rentCellName)) .WhereIF(!string.IsNullOrEmpty(input.returnCellName), p => SqlFunc.Subqueryable().Where(c => c.Id == p.ReturnCellId).Select(c => c.CellCode).Contains(input.returnCellName)) .WhereIF(!string.IsNullOrEmpty(input.createTime_start), p => p.CreateTime >= new DateTime(input.createTime_start.ToDate().Year, input.createTime_start.ToDate().Month, input.createTime_start.ToDate().Day, 0, 0, 0) && p.CreateTime <= new DateTime(input.createTime_end.ToDate().Year, input.createTime_end.ToDate().Month, input.createTime_end.ToDate().Day, 23, 59, 59)) .Select(it => new UavOrderListOutput { id = it.Id, orderNo = it.OrderNo, deviceId = it.DeviceId, deviceName = SqlFunc.Subqueryable().Where(u => u.Id == it.DeviceId).Select(u => u.DeviceName), siteId = it.SiteId, siteName = SqlFunc.Subqueryable().Where(u => u.Id == it.SiteId).Select(u => u.SiteName), aircraftId = it.AircraftId, aircraftName = SqlFunc.Subqueryable().Where(u => u.Id == it.AircraftId).Select(u => u.Model), agentId = it.AgentId, agentName = SqlFunc.Subqueryable().Where(u => u.Id == it.AgentId).Select(u => u.RealName), userAccount = it.UserAccount, userPhone = it.UserPhone, userName = it.UserName, discountAmount = it.DiscountAmount, actualAmount = it.ActualAmount, shareRatio = it.ShareRatio, shareAmount = it.ShareAmount, finalAmount = it.FinalAmount, rentCellId = it.RentCellId, rentCellName = SqlFunc.Subqueryable().Where(u => u.Id == it.RentCellId).Select(u => u.CellCode), returnCellId = it.ReturnCellId, returnCellName = SqlFunc.Subqueryable().Where(u => u.Id == it.ReturnCellId).Select(u => u.CellCode), status = it.Status, remark = it.Remark, endTime = it.EndTime, startTime = it.StartTime, createTime = it.CreateTime, }).MergeTable().Mapper(p => { p.statusString = global::System.Enum.GetName(typeof(UavOrderStatusEnum), p.status); }).OrderBy(sidx + " " + input.sort).ToPagedListAsync(input.currentPage, input.pageSize); return PageResult.SqlSugarPageResult(data); } #endregion #region 新建订单管理 /// /// 新建订单管理 /// /// 参数 /// [HttpPost("")] public async Task Create([FromBody] UavOrderCrInput input) { var entity = input.Adapt(); entity.Id = YitIdHelper.NextId().ToString(); var isOk = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteCommandAsync(); if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); } #endregion #region 新建订单管理(支付宝) /// /// 新建订单管理(支付宝) /// /// 参数 /// [HttpPost("CreateAlipayOrder")] public async Task CreateAlipayOrder([FromBody] UavOrderCrInputByAlipay input) { var DeviceEntity = await _db.Queryable().FirstAsync(x => x.DeviceCode == input.deviceId); var feeRuleEntity = await _db.Queryable().FirstAsync(x => x.Id == input.feeRuleId); if (DeviceEntity.Status != DeviceStatusEnum.启用.GetHashCode()) { return NCCException.Oh("设备未启用,请使用其他设备!"); } // 开启事务 try { var RerutnEntity = await _db.UseTranAsync(async () => { var halfHourAgo = DateTime.Now.AddMinutes(-30); // var halfHourAgo = DateTime.Now.AddMinutes(-0); try { //通过设备去找到设备货道,使用With(SqlSugar.SqlWith.RowLock)添加行锁 var deviceCellInfo = await _db.Queryable() .With(SqlSugar.SqlWith.RowLock) .Where(x => x.DeviceId == DeviceEntity.Id && x.Status == DeviceCellStatusEnum.充电.GetHashCode()) .Where(x => x.UpdateTime <= halfHourAgo) // 添加条件:更新时间超过半个小时 .WhereIF(feeRuleEntity.MultiCells == 1, x => !SqlFunc.IsNullOrEmpty(x.Rfid1) && !SqlFunc.IsNullOrEmpty(x.Rfid2)) .WhereIF(feeRuleEntity.MultiCells == 0, x => !SqlFunc.IsNullOrEmpty(x.Rfid1) && SqlFunc.IsNullOrEmpty(x.Rfid2)) .OrderBy(x => x.UpdateTime, OrderByType.Asc) .FirstAsync(); if (deviceCellInfo.IsNullOrEmpty()) { return new { orderNo = "", cellNo = "", message = "提示充电时间不足或无可使用的无人机!", }; } //再次确认货道状态 if (deviceCellInfo.Status != DeviceCellStatusEnum.充电.GetHashCode()) { return new { orderNo = "", cellNo = "", message = "该货道已被占用,请重试!", }; } //查询当前用户是否有进行中的订单 var OrderInfo = await _db.Queryable() .Where(x => x.UserAccount == _userManager.UserId && x.Status == UavOrderStatusEnum.已租接.GetHashCode()) .OrderBy(x => x.UpdateTime, OrderByType.Desc) .FirstAsync(); if (!OrderInfo.IsNullOrEmpty()) { return new { orderNo = "", cellNo = "", message = "一个ID限租一台无人机!", }; // throw new Exception("一个ID限租一台无人机!"); } // 更新货道状态为空闲,表示已被预定 deviceCellInfo.Status = DeviceCellStatusEnum.空闲.GetHashCode(); await _db.Updateable(deviceCellInfo).ExecuteCommandAsync(); // 创建订单 UavOrderEntity entity = new UavOrderEntity(); entity.Id = YitIdHelper.NextId().ToString(); entity.OrderNo = $"DD{DateTime.Now:yyyyMMddHHmmssfff}{new Random().Next(1000, 9999)}"; entity.UserAccount = _userManager.UserId; entity.UserName = _userManager.RealName; entity.UserPhone = _userManager.Account; entity.DeviceId = DeviceEntity.Id; entity.RentCellId = deviceCellInfo.Id; entity.SiteId = DeviceEntity.SiteId; entity.AgentId = DeviceEntity.BelongUserId; entity.AircraftId = deviceCellInfo.UavCode; entity.RFID1 = deviceCellInfo.Rfid1; entity.RFID2 = deviceCellInfo.Rfid2; entity.Status = UavOrderStatusEnum.待付款.GetHashCode(); entity.CreateTime = DateTime.Now; entity.UpdateTime = DateTime.Now; entity.FeeRuleId = input.feeRuleId; entity.FeeRuleSnapshot = _db.Queryable().First(x => x.Id == input.feeRuleId).ToJson(); var isOk = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteCommandAsync(); if (!(isOk > 0)) { return new { orderNo = "", cellNo = "", message = "订单创建失败", }; } return new { orderNo = entity.OrderNo, cellNo = deviceCellInfo.Id, message = "订单创建成功,请在三分钟内进行支付!", }; } catch (Exception ex) { throw new Exception(ex.Message); } }); if (RerutnEntity.IsSuccess) { return RerutnEntity.Data; } else { Log.Error($"创建订单失败:{RerutnEntity.ErrorMessage}"); return RerutnEntity.Data; // return throw new NCC.Exception("订单创建失败"); } } catch (Exception ex) { Log.Error($"创建订单失败:{ex.Message}"); return new { code = 500, message = ex.Message }; } } #endregion #region 更新订单管理 /// /// 更新订单管理 /// /// 主键 /// 参数 /// [HttpPut("{id}")] public async Task Update(string id, [FromBody] UavOrderUpInput input) { var entity = input.Adapt(); var isOk = await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync(); if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1001); } #endregion #region 删除订单管理 /// /// 删除订单管理 /// /// [HttpDelete("{id}")] public async Task Delete(string id) { var entity = await _db.Queryable().FirstAsync(p => p.Id == id); _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); var isOk = await _db.Deleteable().Where(d => d.Id == id).ExecuteCommandAsync(); if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1002); } #endregion #region 订单完结 /// /// 订单完结 /// /// /// [HttpPost("FinishOrderAsync")] public async Task FinishOrderAsync(UavEndOrderCrInput input) { var order = await _db.Queryable().FirstAsync(p => p.OrderNo == input.orderNo && p.Status == UavOrderStatusEnum.已租接.GetHashCode()); if (order == null) { throw NCCException.Oh("订单不存在或已经操作归还"); } //获取归还缓存数据 var OldcacheEntityInfo = await _db.Queryable().Where(x => x.OrderNo == order.OrderNo && x.Status != OrderDoorStatus.归还错误.GetHashCode()).FirstAsync(); if (OldcacheEntityInfo.IsNotEmptyOrNull()) { throw NCCException.Oh("请勿重复操作归还订单"); } //查询当前柜子是否有空的货道,可以随机一个空闲的货道 //通过设备编码获取设备信息 var DeviceInfo = _db.Queryable().First(p => p.DeviceCode == input.deviceCode); //判断订单里面的设备编码是否一致 if (order.DeviceId != DeviceInfo.Id) { throw NCCException.Oh("请在租借的机柜上进行归还"); } // var CellCodeInfo = await _db.Queryable().Where(x => x.DeviceId == DeviceInfo.Id && x.Status == DeviceCellStatusEnum.空闲.GetHashCode()).OrderBy(x => SqlFunc.GetRandom()).FirstAsync(); // if (CellCodeInfo.IsNullOrEmpty()) // { // throw NCCException.Oh("当前无空闲货道,请换其他机柜进行归还"); // } // await CloseOrderAndUnlockAsync(new UavDeviceOpenCrInput // { // id = order.DeviceId, // lane = CellCodeInfo.CellCode//传入货道编号,1、2、3、4、5、6 // }, order.OrderNo); var CellCodeInfo = await _db.Queryable().Where(x => x.Id == order.RentCellId).OrderBy(x => SqlFunc.GetRandom()).FirstAsync(); await CloseOrderAndUnlockAsync(new UavDeviceOpenCrInput { id = order.DeviceId, lane = CellCodeInfo.CellCode//传入货道编号,1、2、3、4、5、6 }, order.OrderNo); //变成归还中 await _db.Updateable(order).ExecuteCommandAsync(); //这里把订单缓存开门表的信息加上 UavOrderDoorCacheEntity cacheEntity = new UavOrderDoorCacheEntity { Id = YitIdHelper.NextId().ToString(), DeviceCode = input.deviceCode, Lane = CellCodeInfo.CellCode, OrderNo = order.OrderNo, Message = "订单归还开门", Status = OrderDoorStatus.已开门.GetHashCode(), }; var cacheEntityInfo = await _db.Queryable().Where(x => x.OrderNo == order.OrderNo).FirstAsync(); //通过订单查询数据是否存在 if (cacheEntityInfo.IsNotEmptyOrNull()) { cacheEntityInfo.Status = OrderDoorStatus.已开门.GetHashCode(); cacheEntityInfo.Message += "订单重新归还开门"; var isOk = await _db.Updateable(cacheEntityInfo).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync(); } else { await _db.Insertable(cacheEntity).ExecuteCommandAsync(); } return new { orderNo = order.OrderNo, lane = CellCodeInfo.CellCode }; } #endregion #region 获取支付宝扣款后的回调信息 /// /// 获取支付宝扣款后的回调信息 /// /// [HttpPost("orderNotify")] [AllowAnonymous, NonUnify] public async Task OrderNotify() { var request = _httpContextAccessor.HttpContext.Request; try { // 允许读取请求体多次 request.EnableBuffering(); // 读取原始 Body 字符串(可选,调试用) request.Body.Position = 0; using (var reader = new StreamReader(request.Body, Encoding.UTF8, leaveOpen: true)) { var rawBody = await reader.ReadToEndAsync(); Log.Information($"[支付宝扣款回调] 原始 Body:{rawBody}"); request.Body.Position = 0; // 复位 } // 读取 Form 表单参数 var form = await request.ReadFormAsync(); var paramDict = form.ToDictionary(k => k.Key, v => v.Value.ToString()); Log.Information($"[支付宝扣款回调] 表单参数:{JsonSerializer.Serialize(paramDict)}"); // ====== 可选:支付宝签名校验 ====== var isValid = AlipaySignature.RSACheckV1( paramDict, GetAlipayConfig().AlipayPublicKey, // 公钥 "UTF-8", "RSA2", false ); if (!isValid) { Log.Information("[支付宝扣款回调] 签名验证失败"); return "fail"; } // 处理业务逻辑 // 3. 验签通过,处理业务逻辑 string outTradeNo = paramDict["out_trade_no"]; // 商户订单号 string tradeNo = paramDict["trade_no"]; // 支付宝交易号 string tradeStatus = paramDict["trade_status"]; // 交易状态 string totalAmount = paramDict["total_amount"]; // 支付金额 string buyerId = paramDict["buyer_id"]; // 支付宝买家id string passbackParams = paramDict.GetValueOrDefault("passback_params"); // 自定义参数 if (tradeStatus == "TRADE_SUCCESS" || tradeStatus == "TRADE_FINISHED") { // 支付成功,更新数据库订单状态等 Log4jHelper.LogInfo($"支付宝支付成功:订单号={outTradeNo}, 支付宝交易号={tradeNo}"); var order = _db.Queryable().First(x => x.OrderNo == outTradeNo); if (order != null) { order.Status = UavOrderStatusEnum.已完成.GetHashCode(); //实际金额 order.ActualAmount = totalAmount.ToDecimal(); await _db.Updateable(order).ExecuteCommandAsync(); var agent = _db.Queryable().Where(it => it.AgentId == order.AgentId).First(); var agentLIst = _db.Queryable().ToParentList(it => it.ParentId, agent.Id); decimal AgentFinalAmount = 0; foreach (var item in agentLIst) { if (item.ParentId.IsNotEmptyOrNull()) { UavWalletFlowEntity uavWalletFlowEntity_Agent = new UavWalletFlowEntity { Id = YitIdHelper.NextId().ToString(), UserId = item.ParendAgentId, FlowDirection = FlowDirectionEnum.收入.GetHashCode(), Amount = order.ActualAmount * item.ProfitPercent, RelatedId = order.OrderNo, RelatedType = "分润", CreateTime = DateTime.Now, }; AgentFinalAmount += uavWalletFlowEntity_Agent.Amount; await _db.Insertable(uavWalletFlowEntity_Agent).ExecuteCommandAsync(); } } //计算终端最终收入多少钱 var terminalFinalAmount = order.ActualAmount - AgentFinalAmount; //查询终端人员分润设置 var terminalProfitSettingLIst = _db.Queryable().Where(x => x.AgentId == order.AgentId && x.EnabledMark == 1).ToList(); foreach (var item in terminalProfitSettingLIst) { //计算终端人员分润 var terminalProfitAmount = terminalFinalAmount * (item.ProfitRatio / 100); UavWalletFlowEntity uavWalletFlowEntity_Terminal = new UavWalletFlowEntity { Id = YitIdHelper.NextId().ToString(), UserId = item.ProfitUserId, FlowDirection = FlowDirectionEnum.收入.GetHashCode(), Amount = terminalProfitAmount, RelatedId = order.OrderNo, RelatedType = "分润", CreateTime = DateTime.Now, }; await _db.Insertable(uavWalletFlowEntity_Terminal).ExecuteCommandAsync(); } //计算代理商最终收入多少钱 var agentFinalAmount = terminalFinalAmount - AgentFinalAmount; UavWalletFlowEntity uavWalletFlowEntity = new UavWalletFlowEntity { Id = YitIdHelper.NextId().ToString(), UserId = order.AgentId, FlowDirection = FlowDirectionEnum.收入.GetHashCode(), Amount = agentFinalAmount, RelatedId = order.OrderNo, RelatedType = "订单", CreateTime = DateTime.Now, }; await _db.Insertable(uavWalletFlowEntity).ExecuteCommandAsync(); var OpenCacheInfo = _db.Queryable().First(x => x.OrderNo == order.OrderNo); if (OpenCacheInfo.IsNotEmptyOrNull()) { OpenCacheInfo.Status = OrderDoorStatus.已关门.GetHashCode(); OpenCacheInfo.Message += $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} :订单已完成,更新关门"; _db.Updateable(OpenCacheInfo).ExecuteCommand(); } } } return "success"; // 必须返回 success 才视为成功 } catch (Exception ex) { Log.Information(ex, "[支付宝扣款回调] 处理异常"); return "fail"; // 返回 fail 会触发支付宝重试 } } #endregion #region 获取支付宝预授权后的回调信息 /// /// 获取支付宝预授权后的回调信息 /// /// [HttpPost("OrderNotify_V2")] [AllowAnonymous, NonUnify] public async Task OrderNotify_V2() { var request = _httpContextAccessor.HttpContext.Request; try { // 允许读取请求体多次 request.EnableBuffering(); // 读取原始 Body 字符串(可选,调试用) request.Body.Position = 0; using (var reader = new StreamReader(request.Body, Encoding.UTF8, leaveOpen: true)) { var rawBody = await reader.ReadToEndAsync(); Log.Information($"[支付宝授权回调] 原始 Body:{rawBody}"); request.Body.Position = 0; // 复位 } // 读取 Form 表单参数 var form = await request.ReadFormAsync(); var paramDict = form.ToDictionary(k => k.Key, v => v.Value.ToString()); Log.Information($"[支付宝授权回调] 表单参数:{JsonSerializer.Serialize(paramDict)}"); // ====== 可选:支付宝签名校验 ====== var isValid = AlipaySignature.RSACheckV1( paramDict, GetAlipayConfig().AlipayPublicKey, // 公钥 "UTF-8", "RSA2", false ); if (!isValid) { Log.Information("[支付宝回调] 签名验证失败"); return "fail"; } // 处理业务逻辑 string outOrderNo = paramDict.GetValueOrDefault("out_order_no"); string authNo = paramDict.GetValueOrDefault("auth_no"); string status = paramDict.GetValueOrDefault("status"); if (status == "SUCCESS") { Log.Information($"[支付宝回调] 授权成功,订单号: {outOrderNo}, 授权号: {authNo}"); //根据订单编号处理订单并开门 await ProcessOrderAndOpenDoor(outOrderNo, authNo); } return "success"; // 必须返回 success 才视为成功 } catch (Exception ex) { Log.Information(ex, "[支付宝回调] 处理异常"); return "fail"; // 返回 fail 会触发支付宝重试 } } /// /// 根据订单编号处理订单并开门 /// /// 外部订单号 /// 支付宝授权号 /// [NonAction] public async Task ProcessOrderAndOpenDoor(string outOrderNo, string authNo) { var order = _db.Queryable().First(x => x.OrderNo == outOrderNo); if (order == null) { Log.Information($"未找到订单号为 {outOrderNo} 的订单"); return false; } if (order.Status == UavOrderStatusEnum.已租接.GetHashCode()) { Log.Information("订单已处理,请勿重复处理"); return false; } // 更新订单信息 order.Status = UavOrderStatusEnum.已租接.GetHashCode(); order.AliAuthNo = authNo; order.StartTime = DateTime.Now; await _db.Updateable(order).ExecuteCommandAsync(); var CellCodeInfo = _db.Queryable().First(x => x.Id == order.RentCellId); // CellCodeInfo.Status = DeviceCellStatusEnum.已租接.GetHashCode(); // await _db.Updateable(CellCodeInfo).ExecuteCommandAsync(); // 执行开门操作 await OrderCheckOpenDoor(new UavDeviceOpenCrInput { id = order.DeviceId, lane = CellCodeInfo.CellCode//传入货道编号,1、2、3、4、5、6 }, order.OrderNo); return true; } #endregion #region 获取支付宝取消预授权后的回调信息 /// /// 获取支付宝取消预授权后的回调信息 /// /// [HttpPost("OrderNotify_Cancel")] [AllowAnonymous, NonUnify] public async Task OrderNotify_Cancel() { var request = _httpContextAccessor.HttpContext.Request; try { // 允许读取请求体多次 request.EnableBuffering(); // 读取原始 Body 字符串(可选,调试用) request.Body.Position = 0; using (var reader = new StreamReader(request.Body, Encoding.UTF8, leaveOpen: true)) { var rawBody = await reader.ReadToEndAsync(); Log.Information($"[支付宝取消预授权回调] 原始 Body:{rawBody}"); request.Body.Position = 0; // 复位 } // 读取 Form 表单参数 var form = await request.ReadFormAsync(); var paramDict = form.ToDictionary(k => k.Key, v => v.Value.ToString()); Log.Information($"[支付宝取消预授权回调] 表单参数:{JsonSerializer.Serialize(paramDict)}"); // ====== 可选:支付宝签名校验 ====== var isValid = AlipaySignature.RSACheckV1( paramDict, GetAlipayConfig().AlipayPublicKey, // 公钥 "UTF-8", "RSA2", false ); if (!isValid) { Log.Information("[支付宝取消预授权回调] 签名验证失败"); return "fail"; } // 处理业务逻辑 string outOrderNo = paramDict.GetValueOrDefault("out_order_no"); string authNo = paramDict.GetValueOrDefault("auth_no"); string status = paramDict.GetValueOrDefault("action"); Log.Information($"[支付宝取消预授权回调] 授权成功,订单号: {outOrderNo}, 授权号: {authNo}"); if (status == "unfreeze") { var order = _db.Queryable().First(x => x.OrderNo == outOrderNo); if (order != null) { order.Status = UavOrderStatusEnum.已完成.GetHashCode(); order.EndTime = DateTime.Now; await _db.Updateable(order).ExecuteCommandAsync(); // var UavInfo = _db.Queryable().Where(x => x.Id == order.AircraftId).First(); // UavInfo.Status = UavAircraftStatusEnum.空闲.GetHashCode(); // _db.Updateable(UavInfo).ExecuteCommand();//无人机状态改为租赁中 var DeviceCellInfo = _db.Queryable().Where(x => x.Id == order.RentCellId).First();//找到租赁的货道 DeviceCellInfo.Status = DeviceCellStatusEnum.充电.GetHashCode();//更新货道为空闲,后续可以进行归还 _db.Updateable(DeviceCellInfo).ExecuteCommand(); } } return "success"; // 必须返回 success 才视为成功 } catch (Exception ex) { Log.Information(ex, "[支付宝取消预授权回调] 处理异常"); return "fail"; // 返回 fail 会触发支付宝重试 } } #endregion #region 处理消息订阅的信息 /// /// 处理消息订阅的信息 /// /// /// [NonAction] public async Task HandleSubscribeMessage(string message) { UavDeviceOprnNotifyCrInput info = message.ToObject(); if (!string.IsNullOrEmpty(info.rfid_1) && info.rfid_1.All(c => c == '0')) { info.rfid_1 = ""; } if (!string.IsNullOrEmpty(info.rfid_2) && info.rfid_2.All(c => c == '0')) { info.rfid_2 = ""; } //如果两个rfid都为空,无人机不为空,就给mqtt发送消息 // if (string.IsNullOrEmpty(info.rfid_1) && !string.IsNullOrEmpty(info.uavcode)) // { // } //通过设备id去查询设备信息 var entity = await _db.Queryable().Where(x => x.DeviceCode == info.id).FirstAsync(); info.id = entity.Id;//重写ID,这样就不用去修改下面的逻辑了 //通过机柜和货道去找到货道的id var CellCodeInfo = _db.Queryable().First(x => x.DeviceId == info.id && x.CellCode == info.lane); //通过货道去查询货道缓存信息 var OpenCacheInfo = _db.Queryable().First(x => x.DeviceCode == entity.DeviceCode && x.Lane == info.lane); //只有rfid并且没有无人机 if (info.rfid_1.IsNotEmptyOrNull() && info.uavcode.IsNullOrEmpty()) { var order = _db.Queryable().OrderBy(x => x.CreateTime).First(x => x.RentCellId == CellCodeInfo.Id && x.Status == UavOrderStatusEnum.已租接.GetHashCode()); if (order == null) { //如果没有通过货道去找到这个订单,就开门,认为是在补货 string topic = $"device/{entity.DeviceCode}/uav"; string MqttMessage = new MqttContent { className = "OrderErrorOpen", action = "open", lane = info.lane, orderNo = "", instructionTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), tokenUrl = "" }.ToJson(); await _mqttService.PublishAsync(topic, MqttMessage); } else { //查询出开门的缓存信息 if (OpenCacheInfo.IsNotEmptyOrNull()) { OpenCacheInfo.Status = OrderDoorStatus.归还错误.GetHashCode(); OpenCacheInfo.Message += $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} :没有检测到无人机,归还错误。"; _db.Updateable(OpenCacheInfo).ExecuteCommand(); } } return false; } // else if (info.rfid_1.IsNullOrEmpty() && info.uavcode.IsNotEmptyOrNull()) // { // //如果有rfid和无人机,那么就开门 // string topic = $"device/{entity.DeviceCode}/uav"; // string MqttMessage = new MqttContent // { // className = "OrderErrorOpen", // action = "open", // lane = info.lane, // orderNo = info.orderNo, // instructionTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), // tokenUrl = "" // }.ToJson(); // await _mqttService.PublishAsync(topic, MqttMessage); // } //首先判断是否有无人机RFID,没有无人机ID的情况,一般是关门,把无人机拿走,有可能是订单也有可能是维修 if (info.uavcode.IsNullOrEmpty()) { //如果是空,那么通过机柜和货道去找订单 var order = _db.Queryable().OrderBy(x => x.CreateTime).First(x => x.RentCellId == CellCodeInfo.Id && x.Status == UavOrderStatusEnum.已租接.GetHashCode()); if (order.IsNotEmptyOrNull()) { Log.Information("1,进入订单修改机柜的状态"); await OrderSuccessUpdateStatus(order); } //如果订单是空,那么就认定是维修,可能是无人机被管理者拿出去了 if (order.IsNullOrEmpty()) { await UavMaintain(CellCodeInfo); } } //有无人机RFID的情况,正常来说是归还或者是补货 if (info.uavcode.IsNotEmptyOrNull()) { Log.Information($"进入有无人机的订单处理"); //通过无人机code去找到订单 var order = _db.Queryable().OrderBy(x => x.CreateTime).First(x => x.AircraftId == info.uavcode && x.Status == UavOrderStatusEnum.已租接.GetHashCode()); if (order.IsNullOrEmpty()) { await Replenishment(info); } if (order.IsNotEmptyOrNull()) { //如果找到这个订单,那么就需要判断相关rfid和无人机是否一致 bool isSameRFIDPair = (order.RFID1 == info.rfid_1 && order.RFID2 == info.rfid_2) || (order.RFID1 == info.rfid_2 && order.RFID2 == info.rfid_1); if (!isSameRFIDPair || order.AircraftId != info.uavcode) { // 信息不一致,执行你的处理逻辑 order.Remark += $"{DateTime.Now}:订单设备不一致,无法归还,不一致为:{order.RFID1}和{order.RFID2},当前设备为{info.rfid_1}和{info.rfid_2},无人机为{info.uavcode}。"; _db.Updateable(order).ExecuteCommand(); if (OpenCacheInfo.IsNotEmptyOrNull()) { OpenCacheInfo.Status = OrderDoorStatus.归还错误.GetHashCode(); OpenCacheInfo.Message += $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} :订单设备不一致,无法归还,不一致为:{order.RFID1}和{order.RFID2},当前设备为{info.rfid_1}和{info.rfid_2},无人机为{info.uavcode}。"; _db.Updateable(OpenCacheInfo).ExecuteCommand(); } } else { //订单设备一致,就去关闭订单 await OrderReturnStatus(order, info); } } } // string topic = $"device/{entity.DeviceCode}/ack"; // string Returnmessage = new // { // ask = "success" // }.ToJson(); // await _mqttService.PublishAsync(topic, message); return true; } #endregion #region 无人机维护 /// /// 无人机维护 /// /// /// [NonAction] public async Task UavMaintain(UavDeviceCellEntity CellCodeInfo) { //var Aircraft = await _db.Queryable().Where(x => x.CellId == CellCodeInfo.Id).FirstAsync(); //首先把格子状态改为空闲 CellCodeInfo.Rfid1 = ""; CellCodeInfo.Rfid2 = ""; CellCodeInfo.UavCode = ""; CellCodeInfo.Status = DeviceCellStatusEnum.空闲.GetHashCode(); await _db.Updateable(CellCodeInfo).ExecuteCommandAsync(); //通过设备编号,货到编号,去查询订单开门缓存表,并且状态是已开门的 var OpenCacheInfo = _db.Queryable().First(x => x.DeviceCode == CellCodeInfo.DeviceId && x.Lane == CellCodeInfo.CellCode && x.Status == OrderDoorStatus.已开门.GetHashCode()); if (OpenCacheInfo.IsNotEmptyOrNull()) { //没有放无人机进去,修改状态是归还错误 OpenCacheInfo.Status = OrderDoorStatus.归还错误.GetHashCode(); OpenCacheInfo.Message += $"{DateTime.Now}:归还没有放无人机"; _db.Updateable(OpenCacheInfo).ExecuteCommand(); } //然后把格子绑定的无人机相关的绑定取消 // Aircraft.DeviceId = string.Empty; // Aircraft.CellId = string.Empty; // Aircraft.Status = UavAircraftStatusEnum.维修.GetHashCode(); // await _db.Updateable(Aircraft).ExecuteCommandAsync(); } #endregion #region 订单下单成功修改状态 /// /// OrderSuccessUpdateStatus /// /// /// [NonAction] public async Task OrderSuccessUpdateStatus(UavOrderEntity orderInfo) { // var UavInfo = _db.Queryable().Where(x => x.Id == orderInfo.AircraftId).First(); // UavInfo.Status = UavAircraftStatusEnum.租赁中.GetHashCode(); // UavInfo.DeviceId = "";//租赁中的无人机就不在柜子里面了 // UavInfo.CellId = "";//租赁中的无人机就不在柜子里面了 // await _db.Updateable(UavInfo).ExecuteCommandAsync();//无人机状态改为租赁中 var DeviceCellInfo = _db.Queryable().Where(x => x.Id == orderInfo.RentCellId).First();//找到租赁的货道 DeviceCellInfo.Rfid1 = ""; DeviceCellInfo.Rfid2 = ""; DeviceCellInfo.UavCode = ""; DeviceCellInfo.Status = DeviceCellStatusEnum.空闲.GetHashCode();//更新货道为空闲,后续可以进行归还 await _db.Updateable(DeviceCellInfo).ExecuteCommandAsync(); var OpenCacheInfo = _db.Queryable().First(x => x.OrderNo == orderInfo.OrderNo && x.Status == OrderDoorStatus.已开门.GetHashCode()); if (OpenCacheInfo.IsNotEmptyOrNull()) { //没有放无人机进去,修改状态是归还错误 OpenCacheInfo.Status = OrderDoorStatus.归还错误.GetHashCode(); OpenCacheInfo.Message += $"{DateTime.Now}:归还没有放无人机"; _db.Updateable(OpenCacheInfo).ExecuteCommand(); } } #endregion #region 订单归还无人机 /// /// 订单归还无人机 /// /// /// /// [NonAction] public async Task OrderReturnStatus(UavOrderEntity orderInfo, UavDeviceOprnNotifyCrInput Notifyinput) { Log.Information($"进入订单完结操作----1"); //根据设备货道去查询货道id,找到退租的设备货道信息 var DeviceCellInfo = _db.Queryable().Where(x => x.DeviceId == Notifyinput.id && x.CellCode == Notifyinput.lane).First(); Log.Information($"进入订单完结操作----2"); //根据设备去查询场所id,找到设备的场所信息 var DeviceInfo = _db.Queryable().Where(x => x.Id == Notifyinput.id).First(); Log.Information($"进入订单完结操作----3"); //如果关门成功,就进行订单完成的操作 orderInfo.ReturnDeviceId = Notifyinput.id;//设置退租设备id orderInfo.ReturnCellId = DeviceCellInfo.Id; orderInfo.ReturnSiteId = DeviceInfo.SiteId; orderInfo.EndTime = DateTime.Now; UavFeeRuleEntity uavFee = orderInfo.FeeRuleSnapshot.ToObject().Adapt();//获取快照中的费用规则 Log.Information($"进入订单完结操作----4-----{uavFee.ToJson()}"); var TimeSpan = orderInfo.EndTime.ToDate() - orderInfo.StartTime.ToDate(); var TimeSpanMinute = TimeSpan.TotalMilliseconds; var Price = CalculateFee(uavFee, TimeSpan.TotalSeconds.ToInt()); _db.Updateable(orderInfo).IgnoreColumns(it => new { it.Status }).ExecuteCommand(); var ActualAmount = decimal.Parse(Price.ToString()); DeviceCellInfo.Rfid1 = Notifyinput.rfid_1; DeviceCellInfo.Rfid2 = Notifyinput.rfid_2; DeviceCellInfo.UavCode = Notifyinput.uavcode; DeviceCellInfo.Status = DeviceCellStatusEnum.充电.GetHashCode();//更新货道为充电,来表示可以对外租赁 DeviceCellInfo.UpdateTime = DateTime.Now;//更新货道更新时间,更新时间来表示充电开始时间 _db.Updateable(DeviceCellInfo).ExecuteCommand(); //最后通知去芝麻信用去扣款 if (ActualAmount == 0) { //如果金额是0,则不需要去扣款,直接取消预授权 await _aliPayService.liPayOperationCance(new AliPayOperationCancelEntity { OutOrderNo = orderInfo.OrderNo, OutRequestNo = orderInfo.OrderNo, Remark = "免费时间内归还" }); var OpenCacheInfo = _db.Queryable().First(x => x.OrderNo == orderInfo.OrderNo); if (OpenCacheInfo.IsNotEmptyOrNull()) { OpenCacheInfo.Status = OrderDoorStatus.已关门.GetHashCode(); OpenCacheInfo.Message += $"{DateTime.Now}:免费订单完结关门"; _db.Updateable(OpenCacheInfo).ExecuteCommand(); } } else { await _aliPayService.TradePay(new AliPayTradePayEntity { OutTradeNo = orderInfo.OrderNo, TotalAmount = ActualAmount, Subject = "无人机租借" }); } } #endregion #region 费用计算 /// /// 计算费用方法:根据套餐规则和实际使用秒数,计算最终费用 /// /// 套餐规则实体 /// 使用时长(单位:秒) /// 应收费用(单位:元) [NonAction] public decimal CalculateFee(UavFeeRuleEntity info, int usedSeconds) { Log.Information($"进入订单完结操作----5(费用计算)"); // 如果使用时间小于等于0秒,费用为0 if (usedSeconds <= 0) return 0m; // 获取套餐类型(minute 或 hour),默认为 minute string packageType = info.PackageType?.ToLower() ?? "minute"; // 获取免费时长(单位:秒),如果未设置默认为0 int freeSeconds = info.FreeDuration ?? 0; // 如果实际使用时间未超过免费时长,则不计费 if (usedSeconds <= freeSeconds) return 0m; // 不需要扣除免费时长,免费时长已经包含在基础的套餐内 int chargeableSeconds = usedSeconds; // 最终按“分钟”为单位进行计费(包括小时套餐),转换为计费单位数 int chargeableUnits = (int)Math.Ceiling(chargeableSeconds / 60.0); // 计算套餐基础时长(单位:分钟) // 若是小时套餐,则 Duration * 60,转成分钟 int baseUnits = packageType switch { "minute" => info.Duration ?? 0, "hour" => info.Duration.HasValue ? info.Duration.Value * 60 : 0, _ => throw new InvalidOperationException("未知的套餐类型") }; if (baseUnits <= 0) throw new InvalidOperationException("套餐时长未配置"); // 获取价格设置 decimal unitPrice = info.UnitPrice; // 基础套餐费用 decimal overtimeUnitPrice = info.OvertimePrice; // 超时每单位费用(分钟/小时) decimal dailyCap = info.DailyCap; // 日封顶 decimal totalCap = info.TotalCap; // 总封顶 // 每天的分钟数为 1440(24小时) int minutesPerDay = 1440; // 初始化总费用 decimal totalFee = 0; // 计算所有使用时长的总分钟数(用于分天计费) int totalMinutes = chargeableUnits; // 计算完整的使用天数 int fullDays = totalMinutes / minutesPerDay; // 计算最后一天的剩余分钟数 int remainingMinutes = totalMinutes % minutesPerDay; //按天计算费用,考虑是否超时及超时计价方式 decimal CalcDailyFee(int minutesInDay) { if (minutesInDay <= baseUnits) return unitPrice; // 超过套餐时间部分 int overtime = minutesInDay - baseUnits; // 超时单位转换:按分钟或小时计费 int overtimeUnits = packageType switch { "minute" => overtime, "hour" => (int)Math.Ceiling(overtime / 60.0), _ => 0 }; return unitPrice + overtimeUnits * overtimeUnitPrice; } // 计算每一天的费用(如果有多天),每天不超过日封顶 for (int i = 0; i < fullDays; i++) { totalFee += Math.Min(CalcDailyFee(minutesPerDay), dailyCap); } // 计算最后一天的费用(不足一天的分钟数) if (remainingMinutes > 0) { totalFee += Math.Min(CalcDailyFee(remainingMinutes), dailyCap); } // 总费用不超过总封顶金额 if (totalCap > 0 && totalFee > totalCap) { totalFee = totalCap; } // 最终返回保留两位小数的金额 return Math.Round(totalFee, 2); } #endregion #region 补货 /// /// 补货 /// /// /// [NonAction] public async Task Replenishment(UavDeviceOprnNotifyCrInput input) { var result = await _db.Ado.UseTranAsync(async () => { var deviceInfo = await _db.Queryable().FirstAsync(x => x.Id == input.id); if (input.rfid_1 == "000000") { input.rfid_1 = ""; } if (input.rfid_2 == "000000") { input.rfid_2 = ""; } // 检测并清空重复的无人机信息 await ClearDuplicateUavInfo(input.uavcode, input.rfid_1, input.rfid_2, input.id, input.lane); //添加电池1 await HandleReplenishment(input.rfid_1, input, "电池", deviceInfo.BelongUserId); //添加电池2 await HandleReplenishment(input.rfid_2, input, "电池", deviceInfo.BelongUserId); //添加无人机 await HandleReplenishment(input.uavcode, input, "无人机", deviceInfo.BelongUserId); var cell = await _db.Queryable().FirstAsync(x => x.DeviceId == input.id && x.CellCode == input.lane); //更新货道信息 cell.Rfid1 = input.rfid_1; cell.Rfid2 = input.rfid_2; cell.UavCode = input.uavcode; cell.Status = DeviceCellStatusEnum.充电.GetHashCode(); cell.UpdateTime = DateTime.Now; await _db.Updateable(cell).ExecuteCommandAsync(); }); if (!result.IsSuccess) { Log.Error("补货失败:" + result.ErrorMessage); throw new Exception("补货失败:" + result.ErrorMessage); } } [NonAction] private async Task HandleReplenishment(string rfid, UavDeviceOprnNotifyCrInput input, string type, string BelongUserId) { if (string.IsNullOrWhiteSpace(rfid)) return; var uav = await _db.Queryable().FirstAsync(x => x.UavFrid == rfid); if (uav.IsNullOrEmpty() && rfid != "000000" && rfid != "00000000") { // 插入新无人机 var newUav = new UavAircraftEntity { Id = YitIdHelper.NextId().ToString(), DeviceId = input.id, Status = UavAircraftStatusEnum.空闲.GetHashCode(), CreateTime = DateTime.Now, UavFrid = rfid, Model = type, UavBatteryFrid = "", AgentId = BelongUserId, }; await _db.Insertable(newUav).ExecuteCommandAsync(); } } /// /// 检测并清空重复的无人机信息 /// /// 无人机编码 /// 电池1 RFID /// 电池2 RFID /// 当前设备ID /// 当前货道 /// [NonAction] private async Task ClearDuplicateUavInfo(string uavCode, string rfid1, string rfid2, string currentDeviceId, string currentLane) { try { // 主要检测无人机编码重复,如果重复则清空对应的RFID信息 if (!string.IsNullOrWhiteSpace(uavCode)) { var duplicateUavCells = await _db.Queryable().Where(x => x.UavCode == uavCode && !(x.DeviceId == currentDeviceId && x.CellCode == currentLane)).ToListAsync(); if (duplicateUavCells.Any()) { foreach (var cell in duplicateUavCells) { // 清空无人机编码和对应的RFID信息 cell.UavCode = ""; cell.Rfid1 = ""; cell.Rfid2 = ""; cell.UpdateTime = DateTime.Now; } await _db.Updateable(duplicateUavCells).ExecuteCommandAsync(); Log.Information($"清空了 {duplicateUavCells.Count} 个货道中重复的无人机编码及RFID信息: {uavCode}"); } } } catch (Exception ex) { Log.Error($"清空重复无人机信息失败: {ex.Message}"); throw new Exception($"清空重复无人机信息失败: {ex.Message}"); } } #endregion #region 订单下单开门 /// /// 订单下单开门 /// /// /// /// [NonAction] public async Task OrderCheckOpenDoor(UavDeviceOpenCrInput input, string orderNo) { var entity = await _db.Queryable().FirstAsync(p => p.Id == input.id); string topic = $"device/{entity.DeviceCode}/uav"; string message = new MqttContent { className = "OrderOpen", action = "open", lane = input.lane, orderNo = orderNo, instructionTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), tokenUrl = "" }.ToJson(); await _mqttService.PublishAsync(topic, message); return new { code = 200, msg = "开门指令已发送" }; } #endregion #region 订单完成开门 /// /// 订单下单开门 /// /// /// /// [NonAction] public async Task CloseOrderAndUnlockAsync(UavDeviceOpenCrInput input, string orderNo) { var entity = await _db.Queryable().FirstAsync(p => p.Id == input.id); string topic = $"device/{entity.DeviceCode}/uav"; string message = new MqttContent { className = "OrderEnd", action = "open", lane = input.lane, orderNo = orderNo, instructionTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), tokenUrl = "/api/Extend/UavOrder/orderCloseNotify" }.ToJson(); await _mqttService.PublishAsync(topic, message); return new { code = 200, msg = "开门指令已发送" }; } #endregion #region 获取用户订单金额的数据 /// /// 获取用户订单金额的数据 /// /// [HttpGet("GetUserOrderTotalAmount")] public async Task GetUserOrderTotalAmount() { var now = DateTime.Now; var today = now.Date; var yesterday = today.AddDays(-1); var monthStart = new DateTime(now.Year, now.Month, 1); var agentId = _userManager.UserId; var statusCompleted = UavOrderStatusEnum.已完成.GetHashCode(); var statusRenting = UavOrderStatusEnum.已租接.GetHashCode(); // 昨天 var yesterdayRevenue = await _db.Queryable().Where(x => x.Status == statusCompleted && x.AgentId == agentId && x.CreateTime >= yesterday).SumAsync(x => x.ShareAmount); var yesterdayCount = await _db.Queryable().Where(x => x.Status == statusCompleted && x.AgentId == agentId && x.CreateTime >= yesterday).CountAsync(); var yesterdayRentingCount = await _db.Queryable().Where(x => x.Status == statusRenting && x.AgentId == agentId && x.CreateTime >= yesterday).CountAsync(); // 今天 var todayRevenue = await _db.Queryable() .Where(x => x.Status == statusCompleted && x.AgentId == agentId && x.CreateTime >= today) .SumAsync(x => x.ShareAmount); var todayCount = await _db.Queryable() .Where(x => x.Status == statusCompleted && x.AgentId == agentId && x.CreateTime >= today) .CountAsync(); var todayRentingCount = await _db.Queryable() .Where(x => x.Status == statusRenting && x.AgentId == agentId && x.CreateTime >= today) .CountAsync(); // 本月 var thisMonthRevenue = await _db.Queryable() .Where(x => x.Status == statusCompleted && x.AgentId == agentId && x.CreateTime >= monthStart) .SumAsync(x => x.ShareAmount); var thisMonthCount = await _db.Queryable() .Where(x => x.Status == statusCompleted && x.AgentId == agentId && x.CreateTime >= monthStart) .CountAsync(); var thisMonthRentingCount = await _db.Queryable() .Where(x => x.Status == statusRenting && x.AgentId == agentId && x.CreateTime >= monthStart) .CountAsync(); // 构造结果 return new { yesterday = new { revenue = yesterdayRevenue, count = yesterdayCount, rentingCount = yesterdayRentingCount }, today = new { revenue = todayRevenue, count = todayCount, rentingCount = todayRentingCount }, thisMonth = new { revenue = thisMonthRevenue, count = thisMonthCount, rentingCount = thisMonthRentingCount } }; } #endregion #region C端用户获取我的订单 /// /// C端用户获取我的订单 /// /// [HttpGet("GetMyOrders")] public async Task GetMyOrders([FromQuery] UavOrderListQueryInput input) { var UserId = _userManager.UserId; var sidx = input.sidx == null ? "id" : input.sidx; var data = await _db.Queryable() .WhereIF(!string.IsNullOrEmpty(input.orderNo), p => p.OrderNo.Contains(input.orderNo)) .Where(p => p.UserAccount == UserId) .Select(it => new UavOrderListOutput { id = it.Id, orderNo = it.OrderNo, deviceId = it.DeviceId, deviceName = SqlFunc.Subqueryable().Where(p => p.Id == it.DeviceId).Select(p => p.DeviceName), siteId = it.SiteId, siteName = SqlFunc.Subqueryable().Where(p => p.Id == it.SiteId).Select(p => p.SiteName), aircraftId = it.AircraftId, userAccount = it.UserAccount, userPhone = it.UserPhone, actualAmount = it.ActualAmount, rentCellId = it.RentCellId, returnCellId = it.ReturnCellId, status = it.Status, remark = it.Remark, createTime = it.CreateTime, startTime = it.StartTime, endTime = it.EndTime, feeRuleId = it.FeeRuleId, aliAuthNo = it.AliAuthNo, returnDeviceId = it.ReturnDeviceId, returnSiteId = it.ReturnSiteId, rfid1 = it.RFID1, rfid2 = it.RFID2, }).MergeTable().Mapper(p => { p.statusString = global::System.Enum.GetName(typeof(UavOrderStatusEnum), p.status); }).OrderBy(sidx + " " + input.sort).ToPagedListAsync(input.currentPage, input.pageSize); return PageResult.SqlSugarPageResult(data); } #endregion #region 获取某个设备产生的订单 /// /// 获取某个设备产生的订单 /// /// /// [HttpGet("GetOrderByDevice")] public async Task GetOrderByDevice([FromQuery] UavOrderListQueryInput input) { var sidx = input.sidx == null ? "id" : input.sidx; var data = await _db.Queryable() .Where(p => p.AgentId == _userManager.UserId) .WhereIF(!string.IsNullOrEmpty(input.orderNo), p => p.OrderNo.Contains(input.orderNo)) .WhereIF(!string.IsNullOrEmpty(input.createTime_start), p => p.CreateTime >= new DateTime(input.createTime_start.ToDate().Year, input.createTime_start.ToDate().Month, input.createTime_start.ToDate().Day, 0, 0, 0) && p.CreateTime <= new DateTime(input.createTime_end.ToDate().Year, input.createTime_end.ToDate().Month, input.createTime_end.ToDate().Day, 23, 59, 59)) .Select(it => new UavOrderListOutput { id = it.Id, orderNo = it.OrderNo, deviceId = it.DeviceId, siteId = it.SiteId, aircraftId = it.AircraftId, agentId = it.AgentId, userAccount = it.UserAccount, userPhone = it.UserPhone, userName = it.UserName, discountAmount = it.DiscountAmount, actualAmount = it.ActualAmount, shareRatio = it.ShareRatio, shareAmount = it.ShareAmount, finalAmount = it.FinalAmount, rentCellId = it.RentCellId, returnCellId = it.ReturnCellId, status = it.Status, remark = it.Remark, endTime = it.EndTime, startTime = it.StartTime, createTime = it.CreateTime, }).MergeTable().Mapper(p => { p.statusString = global::System.Enum.GetName(typeof(UavOrderStatusEnum), p.status); }).OrderBy(sidx + " " + input.sort).ToPagedListAsync(input.currentPage, input.pageSize); return PageResult.SqlSugarPageResult(data); } #endregion #region 获取某个代理商的每天出货量 /// /// 获取某个代理商的每天出货量 /// /// [HttpGet("GetFlowByDay/{agentId}")] public async Task GetFlowByDay(string agentId) { var data = await _db.Queryable() .Where(p => p.AgentId == agentId && p.Status == UavOrderStatusEnum.已完成.GetHashCode() && p.CreateTime >= DateTime.Now.AddDays(-14) && p.CreateTime <= DateTime.Now ) .GroupBy(p => SqlFunc.DateValue(p.CreateTime.Value, SqlSugar.DateType.Day)) .Select(p => new { date = SqlFunc.DateValue(p.CreateTime.Value, SqlSugar.DateType.Day), count = SqlFunc.AggregateCount(p.Id) }) .ToListAsync(); return data; } #endregion #region 取消三分钟前未付款的订单,并且把订单的订单状态改为取消 /// /// 取消三分钟前未付款的订单 /// /// [HttpGet("CancelOrder")] [AllowAnonymous] public async Task CancelOrder() { try { // 获取3分钟前的时间 var timeLimit = DateTime.Now.AddMinutes(-3); // 使用事务处理 return await _db.UseTranAsync(async () => { // 查找需要取消的订单 var orders = await _db.Queryable() .Where(x => x.Status == UavOrderStatusEnum.待付款.GetHashCode() && x.CreateTime <= timeLimit) .ToListAsync(); foreach (var order in orders) { // 更新订单状态为已取消 order.Status = UavOrderStatusEnum.超时未付款.GetHashCode(); order.EndTime = DateTime.Now; order.Remark = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 系统自动取消超时未支付订单"; await _db.Updateable(order).ExecuteCommandAsync(); // 获取并更新货道状态 var deviceCell = await _db.Queryable().With(SqlSugar.SqlWith.RowLock).FirstAsync(x => x.Id == order.RentCellId); if (deviceCell != null) { // 恢复货道状态为充电状态,表示可以继续租借 //如果已经是充电状态,则不处理 if (deviceCell.Status != DeviceCellStatusEnum.充电.GetHashCode()) { deviceCell.Status = DeviceCellStatusEnum.充电.GetHashCode(); await _db.Updateable(deviceCell).ExecuteCommandAsync(); } } // 记录日志 Log.Information($"已自动取消超时订单:{order.OrderNo}"); } return new { code = 200, msg = $"成功取消{orders.Count}个超时订单", data = orders.Select(x => new { orderNo = x.OrderNo, createTime = x.CreateTime }).ToList() }; }); } catch (Exception ex) { Log.Error($"取消超时订单失败:{ex.Message}"); return new { code = 500, msg = "取消超时订单失败" }; } } #endregion #region 手动完结订单 /// /// 手动完结订单并扣费(货道状态和无人机状态不变,直接完结订单) /// /// 订单编号 /// [HttpPost("FinishOrder/{orderNo}")] public async Task FinishOrder(string orderNo) { try { return await _db.UseTranAsync(async () => { // 1. 查询订单信息 var order = await _db.Queryable().FirstAsync(x => x.OrderNo == orderNo && x.Status == UavOrderStatusEnum.已租接.GetHashCode()); if (order == null) { throw new Exception($"未找到进行中的订单:{orderNo}"); } // 2. 更新订单结束时间和状态 order.EndTime = DateTime.Now; order.Status = UavOrderStatusEnum.已完成.GetHashCode(); order.Remark = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 管理员手动完结订单"; // 3. 获取费用规则并计算费用 var uavFee = order.FeeRuleSnapshot.ToObject().Adapt(); var timeSpan = order.EndTime.Value - order.StartTime.Value; var actualAmount = CalculateFee(uavFee, timeSpan.TotalSeconds.ToInt()); // 4. 更新订单金额 order.ActualAmount = actualAmount; // 5. 更新设备和货道状态 // var deviceCell = await _db.Queryable() .FirstAsync(x => x.Id == order.RentCellId); // if (deviceCell != null) // { // deviceCell.Status = DeviceCellStatusEnum.充电.GetHashCode(); // await _db.Updateable(deviceCell).ExecuteCommandAsync(); // } // // 6. 更新无人机状态 // if (!string.IsNullOrEmpty(order.AircraftId)) // { // var aircraft = await _db.Queryable() // .FirstAsync(x => x.Id == order.AircraftId); // if (aircraft != null) // { // aircraft.Status = UavAircraftStatusEnum.空闲.GetHashCode(); // await _db.Updateable(aircraft).ExecuteCommandAsync(); // } // } // 7. 处理支付 if (actualAmount > 0) { await _aliPayService.TradePay(new AliPayTradePayEntity { OutTradeNo = order.OrderNo, TotalAmount = actualAmount, Subject = "无人机租借-手动完结" }); } else { // 金额为0,直接取消预授权 await _aliPayService.liPayOperationCance(new AliPayOperationCancelEntity { OutOrderNo = order.OrderNo, OutRequestNo = order.OrderNo, Remark = "管理员手动完结-免费订单" }); } // 8. 更新订单状态 await _db.Updateable(order).ExecuteCommandAsync(); return new { code = 200, msg = "订单手动完结成功", data = new { orderNo = order.OrderNo, amount = actualAmount, startTime = order.StartTime, endTime = order.EndTime, duration = timeSpan.TotalMinutes } }; }); } catch (Exception ex) { Log.Error($"手动完结订单失败:{ex.Message}"); return new { code = 500, msg = $"手动完结订单失败:{ex.Message}" }; } } #endregion } }