fix 多钱包调整

This commit is contained in:
2024-11-08 16:36:10 +08:00
parent 36bfd2e8f6
commit 1a0ba28249
36 changed files with 418 additions and 434 deletions

View File

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

View File

@@ -1,12 +1,8 @@
using Dlhis.Middle.Request;
using Dlhis.Middle.Response;
using Kurisu.AspNetCore.UnifyResultAndValidation;
namespace Dlhis.Middle.Services;
public interface IRemoteOrderService : IScopeDependency
{
Task<DefaultApiResult<OrderResultResponse>> AddAsync(OrderAddRequest request);
//Task<DefaultApiResult<OrderResultResponse>> AddAsync(OrderAddRequest request);
Task<bool> AbleRemoteCallAsync();
}

View File

@@ -0,0 +1,8 @@
namespace Dlhis.Middle.Services;
public interface ISettingService : ISingletonDependency
{
string[] GetFactories();
string GetBaseUrl();
}

View File

@@ -0,0 +1,6 @@
namespace Dlhis.Middle.Services;
public interface ITokenService : ISingletonDependency
{
Task<string> AuthAsync();
}

View File

@@ -1,169 +0,0 @@
using System.Net.Http.Headers;
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.Extensions.Caching.Memory;
using Newtonsoft.Json;
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();
var db = scope.ServiceProvider.GetService<IDbContext>();
var ds = scope.ServiceProvider.GetService<IDataDownloadService>();
foreach (var factory in _factory.Split(','))
{
try
{
var balanceRecords = await db.Queryable<LocalBalanceRecordEntity>().Where(x => x.TenantId == factory).Where(x => !x.IsSync).ToListAsync(stoppingToken);
if (balanceRecords.Any())
{
_logger.LogInformation("上传离线订单");
foreach (var balanceRecord in balanceRecords)
{
//事务
await db.Client.Ado.BeginTranAsync();
try
{
var r = await UploadAsync(balanceRecord);
if (r.Code != ApiStateCode.Success)
{
throw new Exception(r.Msg);
}
var d = r.Data;
//更新本地余额表
await db.Updateable<LocalGainfoMealEntity>()
.SetColumns(x => x.Balance == d.Balance)
.Where(x => x.UserCode == d.UserCode)
.ExecuteCommandAsync(stoppingToken);
//eg:一定要在此处更新为已同步
balanceRecord.IsSync = true;
await db.UpdateAsync(balanceRecord);
await db.Client.Ado.CommitTranAsync();
}
catch (Exception e)
{
_logger.LogError(e, "{factory}上传离线消费失败:{message}", factory, e.Message);
await db.Client.Ado.RollbackTranAsync();
}
}
balanceRecords = await db.Queryable<LocalBalanceRecordEntity>().Where(x => x.TenantId == factory).Where(x => !x.IsSync).ToListAsync(stoppingToken);
if (!balanceRecords.Any())
{
_logger.LogInformation("上传离线订单完成");
}
}
}
catch (Exception e)
{
_logger.LogError(e, "{factory}同步离线数据失败:{message}", factory, e.Message);
}
}
if (now is { Hour: 4, Minute: 1 }
&& !_healthCheckService.GetIsOffline()
&& !await db.Queryable<LocalBalanceRecordEntity>().Where(x => !x.IsSync).AnyAsync())
{
await ds.InitAsync(_factory);
}
else if (now is { Hour: 15, Minute: 1 }
&& !_healthCheckService.GetIsOffline()
&& !await db.Queryable<LocalBalanceRecordEntity>().Where(x => !x.IsSync).AnyAsync())
{
await ds.InitAsync(_factory);
}
else if (now is { Hour: 20, Minute: 1 }
&& !_healthCheckService.GetIsOffline()
&& !await db.Queryable<LocalBalanceRecordEntity>().Where(x => !x.IsSync).AnyAsync())
{
await ds.InitAsync(_factory);
}
}
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 = "e509c2aaa98e300c",
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

