fix 优化本地离线和在线消费处理

This commit is contained in:
2024-06-27 10:32:23 +08:00
parent a405af0435
commit cec075bae9
10 changed files with 282 additions and 39 deletions

View File

@@ -0,0 +1,36 @@
using System.Text;
using Dlhis.Middle.Request;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
namespace Dlhis.Middle.Controllers;
/// <summary>
/// OAuth2认证
/// </summary>
[Route("api/user/[controller]")]
[ApiController]
public class OAuth2Controller : ControllerBase
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IConfiguration _configuration;
public OAuth2Controller(IHttpClientFactory httpClientFactory, IConfiguration configuration)
{
_httpClientFactory = httpClientFactory;
_configuration = configuration;
}
[HttpPost("clientgrant")]
public async Task<object> ClientGrant(ClientGrantRequest request)
{
var authority = _configuration.GetSection("Authority").Value;
var httpClient = _httpClientFactory.CreateClient();
var content = new StringContent($"grant_type=client_credentials&client_id={request.ClientId}&client_secret={request.ClientSecret}&scope={request.Scope}", Encoding.UTF8, "application/x-www-form-urlencoded");
var response = await httpClient.PostAsync($"{authority}/connect/token", content);
var result = await response.Content.ReadAsStringAsync();
return JObject.Parse(result);
}
}

View File

@@ -12,7 +12,7 @@ namespace Dlhis.Middle.Controllers;
/// 订单
/// </summary>
[Authorize]
[Route("api/[controller]")]
[Route("api/canteen/[controller]")]
[ApiController]
public class OrdersController : ControllerBase
{

View File

@@ -1,9 +1,10 @@
using Dlhis.Entity.Canteen.Entities;
namespace Dlhis.Middle.Entities
namespace Dlhis.Middle.Entities;
public class LocalBalanceRecordEntity : BalanceRecordEntity
{
public class LocalBalanceRecordEntity : BalanceRecordEntity
{
public new int Id { get; set; }
}
public bool IsSync { get; set; }
}

View File

@@ -1,6 +1,3 @@
using Dlhis.Entity.Basic.Entity;
using Dlhis.Entity.Device.Entities;
using Dlhis.Middle.Entities;
using Dlhis.Middle.Services;
using Dlhis.Middle.Services.Implements;
using Kurisu.AspNetCore.Startup;
@@ -49,16 +46,6 @@ public class Startup : DefaultStartup
var factory = Configuration.GetSection("Factory").Value;
var ds = scope.ServiceProvider.GetService<IDataDownloadService>();
ds.TruncateAsync().Wait();
ds.DownloadAsync<LocalDepartmentEntity>(factory).Wait();
ds.DownloadAsync<LocalDeviceEntity>(factory).Wait();
ds.DownloadAsync<LocalDeviceRegistryEntity>(factory).Wait();
ds.DownloadAsync<LocalBusinessPlaceEntity>(factory).Wait();
ds.DownloadAsync<LocalBusinessShiftEntity>(factory).Wait();
ds.DownloadAsync<LocalBusinessPlaceShiftRelationshipEntity>(factory).Wait();
ds.DownloadAsync<LocalBusinessShiftMealRelationshipEntity>(factory).Wait();
ds.DownloadAsync<LocalGainfoEntity>(factory).Wait();
ds.DownloadAsync<LocalGainfoMealEntity>(factory).Wait();
ds.InitAsync(factory).Wait();
}
}

View File

@@ -0,0 +1,10 @@
namespace Dlhis.Middle.Request;
public class ClientGrantRequest
{
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public string Scope { get; set; } = "yikatong";
}

View File

@@ -2,7 +2,11 @@ namespace Dlhis.Middle.Services;
public interface IDataDownloadService : IScopeDependency
{
Task InitAsync(string factory);
Task DownloadAsync<T>(string factory) where T : class, new();
Task TruncateAsync();
Task DownloadGainfosAsync(string factory);
}

View File

