diff --git a/.gitignore b/.gitignore index f8648cd8..f4b69a9b 100644 --- a/.gitignore +++ b/.gitignore @@ -112,3 +112,4 @@ UpgradeLog*.XML .idea .vs log +*.db diff --git a/Src/Dlhis.Middle/Controllers/OrdersController.cs b/Src/Dlhis.Middle/Controllers/OrdersController.cs new file mode 100644 index 00000000..ae8a6a45 --- /dev/null +++ b/Src/Dlhis.Middle/Controllers/OrdersController.cs @@ -0,0 +1,38 @@ +using Dlhis.Middle.Request; +using Dlhis.Middle.Response; +using Dlhis.Middle.Services; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Aop; +using Kurisu.AspNetCore.UnifyResultAndValidation; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Dlhis.Middle.Controllers; + +/// +/// 订单 +/// +[Authorize] +[Route("api/[controller]")] +[ApiController] +public class OrdersController : ControllerBase +{ + private readonly IOrderService _orderService; + + public OrdersController(IOrderService orderService) + { + _orderService = orderService; + } + + /// + /// 消费 + /// + /// + /// + [Authorize(Policy = "consumer")] + [HttpPost] + [IgnoreTenant] + public async Task> AddAsync(OrderAddRequest request) + { + return await _orderService.AddAsync(request); + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Dlhis.Middle.csproj b/Src/Dlhis.Middle/Dlhis.Middle.csproj new file mode 100644 index 00000000..1b0aef9f --- /dev/null +++ b/Src/Dlhis.Middle/Dlhis.Middle.csproj @@ -0,0 +1,25 @@ + + + + net6.0 + disable + enable + Linux + ..\.. + + + + + + + + + + + + + + + + + diff --git a/Src/Dlhis.Middle/Dockerfile b/Src/Dlhis.Middle/Dockerfile new file mode 100644 index 00000000..ff94ef81 --- /dev/null +++ b/Src/Dlhis.Middle/Dockerfile @@ -0,0 +1,24 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +WORKDIR /app +EXPOSE 80 + +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["Src/Dlhis.Middle/Dlhis.Middle.csproj", "Src/Dlhis.Middle/"] +COPY ["Src/Dlhis.Entity/Dlhis.Entity.csproj", "Src/Dlhis.Entity/"] +RUN dotnet restore "./Src/Dlhis.Middle/./Dlhis.Middle.csproj" +COPY . . +WORKDIR "/src/Src/Dlhis.Middle" +RUN dotnet build "./Dlhis.Middle.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./Dlhis.Middle.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Dlhis.Middle.dll"] \ No newline at end of file diff --git a/Src/Dlhis.Middle/Entities/LocalBalanceRecordEntity.cs b/Src/Dlhis.Middle/Entities/LocalBalanceRecordEntity.cs new file mode 100644 index 00000000..cbc4ecc5 --- /dev/null +++ b/Src/Dlhis.Middle/Entities/LocalBalanceRecordEntity.cs @@ -0,0 +1,9 @@ +using Dlhis.Entity.Canteen.Entities; + +namespace Dlhis.Middle.Entities +{ + public class LocalBalanceRecordEntity : BalanceRecordEntity + { + public new int Id { get; set; } + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Entities/LocalBusinessPlaceEntity.cs b/Src/Dlhis.Middle/Entities/LocalBusinessPlaceEntity.cs new file mode 100644 index 00000000..90b9a107 --- /dev/null +++ b/Src/Dlhis.Middle/Entities/LocalBusinessPlaceEntity.cs @@ -0,0 +1,9 @@ +using Dlhis.Entity.Basic.Entity; + +namespace Dlhis.Middle.Entities +{ + public class LocalBusinessPlaceEntity : BusinessPlaceEntity + { + public new int Id { get; set; } + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Entities/LocalBusinessPlaceShiftRelationshipEntity.cs b/Src/Dlhis.Middle/Entities/LocalBusinessPlaceShiftRelationshipEntity.cs new file mode 100644 index 00000000..bbebf3ea --- /dev/null +++ b/Src/Dlhis.Middle/Entities/LocalBusinessPlaceShiftRelationshipEntity.cs @@ -0,0 +1,9 @@ +using Dlhis.Entity.Basic.Entity; + +namespace Dlhis.Middle.Entities +{ + public class LocalBusinessPlaceShiftRelationshipEntity : BusinessPlaceShiftRelationshipEntity + { + public new int Id { get; set; } + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Entities/LocalBusinessShiftEntity.cs b/Src/Dlhis.Middle/Entities/LocalBusinessShiftEntity.cs new file mode 100644 index 00000000..69c61e8d --- /dev/null +++ b/Src/Dlhis.Middle/Entities/LocalBusinessShiftEntity.cs @@ -0,0 +1,9 @@ +using Dlhis.Entity.Basic.Entity; + +namespace Dlhis.Middle.Entities +{ + public class LocalBusinessShiftEntity : BusinessShiftEntity + { + public new int Id { get; set; } + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Entities/LocalBusinessShiftMealRelationshipEntity.cs b/Src/Dlhis.Middle/Entities/LocalBusinessShiftMealRelationshipEntity.cs new file mode 100644 index 00000000..eed8f859 --- /dev/null +++ b/Src/Dlhis.Middle/Entities/LocalBusinessShiftMealRelationshipEntity.cs @@ -0,0 +1,8 @@ +using Dlhis.Entity.Basic.Entity; + +namespace Dlhis.Middle.Entities; + +public class LocalBusinessShiftMealRelationshipEntity : BusinessShiftMealRelationshipEntity +{ + public new int Id { get; set; } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Entities/LocalCanteenSettingEntity.cs b/Src/Dlhis.Middle/Entities/LocalCanteenSettingEntity.cs new file mode 100644 index 00000000..c1331ed6 --- /dev/null +++ b/Src/Dlhis.Middle/Entities/LocalCanteenSettingEntity.cs @@ -0,0 +1,9 @@ +using Dlhis.Entity.Basic.Entity; + +namespace Dlhis.Middle.Entities +{ + public class LocalCanteenSettingEntity : CanteenSettingEntity + { + public new int Id { get; set; } + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Entities/LocalDepartmentEntity.cs b/Src/Dlhis.Middle/Entities/LocalDepartmentEntity.cs new file mode 100644 index 00000000..6ccfe180 --- /dev/null +++ b/Src/Dlhis.Middle/Entities/LocalDepartmentEntity.cs @@ -0,0 +1,9 @@ +using Dlhis.Entity.Basic.Entity; + +namespace Dlhis.Middle.Entities +{ + public class LocalDepartmentEntity : DepartmentEntity + { + public new int Id { get; set; } + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Entities/LocalDeviceEntity.cs b/Src/Dlhis.Middle/Entities/LocalDeviceEntity.cs new file mode 100644 index 00000000..e019ecac --- /dev/null +++ b/Src/Dlhis.Middle/Entities/LocalDeviceEntity.cs @@ -0,0 +1,9 @@ +using Dlhis.Entity.Basic.Entity; + +namespace Dlhis.Middle.Entities +{ + public class LocalDeviceEntity : DeviceEntity + { + public new int Id { get; set; } + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Entities/LocalDeviceRegistryEntity.cs b/Src/Dlhis.Middle/Entities/LocalDeviceRegistryEntity.cs new file mode 100644 index 00000000..9328dffd --- /dev/null +++ b/Src/Dlhis.Middle/Entities/LocalDeviceRegistryEntity.cs @@ -0,0 +1,9 @@ +using Dlhis.Entity.Device.Entities; + +namespace Dlhis.Middle.Entities +{ + public class LocalDeviceRegistryEntity : DeviceRegistryEntity + { + public new int Id { get; set; } + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Entities/LocalGainfoEntity.cs b/Src/Dlhis.Middle/Entities/LocalGainfoEntity.cs new file mode 100644 index 00000000..74d160de --- /dev/null +++ b/Src/Dlhis.Middle/Entities/LocalGainfoEntity.cs @@ -0,0 +1,9 @@ +using Dlhis.Entity.Basic.Entity; + +namespace Dlhis.Middle.Entities +{ + public class LocalGainfoEntity : GainfoEntity + { + public new int Id { get; set; } + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs b/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs new file mode 100644 index 00000000..e21572d2 --- /dev/null +++ b/Src/Dlhis.Middle/Entities/LocalGainfoMealEntity.cs @@ -0,0 +1,9 @@ +using Dlhis.Entity.Basic.Entity; + +namespace Dlhis.Middle.Entities +{ + public class LocalGainfoMealEntity : GainfoMealEntity + { + public new int Id { get; set; } + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Entities/LocalOrderEntity.cs b/Src/Dlhis.Middle/Entities/LocalOrderEntity.cs new file mode 100644 index 00000000..b61312ee --- /dev/null +++ b/Src/Dlhis.Middle/Entities/LocalOrderEntity.cs @@ -0,0 +1,50 @@ +using Dlhis.Entity.Canteen.Enums; +using Kurisu.AspNetCore.DataAccess.SqlSugar; +using SqlSugar; + +namespace Dlhis.Middle.Entities; + +[SugarTable(TableName = "LocalOrders")] +public class LocalOrderEntity : SugarBaseEntity +{ + + public new int Id { get; set; } + + /// + /// 是否离线数据 + /// + public bool IsOffline { get; set; } + + /// + /// 消费机器唯一信息 + /// + public string DeviceUniqueNo { get; set; } + + /// + /// 第三方订单号 + /// + public string ThirdNo { get; set; } + + /// + /// 档案号/QrCode/学工号 + /// + public string UserUniqueNo { get; set; } + + /// + /// 来源 + /// + public BalanceRecordSourceEnumType Source { get; set; } + + /// + /// 金额 + /// + public decimal Amount { get; set; } + + /// + /// 产生时间 + /// + public DateTime CostDateTime { get; set; } + + + public bool IsSync { get; set; } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Packs/SqlSugarPack.cs b/Src/Dlhis.Middle/Packs/SqlSugarPack.cs new file mode 100644 index 00000000..19e3ff32 --- /dev/null +++ b/Src/Dlhis.Middle/Packs/SqlSugarPack.cs @@ -0,0 +1,150 @@ +using Dlhis.Entity.Basic.Entity; +using Dlhis.Entity.Canteen.Entities; +using Dlhis.Entity.Device.Entities; +using Dlhis.Middle.Entities; +using Kurisu.AspNetCore.Authentication.Abstractions; +using Kurisu.AspNetCore.DataAccess.Entity; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Attributes; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Options; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; +using Kurisu.AspNetCore.Startup; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using SqlSugar; + +namespace Dlhis.Middle.Packs; + +public class SqlSugarPack : BaseAppPack +{ + private const string _infoTemplate = "ExecuteCommand[{ms}] Timeout[{timeout}] \r\n {sql}"; + + public override void ConfigureServices(IServiceCollection services) + { + services.AddSqlSugar(); + services.Replace(ServiceDescriptor.Scoped(typeof(ISqlSugarClient), sp => + { + var options = sp.GetService>().Value; + var logger = sp.GetService>(); + var currentUser = sp.GetService(); + var sugarOptions = sp.GetService(); + + var configs = new List + { + new() + { + ConfigId = "db-main", + + ConnectionString = options.DefaultConnectionString, + DbType = DbType.Sqlite, + InitKeyType = InitKeyType.Attribute, + IsAutoCloseConnection = true, + + MoreSettings = new ConnMoreSettings + { + DisableNvarchar = true, + }, + ConfigureExternalServices = new ConfigureExternalServices + { + EntityService = (c, p) => + { + if (!p.IsPrimarykey && c.PropertyType.IsGenericType && c.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) + || (!p.IsPrimarykey && p.PropertyInfo.PropertyType == typeof(string))) + { + p.IsNullable = true; + } + } + } + } + }; + + ISqlSugarClient db = new SqlSugarClient(configs); + + db.Ado.CommandTimeOut = options.Timeout; + + db.Aop.OnLogExecuted = (sql, parameters) => + { + var ms = db.Ado.SqlExecutionTime.Milliseconds; + + //警告慢sql + if (ms >= options.SlowSqlTime * 1000) + logger.LogWarning(message: _infoTemplate, ms, options.Timeout, sql); + + if (options.EnableSqlLog) + { + sql = UtilMethods.GetSqlString(DbType.MySql, sql, parameters); + logger.LogInformation(message: _infoTemplate, ms, options.Timeout, sql); + } + }; + + //软删除 + db.QueryFilter.AddTableFilter(x => x.IsDeleted == false); + + + //增删改 + db.Aop.DataExecuting = (oldValue, model) => + { + if (sugarOptions.IgnoreTenant == false //启用租户 + && currentUser != null //用户信息存在 + && model.PropertyName == nameof(ITenantId.TenantId) //当前为租户字段 + && typeof(ITenantId).IsAssignableFrom(model.EntityValue.GetType())) //继承ITenantId + { + var tenant = currentUser.GetStringTenantId(); + model.SetValue(tenant); + } + + //处理人员和时间字段 + switch (model.OperationType) + { + case DataFilterType.InsertByObject: + { + if (model.IsAnyAttribute()) + { + model.SetValue(DateTime.Now); + } + + if (currentUser != null && model.IsAnyAttribute()) + { + if ((int)model.EntityColumnInfo.PropertyInfo.GetValue(model.EntityValue) == 0) + { + model.SetValue(currentUser.GetStringSubjectId()); + } + } + + break; + } + case DataFilterType.UpdateByObject: + { + if (model.IsAnyAttribute()) + { + model.SetValue(DateTime.Now); + } + + if (currentUser != null && model.IsAnyAttribute()) + { + if ((int)model.EntityColumnInfo.PropertyInfo.GetValue(model.EntityValue) == 0) + { + model.SetValue(currentUser.GetStringSubjectId()); + } + } + + break; + } + } + }; + + return db; + })); + } + + public override void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + using var scope = app.ApplicationServices.CreateScope(); + var db = scope.ServiceProvider.GetService(); + + db.Client.DbMaintenance.CreateDatabase(); + db.Client.CodeFirst.InitTables(); + db.Client.CodeFirst.InitTables(); + db.Client.CodeFirst.InitTables(); + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Program.cs b/Src/Dlhis.Middle/Program.cs new file mode 100644 index 00000000..9d87298e --- /dev/null +++ b/Src/Dlhis.Middle/Program.cs @@ -0,0 +1,64 @@ +using Dlhis.Entity.Basic.Entity; +using Dlhis.Entity.Device.Entities; +using Dlhis.Middle.Entities; +using Dlhis.Middle.Services; +using Dlhis.Middle.Services.Implements; +using Kurisu.AspNetCore.Startup; +using Microsoft.IdentityModel.Logging; + +namespace Dlhis.Middle; + +class Program +{ + static void Main(string[] args) + { + KurisuHost.Run(args); + } +} + +public class Startup : DefaultStartup +{ + public Startup(IConfiguration configuration) : base(configuration) + { + IdentityModelEventSource.ShowPII = true; + } + + public override void ConfigureServices(IServiceCollection services) + { + base.ConfigureServices(services); + services.AddHttpClient("face").ConfigurePrimaryHttpMessageHandler(() => + { + var handler = new HttpClientHandler + { + ClientCertificateOptions = ClientCertificateOption.Manual, + ServerCertificateCustomValidationCallback = (_, _, _, _) => true + }; + + return handler; + }); + services.AddHostedService(); + services.AddMemoryCache(); + } + + /// + public override void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + base.Configure(app, env); + + using var scope = app.ApplicationServices.CreateScope(); + var factory = Configuration.GetSection("Factory").Value; + + var ds = scope.ServiceProvider.GetService(); + ds.TruncateAsync().Wait(); + ds.DownloadAsync(factory).Wait(); + ds.DownloadAsync(factory).Wait(); + ds.DownloadAsync(factory).Wait(); + ds.DownloadAsync(factory).Wait(); + ds.DownloadAsync(factory).Wait(); + ds.DownloadAsync(factory).Wait(); + ds.DownloadAsync(factory).Wait(); + + ds.DownloadAsync(factory).Wait(); + ds.DownloadAsync(factory).Wait(); + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Properties/launchSettings.json b/Src/Dlhis.Middle/Properties/launchSettings.json new file mode 100644 index 00000000..7f14b6a8 --- /dev/null +++ b/Src/Dlhis.Middle/Properties/launchSettings.json @@ -0,0 +1,37 @@ +{ + "profiles": { + "Dlhis.Middle": { + "commandName": "Project", + "launchUrl": "weatherforecast", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5027" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchUrl": "weatherforecast", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Docker": { + "commandName": "Docker", + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/weatherforecast", + "environmentVariables": { + "ASPNETCORE_URLS": "http://+:80" + }, + "publishAllPorts": true + } + }, + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:1982", + "sslPort": 0 + } + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Request/ConsumeRequest.cs b/Src/Dlhis.Middle/Request/ConsumeRequest.cs new file mode 100644 index 00000000..ffb6ca7b --- /dev/null +++ b/Src/Dlhis.Middle/Request/ConsumeRequest.cs @@ -0,0 +1,44 @@ +using Dlhis.Entity.Canteen.Enums; + +namespace Dlhis.Middle.Request; + +public class ConsumeRequest +{ + /// + /// 是否离线数据 + /// + public bool IsOffline { get; set; } + + /// + /// 消费机器唯一信息 + /// + public string DeviceUniqueNo { get; set; } + + /// + /// 第三方订单号 + /// + public string ThirdNo { get; set; } + + /// + /// 档案号/QrCode/学工号 + /// + public string UserUniqueNo { get; set; } + + /// + /// 来源 + /// + public BalanceRecordSourceEnumType Source { get; set; } + + /// + /// 金额 + /// + public decimal Amount { get; set; } + + /// + /// 产生时间 + /// + public DateTime CostDateTime { get; set; } + + + public string Image { get; set; } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Request/OrderAddRequest.cs b/Src/Dlhis.Middle/Request/OrderAddRequest.cs new file mode 100644 index 00000000..a8cc25b4 --- /dev/null +++ b/Src/Dlhis.Middle/Request/OrderAddRequest.cs @@ -0,0 +1,63 @@ +using System.ComponentModel.DataAnnotations; +using Dlhis.Entity.Canteen.Enums; +using Kurisu.AspNetCore.Utils.Extensions; + +namespace Dlhis.Middle.Request; + +public class OrderAddRequest : IValidatableObject +{ + /// + /// 是否离线数据 + /// + public bool IsOffline { get; set; } = false; + + /// + /// 消费机器唯一信息 + /// + public string DeviceUniqueNo { get; set; } + + /// + /// 第三方订单号 + /// + public string ThirdNo { get; set; } + + /// + /// 档案号/QrCode/学工号 + /// + [Required(ErrorMessage = "人员档案号/QrCode/学工号/userId 不能为空")] + public string UserUniqueNo { get; set; } + + /// + /// 来源 + /// + [EnumDataType(typeof(BalanceRecordSourceEnumType))] + public BalanceRecordSourceEnumType Source { get; set; } + + /// + /// 金额 + /// + [Range(0.01, double.MaxValue)] + public decimal Amount { get; set; } + + /// + /// 产生时间 + /// + public DateTime? CostDateTime { get; set; } + + + public string Image { get; set; } + + + public IEnumerable Validate(ValidationContext validationContext) + { + //处理金额 + Amount = -Math.Abs(Amount); + + CostDateTime ??= DateTime.Now; + + if (DeviceUniqueNo.IsEmpty()) + { + yield return new ValidationResult("设备号不能为空", new[] { nameof(DeviceUniqueNo) }); + } + } +} diff --git a/Src/Dlhis.Middle/Response/CanteenSettingResponse.cs b/Src/Dlhis.Middle/Response/CanteenSettingResponse.cs new file mode 100644 index 00000000..388d0e4c --- /dev/null +++ b/Src/Dlhis.Middle/Response/CanteenSettingResponse.cs @@ -0,0 +1,44 @@ +using Dlhis.Entity.Canteen.Enums; +using Kurisu.AspNetCore.Utils.Extensions; + +namespace Dlhis.Middle.Response; + +public class CanteenSettingResponse +{ + + public long Id { get; set; } + + public CanteenSettingUnqiueEnumType UniqueType { get; set; } + public string UniqueTypeDesc => UniqueType.GetDescription(); + + /// + /// 唯一值 + /// + public string UniqueValue { get; set; } + + /// + /// 唯一值说明 + /// + public string UniqueValueDesc { get; set; } + + /// + /// 分类 + /// + public string Catalog { get; set; } + + /// + /// 参数 + /// + public CanteenSettingItemEnumType Item { get; set; } + public string ItemDesc => Item.GetDescription(); + + /// + /// 值 + /// + public string Value { get; set; } + + /// + /// 是否启用 + /// + public bool Enable { get; set; } +} diff --git a/Src/Dlhis.Middle/Response/OrderResultResponse.cs b/Src/Dlhis.Middle/Response/OrderResultResponse.cs new file mode 100644 index 00000000..86c95218 --- /dev/null +++ b/Src/Dlhis.Middle/Response/OrderResultResponse.cs @@ -0,0 +1,35 @@ +using Newtonsoft.Json; + +namespace Dlhis.Middle.Response; + +public class OrderResultResponse +{ + /// + /// 用户工号 + /// + public string UserCode { get; set; } + + /// + /// 用户名称 + /// + public string UserName { get; set; } + + /// + /// 部门号 + /// + public string DepartmentNo { get; set; } + + /// + /// 部门名称 + /// + public string DepartmentName { get; set; } + + + [JsonIgnore] + public decimal CurrentBalance { get; set; } + + /// + /// 余额 + /// + public string Balance => CurrentBalance.ToString("f2"); +} diff --git a/Src/Dlhis.Middle/Response/TokenResponse.cs b/Src/Dlhis.Middle/Response/TokenResponse.cs new file mode 100644 index 00000000..1e2d84d0 --- /dev/null +++ b/Src/Dlhis.Middle/Response/TokenResponse.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace Dlhis.Middle.Response; + +public class TokenResponse +{ + [JsonProperty("access_token")] + public string AccessToken { get; set; } + + [JsonProperty("token_type")] + public string TokenType { get; set; } + + /// + /// + /// + [JsonProperty("scope")] + public string Scope { get; set; } + + + [JsonProperty("expires_in")] + public int Expires { get; set; } +} diff --git a/Src/Dlhis.Middle/Services/ICanteenSettingService.cs b/Src/Dlhis.Middle/Services/ICanteenSettingService.cs new file mode 100644 index 00000000..2de8d343 --- /dev/null +++ b/Src/Dlhis.Middle/Services/ICanteenSettingService.cs @@ -0,0 +1,18 @@ +using Dlhis.Entity.Canteen.Enums; + +namespace Dlhis.Middle.Services; + +public interface ICanteenSettingService : IScopeDependency +{ + /// + /// 获取人员的就餐配置信息 + /// + /// 就餐人员档案号 + /// + /// + Task> GetUserSettingAsync(string userNo, string tenantId); + + + Task GetBusinessSwitchTimeAsync(string tenantId); +} + diff --git a/Src/Dlhis.Middle/Services/IDataDownloadService.cs b/Src/Dlhis.Middle/Services/IDataDownloadService.cs new file mode 100644 index 00000000..b80ed994 --- /dev/null +++ b/Src/Dlhis.Middle/Services/IDataDownloadService.cs @@ -0,0 +1,8 @@ +namespace Dlhis.Middle.Services; + +public interface IDataDownloadService : IScopeDependency +{ + Task DownloadAsync(string factory) where T : class, new(); + + Task TruncateAsync(); +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/IHealthCheckService.cs b/Src/Dlhis.Middle/Services/IHealthCheckService.cs new file mode 100644 index 00000000..e8c05049 --- /dev/null +++ b/Src/Dlhis.Middle/Services/IHealthCheckService.cs @@ -0,0 +1,8 @@ +namespace Dlhis.Middle.Services; + +public interface IHealthCheckService : ISingletonDependency +{ + bool GetIsOffline(); + + Task DoHealthCheckAsync(); +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/IOrderService.cs b/Src/Dlhis.Middle/Services/IOrderService.cs new file mode 100644 index 00000000..fb5ae382 --- /dev/null +++ b/Src/Dlhis.Middle/Services/IOrderService.cs @@ -0,0 +1,10 @@ +using Dlhis.Middle.Request; +using Dlhis.Middle.Response; +using Kurisu.AspNetCore.UnifyResultAndValidation; + +namespace Dlhis.Middle.Services; + +public interface IOrderService : IScopeDependency +{ + Task> AddAsync(OrderAddRequest request); +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/IRemoteOrderService.cs b/Src/Dlhis.Middle/Services/IRemoteOrderService.cs new file mode 100644 index 00000000..e161d9ad --- /dev/null +++ b/Src/Dlhis.Middle/Services/IRemoteOrderService.cs @@ -0,0 +1,12 @@ +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 AbleRemoteCallAsync(); +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/CanteenSettingService.cs b/Src/Dlhis.Middle/Services/Implements/CanteenSettingService.cs new file mode 100644 index 00000000..e6a16149 --- /dev/null +++ b/Src/Dlhis.Middle/Services/Implements/CanteenSettingService.cs @@ -0,0 +1,104 @@ +using Dlhis.Entity.Basic.Entity; +using Dlhis.Entity.Canteen.Enums; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; +using Mapster; + +namespace Dlhis.Middle.Services.Implements; + +/// +/// 餐饮配置 +/// +public class CanteenSettingService : ICanteenSettingService +{ + private readonly IDbContext _context; + + public CanteenSettingService(IDbContext context) + { + _context = context; + } + + public async Task> GetUserSettingAsync(string userNo, string tenantId) + { + var settings = await _context.Queryable() + .Where(x => x.Enable) + .Where(x => x.TenantId == tenantId) + .Where(x => x.UniqueType == CanteenSettingUnqiueEnumType.Person && x.UniqueValue == userNo || x.UniqueType == CanteenSettingUnqiueEnumType.Tenant) + .ToListAsync(); + + //从基地 -> 个人,确保下面循环做到覆盖,并且以个人配置为主 + settings = settings.OrderBy(x => x.UniqueType).ToList(); + + var result = new Dictionary(); + settings.ForEach(x => { result[x.Item] = x.Value; }); + + return result; + } + + + public async Task GetBusinessSwitchTimeAsync(string tenantId) + { + var setting = await _context.Queryable() + .Where(x => x.Enable) + .Where(x => x.TenantId == tenantId) + .Where(x => x.Item == CanteenSettingItemEnumType.BusinessSwitchTime) + .FirstAsync(); + + try + { + return TimeOnly.Parse(setting.Value); + } + catch (Exception) + { + return null; + } + } + + //*********************************************** help methods ***************************************************// + + + + /// + /// 检验参数 + /// + /// + /// + /// + /// + private static bool IsValidFormat(string value, CanteenSettingItemEnumType? type = null) + { + try + { + //判断0/1 + if (type == CanteenSettingItemEnumType.AllowSendAtMeal || + type == CanteenSettingItemEnumType.AllowRepeatInOneShift || + type == CanteenSettingItemEnumType.AllowNoBalance || + type == CanteenSettingItemEnumType.AllowCrossTenant) + { + var intvaule = int.Parse(value); + if (intvaule == 0 || intvaule == 1) + { + return true; + } + else + { + return false; + } + } + + if (type == CanteenSettingItemEnumType.BusinessSwitchTime) + { + var timeOnly = value.Adapt(); + return timeOnly.Minute == 0 && timeOnly.Second == 0; + } + + //mapster + value.Adapt(); + } + catch (Exception) + { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/DataDownloadService.cs b/Src/Dlhis.Middle/Services/Implements/DataDownloadService.cs new file mode 100644 index 00000000..8450d253 --- /dev/null +++ b/Src/Dlhis.Middle/Services/Implements/DataDownloadService.cs @@ -0,0 +1,85 @@ +using System.Net.Http.Headers; +using System.Reflection; +using System.Text; +using Dlhis.Middle.Entities; +using Dlhis.Middle.Response; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; +using Kurisu.AspNetCore.UnifyResultAndValidation; +using Kurisu.AspNetCore.Utils.Extensions; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Caching.Memory; +using Newtonsoft.Json; +using SqlSugar; + +namespace Dlhis.Middle.Services.Implements; + +public class DataDownloadService : IDataDownloadService +{ + private readonly IHttpClientFactory _httpClientFactory; + private readonly IMemoryCache _memoryCache; + private readonly IDbContext _dbContext; + private readonly string _hostName; + + public DataDownloadService(IHttpClientFactory httpClientFactory, IMemoryCache memoryCache, IConfiguration configuration, IDbContext dbContext) + { + _httpClientFactory = httpClientFactory; + _memoryCache = memoryCache; + _dbContext = dbContext; + _hostName = configuration.GetSection("HostName").Value; + } + + 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 requestUrl = QueryHelpers.AddQueryString($"https://{_hostName}/api/common/SysData/download", new Dictionary + { + ["factory"] = factory, + ["tableName"] = typeof(T).GetCustomAttribute()!.TableName + }); + + var response = await httpClient.GetStringAsync(requestUrl); + var pack = response.ToObject>>(); + + await _dbContext.InsertAsync(pack.Data); + } + + /// + public async Task TruncateAsync() + { + await _dbContext.Client.Deleteable().ExecuteCommandAsync(); + await _dbContext.Client.Deleteable().ExecuteCommandAsync(); + await _dbContext.Client.Deleteable().ExecuteCommandAsync(); + await _dbContext.Client.Deleteable().ExecuteCommandAsync(); + await _dbContext.Client.Deleteable().ExecuteCommandAsync(); + await _dbContext.Client.Deleteable().ExecuteCommandAsync(); + await _dbContext.Client.Deleteable().ExecuteCommandAsync(); + 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/HealthCheckBackgroundService.cs b/Src/Dlhis.Middle/Services/Implements/HealthCheckBackgroundService.cs new file mode 100644 index 00000000..bc658f64 --- /dev/null +++ b/Src/Dlhis.Middle/Services/Implements/HealthCheckBackgroundService.cs @@ -0,0 +1,19 @@ +namespace Dlhis.Middle.Services.Implements; + +public class HealthCheckBackgroundService : BackgroundService +{ + private readonly IHealthCheckService _healthCheckService; + + public HealthCheckBackgroundService(IHealthCheckService healthCheckService) + { + _healthCheckService = healthCheckService; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + await _healthCheckService.DoHealthCheckAsync(); + } + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/HealthcheckService.cs b/Src/Dlhis.Middle/Services/Implements/HealthcheckService.cs new file mode 100644 index 00000000..117ca886 --- /dev/null +++ b/Src/Dlhis.Middle/Services/Implements/HealthcheckService.cs @@ -0,0 +1,65 @@ +using System.Net.Sockets; + +namespace Dlhis.Middle.Services.Implements; + +public class HealthCheckService : IHealthCheckService +{ + private bool _isOffline = true; + private readonly string _hostName; + private readonly ILogger _logger; + private int _successRetry; + private int _retry; + + public HealthCheckService(ILogger logger, IConfiguration configuration) + { + _logger = logger; + _hostName = configuration.GetSection("HostName").Value; + } + + public bool GetIsOffline() + { + return _isOffline; + } + + public async Task DoHealthCheckAsync() + { + var tcpClient = new TcpClient(); + try + { + _logger.LogInformation("tcp connecting {host} {port}", _hostName, 443); + await tcpClient.ConnectAsync(_hostName, 443); + _retry++; + _logger.LogInformation("retry {retry}", _retry); + if (tcpClient.Connected) + { + _successRetry++; + _logger.LogInformation("success connected {count}", _successRetry); + if (_successRetry == 4) + { + _isOffline = false; + _successRetry = 0; + _retry = 0; + _logger.LogInformation("current is {status}", "healthy"); + } + else + { + _logger.LogInformation("wait next check in {waitTime}", _successRetry * 1000); + await Task.Delay(_successRetry * 1000); + } + } + else + { + _successRetry = 0; + _isOffline = true; + _logger.LogInformation("current is {status}", "unavailable"); + await Task.Delay(2000); + } + } + catch (Exception ex) + { + _logger.LogError("fail 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 new file mode 100644 index 00000000..ca86c4e2 --- /dev/null +++ b/Src/Dlhis.Middle/Services/Implements/OrderService.cs @@ -0,0 +1,320 @@ +using Dlhis.Entity.Basic.Entity; +using Dlhis.Entity.Canteen.Entities; +using Dlhis.Entity.Canteen.Enums; +using Dlhis.Entity.Device.Entities; +using Kurisu.AspNetCore.Utils; +using Kurisu.AspNetCore.Utils.Extensions; +using Mapster; +using Kurisu.AspNetCore.CustomClass; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; +using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; +using Kurisu.AspNetCore.UnifyResultAndValidation; +using Dlhis.Middle.Response; +using Dlhis.Middle.Request; + +namespace Dlhis.Middle.Services.Implements; + +public class OrderService : IOrderService +{ + private readonly ICanteenSettingService _canteenSettingService; + private readonly IRemoteOrderService _remoteOrderService; + private readonly string _factory; + private readonly IDbContext _dbContext; + private Dictionary _settings; + + public OrderService(ICanteenSettingService canteenSettingService, + IRemoteOrderService remoteOrderService, + IConfiguration configuration, + IDbContext dbContext) + { + _canteenSettingService = canteenSettingService; + _remoteOrderService = remoteOrderService; + _factory = configuration.GetSection("Factory").Value; + _dbContext = dbContext; + } + + public async Task> AddAsync(OrderAddRequest request) + { + if (await _remoteOrderService.AbleRemoteCallAsync()) + { + return await _remoteOrderService.AddAsync(request); + } + + //尝试匹配userId + _ = long.TryParse(request.UserUniqueNo, out var userId); + + //根据档案号 + var gainfo = await _dbContext.Queryable() + .Where(x => x.Code == request.UserUniqueNo || x.Id == userId || x.CardNo == request.UserUniqueNo) + .FirstAsync(); + + var message = string.Empty; + + gainfo.ThrowIfNull("无消费权限"); + + try + { + (await _dbContext.Queryable().Where(x => x.UserNo == gainfo.No).AnyAsync()) + .ThrowIfFalse("余额不足,无法消费"); + + await ConsumeAsync(request.Adapt(), gainfo); + } + catch (Exception ex) + { + message = ex.Message; + } + + + //获取人员余额 + var data = await _dbContext.Queryable() + .LeftJoin((ga, department) => ga.DepartmentNo == department.No) + .LeftJoin((ga, department, mealUser) => ga.No == mealUser.UserNo) + .Where(ga => ga.Id == gainfo.Id) + .Select((ga, department, mealUser) => new OrderResultResponse + { + UserCode = ga.Code, + UserName = ga.Name, + DepartmentNo = ga.DepartmentNo, + DepartmentName = department.Name, + CurrentBalance = mealUser.Balance + }) + .SingleAsync(); + + return new DefaultApiResult + { + Code = message.IsPresent() ? ApiStateCode.Error : ApiStateCode.Success, + Data = data, + Msg = message, + }; + } + + + /// + /// 消费 + /// + /// + /// + /// + private async Task ConsumeAsync(ConsumeRequest request, GainfoEntity gainfo) + { + var registry = await _dbContext.Queryable().Where(x => x.DeviceUniqueNo == request.DeviceUniqueNo).SingleAsync(); + registry.ThrowIfNull("设备尚未登记"); + + var device = await _dbContext.Queryable().Where(x => x.UniqueNo == request.DeviceUniqueNo).SingleAsync(); + device.ThrowIfNull("设备尚未登记"); + device.Enable.ThrowIfFalse("设备已被停用"); + + var businessPlaceInfo = await _dbContext.Queryable() + .LeftJoin((x, sr) => x.Code == sr.PlaceCode) + .Where(x => x.Code == registry.BusinessPlaceCode) + .Select((x, sr) => new + { + x.Code, + sr.ShiftCode, + x.TenantId + }) + .FirstAsync(); + businessPlaceInfo.ThrowIfNull("设备尚未登记营业点"); + + //获取当前消费餐次 + var shifts = await _dbContext.Queryable().Where(x => x.ShiftCode == businessPlaceInfo.ShiftCode).ToListAsync(); + shifts.ThrowIfEmpty("营业点餐次尚未设置"); + + var shift = shifts.FirstOrDefault(x => new TimePeriod + { + Start = x.Start, + End = x.End + }.IsPresent1(request.CostDateTime)); + shift.ThrowIfNull("非就餐时间,无法消费"); + + var costDate = await GetCostDateAsync(shift, request.CostDateTime); + + var balanceRecord = new BalanceRecordEntity + { + // RequestJson = request.ToJson(), + // ThirdNo = request.ThirdNo, + Type = BalanceRecordTypeEnumType.Out, + ApplicationType = CanteenApplicationTypeEnumType.Device, + Name = gainfo.Name, + Source = request.Source, + UserNo = gainfo.No, + UserCode = gainfo.Code, + Amount = request.Amount, + BusinessPlaceCode = businessPlaceInfo?.Code, + BusinessShiftCode = shift?.ShiftCode, + ShiftType = shift?.ShiftType, + CostDate = costDate.Date, + CostId = gainfo.CostId, + DepartmentNo = gainfo.DepartmentNo, + DeviceUniqueNo = request.DeviceUniqueNo, + + No = _factory + "-" + SnowFlakeHelper.Instance.NextId(), + SubsidiesFromMonth = request.CostDateTime.ToString("yyyyMM"), + // IsOffline = request.IsOffline, + IsCostCrossTenant = await GetSettingAsync(businessPlaceInfo!.TenantId, CanteenSettingItemEnumType.AllowCrossTenant), + + OriginTenantId = gainfo.Factory, + Werks = gainfo.Werks, + + //餐别异常处理 + Status = shift == null + ? BalanceRecordStatusEnumType.Doubtful + : BalanceRecordStatusEnumType.Valid, + + //OrderStatus = CanteenOrderStatusEnumType.Wait, + + CostDateTime = request.CostDateTime, + + //标识当前消费的基地 + TenantId = businessPlaceInfo.TenantId, + }; + + //校验 + await Verify(balanceRecord); + + var allowNoBalance = await GetSettingAsync(balanceRecord.TenantId, CanteenSettingItemEnumType.AllowNoBalance); + + await CommonHelper.RetryAsync(async () => + { + if (!allowNoBalance) + { + var gainfoMeal = await _dbContext.Queryable().Where(x => x.UserNo == balanceRecord.UserNo).SingleAsync(); + //金额+- + gainfoMeal.Balance += balanceRecord.Amount; + + (gainfoMeal.Balance < 0).ThrowIfTrue("余额不足,无法消费"); + + await _dbContext.Updateable() + .SetColumns(x => x.Balance == gainfoMeal.Balance) + .SetColumns(x => x.Version == x.Version + 1) + .SetColumns(x => x.ModifiedTime == DateTime.Now) + .Where(x => x.UserNo == balanceRecord.UserNo && x.Version == gainfoMeal.Version) //必须与查询时的version一致 + .ExecuteCommandThrowVersionAsync(); + } + + await _dbContext.InsertAsync(balanceRecord); + }, 5); + } + + + //******************************************************* help methods ************************************************************/ + + /// + /// 获取成本日期 + /// + /// + /// + /// + private async Task GetCostDateAsync(BusinessShiftMealRelationshipEntity shift, DateTime costDateTime) + { + var switchTime = await GetSettingAsync(shift.TenantId, CanteenSettingItemEnumType.BusinessSwitchTime); + + var costDate = costDateTime.Date; + + if (switchTime.HasValue) + { + var beginOfDay = costDateTime.Date; + + var setTime = beginOfDay.Add(switchTime.Value.ToTimeSpan()); + if (costDateTime < setTime) + costDate = costDateTime.Date.AddDays(-1); + } + + return costDate; + } + + + /// + /// 验证参数 + /// + /// + /// + /// + private async Task Verify(BalanceRecordEntity order) + { + var beginOfDay = DateTime.Today; + var endOfDay = beginOfDay.AddDays(1).AddSeconds(-1); + + //今天所有消费记录 + var todayAllBalanceRecords = await _dbContext.Queryable() + .Where(x => x.UserNo == order.UserNo) + .Where(x => x.Type == BalanceRecordTypeEnumType.Out) + .Where(x => x.Status == BalanceRecordStatusEnumType.Valid) + .Where(x => x.CreateTime >= beginOfDay && x.CreateTime <= endOfDay) + .ToListAsync(); + + //超市营业点 + var supermarketCodes = await _dbContext.Queryable() + .Where(x => x.Catalog == CanteenBusinessPlaceCatalogEnumType.Supermarket) + .Select(x => x.Code) + .ToListAsync(); + + //一天消费金额 + var dayCostMax = await GetSettingAsync(order.TenantId, CanteenSettingItemEnumType.DayCostMax); + if (dayCostMax != default) + { + //must has business place code, use .Value is safe + var cost = todayAllBalanceRecords + .Where(x => !supermarketCodes.Contains(x.BusinessPlaceCode.Value)) //filter supermarket + .Sum(x => Math.Abs(x.Amount)); + (cost + Math.Abs(order.Amount) > dayCostMax).ThrowIfTrue($"超出当天消费限制{dayCostMax}元,无法消费"); + } + + //一次消费金额 + var onceCostMax = await GetSettingAsync(order.TenantId, CanteenSettingItemEnumType.OnceCostMax); + if (onceCostMax != default) + { + (Math.Abs(order.Amount) > onceCostMax).ThrowIfTrue($"单次消费金额不能超出{onceCostMax}元"); + } + + //超时消费限额 + var dayMarketCostMax = await GetSettingAsync(order.TenantId, CanteenSettingItemEnumType.DayMarketCostMax); + if (dayMarketCostMax != default) + { + //current businessplace is supermarket + if (order.BusinessPlaceCode.HasValue && supermarketCodes.Contains(order.BusinessPlaceCode.Value)) + { + var cost = todayAllBalanceRecords.Where(x => x.BusinessPlaceCode.HasValue) + .Where(x => supermarketCodes.Contains(x.BusinessPlaceCode.Value)) + .Sum(x => Math.Abs(x.Amount)); + + (cost + Math.Abs(order.Amount) > dayMarketCostMax).ThrowIfTrue($"超出当天超市消费限制{dayMarketCostMax},无法消费"); + } + } + + //重复餐次消费 + var allowRepeatInOneShift = await GetSettingAsync(order.TenantId, CanteenSettingItemEnumType.AllowRepeatInOneShift, true); + if (!allowRepeatInOneShift) + { + var costCount = todayAllBalanceRecords.Count(x => x.ShiftType == order.ShiftType); + (costCount > 1).ThrowIfTrue($"{order?.ShiftType?.GetDescription()}不允许多次消费"); + } + } + + /// + /// 获取参数配置 + /// + /// + /// + /// + /// + /// + private async Task GetSettingAsync(string tenantId, CanteenSettingItemEnumType item, T defaultValue = default) + { + _settings ??= await _canteenSettingService.GetUserSettingAsync(string.Empty, tenantId); + + if (_settings.TryGetValue(item, out var v)) + { + try + { + return int.TryParse(v, out var n) ? n.Adapt() : v.Adapt(); + } + catch (Exception) + { + return defaultValue; + } + } + + return defaultValue; + } +} \ No newline at end of file diff --git a/Src/Dlhis.Middle/Services/Implements/RemoteOrderService.cs b/Src/Dlhis.Middle/Services/Implements/RemoteOrderService.cs new file mode 100644 index 00000000..cda2a8b7 --- /dev/null +++ b/Src/Dlhis.Middle/Services/Implements/RemoteOrderService.cs @@ -0,0 +1,87 @@ +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, + 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/device", content); + + var responseJson = await response.Content.ReadAsStringAsync(); + return responseJson.ToObject>(); + } + + /// + public async Task AbleRemoteCallAsync() + { + if (await _dbContext.Queryable().AnyAsync(x => !x.IsSync)) + { + return false; + } + + 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 = "e569c2aaa98e300c", + 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/TimePeriodExtensions.cs b/Src/Dlhis.Middle/TimePeriodExtensions.cs new file mode 100644 index 00000000..21a93c43 --- /dev/null +++ b/Src/Dlhis.Middle/TimePeriodExtensions.cs @@ -0,0 +1,42 @@ +using Kurisu.AspNetCore.CustomClass; + +namespace Dlhis.Middle; + + +/// +/// 临时的扩展类 todo: 嵌入框架 +/// +public static class TimePeriodExtensions +{ + public static bool IsPresent1(this TimePeriod period, DateTime dateTime) + { + DateTime start; + DateTime end; + + if (period.IsCrossDay()) + { + //period = [22:00:00,03:00:00] + var currentStart = dateTime.Date.Add(period.Start.Value.ToTimeSpan()); + var endOfDay = dateTime.Date.AddDays(1).AddSeconds(-1); + + //assume is left range . then currentStart is 2024-04-16 22:00:00 and endOfDay is 2024-04-16 23:59:59 + if (dateTime >= currentStart && dateTime <= endOfDay) + { + start = currentStart; //2024-04-16 22:00:00 + end = dateTime.Date.AddDays(1).Add(period.End.Value.ToTimeSpan());//2024-04-17 03:00:00 + } + else + { + start = dateTime.Date.AddDays(-1).Add(period.Start.Value.ToTimeSpan()); + end = dateTime.Date.Add(period.End.Value.ToTimeSpan()); + } + } + else + { + start = dateTime.Date.Add(period.Start.Value.ToTimeSpan()); + end = dateTime.Date.Add(period.End.Value.ToTimeSpan()); + } + + return dateTime >= start && dateTime <= end; + } +}