@@ -1,43 +1,32 @@
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using Dlhis.Middle.Apis;
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 DataDownloadService : IDataDownloadService
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IMemoryCache _memoryCache;
private readonly IDbContext _dbContext;
private readonly ICommonApi _commonApi;
private readonly ILogger<DataDownloadService> _logger;
private readonly string _hostName;
public DataDownloadService(IHttpClientFactory httpClientFactory, IMemoryCache memoryCache, IConfiguration configuration, IDbContext dbContext, ILogger<DataDownloadService> logger)
public DataDownloadService(
IDbContext dbContext,
ICommonApi commonApi,
ILogger<DataDownloadService> logger)
{
_httpClientFactory = httpClientFactory;
_memoryCache = memoryCache;
_dbContext = dbContext;
_commonApi = commonApi;
_logger = logger;
_hostName = configuration.GetSection("HostName").Value;
}
public async Task InitAsync(string factory)
public async Task InitAsync(string[] factories)
{
//需要上传完记录才可下载
if (await _dbContext.Queryable<LocalBalanceRecordEntity>().Where(x => x.IsSync == false).AnyAsync())
{
return;
}
var factories = factory.Split(',');
await _dbContext.Client.Ado.BeginTranAsync();
try
@@ -84,9 +73,10 @@ public class DataDownloadService : IDataDownloadService
_logger.LogInformation("档案{count}", await _dbContext.Queryable<LocalGainfoEntity>().CountAsync());
_logger.LogInformation("余额{count}", await _dbContext.Queryable<LocalGainfoMealEntity>().CountAsync());
_logger.LogInformation("下载完成");
await _dbContext.Client.Ado.CommitTranAsync();
}
catch (Exception e)
catch (Exception)
{
await _dbContext.Client.Ado.RollbackTranAsync();
throw;
@@ -95,27 +85,11 @@ public class DataDownloadService : IDataDownloadService
public async Task DownloadAsync<T>(string factory) where T : class, new()
{
var token = await AuthAsync();
var httpClient = _httpClientFactory.CreateClient("face");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var url = $"https://{_hostName}/api/common/SysData/download";
var requestUrl = QueryHelpers.AddQueryString(url, new Dictionary<string, string>
{
["factory"] = factory,
["tableName"] = typeof(T).GetCustomAttribute<SugarTable>()!.TableName
});
var response = await httpClient.GetStringAsync(requestUrl);
var pack = response.ToObject<DefaultApiResult<List<T>>>();
var pack = await _commonApi.DownloadAsync<T>(factory, typeof(T).GetCustomAttribute<SugarTable>()!.TableName);
await _dbContext.InsertAsync(pack.Data);
}
/// <inheritdoc />
public async Task TruncateAsync()
private async Task TruncateAsync()
{
await _dbContext.Client.Deleteable<LocalDepartmentEntity>().ExecuteCommandAsync();
await _dbContext.Client.Deleteable<LocalDeviceEntity>().ExecuteCommandAsync();
@@ -128,26 +102,4 @@ public class DataDownloadService : IDataDownloadService
await _dbContext.Client.Deleteable<LocalGainfoEntity>().ExecuteCommandAsync();
await _dbContext.Client.Deleteable<LocalGainfoMealEntity>().ExecuteCommandAsync();
}
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 = "e509c2aaa98e300c",
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

@@ -1,19 +0,0 @@
namespace Dlhis.Middle.Services.Implements;
public class HealthCheckBackgroundService : BackgroundService
{
private readonly IHealthCheckService _healthCheckService;
public HealthCheckBackgroundService(IHealthCheckService healthCheckService)
{
_healthCheckService = healthCheckService;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await _healthCheckService.DoHealthCheckAsync();
}
}
}

View File