@@ -0,0 +1,151 @@
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using Dlhis.Middle.Entities;
using Dlhis.Middle.Response;
using Kurisu.AspNetCore.DataAccess.SqlSugar.Services;
using Kurisu.AspNetCore.UnifyResultAndValidation;
using Kurisu.AspNetCore.Utils.Extensions;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using SqlSugar;
namespace Dlhis.Middle.Services.Implements;
public class BalanceRecordCheckBackgroundService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<BalanceRecordCheckBackgroundService> _logger;
private readonly IMemoryCache _memoryCache;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IHealthCheckService _healthCheckService;
private readonly string _factory;
private readonly string _hostName;
public BalanceRecordCheckBackgroundService(IServiceProvider serviceProvider,
ILogger<BalanceRecordCheckBackgroundService> logger,
IMemoryCache memoryCache,
IHttpClientFactory httpClientFactory,
IHealthCheckService healthCheckService,
IConfiguration configuration)
{
_serviceProvider = serviceProvider;
_logger = logger;
_memoryCache = memoryCache;
_httpClientFactory = httpClientFactory;
_healthCheckService = healthCheckService;
_factory = configuration.GetSection("Factory").Value;
_hostName = configuration.GetSection("HostName").Value;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
if (!_healthCheckService.GetIsOffline())
{
var now = DateTime.Now;
using (var scope = _serviceProvider.CreateScope())
{
try
{
var db = scope.ServiceProvider.GetService<IDbContext>();
var ds = scope.ServiceProvider.GetService<IDataDownloadService>();
var balanceRecords = await db.Queryable<LocalBalanceRecordEntity>().Where(x => !x.IsSync).ToListAsync(stoppingToken);
if (balanceRecords.Any())
{
foreach (var balanceRecord in balanceRecords)
{
await db.Client.Ado.BeginTranAsync();
try
{
balanceRecord.IsSync = true;
await db.UpdateAsync(balanceRecord);
var r = await UploadAsync(balanceRecord);
if (r.Code == ApiStateCode.Success)
{
var d = r.Data;
var balance = decimal.Parse(d.Balance);
await db.Updateable<LocalGainfoMealEntity>()
.SetColumns(x => x.Balance == balance)
.Where(x => x.UserCode == d.UserCode)
.ExecuteCommandAsync(stoppingToken);
await db.Client.Ado.CommitTranAsync();
}
}
catch (Exception e)
{
_logger.LogError(e, "{factory}上传离线消费失败:{message}", _factory, e.Message);
await db.Client.Ado.RollbackTranAsync();
}
}
}
if (now.Hour == 4 && now.Minute == 1 && !_healthCheckService.GetIsOffline() && !await db.Queryable<LocalBalanceRecordEntity>().Where(x => !x.IsSync).AnyAsync())
{
await ds.DownloadGainfosAsync(_factory);
}
else if (now.Hour == 15 && now.Minute == 1 && !_healthCheckService.GetIsOffline() && !await db.Queryable<LocalBalanceRecordEntity>().Where(x => !x.IsSync).AnyAsync())
{
await ds.DownloadGainfosAsync(_factory);
}
else if (now.Hour == 20 && now.Minute == 1 && !_healthCheckService.GetIsOffline() && !await db.Queryable<LocalBalanceRecordEntity>().Where(x => !x.IsSync).AnyAsync())
{
await ds.DownloadGainfosAsync(_factory);
}
}
catch (Exception e)
{
_logger.LogError(e, "{factory}同步离线数据失败:{message}", _factory, e.Message);
}
}
}
await Task.Delay(60 * 1000, stoppingToken);
}
}
public async Task<DefaultApiResult<OrderResultResponse>> UploadAsync(LocalBalanceRecordEntity entity)
{
var token = await AuthAsync();
var httpClient = _httpClientFactory.CreateClient("face");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var url = $"https://{_hostName}/api/canteen/middle/upload";
var json = entity.ToJson();
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(url, content);
var responseJson = await response.Content.ReadAsStringAsync();
return responseJson.ToObject<DefaultApiResult<OrderResultResponse>>();
}
private async Task<string> AuthAsync()
{
var httpClient = _httpClientFactory.CreateClient("face");
if (_memoryCache.TryGetValue("token", out string token)) return token;
var tokenResponse = await httpClient.PostAsync($"https://{_hostName}/api/User/OAuth2/clientgrant", new StringContent(JsonConvert.SerializeObject(new
{
ClientId = "e569c2aaa98e300c",
ClientSecret = "6184d8979fc48105"
}), Encoding.UTF8, "application/json"));
var tokenJson = await tokenResponse.Content.ReadAsStringAsync();
var tokenInfo = JsonConvert.DeserializeObject<DefaultApiResult<TokenResponse>>(tokenJson);
token = tokenInfo.Data.AccessToken;
_memoryCache.Set("token", token, TimeSpan.FromSeconds(tokenInfo.Data.Expires));
return token;
}
}

View File

