From 1a0ba2824919ef1d5e1c5780f9156c61ba7d0cac Mon Sep 17 00:00:00 2001 From: liguoyang Date: Fri, 8 Nov 2024 16:36:10 +0800 Subject: [PATCH] =?UTF-8?q?fix=20=E5=A4=9A=E9=92=B1=E5=8C=85=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Order/Controllers/OrdersController.cs | 9 +- .../Implements/BalanceRepository.cs | 17 +- .../Order/Response/OrderResultResponse.cs | 11 +- .../Order/Services/IOrderService.cs | 5 +- .../Order/Services/Implements/OrderService.cs | 155 +++++++++--------- .../Services/ICanteenSettingService.cs | 8 +- .../Api/FaceBearerTokenHandler.cs | 37 ----- .../Apis/Handlers/ClientHandler.cs | 23 +++ .../Apis/Handlers/FaceBearerTokenHandler.cs | 14 ++ .../{Api => Apis}/IAssignmentDeviceTaskApi.cs | 6 +- Src/Dlhis.Middle/{Api => Apis}/ICanteenApi.cs | 19 ++- Src/Dlhis.Middle/Apis/ICommonApi.cs | 22 +++ .../BalanceRecordUploadBackgroundService.cs} | 91 +++------- .../HealthCheckBackgroundService.cs | 4 +- .../Controllers/AssignmentsController.cs | 2 +- .../Controllers/BalancesController.cs | 2 +- .../Controllers/GainfosController.cs | 17 +- .../Controllers/OAuth2Controller.cs | 25 +-- Src/Dlhis.Middle/Dlhis.Middle.csproj | 15 +- .../Entities/LocalGainfoMealEntity.cs | 21 +-- Src/Dlhis.Middle/Program.cs | 22 +-- .../Request/HealthCheckRequest.cs | 6 + .../Response/OrderResultResponse.cs | 3 + .../Services/IDataDownloadService.cs | 4 +- .../Services/IRemoteOrderService.cs | 6 +- Src/Dlhis.Middle/Services/ISettingService.cs | 8 + Src/Dlhis.Middle/Services/ITokenService.cs | 6 + .../Implements/DataDownloadService.cs | 74 ++------- .../Services/Implements/HealthcheckService.cs | 61 ++++--- .../Services/Implements/OrderService.cs | 12 +- .../Services/Implements/RemoteOrderService.cs | 73 ++------- .../Services/Implements/SettingService.cs | 23 +++ .../Services/Implements/TokenService.cs | 43 +++++ Src/Dlhis.Middle/appsettings.Development.json | 5 +- Src/Dlhis.Middle/appsettings.json | 3 - Src/Dlhis.Middle/local.db-journal | Bin 0 -> 127736 bytes 36 files changed, 418 insertions(+), 434 deletions(-) delete mode 100644 Src/Dlhis.Middle/Api/FaceBearerTokenHandler.cs create mode 100644 Src/Dlhis.Middle/Apis/Handlers/ClientHandler.cs create mode 100644 Src/Dlhis.Middle/Apis/Handlers/FaceBearerTokenHandler.cs rename Src/Dlhis.Middle/{Api => Apis}/IAssignmentDeviceTaskApi.cs (69%) rename Src/Dlhis.Middle/{Api => Apis}/ICanteenApi.cs (67%) create mode 100644 Src/Dlhis.Middle/Apis/ICommonApi.cs rename Src/Dlhis.Middle/{Services/Implements/BalanceRecordCheckBackgroundService.cs => BackgroundServices/BalanceRecordUploadBackgroundService.cs} (60%) rename Src/Dlhis.Middle/{Services/Implements => BackgroundServices}/HealthCheckBackgroundService.cs (87%) create mode 100644 Src/Dlhis.Middle/Request/HealthCheckRequest.cs create mode 100644 Src/Dlhis.Middle/Services/ISettingService.cs create mode 100644 Src/Dlhis.Middle/Services/ITokenService.cs create mode 100644 Src/Dlhis.Middle/Services/Implements/SettingService.cs create mode 100644 Src/Dlhis.Middle/Services/Implements/TokenService.cs create mode 100644 Src/Dlhis.Middle/local.db-journal diff --git a/Src/Dlhis.Canteen/Order/Controllers/OrdersController.cs b/Src/Dlhis.Canteen/Order/Controllers/OrdersController.cs index 1949cb5b..ba3482cd 100644 --- a/Src/Dlhis.Canteen/Order/Controllers/OrdersController.cs +++ b/Src/Dlhis.Canteen/Order/Controllers/OrdersController.cs @@ -1,5 +1,4 @@ -using Dlhis.Canteen.Dishes.Request; -using Dlhis.Canteen.Order.Request; +using Dlhis.Canteen.Order.Request; using Dlhis.Canteen.Order.Response; using Dlhis.Canteen.Order.Services; using Kurisu.AspNetCore.DataAccess.Dto; @@ -35,8 +34,7 @@ public class OrdersController : ControllerBase [IgnoreTenant] public async Task> AddAsync(OrderAddRequest request) { - var result = await _orderService.AddAsync(request); - return result.Response; + return await _orderService.AddAsync(request); } /// @@ -49,8 +47,7 @@ public class OrdersController : ControllerBase [IgnoreTenant] public async Task> DeviceConsume(OrderAddRequest request) { - var result = await _orderService.AddAsync(request); - return result.Response; + return await _orderService.AddAsync(request); } /// diff --git a/Src/Dlhis.Canteen/Order/Repository/Implements/BalanceRepository.cs b/Src/Dlhis.Canteen/Order/Repository/Implements/BalanceRepository.cs index 5de3e2f9..f1d60d9e 100644 --- a/Src/Dlhis.Canteen/Order/Repository/Implements/BalanceRepository.cs +++ b/Src/Dlhis.Canteen/Order/Repository/Implements/BalanceRepository.cs @@ -16,15 +16,18 @@ public class BalanceRepository : IBalanceRepository public async Task GetBalanceAsync(string userCode, string factory) { - var factories = await GetWalletFactoriesAsync(factory); - - return await _dbContext.Queryable() + var queryable = _dbContext.Queryable() .Where(x => x.UserCode == userCode && x.Status == BalanceRecordStatusEnumType.Valid) - .Where(x => factories.Contains(x.TenantId)) - .Where(x => x.Source != BalanceRecordSourceEnumType.EMealVoucher) - .SumAsync(x => x.Amount); - } + .Where(x => x.Source != BalanceRecordSourceEnumType.EMealVoucher); + if (!factory.IsPresent()) + { + return await queryable.SumAsync(x => x.Amount); + } + + var factories = await GetWalletFactoriesAsync(factory); + return await queryable.Where(x => factories.Contains(x.TenantId)).SumAsync(x => x.Amount); + } public async Task> GetBalanceRecordsAsync(string userCode, string factory, DateTime startTime, DateTime endTime) { diff --git a/Src/Dlhis.Canteen/Order/Response/OrderResultResponse.cs b/Src/Dlhis.Canteen/Order/Response/OrderResultResponse.cs index d564e96b..a1472e19 100644 --- a/Src/Dlhis.Canteen/Order/Response/OrderResultResponse.cs +++ b/Src/Dlhis.Canteen/Order/Response/OrderResultResponse.cs @@ -32,4 +32,13 @@ public class OrderResultResponse /// 余额 /// public string Balance => CurrentBalance.ToString("f2"); -} + + + [JsonIgnore] + public decimal CurrentTotalBalance { get; set; } + + /// + /// 总余额 + /// + public string TotalBalance => CurrentTotalBalance.ToString("f2"); +} \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Services/IOrderService.cs b/Src/Dlhis.Canteen/Order/Services/IOrderService.cs index a956a361..a738db3f 100644 --- a/Src/Dlhis.Canteen/Order/Services/IOrderService.cs +++ b/Src/Dlhis.Canteen/Order/Services/IOrderService.cs @@ -1,19 +1,20 @@ using Dlhis.Canteen.Order.Request; using Dlhis.Canteen.Order.Response; using Kurisu.AspNetCore.DataAccess.Dto; +using Kurisu.AspNetCore.UnifyResultAndValidation; namespace Dlhis.Canteen.Order.Services; public interface IOrderService : IScopeDependency { - Task AddAsync(OrderAddRequest request); + Task> AddAsync(OrderAddRequest request); /// /// 设备消费 /// /// /// - Task DeviceConsume(OrderAddRequest request); + Task> DeviceConsume(OrderAddRequest request); Task> GetOrdersAggregationAsync(PageDto page); } \ No newline at end of file diff --git a/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs b/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs index 22c6c78d..ff0362d8 100644 --- a/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs +++ b/Src/Dlhis.Canteen/Order/Services/Implements/OrderService.cs @@ -57,7 +57,7 @@ public class OrderService : IOrderService /// /// /// - public async Task DeviceConsume(OrderAddRequest request) + public async Task> DeviceConsume(OrderAddRequest request) { return await AddAsync(request); } @@ -67,7 +67,7 @@ public class OrderService : IOrderService /// /// /// - public async Task AddAsync(OrderAddRequest request) + public async Task> AddAsync(OrderAddRequest request) { await SetDeviceIpInfoAsync(request.DeviceUniqueNo); @@ -75,10 +75,7 @@ public class OrderService : IOrderService handler.Acquired.ThrowIfFalse("请勿频繁消费"); var gainfo = await GetUserAsync(request); - return new OrderResultResponseWithLogs - { - Response = await ToDoAsync(request, gainfo) - }; + return await ToDoAsync(request, gainfo); } @@ -256,7 +253,7 @@ public class OrderService : IOrderService /// /// /// - private async Task HandleOfflineAsync(ConsumeRequest request, GainfoEntity gainfo) + private async Task OfflineHandleAsync(ConsumeRequest request, GainfoEntity gainfo) { var bindingInfo = await _dbContext.Queryable() .LeftJoin((x, b) => x.BusinessPlaceCode == b.Code) @@ -273,11 +270,13 @@ public class OrderService : IOrderService var shifts = await _dbContext.Queryable() .Where(x => x.ShiftCode == bindingInfo.ShiftCode).ToListAsync(); - var shift = shifts.FirstOrDefault(x => new TimePeriod - { - Start = x.Start, - End = x.End - }.IsPresent(request.CostDateTime)); + var shift = shifts.FirstOrDefault(x => + new TimePeriod + { + Start = x.Start, + End = x.End + }.IsPresent(request.CostDateTime) + ); var costDate = await _canteenSettingService.GetCostDateAsync(bindingInfo.Factory, request.CostDateTime); @@ -317,7 +316,7 @@ public class OrderService : IOrderService TenantId = bindingInfo.Factory, }; - await _dbContext.InsertReturnIdentityAsync(order); + await _dbContext.InsertAsync(order); var balanceRecord = order.Adapt(); await _dbContext.InsertAsync(balanceRecord); } @@ -391,16 +390,36 @@ public class OrderService : IOrderService //获取餐券余额 var data = await _dbContext.Queryable() .LeftJoin((x, d) => x.No == d.No) - .LeftJoin((x, d, u) => u.IdNo == d.IdNo) .Where((x, d) => x.No == no && d.IdNo == idNo) - .Select((x, d, u) => new OrderResultResponse + .Select((x, d) => new OrderResultResponse { - UserCode = u.Code, - UserName = u.Name, + UserCode = d.IdNo, + UserName = d.Name, CurrentBalance = d.Balance }) .SingleAsync(); + try + { + var v = data.UserCode ?? string.Empty; + var vc = v.ToCharArray().Reverse().ToList(); + + for (int i = 0; i < vc.Count; i++) + { + if (i + 1 > 4) + { + vc[i] = '*'; + } + } + + vc.Reverse(); + data.UserCode = string.Join("", vc); + } + catch (Exception e) + { + //ignore + } + return new DefaultApiResult { Code = msg.IsPresent() ? ApiStateCode.Error : ApiStateCode.Success, @@ -409,6 +428,30 @@ public class OrderService : IOrderService }; } + + private async Task OfflineConsumeAsync(OrderAddRequest request, GainfoEntity gainfo) + { + try + { + if (request.ThirdNo.IsPresent() && await ToCheckAbleOfflineConsumeAsync(request.ThirdNo)) + { + await using var handler = await _redisCache.LockAsync($"consume:device-offline:{request.ThirdNo}"); + if (handler.Acquired) + { + await OfflineHandleAsync(request.Adapt(), gainfo); + } + } + else + { + await OfflineHandleAsync(request.Adapt(), gainfo); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "离线入库错误: 订单号:{no} .{message}", request.ThirdNo, ex.Message); + } + } + /// /// 分支处理在线/离线消费 /// @@ -423,27 +466,7 @@ public class OrderService : IOrderService return await EMealVoucherConsumeAsync(request, gainfo); if (request.IsOffline) - { - try - { - if (request.ThirdNo.IsPresent() && await ToCheckAbleOfflineConsumeAsync(request.ThirdNo)) - { - await using var handler = await _redisCache.LockAsync($"consume:device-offline:{request.ThirdNo}"); - if (handler.Acquired) - { - await HandleOfflineAsync(request.Adapt(), gainfo); - } - } - else - { - await HandleOfflineAsync(request.Adapt(), gainfo); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "离线入库错误: 订单号:{no} .{message}", request.ThirdNo, ex.Message); - } - } + await OfflineConsumeAsync(request, gainfo); else { try @@ -454,52 +477,17 @@ public class OrderService : IOrderService { message = ex.Message; } - - //获取餐券余额 - var data = await DbContext.Queryable() - .LeftJoin((x, d) => x.No == d.No) - .Where((x, d) => x.No == no && d.IdNo == idNo) - .Select((x, d) => new OrderResultResponse - { - UserCode = d.IdNo, - UserName = d.Name, - CurrentBalance = d.Balance - }) - .SingleAsync(); - - try - { - var v = data.UserCode ?? string.Empty; - var vc = v.ToCharArray().Reverse().ToList(); - - for (int i = 0; i < vc.Count; i++) - { - if (i + 1 > 4) - { - vc[i] = '*'; - } - } - - vc.Reverse(); - data.UserCode = string.Join("", vc); - } - catch (Exception e) - { - - } - - return new DefaultApiResult - { - Code = message.IsPresent() ? ApiStateCode.Error : ApiStateCode.Success, - Data = data, - Msg = message - }; } - var currentFactory = await _dbContext.Queryable().Where(x => x.UniqueNo == request.DeviceUniqueNo).Select(x => x.TenantId).SingleAsync(); - var balance = await _balanceRepository.GetBalanceAsync(gainfo.Code, currentFactory); + //获取人员信息 + var currentFactory = await _dbContext.Queryable() + .Where(x => x.UniqueNo == request.DeviceUniqueNo) + .Select(x => x.TenantId) + .SingleAsync(); + + var balance = await _balanceRepository.GetBalanceAsync(gainfo.Code, currentFactory); + var totalBalance = await _balanceRepository.GetBalanceAsync(gainfo.Code, string.Empty); - //获取人员余额 var data = await _dbContext.Queryable() .LeftJoin((x, department) => x.DepartmentNo == department.No) .Where(x => x.Code == gainfo.Code) @@ -509,7 +497,9 @@ public class OrderService : IOrderService UserName = x.Name, DepartmentNo = x.DepartmentNo, DepartmentName = department.Name, - CurrentBalance = balance + + CurrentBalance = balance, + CurrentTotalBalance = totalBalance }) .SingleAsync(); @@ -555,6 +545,7 @@ public class OrderService : IOrderService (nowDate >= current.Start && nowDate <= current.End).ThrowIfFalse("餐券已过期"); + var eMealVoucher = await _dbContext.Queryable().Where(x => x.No == no).SingleAsync(); var checkData = await PreCheckAsync(request.DeviceUniqueNo, request.CostDateTime); diff --git a/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs b/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs index 2376b87c..9c4c6b36 100644 --- a/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs +++ b/Src/Dlhis.Canteen/Setting/Services/ICanteenSettingService.cs @@ -44,7 +44,13 @@ public interface ICanteenSettingService : IScopeDependency Task GetBusinessSwitchTimeAsync(string tenantId); - Task GetSettingAsync(CanteenSettingItemEnumType item, string factory, string userNo , T defaultValue = default); + Task GetSettingAsync(CanteenSettingItemEnumType item, string factory, string userNo, T defaultValue = default); + /// + /// 获取消费日期 + /// + /// + /// + /// Task GetCostDateAsync(string factory, DateTime costDateTime); } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Api/FaceBearerTokenHandler.cs b/Src/Dlhis.Middle/Api/FaceBearerTokenHandler.cs deleted file mode 100644 index a0e806ff..00000000 --- a/Src/Dlhis.Middle/Api/FaceBearerTokenHandler.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Text; -using Dlhis.Middle.Response; -using Kurisu.AspNetCore.UnifyResultAndValidation; -using Kurisu.RemoteCall.Abstractions; -using Microsoft.Extensions.Caching.Memory; -using Newtonsoft.Json; - -namespace Dlhis.Middle.Api -{ - public class FaceBearerTokenHandler : IAuthTokenHandler - { - /// - public string GetToken(IServiceProvider serviceProvider) - { - var cache = serviceProvider.GetService(); - if (cache.TryGetValue("token", out string token)) return "Bearer " + token; - - var httpClient = serviceProvider.GetService().CreateClient("face"); - var hostName = serviceProvider.GetService().GetSection("HostName").Value; - - var tokenResponse = httpClient.PostAsync($"https://{hostName}/api/user/oauth2/clientgrant", new StringContent(JsonConvert.SerializeObject(new - { - ClientId = "e509c2aaa98e300c", - ClientSecret = "6184d8979fc48105" - }), Encoding.UTF8, "application/json")).GetAwaiter().GetResult(); - - var tokenJson = tokenResponse.Content.ReadAsStringAsync().GetAwaiter().GetResult(); - var tokenInfo = JsonConvert.DeserializeObject>(tokenJson); - - token = tokenInfo.Data.AccessToken; - - cache.Set("token", token, TimeSpan.FromSeconds(tokenInfo.Data.Expires)); - - return token; - } - } -} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Apis/Handlers/ClientHandler.cs b/Src/Dlhis.Middle/Apis/Handlers/ClientHandler.cs new file mode 100644 index 00000000..fa3b1694 --- /dev/null +++ b/Src/Dlhis.Middle/Apis/Handlers/ClientHandler.cs @@ -0,0 +1,23 @@ +using Kurisu.RemoteCall.Abstractions; + +namespace Dlhis.Middle.Apis.Handlers; + +public class ClientHandler : IHttpRemoteCallPolicyHandler +{ + /// + public IHttpClientBuilder ConfigureHttpClient(IHttpClientBuilder builder) + { + builder.ConfigureHttpClient(client => { client.Timeout = TimeSpan.FromSeconds(10); }).ConfigurePrimaryHttpMessageHandler(() => + { + var handler = new HttpClientHandler + { + ClientCertificateOptions = ClientCertificateOption.Manual, + ServerCertificateCustomValidationCallback = (_, _, _, _) => true + }; + + return handler; + }); + + return builder; + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Apis/Handlers/FaceBearerTokenHandler.cs b/Src/Dlhis.Middle/Apis/Handlers/FaceBearerTokenHandler.cs new file mode 100644 index 00000000..37c409a2 --- /dev/null +++ b/Src/Dlhis.Middle/Apis/Handlers/FaceBearerTokenHandler.cs @@ -0,0 +1,14 @@ +using Dlhis.Middle.Services; +using Kurisu.RemoteCall.Abstractions; + +namespace Dlhis.Middle.Apis.Handlers; + +public class FaceBearerTokenHandler : IAsyncAuthTokenHandler +{ + /// + public async Task GetTokenAsync(IServiceProvider serviceProvider) + { + var tokenService = serviceProvider.GetService(); + return await tokenService.AuthAsync(); + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Api/IAssignmentDeviceTaskApi.cs b/Src/Dlhis.Middle/Apis/IAssignmentDeviceTaskApi.cs similarity index 69% rename from Src/Dlhis.Middle/Api/IAssignmentDeviceTaskApi.cs rename to Src/Dlhis.Middle/Apis/IAssignmentDeviceTaskApi.cs index 8c856a82..d15f6f0f 100644 --- a/Src/Dlhis.Middle/Api/IAssignmentDeviceTaskApi.cs +++ b/Src/Dlhis.Middle/Apis/IAssignmentDeviceTaskApi.cs @@ -1,11 +1,13 @@ +using Dlhis.Middle.Apis.Handlers; using Dlhis.Middle.Request; using Dlhis.Middle.Response; using Kurisu.AspNetCore.UnifyResultAndValidation; using Kurisu.RemoteCall.Attributes; -namespace Dlhis.Middle.Api; +namespace Dlhis.Middle.Apis; -[EnableRemoteClient(Name = "face-service", BaseUrl = "${Authority}")] +[RequestLog] +[EnableRemoteClient(Name = "face-service", BaseUrl = "${BaseUrl}", PolicyHandler = typeof(ClientHandler))] public interface IAssignmentDeviceTaskApi { [Auth(TokenHandler = typeof(FaceBearerTokenHandler))] diff --git a/Src/Dlhis.Middle/Api/ICanteenApi.cs b/Src/Dlhis.Middle/Apis/ICanteenApi.cs similarity index 67% rename from Src/Dlhis.Middle/Api/ICanteenApi.cs rename to Src/Dlhis.Middle/Apis/ICanteenApi.cs index 84a4d7e9..bbc2c1eb 100644 --- a/Src/Dlhis.Middle/Api/ICanteenApi.cs +++ b/Src/Dlhis.Middle/Apis/ICanteenApi.cs @@ -1,26 +1,29 @@ +using Dlhis.Middle.Apis.Handlers; +using Dlhis.Middle.Entities; using Dlhis.Middle.Request; using Dlhis.Middle.Response; using Kurisu.AspNetCore.DataAccess.Dto; using Kurisu.AspNetCore.UnifyResultAndValidation; using Kurisu.RemoteCall.Attributes; -namespace Dlhis.Middle.Api; +namespace Dlhis.Middle.Apis; -[EnableRemoteClient(Name = "face-service", BaseUrl = "${Authority}")] +[RequestLog] +[EnableRemoteClient(Name = "face-service", BaseUrl = "${BaseUrl}", PolicyHandler = typeof(ClientHandler))] public interface ICanteenApi { - [Auth(TokenHandler = typeof(FaceBearerTokenHandler))] [Post("/api/canteen/orders")] Task> AddAsync(OrderAddRequest request); - - - + + [Auth(TokenHandler = typeof(FaceBearerTokenHandler))] + [Post("/api/canteen/middle/upload")] + Task> UploadAsync(LocalBalanceRecordEntity entity); + [Auth(TokenHandler = typeof(FaceBearerTokenHandler))] [Post("/api/canteen/balances/device/page")] Task>> GetPageByDeviceUniqueNoAsync(BalanceRecordPageByDeviceUniqueNoRequest request); - - + [Auth(TokenHandler = typeof(FaceBearerTokenHandler))] [Post("/api/canteen/balances/refund")] Task> RefundAsync(BalanceRecordRefundRequest request); diff --git a/Src/Dlhis.Middle/Apis/ICommonApi.cs b/Src/Dlhis.Middle/Apis/ICommonApi.cs new file mode 100644 index 00000000..b44214fc --- /dev/null +++ b/Src/Dlhis.Middle/Apis/ICommonApi.cs @@ -0,0 +1,22 @@ +using Dlhis.Middle.Apis.Handlers; +using Dlhis.Middle.Request; +using Dlhis.Middle.Response; +using Kurisu.AspNetCore.UnifyResultAndValidation; +using Kurisu.RemoteCall.Attributes; + +namespace Dlhis.Middle.Apis; + +[EnableRemoteClient(Name = "face-service", BaseUrl = "${BaseUrl}", PolicyHandler = typeof(ClientHandler))] +public interface ICommonApi +{ + [Post("/api/user/oauth2/clientgrant")] + public Task> AuthAsync(object request); + + [Auth(TokenHandler = typeof(FaceBearerTokenHandler))] + [Post("/api/common/middles/healthz")] + public Task CheckHealthAsync(HealthCheckRequest request); + + [Auth(TokenHandler = typeof(FaceBearerTokenHandler))] + [Get("/api/common/SysData/download")] + public Task>> DownloadAsync(string factory, string tableName); +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/BalanceRecordCheckBackgroundService.cs b/Src/Dlhis.Middle/BackgroundServices/BalanceRecordUploadBackgroundService.cs similarity index 60% rename from Src/Dlhis.Middle/Services/Implements/BalanceRecordCheckBackgroundService.cs rename to Src/Dlhis.Middle/BackgroundServices/BalanceRecordUploadBackgroundService.cs index a117b24b..f0f441a0 100644 --- a/Src/Dlhis.Middle/Services/Implements/BalanceRecordCheckBackgroundService.cs +++ b/Src/Dlhis.Middle/BackgroundServices/BalanceRecordUploadBackgroundService.cs @@ -1,46 +1,43 @@ -using System.Net.Http.Headers; +using System.Net.Http.Headers; using System.Text; +using Dlhis.Middle.Apis; using Dlhis.Middle.Entities; using Dlhis.Middle.Response; +using Dlhis.Middle.Services; 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; +namespace Dlhis.Middle.BackgroundServices; -public class BalanceRecordCheckBackgroundService : BackgroundService +public class BalanceRecordUploadBackgroundService : BackgroundService { private readonly IServiceProvider _serviceProvider; - private readonly ILogger _logger; - private readonly IMemoryCache _memoryCache; - private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + private readonly IWebHostEnvironment _environment; + private readonly ISettingService _settingService; + private readonly ICanteenApi _canteenApi; private readonly IHealthCheckService _healthCheckService; - private readonly string _factory; - private readonly string _hostName; - - public BalanceRecordCheckBackgroundService(IServiceProvider serviceProvider, - ILogger logger, - IMemoryCache memoryCache, - IHttpClientFactory httpClientFactory, - IHealthCheckService healthCheckService, - IConfiguration configuration) + public BalanceRecordUploadBackgroundService(IServiceProvider serviceProvider, + ILogger logger, + IWebHostEnvironment environment, + ISettingService settingService, + ICanteenApi canteenApi, + IHealthCheckService healthCheckService) { _serviceProvider = serviceProvider; _logger = logger; - _memoryCache = memoryCache; - _httpClientFactory = httpClientFactory; + _environment = environment; + _settingService = settingService; + _canteenApi = canteenApi; _healthCheckService = healthCheckService; - _factory = configuration.GetSection("Factory").Value; - _hostName = configuration.GetSection("HostName").Value; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - while (!stoppingToken.IsCancellationRequested) + while (!stoppingToken.IsCancellationRequested && _environment.IsProduction()) { if (!_healthCheckService.GetIsOffline()) { @@ -50,7 +47,8 @@ public class BalanceRecordCheckBackgroundService : BackgroundService var db = scope.ServiceProvider.GetService(); var ds = scope.ServiceProvider.GetService(); - foreach (var factory in _factory.Split(',')) + var factories = _settingService.GetFactories(); + foreach (var factory in factories) { try { @@ -64,7 +62,7 @@ public class BalanceRecordCheckBackgroundService : BackgroundService await db.Client.Ado.BeginTranAsync(); try { - var r = await UploadAsync(balanceRecord); + var r = await _canteenApi.UploadAsync(balanceRecord); if (r.Code != ApiStateCode.Success) { throw new Exception(r.Msg); @@ -108,62 +106,23 @@ public class BalanceRecordCheckBackgroundService : BackgroundService && !_healthCheckService.GetIsOffline() && !await db.Queryable().Where(x => !x.IsSync).AnyAsync()) { - await ds.InitAsync(_factory); + await ds.InitAsync(factories); } else if (now is { Hour: 15, Minute: 1 } && !_healthCheckService.GetIsOffline() && !await db.Queryable().Where(x => !x.IsSync).AnyAsync()) { - await ds.InitAsync(_factory); + await ds.InitAsync(factories); } else if (now is { Hour: 20, Minute: 1 } && !_healthCheckService.GetIsOffline() && !await db.Queryable().Where(x => !x.IsSync).AnyAsync()) { - await ds.InitAsync(_factory); + await ds.InitAsync(factories); } } await Task.Delay(60 * 1000, stoppingToken); } } - - public async Task> 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>(); - } - - private async Task 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>(tokenJson); - - token = tokenInfo.Data.AccessToken; - - _memoryCache.Set("token", token, TimeSpan.FromSeconds(tokenInfo.Data.Expires)); - - return token; - } } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/HealthCheckBackgroundService.cs b/Src/Dlhis.Middle/BackgroundServices/HealthCheckBackgroundService.cs similarity index 87% rename from Src/Dlhis.Middle/Services/Implements/HealthCheckBackgroundService.cs rename to Src/Dlhis.Middle/BackgroundServices/HealthCheckBackgroundService.cs index bc658f64..a765b742 100644 --- a/Src/Dlhis.Middle/Services/Implements/HealthCheckBackgroundService.cs +++ b/Src/Dlhis.Middle/BackgroundServices/HealthCheckBackgroundService.cs @@ -1,4 +1,6 @@ -namespace Dlhis.Middle.Services.Implements; +using Dlhis.Middle.Services; + +namespace Dlhis.Middle.BackgroundServices; public class HealthCheckBackgroundService : BackgroundService { diff --git a/Src/Dlhis.Middle/Controllers/AssignmentsController.cs b/Src/Dlhis.Middle/Controllers/AssignmentsController.cs index 5dfaa784..8238041f 100644 --- a/Src/Dlhis.Middle/Controllers/AssignmentsController.cs +++ b/Src/Dlhis.Middle/Controllers/AssignmentsController.cs @@ -1,5 +1,5 @@ using Dlhis.Entity.Device.Enums; -using Dlhis.Middle.Api; +using Dlhis.Middle.Apis; using Dlhis.Middle.Request; using Dlhis.Middle.Response; using Dlhis.Middle.Services; diff --git a/Src/Dlhis.Middle/Controllers/BalancesController.cs b/Src/Dlhis.Middle/Controllers/BalancesController.cs index 04749614..31444388 100644 --- a/Src/Dlhis.Middle/Controllers/BalancesController.cs +++ b/Src/Dlhis.Middle/Controllers/BalancesController.cs @@ -1,4 +1,4 @@ -using Dlhis.Middle.Api; +using Dlhis.Middle.Apis; using Dlhis.Middle.Request; using Dlhis.Middle.Response; using Dlhis.Middle.Services; diff --git a/Src/Dlhis.Middle/Controllers/GainfosController.cs b/Src/Dlhis.Middle/Controllers/GainfosController.cs index a8f72fa6..6fc33430 100644 --- a/Src/Dlhis.Middle/Controllers/GainfosController.cs +++ b/Src/Dlhis.Middle/Controllers/GainfosController.cs @@ -1,4 +1,5 @@ using Dlhis.Middle.Filters; +using Dlhis.Middle.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -12,14 +13,13 @@ namespace Dlhis.Middle.Controllers; public class GainfosController : ControllerBase { private readonly IHttpClientFactory _httpClientFactory; - private readonly IConfiguration _configuration; - private readonly ILogger _logger; + private readonly ISettingService _settingService; - public GainfosController(IHttpClientFactory httpClientFactory, IConfiguration configuration, ILogger logger) + public GainfosController(IHttpClientFactory httpClientFactory, + ISettingService settingService) { _httpClientFactory = httpClientFactory; - _configuration = configuration; - _logger = logger; + _settingService = settingService; } /// @@ -32,14 +32,11 @@ public class GainfosController : ControllerBase [HttpGet("face")] public async Task GetFaceAsync(string token) { - var hostName = _configuration.GetSection("HostName").Value; - var httpClient = _httpClientFactory.CreateClient("face"); - - _logger.LogInformation("token:{token}", token); + var httpClient = _httpClientFactory.CreateClient(); try { - var faceBytes = await httpClient.GetByteArrayAsync($"https://{hostName}/api/basic/gainfos/face?token={token}"); + var faceBytes = await httpClient.GetByteArrayAsync($"{_settingService.GetBaseUrl()}/api/basic/gainfos/face?token={token}"); if (faceBytes.Length == 0) { diff --git a/Src/Dlhis.Middle/Controllers/OAuth2Controller.cs b/Src/Dlhis.Middle/Controllers/OAuth2Controller.cs index 5b448c0d..f6b8624e 100644 --- a/Src/Dlhis.Middle/Controllers/OAuth2Controller.cs +++ b/Src/Dlhis.Middle/Controllers/OAuth2Controller.cs @@ -1,4 +1,5 @@ using System.Text; +using Dlhis.Middle.Apis; using Dlhis.Middle.Request; using Dlhis.Middle.Services; using Microsoft.AspNetCore.Mvc; @@ -13,23 +14,20 @@ namespace Dlhis.Middle.Controllers; [ApiController] public class OAuth2Controller : ControllerBase { - private readonly IHttpClientFactory _httpClientFactory; - private readonly IConfiguration _configuration; + private readonly ICommonApi _commonApi; private readonly IHealthCheckService _healthCheckService; - public OAuth2Controller(IHttpClientFactory httpClientFactory, IConfiguration configuration, IHealthCheckService healthCheckService) + public OAuth2Controller( + ICommonApi commonApi, + IHealthCheckService healthCheckService) { - _httpClientFactory = httpClientFactory; - _configuration = configuration; + _commonApi = commonApi; _healthCheckService = healthCheckService; } [HttpPost("clientgrant")] public async Task ClientGrant(ClientGrantRequest request) { - var authority = _configuration.GetSection("Authority").Value; - var httpClient = _httpClientFactory.CreateClient("face"); - if (_healthCheckService.GetIsOffline()) { return new @@ -41,9 +39,12 @@ public class OAuth2Controller : ControllerBase }; } - 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}/oauth2/connect/token", content); - var result = await response.Content.ReadAsStringAsync(); - return JObject.Parse(result); + var r = await _commonApi.AuthAsync(new + { + request.ClientId, + request.ClientSecret + }); + + return r.Data; } } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Dlhis.Middle.csproj b/Src/Dlhis.Middle/Dlhis.Middle.csproj index accf19a9..07f7413e 100644 --- a/Src/Dlhis.Middle/Dlhis.Middle.csproj +++ b/Src/Dlhis.Middle/Dlhis.Middle.csproj @@ -17,10 +17,23 @@ - + + + + appsettings.json + + + + + + + + + + diff --git a/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs b/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs index bb9809ff..322272aa 100644 --- a/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs +++ b/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs @@ -1,4 +1,3 @@ -using Dlhis.Entity.Basic.Entity; using Kurisu.AspNetCore.DataAccess.Entity; using Kurisu.AspNetCore.DataAccess.SqlSugar; using SqlSugar; @@ -12,7 +11,7 @@ namespace Dlhis.Middle.Entities /// - /// 就餐人员 + /// 就餐人员 /// [SugarTable(TableName = "GainfoMeal", TableDescription = "就餐人员")] public class GainfoMealEntity : SugarBaseEntity, IVersion @@ -24,25 +23,13 @@ namespace Dlhis.Middle.Entities public string UserCode { get; set; } /// - /// 余额 + /// 余额 /// [SugarColumn(ColumnDescription = "余额")] public decimal Balance { get; set; } - + /// - /// 余额(充值) - /// - [SugarColumn(ColumnDescription = "余额(充值)")] - public decimal BalanceOfRecharge { get; set; } - - /// - /// 余额(餐补) - /// - [SugarColumn(ColumnDescription = "余额(餐补)")] - public decimal BalanceOfSubsidies { get; set; } - - /// - /// 版本号 + /// 版本号 /// [SugarColumn(ColumnDescription = "版本号")] public int Version { get; set; } diff --git a/Src/Dlhis.Middle/Program.cs b/Src/Dlhis.Middle/Program.cs index 99f6c4a6..7558c266 100644 --- a/Src/Dlhis.Middle/Program.cs +++ b/Src/Dlhis.Middle/Program.cs @@ -1,5 +1,5 @@ +using Dlhis.Middle.BackgroundServices; using Dlhis.Middle.Services; -using Dlhis.Middle.Services.Implements; using Kurisu.AspNetCore; using Kurisu.AspNetCore.Startup; using Microsoft.IdentityModel.Logging; @@ -24,20 +24,11 @@ public class Startup : DefaultStartup public override void ConfigureServices(IServiceCollection services) { base.ConfigureServices(services); + + services.AddHostedService(); + services.AddHostedService(); services.AddRemoteCall(App.ActiveTypes); - services.AddHttpClient("face", client => { client.Timeout = TimeSpan.FromSeconds(30); }).ConfigurePrimaryHttpMessageHandler(() => - { - var handler = new HttpClientHandler - { - ClientCertificateOptions = ClientCertificateOption.Manual, - ServerCertificateCustomValidationCallback = (_, _, _, _) => true - }; - - return handler; - }); - services.AddHostedService(); - services.AddHostedService(); services.AddMemoryCache(); } @@ -49,10 +40,9 @@ public class Startup : DefaultStartup if (Configuration.GetSection("Download").Value == "1") { using var scope = app.ApplicationServices.CreateScope(); - var factory = Configuration.GetSection("Factory").Value; - + var factories = scope.ServiceProvider.GetService().GetFactories(); var ds = scope.ServiceProvider.GetService(); - ds.InitAsync(factory).Wait(); + ds.InitAsync(factories).Wait(); } } } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Request/HealthCheckRequest.cs b/Src/Dlhis.Middle/Request/HealthCheckRequest.cs new file mode 100644 index 00000000..e783dc81 --- /dev/null +++ b/Src/Dlhis.Middle/Request/HealthCheckRequest.cs @@ -0,0 +1,6 @@ +namespace Dlhis.Middle.Request; + +public class HealthCheckRequest +{ + public List Factories { get; set; } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Response/OrderResultResponse.cs b/Src/Dlhis.Middle/Response/OrderResultResponse.cs index 65b07959..eaf00498 100644 --- a/Src/Dlhis.Middle/Response/OrderResultResponse.cs +++ b/Src/Dlhis.Middle/Response/OrderResultResponse.cs @@ -28,4 +28,7 @@ public class OrderResultResponse /// 余额 /// public decimal Balance { get; set; } + + + public decimal TotalBalance { get; set; } } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/IDataDownloadService.cs b/Src/Dlhis.Middle/Services/IDataDownloadService.cs index 0efb64d4..eccedb1c 100644 --- a/Src/Dlhis.Middle/Services/IDataDownloadService.cs +++ b/Src/Dlhis.Middle/Services/IDataDownloadService.cs @@ -2,9 +2,7 @@ namespace Dlhis.Middle.Services; public interface IDataDownloadService : IScopeDependency { - Task InitAsync(string factory); + Task InitAsync(string[] factories); Task DownloadAsync(string factory) where T : class, new(); - - Task TruncateAsync(); } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/IRemoteOrderService.cs b/Src/Dlhis.Middle/Services/IRemoteOrderService.cs index e161d9ad..e5f6dfc0 100644 --- a/Src/Dlhis.Middle/Services/IRemoteOrderService.cs +++ b/Src/Dlhis.Middle/Services/IRemoteOrderService.cs @@ -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> AddAsync(OrderAddRequest request); + //Task> AddAsync(OrderAddRequest request); Task AbleRemoteCallAsync(); } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/ISettingService.cs b/Src/Dlhis.Middle/Services/ISettingService.cs new file mode 100644 index 00000000..b7946df7 --- /dev/null +++ b/Src/Dlhis.Middle/Services/ISettingService.cs @@ -0,0 +1,8 @@ +namespace Dlhis.Middle.Services; + +public interface ISettingService : ISingletonDependency +{ + string[] GetFactories(); + + string GetBaseUrl(); +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/ITokenService.cs b/Src/Dlhis.Middle/Services/ITokenService.cs new file mode 100644 index 00000000..d4dffabe --- /dev/null +++ b/Src/Dlhis.Middle/Services/ITokenService.cs @@ -0,0 +1,6 @@ +namespace Dlhis.Middle.Services; + +public interface ITokenService : ISingletonDependency +{ + Task AuthAsync(); +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/DataDownloadService.cs b/Src/Dlhis.Middle/Services/Implements/DataDownloadService.cs index c9498000..9f1e83bd 100644 --- a/Src/Dlhis.Middle/Services/Implements/DataDownloadService.cs +++ b/Src/Dlhis.Middle/Services/Implements/DataDownloadService.cs @@ -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 _logger; - private readonly string _hostName; - public DataDownloadService(IHttpClientFactory httpClientFactory, IMemoryCache memoryCache, IConfiguration configuration, IDbContext dbContext, ILogger logger) + public DataDownloadService( + IDbContext dbContext, + ICommonApi commonApi, + ILogger 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().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().CountAsync()); _logger.LogInformation("余额{count}", await _dbContext.Queryable().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(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 - { - ["factory"] = factory, - ["tableName"] = typeof(T).GetCustomAttribute()!.TableName - }); - - var response = await httpClient.GetStringAsync(requestUrl); - var pack = response.ToObject>>(); - + var pack = await _commonApi.DownloadAsync(factory, typeof(T).GetCustomAttribute()!.TableName); await _dbContext.InsertAsync(pack.Data); } - /// - public async Task TruncateAsync() + private async Task TruncateAsync() { await _dbContext.Client.Deleteable().ExecuteCommandAsync(); await _dbContext.Client.Deleteable().ExecuteCommandAsync(); @@ -128,26 +102,4 @@ public class DataDownloadService : IDataDownloadService await _dbContext.Client.Deleteable().ExecuteCommandAsync(); await _dbContext.Client.Deleteable().ExecuteCommandAsync(); } - - private async Task 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>(tokenJson); - - token = tokenInfo.Data.AccessToken; - - _memoryCache.Set("token", token, TimeSpan.FromSeconds(tokenInfo.Data.Expires)); - - return token; - } } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/HealthcheckService.cs b/Src/Dlhis.Middle/Services/Implements/HealthcheckService.cs index 2c01192a..a254668d 100644 --- a/Src/Dlhis.Middle/Services/Implements/HealthcheckService.cs +++ b/Src/Dlhis.Middle/Services/Implements/HealthcheckService.cs @@ -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 _logger; - private int _successRetry; - private int _retry; + private readonly ISettingService _settingService; + private readonly ICommonApi _healthCheckApi; + private int _attempted; - public HealthCheckService(ILogger logger, IConfiguration configuration) + private bool _isBegin; + + public HealthCheckService(ILogger 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); } } } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/OrderService.cs b/Src/Dlhis.Middle/Services/Implements/OrderService.cs index 919663c6..fadb172f 100644 --- a/Src/Dlhis.Middle/Services/Implements/OrderService.cs +++ b/Src/Dlhis.Middle/Services/Implements/OrderService.cs @@ -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 _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 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() - .SetColumns(x => x.Balance == d.Balance) + .SetColumns(x => x.Balance == d.TotalBalance) .Where(x => x.UserCode == d.UserCode) .ExecuteCommandAsync(); } diff --git a/Src/Dlhis.Middle/Services/Implements/RemoteOrderService.cs b/Src/Dlhis.Middle/Services/Implements/RemoteOrderService.cs index 05d84b14..707e7338 100644 --- a/Src/Dlhis.Middle/Services/Implements/RemoteOrderService.cs +++ b/Src/Dlhis.Middle/Services/Implements/RemoteOrderService.cs @@ -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; } - - /// - public async Task> 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>(); - } + ///// + // public async Task> 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>(); + // } /// public async Task AbleRemoteCallAsync() @@ -60,28 +43,4 @@ public class RemoteOrderService : IRemoteOrderService return !_healthCheckService.GetIsOffline(); } - - - - private async Task 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>(tokenJson); - - token = tokenInfo.Data.AccessToken; - - _memoryCache.Set("token", token, TimeSpan.FromSeconds(tokenInfo.Data.Expires)); - - return token; - } } \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/SettingService.cs b/Src/Dlhis.Middle/Services/Implements/SettingService.cs new file mode 100644 index 00000000..b583f924 --- /dev/null +++ b/Src/Dlhis.Middle/Services/Implements/SettingService.cs @@ -0,0 +1,23 @@ +namespace Dlhis.Middle.Services.Implements; + +public class SettingService : ISettingService +{ + private readonly IConfiguration _configuration; + + public SettingService(IConfiguration configuration) + { + _configuration = configuration; + } + + /// + public string[] GetFactories() + { + return _configuration.GetSection("Factory").Value.Split(","); + } + + /// + public string GetBaseUrl() + { + return _configuration.GetSection("BaseUrl").Value; + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/TokenService.cs b/Src/Dlhis.Middle/Services/Implements/TokenService.cs new file mode 100644 index 00000000..226a9de0 --- /dev/null +++ b/Src/Dlhis.Middle/Services/Implements/TokenService.cs @@ -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; + } + + /// + public async Task 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; + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/appsettings.Development.json b/Src/Dlhis.Middle/appsettings.Development.json index efb527ae..1339edac 100644 --- a/Src/Dlhis.Middle/appsettings.Development.json +++ b/Src/Dlhis.Middle/appsettings.Development.json @@ -5,8 +5,7 @@ "Microsoft.AspNetCore": "Warning" } }, - "Authority": "https://face.jinkosolar.com", - "Factory": "1201", - "HostName": "face.jinkosolar.com", + "BaseUrl": "https://yikatong.isawesome.cn", + "Factory": "1234", "Download":1 } diff --git a/Src/Dlhis.Middle/appsettings.json b/Src/Dlhis.Middle/appsettings.json index f85ae1ef..b6e736e0 100644 --- a/Src/Dlhis.Middle/appsettings.json +++ b/Src/Dlhis.Middle/appsettings.json @@ -12,9 +12,6 @@ "SlowSqlTime": 1, "EnableSqlLog": false }, - "Authority": "https://face.jinkosolar.com", - "Factory": "", - "HostName": "face.jinkosolar.com", "AllowedHosts": "*", "SwaggerOptions": { "Enable": true, diff --git a/Src/Dlhis.Middle/local.db-journal b/Src/Dlhis.Middle/local.db-journal new file mode 100644 index 0000000000000000000000000000000000000000..750c5ab6103838ae055a5ee18909d19742871fc0 GIT binary patch literal 127736 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 YzWx7cZUCoa82sA28CfTSCve^W0U__dL;wH) literal 0 HcmV?d00001