@@ -1,19 +1,26 @@
using System.Net.Sockets;
using Dlhis.Middle.Apis;
using Dlhis.Middle.Request;
using Kurisu.AspNetCore.UnifyResultAndValidation;
namespace Dlhis.Middle.Services.Implements;
public class HealthCheckService : IHealthCheckService
{
private bool _isOffline;
private readonly string _hostName;
private readonly ILogger<HealthCheckService> _logger;
private int _successRetry;
private int _retry;
private readonly ISettingService _settingService;
private readonly ICommonApi _healthCheckApi;
private int _attempted;
public HealthCheckService(ILogger<HealthCheckService> logger, IConfiguration configuration)
private bool _isBegin;
public HealthCheckService(ILogger<HealthCheckService> logger,
ISettingService settingService,
ICommonApi healthCheckApi)
{
_logger = logger;
_hostName = configuration.GetSection("HostName").Value;
_settingService = settingService;
_healthCheckApi = healthCheckApi;
}
public bool GetIsOffline()
@@ -23,44 +30,44 @@ public class HealthCheckService : IHealthCheckService
public async Task DoHealthCheckAsync()
{
var cancellationTokenSource = new CancellationTokenSource(5000);
var tcpClient = new TcpClient();
try
{
_logger.LogDebug("tcp connecting {host} {port}", _hostName, 443);
await tcpClient.ConnectAsync(_hostName, 443, cancellationTokenSource.Token);
_retry++;
_logger.LogDebug("retry {retry}", _retry);
if (tcpClient.Connected)
var factories = _settingService.GetFactories();
var response = await _healthCheckApi.CheckHealthAsync(new HealthCheckRequest { Factories = factories.ToList() });
if (response.Code == ApiStateCode.Success)
{
_successRetry++;
_logger.LogDebug("success connected {count}", _successRetry);
if (_successRetry == 4)
_attempted++;
if (_attempted == 5)
{
_isOffline = false;
_successRetry = 0;
_retry = 0;
_logger.LogInformation("current is {status}", "healthy");
_attempted = 0;
}
else
if (!_isBegin)
{
_logger.LogDebug("wait next check in {waitTime}", _successRetry * 1000);
await Task.Delay(_successRetry * 1000, cancellationTokenSource.Token);
_isOffline = false;
_isBegin = true;
}
if (_isOffline == false)
{
_logger.LogInformation("Health");
}
await Task.Delay(_attempted * 1000);
}
else
{
_successRetry = 0;
_isOffline = true;
_logger.LogDebug("current is {status}", "unavailable");
await Task.Delay(500);
_logger.LogInformation("Error {message}", response.Msg);
}
}
catch (Exception ex)
{
_logger.LogError("fail cause :{reason}", ex.Message);
_logger.LogError("Failed cause :{reason}", ex.Message);
_isOffline = true;
await Task.Delay(5000);
}
}
}

View File

