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

@@ -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<DefaultApiResult<OrderResultResponse>> AddAsync(OrderAddRequest request)
{
var result = await _orderService.AddAsync(request);
return result.Response;
return await _orderService.AddAsync(request);
}
/// <summary>
@@ -49,8 +47,7 @@ public class OrdersController : ControllerBase
[IgnoreTenant]
public async Task<DefaultApiResult<OrderResultResponse>> DeviceConsume(OrderAddRequest request)
{
var result = await _orderService.AddAsync(request);
return result.Response;
return await _orderService.AddAsync(request);
}
/// <summary>

View File

@@ -16,15 +16,18 @@ public class BalanceRepository : IBalanceRepository
public async Task<decimal> GetBalanceAsync(string userCode, string factory)
{
var factories = await GetWalletFactoriesAsync(factory);
return await _dbContext.Queryable<BalanceRecordEntity>()
var queryable = _dbContext.Queryable<BalanceRecordEntity>()
.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<List<BalanceRecordEntity>> GetBalanceRecordsAsync(string userCode, string factory, DateTime startTime, DateTime endTime)
{

View File

@@ -32,4 +32,13 @@ public class OrderResultResponse
/// 余额
/// </summary>
public string Balance => CurrentBalance.ToString("f2");
[JsonIgnore]
public decimal CurrentTotalBalance { get; set; }
/// <summary>
/// 总余额
/// </summary>
public string TotalBalance => CurrentTotalBalance.ToString("f2");
}

View File

@@ -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<OrderResultResponseWithLogs> AddAsync(OrderAddRequest request);
Task<DefaultApiResult<OrderResultResponse>> AddAsync(OrderAddRequest request);
/// <summary>
/// 设备消费
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
Task<OrderResultResponseWithLogs> DeviceConsume(OrderAddRequest request);
Task<DefaultApiResult<OrderResultResponse>> DeviceConsume(OrderAddRequest request);
Task<Pagination<OrderAggregationResponse>> GetOrdersAggregationAsync(PageDto page);
}

View File