@@ -28,6 +28,31 @@ public class DataDownloadService : IDataDownloadService
_hostName = configuration.GetSection("HostName").Value;
}
public async Task InitAsync(string factory)
{
await TruncateAsync();
await DownloadAsync<LocalDepartmentEntity>(factory);
await DownloadAsync<LocalDeviceEntity>(factory);
await DownloadAsync<LocalDeviceRegistryEntity>(factory);
await DownloadAsync<LocalBusinessPlaceEntity>(factory);
await DownloadAsync<LocalBusinessShiftEntity>(factory);
await DownloadAsync<LocalBusinessPlaceShiftRelationshipEntity>(factory);
await DownloadAsync<LocalBusinessShiftMealRelationshipEntity>(factory);
if (!await _dbContext.Queryable<LocalGainfoEntity>().AnyAsync())
{
await DownloadAsync<LocalGainfoEntity>(factory);
}
if (!await _dbContext.Queryable<LocalGainfoMealEntity>().AnyAsync())
{
await DownloadAsync<LocalGainfoMealEntity>(factory);
}
}
public async Task DownloadAsync<T>(string factory) where T : class, new()
{
var token = await AuthAsync();
@@ -37,7 +62,7 @@ public class DataDownloadService : IDataDownloadService
var url = $"https://{_hostName}/api/common/SysData/download";
#if DEBUG
url = $"http://localhost:5001/api/SysData/download";
url = "http://localhost:5001/api/SysData/download";
#endif
var requestUrl = QueryHelpers.AddQueryString(url, new Dictionary<string, string>
@@ -62,8 +87,29 @@ public class DataDownloadService : IDataDownloadService
await _dbContext.Client.Deleteable<LocalBusinessShiftEntity>().ExecuteCommandAsync();
await _dbContext.Client.Deleteable<LocalBusinessPlaceShiftRelationshipEntity>().ExecuteCommandAsync();
await _dbContext.Client.Deleteable<LocalBusinessShiftMealRelationshipEntity>().ExecuteCommandAsync();
//await _dbContext.Client.Deleteable<LocalGainfoEntity>().ExecuteCommandAsync();
//await _dbContext.Client.Deleteable<LocalGainfoMealEntity>().ExecuteCommandAsync();
}
/// <inheritdoc />
public async Task DownloadGainfosAsync(string factory)
{
await _dbContext.Client.Ado.BeginTranAsync();
try
{
await _dbContext.Client.Deleteable<LocalGainfoEntity>().ExecuteCommandAsync();
await _dbContext.Client.Deleteable<LocalGainfoMealEntity>().ExecuteCommandAsync();
await DownloadAsync<LocalGainfoEntity>(factory);
await DownloadAsync<LocalGainfoMealEntity>(factory);
await _dbContext.Client.Ado.CommitTranAsync();
}
catch (Exception)
{
await _dbContext.Client.Ado.RollbackTranAsync();
}
}
private async Task<string> AuthAsync()

View File

@@ -2,6 +2,7 @@
using Dlhis.Entity.Canteen.Entities;
using Dlhis.Entity.Canteen.Enums;
using Dlhis.Entity.Device.Entities;
using Dlhis.Middle.Entities;
using Kurisu.AspNetCore.Utils;
using Kurisu.AspNetCore.Utils.Extensions;
using Mapster;
@@ -37,14 +38,20 @@ public class OrderService : IOrderService
{
if (await _remoteOrderService.AbleRemoteCallAsync())
{
return await _remoteOrderService.AddAsync(request);
var remoteResult = await _remoteOrderService.AddAsync(request);
var d = remoteResult.Data;
var balance = decimal.Parse(d.Balance);
await _dbContext.Updateable<LocalGainfoMealEntity>()
.SetColumns(x => x.Balance == balance)
.Where(x => x.UserCode == d.UserCode)
.ExecuteCommandAsync();
}
//尝试匹配userId
_ = long.TryParse(request.UserUniqueNo, out var userId);
//根据档案号
var gainfo = await _dbContext.Queryable<GainfoEntity>()
var gainfo = await _dbContext.Queryable<LocalGainfoEntity>()
.Where(x => x.Code == request.UserUniqueNo || x.Id == userId || x.CardNo == request.UserUniqueNo)
.FirstAsync();
@@ -54,7 +61,7 @@ public class OrderService : IOrderService
try
{
(await _dbContext.Queryable<GainfoMealEntity>().Where(x => x.UserNo == gainfo.No).AnyAsync())
(await _dbContext.Queryable<LocalGainfoMealEntity>().Where(x => x.UserNo == gainfo.No).AnyAsync())
.ThrowIfFalse("余额不足,无法消费");
await ConsumeAsync(request.Adapt<ConsumeRequest>(), gainfo);
@@ -95,17 +102,17 @@ public class OrderService : IOrderService
/// <param name="request"></param>
/// <param name="gainfo"></param>
/// <returns></returns>
private async Task ConsumeAsync(ConsumeRequest request, GainfoEntity gainfo)
private async Task ConsumeAsync(ConsumeRequest request, LocalGainfoEntity gainfo)
{
var registry = await _dbContext.Queryable<DeviceRegistryEntity>().Where(x => x.DeviceUniqueNo == request.DeviceUniqueNo).SingleAsync();
var registry = await _dbContext.Queryable<LocalDeviceRegistryEntity>().Where(x => x.DeviceUniqueNo == request.DeviceUniqueNo).SingleAsync();
registry.ThrowIfNull("设备尚未登记");
var device = await _dbContext.Queryable<DeviceEntity>().Where(x => x.UniqueNo == request.DeviceUniqueNo).SingleAsync();
var device = await _dbContext.Queryable<LocalDeviceEntity>().Where(x => x.UniqueNo == request.DeviceUniqueNo).SingleAsync();
device.ThrowIfNull("设备尚未登记");
device.Enable.ThrowIfFalse("设备已被停用");
var businessPlaceInfo = await _dbContext.Queryable<BusinessPlaceEntity>()
.LeftJoin<BusinessPlaceShiftRelationshipEntity>((x, sr) => x.Code == sr.PlaceCode)
var businessPlaceInfo = await _dbContext.Queryable<LocalBusinessPlaceEntity>()
.LeftJoin<LocalBusinessPlaceShiftRelationshipEntity>((x, sr) => x.Code == sr.PlaceCode)
.Where(x => x.Code == registry.BusinessPlaceCode)
.Select((x, sr) => new
{
@@ -117,7 +124,7 @@ public class OrderService : IOrderService
businessPlaceInfo.ThrowIfNull("设备尚未登记营业点");
//获取当前消费餐次
var shifts = await _dbContext.Queryable<BusinessShiftMealRelationshipEntity>().Where(x => x.ShiftCode == businessPlaceInfo.ShiftCode).ToListAsync();
var shifts = await _dbContext.Queryable<LocalBusinessShiftMealRelationshipEntity>().Where(x => x.ShiftCode == businessPlaceInfo.ShiftCode).ToListAsync();
shifts.ThrowIfEmpty("营业点餐次尚未设置");
var shift = shifts.FirstOrDefault(x => new TimePeriod
@@ -129,7 +136,7 @@ public class OrderService : IOrderService
var costDate = await GetCostDateAsync(shift, request.CostDateTime);
var balanceRecord = new BalanceRecordEntity
var balanceRecord = new LocalBalanceRecordEntity
{
// RequestJson = request.ToJson(),
// ThirdNo = request.ThirdNo,
@@ -178,13 +185,13 @@ public class OrderService : IOrderService
{
if (!allowNoBalance)
{
var gainfoMeal = await _dbContext.Queryable<GainfoMealEntity>().Where(x => x.UserNo == balanceRecord.UserNo).SingleAsync();
var gainfoMeal = await _dbContext.Queryable<LocalGainfoMealEntity>().Where(x => x.UserNo == balanceRecord.UserNo).SingleAsync();
//金额+-
gainfoMeal.Balance += balanceRecord.Amount;
(gainfoMeal.Balance < 0).ThrowIfTrue("余额不足,无法消费");
await _dbContext.Updateable<GainfoMealEntity>()
await _dbContext.Updateable<LocalGainfoMealEntity>()
.SetColumns(x => x.Balance == gainfoMeal.Balance)
.SetColumns(x => x.Version == x.Version + 1)
.SetColumns(x => x.ModifiedTime == DateTime.Now)
@@ -230,13 +237,13 @@ public class OrderService : IOrderService
/// <param name="order"></param>
/// <returns></returns>
/// <exception cref="UserFriendlyException"></exception>
private async Task Verify(BalanceRecordEntity order)
private async Task Verify(LocalBalanceRecordEntity order)
{
var beginOfDay = DateTime.Today;
var endOfDay = beginOfDay.AddDays(1).AddSeconds(-1);
//今天所有消费记录
var todayAllBalanceRecords = await _dbContext.Queryable<BalanceRecordEntity>()
var todayAllBalanceRecords = await _dbContext.Queryable<LocalBalanceRecordEntity>()
.Where(x => x.UserNo == order.UserNo)
.Where(x => x.Type == BalanceRecordTypeEnumType.Out)
.Where(x => x.Status == BalanceRecordStatusEnumType.Valid)
@@ -244,7 +251,7 @@ public class OrderService : IOrderService
.ToListAsync();
//超市营业点
var supermarketCodes = await _dbContext.Queryable<BusinessPlaceEntity>()
var supermarketCodes = await _dbContext.Queryable<LocalBusinessPlaceEntity>()
.Where(x => x.Catalog == CanteenBusinessPlaceCatalogEnumType.Supermarket)
.Select(x => x.Code)
.ToListAsync();

View File

@@ -12,8 +12,9 @@
"SlowSqlTime": 1,
"EnableSqlLog": false
},
"Authority": "https://face.jinkosolar.com",
"Factory": "",
"HostName": "",
"HostName": "face.jinkosolar.com",
"AllowedHosts": "*",
"Serilog": {
"MinimumLevel": {