@@ -1,5 +1,6 @@
using System.Diagnostics;
using Dlhis.Entity.Canteen.Enums;
using Dlhis.Middle.Apis;
using Dlhis.Middle.Entities;
using Kurisu.AspNetCore.Utils;
using Kurisu.AspNetCore.Utils.Extensions;
@@ -17,6 +18,7 @@ public class OrderService : IOrderService
{
private readonly ICanteenSettingService _canteenSettingService;
private readonly IRemoteOrderService _remoteOrderService;
private readonly ICanteenApi _canteenApi;
private readonly ILogger<OrderService> _logger;
private readonly string _factory;
private readonly IDbContext _dbContext;
@@ -24,14 +26,16 @@ public class OrderService : IOrderService
public OrderService(ICanteenSettingService canteenSettingService,
IRemoteOrderService remoteOrderService,
IConfiguration configuration,
ISettingService settingService,
ICanteenApi canteenApi,
ILogger<OrderService> logger,
IDbContext dbContext)
{
_canteenSettingService = canteenSettingService;
_remoteOrderService = remoteOrderService;
_canteenApi = canteenApi;
_logger = logger;
_factory = configuration.GetSection("Factory").Value;
_factory = settingService.GetFactories().Join(",");
_dbContext = dbContext;
}
@@ -43,12 +47,12 @@ public class OrderService : IOrderService
{
if (await _remoteOrderService.AbleRemoteCallAsync())
{
var remoteResult = await _remoteOrderService.AddAsync(request);
var remoteResult = await _canteenApi.AddAsync(request);
if (remoteResult.Code == ApiStateCode.Success)
{
var d = remoteResult.Data;
await _dbContext.Updateable<LocalGainfoMealEntity>()
.SetColumns(x => x.Balance == d.Balance)
.SetColumns(x => x.Balance == d.TotalBalance)
.Where(x => x.UserCode == d.UserCode)
.ExecuteCommandAsync();
}

View File

@@ -1,54 +1,37 @@
using System.Net.Http.Headers;
using System.Text;
using Dlhis.Middle.Entities;
using Dlhis.Middle.Request;
using Dlhis.Middle.Response;
using Kurisu.AspNetCore.DataAccess.SqlSugar.Services;
using Kurisu.AspNetCore.UnifyResultAndValidation;
using Kurisu.AspNetCore.Utils.Extensions;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
namespace Dlhis.Middle.Services.Implements;
public class RemoteOrderService : IRemoteOrderService
{
private readonly string _hostName;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IMemoryCache _memoryCache;
private readonly IHealthCheckService _healthCheckService;
private readonly IDbContext _dbContext;
public RemoteOrderService(IHttpClientFactory httpClientFactory,
IMemoryCache memoryCache,
IConfiguration configuration,
public RemoteOrderService(
IHealthCheckService healthCheckService,
IDbContext dbContext)
{
_httpClientFactory = httpClientFactory;
_memoryCache = memoryCache;
_hostName = configuration.GetSection("hostName").Value;
_healthCheckService = healthCheckService;
_dbContext = dbContext;
}
/// <inheritdoc />
public async Task<DefaultApiResult<OrderResultResponse>> AddAsync(OrderAddRequest request)
{
var token = await AuthAsync();
var httpClient = _httpClientFactory.CreateClient("face");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var content = new StringContent(request.ToJson(), Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync($"https://{_hostName}/api/canteen/orders", content);
var responseJson = await response.Content.ReadAsStringAsync();
return responseJson.ToObject<DefaultApiResult<OrderResultResponse>>();
}
///// <inheritdoc />
// public async Task<DefaultApiResult<OrderResultResponse>> AddAsync(OrderAddRequest request)
// {
// var token = await _tokenService.AuthAsync();
//
// var httpClient = _httpClientFactory.CreateClient("face");
// httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
//
// var content = new StringContent(request.ToJson(), Encoding.UTF8, "application/json");
//
// var response = await httpClient.PostAsync($"{_settingService.GetBaseUrl()}/api/canteen/orders", content);
//
// var responseJson = await response.Content.ReadAsStringAsync();
// return responseJson.ToObject<DefaultApiResult<OrderResultResponse>>();
// }
/// <inheritdoc />
public async Task<bool> AbleRemoteCallAsync()
@@ -60,28 +43,4 @@ public class RemoteOrderService : IRemoteOrderService
return !_healthCheckService.GetIsOffline();
}
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 = "e509c2aaa98e300c",
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

@@ -0,0 +1,23 @@
namespace Dlhis.Middle.Services.Implements;
public class SettingService : ISettingService
{
private readonly IConfiguration _configuration;
public SettingService(IConfiguration configuration)
{
_configuration = configuration;
}
/// <inheritdoc />
public string[] GetFactories()
{
return _configuration.GetSection("Factory").Value.Split(",");
}
/// <inheritdoc />
public string GetBaseUrl()
{
return _configuration.GetSection("BaseUrl").Value;
}
}

View File

@@ -0,0 +1,43 @@
using Dlhis.Middle.Apis;
using Microsoft.Extensions.Caching.Memory;
namespace Dlhis.Middle.Services.Implements;
public class TokenService : ITokenService
{
private readonly IMemoryCache _memoryCache;
private readonly ICommonApi _commonApi;
public TokenService(IMemoryCache memoryCache, ICommonApi commonApi)
{
_memoryCache = memoryCache;
_commonApi = commonApi;
}
/// <inheritdoc />
public async Task<string> AuthAsync()
{
var exists = _memoryCache.TryGetValue("token", out string token);
try
{
if (!exists)
{
var tokenInfo = await _commonApi.AuthAsync(new
{
ClientId = "e509c2aaa98e300c",
ClientSecret = "6184d8979fc48105"
});
token = tokenInfo.Data.AccessToken;
_memoryCache.Set("token", token, TimeSpan.FromSeconds(tokenInfo.Data.Expires));
}
}
finally
{
token = "Bearer " + token;
}
return token;
}
}