@@ -57,7 +57,7 @@ public class OrderService : IOrderService
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task<OrderResultResponseWithLogs> DeviceConsume(OrderAddRequest request)
public async Task<DefaultApiResult<OrderResultResponse>> DeviceConsume(OrderAddRequest request)
{
return await AddAsync(request);
}
@@ -67,7 +67,7 @@ public class OrderService : IOrderService
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task<OrderResultResponseWithLogs> AddAsync(OrderAddRequest request)
public async Task<DefaultApiResult<OrderResultResponse>> 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
/// </summary>
/// <param name="request"></param>
/// <param name="gainfo"></param>
private async Task HandleOfflineAsync(ConsumeRequest request, GainfoEntity gainfo)
private async Task OfflineHandleAsync(ConsumeRequest request, GainfoEntity gainfo)
{
var bindingInfo = await _dbContext.Queryable<DeviceRegistryEntity>()
.LeftJoin<BusinessPlaceEntity>((x, b) => x.BusinessPlaceCode == b.Code)
@@ -273,11 +270,13 @@ public class OrderService : IOrderService
var shifts = await _dbContext.Queryable<BusinessShiftMealRelationshipEntity>()
.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<BalanceRecordEntity>();
await _dbContext.InsertAsync(balanceRecord);
}
@@ -391,16 +390,36 @@ public class OrderService : IOrderService
//获取餐券余额
var data = await _dbContext.Queryable<EMealVoucherEntity>()
.LeftJoin<EMealVoucherDetailEntity>((x, d) => x.No == d.No)
.LeftJoin<GainfoEntity>((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<OrderResultResponse>
{
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<ConsumeRequest>(), gainfo);
}
}
else
{
await OfflineHandleAsync(request.Adapt<ConsumeRequest>(), gainfo);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "离线入库错误: 订单号:{no} .{message}", request.ThirdNo, ex.Message);
}
}
/// <summary>
/// 分支处理在线/离线消费
/// </summary>
@@ -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<ConsumeRequest>(), gainfo);
}
}
else
{
await HandleOfflineAsync(request.Adapt<ConsumeRequest>(), 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<EMealVoucherEntity>()
.LeftJoin<EMealVoucherDetailEntity>((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<OrderResultResponse>
{
Code = message.IsPresent() ? ApiStateCode.Error : ApiStateCode.Success,
Data = data,
Msg = message
};
}
var currentFactory = await _dbContext.Queryable<DeviceEntity>().Where(x => x.UniqueNo == request.DeviceUniqueNo).Select(x => x.TenantId).SingleAsync();
var balance = await _balanceRepository.GetBalanceAsync(gainfo.Code, currentFactory);
//获取人员信息
var currentFactory = await _dbContext.Queryable<DeviceEntity>()
.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<GainfoEntity>()
.LeftJoin<DepartmentEntity>((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<EMealVoucherEntity>().Where(x => x.No == no).SingleAsync();
var checkData = await PreCheckAsync(request.DeviceUniqueNo, request.CostDateTime);

View File

@@ -44,7 +44,13 @@ public interface ICanteenSettingService : IScopeDependency
Task<TimeOnly?> GetBusinessSwitchTimeAsync(string tenantId);
Task<T> GetSettingAsync<T>(CanteenSettingItemEnumType item, string factory, string userNo , T defaultValue = default);
Task<T> GetSettingAsync<T>(CanteenSettingItemEnumType item, string factory, string userNo, T defaultValue = default);
/// <summary>
/// 获取消费日期
/// </summary>
/// <param name="factory"></param>
/// <param name="costDateTime"></param>
/// <returns></returns>
Task<DateTime> GetCostDateAsync(string factory, DateTime costDateTime);
}

View File

@@ -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
{
/// <inheritdoc />
public string GetToken(IServiceProvider serviceProvider)
{
var cache = serviceProvider.GetService<IMemoryCache>();
if (cache.TryGetValue("token", out string token)) return "Bearer " + token;
var httpClient = serviceProvider.GetService<IHttpClientFactory>().CreateClient("face");
var hostName = serviceProvider.GetService<IConfiguration>().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<DefaultApiResult<TokenResponse>>(tokenJson);
token = tokenInfo.Data.AccessToken;
cache.Set("token", token, TimeSpan.FromSeconds(tokenInfo.Data.Expires));
return token;
}
}
}

View File

@@ -0,0 +1,23 @@
using Kurisu.RemoteCall.Abstractions;
namespace Dlhis.Middle.Apis.Handlers;
public class ClientHandler : IHttpRemoteCallPolicyHandler
{
/// <inheritdoc />
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;
}
}

View File

@@ -0,0 +1,14 @@
using Dlhis.Middle.Services;
using Kurisu.RemoteCall.Abstractions;
namespace Dlhis.Middle.Apis.Handlers;
public class FaceBearerTokenHandler : IAsyncAuthTokenHandler
{
/// <inheritdoc />
public async Task<string> GetTokenAsync(IServiceProvider serviceProvider)
{
var tokenService = serviceProvider.GetService<ITokenService>();
return await tokenService.AuthAsync();
}
}

View File

@@ -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))]

View File

@@ -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<DefaultApiResult<OrderResultResponse>> AddAsync(OrderAddRequest request);
[Auth(TokenHandler = typeof(FaceBearerTokenHandler))]
[Post("/api/canteen/middle/upload")]
Task<DefaultApiResult<OrderResultResponse>> UploadAsync(LocalBalanceRecordEntity entity);
[Auth(TokenHandler = typeof(FaceBearerTokenHandler))]
[Post("/api/canteen/balances/device/page")]
Task<DefaultApiResult<Pagination<BalanceRecordDeviceResponse>>> GetPageByDeviceUniqueNoAsync(BalanceRecordPageByDeviceUniqueNoRequest request);
[Auth(TokenHandler = typeof(FaceBearerTokenHandler))]
[Post("/api/canteen/balances/refund")]
Task<DefaultApiResult<OrderResultResponse>> RefundAsync(BalanceRecordRefundRequest request);

View File

@@ -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<DefaultApiResult<TokenResponse>> AuthAsync(object request);
[Auth(TokenHandler = typeof(FaceBearerTokenHandler))]
[Post("/api/common/middles/healthz")]
public Task<DefaultApiResult> CheckHealthAsync(HealthCheckRequest request);
[Auth(TokenHandler = typeof(FaceBearerTokenHandler))]
[Get("/api/common/SysData/download")]
public Task<DefaultApiResult<List<T>>> DownloadAsync<T>(string factory, string tableName);
}

View File

@@ -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<BalanceRecordCheckBackgroundService> _logger;
private readonly IMemoryCache _memoryCache;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<BalanceRecordUploadBackgroundService> _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<BalanceRecordCheckBackgroundService> logger,
IMemoryCache memoryCache,
IHttpClientFactory httpClientFactory,
IHealthCheckService healthCheckService,
IConfiguration configuration)
public BalanceRecordUploadBackgroundService(IServiceProvider serviceProvider,
ILogger<BalanceRecordUploadBackgroundService> 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<IDbContext>();
var ds = scope.ServiceProvider.GetService<IDataDownloadService>();
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<LocalBalanceRecordEntity>().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<LocalBalanceRecordEntity>().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<LocalBalanceRecordEntity>().Where(x => !x.IsSync).AnyAsync())
{
await ds.InitAsync(_factory);
await ds.InitAsync(factories);
}
}
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,4 +1,6 @@
namespace Dlhis.Middle.Services.Implements;
using Dlhis.Middle.Services;
namespace Dlhis.Middle.BackgroundServices;
public class HealthCheckBackgroundService : BackgroundService
{

View File

@@ -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;

View File

@@ -1,4 +1,4 @@
using Dlhis.Middle.Api;
using Dlhis.Middle.Apis;
using Dlhis.Middle.Request;
using Dlhis.Middle.Response;
using Dlhis.Middle.Services;

View File

@@ -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<GainfosController> _logger;
private readonly ISettingService _settingService;
public GainfosController(IHttpClientFactory httpClientFactory, IConfiguration configuration, ILogger<GainfosController> logger)
public GainfosController(IHttpClientFactory httpClientFactory,
ISettingService settingService)
{
_httpClientFactory = httpClientFactory;
_configuration = configuration;
_logger = logger;
_settingService = settingService;
}
/// <summary>
@@ -32,14 +32,11 @@ public class GainfosController : ControllerBase
[HttpGet("face")]
public async Task<IActionResult> 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)
{

View File

@@ -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<object> 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;
}
}

View File

@@ -17,10 +17,23 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Kurisu.RemoteCall" Version="0.4.2" />
<PackageReference Include="Kurisu.RemoteCall" Version="0.4.4" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.6" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlcipher" Version="2.1.8" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.Development.json">
<DependentUpon>appsettings.json</DependentUpon>
</Content>
</ItemGroup>
<ItemGroup>
<Compile Remove="Api\FaceBearerTokenHandler.cs" />
<Compile Remove="Api\IAssignmentDeviceTaskApi.cs" />
<Compile Remove="Services\Implements\BalanceRecordCheckBackgroundService.cs" />
<Compile Remove="Services\Implements\HealthCheckBackgroundService.cs" />
</ItemGroup>
</Project>

View File

@@ -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
/// <summary>
/// 就餐人员
/// 就餐人员
/// </summary>
[SugarTable(TableName = "GainfoMeal", TableDescription = "就餐人员")]
public class GainfoMealEntity : SugarBaseEntity, IVersion
@@ -24,25 +23,13 @@ namespace Dlhis.Middle.Entities
public string UserCode { get; set; }
/// <summary>
/// 余额
/// 余额
/// </summary>
[SugarColumn(ColumnDescription = "余额")]
public decimal Balance { get; set; }
/// <summary>
/// 余额(充值)
/// </summary>
[SugarColumn(ColumnDescription = "余额(充值)")]
public decimal BalanceOfRecharge { get; set; }
/// <summary>
/// 余额(餐补)
/// </summary>
[SugarColumn(ColumnDescription = "余额(餐补)")]
public decimal BalanceOfSubsidies { get; set; }
/// <summary>
/// 版本号
/// 版本号
/// </summary>
[SugarColumn(ColumnDescription = "版本号")]
public int Version { get; set; }

View File

@@ -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;
@@ -25,19 +25,10 @@ public class Startup : DefaultStartup
{
base.ConfigureServices(services);
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<BalanceRecordCheckBackgroundService>();
services.AddHostedService<BalanceRecordUploadBackgroundService>();
services.AddHostedService<HealthCheckBackgroundService>();
services.AddRemoteCall(App.ActiveTypes);
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<ISettingService>().GetFactories();
var ds = scope.ServiceProvider.GetService<IDataDownloadService>();
ds.InitAsync(factory).Wait();
ds.InitAsync(factories).Wait();
}
}
}

View File

@@ -0,0 +1,6 @@
namespace Dlhis.Middle.Request;
public class HealthCheckRequest
{
public List<string> Factories { get; set; }
}

View File

@@ -28,4 +28,7 @@ public class OrderResultResponse
/// 余额
/// </summary>
public decimal Balance { get; set; }
public decimal TotalBalance { get; set; }
}

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,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 +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;
}
}

View File

@@ -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
}

View File

@@ -12,9 +12,6 @@
"SlowSqlTime": 1,
"EnableSqlLog": false
},
"Authority": "https://face.jinkosolar.com",
"Factory": "",
"HostName": "face.jinkosolar.com",
"AllowedHosts": "*",
"SwaggerOptions": {
"Enable": true,

Binary file not shown.