Merge branch 'Test_IRC_Net8' into Uat_IRC_Net8
continuous-integration/drone/push Build encountered an error
Details
continuous-integration/drone/push Build encountered an error
Details
commit
d35b9a6f6f
|
@ -77,7 +77,7 @@ namespace IRaCIS.Core.Application.Service.BusinessFilter
|
|||
else if(statusCode != 200&&!(objectResult.Value is IResponseOutput))
|
||||
{
|
||||
//---程序错误,请联系开发人员。
|
||||
var apiResponse = ResponseOutput.NotOk(StaticData.International("UnifiedAPI_ProgramError"));
|
||||
var apiResponse = ResponseOutput.NotOk(I18n.T("UnifiedAPI_ProgramError"));
|
||||
|
||||
objectResult.Value = apiResponse;
|
||||
objectResult.DeclaredType = apiResponse.GetType();
|
||||
|
|
|
@ -10,25 +10,25 @@
|
|||
<PackageReference Include="AlibabaCloud.SDK.Sts20150401" Version="1.1.4" />
|
||||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
|
||||
<PackageReference Include="AWSSDK.S3" Version="3.7.402.7" />
|
||||
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.400.16" />
|
||||
<PackageReference Include="AWSSDK.S3" Version="3.7.405" />
|
||||
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.400.36" />
|
||||
<PackageReference Include="DistributedLock.Core" Version="1.0.7" />
|
||||
<PackageReference Include="DistributedLock.SqlServer" Version="1.0.5" />
|
||||
<PackageReference Include="fo-dicom" Version="5.1.3" />
|
||||
<PackageReference Include="fo-dicom.Codecs" Version="5.14.5" />
|
||||
<PackageReference Include="fo-dicom.Codecs" Version="5.15.1" />
|
||||
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.1.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
|
||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||
<PackageReference Include="Minio" Version="6.0.3" />
|
||||
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0">
|
||||
<TreatAsUsed>true</TreatAsUsed>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Panda.DynamicWebApi" Version="1.2.2" />
|
||||
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.1.1" />
|
||||
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.1.2" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.9.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -315,7 +315,7 @@ namespace IRaCIS.Core.SCP.Service
|
|||
_SCPStudyIdList.Add(scpStudyId);
|
||||
}
|
||||
|
||||
var series = await _seriesRepository.FindAsync(seriesId);
|
||||
var series = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
|
||||
|
||||
//没有缩略图
|
||||
if (series != null && string.IsNullOrEmpty(series.ImageResizePath))
|
||||
|
|
|
@ -75,9 +75,9 @@ namespace IRaCIS.Core.SCP.Service
|
|||
//using (@lock.Acquire())
|
||||
{
|
||||
var findPatient = await _patientRepository.FirstOrDefaultAsync(t => t.PatientIdStr == patientIdStr && t.TrialSiteId==trialSiteId );
|
||||
var findStudy = await _studyRepository.FindAsync(studyId);
|
||||
var findSerice = await _seriesRepository.FindAsync(seriesId);
|
||||
var findInstance = await _instanceRepository.FindAsync(instanceId);
|
||||
var findStudy = await _studyRepository.FirstOrDefaultAsync(t=>t.Id== studyId);
|
||||
var findSerice = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
|
||||
var findInstance = await _instanceRepository.FirstOrDefaultAsync(t => t.Id == instanceId);
|
||||
|
||||
DateTime? studyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.StudyDate).Add(dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.StudyTime).TimeOfDay);
|
||||
|
||||
|
@ -307,7 +307,7 @@ namespace IRaCIS.Core.SCP.Service
|
|||
await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == instanceId, u => new SCPInstance() { Path = fileRelativePath,FileSize=fileSize });
|
||||
}
|
||||
|
||||
//await _studyRepository.SaveChangesAsync();
|
||||
await _studyRepository.SaveChangesAsync();
|
||||
|
||||
return findStudy.Id;
|
||||
}
|
||||
|
|
|
@ -12,18 +12,18 @@
|
|||
"RegionId": "cn-shanghai",
|
||||
"InternalEndpoint": "https://oss-cn-shanghai-internal.aliyuncs.com",
|
||||
"EndPoint": "https://oss-cn-shanghai.aliyuncs.com",
|
||||
"AccessKeyId": "LTAI5tRRZehUp2V9pyTPtAJm",
|
||||
"AccessKeySecret": "FLizxkHsMm4CGYHtkV8E3PNJJZU7oV",
|
||||
"RoleArn": "acs:ram::1899121822495495:role/dev-oss-access",
|
||||
"BucketName": "zy-irc-uat-store",
|
||||
"ViewEndpoint": "https://zy-irc-uat-store.oss-cn-shanghai.aliyuncs.com",
|
||||
"AccessKeyId": "LTAI5tFUCCmz5TwghZHsj45Y",
|
||||
"AccessKeySecret": "8evrBy1fVfzJG25i67Jm0xqn9Xcw2T",
|
||||
"RoleArn": "acs:ram::1078130221702011:role/uat-oss-access",
|
||||
"BucketName": "tl-med-irc-uat-store",
|
||||
"ViewEndpoint": "https://tl-med-irc-uat-store.oss-cn-shanghai.aliyuncs.com",
|
||||
"Region": "oss-cn-shanghai",
|
||||
"DurationSeconds": 7200
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"RemoteNew": "Server=47.117.164.182,1434;Database=Uat_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
|
||||
"Hangfire": "Server=47.117.164.182,1434;Database=Uat_IRC.Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
|
||||
"RemoteNew": "Server=101.132.253.119,1435;Database=Uat_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
|
||||
"Hangfire": "Server101.132.253.119,1435;Database=Uat_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
|
||||
},
|
||||
"DicomSCPServiceConfig": {
|
||||
"CalledAEList": [
|
||||
|
|
|
@ -25,12 +25,12 @@ namespace EasyCaching.Demo.Interceptors.Controllers
|
|||
{
|
||||
ControllerContext.HttpContext.Response.StatusCode = 401;
|
||||
}
|
||||
|
||||
|
||||
return ResponseOutput.NotOk($"Client error, actual request error status code({code})");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
|
||||
return ResponseOutput.NotOk($"Server error , actual request error status code({code})");
|
||||
}
|
||||
|
||||
|
|
|
@ -1,43 +1,38 @@
|
|||
using System;
|
||||
using System.Net.Http;
|
||||
using IRaCIS.Application.Interfaces;
|
||||
using IRaCIS.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Auth;
|
||||
using IRaCIS.Core.Application.Filter;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using IRaCIS.Core.Application.Interfaces;
|
||||
using System.Threading.Tasks;
|
||||
using IRaCIS.Core.Application.Service;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MassTransit;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using Microsoft.Extensions.Options;
|
||||
using IRaCIS.Core.Application.Contracts;
|
||||
using LoginReturnDTO = IRaCIS.Application.Contracts.LoginReturnDTO;
|
||||
using DocumentFormat.OpenXml.Spreadsheet;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using NetTopologySuite.Algorithm;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
using AlibabaCloud.SDK.Sts20150401;
|
||||
using AlibabaCloud.SDK.Sts20150401.Models;
|
||||
using Org.BouncyCastle.Tls;
|
||||
using Amazon.SecurityToken.Model;
|
||||
using Amazon.Auth.AccessControlPolicy;
|
||||
using Amazon.SecurityToken;
|
||||
using Amazon;
|
||||
using Azure.Core;
|
||||
using IdentityModel.Client;
|
||||
using IdentityModel.OidcClient;
|
||||
using IRaCIS.Application.Contracts;
|
||||
using IRaCIS.Application.Interfaces;
|
||||
using IRaCIS.Core.Application.Auth;
|
||||
using IRaCIS.Core.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Application.Service;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using MassTransit;
|
||||
using MassTransit.Futures.Contracts;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using RestSharp;
|
||||
using RestSharp.Authenticators;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
using AssumeRoleRequest = Amazon.SecurityToken.Model.AssumeRoleRequest;
|
||||
using AutoMapper;
|
||||
using LoginReturnDTO = IRaCIS.Application.Contracts.LoginReturnDTO;
|
||||
|
||||
namespace IRaCIS.Api.Controllers
|
||||
{
|
||||
|
@ -45,7 +40,10 @@ namespace IRaCIS.Api.Controllers
|
|||
/// 医生基本信息 、工作信息 专业信息、审核状态
|
||||
/// </summary>
|
||||
[ApiController, ApiExplorerSettings(GroupName = "Reviewer")]
|
||||
public class ExtraController : ControllerBase
|
||||
public class ExtraController([FromServices] IAttachmentService attachmentService, [FromServices] IDoctorService _doctorService,
|
||||
[FromServices] IEducationService _educationService, [FromServices] ITrialExperienceService _trialExperienceService,
|
||||
|
||||
[FromServices] IResearchPublicationService _researchPublicationService, [FromServices] IVacationService _vacationService) : ControllerBase
|
||||
{
|
||||
|
||||
|
||||
|
@ -60,34 +58,40 @@ namespace IRaCIS.Api.Controllers
|
|||
/// <param name="_vacationService"></param>
|
||||
/// <param name="doctorId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet, Route("doctor/getDetail/{doctorId:guid}")]
|
||||
[HttpPost, Route("doctor/getDetail")]
|
||||
|
||||
public async Task<IResponseOutput<DoctorDetailDTO>> GetDoctorDetail([FromServices] IAttachmentService attachmentService, [FromServices] IDoctorService _doctorService,
|
||||
[FromServices] IEducationService _educationService, [FromServices] ITrialExperienceService _trialExperienceService,
|
||||
|
||||
[FromServices] IResearchPublicationService _researchPublicationService, [FromServices] IVacationService _vacationService, Guid doctorId)
|
||||
public async Task<IResponseOutput<DoctorDetailDTO>> GetDoctorDetail(GetDoctorDetailInDto inDto)
|
||||
{
|
||||
var education = await _educationService.GetEducation(doctorId);
|
||||
var education = await _educationService.GetEducation(inDto.doctorId);
|
||||
|
||||
var sowList = _doctorService.GetDoctorSowList(doctorId);
|
||||
var ackSowList = _doctorService.GetDoctorAckSowList(doctorId);
|
||||
var sowList = _doctorService.GetDoctorSowList(inDto.doctorId);
|
||||
var ackSowList = _doctorService.GetDoctorAckSowList(inDto.doctorId);
|
||||
|
||||
var doctorDetail = new DoctorDetailDTO
|
||||
{
|
||||
AuditView = await _doctorService.GetAuditState(doctorId),
|
||||
BasicInfoView = await _doctorService.GetBasicInfo(doctorId),
|
||||
EmploymentView = await _doctorService.GetEmploymentInfo(doctorId),
|
||||
AttachmentList = await attachmentService.GetAttachments(doctorId),
|
||||
|
||||
AuditView = await _doctorService.GetAuditState(inDto.doctorId),
|
||||
BasicInfoView = await _doctorService.GetBasicInfo(inDto.doctorId),
|
||||
EmploymentView = await _doctorService.GetEmploymentInfo(inDto.doctorId),
|
||||
AttachmentList = await attachmentService.GetAttachments(inDto.doctorId),
|
||||
SummarizeInfo = await _doctorService.GetSummarizeInfo(new GetSummarizeInfoInDto()
|
||||
{
|
||||
DoctorId = inDto.doctorId,
|
||||
TrialId = inDto.TrialId
|
||||
}),
|
||||
PaymentModeInfo = await _doctorService.GetPaymentMode(inDto.doctorId),
|
||||
EducationList = education.EducationList,
|
||||
PostgraduateList = education.PostgraduateList,
|
||||
|
||||
TrialExperienceView = await _trialExperienceService.GetTrialExperience(doctorId),
|
||||
ResearchPublicationView = await _researchPublicationService.GetResearchPublication(doctorId),
|
||||
TrialExperienceView = await _trialExperienceService.GetTrialExperience(new TrialExperienceModelIndto()
|
||||
{
|
||||
DoctorId = inDto.doctorId,
|
||||
TrialId = inDto.TrialId
|
||||
}),
|
||||
ResearchPublicationView = await _researchPublicationService.GetResearchPublication(inDto.doctorId),
|
||||
|
||||
SpecialtyView = await _doctorService.GetSpecialtyInfo(doctorId),
|
||||
InHoliday = (await _vacationService.OnVacation(doctorId)).IsSuccess,
|
||||
IntoGroupInfo = _doctorService.GetDoctorIntoGroupInfo(doctorId),
|
||||
SpecialtyView = await _doctorService.GetSpecialtyInfo(inDto.doctorId),
|
||||
InHoliday = (await _vacationService.OnVacation(inDto.doctorId)).IsSuccess,
|
||||
IntoGroupInfo = _doctorService.GetDoctorIntoGroupInfo(inDto.doctorId),
|
||||
SowList = sowList,
|
||||
AckSowList = ackSowList
|
||||
};
|
||||
|
@ -307,7 +311,7 @@ namespace IRaCIS.Api.Controllers
|
|||
{
|
||||
var token = _tokenService.GetToken(IRaCISClaims.Create(new UserBasicInfo()
|
||||
{
|
||||
Id = Guid.Empty,
|
||||
Id = Guid.NewGuid(),
|
||||
IsReviewer = false,
|
||||
IsAdmin = false,
|
||||
RealName = "Share001",
|
||||
|
@ -371,49 +375,6 @@ namespace IRaCIS.Api.Controllers
|
|||
}
|
||||
|
||||
|
||||
#region aliyun-net-sdk-sts 之前
|
||||
//[HttpGet("user/GenerateSTS")]
|
||||
//public IResponseOutput GenerateSTS([FromServices] IOptionsMonitor<AliyunOSSOptions> options)
|
||||
//{
|
||||
// var ossOptions = options.CurrentValue;
|
||||
|
||||
|
||||
// IClientProfile profile = DefaultProfile.GetProfile(ossOptions.regionId, ossOptions.accessKeyId, ossOptions.accessKeySecret);
|
||||
// DefaultAcsClient client = new DefaultAcsClient(profile);
|
||||
|
||||
|
||||
// // 创建一个STS请求
|
||||
// AssumeRoleRequest request = new AssumeRoleRequest
|
||||
// {
|
||||
// RoleArn = ossOptions.roleArn, // 角色ARN,需要替换为你的角色ARN
|
||||
// RoleSessionName = $"session-name-{NewId.NextGuid()}", // 角色会话名称,可自定义
|
||||
// DurationSeconds = 900, // 令牌有效期(单位:秒),这里设置为1小时
|
||||
// };
|
||||
|
||||
|
||||
// AssumeRoleResponse response = client.GetAcsResponse(request);
|
||||
|
||||
// // 返回STS令牌信息给前端
|
||||
// var stsToken = new
|
||||
// {
|
||||
// AccessKeyId = response.Credentials.AccessKeyId,
|
||||
// AccessKeySecret = response.Credentials.AccessKeySecret,
|
||||
// SecurityToken = response.Credentials.SecurityToken,
|
||||
// Expiration = response.Credentials.Expiration,
|
||||
|
||||
// Region = ossOptions.region,
|
||||
// BucketName = ossOptions.bucketName,
|
||||
// ViewEndpoint = ossOptions.viewEndpoint,
|
||||
|
||||
// };
|
||||
|
||||
// return ResponseOutput.Ok(stsToken);
|
||||
|
||||
//}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[HttpGet("User/UserRedirect")]
|
||||
|
@ -445,7 +406,47 @@ namespace IRaCIS.Api.Controllers
|
|||
|
||||
|
||||
|
||||
#region 项目支持Oauth 对接修改
|
||||
|
||||
/// <summary>
|
||||
/// 回调到前端,前端调用后端的接口
|
||||
/// 参考链接:https://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html
|
||||
/// 后端通过这个code ,带上客户端信息,和授权类型 可以向单点登录提供商,获取厂商token
|
||||
///
|
||||
/// 但是单点登录提供商提供的token 和我们系统的token 是有区别的,我们的token里面有我们业务系统的UserId,涉及到很多业务操作,所以在此出现了两种方案
|
||||
/// 1、前端使用厂商的Token。 后端通过code 获取厂商的Token 返回前端的同时返回我们系统的UserId,前段在http 请求头加上一个自定义参数,带上UserId 后端取用户Id的地方变动下,
|
||||
/// 但是除了UserId外,后端还有其他信息也是从Token取的,所以在请求头也需要带上,此外后端认证Token的方式也需要变化,改造成本稍大(如果是微服务,做这种处理还是可以的)。
|
||||
/// 2、前端还是使用我们后台自己的Token。后端通过code 获取厂商Token的同时,后端做一个隐藏登录,返回厂商的Token的同时,也返回我们系统的Token。
|
||||
/// (像我们单体,这种方式最简单,我们用单点登录,无非就是不想记多个系统的密码,自动登录而已,其他不支持的项目改造成本也是最低的)
|
||||
/// </summary>
|
||||
/// <param name="type">回调的厂商类型 比如github, google, 我们用的logto ,不同的厂商回调到前端的地址可以不同的,但是请求后端的接口可以是同一个 </param>
|
||||
/// <param name="code">在第三方平台登录成功后,回调前端的时候会返回一个code </param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("User/OAuthCallBack")]
|
||||
public async Task<IResponseOutput> OAuthCallBack(string type, string code)
|
||||
{
|
||||
#region 获取AccessTo
|
||||
|
||||
//var headerDic = new Dictionary<string, string>();
|
||||
//headerDic.Add("code", code);
|
||||
//headerDic.Add("grant_type", "authorization_code");
|
||||
//headerDic.Add("redirect_uri", "http://localhost:6100");
|
||||
//headerDic.Add("scope", "all");
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
return ResponseOutput.Ok();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region 测试获取用户 ip
|
||||
[HttpGet, Route("ip")]
|
||||
[AllowAnonymous]
|
||||
public IResponseOutput Get([FromServices] IHttpContextAccessor _context)
|
||||
|
@ -486,6 +487,9 @@ namespace IRaCIS.Api.Controllers
|
|||
}
|
||||
return ResponseOutput.Ok(sb.ToString());
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using IRaCIS.Application.Contracts;
|
||||
using IRaCIS.Application.Interfaces;
|
||||
using IRaCIS.Application.Contracts;
|
||||
using IRaCIS.Core.Application.BusinessFilter;
|
||||
using IRaCIS.Core.Application.Filter;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Threading.Tasks;
|
||||
using IRaCIS.Core.Application.Service;
|
||||
using IRaCIS.Core.Application.Service.Inspection.DTO;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Application.Auth;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IRaCIS.Core.API.Controllers.Special
|
||||
{
|
||||
|
@ -24,7 +24,7 @@ namespace IRaCIS.Core.API.Controllers.Special
|
|||
ICalculateService _calculateService,
|
||||
IStringLocalizer _localizer) : ControllerBase
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
//[TrialAudit(AuditType.TrialAudit, AuditOptType.AddOrUpdateTrial)]
|
||||
|
@ -36,8 +36,8 @@ namespace IRaCIS.Core.API.Controllers.Special
|
|||
|
||||
public async Task<IResponseOutput> AddOrUpdateTrialInspection(DataInspectionDto<TrialCommand> opt)
|
||||
{
|
||||
var fun =await AddOrUpdateTrial(opt.Data);
|
||||
|
||||
var fun = await AddOrUpdateTrial(opt.Data);
|
||||
|
||||
return fun;
|
||||
}
|
||||
|
||||
|
@ -48,8 +48,8 @@ namespace IRaCIS.Core.API.Controllers.Special
|
|||
[HttpPost, Route("trial/addOrUpdateTrial")]
|
||||
//[Authorize(Policy = IRaCISPolicy.PM_APM)]
|
||||
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AddOrUpdateTrial", "BeforeOngoingCantOpt", "AfterStopCannNotOpt" })]
|
||||
public async Task<IResponseOutput<Trial>> AddOrUpdateTrial(TrialCommand param)
|
||||
[TrialGlobalLimit( "AddOrUpdateTrial", "BeforeOngoingCantOpt", "AfterStopCannNotOpt" )]
|
||||
public async Task<IResponseOutput<Trial>> AddOrUpdateTrial(TrialCommand param)
|
||||
{
|
||||
var userId = Guid.Parse(User.FindFirst("id").Value);
|
||||
var result = await _trialService.AddOrUpdateTrial(param);
|
||||
|
@ -86,9 +86,9 @@ namespace IRaCIS.Core.API.Controllers.Special
|
|||
/// <param name="_trialWorkloadService"></param>
|
||||
/// <param name="workLoadAddOrUpdateModel"></param>
|
||||
/// <returns></returns>
|
||||
|
||||
|
||||
[HttpPost, Route("doctorWorkload/workLoadAddOrUpdate")]
|
||||
[TypeFilter(typeof(TrialResourceFilter),Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
public async Task<IResponseOutput> WorkLoadAddOrUpdate([FromServices] IDoctorWorkloadService _trialWorkloadService, WorkloadCommand workLoadAddOrUpdateModel)
|
||||
{
|
||||
var userId = Guid.Parse(User.FindFirst("id").Value);
|
||||
|
@ -108,9 +108,9 @@ namespace IRaCIS.Core.API.Controllers.Special
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[HttpDelete, Route("doctorWorkload/deleteWorkLoad/{id:guid}/{trialId:guid}")]
|
||||
[TypeFilter(typeof(TrialResourceFilter),Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
public async Task<IResponseOutput> DeleteWorkLoad([FromServices] IDoctorWorkloadService _trialWorkloadService, Guid id)
|
||||
{
|
||||
//先判断该工作量的费用是否被锁定,如果被锁定,则不能删除
|
||||
|
@ -143,7 +143,7 @@ namespace IRaCIS.Core.API.Controllers.Special
|
|||
/// <summary>
|
||||
/// 添加或更新汇率(会触发没有对锁定的费用计算)
|
||||
/// </summary>
|
||||
|
||||
|
||||
[HttpPost, Route("exchangeRate/addOrUpdateExchangeRate")]
|
||||
public async Task<IResponseOutput> AddOrUpdateExchangeRate([FromServices] IExchangeRateService _exchangeRateService, [FromServices] IPaymentAdjustmentService _costAdjustmentService, ExchangeRateCommand addOrUpdateModel)
|
||||
{
|
||||
|
@ -153,7 +153,7 @@ namespace IRaCIS.Core.API.Controllers.Special
|
|||
{
|
||||
if (item != null)
|
||||
{
|
||||
await _calculateService.CalculateMonthlyPayment(new CalculateDoctorAndMonthDTO()
|
||||
await _calculateService.CalculateMonthlyPayment(new CalculateDoctorAndMonthDTO()
|
||||
{
|
||||
NeedCalculateReviewers = new List<Guid>()
|
||||
{
|
||||
|
@ -163,7 +163,7 @@ namespace IRaCIS.Core.API.Controllers.Special
|
|||
}, User.FindFirst("id").Value);
|
||||
}
|
||||
}
|
||||
await _costAdjustmentService.CalculateCNY(addOrUpdateModel.YearMonth, addOrUpdateModel.Rate);
|
||||
await _costAdjustmentService.CalculateCNY(addOrUpdateModel.YearMonth, addOrUpdateModel.Rate);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -171,13 +171,13 @@ namespace IRaCIS.Core.API.Controllers.Special
|
|||
/// <summary>
|
||||
/// 添加或更新 职称单价[AUTH]
|
||||
/// </summary>
|
||||
|
||||
|
||||
[HttpPost, Route("rankPrice/addOrUpdateRankPrice")]
|
||||
public async Task<IResponseOutput> AddOrUpdateRankPrice([FromServices] IReviewerPayInfoService _reviewerPayInfoService, [FromServices] IRankPriceService _rankPriceService, RankPriceCommand addOrUpdateModel)
|
||||
{
|
||||
if (addOrUpdateModel.Id != Guid.Empty && addOrUpdateModel.Id != null)
|
||||
{
|
||||
var needCalReviewerIds =await _reviewerPayInfoService.GetReviewerIdByRankId(Guid.Parse(addOrUpdateModel.Id.ToString()));
|
||||
var needCalReviewerIds = await _reviewerPayInfoService.GetReviewerIdByRankId(Guid.Parse(addOrUpdateModel.Id.ToString()));
|
||||
var calcList = await _calculateService.GetNeedCalculateReviewerList(Guid.Empty, string.Empty);
|
||||
|
||||
foreach (var item in calcList)
|
||||
|
@ -204,12 +204,12 @@ namespace IRaCIS.Core.API.Controllers.Special
|
|||
/// <summary>
|
||||
/// 添加或更新(替换)医生支付展信息[AUTH]
|
||||
/// </summary>
|
||||
|
||||
|
||||
[HttpPost, Route("reviewerPayInfo/addOrUpdateReviewerPayInfo")]
|
||||
public async Task<IResponseOutput> AddOrUpdateReviewerPayInfo([FromServices] IReviewerPayInfoService _doctorPayInfoService, ReviewerPayInfoCommand addOrUpdateModel)
|
||||
{
|
||||
var userId = Guid.Parse(User.FindFirst("id").Value);
|
||||
var result =await _doctorPayInfoService.AddOrUpdateReviewerPayInfo(addOrUpdateModel, userId);
|
||||
var result = await _doctorPayInfoService.AddOrUpdateReviewerPayInfo(addOrUpdateModel, userId);
|
||||
var calcList = await _calculateService.GetNeedCalculateReviewerList(addOrUpdateModel.DoctorId, string.Empty);
|
||||
foreach (var item in calcList)
|
||||
{
|
||||
|
@ -233,12 +233,12 @@ namespace IRaCIS.Core.API.Controllers.Special
|
|||
/// <summary>
|
||||
/// 保存(替换)项目支付价格信息(会触发没有被锁定的费用计算)[AUTH]
|
||||
/// </summary>
|
||||
|
||||
|
||||
[HttpPost, Route("trialPaymentPrice/addOrUpdateTrialPaymentPrice")]
|
||||
public async Task<IResponseOutput> AddOrUpdateTrialPaymentPrice([FromServices] ITrialPaymentPriceService _trialPaymentPriceService, TrialPaymentPriceCommand addOrUpdateModel)
|
||||
{
|
||||
var userId = Guid.Parse(User.FindFirst("id").Value);
|
||||
var result =await _trialPaymentPriceService.AddOrUpdateTrialPaymentPrice(addOrUpdateModel);
|
||||
var result = await _trialPaymentPriceService.AddOrUpdateTrialPaymentPrice(addOrUpdateModel);
|
||||
var needCalReviewerIds = await _trialService.GetTrialEnrollmentReviewerIds(addOrUpdateModel.TrialId);
|
||||
var calcList = await _calculateService.GetNeedCalculateReviewerList(Guid.Empty, string.Empty);
|
||||
|
||||
|
@ -246,7 +246,7 @@ namespace IRaCIS.Core.API.Controllers.Special
|
|||
{
|
||||
if (item != null && needCalReviewerIds.Contains(item.DoctorId))
|
||||
{
|
||||
await _calculateService.CalculateMonthlyPayment(new CalculateDoctorAndMonthDTO()
|
||||
await _calculateService.CalculateMonthlyPayment(new CalculateDoctorAndMonthDTO()
|
||||
{
|
||||
NeedCalculateReviewers = new List<Guid>()
|
||||
{
|
||||
|
@ -262,12 +262,12 @@ namespace IRaCIS.Core.API.Controllers.Special
|
|||
/// <summary>
|
||||
/// 批量更新奖励费用[AUTH]
|
||||
/// </summary>
|
||||
|
||||
|
||||
[HttpPost, Route("volumeReward/addOrUpdatevolumeRewardPriceList")]
|
||||
public async Task<IResponseOutput> AddOrUpdateAwardPriceList([FromServices] IVolumeRewardService _volumeRewardService, IEnumerable<AwardPriceCommand> addOrUpdateModel)
|
||||
{
|
||||
|
||||
var result =await _volumeRewardService.AddOrUpdateVolumeRewardPriceList(addOrUpdateModel);
|
||||
var result = await _volumeRewardService.AddOrUpdateVolumeRewardPriceList(addOrUpdateModel);
|
||||
|
||||
var calcList = await _calculateService.GetNeedCalculateReviewerList(Guid.Empty, string.Empty);
|
||||
foreach (var item in calcList)
|
||||
|
|
|
@ -1,24 +1,19 @@
|
|||
|
||||
using System.Threading.Tasks;
|
||||
using AutoMapper;
|
||||
|
||||
using IRaCIS.Application.Interfaces;
|
||||
using IRaCIS.Core.Application.BusinessFilter;
|
||||
using IRaCIS.Core.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Filter;
|
||||
using IRaCIS.Core.Application.Image.QA;
|
||||
using IRaCIS.Core.Application.Interfaces;
|
||||
using IRaCIS.Core.Application.Service;
|
||||
using IRaCIS.Core.Application.Service.Inspection.DTO;
|
||||
using IRaCIS.Core.Application.Service.Inspection.Interface;
|
||||
using IRaCIS.Core.Application.Service.Reading.Dto;
|
||||
using IRaCIS.Core.Application.Service.Reading.Interface;
|
||||
using IRaCIS.Core.Application.ViewModel;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace IRaCIS.Core.API.Controllers
|
||||
|
@ -38,7 +33,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
IReadingMedicineQuestionService _readingMedicineQuestionService
|
||||
) : ControllerBase
|
||||
{
|
||||
|
||||
|
||||
#region 获取稽查数据
|
||||
/// <summary>
|
||||
/// 获取稽查数据
|
||||
|
@ -57,7 +52,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingImageTask/SubmitOncologyReadingInfo")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> SetOncologyReadingInfo(DataInspectionDto<SubmitOncologyReadingInfoInDto> opt)
|
||||
|
@ -74,7 +69,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingImageTask/SubmitDicomVisitTask")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> SubmitDicomVisitTask(DataInspectionDto<SubmitDicomVisitTaskInDto> opt)
|
||||
|
@ -93,7 +88,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingImageTask/SubmitGlobalReadingInfo")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> SubmitGlobalReadingInfo(DataInspectionDto<SubmitGlobalReadingInfoInDto> opt)
|
||||
|
@ -112,12 +107,12 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/configTrialBasicInfo/TrialReadingInfoSign")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> TrialReadingInfoSign(DataInspectionDto<TrialReadingInfoSignInDto> opt)
|
||||
{
|
||||
|
||||
|
||||
var singid = await _inspectionService.RecordSing(opt.SignInfo);
|
||||
var result = await _trialConfigService.TrialReadingInfoSign(opt.Data);
|
||||
await _inspectionService.CompletedSign(singid, result);
|
||||
|
@ -131,7 +126,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingMedicalReview/FinishMedicalReview")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> FinishMedicalReview(DataInspectionDto<FinishMedicalReviewInDto> opt)
|
||||
|
@ -148,7 +143,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingMedicineQuestion/ConfirmReadingMedicineQuestion")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> ConfirmReadingMedicineQuestion(DataInspectionDto<ConfirmReadingMedicineQuestionInDto> opt)
|
||||
|
@ -166,7 +161,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingImageTask/SubmitVisitTaskQuestions")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> SubmitVisitTaskQuestions(DataInspectionDto<SubmitVisitTaskQuestionsInDto> opt)
|
||||
|
@ -184,7 +179,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ClinicalAnswer/CRCSignClinicalData")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> CRCSignClinicalData(DataInspectionDto<CRCSignClinicalDataInDto> opt)
|
||||
|
@ -202,7 +197,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ClinicalAnswer/CRCConfirmClinical")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> CRCConfirmClinical(DataInspectionDto<CRCConfirmClinicalInDto> opt)
|
||||
|
@ -219,7 +214,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ClinicalAnswer/CRCCancelConfirmClinical")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> CRCCancelConfirmClinical(DataInspectionDto<CRCCancelConfirmClinicalInDto> opt)
|
||||
|
@ -237,7 +232,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ClinicalAnswer/PMConfirmClinical")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> PMConfirmClinical(DataInspectionDto<CRCConfirmClinicalInDto> opt)
|
||||
|
@ -255,7 +250,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingClinicalData/SignConsistencyAnalysisReadingClinicalData")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> SignConsistencyAnalysisReadingClinicalData(DataInspectionDto<SignConsistencyAnalysisReadingClinicalDataInDto> opt)
|
||||
|
@ -272,7 +267,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ClinicalAnswer/SubmitClinicalFormAndSign")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> SubmitClinicalFormAndSign(DataInspectionDto<SubmitClinicalFormInDto> opt)
|
||||
|
@ -289,7 +284,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingImageTask/SubmitJudgeVisitTaskResult")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> SubmitJudgeVisitTaskResult(DataInspectionDto<SaveJudgeVisitTaskResult> opt)
|
||||
|
@ -308,12 +303,12 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialBasicInfoConfirm")]
|
||||
[UnitOfWork]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt" })]
|
||||
[TrialGlobalLimit( "BeforeOngoingCantOpt" )]
|
||||
public async Task<IResponseOutput> ConfigTrialBasicInfoConfirm(DataInspectionDto<BasicTrialConfig> opt)
|
||||
{
|
||||
|
||||
opt.Data.IsTrialBasicLogicConfirmed = true;
|
||||
var singid= await _inspectionService.RecordSing(opt.SignInfo);
|
||||
var singid = await _inspectionService.RecordSing(opt.SignInfo);
|
||||
var result = await _trialConfigService.ConfigTrialBasicInfo(opt.Data);
|
||||
await _inspectionService.CompletedSign(singid, result);
|
||||
return result;
|
||||
|
@ -328,7 +323,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialProcessInfoConfirm")]
|
||||
[UnitOfWork]
|
||||
//[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt" })]
|
||||
//[TrialGlobalLimit( "BeforeOngoingCantOpt" )]
|
||||
public async Task<IResponseOutput> ConfigTrialProcessInfoConfirm(DataInspectionDto<TrialProcessConfig> opt)
|
||||
{
|
||||
opt.Data.IsTrialProcessConfirmed = true;
|
||||
|
@ -340,7 +335,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
|
@ -350,12 +345,12 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialUrgentInfoConfirm")]
|
||||
[UnitOfWork]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt" })]
|
||||
[TrialGlobalLimit( "BeforeOngoingCantOpt" )]
|
||||
public async Task<IResponseOutput> ConfigTrialUrgentInfoConfirm(DataInspectionDto<TrialUrgentConfig> opt)
|
||||
{
|
||||
opt.Data.IsTrialUrgentConfirmed = true;
|
||||
var singid = await _inspectionService.RecordSing(opt.SignInfo);
|
||||
var result= await _trialConfigService.ConfigTrialUrgentInfo(opt.Data);
|
||||
var result = await _trialConfigService.ConfigTrialUrgentInfo(opt.Data);
|
||||
await _inspectionService.CompletedSign(singid, result);
|
||||
return result;
|
||||
}
|
||||
|
@ -363,7 +358,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
|
||||
[HttpPost, Route("Inspection/configTrialBasicInfo/ConfigTrialPACSInfoConfirm")]
|
||||
[UnitOfWork]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt" })]
|
||||
[TrialGlobalLimit( "BeforeOngoingCantOpt" )]
|
||||
public async Task<IResponseOutput> ConfigTrialPACSInfoConfirm(DataInspectionDto<TrialPACSConfig> opt)
|
||||
{
|
||||
opt.Data.IsTrialPACSConfirmed = true;
|
||||
|
@ -379,7 +374,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/configTrialBasicInfo/TrialConfigSignatureConfirm")]
|
||||
[UnitOfWork]
|
||||
[TypeFilter(typeof(TrialResourceFilter),Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
|
||||
public async Task<IResponseOutput> TrialConfigSignatureConfirm(DataInspectionDto<SignConfirmDTO> opt)
|
||||
{
|
||||
|
@ -390,31 +385,31 @@ namespace IRaCIS.Core.API.Controllers
|
|||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 重置并同步项目阅片标准
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingCriterion/ResetAndAsyncCriterion")]
|
||||
[UnitOfWork]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
/// <summary>
|
||||
/// 重置并同步项目阅片标准
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadingCriterion/ResetAndAsyncCriterion")]
|
||||
[UnitOfWork]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
|
||||
public async Task<IResponseOutput> ResetAndAsyncCriterion(DataInspectionDto<ResetAndAsyncCriterionInDto> opt)
|
||||
{
|
||||
var singid = await _inspectionService.RecordSing(opt.SignInfo);
|
||||
var result = await _trialConfigService.ResetAndAsyncCriterion(opt.Data);
|
||||
await _inspectionService.CompletedSign(singid, result);
|
||||
return result;
|
||||
}
|
||||
public async Task<IResponseOutput> ResetAndAsyncCriterion(DataInspectionDto<ResetAndAsyncCriterionInDto> opt)
|
||||
{
|
||||
var singid = await _inspectionService.RecordSing(opt.SignInfo);
|
||||
var result = await _trialConfigService.ResetAndAsyncCriterion(opt.Data);
|
||||
await _inspectionService.CompletedSign(singid, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// CRC RequestToQC 批量提交
|
||||
/// </summary>
|
||||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/QCOperation/CRCRequestToQC")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
/// <summary>
|
||||
/// CRC RequestToQC 批量提交
|
||||
/// </summary>
|
||||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/QCOperation/CRCRequestToQC")]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> CRCRequestToQC(DataInspectionDto<CRCRequestToQCCommand> opt)
|
||||
{
|
||||
|
@ -429,12 +424,12 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// 设置QC 通过或者不通过 7:QC failed 8:QC passed
|
||||
/// </summary>
|
||||
[HttpPost, Route("Inspection/QCOperation/QCPassedOrFailed")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> QCPassedOrFailed(DataInspectionDto<QCPassedOrFailedDto> opt)
|
||||
{
|
||||
var singid = await _inspectionService.RecordSing(opt.SignInfo);
|
||||
var result= await _qCOperationService.QCPassedOrFailed(opt.Data.trialId, opt.Data.subjectVisitId, opt.Data.auditState);
|
||||
var result = await _qCOperationService.QCPassedOrFailed(opt.Data.trialId, opt.Data.subjectVisitId, opt.Data.auditState);
|
||||
await _inspectionService.CompletedSign(singid, result);
|
||||
return result;
|
||||
}
|
||||
|
@ -443,12 +438,12 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// 一致性核查 回退 对话记录不清除 只允许PM回退
|
||||
/// </summary>
|
||||
[HttpPost, Route("Inspection/QCOperation/CheckBack")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> CheckBack(DataInspectionDto<IDDto> opt)
|
||||
{
|
||||
var singid = await _inspectionService.RecordSing(opt.SignInfo);
|
||||
var result = await _qCOperationService.CheckBack(opt.Data.Id);
|
||||
var result = await _qCOperationService.CheckBack(opt.Data.Id);
|
||||
await _inspectionService.CompletedSign(singid, result);
|
||||
return result;
|
||||
}
|
||||
|
@ -460,7 +455,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/ReadClinicalData/ReadClinicalDataSign")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> ReadClinicalDataSign(DataInspectionDto<ReadingClinicalDataSignIndto> opt)
|
||||
{
|
||||
|
@ -475,9 +470,9 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// CRC 设置已经重传完成
|
||||
/// </summary>
|
||||
[HttpPost, Route("Inspection/QCOperation/SetReuploadFinished")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> SetReuploadFinished(DataInspectionDto<CRCReuploadFinishedCommand> opt)
|
||||
public async Task<IResponseOutput> SetReuploadFinished(DataInspectionDto<CRCReuploadFinishedCommand> opt)
|
||||
{
|
||||
var singid = await _inspectionService.RecordSing(opt.SignInfo);
|
||||
var result = await _qCOperationService.SetReuploadFinished(opt.Data);
|
||||
|
@ -491,8 +486,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="opt"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/TrialConfig/updateTrialState")]
|
||||
//[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt" })]
|
||||
[TrialGlobalLimit( "BeforeOngoingCantOpt")]
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> UpdateTrialState(DataInspectionDto<UpdateTrialStateDto> opt)
|
||||
{
|
||||
|
@ -508,7 +502,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/TrialDocument/userConfirm")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt", "SignSystemDocNoTrialId", "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "BeforeOngoingCantOpt", "SignSystemDocNoTrialId", "AfterStopCannNotOpt" )]
|
||||
[UnitOfWork]
|
||||
public async Task<IResponseOutput> UserConfirm(DataInspectionDto<UserConfirmCommand> opt)
|
||||
{
|
||||
|
@ -525,16 +519,16 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Inspection/VisitTask/ConfirmReReading")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
[UnitOfWork]
|
||||
|
||||
public async Task<IResponseOutput> ConfirmReReading(DataInspectionDto<ConfirmReReadingCommand> opt , [FromServices] IVisitTaskHelpeService _visitTaskCommonService,[FromServices] IVisitTaskService _visitTaskService)
|
||||
public async Task<IResponseOutput> ConfirmReReading(DataInspectionDto<ConfirmReReadingCommand> opt, [FromServices] IVisitTaskHelpeService _visitTaskCommonService, [FromServices] IVisitTaskService _visitTaskService)
|
||||
{
|
||||
var singId = await _inspectionService.RecordSing(opt.SignInfo);
|
||||
var result = await _visitTaskService.ConfirmReReading(opt.Data, _visitTaskCommonService);
|
||||
await _inspectionService.CompletedSign(singId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
using AutoMapper;
|
||||
using DocumentFormat.OpenXml.Drawing;
|
||||
using ExcelDataReader;
|
||||
using IRaCIS.Application.Contracts;
|
||||
using IRaCIS.Application.Interfaces;
|
||||
using IRaCIS.Core.Application.Auth;
|
||||
using IRaCIS.Core.Application.BusinessFilter;
|
||||
using IRaCIS.Core.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Contracts.Dicom;
|
||||
using IRaCIS.Core.Application.Contracts.Dicom.DTO;
|
||||
|
@ -11,20 +9,17 @@ using IRaCIS.Core.Application.Filter;
|
|||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Application.MassTransit.Command;
|
||||
using IRaCIS.Core.Application.Service;
|
||||
using IRaCIS.Core.Application.Service.ImageAndDoc;
|
||||
using IRaCIS.Core.Application.Service.Reading.Dto;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using MassTransit;
|
||||
using MassTransit.Mediator;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
|
@ -40,23 +35,46 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace IRaCIS.Core.API.Controllers
|
||||
{
|
||||
|
||||
#region 上传基类封装
|
||||
|
||||
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
|
||||
{
|
||||
public void OnResourceExecuting(ResourceExecutingContext context)
|
||||
{
|
||||
|
||||
|
||||
var factories = context.ValueProviderFactories;
|
||||
//factories.RemoveType<FormValueProviderFactory>();
|
||||
factories.RemoveType<FormFileValueProviderFactory>();
|
||||
factories.RemoveType<JQueryFormValueProviderFactory>();
|
||||
context.HttpContext.Request.EnableBuffering();
|
||||
}
|
||||
|
||||
public void OnResourceExecuted(ResourceExecutedContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[DisableFormValueModelBinding]
|
||||
public abstract class UploadBaseController : ControllerBase
|
||||
{
|
||||
/// <summary> 流式上传 直接返回</summary>
|
||||
[Route("base")]
|
||||
[Route("SingleFileUpload")]
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
|
||||
public virtual async Task<IResponseOutput> SingleFileUploadAsync(Func<string, (string, string)> filePathFunc)
|
||||
{
|
||||
var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value;
|
||||
|
@ -91,7 +109,9 @@ namespace IRaCIS.Core.API.Controllers
|
|||
|
||||
|
||||
/// <summary> 流式上传 通用封装 不返回任何数据,后续还有事情处理 </summary>
|
||||
[Route("base")]
|
||||
[Route("FileUpload")]
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
|
||||
public virtual async Task FileUploadAsync(Func<string, Task<string>> filePathFunc)
|
||||
{
|
||||
var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value;
|
||||
|
@ -134,7 +154,9 @@ namespace IRaCIS.Core.API.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
[Route("base")]
|
||||
[Route("FileUploadToOSS")]
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
|
||||
public virtual async Task FileUploadToOSSAsync(Func<string, Stream, Task<string>> toMemoryStreamFunc)
|
||||
{
|
||||
var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value;
|
||||
|
@ -160,10 +182,10 @@ namespace IRaCIS.Core.API.Controllers
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary> 流式上传 Dicom上传 </summary>
|
||||
[Route("base")]
|
||||
[Route("DicomFileUpload")]
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
|
||||
public virtual async Task DicomFileUploadAsync(Func<string, Stream, int, Task> filePathFunc, string boundary)
|
||||
{
|
||||
|
||||
|
@ -225,9 +247,42 @@ namespace IRaCIS.Core.API.Controllers
|
|||
#endregion
|
||||
|
||||
#region Dicom 影像上传 临床数据 非diocm
|
||||
public class UploadNoneDicomFileCommand
|
||||
{
|
||||
[NotDefault]
|
||||
public Guid SubjectVisitId { get; set; }
|
||||
|
||||
[ApiExplorerSettings(GroupName = "Image")]
|
||||
[ApiController]
|
||||
|
||||
[NotDefault]
|
||||
public Guid StudyMonitorId { get; set; }
|
||||
|
||||
|
||||
public Guid? NoneDicomStudyId { get; set; }
|
||||
|
||||
//IR 上传的时候跟任务绑定
|
||||
public Guid? VisitTaskId { get; set; }
|
||||
|
||||
public string RecordPath { get; set; }
|
||||
|
||||
public int FailedFileCount { get; set; }
|
||||
|
||||
public long FileSize { get; set; }
|
||||
|
||||
|
||||
public List<OSSFileDTO> UploadedFileList { get; set; } = new List<OSSFileDTO>();
|
||||
|
||||
|
||||
public class OSSFileDTO
|
||||
{
|
||||
public string FilePath { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public int FileFize { get; set; }
|
||||
|
||||
public string FileType { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
[ApiController, ApiExplorerSettings(GroupName = "Image")]
|
||||
public class StudyController(
|
||||
IMediator _mediator,
|
||||
QCCommon _qCCommon,
|
||||
|
@ -238,14 +293,11 @@ namespace IRaCIS.Core.API.Controllers
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>Dicom 归档</summary>
|
||||
[HttpPost, Route("Study/ArchiveStudy")]
|
||||
[DisableFormValueModelBinding]
|
||||
[DisableRequestSizeLimit]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
public async Task<IResponseOutput> ArchiveStudyNew(Guid trialId, Guid subjectVisitId, string studyInstanceUid, Guid? abandonStudyId, Guid studyMonitorId,
|
||||
[FromServices] ILogger<UploadDownLoadController> _logger,
|
||||
[FromServices] IStudyService _studyService,
|
||||
|
@ -392,41 +444,6 @@ namespace IRaCIS.Core.API.Controllers
|
|||
}
|
||||
|
||||
|
||||
public class UploadNoneDicomFileCommand
|
||||
{
|
||||
[NotDefault]
|
||||
public Guid SubjectVisitId { get; set; }
|
||||
|
||||
|
||||
[NotDefault]
|
||||
public Guid StudyMonitorId { get; set; }
|
||||
|
||||
|
||||
public Guid? NoneDicomStudyId { get; set; }
|
||||
|
||||
//IR 上传的时候跟任务绑定
|
||||
public Guid? VisitTaskId { get; set; }
|
||||
|
||||
public string RecordPath { get; set; }
|
||||
|
||||
public int FailedFileCount { get; set; }
|
||||
|
||||
public long FileSize { get; set; }
|
||||
|
||||
|
||||
public List<OSSFileDTO> UploadedFileList { get; set; } = new List<OSSFileDTO>();
|
||||
|
||||
|
||||
public class OSSFileDTO
|
||||
{
|
||||
public string FilePath { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public int FileFize { get; set; }
|
||||
|
||||
public string FileType { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 非dicom 上传预上传接口
|
||||
/// </summary>
|
||||
|
@ -435,7 +452,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="_studyMonitorRepository"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("Study/PreArchiveStudy")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
public async Task<IResponseOutput> PreArchiveStudy(PreArchiveStudyCommand preArchiveStudyCommand,
|
||||
[FromServices] IStudyService _studyService,
|
||||
[FromServices] IRepository<StudyMonitor> _studyMonitorRepository)
|
||||
|
@ -451,7 +468,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
|
||||
IsSuccess = false,
|
||||
UploadStartTime = DateTime.Now,
|
||||
FileCount=preArchiveStudyCommand.FileCount,
|
||||
FileCount = preArchiveStudyCommand.FileCount,
|
||||
IsDicom = preArchiveStudyCommand.IsDicom,
|
||||
IP = _userInfo.IP
|
||||
};
|
||||
|
@ -471,8 +488,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <param name="_noneDicomStudyFileRepository"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("NoneDicomStudy/UploadNoneDicomFile")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
public async Task<IResponseOutput> UploadNoneDicomFile(UploadNoneDicomFileCommand incommand,
|
||||
[FromServices] IRepository<NoneDicomStudy> _noneDicomStudyRepository,
|
||||
[FromServices] IRepository<StudyMonitor> _studyMonitorRepository,
|
||||
|
@ -544,7 +560,7 @@ namespace IRaCIS.Core.API.Controllers
|
|||
/// <returns></returns>
|
||||
/// <exception cref="BusinessValidationFailedException"></exception>
|
||||
[HttpPost("QCOperation/UploadVisitCheckExcel/{trialId:guid}")]
|
||||
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })]
|
||||
[TrialGlobalLimit( "AfterStopCannNotOpt" )]
|
||||
|
||||
public async Task<IResponseOutput> UploadVisitCheckExcel(Guid trialId, [FromServices] IOSSService oSSService, [FromServices] IRepository<InspectionFile> _inspectionFileRepository)
|
||||
{
|
||||
|
@ -757,146 +773,13 @@ namespace IRaCIS.Core.API.Controllers
|
|||
|
||||
#endregion
|
||||
|
||||
#region 医生文件上传下载
|
||||
|
||||
|
||||
#region DTO
|
||||
public class DoctorDownloadInfo
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string ReviewerCode { get; set; }
|
||||
|
||||
public List<DownloadFileInfo> FileList { get; set; }
|
||||
}
|
||||
public class DownloadFileInfo
|
||||
{
|
||||
public string FileName { get; set; }
|
||||
|
||||
public string Path { get; set; }
|
||||
}
|
||||
|
||||
public class GetDoctorPathCommand
|
||||
{
|
||||
public int Language { get; set; }
|
||||
|
||||
public List<Guid> DoctorIdList { get; set; }
|
||||
}
|
||||
|
||||
public class GetDoctoreAttachPathCommand
|
||||
{
|
||||
public Guid DoctorId { get; set; }
|
||||
public List<Guid> AttachmentIdList { get; set; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>医生文件上传下载</summary>
|
||||
[ApiExplorerSettings(GroupName = "Common")]
|
||||
[ApiController]
|
||||
public class FileController : UploadBaseController
|
||||
{
|
||||
public IMapper _mapper { get; set; }
|
||||
public IUserInfo _userInfo { get; set; }
|
||||
|
||||
|
||||
private readonly IWebHostEnvironment _hostEnvironment;
|
||||
|
||||
private readonly IFileService _fileService;
|
||||
|
||||
|
||||
public FileController(IMapper mapper, IUserInfo userInfo, IWebHostEnvironment hostEnvironment, IFileService fileService)
|
||||
{
|
||||
_fileService = fileService;
|
||||
_hostEnvironment = hostEnvironment;
|
||||
_mapper = mapper;
|
||||
_userInfo = userInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// New 医生首页 多选 获取多个医生信息+文件路径列表 医生压缩包名称 doctorCode + "_" + doctorName _(时间戳或者随机的Guid)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("file/GetOfficialResume")]
|
||||
public async Task<IResponseOutput<List<DoctorDownloadInfo>>> GetOfficialResume(GetDoctorPathCommand command,
|
||||
[FromServices] IRepository<Attachment> _attachmentrepository)
|
||||
{
|
||||
|
||||
var list = await _attachmentrepository.Where(t => command.DoctorIdList.Contains(t.DoctorId) && command.Language == t.Language).GroupBy(t => new { Name = t.Doctor.FirstName + "_" + t.Doctor.LastName, ReviewerCode = t.Doctor.ReviewerCode, t.DoctorId })
|
||||
.Select(g => new DoctorDownloadInfo()
|
||||
{
|
||||
Id = g.Key.DoctorId,
|
||||
Name = g.Key.Name,
|
||||
ReviewerCode = g.Key.ReviewerCode,
|
||||
FileList = g.Select(t => new DownloadFileInfo() { FileName = t.FileName, Path = t.Path }).ToList()
|
||||
}).ToListAsync();
|
||||
|
||||
return ResponseOutput.Ok(list);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// New 项目入组 勾选获取简历路径
|
||||
/// </summary>
|
||||
/// <param name="command"></param>
|
||||
/// <param name="_doctorService"></param>
|
||||
/// <param name="_attachmentrepository"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("file/GetTrialDoctorOfficialResume")]
|
||||
public async Task<IResponseOutput<List<DoctorDownloadInfo>>> GetTrialDoctorOfficialResume(GetDoctorPathCommand command,
|
||||
[FromServices] IDoctorService _doctorService,
|
||||
[FromServices] IRepository<Attachment> _attachmentrepository)
|
||||
{
|
||||
|
||||
var list = await _attachmentrepository.Where(t => command.DoctorIdList.Contains(t.DoctorId) && command.Language == t.Language && t.IsOfficial && t.Type.Equals("Resume")).GroupBy(t => new { Name = t.Doctor.FirstName + "_" + t.Doctor.LastName, ReviewerCode = t.Doctor.ReviewerCode, t.DoctorId })
|
||||
.Select(g => new DoctorDownloadInfo()
|
||||
{
|
||||
Id = g.Key.DoctorId,
|
||||
Name = g.Key.Name,
|
||||
ReviewerCode = g.Key.ReviewerCode,
|
||||
FileList = g.Select(t => new DownloadFileInfo() { FileName = t.FileName, Path = t.Path }).ToList()
|
||||
}).ToListAsync();
|
||||
|
||||
return ResponseOutput.Ok(list);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// new 医生详情 勾选或者下载文件路径
|
||||
/// </summary>
|
||||
/// <param name="command"></param>
|
||||
/// <param name="_doctorService"></param>
|
||||
/// <param name="_attachmentrepository"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost, Route("file/GetDoctorAttachment")]
|
||||
public async Task<IResponseOutput<DoctorDownloadInfo>> GetDoctorAttachment(GetDoctoreAttachPathCommand command,
|
||||
[FromServices] IDoctorService _doctorService,
|
||||
[FromServices] IRepository<Attachment> _attachmentrepository)
|
||||
{
|
||||
|
||||
var find = await _attachmentrepository.Where(t => command.DoctorId == t.DoctorId && command.AttachmentIdList.Contains(t.Id)).GroupBy(t => new { Name = t.Doctor.FirstName + "_" + t.Doctor.LastName, ReviewerCode = t.Doctor.ReviewerCode, t.DoctorId })
|
||||
.Select(g => new DoctorDownloadInfo()
|
||||
{
|
||||
Id = g.Key.DoctorId,
|
||||
Name = g.Key.Name,
|
||||
ReviewerCode = g.Key.ReviewerCode,
|
||||
FileList = g.Select(t => new DownloadFileInfo() { FileName = t.FileName, Path = t.Path }).ToList()
|
||||
}).FirstOrDefaultAsync();
|
||||
|
||||
return ResponseOutput.Ok(find);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region 项目 系统 基本文件 上传 下载 预览
|
||||
|
||||
[ApiExplorerSettings(GroupName = "Common")]
|
||||
[ApiController]
|
||||
[ApiController, ApiExplorerSettings(GroupName = "Common")]
|
||||
|
||||
public class UploadDownLoadController : UploadBaseController
|
||||
{
|
||||
public IMapper _mapper { get; set; }
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
using Microsoft.Extensions.Hosting;
|
||||
using System.Threading;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using MassTransit;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Hangfire;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Application.MassTransit.Consumer;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using MassTransit.Scheduling;
|
||||
using Hangfire.Storage;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MassTransit.Mediator;
|
||||
|
||||
namespace IRaCIS.Core.API.HostService;
|
||||
|
||||
public class HangfireHostService(IRecurringMessageScheduler _recurringMessageScheduler,
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository,
|
||||
IMediator _mediator,
|
||||
ILogger<HangfireHostService> _logger) : IHostedService
|
||||
{
|
||||
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("项目启动 hangfire 任务初始化 执行开始~");
|
||||
|
||||
|
||||
//创建邮件定时任务
|
||||
//项目定时任务都在default 队列
|
||||
var dbJobIdList = JobStorage.Current.GetConnection().GetRecurringJobs().Where(t => t.Queue == "default").Select(t => t.Id).ToList();
|
||||
|
||||
foreach (var jobId in dbJobIdList)
|
||||
{
|
||||
HangfireJobHelper.RemoveCronJob(jobId);
|
||||
}
|
||||
|
||||
|
||||
var taskInfoList = await _trialEmailNoticeConfigRepository.Where(t => t.Trial.TrialStatusStr == StaticData.TrialState.TrialOngoing && t.EmailCron != string.Empty && t.IsAutoSend)
|
||||
.Select(t => new { t.Id, t.Code, TrialCode = t.Trial.TrialCode, t.EmailCron, t.BusinessScenarioEnum, t.TrialId })
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var task in taskInfoList)
|
||||
{
|
||||
//利用主键作为任务Id
|
||||
var jobId = $"{task.TrialId}({task.TrialCode})_({task.BusinessScenarioEnum})";
|
||||
|
||||
var trialId = task.TrialId;
|
||||
|
||||
HangfireJobHelper.AddOrUpdateTrialCronJob(jobId, trialId, task.BusinessScenarioEnum, task.EmailCron);
|
||||
}
|
||||
|
||||
|
||||
//await _recurringMessageScheduler.ScheduleRecurringPublish(new QCImageQuestionSchedule() { CronExpression = "0/3 * * * * ? " }, new MasstransiTestCommand { value = "message at " + DateTime.Now.ToString() });
|
||||
|
||||
|
||||
|
||||
_logger.LogInformation("项目启动 hangfire 任务初始化 执行结束");
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using IRaCIS.Core.Application.MassTransit.Consumer;
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IRaCIS.Core.API.HostService
|
||||
{
|
||||
public class RecurringJobConfigurationService :
|
||||
BackgroundService
|
||||
{
|
||||
readonly IServiceScopeFactory _scopeFactory;
|
||||
|
||||
public RecurringJobConfigurationService(IServiceScopeFactory scopeFactory)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
await using var scope = _scopeFactory.CreateAsyncScope();
|
||||
|
||||
var endpoint = scope.ServiceProvider.GetRequiredService<IPublishEndpoint>();
|
||||
|
||||
await endpoint.AddOrUpdateRecurringJob(nameof(MasstransitTestConsumer), new MasstransiTestCommand(), x => x.Every(minutes: 1),
|
||||
stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,11 +38,18 @@
|
|||
<None Remove="GrpcToken.proto" />
|
||||
<None Remove="IRaCIS.Core.API.xml" />
|
||||
<None Remove="Protos\GrpcToken.proto" />
|
||||
<None Remove="Resources\ip2region.xdb" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="GrpcToken.proto" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Resources\ip2region.xdb">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="wwwroot\swagger\ui\abp.js">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
|
@ -62,25 +69,24 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
|
||||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="ConfigMapFileProvider" Version="2.0.1" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.14" />
|
||||
<PackageReference Include="Hangfire.Dashboard.BasicAuthorization" Version="1.0.2" />
|
||||
<PackageReference Include="Hangfire.InMemory" Version="0.10.3" />
|
||||
<PackageReference Include="Hangfire.InMemory" Version="1.0.0" />
|
||||
<PackageReference Include="Hangfire.SqlServer" Version="1.8.14" />
|
||||
<PackageReference Include="Invio.Extensions.Authentication.JwtBearer" Version="2.0.1" />
|
||||
<PackageReference Include="LogDashboard" Version="1.4.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2" />
|
||||
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
|
||||
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Email" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.7.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.9.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -100,6 +106,12 @@
|
|||
<Content Update="appsettings.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Resources\en-US.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Resources\zh-CN.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ProjectExtensions>
|
||||
|
|
|
@ -16,7 +16,12 @@
|
|||
医生基本信息 、工作信息 专业信息、审核状态
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:IRaCIS.Api.Controllers.ExtraController.GetDoctorDetail(IRaCIS.Application.Interfaces.IAttachmentService,IRaCIS.Application.Interfaces.IDoctorService,IRaCIS.Application.Interfaces.IEducationService,IRaCIS.Application.Interfaces.ITrialExperienceService,IRaCIS.Application.Interfaces.IResearchPublicationService,IRaCIS.Application.Interfaces.IVacationService,System.Guid)">
|
||||
<member name="M:IRaCIS.Api.Controllers.ExtraController.#ctor(IRaCIS.Application.Interfaces.IAttachmentService,IRaCIS.Application.Interfaces.IDoctorService,IRaCIS.Application.Interfaces.IEducationService,IRaCIS.Application.Interfaces.ITrialExperienceService,IRaCIS.Application.Interfaces.IResearchPublicationService,IRaCIS.Application.Interfaces.IVacationService)">
|
||||
<summary>
|
||||
医生基本信息 、工作信息 专业信息、审核状态
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:IRaCIS.Api.Controllers.ExtraController.GetDoctorDetail(IRaCIS.Application.Contracts.GetDoctorDetailInDto)">
|
||||
<summary>
|
||||
获取医生详情
|
||||
</summary>
|
||||
|
@ -32,6 +37,22 @@
|
|||
<member name="M:IRaCIS.Api.Controllers.ExtraController.Login(IRaCIS.Application.Contracts.UserLoginDTO,ZiggyCreatures.Caching.Fusion.IFusionCache,IRaCIS.Core.Application.Service.IUserService,IRaCIS.Core.Application.Auth.ITokenService,IRaCIS.Core.Application.Contracts.IReadingImageTaskService,Microsoft.Extensions.Options.IOptionsMonitor{IRaCIS.Core.Domain.Share.ServiceVerifyConfigOption},Microsoft.Extensions.Options.IOptionsMonitor{IRaCIS.Core.Domain.Share.SystemEmailSendConfig},IRaCIS.Core.Application.Service.IMailVerificationService)">
|
||||
<summary> 系统用户登录接口[New] </summary>
|
||||
</member>
|
||||
<member name="M:IRaCIS.Api.Controllers.ExtraController.OAuthCallBack(System.String,System.String)">
|
||||
<summary>
|
||||
回调到前端,前端调用后端的接口
|
||||
参考链接:https://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html
|
||||
后端通过这个code ,带上客户端信息,和授权类型 可以向单点登录提供商,获取厂商token
|
||||
|
||||
但是单点登录提供商提供的token 和我们系统的token 是有区别的,我们的token里面有我们业务系统的UserId,涉及到很多业务操作,所以在此出现了两种方案
|
||||
1、前端使用厂商的Token。 后端通过code 获取厂商的Token 返回前端的同时返回我们系统的UserId,前段在http 请求头加上一个自定义参数,带上UserId 后端取用户Id的地方变动下,
|
||||
但是除了UserId外,后端还有其他信息也是从Token取的,所以在请求头也需要带上,此外后端认证Token的方式也需要变化,改造成本稍大(如果是微服务,做这种处理还是可以的)。
|
||||
2、前端还是使用我们后台自己的Token。后端通过code 获取厂商Token的同时,后端做一个隐藏登录,返回厂商的Token的同时,也返回我们系统的Token。
|
||||
(像我们单体,这种方式最简单,我们用单点登录,无非就是不想记多个系统的密码,自动登录而已,其他不支持的项目改造成本也是最低的)
|
||||
</summary>
|
||||
<param name="type">回调的厂商类型 比如github, google, 我们用的logto ,不同的厂商回调到前端的地址可以不同的,但是请求后端的接口可以是同一个 </param>
|
||||
<param name="code">在第三方平台登录成功后,回调前端的时候会返回一个code </param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:IRaCIS.Core.API.Controllers.Special.FinancialChangeController.AddOrUpdateTrialInspection(IRaCIS.Core.Application.Service.Inspection.DTO.DataInspectionDto{IRaCIS.Application.Contracts.TrialCommand})">
|
||||
<summary> 添加实验项目-返回新增Id[AUTH]</summary>
|
||||
<returns>新记录Id</returns>
|
||||
|
@ -290,7 +311,7 @@
|
|||
<param name="_studyMonitorRepository"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:IRaCIS.Core.API.Controllers.StudyController.UploadNoneDicomFile(IRaCIS.Core.API.Controllers.StudyController.UploadNoneDicomFileCommand,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.NoneDicomStudy},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.StudyMonitor},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.NoneDicomStudyFile})">
|
||||
<member name="M:IRaCIS.Core.API.Controllers.StudyController.UploadNoneDicomFile(IRaCIS.Core.API.Controllers.UploadNoneDicomFileCommand,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.NoneDicomStudy},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.StudyMonitor},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.NoneDicomStudyFile})">
|
||||
<summary>
|
||||
上传非Dicom 文件 支持压缩包 多文件上传
|
||||
</summary>
|
||||
|
@ -310,33 +331,6 @@
|
|||
<returns></returns>
|
||||
<exception cref="T:IRaCIS.Core.Infrastructure.BusinessValidationFailedException"></exception>
|
||||
</member>
|
||||
<member name="T:IRaCIS.Core.API.Controllers.FileController">
|
||||
<summary>医生文件上传下载</summary>
|
||||
</member>
|
||||
<member name="M:IRaCIS.Core.API.Controllers.FileController.GetOfficialResume(IRaCIS.Core.API.Controllers.GetDoctorPathCommand,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Attachment})">
|
||||
<summary>
|
||||
New 医生首页 多选 获取多个医生信息+文件路径列表 医生压缩包名称 doctorCode + "_" + doctorName _(时间戳或者随机的Guid)
|
||||
</summary>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:IRaCIS.Core.API.Controllers.FileController.GetTrialDoctorOfficialResume(IRaCIS.Core.API.Controllers.GetDoctorPathCommand,IRaCIS.Application.Interfaces.IDoctorService,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Attachment})">
|
||||
<summary>
|
||||
New 项目入组 勾选获取简历路径
|
||||
</summary>
|
||||
<param name="command"></param>
|
||||
<param name="_doctorService"></param>
|
||||
<param name="_attachmentrepository"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:IRaCIS.Core.API.Controllers.FileController.GetDoctorAttachment(IRaCIS.Core.API.Controllers.GetDoctoreAttachPathCommand,IRaCIS.Application.Interfaces.IDoctorService,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Attachment})">
|
||||
<summary>
|
||||
new 医生详情 勾选或者下载文件路径
|
||||
</summary>
|
||||
<param name="command"></param>
|
||||
<param name="_doctorService"></param>
|
||||
<param name="_attachmentrepository"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:IRaCIS.Core.API.Controllers.UploadDownLoadController.DownloadCommonFile(System.String,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.CommonDocument})">
|
||||
<summary> 通用文件下载 </summary>
|
||||
</member>
|
||||
|
@ -367,13 +361,10 @@
|
|||
序列化,反序列化的时候,处理时间 时区转换
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:IRaCIS.Core.API.NullToEmptyStringResolver.CreateProperties(System.Type,Newtonsoft.Json.MemberSerialization)">
|
||||
<member name="T:IRaCIS.Core.API.NullToEmptyStringResolver">
|
||||
<summary>
|
||||
创建属性
|
||||
LowerCamelCaseJsonAttribute 可以设置类小写返回给前端
|
||||
</summary>
|
||||
<param name="type">类型</param>
|
||||
<param name="memberSerialization">序列化成员</param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="T:ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt.CustomHSJWTService">
|
||||
<summary>
|
||||
|
|
|
@ -1,43 +1,34 @@
|
|||
using System;
|
||||
using Autofac.Extensions.DependencyInjection;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Serilog;
|
||||
using System.Threading.Tasks;
|
||||
using IRaCIS.Core.API;
|
||||
using IRaCIS.Core.API.HostService;
|
||||
using IRaCIS.Core.Application.BusinessFilter;
|
||||
using IRaCIS.Core.Application.Filter;
|
||||
using IRaCIS.Core.Application.MassTransit.Consumer;
|
||||
using IRaCIS.Core.Application.Service;
|
||||
using IRaCIS.Core.Application.Service.BusinessFilter;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using LogDashboard;
|
||||
using MassTransit;
|
||||
using MassTransit.NewIdProviders;
|
||||
using System.IO;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using IRaCIS.Core.API;
|
||||
using Autofac;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using IRaCIS.Core.Application.Filter;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using IRaCIS.Core.Application.Service.BackGroundJob;
|
||||
using LogDashboard;
|
||||
using FellowOakDicom.Network;
|
||||
using IRaCIS.Core.Application.Service.ImageAndDoc;
|
||||
using IP2Region.Net.Abstractions;
|
||||
using IP2Region.Net.XDB;
|
||||
using IRaCIS.Core.Application.BusinessFilter;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Newtonsoft.Json;
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using IRaCIS.Core.Application.MassTransit.Command;
|
||||
using IRaCIS.Core.Application.MassTransit.Consumer;
|
||||
using DocumentFormat.OpenXml.InkML;
|
||||
using IRaCIS.Core.Domain;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
|
||||
AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
|
||||
|
||||
#region 获取环境变量
|
||||
//以配置文件为准,否则 从url中取环境值(服务以命令行传递参数启动,配置文件配置了就不需要传递环境参数)
|
||||
var config = new ConfigurationBuilder()
|
||||
|
@ -56,29 +47,15 @@ if (string.IsNullOrWhiteSpace(enviromentName))
|
|||
}
|
||||
#endregion
|
||||
|
||||
// Serilog
|
||||
SerilogExtension.AddSerilogSetup(enviromentName);
|
||||
|
||||
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
|
||||
{
|
||||
|
||||
EnvironmentName = enviromentName
|
||||
});
|
||||
|
||||
#region 兼容windows 服务命令行的方式
|
||||
|
||||
//foreach (var arg in args)
|
||||
//{
|
||||
// Console.WriteLine(arg);
|
||||
//}
|
||||
|
||||
int urlsIndex = Array.FindIndex(args, arg => arg != null && arg.StartsWith("--urls"));
|
||||
|
||||
if (urlsIndex > -1)
|
||||
{
|
||||
var url = args[urlsIndex].Substring("--urls=".Length);
|
||||
Console.WriteLine(url);
|
||||
builder.WebHost.UseUrls(url);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 主机配置
|
||||
|
||||
|
||||
|
@ -87,50 +64,47 @@ NewId.SetProcessIdProvider(new CurrentProcessIdProvider());
|
|||
builder.Configuration.AddJsonFile(ConfigMapFileProvider.FromRelativePath(""), "appsettings.json", false, true)
|
||||
.AddJsonFile(ConfigMapFileProvider.FromRelativePath(""), $"appsettings.{enviromentName}.json", false, true);
|
||||
|
||||
builder.Host
|
||||
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
|
||||
.ConfigureContainer<ContainerBuilder>(containerBuilder =>
|
||||
{
|
||||
containerBuilder.RegisterModule<AutofacModuleSetup>();
|
||||
})
|
||||
.UseWindowsService().UseSerilog();
|
||||
builder.Host.UseSerilog();
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region 配置服务
|
||||
var _configuration = builder.Configuration;
|
||||
|
||||
//手动注册服务
|
||||
builder.Services.ConfigureServices(_configuration);
|
||||
|
||||
builder.Services.AddHostedService<HangfireHostService>();
|
||||
|
||||
//minimal api 异常处理
|
||||
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
|
||||
//builder.Services.AddProblemDetails();
|
||||
|
||||
//健康检查
|
||||
builder.Services.AddHealthChecks();
|
||||
builder.Services.AddSerilog();
|
||||
//本地化
|
||||
builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resources");
|
||||
|
||||
// 异常、参数统一验证过滤器、Json序列化配置、字符串参数绑型统一Trim()
|
||||
builder.Services.AddControllers(options =>
|
||||
{
|
||||
//options.Filters.Add<LogActionFilter>();
|
||||
options.Filters.Add<ModelActionFilter>();
|
||||
options.Filters.Add<ProjectExceptionFilter>();
|
||||
options.Filters.Add<UnitOfWorkFilter>();
|
||||
//options.Filters.Add<EncreptApiResultFilter>(10);
|
||||
options.Filters.Add<LimitUserRequestAuthorization>();
|
||||
|
||||
options.Filters.Add<TrialGlobalLimitActionFilter>();
|
||||
|
||||
})
|
||||
.AddNewtonsoftJsonSetup(builder.Services); // NewtonsoftJson 序列化 处理
|
||||
|
||||
builder.Services.AddOptions().Configure<SystemEmailSendConfig>(_configuration.GetSection("SystemEmailSendConfig"));
|
||||
builder.Services.AddOptions().Configure<ServiceVerifyConfigOption>(_configuration.GetSection("BasicSystemConfig"));
|
||||
builder.Services.AddOptions().Configure<AliyunOSSOptions>(_configuration.GetSection("AliyunOSS"));
|
||||
builder.Services.AddOptions().Configure<ObjectStoreServiceOptions>(_configuration.GetSection("ObjectStoreService"));
|
||||
builder.Services.AddOptions().Configure<IRCEncreptOption>(_configuration.GetSection("EncrypteResponseConfig"));
|
||||
builder.Services.AddOptions().Configure<SystemPacsConfig>(_configuration.GetSection("SystemPacsConfig"));
|
||||
|
||||
builder.Services.Configure<IRaCISBasicConfigOption>(_configuration.GetSection("IRaCISBasicConfig"));
|
||||
|
||||
|
||||
//动态WebApi + UnifiedApiResultFilter 省掉控制器代码
|
||||
// Panda动态WebApi + UnifiedApiResultFilter + 省掉控制器代码
|
||||
builder.Services.AddDynamicWebApiSetup();
|
||||
//MinimalAPI
|
||||
builder.Services.AddMasaMinimalAPiSetUp();
|
||||
|
||||
//AutoMapper
|
||||
builder.Services.AddAutoMapperSetup();
|
||||
//EF ORM QueryWithNoLock
|
||||
|
@ -142,91 +116,33 @@ builder.Services.AddSwaggerSetup();
|
|||
//JWT Token 验证
|
||||
builder.Services.AddJWTAuthSetup(_configuration);
|
||||
|
||||
// MediatR 进程内消息 事件解耦 从程序集中 注册命令和handler对应关系
|
||||
//builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining<ConsistencyVerificationHandler>());
|
||||
|
||||
#region MassTransit
|
||||
//masstransit组件 也支持MediatR 中介者模式,但是支持分布式,考虑后续,所以在次替代MediatR
|
||||
//参考链接:https://masstransit.io/documentation/concepts/mediator#scoped-mediator
|
||||
builder.Services.AddMediator(cfg =>
|
||||
{
|
||||
cfg.AddConsumer<ConsistencyCheckConsumer>();
|
||||
cfg.AddConsumer<AddSubjectTriggerConsumer>();
|
||||
cfg.AddConsumer<AddSubjectTriggerConsumer2>();
|
||||
//cfg.ConfigureMediator((context, cfg) => cfg.UseHttpContextScopeFilter(context));
|
||||
});
|
||||
|
||||
//添加 MassTransit 和 InMemory 传输
|
||||
builder.Services.AddMassTransit(cfg =>
|
||||
{
|
||||
// 注册消费者
|
||||
cfg.AddConsumer<AddSubjectTriggerConsumer>(); // 替换为你的消费者类
|
||||
cfg.AddConsumer<AddSubjectTriggerConsumer2>();
|
||||
|
||||
// 使用 InMemory 作为消息传递机制
|
||||
cfg.UsingInMemory((context, cfg) =>
|
||||
{
|
||||
// 这里可以进行额外的配置
|
||||
cfg.ConfigureEndpoints(context); // 自动配置所有消费者的端点
|
||||
});
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region FusionCache
|
||||
//MassTransit
|
||||
builder.Services.AddMassTransitSetup();
|
||||
|
||||
// FusionCache
|
||||
builder.Services.AddFusionCache();
|
||||
|
||||
#endregion
|
||||
|
||||
// EasyCaching 缓存
|
||||
//builder.Services.AddEasyCachingSetup(_configuration);
|
||||
|
||||
// hangfire 定时任务框架 有界面,更友好~
|
||||
builder.Services.AddhangfireSetup(_configuration);
|
||||
|
||||
|
||||
//Serilog 日志可视化 LogDashboard日志
|
||||
builder.Services.AddLogDashboardSetup();
|
||||
|
||||
|
||||
builder.Services.AddJsonConfigSetup(_configuration);
|
||||
//转发头设置 获取真实IP
|
||||
builder.Services.Configure<ForwardedHeadersOptions>(options =>
|
||||
{
|
||||
options.ForwardedHeaders =
|
||||
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
|
||||
});
|
||||
//Dicom影像渲染图片 跨平台
|
||||
builder.Services.AddDicomSetup();
|
||||
|
||||
// 实时应用
|
||||
builder.Services.AddSignalR();
|
||||
|
||||
builder.Services.AddSingleton<IUserIdProvider, IRaCISUserIdProvider>();
|
||||
|
||||
|
||||
builder.Services.AddSingleton<ISearcher>(new Searcher(CachePolicy.Content, Path.Combine(AppContext.BaseDirectory, StaticData.Folder.Resources, "ip2region.xdb")));
|
||||
|
||||
//builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
|
||||
//builder.Services.AddProblemDetails();
|
||||
|
||||
|
||||
#region 历史废弃配置
|
||||
//builder.Services.AddMemoryCache();
|
||||
////上传限制 配置
|
||||
//builder.Services.Configure<FormOptions>(options =>
|
||||
//// 添加反伪造服务
|
||||
//builder.Services.AddAntiforgery(options =>
|
||||
//{
|
||||
// options.MultipartBodyLengthLimit = int.MaxValue;
|
||||
// options.ValueCountLimit = int.MaxValue;
|
||||
// options.ValueLengthLimit = int.MaxValue;
|
||||
// // 可选:设置自定义的头部名称以支持 AJAX 请求等
|
||||
// options.HeaderName = "X-XSRF-TOKEN";
|
||||
//});
|
||||
//IP 限流 可设置白名单 或者黑名单
|
||||
//services.AddIpPolicyRateLimitSetup(_configuration);
|
||||
// 用户类型 策略授权
|
||||
//services.AddAuthorizationPolicySetup(_configuration);
|
||||
#endregion
|
||||
|
||||
//builder.Services.AddAntiforgery();
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -235,9 +151,8 @@ var env = app.Environment;
|
|||
|
||||
#region 配置中间件
|
||||
|
||||
app.UseMiddleware<EncryptionRequestMiddleware>();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
app.UseMiddleware<EncryptionRequestMiddleware>();
|
||||
|
||||
#region 异常处理 全局业务异常已统一处理了,非业务错误会来到这里 400 -500状态码
|
||||
|
||||
|
@ -260,54 +175,8 @@ app.UseStatusCodePages(async context =>
|
|||
|
||||
});
|
||||
|
||||
//app.UseExceptionHandler(o => { });
|
||||
|
||||
//这里没生效,原因未知,官方文档也是这种写法,也用了GlobalExceptionHandler 尝试,还是不行,怀疑框架bug
|
||||
//app.UseExceptionHandler(configure =>
|
||||
//{
|
||||
// configure.Run(async context =>
|
||||
// {
|
||||
// var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
|
||||
//var ex = exceptionHandlerPathFeature?.Error;
|
||||
//context.Response.ContentType = "application/json";
|
||||
|
||||
//if (ex != null)
|
||||
//{
|
||||
// var errorInfo = $"Exception: {ex.Message}[{ex.StackTrace}]" + (ex.InnerException != null ? $" InnerException: {ex.InnerException.Message}[{ex.InnerException.StackTrace}]" : "");
|
||||
|
||||
// await context.Response.WriteAsync(JsonConvert.SerializeObject(ResponseOutput.NotOk($"{ex?.Message}")));
|
||||
|
||||
// Log.Logger.Error(errorInfo);
|
||||
|
||||
|
||||
//}
|
||||
|
||||
// });
|
||||
//});
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
//本地化
|
||||
app.UseLocalization();
|
||||
|
||||
app.UseForwardedHeaders();
|
||||
|
||||
//响应压缩
|
||||
app.UseResponseCompression();
|
||||
|
||||
|
||||
//不需要 token 访问的静态文件 wwwroot css, JavaScript, and images don't require authentication.
|
||||
app.UseStaticFiles();
|
||||
|
||||
|
||||
|
||||
//LogDashboard
|
||||
app.UseLogDashboard("/LogDashboard");
|
||||
|
||||
//hangfire
|
||||
app.UseHangfireConfig(env);
|
||||
//app.UseExceptionHandler();
|
||||
app.UseExceptionHandler(o => { });
|
||||
|
||||
#region 暂时废弃
|
||||
|
||||
|
@ -322,46 +191,61 @@ app.UseHangfireConfig(env);
|
|||
//{
|
||||
// //app.UseHsts();
|
||||
//}
|
||||
|
||||
//app.UseIRacisHostStaticFileStore(env);
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
app.UseIRacisHostStaticFileStore(env);
|
||||
|
||||
//本地化
|
||||
await app.UseLocalization(app.Services);
|
||||
|
||||
app.UseForwardedHeaders();
|
||||
|
||||
//响应压缩
|
||||
app.UseResponseCompression();
|
||||
|
||||
//不需要 token 访问的静态文件 wwwroot css, JavaScript, and images don't require authentication.
|
||||
app.UseStaticFiles();
|
||||
|
||||
//LogDashboard
|
||||
app.UseLogDashboard("/LogDashboard");
|
||||
|
||||
//hangfire
|
||||
app.UseHangfireConfig(env);
|
||||
|
||||
// Swagger
|
||||
SwaggerSetup.Configure(app, env);
|
||||
|
||||
|
||||
////serilog 记录请求的用户信息
|
||||
//serilog 记录请求的用户信息
|
||||
app.UseSerilogConfig(env);
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseCors(t => t.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
|
||||
|
||||
//app.UseIRacisHostStaticFileStore(env);
|
||||
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
|
||||
//Map MinimalAPI routes
|
||||
app.MapMasaMinimalAPIs();
|
||||
|
||||
//// 这里添加反伪造中间件
|
||||
//app.UseAntiforgery();
|
||||
app.MapControllers();
|
||||
|
||||
app.MapHub<UploadHub>("/UploadHub");
|
||||
app.MapHealthChecks("/health");
|
||||
|
||||
|
||||
// Serilog
|
||||
SerilogExtension.AddSerilogSetup(enviromentName, app.Services);
|
||||
|
||||
|
||||
var hangfireJobService = app.Services.GetRequiredService<IIRaCISHangfireJob>();
|
||||
|
||||
await hangfireJobService.InitHangfireJobTaskAsync();
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
#region 运行环境 部署平台
|
||||
|
@ -381,9 +265,6 @@ try
|
|||
Log.Logger.Warning($"当前部署平台环境:OSX or FreeBSD");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
Log.Logger.Warning($"ContentRootPath:{env.ContentRootPath}");
|
||||
|
||||
|
||||
|
@ -394,10 +275,11 @@ try
|
|||
|
||||
//Log.Logger.Warning($"ContentRootPath——GetParent:{Directory.GetParent(env.ContentRootPath).Parent.FullName}");
|
||||
//Log.Logger.Warning($"ContentRootPath——xx:{Path.GetDirectoryName(Path.GetDirectoryName(env.ContentRootPath))}");
|
||||
|
||||
#endregion
|
||||
|
||||
app.Run();
|
||||
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
@ -38,7 +38,7 @@ namespace IRaCIS.Core.API
|
|||
public override Task OnConnectedAsync()
|
||||
{
|
||||
//base.Context.User.id
|
||||
|
||||
|
||||
|
||||
_logger.LogError("连接: " + Context.ConnectionId);
|
||||
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using System;
|
||||
|
||||
namespace IRaCIS.Core.API.Filter
|
||||
{
|
||||
public class EnableBufferingAttribute : Attribute, IResourceFilter
|
||||
{
|
||||
public void OnResourceExecuting(ResourceExecutingContext context)
|
||||
{
|
||||
context.HttpContext.Request.EnableBuffering();
|
||||
}
|
||||
|
||||
public void OnResourceExecuted(ResourceExecutedContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,270 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace IRaCIS.Core.API.Utility
|
||||
{
|
||||
public static class FileHelpers
|
||||
{
|
||||
private static readonly byte[] _allowedChars = { };
|
||||
// For more file signatures, see the File Signatures Database (https://www.filesignatures.net/)
|
||||
// and the official specifications for the file types you wish to add.
|
||||
private static readonly Dictionary<string, List<byte[]>> _fileSignature = new Dictionary<string, List<byte[]>>
|
||||
{
|
||||
{ ".gif", new List<byte[]> { new byte[] { 0x47, 0x49, 0x46, 0x38 } } },
|
||||
{ ".png", new List<byte[]> { new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A } } },
|
||||
{ ".jpeg", new List<byte[]>
|
||||
{
|
||||
new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
|
||||
new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
|
||||
new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
|
||||
}
|
||||
},
|
||||
{ ".jpg", new List<byte[]>
|
||||
{
|
||||
new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
|
||||
new byte[] { 0xFF, 0xD8, 0xFF, 0xE1 },
|
||||
new byte[] { 0xFF, 0xD8, 0xFF, 0xE8 },
|
||||
}
|
||||
},
|
||||
{ ".zip", new List<byte[]>
|
||||
{
|
||||
new byte[] { 0x50, 0x4B, 0x03, 0x04 },
|
||||
new byte[] { 0x50, 0x4B, 0x4C, 0x49, 0x54, 0x45 },
|
||||
new byte[] { 0x50, 0x4B, 0x53, 0x70, 0x58 },
|
||||
new byte[] { 0x50, 0x4B, 0x05, 0x06 },
|
||||
new byte[] { 0x50, 0x4B, 0x07, 0x08 },
|
||||
new byte[] { 0x57, 0x69, 0x6E, 0x5A, 0x69, 0x70 },
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// **WARNING!**
|
||||
// In the following file processing methods, the file's content isn't scanned.
|
||||
// In most production scenarios, an anti-virus/anti-malware scanner API is
|
||||
// used on the file before making the file available to users or other
|
||||
// systems. For more information, see the topic that accompanies this sample
|
||||
// app.
|
||||
|
||||
public static async Task<byte[]> ProcessFormFile<T>(IFormFile formFile,
|
||||
ModelStateDictionary modelState, string[] permittedExtensions,
|
||||
long sizeLimit)
|
||||
{
|
||||
var fieldDisplayName = string.Empty;
|
||||
|
||||
// Use reflection to obtain the display name for the model
|
||||
// property associated with this IFormFile. If a display
|
||||
// name isn't found, error messages simply won't show
|
||||
// a display name.
|
||||
MemberInfo property =
|
||||
typeof(T).GetProperty(
|
||||
formFile.Name.Substring(formFile.Name.IndexOf(".",
|
||||
StringComparison.Ordinal) + 1));
|
||||
|
||||
if (property != null)
|
||||
{
|
||||
if (property.GetCustomAttribute(typeof(DisplayAttribute)) is
|
||||
DisplayAttribute displayAttribute)
|
||||
{
|
||||
fieldDisplayName = $"{displayAttribute.Name} ";
|
||||
}
|
||||
}
|
||||
|
||||
// Don't trust the file name sent by the client. To display
|
||||
// the file name, HTML-encode the value.
|
||||
var trustedFileNameForDisplay = WebUtility.HtmlEncode(
|
||||
formFile.FileName);
|
||||
|
||||
// Check the file length. This check doesn't catch files that only have
|
||||
// a BOM as their content.
|
||||
if (formFile.Length == 0)
|
||||
{
|
||||
modelState.AddModelError(formFile.Name,
|
||||
$"{fieldDisplayName}({trustedFileNameForDisplay}) is empty.");
|
||||
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
if (formFile.Length > sizeLimit)
|
||||
{
|
||||
var megabyteSizeLimit = sizeLimit / 1048576;
|
||||
modelState.AddModelError(formFile.Name,
|
||||
$"{fieldDisplayName}({trustedFileNameForDisplay}) exceeds " +
|
||||
$"{megabyteSizeLimit:N1} MB.");
|
||||
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
await formFile.CopyToAsync(memoryStream);
|
||||
|
||||
// Check the content length in case the file's only
|
||||
// content was a BOM and the content is actually
|
||||
// empty after removing the BOM.
|
||||
if (memoryStream.Length == 0)
|
||||
{
|
||||
modelState.AddModelError(formFile.Name,
|
||||
$"{fieldDisplayName}({trustedFileNameForDisplay}) is empty.");
|
||||
}
|
||||
|
||||
if (!IsValidFileExtensionAndSignature(
|
||||
formFile.FileName, memoryStream, permittedExtensions))
|
||||
{
|
||||
modelState.AddModelError(formFile.Name,
|
||||
$"{fieldDisplayName}({trustedFileNameForDisplay}) file " +
|
||||
"type isn't permitted or the file's signature " +
|
||||
"doesn't match the file's extension.");
|
||||
}
|
||||
else
|
||||
{
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
modelState.AddModelError(formFile.Name,
|
||||
$"{fieldDisplayName}({trustedFileNameForDisplay}) upload failed. " +
|
||||
$"Please contact the Help Desk for support. Error: {ex.HResult}");
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
public static async Task<byte[]> ProcessStreamedFile(
|
||||
MultipartSection section, ContentDispositionHeaderValue contentDisposition,
|
||||
ModelStateDictionary modelState, string[] permittedExtensions, long sizeLimit)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
await section.Body.CopyToAsync(memoryStream);
|
||||
|
||||
// Check if the file is empty or exceeds the size limit.
|
||||
if (memoryStream.Length == 0)
|
||||
{
|
||||
modelState.AddModelError("File", "The file is empty.");
|
||||
}
|
||||
else if (memoryStream.Length > sizeLimit)
|
||||
{
|
||||
var megabyteSizeLimit = sizeLimit / 1048576;
|
||||
modelState.AddModelError("File",
|
||||
$"The file exceeds {megabyteSizeLimit:N1} MB.");
|
||||
}
|
||||
else if (!IsValidFileExtensionAndSignature(
|
||||
contentDisposition.FileName.Value, memoryStream,
|
||||
permittedExtensions))
|
||||
{
|
||||
modelState.AddModelError("File",
|
||||
"The file type isn't permitted or the file's " +
|
||||
"signature doesn't match the file's extension.");
|
||||
}
|
||||
else
|
||||
{
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
modelState.AddModelError("File",
|
||||
"The upload failed. Please contact the Help Desk " +
|
||||
$" for support. Error: {ex.HResult}");
|
||||
// Log the exception
|
||||
}
|
||||
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
private static bool IsValidFileExtensionAndSignature(string fileName, Stream data, string[] permittedExtensions)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fileName) || data == null || data.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var ext = Path.GetExtension(fileName).ToLowerInvariant();
|
||||
|
||||
if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
data.Position = 0;
|
||||
|
||||
using (var reader = new BinaryReader(data))
|
||||
{
|
||||
if (ext.Equals(".txt") || ext.Equals(".csv") || ext.Equals(".prn"))
|
||||
{
|
||||
if (_allowedChars.Length == 0)
|
||||
{
|
||||
// Limits characters to ASCII encoding.
|
||||
for (var i = 0; i < data.Length; i++)
|
||||
{
|
||||
if (reader.ReadByte() > sbyte.MaxValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Limits characters to ASCII encoding and
|
||||
// values of the _allowedChars array.
|
||||
for (var i = 0; i < data.Length; i++)
|
||||
{
|
||||
var b = reader.ReadByte();
|
||||
if (b > sbyte.MaxValue ||
|
||||
!_allowedChars.Contains(b))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Uncomment the following code block if you must permit
|
||||
// files whose signature isn't provided in the _fileSignature
|
||||
// dictionary. We recommend that you add file signatures
|
||||
// for files (when possible) for all file types you intend
|
||||
// to allow on the system and perform the file signature
|
||||
// check.
|
||||
|
||||
//if (!_fileSignature.ContainsKey(ext))
|
||||
//{
|
||||
// return true;
|
||||
//}
|
||||
|
||||
|
||||
// File signature check
|
||||
// --------------------
|
||||
// With the file signatures provided in the _fileSignature
|
||||
// dictionary, the following code tests the input content's
|
||||
// file signature.
|
||||
|
||||
//var signatures = _fileSignature[ext];
|
||||
//var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));
|
||||
|
||||
//return signatures.Any(signature =>
|
||||
// headerBytes.Take(signature.Length).SequenceEqual(signature));
|
||||
|
||||
//test
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,9 @@
|
|||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt
|
||||
{
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt
|
||||
{
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt
|
||||
namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt
|
||||
{
|
||||
public interface ICustomJWTService
|
||||
{
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt
|
||||
namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt
|
||||
{
|
||||
public class JWTTokenOptions
|
||||
{
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ZhaoXi._001.NET5Demo.Practice.WebApi.Utility.Jwt
|
||||
{
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace IRaCIS.Core.API.Utility
|
||||
{
|
||||
public static class MultipartRequestHelper
|
||||
{
|
||||
// Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
|
||||
// The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
|
||||
public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
|
||||
{
|
||||
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(boundary))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
"Missing content-type boundary.");
|
||||
}
|
||||
|
||||
if (boundary.Length > lengthLimit)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Multipart boundary length limit {lengthLimit} exceeded.");
|
||||
}
|
||||
|
||||
return boundary;
|
||||
}
|
||||
|
||||
public static bool IsMultipartContentType(string contentType)
|
||||
{
|
||||
return !string.IsNullOrEmpty(contentType)
|
||||
&& contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
|
||||
{
|
||||
// Content-Disposition: form-data; name="key";
|
||||
return contentDisposition != null
|
||||
&& contentDisposition.DispositionType.Equals("form-data")
|
||||
&& string.IsNullOrEmpty(contentDisposition.FileName.Value)
|
||||
&& string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
|
||||
}
|
||||
|
||||
public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
|
||||
{
|
||||
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
|
||||
return contentDisposition != null
|
||||
&& contentDisposition.DispositionType.Equals("form-data")
|
||||
&& (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
|
||||
|| !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,6 @@
|
|||
using Hangfire;
|
||||
using Hangfire.Dashboard;
|
||||
using Hangfire.Dashboard.BasicAuthorization;
|
||||
using IRaCIS.Core.Application.Service.BackGroundJob;
|
||||
using IRaCIS.Core.API.Filter;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
|
@ -39,7 +36,7 @@ namespace IRaCIS.Core.API
|
|||
})
|
||||
},
|
||||
|
||||
DashboardTitle ="后台任务管理",
|
||||
DashboardTitle = "后台任务管理",
|
||||
|
||||
|
||||
//Authorization = new BasicAuthAuthorizationFilter[] {
|
||||
|
|
|
@ -1,21 +1,12 @@
|
|||
using Azure;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.FileProviders.Physical;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Hosting.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.VisualBasic;
|
||||
using Newtonsoft.Json;
|
||||
using SharpCompress.Common;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -32,7 +23,7 @@ namespace IRaCIS.Core.API
|
|||
|
||||
private string iRaCISDefaultDataFolder = string.Empty;
|
||||
|
||||
public MultiDiskStaticFilesMiddleware(RequestDelegate next, IWebHostEnvironment hostingEnv, ILoggerFactory loggerFactory)
|
||||
public MultiDiskStaticFilesMiddleware(RequestDelegate next, IWebHostEnvironment hostingEnv, ILoggerFactory loggerFactory)
|
||||
{
|
||||
_next = next;
|
||||
_hostingEnv = hostingEnv;
|
||||
|
@ -89,7 +80,7 @@ namespace IRaCIS.Core.API
|
|||
if (defaultFileProvider.GetFileInfo(path).Exists)
|
||||
{
|
||||
|
||||
var actrualPath = defaultFileProvider.GetFileInfo(path).PhysicalPath;
|
||||
var actrualPath = defaultFileProvider.GetFileInfo(path).PhysicalPath;
|
||||
|
||||
await context.Response.SendFileAsync(new PhysicalFileInfo(new FileInfo(actrualPath)));
|
||||
|
||||
|
@ -110,7 +101,7 @@ namespace IRaCIS.Core.API
|
|||
.Where(d => d.IsReady && d.DriveType == DriveType.Fixed)/*.Where(t => !t.Name.Contains("C") && !t.Name.Contains("c"))*/
|
||||
.OrderBy(d => d.AvailableFreeSpace)
|
||||
.Select(d => d.RootDirectory.FullName)
|
||||
.ToArray().Where(t=>!t.Contains(defaultRoot));
|
||||
.ToArray().Where(t => !t.Contains(defaultRoot));
|
||||
|
||||
|
||||
foreach (var item in disks)
|
||||
|
@ -121,10 +112,10 @@ namespace IRaCIS.Core.API
|
|||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var otherFileProvider= new PhysicalFileProvider(otherFileStoreFolder);
|
||||
|
||||
|
||||
var otherFileProvider = new PhysicalFileProvider(otherFileStoreFolder);
|
||||
|
||||
|
||||
|
||||
if (otherFileProvider.GetFileInfo(path).Exists)
|
||||
{
|
||||
|
@ -132,7 +123,7 @@ namespace IRaCIS.Core.API
|
|||
var actrualPath = otherFileProvider.GetFileInfo(path).PhysicalPath;
|
||||
|
||||
//方式一
|
||||
await context.Response.SendFileAsync( new PhysicalFileInfo(new FileInfo(actrualPath)));
|
||||
await context.Response.SendFileAsync(new PhysicalFileInfo(new FileInfo(actrualPath)));
|
||||
|
||||
#region 方式二 报错 otherFileProvider 应该还包含/{StaticData.Folder.IRaCISDataFolder} 这一层级
|
||||
//var otherStaticFileOptions = new StaticFileOptions
|
||||
|
@ -158,7 +149,7 @@ namespace IRaCIS.Core.API
|
|||
|
||||
await context.Response.WriteAsync(JsonConvert.SerializeObject(ResponseOutput.NotOk("File not found")));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 如果所有磁盘都不存在所请求的文件,则将请求传递给下一个中间件组件。
|
||||
await _next.Invoke(context);
|
||||
|
|
|
@ -1,19 +1,28 @@
|
|||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Localization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
public static class LocalizationConfig
|
||||
{
|
||||
|
||||
public static void UseLocalization(this IApplicationBuilder app)
|
||||
public static async Task UseLocalization(this IApplicationBuilder app,IServiceProvider serviceProvider)
|
||||
{
|
||||
var supportedCultures = new List<CultureInfo>
|
||||
{
|
||||
|
||||
|
||||
new CultureInfo(StaticData.CultureInfo.en_US),
|
||||
new CultureInfo(StaticData.CultureInfo.zh_CN)
|
||||
};
|
||||
|
@ -31,6 +40,28 @@ namespace IRaCIS.Core.API
|
|||
//options.RequestCultureProviders.RemoveAt(1);
|
||||
|
||||
app.UseRequestLocalization(options);
|
||||
|
||||
//设置国际化I18n
|
||||
var localizer = serviceProvider.GetRequiredService<IStringLocalizer>();
|
||||
I18n.SetLocalizer(localizer);
|
||||
|
||||
//初始化国际化
|
||||
|
||||
var _internationalizationRepository = serviceProvider.GetRequiredService<IRepository<Internationalization>>();
|
||||
|
||||
//查询数据库的数据
|
||||
var toJsonList = await _internationalizationRepository.Where(t => t.InternationalizationType == 1).Select(t => new IRCGlobalInfoDTO()
|
||||
{
|
||||
Code = t.Code,
|
||||
Value = t.Value,
|
||||
ValueCN = t.ValueCN,
|
||||
Description = t.Description
|
||||
}).ToListAsync();
|
||||
|
||||
|
||||
await InternationalizationHelper.BatchAddJsonKeyValueAsync(toJsonList);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ using LogDashboard.Authorization;
|
|||
|
||||
namespace IRaCIS.Core.API.Filter
|
||||
{
|
||||
|
||||
|
||||
public class LogDashBoardAuthFilter : ILogDashboardAuthorizationFilter
|
||||
{
|
||||
//在此可以利用 本系统的UerTypeEnum 判断
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Serilog;
|
||||
using Serilog.Context;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
using Hangfire;
|
||||
using Hangfire.Dashboard;
|
||||
using IRaCIS.Core.API._PipelineExtensions.Serilog;
|
||||
using IRaCIS.Core.API._PipelineExtensions.Serilog;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Serilog;
|
||||
using System.Linq;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
|
||||
|
||||
|
||||
public static class SerilogConfig
|
||||
{
|
||||
|
@ -19,8 +20,66 @@ namespace IRaCIS.Core.API
|
|||
app.UseSerilogRequestLogging(opts
|
||||
=>
|
||||
{
|
||||
opts.MessageTemplate = "{TokenUserRealName} {TokenUserTypeShortName} {ClientIp} {LocalIP} {Host} {Protocol} {RequestMethod} {RequestPath} {RequestBody} responded {StatusCode} in {Elapsed:0.0000} ms";
|
||||
opts.EnrichDiagnosticContext = SerilogHelper.EnrichFromRequest;
|
||||
|
||||
opts.MessageTemplate = "{FullName} {UserType} {UserIp} {Host} {RequestMethod} {RequestPath} {RequestBody} responded {StatusCode} in {Elapsed:0.0000} ms";
|
||||
|
||||
opts.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
|
||||
{
|
||||
|
||||
var request = httpContext.Request;
|
||||
|
||||
// Set all the common properties available for every request
|
||||
diagnosticContext.Set("Host", request.Host.Value);
|
||||
|
||||
|
||||
// Only set it if available. You're not sending sensitive data in a querystring right?!
|
||||
if (request.QueryString.HasValue)
|
||||
{
|
||||
diagnosticContext.Set("QueryString", request.QueryString.Value);
|
||||
}
|
||||
|
||||
diagnosticContext.Set("FullName", httpContext?.User?.FindFirst(JwtIRaCISClaimType.RealName)?.Value);
|
||||
|
||||
diagnosticContext.Set("UserType", httpContext?.User?.FindFirst(JwtIRaCISClaimType.UserTypeShortName)?.Value);
|
||||
|
||||
var clientIp = httpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault() ??
|
||||
httpContext.Connection.RemoteIpAddress?.ToString();
|
||||
|
||||
if (clientIp.StartsWith("::ffff:"))
|
||||
{
|
||||
clientIp = clientIp.Substring(7); // 移除前缀
|
||||
}
|
||||
|
||||
diagnosticContext.Set("UserIp", clientIp);
|
||||
|
||||
#region 非必要不记录
|
||||
//diagnosticContext.Set("Protocol", request.Protocol);
|
||||
//diagnosticContext.Set("Scheme", request.Scheme);
|
||||
//// Retrieve the IEndpointFeature selected for the request
|
||||
//var endpoint = httpContext.GetEndpoint();
|
||||
//if (endpoint is object) // endpoint != null
|
||||
//{
|
||||
// diagnosticContext.Set("EndpointName", endpoint.DisplayName);
|
||||
//}
|
||||
// Set the content-type of the Response at this point
|
||||
//diagnosticContext.Set("ContentType", httpContext.Response.ContentType);
|
||||
#endregion
|
||||
|
||||
|
||||
#region old 未用
|
||||
//这种获取的Ip不准 配置服务才行
|
||||
//diagnosticContext.Set("RequestIP", httpContext.Connection.RemoteIpAddress.ToString());
|
||||
|
||||
//这种方式可以,但是serilog提供了 就不用了
|
||||
//diagnosticContext.Set("TestIP", httpContext.GetUserIp());
|
||||
|
||||
//这种方式不行 读取的body为空字符串 必须在中间件中读取
|
||||
//diagnosticContext.Set("RequestBody", await ReadRequestBody(httpContext.Request));
|
||||
//diagnosticContext.Set("RequestBody", RequestPayload);
|
||||
#endregion
|
||||
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
public class SerilogHelper
|
||||
{
|
||||
//public static string RequestPayload = "";
|
||||
|
||||
public static void EnrichFromRequest(IDiagnosticContext diagnosticContext, HttpContext httpContext)
|
||||
{
|
||||
var request = httpContext.Request;
|
||||
|
||||
// Set all the common properties available for every request
|
||||
diagnosticContext.Set("Host", request.Host);
|
||||
|
||||
diagnosticContext.Set("Protocol", request.Protocol);
|
||||
diagnosticContext.Set("Scheme", request.Scheme);
|
||||
|
||||
#region old 未用
|
||||
//这种获取的Ip不准 配置服务才行
|
||||
//diagnosticContext.Set("RequestIP", httpContext.Connection.RemoteIpAddress.ToString());
|
||||
|
||||
//这种方式可以,但是serilog提供了 就不用了
|
||||
//diagnosticContext.Set("TestIP", httpContext.GetUserIp());
|
||||
|
||||
//这种方式不行 读取的body为空字符串 必须在中间件中读取
|
||||
//diagnosticContext.Set("RequestBody", await ReadRequestBody(httpContext.Request));
|
||||
//diagnosticContext.Set("RequestBody", RequestPayload);
|
||||
#endregion
|
||||
|
||||
// Only set it if available. You're not sending sensitive data in a querystring right?!
|
||||
if (request.QueryString.HasValue)
|
||||
{
|
||||
diagnosticContext.Set("QueryString", request.QueryString.Value);
|
||||
}
|
||||
|
||||
// Set the content-type of the Response at this point
|
||||
diagnosticContext.Set("ContentType", httpContext.Response.ContentType);
|
||||
|
||||
diagnosticContext.Set("TokenUserRealName", httpContext?.User?.FindFirst(JwtIRaCISClaimType.RealName)?.Value);
|
||||
|
||||
diagnosticContext.Set("TokenUserTypeShortName", httpContext?.User?.FindFirst(JwtIRaCISClaimType.UserTypeShortName)?.Value);
|
||||
|
||||
// Retrieve the IEndpointFeature selected for the request
|
||||
var endpoint = httpContext.GetEndpoint();
|
||||
if (endpoint is object) // endpoint != null
|
||||
{
|
||||
diagnosticContext.Set("EndpointName", endpoint.DisplayName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ namespace IRaCIS.Core.API
|
|||
{
|
||||
policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(), ((int)UserTypeEnum.IQC).ToString());
|
||||
});
|
||||
|
||||
|
||||
|
||||
options.AddPolicy(IRaCISPolicy.PM, policyBuilder =>
|
||||
{
|
||||
|
@ -54,7 +54,7 @@ namespace IRaCIS.Core.API
|
|||
|
||||
options.AddPolicy(IRaCISPolicy.PM_APM_CRC_QC, policyBuilder =>
|
||||
{
|
||||
policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(),((int)UserTypeEnum.ClinicalResearchCoordinator).ToString(), ((int)UserTypeEnum.APM).ToString(), ((int)UserTypeEnum.IQC).ToString());
|
||||
policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(), ((int)UserTypeEnum.ClinicalResearchCoordinator).ToString(), ((int)UserTypeEnum.APM).ToString(), ((int)UserTypeEnum.IQC).ToString());
|
||||
});
|
||||
|
||||
options.AddPolicy(IRaCISPolicy.SPM_CPM, policyBuilder =>
|
||||
|
@ -64,23 +64,23 @@ namespace IRaCIS.Core.API
|
|||
|
||||
options.AddPolicy(IRaCISPolicy.PM_APM_SPM_CPM, policyBuilder =>
|
||||
{
|
||||
policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(), ((int)UserTypeEnum.APM).ToString(),((int)UserTypeEnum.SPM).ToString(), ((int)UserTypeEnum.CPM).ToString());
|
||||
policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(), ((int)UserTypeEnum.APM).ToString(), ((int)UserTypeEnum.SPM).ToString(), ((int)UserTypeEnum.CPM).ToString());
|
||||
});
|
||||
|
||||
options.AddPolicy(IRaCISPolicy.PM_APM_SPM_CPM_SMM_CMM, policyBuilder =>
|
||||
{
|
||||
policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(), ((int)UserTypeEnum.APM).ToString(), ((int)UserTypeEnum.SPM).ToString(),
|
||||
policyBuilder.RequireClaim("userTypeEnumInt", ((int)UserTypeEnum.ProjectManager).ToString(), ((int)UserTypeEnum.APM).ToString(), ((int)UserTypeEnum.SPM).ToString(),
|
||||
((int)UserTypeEnum.CPM).ToString(), ((int)UserTypeEnum.SMM).ToString(), ((int)UserTypeEnum.CMM).ToString());
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -14,13 +14,13 @@ namespace IRaCIS.Core.API
|
|||
public class ApiResponseHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
{
|
||||
public IStringLocalizer _localizer;
|
||||
public ApiResponseHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
IStringLocalizer localizer,
|
||||
ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder)
|
||||
public ApiResponseHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
IStringLocalizer localizer,
|
||||
ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder)
|
||||
{
|
||||
_localizer = localizer;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
|
@ -30,7 +30,7 @@ namespace IRaCIS.Core.API
|
|||
{
|
||||
Response.ContentType = "application/json";
|
||||
Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
//---您无权访问该接口
|
||||
//---您无权访问该接口
|
||||
await Response.WriteAsync(JsonConvert.SerializeObject(ResponseOutput.NotOk(_localizer["ApiResponse_NoAccess"], ApiResponseCodeEnum.NoToken)));
|
||||
}
|
||||
|
||||
|
@ -38,8 +38,8 @@ namespace IRaCIS.Core.API
|
|||
{
|
||||
Response.ContentType = "application/json";
|
||||
Response.StatusCode = StatusCodes.Status403Forbidden;
|
||||
//---您的权限不允许进行该操作
|
||||
await Response.WriteAsync(JsonConvert.SerializeObject(ResponseOutput.NotOk(_localizer["ApiResponse_Permission"],ApiResponseCodeEnum.HaveTokenNotAccess)));
|
||||
//---您的权限不允许进行该操作
|
||||
await Response.WriteAsync(JsonConvert.SerializeObject(ResponseOutput.NotOk(_localizer["ApiResponse_Permission"], ApiResponseCodeEnum.HaveTokenNotAccess)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ namespace IRaCIS.Core.API
|
|||
{
|
||||
OnMessageReceived = (context) =>
|
||||
{
|
||||
|
||||
|
||||
if (context.Request.Query.TryGetValue("access_token", out StringValues values))
|
||||
{
|
||||
var queryToken = values.FirstOrDefault();
|
||||
|
@ -58,10 +58,10 @@ namespace IRaCIS.Core.API
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//仅仅是访问文件的时候才会去取token认证 前端对cookie设置了有效期
|
||||
|
||||
if (context.Request.Path.ToString().Contains("IRaCISData") || context.Request.Path.ToString().Contains("SystemData") )
|
||||
if (context.Request.Path.ToString().Contains("IRaCISData") || context.Request.Path.ToString().Contains("SystemData"))
|
||||
{
|
||||
var cookieToken = context.Request.Cookies["access_token"];
|
||||
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
using Autofac;
|
||||
using IRaCIS.Core.Application;
|
||||
using IRaCIS.Core.Application.BackGroundJob;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Panda.DynamicWebApi;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Application.Service;
|
||||
using IRaCIS.Application.Interfaces;
|
||||
using AutoMapper;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
// ReSharper disable once IdentifierTypo
|
||||
public class AutofacModuleSetup : Autofac.Module
|
||||
{
|
||||
protected override void Load(ContainerBuilder containerBuilder)
|
||||
{
|
||||
|
||||
#region byzhouhang 20210917 此处注册泛型仓储 可以减少Domain层 和Infra.EFcore 两层 空的仓储接口定义和 仓储文件定义
|
||||
|
||||
containerBuilder.RegisterGeneric(typeof(Repository<>))
|
||||
.As(typeof(IRepository<>)).InstancePerLifetimeScope();//注册泛型仓储
|
||||
|
||||
containerBuilder.RegisterType<Repository>().As<IRepository>().InstancePerLifetimeScope();
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region 指定控制器也由autofac 来进行实例获取 https://www.cnblogs.com/xwhqwer/p/15320838.html
|
||||
|
||||
//获取所有控制器类型并使用属性注入
|
||||
containerBuilder.RegisterAssemblyTypes(typeof(BaseService).Assembly)
|
||||
.Where(type => typeof(IDynamicWebApi).IsAssignableFrom(type))
|
||||
.PropertiesAutowired();
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Assembly application = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + "IRaCIS.Core.Application.dll");
|
||||
containerBuilder.RegisterAssemblyTypes(application).Where(t => t.FullName.Contains("Service"))
|
||||
.PropertiesAutowired().AsImplementedInterfaces();
|
||||
|
||||
|
||||
|
||||
//containerBuilder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>().SingleInstance();
|
||||
//containerBuilder.RegisterType<UserInfo>().As<IUserInfo>().InstancePerLifetimeScope();
|
||||
|
||||
|
||||
//注册hangfire任务 依赖注入
|
||||
containerBuilder.RegisterType<ObtainTaskAutoCancelJob>().As<IObtainTaskAutoCancelJob>().InstancePerDependency();
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,14 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using IRaCIS.Core.Application.BusinessFilter;
|
||||
using IRaCIS.Core.Application.Service.BusinessFilter;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Panda.DynamicWebApi;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
public static class DynamicWebApiSetup
|
||||
public static class WebApiSetup
|
||||
{
|
||||
//20210910 避免冗余的控制器层代码编写,仅仅包了一层前后台定义的格式 这里采用动态webAPi+IResultFilter 替代大部分情况
|
||||
public static void AddDynamicWebApiSetup(this IServiceCollection services)
|
||||
|
@ -20,5 +25,38 @@ namespace IRaCIS.Core.API
|
|||
|
||||
});
|
||||
}
|
||||
|
||||
public static void AddMasaMinimalAPiSetUp(this IServiceCollection services)
|
||||
{
|
||||
services.AddMasaMinimalAPIs(options =>
|
||||
{
|
||||
options.Prefix = "";//自定义前缀 默认是api
|
||||
options.Version = ""; //默认是V1
|
||||
options.AutoAppendId = false; //路由是否自动附加参数Id 默认是true
|
||||
options.PluralizeServiceName = false; //服务名称是否启用复数
|
||||
|
||||
//options.Assemblies = new List<Assembly>() { typeof(UserSiteSurveySubmitedEventConsumer).Assembly };
|
||||
|
||||
options.GetPrefixes = new List<string> { "Get", "Select", "Find" };
|
||||
options.PostPrefixes = new List<string> { "Post", "Add", "Create", "List" };
|
||||
options.PutPrefixes = new List<string> { "Put", "Update" };
|
||||
options.DeletePrefixes = new List<string> { "Delete", "Remove" };
|
||||
|
||||
options.RouteHandlerBuilder = t => {
|
||||
t.RequireAuthorization()
|
||||
.AddEndpointFilter<LimitUserRequestAuthorizationEndpointFilter>()
|
||||
.AddEndpointFilter<TrialGlobalLimitEndpointFilter>()
|
||||
//.AddEndpointFilter<ModelValidationEndpointFilter>()
|
||||
.AddEndpointFilter<UnifiedApiResultEndpointFilter>()
|
||||
.WithGroupName("Institution").DisableAntiforgery();
|
||||
};
|
||||
options.MapHttpMethodsForUnmatched = new string[] { "Post" };
|
||||
options.DisableTrimMethodPrefix = true; //禁用去除方法前缀
|
||||
options.DisableAutoMapRoute = false;//可通过配置true禁用全局自动路由映射或者删除此配置以启用全局自动路由映射
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using EntityFramework.Exceptions.SqlServer;
|
||||
using Hangfire.SqlServer;
|
||||
using IRaCIS.Core.Application.Triggers;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
|
@ -12,14 +11,12 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
public static class EFSetup
|
||||
{
|
||||
public static void AddEFSetup( this IServiceCollection services, IConfiguration configuration,string envName)
|
||||
public static void AddEFSetup(this IServiceCollection services, IConfiguration configuration, string envName)
|
||||
{
|
||||
|
||||
services.AddHttpContextAccessor();
|
||||
|
@ -37,9 +34,9 @@ namespace IRaCIS.Core.API
|
|||
|
||||
// 在控制台
|
||||
//public static readonly ILoggerFactory MyLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });
|
||||
var logFactory = LoggerFactory.Create(builder => { builder.AddDebug(); });
|
||||
var logFactory = LoggerFactory.Create(builder => { builder.AddDebug(); });
|
||||
|
||||
var dbType = configuration.GetSection("ConnectionStrings:Db_Type").Value ;
|
||||
var dbType = configuration.GetSection("ConnectionStrings:Db_Type").Value;
|
||||
if (!string.IsNullOrWhiteSpace(dbType) && dbType == "pgsql")
|
||||
{
|
||||
options.UseNpgsql(configuration.GetSection("ConnectionStrings:RemoteNew").Value, contextOptionsBuilder => contextOptionsBuilder.EnableRetryOnFailure());
|
||||
|
@ -54,7 +51,6 @@ namespace IRaCIS.Core.API
|
|||
//迁移的时候,不生成外键
|
||||
options.ReplaceService<IMigrationsSqlGenerator, NoForeignKeyMigrationsSqlGenerator>();
|
||||
|
||||
|
||||
options.UseLoggerFactory(logFactory);
|
||||
|
||||
options.UseExceptionProcessor();
|
||||
|
@ -66,17 +62,13 @@ namespace IRaCIS.Core.API
|
|||
|
||||
options.UseProjectables();
|
||||
|
||||
//options.AddInterceptors(new AuditingInterceptor(configuration.GetSection("ConnectionStrings:RemoteNew").Value));
|
||||
|
||||
//options.UseTriggers(triggerOptions => triggerOptions.AddTrigger<SubjectVisitImageDateTrigger>());
|
||||
|
||||
//options.UseTriggers(triggerOptions => triggerOptions.AddAssemblyTriggers(typeof(SubjectVisitTrigger).Assembly));
|
||||
|
||||
|
||||
options.UseTriggers(triggerOptions =>
|
||||
{
|
||||
triggerOptions.AddTrigger<SubjectTrigger>();
|
||||
triggerOptions.AddTrigger<ChallengeStateTrigger>();
|
||||
triggerOptions.AddTrigger<ChallengeStateTrigger>();
|
||||
triggerOptions.AddTrigger<SubjectVisitTrigger>();
|
||||
triggerOptions.AddTrigger<SubjectVisitScanDateTrigger>();
|
||||
triggerOptions.AddTrigger<TrialCriterionSignTrigger>();
|
||||
|
@ -87,13 +79,10 @@ namespace IRaCIS.Core.API
|
|||
|
||||
triggerOptions.AddTrigger<UserLogTrigger>();
|
||||
|
||||
|
||||
|
||||
|
||||
triggerOptions.AddTrigger<UserAddTrigger>();
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
// Register an additional context factory as a Scoped service, which gets a pooled context from the Singleton factory we registered above,
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
public static class EasyCachingSetup
|
||||
{
|
||||
public static void AddEasyCachingSetup(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
//services.AddEasyCaching(options =>
|
||||
//{
|
||||
// options.UseInMemory();
|
||||
|
||||
// //options.UseRedis(configuration, EasyCachingConstValue.DefaultRedisName).WithMessagePack(EasyCachingConstValue.DefaultRedisName);
|
||||
|
||||
|
||||
//});
|
||||
|
||||
////services.ConfigureCastleInterceptor(options => options.CacheProviderName = EasyCachingConstValue.DefaultRedisName);
|
||||
|
||||
//services.ConfigureCastleInterceptor(options => options.CacheProviderName = EasyCachingConstValue.DefaultInMemoryName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
using AspNetCoreRateLimit;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
|
@ -13,7 +11,7 @@ namespace IRaCIS.Core.API
|
|||
{
|
||||
public static void AddIpPolicyRateLimitSetup(this IServiceCollection services, IConfiguration Configuration)
|
||||
{
|
||||
|
||||
|
||||
// needed to store rate limit counters and ip rules
|
||||
services.AddMemoryCache();
|
||||
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
using IRaCIS.Core.Domain.Share;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
public static class JsonConfigSetup
|
||||
{
|
||||
public static void AddJsonConfigSetup(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
|
||||
services.Configure<ServiceVerifyConfigOption>(configuration.GetSection("BasicSystemConfig"));
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
|
||||
using LogDashboard;
|
||||
using LogDashboard.Authorization.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
public static class LogDashboardSetup
|
||||
public static class LogDashboardSetup
|
||||
{
|
||||
public static void AddLogDashboardSetup(this IServiceCollection services)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
using IRaCIS.Core.API.HostService;
|
||||
using IRaCIS.Core.Application.MassTransit.Consumer;
|
||||
using IRaCIS.Core.Domain.BaseModel;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
public static class MassTransitSetup
|
||||
{
|
||||
public static void AddMassTransitSetup(this IServiceCollection services)
|
||||
{
|
||||
|
||||
#region MassTransit
|
||||
//masstransit组件 也支持MediatR 中介者模式,但是支持分布式,考虑后续,所以在次替代MediatR
|
||||
//参考链接:https://masstransit.io/documentation/concepts/mediator#scoped-mediator
|
||||
services.AddMediator(cfg =>
|
||||
{
|
||||
cfg.AddConsumers(typeof(UserSiteSurveySubmitedEventConsumer).Assembly);
|
||||
|
||||
//cfg.AddConsumer<ConsistencyCheckConsumer>();
|
||||
//cfg.AddConsumer<AddSubjectTriggerConsumer>();
|
||||
//cfg.AddConsumer<AddSubjectTriggerConsumer2>();
|
||||
//cfg.ConfigureMediator((context, cfg) => cfg.UseHttpContextScopeFilter(context));
|
||||
});
|
||||
|
||||
//添加 MassTransit 和 InMemory 传输
|
||||
services.AddMassTransit(cfg =>
|
||||
{
|
||||
cfg.AddConsumers(typeof(UserSiteSurveySubmitedEventConsumer).Assembly);
|
||||
|
||||
cfg.AddPublishMessageScheduler();
|
||||
cfg.AddHangfireConsumers();
|
||||
|
||||
// 使用 InMemory 作为消息传递机制
|
||||
cfg.UsingInMemory((context, cfg) =>
|
||||
{
|
||||
cfg.UsePublishMessageScheduler();
|
||||
|
||||
|
||||
cfg.UseConsumeFilter(typeof(ConsumeExceptionFilter<>), context,
|
||||
x => x.Include(type => type.IsAssignableTo(typeof(DomainEvent))));
|
||||
|
||||
cfg.UseConsumeFilter(typeof(CultureInfoFilter<>), context,
|
||||
x => x.Include(type => type.IsAssignableTo(typeof(DomainEvent))));
|
||||
|
||||
cfg.ConfigureEndpoints(context); // 自动配置所有消费者的端点
|
||||
|
||||
|
||||
});
|
||||
|
||||
#region rabitmq obsolute
|
||||
|
||||
//cfg.UsingRabbitMq((context, cfg) =>
|
||||
//{
|
||||
// cfg.UsePublishMessageScheduler();
|
||||
|
||||
// cfg.Host(
|
||||
// host: "106.14.89.110",
|
||||
// port: 5672,
|
||||
// virtualHost: "/",
|
||||
// configure: hostConfig =>
|
||||
// {
|
||||
// hostConfig.Username("rabbitmq");
|
||||
// hostConfig.Password("rabbitmq");
|
||||
// });
|
||||
|
||||
// cfg.ConfigureEndpoints(context);
|
||||
//});
|
||||
#endregion
|
||||
|
||||
#region Outbox obsolute
|
||||
|
||||
//cfg.AddConfigureEndpointsCallback((context, name, cfg) =>
|
||||
//{
|
||||
// cfg.UseEntityFrameworkOutbox<IRaCISDBContext>(context);
|
||||
|
||||
// //cfg.UseDelayedRedelivery(r => r.Intervals(TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(30)));
|
||||
|
||||
// //// 全局重试策略:重试 3 次,每次延迟 5 秒
|
||||
// //cfg.UseMessageRetry(retryConfig =>
|
||||
// //{
|
||||
// // retryConfig.Interval(3, TimeSpan.FromSeconds(10));
|
||||
// //});
|
||||
//});
|
||||
|
||||
//cfg.AddEntityFrameworkOutbox<IRaCISDBContext>(o =>
|
||||
//{
|
||||
// o.UseSqlServer();
|
||||
// o.UseBusOutbox();
|
||||
//});
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
//services.AddOptions<MassTransitHostOptions>()
|
||||
// .Configure(options =>
|
||||
// {
|
||||
// options.WaitUntilStarted = true;
|
||||
// options.StartTimeout = TimeSpan.FromMinutes(1);
|
||||
// options.StopTimeout = TimeSpan.FromMinutes(1);
|
||||
// });
|
||||
|
||||
//services.AddOptions<HostOptions>()
|
||||
// .Configure(options => options.ShutdownTimeout = TimeSpan.FromMinutes(1));
|
||||
|
||||
//services.AddHostedService<RecurringJobConfigurationService>();
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using StackExchange.Redis;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
|
@ -14,9 +13,9 @@ namespace IRaCIS.Core.API
|
|||
public class JSONTimeZoneConverter(IHttpContextAccessor _httpContextAccessor) : DateTimeConverterBase
|
||||
{
|
||||
|
||||
private TimeZoneInfo _clientTimeZone;
|
||||
private TimeZoneInfo _clientTimeZone;
|
||||
|
||||
private string _dateFormat;
|
||||
private string _dateFormat;
|
||||
|
||||
|
||||
|
||||
|
@ -59,7 +58,7 @@ namespace IRaCIS.Core.API
|
|||
|
||||
|
||||
// 仅支持 DateTime 类型的转换
|
||||
return objectType == typeof(DateTime)|| objectType == typeof(DateTime?);
|
||||
return objectType == typeof(DateTime) || objectType == typeof(DateTime?);
|
||||
|
||||
}
|
||||
|
||||
|
@ -93,20 +92,20 @@ namespace IRaCIS.Core.API
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 将客户端时间转换为服务器时区的时间
|
||||
var serverZoneTime = TimeZoneInfo.ConvertTime(dateTime, _clientTimeZone, TimeZoneInfo.Local);
|
||||
|
||||
return serverZoneTime;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
{
|
||||
DateTime? nullableDateTime = value as DateTime?;
|
||||
|
||||
if (nullableDateTime != null && nullableDateTime.HasValue)
|
||||
|
@ -121,8 +120,79 @@ namespace IRaCIS.Core.API
|
|||
else
|
||||
{
|
||||
writer.WriteNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region 废弃
|
||||
|
||||
public class MyDateTimeConverter : JsonConverter<DateTime>
|
||||
{
|
||||
public override DateTime ReadJson(JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
return reader.ReadAsDateTime().Value;
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, DateTime value, JsonSerializer serializer)
|
||||
{
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
string dateFormat;
|
||||
if (!isEn_US)
|
||||
{
|
||||
// Chinese date format
|
||||
dateFormat = "yyyy-MM-dd HH:mm:ss";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default or English date format
|
||||
dateFormat = "MM/dd/yyyy HH:mm:ss";
|
||||
}
|
||||
|
||||
writer.WriteValue(value.ToString(dateFormat));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class MyNullableDateTimeConverter : JsonConverter<DateTime?>
|
||||
{
|
||||
public override DateTime? ReadJson(JsonReader reader, Type objectType, DateTime? existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
var val = reader.ReadAsDateTime();
|
||||
return val;
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, DateTime? value, JsonSerializer serializer)
|
||||
{
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
string dateFormat;
|
||||
if (!isEn_US)
|
||||
{
|
||||
// Chinese date format
|
||||
dateFormat = "yyyy-MM-dd HH:mm:ss";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default or English date format
|
||||
dateFormat = "MM/dd/yyyy HH:mm:ss";
|
||||
}
|
||||
|
||||
if (value.HasValue)
|
||||
{
|
||||
writer.WriteValue(value.Value.ToString(dateFormat));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteValue(default(DateTime?));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
using IRaCIS.Core.Domain.Share;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
|
||||
|
||||
//public class CustomContractResolver : DefaultContractResolver
|
||||
//{
|
||||
// protected override JsonContract CreateContract(Type objectType)
|
||||
// {
|
||||
// var contract = base.CreateContract(objectType);
|
||||
|
||||
// // 检查类是否有 LowercaseJsonAttribute 标记
|
||||
// if (objectType.GetCustomAttribute<LowerCamelCaseJsonAttribute>() != null)
|
||||
// {
|
||||
// contract.NamingStrategy = new IRCCamelCaseNamingStrategy();
|
||||
// }
|
||||
|
||||
// return contract;
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
|
||||
#region 废弃
|
||||
|
||||
public class MyDateTimeConverter : JsonConverter<DateTime>
|
||||
{
|
||||
public override DateTime ReadJson(JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
return reader.ReadAsDateTime().Value;
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, DateTime value, JsonSerializer serializer)
|
||||
{
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
string dateFormat;
|
||||
if (!isEn_US)
|
||||
{
|
||||
// Chinese date format
|
||||
dateFormat = "yyyy-MM-dd HH:mm:ss";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default or English date format
|
||||
dateFormat = "MM/dd/yyyy HH:mm:ss";
|
||||
}
|
||||
|
||||
writer.WriteValue(value.ToString(dateFormat));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class MyNullableDateTimeConverter : JsonConverter<DateTime?>
|
||||
{
|
||||
public override DateTime? ReadJson(JsonReader reader, Type objectType, DateTime? existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
var val = reader.ReadAsDateTime();
|
||||
return val;
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, DateTime? value, JsonSerializer serializer)
|
||||
{
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
string dateFormat;
|
||||
if (!isEn_US)
|
||||
{
|
||||
// Chinese date format
|
||||
dateFormat = "yyyy-MM-dd HH:mm:ss";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default or English date format
|
||||
dateFormat = "MM/dd/yyyy HH:mm:ss";
|
||||
}
|
||||
|
||||
if (value.HasValue)
|
||||
{
|
||||
writer.WriteValue(value.Value.ToString(dateFormat));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteValue(default(DateTime?));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -1,16 +1,9 @@
|
|||
|
||||
using IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
|
@ -29,13 +22,6 @@ namespace IRaCIS.Core.API
|
|||
builder.AddNewtonsoftJson(options =>
|
||||
{
|
||||
|
||||
//大驼峰
|
||||
//options.SerializerSettings.ContractResolver = new DefaultContractResolver();
|
||||
//小驼峰
|
||||
//options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
|
||||
|
||||
|
||||
|
||||
// 忽略循环引用
|
||||
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
||||
|
||||
|
@ -47,10 +33,18 @@ namespace IRaCIS.Core.API
|
|||
|
||||
options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind;
|
||||
|
||||
#region 废弃
|
||||
//大驼峰
|
||||
//options.SerializerSettings.ContractResolver = new DefaultContractResolver();
|
||||
//小驼峰
|
||||
//options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
|
||||
|
||||
|
||||
|
||||
//二者只能取其一
|
||||
//options.SerializerSettings.Converters.Add(new MyDateTimeConverter());
|
||||
//options.SerializerSettings.Converters.Add(new MyNullableDateTimeConverter());
|
||||
#endregion
|
||||
|
||||
//必须放在后面
|
||||
options.SerializerSettings.Converters.Add(services.BuildServiceProvider().GetService<JSONTimeZoneConverter>());
|
||||
|
@ -62,6 +56,7 @@ namespace IRaCIS.Core.API
|
|||
{
|
||||
o.SuppressModelStateInvalidFilter = true; //自己写验证
|
||||
|
||||
#region 废弃验证
|
||||
////这里是自定义验证结果和返回状态码 因为这里是在[ApiController]控制器层校验,动态webApi的不会校验 所以需要单独写一个Filter
|
||||
//o.InvalidModelStateResponseFactory = (context) =>
|
||||
//{
|
||||
|
@ -72,7 +67,7 @@ namespace IRaCIS.Core.API
|
|||
|
||||
//return new JsonResult(ResponseOutput.NotOk("The inputs supplied to the API are invalid. " + JsonConvert.SerializeObject( error)));
|
||||
//};
|
||||
|
||||
#endregion
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -8,34 +8,18 @@ using System.Reflection;
|
|||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
/// <summary>
|
||||
/// LowerCamelCaseJsonAttribute 可以设置类小写返回给前端
|
||||
/// </summary>
|
||||
public class NullToEmptyStringResolver : DefaultContractResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建属性
|
||||
/// </summary>
|
||||
/// <param name="type">类型</param>
|
||||
/// <param name="memberSerialization">序列化成员</param>
|
||||
/// <returns></returns>
|
||||
//protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
|
||||
//{
|
||||
// IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
|
||||
|
||||
|
||||
// foreach (var jsonProperty in properties)
|
||||
// {
|
||||
// jsonProperty.DefaultValue = new NullToEmptyStringValueProvider(jsonProperty);
|
||||
// }
|
||||
|
||||
// return properties;
|
||||
|
||||
//}
|
||||
|
||||
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
|
||||
{
|
||||
// 检查类是否有 LowerCamelCaseJsonAttribute 标记 有的话,属性名小写
|
||||
if (type.GetCustomAttribute<LowerCamelCaseJsonAttribute>() != null)
|
||||
{
|
||||
base.NamingStrategy= new LowerCamelCaseNamingStrategy();
|
||||
base.NamingStrategy = new LowerCamelCaseNamingStrategy();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -46,7 +30,7 @@ namespace IRaCIS.Core.API
|
|||
IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
|
||||
|
||||
|
||||
var list= type.GetProperties()
|
||||
var list = type.GetProperties()
|
||||
.Select(p =>
|
||||
{
|
||||
var jp = base.CreateProperty(p, memberSerialization);
|
||||
|
@ -57,12 +41,45 @@ namespace IRaCIS.Core.API
|
|||
var uu = list.Select(t => t.PropertyName).ToList();
|
||||
|
||||
//获取复杂对象属性
|
||||
properties = properties.TakeWhile(t => !uu.Contains(t.PropertyName)).ToList();
|
||||
properties = properties.TakeWhile(t => !uu.Contains(t.PropertyName)).ToList();
|
||||
|
||||
list.AddRange(properties);
|
||||
return list;
|
||||
list.AddRange(properties);
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
public class NullToEmptyStringValueProvider : IValueProvider
|
||||
{
|
||||
PropertyInfo _MemberInfo;
|
||||
public NullToEmptyStringValueProvider(PropertyInfo memberInfo)
|
||||
{
|
||||
_MemberInfo = memberInfo;
|
||||
}
|
||||
public object GetValue(object target)
|
||||
{
|
||||
object result = _MemberInfo.GetValue(target);
|
||||
if (_MemberInfo.PropertyType == typeof(string) && result == null) result = "";
|
||||
else if (_MemberInfo.PropertyType == typeof(String[]) && result == null) result = new string[] { };
|
||||
//else if (_MemberInfo.PropertyType == typeof(Nullable<Int32>) && result == null) result = 0;
|
||||
//else if (_MemberInfo.PropertyType == typeof(Nullable<Decimal>) && result == null) result = 0.00M;
|
||||
|
||||
return result;
|
||||
}
|
||||
public void SetValue(object target, object value)
|
||||
{
|
||||
|
||||
if (_MemberInfo.PropertyType == typeof(string))
|
||||
{
|
||||
//去掉前后空格
|
||||
_MemberInfo.SetValue(target, value == null ? string.Empty : value.ToString() == string.Empty ? value : value/*.ToString().Trim()*/);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
_MemberInfo.SetValue(target, value);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
|
||||
public class NullToEmptyStringValueProvider : IValueProvider
|
||||
{
|
||||
PropertyInfo _MemberInfo;
|
||||
public NullToEmptyStringValueProvider(PropertyInfo memberInfo)
|
||||
{
|
||||
_MemberInfo = memberInfo;
|
||||
}
|
||||
public object GetValue(object target)
|
||||
{
|
||||
object result = _MemberInfo.GetValue(target);
|
||||
if (_MemberInfo.PropertyType == typeof(string) && result == null) result = "";
|
||||
else if (_MemberInfo.PropertyType == typeof(String[]) && result == null) result = new string[] { };
|
||||
//else if (_MemberInfo.PropertyType == typeof(Nullable<Int32>) && result == null) result = 0;
|
||||
else if (_MemberInfo.PropertyType == typeof(Nullable<Decimal>) && result == null) result = 0.00M;
|
||||
|
||||
return result;
|
||||
}
|
||||
public void SetValue(object target, object value)
|
||||
{
|
||||
|
||||
if(_MemberInfo.PropertyType == typeof(string))
|
||||
{
|
||||
//去掉前后空格
|
||||
_MemberInfo.SetValue(target, value==null?string.Empty: value.ToString()==string.Empty? value:value/*.ToString().Trim()*/);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
_MemberInfo.SetValue(target, value);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +1,5 @@
|
|||
using IRaCIS.Core.Application.Helper;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
@ -14,7 +11,7 @@ namespace IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson
|
|||
private readonly IOSSService _oSSService;
|
||||
|
||||
// 构造函数
|
||||
public ObjectStorePathConvert( IOSSService oSSService)
|
||||
public ObjectStorePathConvert(IOSSService oSSService)
|
||||
{
|
||||
_oSSService = oSSService;
|
||||
}
|
||||
|
@ -41,9 +38,9 @@ namespace IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson
|
|||
|
||||
Regex guidRegex = new Regex(@"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}");
|
||||
|
||||
if (propertyName.IndexOf("Path") > -1 && value.IndexOf('.')>-1 && guidRegex.IsMatch(value))
|
||||
if (propertyName.IndexOf("Path") > -1 && value.IndexOf('.') > -1 && guidRegex.IsMatch(value))
|
||||
{
|
||||
var tt= _oSSService.GetSignedUrl(value);
|
||||
var tt = _oSSService.GetSignedUrl(value);
|
||||
writer.WriteValue(tt);
|
||||
}
|
||||
else
|
||||
|
@ -51,13 +48,13 @@ namespace IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson
|
|||
// 将处理后的字符串写入到 JsonWriter 中
|
||||
writer.WriteValue(value);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteNull();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Serilog;
|
||||
using Serilog.Configuration;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using System;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
public static class EnricherExtensions
|
||||
{
|
||||
public static LoggerConfiguration WithHttpContextInfo(this LoggerEnrichmentConfiguration enrich, IServiceProvider serviceProvider)
|
||||
{
|
||||
if (enrich == null)
|
||||
throw new ArgumentNullException(nameof(enrich));
|
||||
|
||||
return enrich.With(new HttpContextEnricher(serviceProvider));
|
||||
}
|
||||
public static LoggerConfiguration WithHttpContextInfo(this LoggerEnrichmentConfiguration enrich, IServiceProvider serviceProvider, Action<LogEvent, ILogEventPropertyFactory, HttpContext> enrichAction)
|
||||
{
|
||||
if (enrich == null)
|
||||
throw new ArgumentNullException(nameof(enrich));
|
||||
|
||||
return enrich.With(new HttpContextEnricher(serviceProvider, enrichAction));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
using IRaCIS.Core.Infrastructure;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
public class HttpContextEnricher : ILogEventEnricher
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly Action<LogEvent, ILogEventPropertyFactory, HttpContext> _enrichAction;
|
||||
|
||||
public HttpContextEnricher(IServiceProvider serviceProvider) : this(serviceProvider, null)
|
||||
{ }
|
||||
|
||||
public HttpContextEnricher(IServiceProvider serviceProvider, Action<LogEvent, ILogEventPropertyFactory, HttpContext> enrichAction)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
if (enrichAction == null)
|
||||
{
|
||||
_enrichAction = (logEvent, propertyFactory, httpContext) =>
|
||||
{
|
||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("RequestIP", httpContext.Connection.RemoteIpAddress.ToString()));
|
||||
|
||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("LocalIP", httpContext.Connection.LocalIpAddress.MapToIPv4().ToString()));
|
||||
|
||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("TokenUserRealName", httpContext?.User?.FindFirst(ClaimAttributes.RealName)?.Value));
|
||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("TokenUserType", httpContext?.User?.FindFirst(JwtIRaCISClaimType.UserTypeShortName)?.Value));
|
||||
|
||||
|
||||
//这样读取没用
|
||||
//logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("RequestBody", await ReadRequestBody(httpContext.Request)));
|
||||
//logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("RequestIP", IPHelper.GetIP(httpContext.Request) ));
|
||||
//logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Referer", httpContext.Request.Headers["Referer"].ToString()));
|
||||
//logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("request_path", httpContext.Request.Path));
|
||||
//logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("request_method", httpContext.Request.Method));
|
||||
//if (httpContext.Response.HasStarted)
|
||||
//{
|
||||
// logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("response_status", httpContext.Response.StatusCode));
|
||||
//}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
_enrichAction = enrichAction;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
var httpContext = _serviceProvider.GetService<IHttpContextAccessor>()?.HttpContext;
|
||||
if (null != httpContext)
|
||||
{
|
||||
_enrichAction.Invoke(logEvent, propertyFactory, httpContext);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> ReadRequestBody(HttpRequest request)
|
||||
{
|
||||
// Ensure the request's body can be read multiple times (for the next middlewares in the pipeline).
|
||||
request.EnableBuffering();
|
||||
|
||||
using var streamReader = new StreamReader(request.Body, leaveOpen: true);
|
||||
var requestBody = await streamReader.ReadToEndAsync();
|
||||
|
||||
// Reset the request's body stream position for next middleware in the pipeline.
|
||||
request.Body.Position = 0;
|
||||
return requestBody==null?String.Empty: requestBody.Trim();
|
||||
}
|
||||
|
||||
private async Task<string> ReadResponseBody(HttpResponse response)
|
||||
{
|
||||
response.Body.Seek(0, SeekOrigin.Begin);
|
||||
string responseBody = await new StreamReader(response.Body).ReadToEndAsync();
|
||||
response.Body.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
return $"{responseBody}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
using Microsoft.AspNetCore.Builder;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
//using Serilog.Sinks.Email;
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
public class SerilogExtension
|
||||
{
|
||||
|
||||
public static void AddSerilogSetup(string environment, IServiceProvider serviceProvider)
|
||||
{
|
||||
|
||||
var config = new LoggerConfiguration()
|
||||
.MinimumLevel.Information()
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
||||
// Filter out ASP.NET Core infrastructre logs that are Information and below 日志太多了 一个请求 记录好几条
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Hangfire", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("System.Net.Http.HttpClient.HttpReports", LogEventLevel.Warning)
|
||||
.Enrich.WithClientIp()
|
||||
.Enrich.FromLogContext()
|
||||
.Filter.ByExcluding(logEvent =>logEvent.Properties.ContainsKey("RequestPath") && logEvent.Properties["RequestPath"].ToString().Contains("/health"))
|
||||
|
||||
//控制台 方便调试 问题 我们显示记录日志 时 获取上下文的ip 和用户名 用户类型
|
||||
.WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Warning,
|
||||
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext:l} || {Message} || {Exception} ||end {NewLine}")
|
||||
|
||||
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day,
|
||||
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext:l} || {Message} || {Exception} ||end {NewLine}");
|
||||
|
||||
//.WriteTo.MSSqlServer("Data Source=DESKTOP-4TU9A6M;Initial Catalog=CoreFrame;User ID=sa;Password=123456", "logs", autoCreateSqlTable: true, restrictedToMinimumLevel: LogEventLevel.Information)//从左至右四个参数分别是数据库连接字符串、表名、如果表不存在是否创建、最低等级。Serilog会默认创建一些列。
|
||||
|
||||
//if (environment == "Production")
|
||||
//{
|
||||
// config.WriteTo.Email(new EmailConnectionInfo()
|
||||
// {
|
||||
// EmailSubject = "系统警告,请速速查看!",//邮件标题
|
||||
// FromEmail = "test@extimaging.com",//发件人邮箱
|
||||
// MailServer = "smtp.qiye.aliyun.com",//smtp服务器地址
|
||||
// NetworkCredentials = new NetworkCredential("test@extimaging.com", "SHzyyl2021"),//两个参数分别是发件人邮箱与客户端授权码
|
||||
// Port = 465,//端口号
|
||||
// ToEmail = "872297557@qq.com"//收件人
|
||||
// }, restrictedToMinimumLevel: LogEventLevel.Error,
|
||||
// outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [ {Level} {ClientIp} {ClientAgent} {TokenUserRealName} {TokenUserType} ] || [path: {RequestPath} arguments: {RequestBody}] {SourceContext:l} || {Message} || {Exception} ||end {NewLine})");
|
||||
//}
|
||||
|
||||
//扩展方法 获取上下文的ip 用户名 用户类型
|
||||
Log.Logger = config.Enrich.WithHttpContextInfo(serviceProvider).CreateLogger();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
using Amazon.SecurityToken.Model;
|
||||
using DocumentFormat.OpenXml.Bibliography;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using Microsoft.Extensions.Configuration.Json;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Serilog.Formatting.Compact;
|
||||
using Serilog.Formatting.Display;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
public class SerilogExtension
|
||||
{
|
||||
|
||||
public static void AddSerilogSetup(string environment)
|
||||
{
|
||||
|
||||
var config = new LoggerConfiguration()
|
||||
.MinimumLevel.Information()
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
||||
// Filter out ASP.NET Core infrastructre logs that are Information and below 日志太多了 一个请求 记录好几条
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Hangfire", LogEventLevel.Warning)
|
||||
.Enrich.FromLogContext()
|
||||
.Filter.ByExcluding(logEvent => logEvent.Properties.ContainsKey("RequestPath") && logEvent.Properties["RequestPath"].ToString().Contains("/health"))
|
||||
.WriteTo.Console()
|
||||
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day);
|
||||
|
||||
#region 根据环境配置是否打开错误发送邮件通知
|
||||
|
||||
//读取配置文件
|
||||
var configuration = new ConfigurationBuilder().Add(new JsonConfigurationSource { Path = $"appsettings.{environment}.json", ReloadOnChange = true }).Build();
|
||||
|
||||
// 手动绑定配置
|
||||
var emailConfig = new SystemEmailSendConfig();
|
||||
configuration.GetSection("SystemEmailSendConfig").Bind(emailConfig);
|
||||
|
||||
if (emailConfig.IsOpenErrorNoticeEmail)
|
||||
{
|
||||
config.WriteTo.Email(options: new Serilog.Sinks.Email.EmailSinkOptions()
|
||||
{
|
||||
From = emailConfig.FromEmail,
|
||||
To = emailConfig.ErrorNoticeEmailList,
|
||||
Host = emailConfig.Host,
|
||||
Port = emailConfig.Port,
|
||||
Subject = new MessageTemplateTextFormatter("Log Alert - 系统发生了异常,请核查"),
|
||||
Credentials = new NetworkCredential(emailConfig.FromEmail, emailConfig.AuthorizationCode)
|
||||
|
||||
}, restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Error);
|
||||
}
|
||||
#endregion
|
||||
|
||||
Log.Logger = config.CreateLogger();
|
||||
|
||||
#region 废弃-输出Json格式的日志
|
||||
//如果有反向代理并不会获取到用户的真实IP
|
||||
//.Enrich.WithClientIp()
|
||||
//.Enrich.WithRequestHeader("User-Agent")
|
||||
//https://github.com/serilog/serilog-formatting-compact
|
||||
//// 控制台输出 JSON 格式
|
||||
//.WriteTo.Console(formatter: new CompactJsonFormatter(), LogEventLevel.Warning),
|
||||
//// 文件输出 JSON 格式
|
||||
//.WriteTo.File(new CompactJsonFormatter(), $"{AppContext.BaseDirectory}Serilogs/.json", rollingInterval: RollingInterval.Day);
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
using IP2Region.Net.Abstractions;
|
||||
using IP2Region.Net.XDB;
|
||||
using IRaCIS.Core.Application.BackGroundJob;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Application.Service;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Panda.DynamicWebApi;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace IRaCIS.Core.API;
|
||||
|
||||
|
||||
public static class ServiceCollectionSetup
|
||||
{
|
||||
public static void ConfigureServices(this IServiceCollection services, IConfiguration _configuration)
|
||||
{
|
||||
services.AddOptions().Configure<SystemEmailSendConfig>(_configuration.GetSection("SystemEmailSendConfig"));
|
||||
services.AddOptions().Configure<ServiceVerifyConfigOption>(_configuration.GetSection("BasicSystemConfig"));
|
||||
services.AddOptions().Configure<AliyunOSSOptions>(_configuration.GetSection("AliyunOSS"));
|
||||
services.AddOptions().Configure<ObjectStoreServiceOptions>(_configuration.GetSection("ObjectStoreService"));
|
||||
services.AddOptions().Configure<IRCEncreptOption>(_configuration.GetSection("EncrypteResponseConfig"));
|
||||
services.AddOptions().Configure<SystemPacsConfig>(_configuration.GetSection("SystemPacsConfig"));
|
||||
services.Configure<IRaCISBasicConfigOption>(_configuration.GetSection("IRaCISBasicConfig"));
|
||||
|
||||
services.Configure<ServiceVerifyConfigOption>(_configuration.GetSection("BasicSystemConfig"));
|
||||
|
||||
//转发头设置 获取真实IP
|
||||
services.Configure<ForwardedHeadersOptions>(options =>
|
||||
{
|
||||
options.ForwardedHeaders =
|
||||
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
|
||||
});
|
||||
|
||||
services.AddSingleton<IUserIdProvider, IRaCISUserIdProvider>();
|
||||
services.AddSingleton<ISearcher>(new Searcher(CachePolicy.Content, Path.Combine(AppContext.BaseDirectory, StaticData.Folder.Resources, "ip2region.xdb")));
|
||||
|
||||
|
||||
|
||||
|
||||
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
|
||||
services.AddScoped<IRepository, Repository>();
|
||||
|
||||
services.AddScoped<IObtainTaskAutoCancelJob, ObtainTaskAutoCancelJob>();
|
||||
|
||||
// 注册以Service 结尾的服务
|
||||
services.Scan(scan => scan
|
||||
.FromAssemblies(typeof(BaseService).Assembly)
|
||||
.AddClasses(classes => classes.Where(t => t.Name.Contains("Service")))
|
||||
.AsImplementedInterfaces()
|
||||
.WithScopedLifetime());
|
||||
|
||||
#region Register Controllers
|
||||
|
||||
// Register all controllers in the assembly that implement IDynamicWebApi
|
||||
services.Scan(scan => scan
|
||||
.FromAssemblies(typeof(BaseService).Assembly)
|
||||
.AddClasses(classes => classes.AssignableTo<IDynamicWebApi>())
|
||||
.AsSelf()
|
||||
.WithScopedLifetime());
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
// MediatR 进程内消息 事件解耦 从程序集中 注册命令和handler对应关系
|
||||
//builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining<ConsistencyVerificationHandler>());
|
||||
|
||||
|
||||
|
||||
#region 历史废弃配置
|
||||
//builder.Services.AddMemoryCache();
|
||||
////上传限制 配置
|
||||
//builder.Services.Configure<FormOptions>(options =>
|
||||
//{
|
||||
// options.MultipartBodyLengthLimit = int.MaxValue;
|
||||
// options.ValueCountLimit = int.MaxValue;
|
||||
// options.ValueLengthLimit = int.MaxValue;
|
||||
//});
|
||||
//IP 限流 可设置白名单 或者黑名单
|
||||
//services.AddIpPolicyRateLimitSetup(_configuration);
|
||||
// 用户类型 策略授权
|
||||
//services.AddAuthorizationPolicySetup(_configuration);
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#region Autofac 废弃
|
||||
//public class AutofacModuleSetup : Autofac.Module
|
||||
//{
|
||||
// protected override void Load(ContainerBuilder containerBuilder)
|
||||
// {
|
||||
|
||||
// #region byzhouhang 20210917 此处注册泛型仓储 可以减少Domain层 和Infra.EFcore 两层 空的仓储接口定义和 仓储文件定义
|
||||
|
||||
// containerBuilder.RegisterGeneric(typeof(Repository<>))
|
||||
// .As(typeof(IRepository<>)).InstancePerLifetimeScope();//注册泛型仓储
|
||||
|
||||
// containerBuilder.RegisterType<Repository>().As<IRepository>().InstancePerLifetimeScope();
|
||||
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region 指定控制器也由autofac 来进行实例获取 https://www.cnblogs.com/xwhqwer/p/15320838.html
|
||||
|
||||
// //获取所有控制器类型并使用属性注入
|
||||
// containerBuilder.RegisterAssemblyTypes(typeof(BaseService).Assembly)
|
||||
// .Where(type => typeof(IDynamicWebApi).IsAssignableFrom(type))
|
||||
// .PropertiesAutowired();
|
||||
|
||||
// #endregion
|
||||
|
||||
|
||||
// Assembly application = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + "IRaCIS.Core.Application.dll");
|
||||
// containerBuilder.RegisterAssemblyTypes(application).Where(t => t.FullName.Contains("Service"))
|
||||
// .PropertiesAutowired().AsImplementedInterfaces();
|
||||
|
||||
// //containerBuilder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>().SingleInstance();
|
||||
// //containerBuilder.RegisterType<UserInfo>().As<IUserInfo>().InstancePerLifetimeScope();
|
||||
|
||||
|
||||
// //注册hangfire任务 依赖注入
|
||||
// containerBuilder.RegisterType<ObtainTaskAutoCancelJob>().As<IObtainTaskAutoCancelJob>().InstancePerDependency();
|
||||
|
||||
// }
|
||||
//}
|
||||
#endregion
|
|
@ -1,18 +0,0 @@
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
public static class StaticFileAuthorizationSetup
|
||||
{
|
||||
public static void AddStaticFileAuthorizationSetup(this IServiceCollection services)
|
||||
{
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.FallbackPolicy = new AuthorizationPolicyBuilder()
|
||||
.RequireAuthenticatedUser()
|
||||
.Build();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
using IRaCIS.Core.Application.Contracts;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.Filters;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Swashbuckle.AspNetCore.SwaggerUI;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace IRaCIS.Core.API
|
||||
{
|
||||
public static class SwaggerSetup
|
||||
{
|
||||
public static void AddSwaggerSetup(this IServiceCollection services)
|
||||
{
|
||||
|
||||
services.AddSwaggerGen(options =>
|
||||
{
|
||||
//此处的Name 是控制器上分组的名称 Title是界面的大标题
|
||||
//分组
|
||||
|
||||
options.SwaggerDoc("Reviewer", new OpenApiInfo {Title = "医生模块",Version = "Reviewer", });
|
||||
options.SwaggerDoc("Trial", new OpenApiInfo { Title = "项目模块", Version = "Trial" });
|
||||
options.SwaggerDoc("Enroll", new OpenApiInfo { Title = "入组模块", Version = "Enroll" });
|
||||
options.SwaggerDoc("Workload", new OpenApiInfo { Title = "工作量模块", Version = "Workload" });
|
||||
options.SwaggerDoc("Common", new OpenApiInfo { Title = "通用信息获取", Version = "Common" });
|
||||
options.SwaggerDoc("Institution", new OpenApiInfo { Title = "机构信息模块", Version = "Institution" });
|
||||
options.SwaggerDoc("Dashboard&Statistics", new OpenApiInfo { Title = "统计模块", Version = "Dashboard&Statistics" });
|
||||
|
||||
options.SwaggerDoc("Financial", new OpenApiInfo { Title = "财务模块", Version = "Financial" });
|
||||
options.SwaggerDoc("Management", new OpenApiInfo { Title = "管理模块", Version = "Management" });
|
||||
options.SwaggerDoc("Image", new OpenApiInfo { Title = "影像模块", Version = "Image" });
|
||||
options.SwaggerDoc("Reading", new OpenApiInfo { Title = "读片模块", Version = "Reading" });
|
||||
|
||||
|
||||
// 接口排序
|
||||
options.OrderActionsBy(o => o.GroupName);
|
||||
|
||||
options.DocInclusionPredicate((docName, apiDes) =>
|
||||
{
|
||||
if (!apiDes.TryGetMethodInfo(out MethodInfo methodInfo)) return false;
|
||||
var versions = methodInfo.DeclaringType.GetCustomAttributes(true)
|
||||
.OfType<ApiExplorerSettingsAttribute>()
|
||||
.Select(attr => attr.GroupName);
|
||||
|
||||
return versions.Any(v => v.ToString() == docName);
|
||||
});
|
||||
|
||||
var xmlPath = Path.Combine(AppContext.BaseDirectory, "IRaCIS.Core.API.xml");//这个就是刚刚配置的xml文件名
|
||||
options.IncludeXmlComments(xmlPath, true);
|
||||
|
||||
var xmlPath2 = Path.Combine(AppContext.BaseDirectory, "IRaCIS.Core.Application.xml");//这个就是刚刚配置的xml文件名
|
||||
options.IncludeXmlComments(xmlPath2, true);
|
||||
//默认的第二个参数是false,这个是controller的注释,记得修改
|
||||
|
||||
|
||||
// 在header中添加token,传递到后台
|
||||
options.OperationFilter<SecurityRequirementsOperationFilter>();
|
||||
|
||||
|
||||
// 添加登录按钮
|
||||
options.AddSecurityDefinition("bearerAuth", new OpenApiSecurityScheme()
|
||||
{
|
||||
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
|
||||
Name = "Authorization",
|
||||
|
||||
//In = "header",
|
||||
//Type = "apiKey"
|
||||
});
|
||||
|
||||
|
||||
//// Bearer
|
||||
//options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
|
||||
//{
|
||||
// Description = "JWT Authorization header using the Bearer scheme.",
|
||||
// Name = "Authorization",
|
||||
// In = ParameterLocation.Header,
|
||||
// Scheme = "bearer",
|
||||
// Type = SecuritySchemeType.Http,
|
||||
// BearerFormat = "JWT"
|
||||
//});
|
||||
});
|
||||
}
|
||||
|
||||
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(options =>
|
||||
{
|
||||
//此处的Name 是页面 选择文档下拉框 显示的名称
|
||||
options.SwaggerEndpoint($"swagger/Reviewer/swagger.json", "医生模块");
|
||||
options.SwaggerEndpoint($"swagger/Trial/swagger.json", "项目模块");
|
||||
options.SwaggerEndpoint($"swagger/Enroll/swagger.json", "入组模块");
|
||||
options.SwaggerEndpoint($"swagger/Workload/swagger.json", "工作量模块");
|
||||
options.SwaggerEndpoint($"swagger/Dashboard&Statistics/swagger.json", "统计模块");
|
||||
options.SwaggerEndpoint($"swagger/Common/swagger.json", "通用模块");
|
||||
|
||||
options.SwaggerEndpoint($"swagger/Financial/swagger.json", "财务模块");
|
||||
options.SwaggerEndpoint($"swagger/Institution/swagger.json", "机构信息模块");
|
||||
options.SwaggerEndpoint($"swagger/Management/swagger.json", "管理模块");
|
||||
options.SwaggerEndpoint($"swagger/Image/swagger.json", "影像模块");
|
||||
options.SwaggerEndpoint($"swagger/Reading/swagger.json", "读片模块");
|
||||
|
||||
|
||||
//路径配置,设置为空,表示直接在根域名(localhost:8001)访问该文件,
|
||||
//注意localhost:8001/swagger是访问不到的,去launchSettings.json把launchUrl去掉,如果你想换一个路径,直接写名字即可,比如直接写c.Route = "doc";
|
||||
//options.RoutePrefix = string.Empty;
|
||||
|
||||
var data = Assembly.GetExecutingAssembly().Location;
|
||||
options.IndexStream = () => Assembly.GetExecutingAssembly()
|
||||
.GetManifestResourceStream("IRaCIS.Core.API.wwwroot.swagger.ui.Index.html");
|
||||
|
||||
options.RoutePrefix = string.Empty;
|
||||
|
||||
//DocExpansion设置为none可折叠所有方法
|
||||
options.DocExpansion(DocExpansion.None);
|
||||
//DefaultModelsExpandDepth设置为 - 1 可不显示models
|
||||
options.DefaultModelsExpandDepth(-1);
|
||||
|
||||
|
||||
// 引入静态文件添加登录功能
|
||||
// 清除静态文件缓存
|
||||
// options.IndexStream = () => null;
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.Filters;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Swashbuckle.AspNetCore.SwaggerUI;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace IRaCIS.Core.API;
|
||||
|
||||
public enum SwaggerVersion
|
||||
{
|
||||
[Description("医生模块")]
|
||||
Reviewer = 1,
|
||||
[Description("项目模块")]
|
||||
Trial = 2,
|
||||
[Description("入组模块")]
|
||||
Enroll = 3,
|
||||
[Description("工作量模块")]
|
||||
Workload = 4,
|
||||
[Description("通用信息获取")]
|
||||
Common = 5,
|
||||
[Description("机构信息模块")]
|
||||
Institution = 6,
|
||||
[Description("统计模块")]
|
||||
DashboardStatistics = 7,
|
||||
[Description("财务模块")]
|
||||
Financial = 8,
|
||||
[Description("管理模块")]
|
||||
Management =9,
|
||||
[Description("影像模块")]
|
||||
Image =10,
|
||||
[Description("读片模块")]
|
||||
Reading =11
|
||||
};
|
||||
|
||||
|
||||
public static class SwaggerSetup
|
||||
{
|
||||
public static void AddSwaggerSetup(this IServiceCollection services)
|
||||
{
|
||||
services.AddEndpointsApiExplorer();
|
||||
services.AddSwaggerGen(options =>
|
||||
{
|
||||
|
||||
typeof(SwaggerVersion).GetFields(BindingFlags.Public | BindingFlags.Static).ToList()
|
||||
.ForEach(field =>
|
||||
{
|
||||
var description = field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? field.Name;
|
||||
options.SwaggerDoc(field.Name, new Microsoft.OpenApi.Models.OpenApiInfo
|
||||
{
|
||||
Version = field.Name,
|
||||
Description = $"{field.Name} API",
|
||||
Title = description // 使用Description作为Title
|
||||
});
|
||||
});
|
||||
|
||||
// 接口排序
|
||||
options.OrderActionsBy(o => o.GroupName);
|
||||
|
||||
//添加注释
|
||||
var basePath = AppContext.BaseDirectory;
|
||||
var xmlPath1 = Path.Combine(basePath, "IRaCIS.Core.Application.xml");
|
||||
var xmlPath2 = Path.Combine(basePath, "IRaCIS.Core.API.xml");
|
||||
options.IncludeXmlComments(xmlPath1, true);
|
||||
options.IncludeXmlComments(xmlPath2, true);
|
||||
|
||||
// 在header中添加token,传递到后台
|
||||
options.OperationFilter<SecurityRequirementsOperationFilter>();
|
||||
|
||||
|
||||
// 添加登录按钮
|
||||
options.AddSecurityDefinition("bearerAuth", new OpenApiSecurityScheme()
|
||||
{
|
||||
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
|
||||
Name = "Authorization",
|
||||
//In = "header",
|
||||
//Type = "apiKey"
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(options =>
|
||||
{
|
||||
typeof(SwaggerVersion).GetFields(BindingFlags.Public | BindingFlags.Static).ToList()
|
||||
.ForEach(field =>
|
||||
{
|
||||
var description = field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? field.Name;
|
||||
options.SwaggerEndpoint($"swagger/{field.Name}/swagger.json", $"{description}");
|
||||
});
|
||||
|
||||
|
||||
var data = Assembly.GetExecutingAssembly().Location;
|
||||
options.IndexStream = () => Assembly.GetExecutingAssembly()
|
||||
.GetManifestResourceStream("IRaCIS.Core.API.wwwroot.swagger.ui.Index.html");
|
||||
|
||||
//路径配置,设置为空,表示直接在根域名(localhost:8001)访问该文件,
|
||||
//注意localhost:8001/swagger是访问不到的,去launchSettings.json把launchUrl去掉,如果你想换一个路径,直接写名字即可,比如直接写c.Route = "doc";
|
||||
options.RoutePrefix = string.Empty;
|
||||
|
||||
//DocExpansion设置为none可折叠所有方法
|
||||
options.DocExpansion(DocExpansion.None);
|
||||
|
||||
//DefaultModelsExpandDepth设置为 - 1 可不显示models
|
||||
options.DefaultModelsExpandDepth(-1);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -27,18 +27,10 @@ namespace IRaCIS.Core.API
|
|||
hangFireConfig.UseSqlServerStorage(hangFireConnStr, new SqlServerStorageOptions()
|
||||
{
|
||||
SchemaName = "dbo",
|
||||
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
|
||||
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
|
||||
QueuePollInterval = TimeSpan.Zero,
|
||||
UseRecommendedIsolationLevel = true,
|
||||
DisableGlobalLocks = true
|
||||
});
|
||||
}).UseRecommendedSerializerSettings().UseSimpleAssemblyNameTypeSerializer();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//hangFireConfig.UseTagsWithSql(); //nuget引入Hangfire.Tags.SqlServer
|
||||
//.UseHangfireHttpJob();
|
||||
|
||||
|
@ -46,6 +38,8 @@ namespace IRaCIS.Core.API
|
|||
|
||||
services.AddHangfireServer(option =>
|
||||
{
|
||||
//默认15s检查一次
|
||||
option.SchedulePollingInterval = TimeSpan.FromSeconds(5);
|
||||
option.Queues = new[] { "immediately_once", "default", "sys_init", "not_immediately_once" };
|
||||
});
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
"ContinuousReadingTimeMin": 120,
|
||||
|
||||
"ReadingRestTimeMin": 10,
|
||||
|
||||
"IsNeedChangePassWord": true,
|
||||
|
||||
"ChangePassWordDays": 90
|
||||
|
@ -69,7 +70,9 @@
|
|||
"CompanyNameCN": "上海展影医疗科技有限公司",
|
||||
"CompanyShortName": "Extensive Imaging",
|
||||
"CompanyShortNameCN": "展影医疗",
|
||||
"IsEnv_US": false
|
||||
"IsEnv_US": false,
|
||||
"IsOpenErrorNoticeEmail": true,
|
||||
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
|
||||
},
|
||||
"SystemPacsConfig": {
|
||||
"Port": "11113",
|
||||
|
|
|
@ -72,7 +72,9 @@
|
|||
"ReadingRestTimeMin": 10,
|
||||
"IsNeedChangePassWord": true,
|
||||
|
||||
"ChangePassWordDays": 90
|
||||
"ChangePassWordDays": 90,
|
||||
|
||||
"ThirdPdfUrl": "http://106.14.89.110:30088/api/v1/convert/file/pdf"
|
||||
},
|
||||
|
||||
"SystemEmailSendConfig": {
|
||||
|
@ -89,7 +91,9 @@
|
|||
"CompanyNameCN": "上海展影医疗科技有限公司",
|
||||
"CompanyShortName": "Extensive Imaging",
|
||||
"CompanyShortNameCN": "展影医疗",
|
||||
"IsEnv_US": false
|
||||
"IsEnv_US": false,
|
||||
"IsOpenErrorNoticeEmail": false,
|
||||
"ErrorNoticeEmailList": ["872297557@qq.com"]
|
||||
},
|
||||
|
||||
"SystemPacsConfig": {
|
||||
|
|
|
@ -74,7 +74,9 @@
|
|||
"CompanyShortName": "Elevate Imaging",
|
||||
"CompanyShortNameCN": "展影医疗",
|
||||
"SiteUrl": "https://lili.elevateimaging.ai/login",
|
||||
"IsEnv_US": true
|
||||
"IsEnv_US": true,
|
||||
"IsOpenErrorNoticeEmail": true,
|
||||
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
|
||||
},
|
||||
|
||||
"SystemPacsConfig": {
|
||||
|
|
|
@ -82,7 +82,9 @@
|
|||
"CompanyShortName": "Elevate Imaging",
|
||||
"CompanyShortNameCN": "展影医疗",
|
||||
"SiteUrl": "https://lili.test.elevateimaging.ai/login",
|
||||
"IsEnv_US": true
|
||||
"IsEnv_US": true,
|
||||
"IsOpenErrorNoticeEmail": false,
|
||||
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
|
||||
},
|
||||
|
||||
"SystemPacsConfig": {
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
"AccessKeyId": "AKIAW3MEAFJX7IPXISP4",
|
||||
"SecretAccessKey": "Pgrg3le5jPxZQ7MR1yYNS30J0XRyJeKVyIIjElXc",
|
||||
"BucketName": "ei-med-s3-lili-uat-store",
|
||||
"ViewEndpoint": "https://ei-med-s3-lili-uat-store.s3.amazonaws.com/",
|
||||
"ViewEndpoint": "https://ei-med-s3-lili-uat-store.s3.amazonaws.com",
|
||||
"DurationSeconds": 7200
|
||||
}
|
||||
},
|
||||
|
@ -86,7 +86,9 @@
|
|||
"CompanyShortName": "Elevate Imaging",
|
||||
"CompanyShortNameCN": "展影医疗",
|
||||
"SiteUrl": "https://lili.test.elevateimaging.ai/login",
|
||||
"IsEnv_US": true
|
||||
"IsEnv_US": true,
|
||||
"IsOpenErrorNoticeEmail": false,
|
||||
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
|
||||
},
|
||||
|
||||
"SystemPacsConfig": {
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"RemoteNew": "Server=47.117.164.182,1434;Database=Uat_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
|
||||
"Hangfire": "Server=47.117.164.182,1434;Database=Uat_IRC.Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
|
||||
"RemoteNew": "Server=101.132.253.119,1435;Database=Uat_IRC;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
|
||||
"Hangfire": "Server=101.132.253.119,1435;Database=Uat_IRC_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
|
||||
},
|
||||
"ObjectStoreService": {
|
||||
|
||||
|
@ -18,11 +18,11 @@
|
|||
"RegionId": "cn-shanghai",
|
||||
"InternalEndpoint": "https://oss-cn-shanghai-internal.aliyuncs.com",
|
||||
"EndPoint": "https://oss-cn-shanghai.aliyuncs.com",
|
||||
"AccessKeyId": "LTAI5tRRZehUp2V9pyTPtAJm",
|
||||
"AccessKeySecret": "FLizxkHsMm4CGYHtkV8E3PNJJZU7oV",
|
||||
"RoleArn": "acs:ram::1899121822495495:role/dev-oss-access",
|
||||
"BucketName": "zy-irc-uat-store",
|
||||
"ViewEndpoint": "https://zy-irc-uat-store.oss-cn-shanghai.aliyuncs.com",
|
||||
"AccessKeyId": "LTAI5tFUCCmz5TwghZHsj45Y",
|
||||
"AccessKeySecret": "8evrBy1fVfzJG25i67Jm0xqn9Xcw2T",
|
||||
"RoleArn": "acs:ram::1078130221702011:role/uat-oss-access",
|
||||
"BucketName": "tl-med-irc-uat-store",
|
||||
"ViewEndpoint": "https://tl-med-irc-uat-store.oss-cn-shanghai.aliyuncs.com",
|
||||
"Region": "oss-cn-shanghai",
|
||||
"DurationSeconds": 7200
|
||||
},
|
||||
|
@ -87,7 +87,9 @@
|
|||
"CompanyNameCN": "上海展影医疗科技有限公司",
|
||||
"CompanyShortName": "Extensive Imaging",
|
||||
"CompanyShortNameCN": "展影医疗",
|
||||
"IsEnv_US": false
|
||||
"IsEnv_US": false,
|
||||
"IsOpenErrorNoticeEmail": false,
|
||||
"ErrorNoticeEmailList": [ "872297557@qq.com" ]
|
||||
},
|
||||
|
||||
"SystemPacsConfig": {
|
||||
|
|
|
@ -66,5 +66,13 @@
|
|||
"ApiPathList": [
|
||||
"/test/get"
|
||||
]
|
||||
},
|
||||
"oauth": {
|
||||
"github": {
|
||||
"app_id": "github_app_id",
|
||||
"app_key": "github_app_key",
|
||||
"redirect_uri": "https://oauthlogin.net/oauth/githubcallback",
|
||||
"scope": "repo"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,6 +22,8 @@ namespace IRaCIS.Core.Application.Auth
|
|||
|
||||
public bool IsTestUser { get; set; }
|
||||
|
||||
public bool IsZhiZhun { get; set; }
|
||||
|
||||
public string Phone { get; set; } = String.Empty;
|
||||
|
||||
public static IRaCISClaims Create(UserBasicInfo user)
|
||||
|
@ -36,18 +38,9 @@ namespace IRaCIS.Core.Application.Auth
|
|||
IsTestUser = user.IsTestUser,
|
||||
Code = user.Code,
|
||||
PermissionStr = user.PermissionStr,
|
||||
|
||||
IsZhiZhun = user.IsZhiZhun,
|
||||
UserTypeShortName = user.UserTypeShortName
|
||||
};
|
||||
}
|
||||
public static IRaCISClaims Create(DoctorAccountDTO doctor)
|
||||
{
|
||||
return new IRaCISClaims
|
||||
{
|
||||
Id = doctor.Id,
|
||||
FullName = doctor.FirstName + doctor.LastName,
|
||||
Phone = doctor.Phone,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,7 +36,7 @@ namespace IRaCIS.Core.Application.Auth
|
|||
new Claim(JwtIRaCISClaimType.UserTypeEnumInt,((int)user.UserTypeEnum).ToString()),
|
||||
new Claim(JwtIRaCISClaimType.UserTypeShortName,user.UserTypeShortName),
|
||||
new Claim(JwtIRaCISClaimType.PermissionStr,user.PermissionStr),
|
||||
|
||||
new Claim(JwtIRaCISClaimType.IsZhiZhun,user.IsZhiZhun.ToString()),
|
||||
new Claim(JwtIRaCISClaimType.IsTestUser,user.IsTestUser.ToString())
|
||||
};
|
||||
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
using Hangfire;
|
||||
using Hangfire.Storage;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace IRaCIS.Core.Application.Service.BackGroundJob
|
||||
{
|
||||
|
||||
public interface IIRaCISHangfireJob
|
||||
{
|
||||
|
||||
//Task MemoryCacheTrialStatusAsync();
|
||||
|
||||
Task InitHangfireJobTaskAsync();
|
||||
|
||||
}
|
||||
public class IRaCISCHangfireJob(ILogger<IRaCISCHangfireJob> _logger,
|
||||
IRepository<Internationalization> _internationalizationRepository,
|
||||
IRepository<TrialEmailNoticeConfig> _trialEmailNoticeConfigRepository
|
||||
) : IIRaCISHangfireJob
|
||||
{
|
||||
public static string JsonFileFolder = Path.Combine(AppContext.BaseDirectory, StaticData.Folder.Resources);
|
||||
|
||||
|
||||
public async Task InitHangfireJobTaskAsync()
|
||||
{
|
||||
_logger.LogInformation("项目启动 hangfire 任务初始化 执行开始~");
|
||||
|
||||
//初始化国际化
|
||||
|
||||
await InternationalizationHelper.InitInternationlizationDataAndWatchJsonFileAsync(_internationalizationRepository);
|
||||
|
||||
//创建邮件定时任务
|
||||
await InitSysAndTrialCronJobAsync();
|
||||
|
||||
#region 废弃
|
||||
////项目状态 立即加载到缓存中
|
||||
//await MemoryCacheTrialStatusAsync();
|
||||
|
||||
////await MemoryCacheAnonymizeData();
|
||||
|
||||
|
||||
////创建项目缓存 定时任务
|
||||
//HangfireJobHelper.AddOrUpdateInitCronJob<IIRaCISHangfireJob>("RecurringJob_Cache_TrialState", t => t.MemoryCacheTrialStatusAsync(), Cron.Daily());
|
||||
#endregion
|
||||
|
||||
|
||||
_logger.LogInformation("项目启动 hangfire 任务初始化 执行结束");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public async Task InitSysAndTrialCronJobAsync()
|
||||
{
|
||||
//var deleteJobIdList = await _trialEmailNoticeConfigRepository.Where(t => t.Trial.TrialStatusStr != StaticData.TrialState.TrialOngoing && t.EmailCron != string.Empty && t.IsAutoSend)
|
||||
// .Select(t => t.TrialId + "_" + t.Id)
|
||||
// .ToListAsync();
|
||||
|
||||
//foreach (var jobId in deleteJobIdList)
|
||||
//{
|
||||
// HangfireJobHelper.RemoveCronJob(jobId);
|
||||
//}
|
||||
|
||||
|
||||
var taskInfoList = await _trialEmailNoticeConfigRepository.Where(t => t.Trial.TrialStatusStr == StaticData.TrialState.TrialOngoing && t.EmailCron != string.Empty && t.IsAutoSend)
|
||||
.Select(t => new { t.Id, t.Code, t.EmailCron, t.BusinessScenarioEnum, t.TrialId })
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var task in taskInfoList)
|
||||
{
|
||||
//利用主键作为任务Id
|
||||
var jobId = $"{task.TrialId}_{task.Id}";
|
||||
|
||||
HangfireJobHelper.AddOrUpdateTrialCronJob(jobId, task.TrialId, task.BusinessScenarioEnum, task.EmailCron);
|
||||
}
|
||||
|
||||
var addOrUpdateJobIdList = taskInfoList.Select(t => $"{t.TrialId}_{t.Id}").ToList();
|
||||
|
||||
var list = JobStorage.Current.GetConnection().GetRecurringJobs().ToList();
|
||||
|
||||
//项目定时任务都在default 队列
|
||||
//var dbJobIdList = JobStorage.Current.GetConnection().GetRecurringJobs().Where(t => t.Queue == "default").Select(t => t.Id).ToList();
|
||||
|
||||
//var deleteList= dbJobIdList.Except(addOrUpdateJobIdList).ToList();
|
||||
|
||||
// foreach (var jobId in deleteList)
|
||||
// {
|
||||
// HangfireJobHelper.RemoveCronJob(jobId);
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -2,116 +2,30 @@
|
|||
using IRaCIS.Core.Application.Service.BusinessFilter;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Panda.DynamicWebApi;
|
||||
using Panda.DynamicWebApi.Attributes;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace IRaCIS.Core.Application.Service
|
||||
|
||||
namespace IRaCIS.Core.Application.Service;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[TypeFilter(typeof(UnifiedApiResultFilter))]
|
||||
[Authorize, DynamicWebApi]
|
||||
public class BaseService : IDynamicWebApi
|
||||
{
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
|
||||
#region 非泛型版本
|
||||
|
||||
[TypeFilter(typeof(UnifiedApiResultFilter))]
|
||||
[Authorize, DynamicWebApi]
|
||||
public class BaseService : IBaseService, IDynamicWebApi
|
||||
public static IResponseOutput Null404NotFound<TEntity>(TEntity? businessObject) where TEntity : class
|
||||
{
|
||||
public IMapper _mapper { get; set; }
|
||||
|
||||
public IUserInfo _userInfo { get; set; }
|
||||
|
||||
|
||||
|
||||
public IStringLocalizer _localizer { get; set; }
|
||||
|
||||
public IWebHostEnvironment _hostEnvironment { get; set; }
|
||||
|
||||
public IFusionCache _fusionCache { get; set; }
|
||||
|
||||
|
||||
public static IResponseOutput Null404NotFound<TEntity>(TEntity? businessObject) where TEntity : class
|
||||
{
|
||||
return new ResponseOutput<string>()
|
||||
.NotOk($"The query object {typeof(TEntity).Name} does not exist , or was deleted by someone else, or an incorrect parameter query caused", code: ApiResponseCodeEnum.DataNotExist);
|
||||
}
|
||||
return new ResponseOutput<string>()
|
||||
.NotOk($"The query object {typeof(TEntity).Name} does not exist , or was deleted by someone else, or an incorrect parameter query caused", code: ApiResponseCodeEnum.DataNotExist);
|
||||
}
|
||||
|
||||
|
||||
public interface IBaseService
|
||||
{
|
||||
[MemberNotNull(nameof(_mapper))]
|
||||
public IMapper _mapper { get; set; }
|
||||
|
||||
[MemberNotNull(nameof(_userInfo))]
|
||||
public IUserInfo _userInfo { get; set; }
|
||||
|
||||
|
||||
[MemberNotNull(nameof(_localizer))]
|
||||
public IStringLocalizer _localizer { get; set; }
|
||||
|
||||
[MemberNotNull(nameof(_hostEnvironment))]
|
||||
public IWebHostEnvironment _hostEnvironment { get; set; }
|
||||
|
||||
[MemberNotNull(nameof(_fusionCache))]
|
||||
public IFusionCache _fusionCache { get; set; }
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region 泛型版本测试
|
||||
|
||||
|
||||
public interface IBaseServiceTest<T> where T : Entity
|
||||
{
|
||||
[MemberNotNull(nameof(_mapper))]
|
||||
public IMapper _mapper { get; set; }
|
||||
|
||||
[MemberNotNull(nameof(_userInfo))]
|
||||
public IUserInfo _userInfo { get; set; }
|
||||
|
||||
[MemberNotNull(nameof(_localizer))]
|
||||
public IStringLocalizer _localizer { get; set; }
|
||||
|
||||
[MemberNotNull(nameof(_fusionCache))]
|
||||
public IFusionCache _fusionCache { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[TypeFilter(typeof(UnifiedApiResultFilter))]
|
||||
[Authorize, DynamicWebApi]
|
||||
public class BaseServiceTest<T> : IBaseServiceTest<T>, IDynamicWebApi where T : Entity
|
||||
{
|
||||
public IMapper _mapper { get; set; }
|
||||
|
||||
public IUserInfo _userInfo { get; set; }
|
||||
|
||||
public IStringLocalizer _localizer { get; set; }
|
||||
|
||||
public IFusionCache _fusionCache { get; set; }
|
||||
|
||||
public static IResponseOutput Null404NotFound<TEntity>(TEntity? businessObject) where TEntity : class
|
||||
{
|
||||
return new ResponseOutput<string>()
|
||||
.NotOk($"The query object {typeof(TEntity).Name} does not exist , or was deleted by someone else, or an incorrect parameter query caused", code: ApiResponseCodeEnum.DataNotExist);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
||||
namespace IRaCIS.Core.Application.BusinessFilter;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 不生效,不知道为啥
|
||||
/// </summary>
|
||||
public class GlobalExceptionHandler : IExceptionHandler
|
||||
{
|
||||
private readonly ILogger<GlobalExceptionHandler> _logger;
|
||||
public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
|
||||
{
|
||||
this._logger = logger;
|
||||
}
|
||||
public ValueTask<bool> TryHandleAsync(
|
||||
HttpContext httpContext,
|
||||
Exception exception,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
|
||||
httpContext.Response.ContentType = "application/json";
|
||||
|
||||
|
||||
var ex = exception;
|
||||
var errorInfo = $"Exception: {ex.Message}[{ex.StackTrace}]" + (ex.InnerException != null ? $" InnerException: {ex.InnerException.Message}[{ex.InnerException.StackTrace}]" : "");
|
||||
|
||||
httpContext.Response.WriteAsJsonAsync(ResponseOutput.NotOk($"{ex?.Message}"));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_logger.LogError(errorInfo);
|
||||
|
||||
// return true to signal that this exception is handled
|
||||
return ValueTask.FromResult(false);
|
||||
}
|
||||
}
|
|
@ -13,24 +13,10 @@ namespace IRaCIS.Core.Application.Filter;
|
|||
|
||||
|
||||
|
||||
public class LimitUserRequestAuthorization : IAsyncAuthorizationFilter
|
||||
public class LimitUserRequestAuthorization(
|
||||
IFusionCache _fusionCache, IUserInfo _userInfo, IStringLocalizer _localizer,
|
||||
IOptionsMonitor<ServiceVerifyConfigOption> _verifyConfig) : IAsyncAuthorizationFilter
|
||||
{
|
||||
public IStringLocalizer _localizer { get; set; }
|
||||
|
||||
private readonly IFusionCache _fusionCache;
|
||||
|
||||
private readonly IUserInfo _userInfo;
|
||||
|
||||
private readonly IOptionsMonitor<ServiceVerifyConfigOption> _verifyConfig;
|
||||
|
||||
public LimitUserRequestAuthorization(IFusionCache fusionCache, IUserInfo userInfo, IStringLocalizer localizer, IOptionsMonitor<ServiceVerifyConfigOption> verifyConfig)
|
||||
{
|
||||
_fusionCache = fusionCache;
|
||||
_userInfo = userInfo;
|
||||
_verifyConfig = verifyConfig;
|
||||
_localizer = localizer;
|
||||
}
|
||||
|
||||
|
||||
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
|
||||
{
|
|
@ -8,13 +8,9 @@ namespace IRaCIS.Core.Application.Filter;
|
|||
|
||||
|
||||
|
||||
public class ModelActionFilter : ActionFilterAttribute, IActionFilter
|
||||
public class ModelActionFilter(IStringLocalizer _localizer) : ActionFilterAttribute, IActionFilter
|
||||
{
|
||||
public IStringLocalizer _localizer;
|
||||
public ModelActionFilter(IStringLocalizer localizer)
|
||||
{
|
||||
_localizer = localizer;
|
||||
}
|
||||
|
||||
|
||||
public override void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
|
@ -0,0 +1,68 @@
|
|||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace IRaCIS.Core.Application.Filter;
|
||||
|
||||
public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, IStringLocalizer _localizer) : Attribute, IExceptionFilter
|
||||
{
|
||||
|
||||
public void OnException(ExceptionContext context)
|
||||
{
|
||||
//context.ExceptionHandled;//记录当前这个异常是否已经被处理过了
|
||||
|
||||
var exception = context.Exception;
|
||||
|
||||
if (!context.ExceptionHandled)
|
||||
{
|
||||
if (exception.GetType().Name == "DbUpdateConcurrencyException")
|
||||
{
|
||||
//---并发更新,当前不允许该操作
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["ProjectException_ConcurrentUpdateNotAllowed"] + exception.Message));
|
||||
}
|
||||
|
||||
if (exception.GetType() == typeof(BusinessValidationFailedException))
|
||||
{
|
||||
var error = exception as BusinessValidationFailedException;
|
||||
|
||||
var info = string.Empty;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(error!.LocalizedKey) && StaticData.Localizer_Dev_Dic.ContainsKey(error!.LocalizedKey))
|
||||
{
|
||||
info = $"[{error!.LocalizedKey}]:{StaticData.Localizer_Dev_Dic[error!.LocalizedKey]}";
|
||||
}
|
||||
|
||||
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(exception.Message, "", error!.Code, localizedInfo: info));
|
||||
|
||||
}
|
||||
else if (exception.GetType() == typeof(QueryBusinessObjectNotExistException))
|
||||
{
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(exception.Message, ApiResponseCodeEnum.DataNotExist));
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["Project_ExceptionContactDeveloper"] + (exception.InnerException is null ? (exception.Message)
|
||||
: (exception.InnerException?.Message )), ApiResponseCodeEnum.ProgramException));
|
||||
|
||||
}
|
||||
|
||||
context.ExceptionHandled = true;//标记当前异常已经被处理过了
|
||||
|
||||
|
||||
var errorInfo = $"Exception: {exception.Message}[{exception.StackTrace}]" + (exception.InnerException != null ? $" InnerException: {exception.InnerException.Message}[{exception.InnerException.StackTrace}]" : "");
|
||||
|
||||
|
||||
_logger.LogError(errorInfo);
|
||||
|
||||
//_logger.LogError(exception, exception.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
//继续
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
using IRaCIS.Core.Application.BusinessFilter;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using static IRaCIS.Core.Domain.Share.StaticData;
|
||||
|
||||
namespace IRaCIS.Core.Application.Filter;
|
||||
|
||||
public class TrialGlobalLimitActionFilter(IFusionCache _fusionCache, IUserInfo _userInfo, IRepository<Trial> _trialRepository) : IAsyncActionFilter
|
||||
{
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
// 检查当前 endpoint 是否包含 TrialGlobalLimitAttribute 特性
|
||||
var trialLimitAttribute = context.HttpContext.GetEndpoint()?.Metadata.GetMetadata<TrialGlobalLimitAttribute>();
|
||||
|
||||
if (trialLimitAttribute != null)
|
||||
{
|
||||
var optActions = trialLimitAttribute._optActions;
|
||||
|
||||
|
||||
// 这里开始执行原有的逻辑
|
||||
var requestHost = context.HttpContext.Request.Host;
|
||||
|
||||
// 检查请求是否来自 localhost:6100
|
||||
//if (requestHost.Host == "localhost" && (requestHost.Port == 6100 || requestHost.Port == 3305))
|
||||
//{
|
||||
// await next();
|
||||
// return;
|
||||
//}
|
||||
|
||||
#region 特殊用户类型拦截
|
||||
// 用户类型检查
|
||||
if (_userInfo.UserTypeEnumInt == (int)UserTypeEnum.CRA && _userInfo.RequestUrl.ToLower() != "TrialDocument/userConfirm".ToLower())
|
||||
{
|
||||
//对不起,您的账户没有操作权限
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_NoAccessPermission")));
|
||||
|
||||
return;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region 从多种途径取TrialId
|
||||
//TrialId 传递的途径多种,可能在path 可能在body 可能在数组中,也可能在对象中,可能就在url
|
||||
var trialIdStr = string.Empty;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(context.HttpContext.Request.Query["trialId"]))
|
||||
{
|
||||
trialIdStr = context.HttpContext.Request.Query["trialId"];
|
||||
}
|
||||
|
||||
//先尝试从path中取TrialId
|
||||
else if (context.HttpContext.Request.RouteValues.Keys.Any(t => t.Contains("trialId")))
|
||||
{
|
||||
var index = context.HttpContext.Request.RouteValues.Keys.ToList().IndexOf("trialId");
|
||||
trialIdStr = context.HttpContext.Request.RouteValues.Values.ToList()[index] as string;
|
||||
}
|
||||
else if (context.HttpContext.Request.Headers["Referer"].ToString().Contains("trialId"))
|
||||
{
|
||||
var headerStr = context.HttpContext.Request.Headers["Referer"].ToString();
|
||||
|
||||
var trialIdIndex = headerStr.IndexOf("trialId");
|
||||
|
||||
|
||||
var matchResult = Regex.Match(headerStr.Substring(trialIdIndex), @"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}");
|
||||
|
||||
if (matchResult.Success)
|
||||
{
|
||||
trialIdStr = matchResult.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
//---正则取请求Refer 中trialId 失败,请联系开发人员核查
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_ReferTrialIdFailed")));
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
#region body 中取数据
|
||||
|
||||
//设置可以多次读
|
||||
context.HttpContext.Request.EnableBuffering();
|
||||
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
|
||||
var reader = new StreamReader(context.HttpContext.Request.Body);
|
||||
var contentFromBody = await reader.ReadToEndAsync();
|
||||
//读取后,流的位置还原
|
||||
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
|
||||
//context.HttpContext.Request.Body.Position = 0;
|
||||
|
||||
//找到参数位置在字符串中的索引
|
||||
var trialIdIndex = contentFromBody.IndexOf("\"TrialId\"", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (trialIdIndex > -1)
|
||||
{
|
||||
// (?<="trialId" *: *").*?(?=",)
|
||||
|
||||
//使用正则 [0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}
|
||||
|
||||
var matchResult = Regex.Match(contentFromBody.Substring(trialIdIndex), @"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}");
|
||||
|
||||
if (matchResult.Success)
|
||||
{
|
||||
//有可能匹配错误 "trialId":"","documentId":"b8180000-3e2c-0016-9fe0-08da33f96236" 从缓存里面验证下
|
||||
|
||||
trialIdStr = matchResult.Value;
|
||||
|
||||
var trialStatusStr = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(trialStatusStr))
|
||||
{
|
||||
|
||||
//数据库 检查该项目Id不对
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_ReferTrialIdFailed")));
|
||||
return;
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//---正则取请求Refer 中trialId 失败,请联系开发人员核查
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_ReferTrialIdFailed")));
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
//使用字符串取 如果是swagger 可能有时取的不对 因为空格的原因
|
||||
//trialIdStr = contentFromBody.Substring(trialIdIndex + "TrialId".Length + 4, 3
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
//通过path 或者body 找到trialId 了
|
||||
if (!string.IsNullOrWhiteSpace(trialIdStr))
|
||||
{
|
||||
var trialStatusStr = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7));
|
||||
|
||||
// 这里是统一拦截 项目有关的操作允许情况(特殊的地方,比如项目配置(有的在多种状态(初始化,ongoing)都可以操作,有的仅仅在Initializing)还有 项目添加和更新,不走这里,特殊处理,不然在这里显得很乱,判断是哪个接口)
|
||||
if (trialStatusStr == StaticData.TrialState.TrialOngoing || optActions.Any(t => t == TrialOpt.BeforeOngoingCantOpt))
|
||||
{
|
||||
|
||||
await next();
|
||||
|
||||
}
|
||||
// 项目停止、或者完成 不允许操作
|
||||
else
|
||||
{
|
||||
//---本次请求被配置规则拦截:项目状态处于进行中时,才允许操作,若此处逻辑有误,请联系开发人员修改
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_InterceptedProjectStatusRule")));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
//添加项目 签名系统文档的时候 不做拦截 但是更新项目 签名项目文档的时候需要拦截
|
||||
else if (optActions.Any(t => t == TrialOpt.AddOrUpdateTrial || t == TrialOpt.SignSystemDocNoTrialId))
|
||||
{
|
||||
await next();
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
//如果项目相关接口没有传递trialId 会来到这里,提醒,以便修改
|
||||
|
||||
//---该接口参数中,没有传递项目编号,请核对。
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_MissingProjectNumber")));
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果没有 TrialGlobalLimitAttribute,则直接执行后续逻辑
|
||||
await next();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -1,10 +1,13 @@
|
|||
using IRaCIS.Core.Domain.Share;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace IRaCIS.Core.Application.Service.BusinessFilter;
|
||||
|
||||
|
||||
#region 控制器流程
|
||||
/// <summary>
|
||||
/// 统一返回前端数据包装,之前在控制器包装,现在修改为动态Api 在ResultFilter这里包装,减少重复冗余代码
|
||||
/// by zhouhang 2021.09.12 周末
|
||||
|
@ -80,7 +83,7 @@ public class UnifiedApiResultFilter : Attribute, IAsyncResultFilter
|
|||
else if (statusCode != 200 && !(objectResult.Value is IResponseOutput))
|
||||
{
|
||||
//---程序错误,请联系开发人员。
|
||||
var apiResponse = ResponseOutput.NotOk(StaticData.International("UnifiedAPI_ProgramError"));
|
||||
var apiResponse = ResponseOutput.NotOk(I18n.T("UnifiedAPI_ProgramError"));
|
||||
|
||||
objectResult.Value = apiResponse;
|
||||
objectResult.DeclaredType = apiResponse.GetType();
|
||||
|
@ -120,3 +123,8 @@ public class UnifiedApiResultFilter : Attribute, IAsyncResultFilter
|
|||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
using DocumentFormat.OpenXml.InkML;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
||||
namespace IRaCIS.Core.Application.BusinessFilter;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// minimal api 生效,但是传统控制器,没生效
|
||||
/// 参考处理链接: https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/error-handling?view=aspnetcore-8.0
|
||||
/// </summary>
|
||||
public class GlobalExceptionHandler(IStringLocalizer _localizer, ILogger<GlobalExceptionHandler> _logger) : IExceptionHandler
|
||||
{
|
||||
|
||||
public ValueTask<bool> TryHandleAsync(
|
||||
HttpContext httpContext,
|
||||
Exception exception,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
httpContext.Response.StatusCode =StatusCodes.Status200OK;
|
||||
|
||||
httpContext.Response.ContentType = "application/json";
|
||||
|
||||
if (exception.GetType().Name == "DbUpdateConcurrencyException")
|
||||
{
|
||||
//---并发更新,当前不允许该操作
|
||||
|
||||
httpContext.Response.WriteAsJsonAsync(ResponseOutput.NotOk(_localizer["ProjectException_ConcurrentUpdateNotAllowed"] + exception.Message));
|
||||
}
|
||||
|
||||
if (exception.GetType() == typeof(BusinessValidationFailedException))
|
||||
{
|
||||
var error = exception as BusinessValidationFailedException;
|
||||
|
||||
var info = string.Empty;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(error!.LocalizedKey) && StaticData.Localizer_Dev_Dic.ContainsKey(error!.LocalizedKey))
|
||||
{
|
||||
info = $"[{error!.LocalizedKey}]:{StaticData.Localizer_Dev_Dic[error!.LocalizedKey]}";
|
||||
}
|
||||
|
||||
httpContext.Response.WriteAsJsonAsync(ResponseOutput.NotOk(exception.Message, "", error!.Code, localizedInfo: info));
|
||||
|
||||
|
||||
|
||||
//warning 级别记录
|
||||
//_logger.LogWarning($"[{error!.LocalizedKey}]:{StaticData.Log_Locoalize_Dic[error!.LocalizedKey]}");
|
||||
}
|
||||
else if (exception.GetType() == typeof(QueryBusinessObjectNotExistException))
|
||||
{
|
||||
httpContext.Response.WriteAsJsonAsync(ResponseOutput.NotOk(exception.Message, ApiResponseCodeEnum.DataNotExist));
|
||||
}
|
||||
else
|
||||
{
|
||||
httpContext.Response.WriteAsJsonAsync(ResponseOutput.NotOk(_localizer["Project_ExceptionContactDeveloper"] + (exception.InnerException is null ? (exception.Message )
|
||||
: (exception.InnerException?.Message)), ApiResponseCodeEnum.ProgramException));
|
||||
|
||||
}
|
||||
|
||||
var errorInfo = $"Exception: {exception.Message}[{exception.StackTrace}]" + (exception.InnerException != null ? $" InnerException: {exception.InnerException.Message}[{exception.InnerException.StackTrace}]" : "");
|
||||
|
||||
_logger.LogError(errorInfo);
|
||||
|
||||
// Return false to continue with the default behavior
|
||||
// - or - return true to signal that this exception is handled
|
||||
return ValueTask.FromResult(false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
using IRaCIS.Core.Application.Helper;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IRaCIS.Core.Application.Service.BusinessFilter;
|
||||
|
||||
#region minimalapi 流程
|
||||
|
||||
|
||||
public class LimitUserRequestAuthorizationEndpointFilter(
|
||||
IFusionCache _fusionCache, IUserInfo _userInfo, IStringLocalizer _localizer,
|
||||
IOptionsMonitor<ServiceVerifyConfigOption> _verifyConfig) : IEndpointFilter
|
||||
{
|
||||
|
||||
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
|
||||
{
|
||||
var minutes = _verifyConfig.CurrentValue.AutoLoginOutMinutes;
|
||||
|
||||
if (_verifyConfig.CurrentValue.OpenLoginLimit)
|
||||
{
|
||||
// 如果此请求允许匿名访问,不进行处理
|
||||
var endpoint = context.HttpContext.GetEndpoint();
|
||||
if (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null)
|
||||
{
|
||||
return await next(context);
|
||||
}
|
||||
|
||||
// 没有从请求中取到 token
|
||||
if (string.IsNullOrWhiteSpace(_userInfo.UserToken))
|
||||
{
|
||||
context.HttpContext.Response.ContentType = "application/json";
|
||||
return Results.Json(ResponseOutput.NotOk(_localizer["LimitUser_AuthTokenMissing"]), statusCode: StatusCodes.Status200OK);
|
||||
}
|
||||
|
||||
// 获取缓存中的用户 token
|
||||
var cacheUserToken = await _fusionCache.GetOrDefaultAsync<string>(CacheKeys.UserToken(_userInfo.Id));
|
||||
|
||||
// 缓存中没有取到 token
|
||||
if (string.IsNullOrWhiteSpace(cacheUserToken))
|
||||
{
|
||||
// 设置当前用户最新 token
|
||||
await _fusionCache.SetAsync(CacheKeys.UserToken(_userInfo.Id), _userInfo.UserToken, TimeSpan.FromDays(7));
|
||||
await _fusionCache.SetAsync(CacheKeys.UserAutoLoginOut(_userInfo.Id), DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), TimeSpan.FromMinutes(minutes));
|
||||
}
|
||||
// 如果是同一个用户
|
||||
else if (cacheUserToken == _userInfo.UserToken)
|
||||
{
|
||||
var cacheTime = await _fusionCache.GetOrDefaultAsync<string>(CacheKeys.UserAutoLoginOut(_userInfo.Id));
|
||||
|
||||
// 如果过期,自动登出
|
||||
if (string.IsNullOrEmpty(cacheTime))
|
||||
{
|
||||
context.HttpContext.Response.ContentType = "application/json";
|
||||
return Results.Json(ResponseOutput.NotOk(_localizer["LimitUser_AccountAuto_LoginOut"], ApiResponseCodeEnum.AutoLoginOut), statusCode: StatusCodes.Status403Forbidden);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _fusionCache.SetAsync(CacheKeys.UserAutoLoginOut(_userInfo.Id), DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), TimeSpan.FromMinutes(minutes));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果账户在其他地方已登录,当前用户被迫下线
|
||||
context.HttpContext.Response.ContentType = "application/json";
|
||||
return Results.Json(ResponseOutput.NotOk(_localizer["LimitUser_AccountLoggedInElsewhere"], ApiResponseCodeEnum.LoginInOtherPlace), statusCode: StatusCodes.Status403Forbidden);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果通过授权,继续执行请求管道
|
||||
return await next(context);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
|
@ -0,0 +1,58 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IRaCIS.Core.Application.BusinessFilter;
|
||||
|
||||
public class ModelValidationEndpointFilter(IStringLocalizer _localizer) : IEndpointFilter
|
||||
{
|
||||
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
|
||||
{
|
||||
var httpContext = context.HttpContext;
|
||||
|
||||
//// 1. 获取路由参数并验证
|
||||
//foreach (var routeValue in httpContext.Request.RouteValues)
|
||||
//{
|
||||
// {
|
||||
// return Results.BadRequest(new { ErrorMessage = $"Invalid format for {routeValue.Key}." });
|
||||
// }
|
||||
//}
|
||||
|
||||
//// 2. 获取查询参数并验证
|
||||
//foreach (var queryValue in httpContext.Request.Query)
|
||||
//{
|
||||
// {
|
||||
// return Results.BadRequest(new { ErrorMessage = $"Invalid format for {queryValue.Key}." });
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
// 获取请求中的模型参数
|
||||
var model = context.Arguments.FirstOrDefault(a => a != null && a.GetType().IsClass);
|
||||
|
||||
// 仅当模型存在时执行验证
|
||||
if (model != null)
|
||||
{
|
||||
var validationResults = new List<ValidationResult>();
|
||||
var validationContext = new ValidationContext(model);
|
||||
bool isValid = Validator.TryValidateObject(model, validationContext, validationResults, true);
|
||||
|
||||
if (!isValid)
|
||||
{
|
||||
var validationErrors = validationResults.Select(vr => vr.ErrorMessage).ToArray();
|
||||
return Results.Json(new
|
||||
{
|
||||
ErrorMessage = _localizer["ModelAction_InvalidAPIParameter"],
|
||||
Errors = validationErrors
|
||||
}, statusCode: StatusCodes.Status400BadRequest);
|
||||
}
|
||||
}
|
||||
|
||||
// 验证通过,继续执行
|
||||
return await next(context);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
using IRaCIS.Core.Application.Helper;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using static IRaCIS.Core.Domain.Share.StaticData;
|
||||
|
||||
namespace IRaCIS.Core.Application.BusinessFilter;
|
||||
|
||||
public class TrialGlobalLimitAttribute : Attribute
|
||||
{
|
||||
public readonly string[] _optActions;
|
||||
|
||||
public TrialGlobalLimitAttribute(params string[] optActions)
|
||||
{
|
||||
_optActions = optActions;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public class TrialGlobalLimitEndpointFilter(IFusionCache _fusionCache, IUserInfo _userInfo, IRepository<Trial> _trialRepository) : IEndpointFilter
|
||||
|
||||
{
|
||||
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
|
||||
{
|
||||
// 检查当前 endpoint 是否包含 TrialGlobalLimitAttribute 特性
|
||||
var trialLimitAttribute = context.HttpContext.GetEndpoint()?.Metadata.GetMetadata<TrialGlobalLimitAttribute>();
|
||||
|
||||
if (trialLimitAttribute != null)
|
||||
{
|
||||
var optActions = trialLimitAttribute._optActions;
|
||||
|
||||
|
||||
// 这里开始执行原有的逻辑
|
||||
var requestHost = context.HttpContext.Request.Host;
|
||||
|
||||
// 检查请求是否来自 localhost:6100
|
||||
if (requestHost.Host == "localhost" && (requestHost.Port == 6100 || requestHost.Port == 3305))
|
||||
{
|
||||
return await next(context);
|
||||
}
|
||||
|
||||
#region 特殊用户类型拦截
|
||||
// 用户类型检查
|
||||
if (_userInfo.UserTypeEnumInt == (int)UserTypeEnum.CRA && _userInfo.RequestUrl.ToLower() != "TrialDocument/userConfirm".ToLower())
|
||||
{
|
||||
//对不起,您的账户没有操作权限
|
||||
return Results.Json(ResponseOutput.NotOk(I18n.T("TrialResource_NoAccessPermission")));
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region 从多种途径取TrialId
|
||||
//TrialId 传递的途径多种,可能在path 可能在body 可能在数组中,也可能在对象中,可能就在url
|
||||
var trialIdStr = string.Empty;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(context.HttpContext.Request.Query["trialId"]))
|
||||
{
|
||||
trialIdStr = context.HttpContext.Request.Query["trialId"];
|
||||
}
|
||||
|
||||
//先尝试从path中取TrialId
|
||||
else if (context.HttpContext.Request.RouteValues.Keys.Any(t => t.Contains("trialId")))
|
||||
{
|
||||
var index = context.HttpContext.Request.RouteValues.Keys.ToList().IndexOf("trialId");
|
||||
trialIdStr = context.HttpContext.Request.RouteValues.Values.ToList()[index] as string;
|
||||
}
|
||||
else if (context.HttpContext.Request.Headers["Referer"].ToString().Contains("trialId"))
|
||||
{
|
||||
var headerStr = context.HttpContext.Request.Headers["Referer"].ToString();
|
||||
|
||||
var trialIdIndex = headerStr.IndexOf("trialId");
|
||||
|
||||
|
||||
var matchResult = Regex.Match(headerStr.Substring(trialIdIndex), @"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}");
|
||||
|
||||
if (matchResult.Success)
|
||||
{
|
||||
trialIdStr = matchResult.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
//---正则取请求Refer 中trialId 失败,请联系开发人员核查
|
||||
return Results.Json(ResponseOutput.NotOk(I18n.T("TrialResource_ReferTrialIdFailed")));
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
#region body 中取数据
|
||||
|
||||
//设置可以多次读
|
||||
context.HttpContext.Request.EnableBuffering();
|
||||
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
|
||||
var reader = new StreamReader(context.HttpContext.Request.Body);
|
||||
var contentFromBody = await reader.ReadToEndAsync();
|
||||
//读取后,流的位置还原
|
||||
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
|
||||
//context.HttpContext.Request.Body.Position = 0;
|
||||
|
||||
//找到参数位置在字符串中的索引
|
||||
var trialIdIndex = contentFromBody.IndexOf("\"TrialId\"", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (trialIdIndex > -1)
|
||||
{
|
||||
// (?<="trialId" *: *").*?(?=",)
|
||||
|
||||
//使用正则 [0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}
|
||||
|
||||
var matchResult = Regex.Match(contentFromBody.Substring(trialIdIndex), @"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}");
|
||||
|
||||
if (matchResult.Success)
|
||||
{
|
||||
//有可能匹配错误 "trialId":"","documentId":"b8180000-3e2c-0016-9fe0-08da33f96236" 从缓存里面验证下
|
||||
|
||||
trialIdStr = matchResult.Value;
|
||||
|
||||
var trialStatusStr = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(trialStatusStr))
|
||||
{
|
||||
|
||||
//数据库 检查该项目Id不对
|
||||
return Results.Json(ResponseOutput.NotOk(I18n.T("TrialResource_ReferTrialIdFailed")));
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//---正则取请求Refer 中trialId 失败,请联系开发人员核查
|
||||
return Results.Json(ResponseOutput.NotOk(I18n.T("TrialResource_ReferTrialIdFailed")));
|
||||
|
||||
}
|
||||
|
||||
//使用字符串取 如果是swagger 可能有时取的不对 因为空格的原因
|
||||
//trialIdStr = contentFromBody.Substring(trialIdIndex + "TrialId".Length + 4, 3
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
//通过path 或者body 找到trialId 了
|
||||
if (!string.IsNullOrWhiteSpace(trialIdStr))
|
||||
{
|
||||
var trialStatusStr = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7));
|
||||
|
||||
// 这里是统一拦截 项目有关的操作允许情况(特殊的地方,比如项目配置(有的在多种状态(初始化,ongoing)都可以操作,有的仅仅在Initializing)还有 项目添加和更新,不走这里,特殊处理,不然在这里显得很乱,判断是哪个接口)
|
||||
if (trialStatusStr == StaticData.TrialState.TrialOngoing || optActions.Any(t => t == TrialOpt.BeforeOngoingCantOpt))
|
||||
{
|
||||
|
||||
return await next(context);
|
||||
|
||||
}
|
||||
// 项目停止、或者完成 不允许操作
|
||||
else
|
||||
{
|
||||
//---本次请求被配置规则拦截:项目状态处于进行中时,才允许操作,若此处逻辑有误,请联系开发人员修改
|
||||
return Results.Json(ResponseOutput.NotOk(I18n.T("TrialResource_InterceptedProjectStatusRule")));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
//添加项目 签名系统文档的时候 不做拦截 但是更新项目 签名项目文档的时候需要拦截
|
||||
else if (optActions.Any(t => t == TrialOpt.AddOrUpdateTrial || t == TrialOpt.SignSystemDocNoTrialId))
|
||||
{
|
||||
return await next(context);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
//如果项目相关接口没有传递trialId 会来到这里,提醒,以便修改
|
||||
|
||||
//---该接口参数中,没有传递项目编号,请核对。
|
||||
return Results.Json(ResponseOutput.NotOk(I18n.T("TrialResource_MissingProjectNumber")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 如果没有 TrialGlobalLimitAttribute,则直接执行后续逻辑
|
||||
return await next(context);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IRaCIS.Core.Application.Service.BusinessFilter;
|
||||
|
||||
#region minimalapi 流程
|
||||
|
||||
|
||||
public class UnifiedApiResultEndpointFilter(ILogger<UnifiedApiResultEndpointFilter> _logger) : IEndpointFilter
|
||||
{
|
||||
|
||||
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
|
||||
{
|
||||
// 调用下一个过滤器或端点处理程序
|
||||
var result = await next(context);
|
||||
|
||||
// 检查返回值类型
|
||||
if (result is null)
|
||||
{
|
||||
return ResponseOutput.NotOk("No data found."); // 处理 null 返回
|
||||
}
|
||||
|
||||
// 如果返回的是元组
|
||||
if (result is ValueTuple<object, object> tuple)
|
||||
{
|
||||
return ResponseOutput.Ok(tuple.Item1, tuple.Item2);
|
||||
}
|
||||
|
||||
if (result is IResponseOutput responseOutput)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(responseOutput.LocalizedInfo))
|
||||
{
|
||||
//统一在这里记录国际化的日志信息
|
||||
_logger.LogWarning($"{responseOutput.LocalizedInfo}");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 对于其他情况,直接返回结果
|
||||
return ResponseOutput.Ok(result);
|
||||
}
|
||||
|
||||
private IResponseOutput WrapResponse(object? value)
|
||||
{
|
||||
// 处理元组的情况
|
||||
if (value is ValueTuple<object, object> tuple)
|
||||
{
|
||||
return ResponseOutput.Ok(tuple.Item1, tuple.Item2);
|
||||
}
|
||||
|
||||
// 包装单个对象
|
||||
return ResponseOutput.Ok(value);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
|
@ -1,26 +0,0 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace IRaCIS.Core.Application.Filter;
|
||||
|
||||
#region snippet_DisableFormValueModelBindingAttribute
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
|
||||
{
|
||||
public void OnResourceExecuting(ResourceExecutingContext context)
|
||||
{
|
||||
|
||||
|
||||
var factories = context.ValueProviderFactories;
|
||||
//factories.RemoveType<FormValueProviderFactory>();
|
||||
factories.RemoveType<FormFileValueProviderFactory>();
|
||||
factories.RemoveType<JQueryFormValueProviderFactory>();
|
||||
context.HttpContext.Request.EnableBuffering();
|
||||
}
|
||||
|
||||
public void OnResourceExecuted(ResourceExecutedContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
#endregion
|
|
@ -1,75 +0,0 @@
|
|||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace IRaCIS.Core.Application.Filter;
|
||||
|
||||
public class ProjectExceptionFilter : Attribute, IExceptionFilter
|
||||
{
|
||||
private readonly ILogger<ProjectExceptionFilter> _logger;
|
||||
|
||||
public IStringLocalizer _localizer;
|
||||
|
||||
public ProjectExceptionFilter(IStringLocalizer localizer, ILogger<ProjectExceptionFilter> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_localizer = localizer;
|
||||
}
|
||||
public void OnException(ExceptionContext context)
|
||||
{
|
||||
//context.ExceptionHandled;//记录当前这个异常是否已经被处理过了
|
||||
|
||||
if (!context.ExceptionHandled)
|
||||
{
|
||||
if (context.Exception.GetType().Name == "DbUpdateConcurrencyException")
|
||||
{
|
||||
//---并发更新,当前不允许该操作
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["ProjectException_ConcurrentUpdateNotAllowed"] + context.Exception.Message));
|
||||
}
|
||||
|
||||
if (context.Exception.GetType() == typeof(BusinessValidationFailedException))
|
||||
{
|
||||
var error = context.Exception as BusinessValidationFailedException;
|
||||
|
||||
var info = string.Empty;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(error!.LocalizedKey) && StaticData.Log_Locoalize_Dic.ContainsKey(error!.LocalizedKey))
|
||||
{
|
||||
info = $"[{error!.LocalizedKey}]:{StaticData.Log_Locoalize_Dic[error!.LocalizedKey]}";
|
||||
}
|
||||
|
||||
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(context.Exception.Message, "", error!.Code, localizedInfo: info));
|
||||
|
||||
|
||||
//warning 级别记录
|
||||
//_logger.LogWarning($"[{error!.LocalizedKey}]:{StaticData.Log_Locoalize_Dic[error!.LocalizedKey]}");
|
||||
}
|
||||
else if (context.Exception.GetType() == typeof(QueryBusinessObjectNotExistException))
|
||||
{
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(context.Exception.Message, ApiResponseCodeEnum.DataNotExist));
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["Project_ExceptionContactDeveloper"] + (context.Exception.InnerException is null ? (context.Exception.Message /*+ context.Exception.StackTrace*/)
|
||||
: (context.Exception.InnerException?.Message /*+ context.Exception.InnerException?.StackTrace*/)), ApiResponseCodeEnum.ProgramException));
|
||||
|
||||
_logger.LogError(context.Exception.InnerException is null ? (context.Exception.Message + context.Exception.StackTrace) : (context.Exception.InnerException?.Message + context.Exception.InnerException?.StackTrace));
|
||||
|
||||
}
|
||||
|
||||
context.ExceptionHandled = true;//标记当前异常已经被处理过了
|
||||
|
||||
|
||||
//throw new Exception("test-result-exceptioin");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//继续
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,196 +0,0 @@
|
|||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using System.Text.RegularExpressions;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
using static IRaCIS.Core.Domain.Share.StaticData;
|
||||
|
||||
namespace IRaCIS.Core.Application.Filter;
|
||||
|
||||
/// <summary>
|
||||
/// 主要为了 处理项目结束 锁库,不允许操作
|
||||
/// </summary>
|
||||
public class TrialResourceFilter : Attribute, IAsyncResourceFilter
|
||||
{
|
||||
private readonly IUserInfo _userInfo;
|
||||
private readonly IFusionCache _fusionCache;
|
||||
public IStringLocalizer _localizer;
|
||||
private readonly IRepository<Trial> _trialRepository;
|
||||
private readonly List<string> _trialOptList = new List<string>();
|
||||
|
||||
|
||||
public TrialResourceFilter(IFusionCache fusionCache, IRepository<Trial> trialRepository, IStringLocalizer localizer, IUserInfo userInfo, string trialOpt = null, string trialOpt2 = null, string trialOpt3 = null)
|
||||
{
|
||||
_fusionCache = fusionCache;
|
||||
_userInfo = userInfo;
|
||||
_localizer = localizer;
|
||||
_trialRepository = trialRepository;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(trialOpt)) _trialOptList.Add(trialOpt.Trim());
|
||||
if (!string.IsNullOrWhiteSpace(trialOpt2)) _trialOptList.Add(trialOpt2.Trim());
|
||||
if (!string.IsNullOrWhiteSpace(trialOpt3)) _trialOptList.Add(trialOpt3.Trim());
|
||||
|
||||
}
|
||||
|
||||
//优先选择异步的方法
|
||||
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
|
||||
{
|
||||
// var typeFilter = context.ActionDescriptor.EndpointMetadata.Where(t => t.GetType() == typeof(TypeFilterAttribute)).Select(t => (TypeFilterAttribute)t).ToList().FirstOrDefault();
|
||||
//var _trialOptList= typeFilter.Arguments.Select(t => t.ToString()).ToList();
|
||||
|
||||
// 获取当前请求的 Host 信息
|
||||
var requestHost = context.HttpContext.Request.Host;
|
||||
|
||||
// 检查请求是否来自 localhost:6100
|
||||
if (requestHost.Host == "localhost" && (requestHost.Port == 6100|| requestHost.Port==3305))
|
||||
{
|
||||
await next.Invoke();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#region 处理新的用户类型,不能操作项目相关接口
|
||||
|
||||
// 后期列举出具体的类型,其他任何用户类型,都不允许操作
|
||||
if (_userInfo.UserTypeEnumInt == (int)UserTypeEnum.CRA && _userInfo.RequestUrl.ToLower() != "TrialDocument/userConfirm".ToLower())
|
||||
{
|
||||
//---对不起,您的账户没有操作权限。
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["TrialResource_NoAccessPermission"]));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
//TrialId 传递的途径多种,可能在path 可能在body 可能在数组中,也可能在对象中,可能就在url
|
||||
var trialIdStr = string.Empty;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(context.HttpContext.Request.Query["trialId"]))
|
||||
{
|
||||
trialIdStr = context.HttpContext.Request.Query["trialId"];
|
||||
}
|
||||
|
||||
//先尝试从path中取TrialId
|
||||
else if (context.RouteData.Values.Keys.Any(t => t.Contains("trialId")))
|
||||
{
|
||||
var index = context.RouteData.Values.Keys.ToList().IndexOf("trialId");
|
||||
trialIdStr = context.RouteData.Values.Values.ToList()[index] as string;
|
||||
}
|
||||
else if (context.HttpContext.Request.Headers["Referer"].ToString().Contains("trialId"))
|
||||
{
|
||||
var headerStr = context.HttpContext.Request.Headers["Referer"].ToString();
|
||||
|
||||
var trialIdIndex = headerStr.IndexOf("trialId");
|
||||
|
||||
|
||||
var matchResult = Regex.Match(headerStr.Substring(trialIdIndex), @"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}");
|
||||
|
||||
if (matchResult.Success)
|
||||
{
|
||||
trialIdStr = matchResult.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
//---正则取请求Refer 中trialId 失败,请联系开发人员核查
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["TrialResource_ReferTrialIdFailed"]));
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
#region body 中取数据
|
||||
|
||||
//设置可以多次读
|
||||
context.HttpContext.Request.EnableBuffering();
|
||||
var reader = new StreamReader(context.HttpContext.Request.Body);
|
||||
var contentFromBody = await reader.ReadToEndAsync();
|
||||
//读取后,流的位置还原
|
||||
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
|
||||
//context.HttpContext.Request.Body.Position = 0;
|
||||
|
||||
//找到参数位置在字符串中的索引
|
||||
var trialIdIndex = contentFromBody.IndexOf("\"TrialId\"", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (trialIdIndex > -1)
|
||||
{
|
||||
// (?<="trialId" *: *").*?(?=",)
|
||||
|
||||
//使用正则 [0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}
|
||||
|
||||
var matchResult = Regex.Match(contentFromBody.Substring(trialIdIndex), @"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}");
|
||||
|
||||
if (matchResult.Success)
|
||||
{
|
||||
//有可能匹配错误 "trialId":"","documentId":"b8180000-3e2c-0016-9fe0-08da33f96236" 从缓存里面验证下
|
||||
|
||||
trialIdStr = matchResult.Value;
|
||||
|
||||
var trialStatusStr = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(trialStatusStr))
|
||||
{
|
||||
|
||||
//数据库 检查该项目Id不对
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["TrialResource_ReferTrialIdFailed"]));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//---正则取请求Refer 中trialId 失败,请联系开发人员核查
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["TrialResource_ReferTrialIdFailed"]));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//使用字符串取 如果是swagger 可能有时取的不对 因为空格的原因
|
||||
//trialIdStr = contentFromBody.Substring(trialIdIndex + "TrialId".Length + 4, 3
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
//通过path 或者body 找到trialId 了
|
||||
if (!string.IsNullOrWhiteSpace(trialIdStr))
|
||||
{
|
||||
var trialStatusStr = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7));
|
||||
|
||||
// 这里是统一拦截 项目有关的操作允许情况(特殊的地方,比如项目配置(有的在多种状态(初始化,ongoing)都可以操作,有的仅仅在Initializing)还有 项目添加和更新,不走这里,特殊处理,不然在这里显得很乱,判断是哪个接口)
|
||||
if (trialStatusStr == StaticData.TrialState.TrialOngoing || _trialOptList.Any(t => t == TrialOpt.BeforeOngoingCantOpt))
|
||||
{
|
||||
|
||||
await next.Invoke();
|
||||
|
||||
}
|
||||
// 项目停止、或者完成 不允许操作
|
||||
else
|
||||
{
|
||||
//---本次请求被配置规则拦截:项目状态处于进行中时,才允许操作,若此处逻辑有误,请联系开发人员修改
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["TrialResource_InterceptedProjectStatusRule"]));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
//添加项目 签名系统文档的时候 不做拦截 但是更新项目 签名项目文档的时候需要拦截
|
||||
else if (_trialOptList.Any(t => t == TrialOpt.AddOrUpdateTrial || t == TrialOpt.SignSystemDocNoTrialId))
|
||||
{
|
||||
await next.Invoke();
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
//如果项目相关接口没有传递trialId 会来到这里,提醒,以便修改
|
||||
|
||||
//---该接口参数中,没有传递项目编号,请核对。
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["TrialResource_MissingProjectNumber"]));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -34,6 +34,8 @@ public class ServiceVerifyConfigOption
|
|||
[Description("修改密码的天数")]
|
||||
public int ChangePassWordDays { get; set; }
|
||||
|
||||
public string ThirdPdfUrl { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class SystemEmailSendConfig
|
||||
|
@ -61,6 +63,10 @@ public class SystemEmailSendConfig
|
|||
public string CompanyShortNameCN { get; set; } = string.Empty;
|
||||
|
||||
public bool IsEnv_US { get; set; }
|
||||
|
||||
public bool IsOpenErrorNoticeEmail { get; set; }
|
||||
|
||||
public List<string> ErrorNoticeEmailList { get; set; } =new List<string>();
|
||||
}
|
||||
|
||||
public class SystemEmailSendConfigView
|
||||
|
|
|
@ -5,7 +5,10 @@ global using IRaCIS.Core.Infra.EFCore;
|
|||
global using IRaCIS.Core.Infrastructure.Extention;
|
||||
global using Microsoft.EntityFrameworkCore;
|
||||
global using System.Linq.Expressions;
|
||||
|
||||
|
||||
global using ZiggyCreatures.Caching.Fusion;
|
||||
global using Microsoft.Extensions.Localization;
|
||||
global using AutoMapper;
|
||||
global using IRaCIS.Core.Domain.Share;
|
||||
global using IRaCIS.Core.Application.BusinessFilter;
|
||||
|
||||
|
||||
|
|
|
@ -27,6 +27,13 @@ public static class CacheKeys
|
|||
//超时没请求接口自动退出
|
||||
public static string UserAutoLoginOut(Guid userId) => $"UserAutoLoginOut:{userId}";
|
||||
|
||||
/// <summary>
|
||||
/// 用户登录错误 限制登录
|
||||
/// </summary>
|
||||
/// <param name="userName"></param>
|
||||
/// <returns></returns>
|
||||
public static string UserLoginError(string userName) => $"login-failures:{userName}";
|
||||
|
||||
/// <summary>
|
||||
/// 跳过阅片
|
||||
/// </summary>
|
||||
|
|
|
@ -42,7 +42,7 @@ public static class SendEmailHelper
|
|||
{
|
||||
|
||||
//---邮件发送失败,您进行的操作未能成功,请检查邮箱或联系维护人员
|
||||
throw new Exception(StaticData.International("SendEmail_SendFail"), new Exception(ex.Message));
|
||||
throw new Exception(I18n.T("SendEmail_SendFail"), new Exception(ex.Message));
|
||||
}
|
||||
|
||||
|
||||
|
@ -82,7 +82,7 @@ public static class SendEmailHelper
|
|||
if (sMTPEmailConfig.ToMailAddressList.Count == 0)
|
||||
{
|
||||
//---没有收件人
|
||||
throw new ArgumentException(StaticData.International("SendEmail_NoRecipient"));
|
||||
throw new ArgumentException(I18n.T("SendEmail_NoRecipient"));
|
||||
}
|
||||
else
|
||||
{
|
|
@ -1,440 +0,0 @@
|
|||
using IRaCIS.Application.Contracts;
|
||||
using IRaCIS.Application.Interfaces;
|
||||
using IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using MiniExcelLibs;
|
||||
using MiniExcelLibs.OpenXml;
|
||||
using NPOI.HSSF.UserModel;
|
||||
using NPOI.SS.Formula.Functions;
|
||||
using NPOI.SS.UserModel;
|
||||
using NPOI.XSSF.UserModel;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
|
||||
namespace IRaCIS.Core.Application.Service;
|
||||
|
||||
public static class ExcelExportHelper
|
||||
{
|
||||
//MiniExcel_Export
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="code"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="exportFileNamePrefix">文件名前缀</param>
|
||||
/// <param name="_commonDocumentRepository"></param>
|
||||
/// <param name="_hostEnvironment"></param>
|
||||
/// <param name="_dictionaryService"></param>
|
||||
/// <param name="translateType"></param>
|
||||
/// <param name="criterionType"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<IActionResult> DataExportAsync(string code, ExcelExportInfo data, string exportFileNamePrefix, IRepository<CommonDocument> _commonDocumentRepository, IWebHostEnvironment _hostEnvironment, IDictionaryService? _dictionaryService = null, Type? translateType = null, CriterionType? criterionType = null)
|
||||
{
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
//判断是否有字典翻译
|
||||
object translateData = data;
|
||||
|
||||
if (_dictionaryService != null && translateType != null)
|
||||
{
|
||||
//一个值 对应不同的字典翻译
|
||||
var needTranslatePropertyList = translateType.GetProperties().Where(t => t.IsDefined(typeof(DictionaryTranslateAttribute), true))
|
||||
.SelectMany(c =>
|
||||
c.GetCustomAttributes(typeof(DictionaryTranslateAttribute), false).Select(f => (DictionaryTranslateAttribute?)f).Where(t => t?.CriterionType == criterionType || t?.CriterionType == null)
|
||||
.Select(k => new { c.Name, k.DicParentCode, k.IsTranslateDenpendOtherProperty, k.DependPropertyName, k.DependPropertyValueStr })
|
||||
).ToList();
|
||||
|
||||
|
||||
|
||||
//字典表查询出所有需要翻译的数据
|
||||
|
||||
var translateDataList = await _dictionaryService.GetBasicDataSelect(needTranslatePropertyList.Select(t => t.DicParentCode).Distinct().ToArray());
|
||||
|
||||
|
||||
var dic = data.ConvertToDictionary();
|
||||
|
||||
|
||||
foreach (var key in dic.Keys)
|
||||
{
|
||||
//是数组 那么找到对应的属性 进行翻译
|
||||
if (dic[key].GetType().GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>)))
|
||||
{
|
||||
|
||||
var newObjList = new List<object>();
|
||||
var no = 1;
|
||||
|
||||
foreach (var item in dic[key] as IList)
|
||||
{
|
||||
var itemDic = item.ConvertToDictionary();
|
||||
|
||||
//处理集合里面时间类型,根据当前语言将时间转变为字符串
|
||||
foreach (var itemValuePair in itemDic)
|
||||
{
|
||||
if (DateTime.TryParse(itemValuePair.Value?.ToString(), out DateTime result))
|
||||
{
|
||||
itemDic[itemValuePair.Key] = ExportExcelConverterDate.DateTimeInternationalToString(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (var needTranslateProperty in needTranslatePropertyList)
|
||||
{
|
||||
//翻译的属性依赖其他属性
|
||||
if (needTranslateProperty.IsTranslateDenpendOtherProperty)
|
||||
{
|
||||
if (itemDic[needTranslateProperty.DependPropertyName]?.ToString().ToLower() == needTranslateProperty.DependPropertyValueStr.ToLower())
|
||||
{
|
||||
var beforeValue = itemDic[needTranslateProperty.Name]?.ToString();
|
||||
|
||||
itemDic[needTranslateProperty.Name] = translateDataList[needTranslateProperty.DicParentCode].Where(t => t.Code.ToLower() == beforeValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
|
||||
}
|
||||
}
|
||||
//普通翻译 或者某一标准翻译
|
||||
else
|
||||
{
|
||||
var beforeValue = itemDic[needTranslateProperty.Name]?.ToString();
|
||||
|
||||
|
||||
itemDic[needTranslateProperty.Name] = translateDataList[needTranslateProperty.DicParentCode].Where(t => t.Code.ToLower() == beforeValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
itemDic.Add("No", no++);
|
||||
|
||||
|
||||
newObjList.Add(itemDic);
|
||||
}
|
||||
|
||||
dic[key] = newObjList;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//data = dic;
|
||||
|
||||
translateData = dic;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var (physicalPath, fileName) = await FileStoreHelper.GetCommonDocPhysicalFilePathAsync(_hostEnvironment, _commonDocumentRepository, code);
|
||||
|
||||
|
||||
//模板路径
|
||||
var tplPath = physicalPath;
|
||||
|
||||
#region 根据中英文 删除模板sheet
|
||||
|
||||
// 打开模板文件
|
||||
var templateFile = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
|
||||
|
||||
// 获取文件流
|
||||
var templateStream = new MemoryStream();
|
||||
templateFile.CopyTo(templateStream);
|
||||
templateStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var workbook = new XSSFWorkbook(templateStream);
|
||||
|
||||
int sheetCount = workbook.NumberOfSheets;
|
||||
|
||||
if (sheetCount == 2)
|
||||
{
|
||||
if (isEn_US)
|
||||
{
|
||||
workbook.RemoveSheetAt(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
workbook.RemoveSheetAt(1);
|
||||
}
|
||||
|
||||
var memoryStream2 = new MemoryStream();
|
||||
workbook.Write(memoryStream2, true);
|
||||
|
||||
memoryStream2.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
templateStream = memoryStream2;
|
||||
}
|
||||
|
||||
// 文件名称 从sheet里面取
|
||||
//fileNmae = workbook.GetSheetName(0);
|
||||
#endregion
|
||||
|
||||
#region MiniExcel
|
||||
|
||||
var memoryStream = new MemoryStream();
|
||||
|
||||
var config = new OpenXmlConfiguration()
|
||||
{
|
||||
IgnoreTemplateParameterMissing = true,
|
||||
};
|
||||
|
||||
await MiniExcel.SaveAsByTemplateAsync(memoryStream, templateStream.ToArray(), translateData, config);
|
||||
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
|
||||
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
{
|
||||
FileDownloadName = $"{(string.IsNullOrEmpty(exportFileNamePrefix) ? "" : exportFileNamePrefix + "_")}{Path.GetFileNameWithoutExtension(fileName)}_{DateTime.Now.ToString("yyyyMMddHHmmss")}.xlsx"
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static async Task<(MemoryStream, string)> DataExport_NpoiTestAsync(string code, ExcelExportInfo data, IRepository<CommonDocument> _commonDocumentRepository, IWebHostEnvironment _hostEnvironment, IDictionaryService? _dictionaryService = null, Type? translateType = null, CriterionType? criterionType = null)
|
||||
{
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
//判断是否有字典翻译
|
||||
|
||||
object translateData = data;
|
||||
|
||||
if (_dictionaryService != null && translateType != null)
|
||||
{
|
||||
|
||||
//一个值 对应不同的字典翻译
|
||||
var needTranslatePropertyList = translateType.GetProperties().Where(t => t.IsDefined(typeof(DictionaryTranslateAttribute), true))
|
||||
.SelectMany(c =>
|
||||
c.GetCustomAttributes(typeof(DictionaryTranslateAttribute), false).Select(f => (DictionaryTranslateAttribute?)f).Where(t => t.CriterionType == criterionType || t.CriterionType == null)
|
||||
.Select(k => new { c.Name, k.DicParentCode, k.IsTranslateDenpendOtherProperty, k.DependPropertyName, k.DependPropertyValueStr })
|
||||
).ToList();
|
||||
|
||||
|
||||
|
||||
//字典表查询出所有需要翻译的数据
|
||||
|
||||
var translateDataList = await _dictionaryService.GetBasicDataSelect(needTranslatePropertyList.Select(t => t.DicParentCode).Distinct().ToArray());
|
||||
|
||||
var dic = data.ConvertToDictionary();
|
||||
//var dic = (JsonConvert.DeserializeObject<IDictionary<string, object>>(data.ToJsonNotIgnoreNull())).IfNullThrowException();
|
||||
|
||||
foreach (var key in dic.Keys)
|
||||
{
|
||||
//是数组 那么找到对应的属性 进行翻译
|
||||
if (dic[key].GetType().GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>)))
|
||||
//if (dic[key].GetType().IsAssignableFrom(typeof(JArray)))
|
||||
{
|
||||
|
||||
var newObjList = new List<object>();
|
||||
var no = 1;
|
||||
foreach (var item in dic[key] as IList)
|
||||
//foreach (var item in dic[key] as JArray)
|
||||
{
|
||||
//var itemDic = JsonConvert.DeserializeObject<IDictionary<string, object>>(item.ToJsonNotIgnoreNull());
|
||||
var itemDic = item.ConvertToDictionary();
|
||||
|
||||
//处理集合里面时间类型,根据当前语言将时间转变为字符串
|
||||
foreach (var itemValuePair in itemDic)
|
||||
{
|
||||
if (DateTime.TryParse(itemValuePair.Value?.ToString(), out DateTime result))
|
||||
{
|
||||
itemDic[itemValuePair.Key] = ExportExcelConverterDate.DateTimeInternationalToString(result);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var needTranslateProperty in needTranslatePropertyList)
|
||||
{
|
||||
//翻译的属性依赖其他属性
|
||||
if (needTranslateProperty.IsTranslateDenpendOtherProperty)
|
||||
{
|
||||
if (itemDic[needTranslateProperty.DependPropertyName]?.ToString().ToLower() == needTranslateProperty.DependPropertyValueStr.ToLower())
|
||||
{
|
||||
var beforeValue = itemDic[needTranslateProperty.Name]?.ToString();
|
||||
|
||||
itemDic[needTranslateProperty.Name] = translateDataList[needTranslateProperty.DicParentCode].Where(t => t.Code.ToLower() == beforeValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
|
||||
}
|
||||
}
|
||||
//普通翻译 或者某一标准翻译
|
||||
else
|
||||
{
|
||||
var beforeValue = itemDic[needTranslateProperty.Name]?.ToString();
|
||||
|
||||
|
||||
itemDic[needTranslateProperty.Name] = translateDataList[needTranslateProperty.DicParentCode].Where(t => t.Code.ToLower() == beforeValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
itemDic.Add("No", no++);
|
||||
newObjList.Add(itemDic);
|
||||
}
|
||||
|
||||
dic[key] = newObjList;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//data = dic;
|
||||
translateData = dic;
|
||||
|
||||
}
|
||||
|
||||
|
||||
var (physicalPath, fileName) = await FileStoreHelper.GetCommonDocPhysicalFilePathAsync(_hostEnvironment, _commonDocumentRepository, code);
|
||||
|
||||
|
||||
//模板路径
|
||||
var tplPath = physicalPath;
|
||||
|
||||
#region 根据中英文 删除模板sheet
|
||||
|
||||
// 打开模板文件
|
||||
var templateFile = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
|
||||
|
||||
// 获取文件流
|
||||
var templateStream = new MemoryStream();
|
||||
templateFile.CopyTo(templateStream);
|
||||
templateStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var workbook = new XSSFWorkbook(templateStream);
|
||||
|
||||
int sheetCount = workbook.NumberOfSheets;
|
||||
|
||||
if (sheetCount == 2)
|
||||
{
|
||||
if (isEn_US)
|
||||
{
|
||||
workbook.RemoveSheetAt(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
workbook.RemoveSheetAt(1);
|
||||
}
|
||||
|
||||
var memoryStream2 = new MemoryStream();
|
||||
workbook.Write(memoryStream2, true);
|
||||
|
||||
memoryStream2.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
templateStream = memoryStream2;
|
||||
}
|
||||
|
||||
// 文件名称 从sheet里面取
|
||||
//fileName = workbook.GetSheetName(0);
|
||||
#endregion
|
||||
|
||||
#region MiniExcel
|
||||
|
||||
var memoryStream = new MemoryStream();
|
||||
|
||||
var config = new OpenXmlConfiguration()
|
||||
{
|
||||
IgnoreTemplateParameterMissing = true,
|
||||
};
|
||||
|
||||
await MiniExcel.SaveAsByTemplateAsync(memoryStream, templateStream.ToArray(), translateData, config);
|
||||
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
|
||||
return (memoryStream, fileName);
|
||||
|
||||
//var wb = new HSSFWorkbook(memoryStream);
|
||||
|
||||
//var sheet = wb.GetSheetAt(0);
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 导出文件模板
|
||||
/// </summary>
|
||||
/// <param name="inDto"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<FileResult> ExportTemplateAsync(ExportTemplateServiceDto inDto)
|
||||
{
|
||||
var (physicalPath, fileName) = await FileStoreHelper.GetCommonDocPhysicalFilePathAsync(inDto.hostEnvironment, inDto.commonDocumentRepository, inDto.TemplateCode);
|
||||
|
||||
|
||||
//模板路径
|
||||
var tplPath = physicalPath;
|
||||
|
||||
#region 根据中英文 删除模板sheet
|
||||
|
||||
// 打开模板文件
|
||||
var templateFile = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
|
||||
|
||||
// 获取文件流
|
||||
var templateStream = new MemoryStream();
|
||||
templateFile.CopyTo(templateStream);
|
||||
templateStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var workbook = new XSSFWorkbook(templateStream);
|
||||
|
||||
int sheetCount = workbook.NumberOfSheets;
|
||||
|
||||
if (sheetCount == 2)
|
||||
{
|
||||
if (inDto.IsEnglish)
|
||||
{
|
||||
workbook.RemoveSheetAt(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
workbook.RemoveSheetAt(1);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
ISheet sheet = workbook.GetSheetAt(0); // 获取第一个工作表
|
||||
|
||||
|
||||
|
||||
for (int i = 0; i < sheet.LastRowNum + 1; i++)
|
||||
{
|
||||
IRow row = sheet.GetRow(i);
|
||||
for (int j = 0; j < sheet.LastRowNum; j++)
|
||||
{
|
||||
ICell cell = row.GetCell(j);
|
||||
foreach (var property in inDto.Data.GetType().GetProperties())
|
||||
{
|
||||
var value = property.GetValue(inDto.Data);
|
||||
if (cell!=null&&cell.ToString() == "{{" + property.Name + "}}")
|
||||
{
|
||||
sheet.GetRow(i).GetCell(j).SetCellValue(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
var memoryStream2 = new MemoryStream();
|
||||
workbook.Write(memoryStream2, true);
|
||||
|
||||
memoryStream2.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
templateStream = memoryStream2;
|
||||
|
||||
|
||||
return new FileStreamResult(templateStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
{
|
||||
FileDownloadName = $"{(string.IsNullOrEmpty(inDto.ExportFileName) ? "" : inDto.ExportFileName + "_")}{Path.GetFileNameWithoutExtension(fileName)}_{DateTime.Now.ToString("yyyyMMddHHmmss")}.xlsx"
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
using System.Diagnostics;
|
||||
|
||||
namespace IRaCIS.Core.Application.Helper;
|
||||
|
||||
public class FileConvertHelper
|
||||
{
|
||||
|
||||
|
||||
static public void ConvertWordToPdf(string inputWordFilePath, string outputPdfDir)
|
||||
{
|
||||
// 设置 libreoffice 命令行参数
|
||||
string arguments = $"--headless --invisible --convert-to pdf \"{inputWordFilePath}\" --outdir \"{outputPdfDir}\"";
|
||||
|
||||
// 启动 libreoffice 进程
|
||||
using (Process process = new Process())
|
||||
{
|
||||
process.StartInfo.FileName = "libreoffice";
|
||||
process.StartInfo.Arguments = arguments;
|
||||
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
|
||||
process.Start();
|
||||
|
||||
// 等待进程结束
|
||||
process.WaitForExit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,719 @@
|
|||
using DocumentFormat.OpenXml.Spreadsheet;
|
||||
using DocumentFormat.OpenXml.Wordprocessing;
|
||||
using IRaCIS.Application.Contracts;
|
||||
using IRaCIS.Application.Interfaces;
|
||||
using IRaCIS.Core.API._ServiceExtensions.NewtonsoftJson;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using MiniExcelLibs;
|
||||
using MiniExcelLibs.OpenXml;
|
||||
using NPOI.HSSF.UserModel;
|
||||
using NPOI.SS.Formula.Functions;
|
||||
using NPOI.SS.UserModel;
|
||||
using NPOI.XSSF.UserModel;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
using Xceed.Document.NET;
|
||||
|
||||
namespace IRaCIS.Core.Application.Service;
|
||||
|
||||
public static class ExcelExportHelper
|
||||
{
|
||||
//MiniExcel_Export
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="code"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="exportFileNamePrefix">文件名前缀</param>
|
||||
/// <param name="_commonDocumentRepository"></param>
|
||||
/// <param name="_hostEnvironment"></param>
|
||||
/// <param name="_dictionaryService"></param>
|
||||
/// <param name="translateType"></param>
|
||||
/// <param name="criterionType"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<IActionResult> DataExportAsync(string code, ExcelExportInfo data, string exportFileNamePrefix, IRepository<CommonDocument> _commonDocumentRepository, IWebHostEnvironment _hostEnvironment, IDictionaryService? _dictionaryService = null, Type? translateType = null, CriterionType? criterionType = null)
|
||||
{
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
|
||||
//判断是否有字典翻译
|
||||
object translateData = data;
|
||||
|
||||
if (_dictionaryService != null && translateType != null)
|
||||
{
|
||||
//一个值 对应不同的字典翻译
|
||||
var needTranslatePropertyList = translateType.GetProperties().Where(t => t.IsDefined(typeof(DictionaryTranslateAttribute), true))
|
||||
.SelectMany(c =>
|
||||
c.GetCustomAttributes(typeof(DictionaryTranslateAttribute), false).Select(f => (DictionaryTranslateAttribute?)f).Where(t => t?.CriterionType == criterionType || t?.CriterionType == null)
|
||||
.Select(k => new { c.Name, k.DicParentCode, k.IsTranslateDenpendOtherProperty, k.DependPropertyName, k.DependPropertyValueStr })
|
||||
).ToList();
|
||||
|
||||
|
||||
|
||||
//字典表查询出所有需要翻译的数据
|
||||
|
||||
var translateDataList = await _dictionaryService.GetBasicDataSelect(needTranslatePropertyList.Select(t => t.DicParentCode).Distinct().ToArray());
|
||||
|
||||
|
||||
var dic = data.ConvertToDictionary();
|
||||
|
||||
|
||||
foreach (var key in dic.Keys)
|
||||
{
|
||||
//是数组 那么找到对应的属性 进行翻译
|
||||
if (dic[key].GetType().GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>)))
|
||||
{
|
||||
|
||||
var newObjList = new List<object>();
|
||||
var no = 1;
|
||||
|
||||
foreach (var item in dic[key] as IList)
|
||||
{
|
||||
var itemDic = item.ConvertToDictionary();
|
||||
|
||||
////处理集合里面时间类型,根据当前语言将时间转变为字符串
|
||||
//foreach (var itemValuePair in itemDic)
|
||||
//{
|
||||
// // 临床数据 1,1 会变成2024-01-01
|
||||
// if (itemValuePair.Value?.ToString().Length > 8 && DateTime.TryParse(itemValuePair.Value?.ToString(), out DateTime result))
|
||||
// {
|
||||
// itemDic[itemValuePair.Key] = ExportExcelConverterDate.DateTimeInternationalToString(result);
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
foreach (var needTranslateProperty in needTranslatePropertyList)
|
||||
{
|
||||
if (itemDic.Keys.Any(t => t == needTranslateProperty.Name))
|
||||
{
|
||||
//翻译的属性依赖其他属性
|
||||
if (needTranslateProperty.IsTranslateDenpendOtherProperty)
|
||||
{
|
||||
if (itemDic[needTranslateProperty.DependPropertyName]?.ToString().ToLower() == needTranslateProperty.DependPropertyValueStr.ToLower())
|
||||
{
|
||||
var beforeValue = itemDic[needTranslateProperty.Name]?.ToString();
|
||||
|
||||
itemDic[needTranslateProperty.Name] = translateDataList[needTranslateProperty.DicParentCode].Where(t => t.Code.ToLower() == beforeValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
|
||||
}
|
||||
}
|
||||
//普通翻译 或者某一标准翻译
|
||||
else
|
||||
{
|
||||
var beforeValue = itemDic[needTranslateProperty.Name]?.ToString();
|
||||
|
||||
|
||||
itemDic[needTranslateProperty.Name] = translateDataList[needTranslateProperty.DicParentCode].Where(t => t.Code.ToLower() == beforeValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
itemDic.Add("No", no++);
|
||||
|
||||
|
||||
newObjList.Add(itemDic);
|
||||
}
|
||||
|
||||
dic[key] = newObjList;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//data = dic;
|
||||
|
||||
translateData = dic;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var (physicalPath, fileName) = await FileStoreHelper.GetCommonDocPhysicalFilePathAsync(_hostEnvironment, _commonDocumentRepository, code);
|
||||
|
||||
|
||||
//模板路径
|
||||
var tplPath = physicalPath;
|
||||
|
||||
#region 根据中英文 删除模板sheet
|
||||
|
||||
// 打开模板文件
|
||||
var templateFile = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
|
||||
|
||||
// 获取文件流
|
||||
var templateStream = new MemoryStream();
|
||||
templateFile.CopyTo(templateStream);
|
||||
templateStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var workbook = new XSSFWorkbook(templateStream);
|
||||
|
||||
int sheetCount = workbook.NumberOfSheets;
|
||||
|
||||
if (sheetCount == 2)
|
||||
{
|
||||
if (isEn_US)
|
||||
{
|
||||
workbook.RemoveSheetAt(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
workbook.RemoveSheetAt(1);
|
||||
}
|
||||
|
||||
//中文替换项目术语
|
||||
if (data.TrialObjectNameList.Count > 0)
|
||||
{
|
||||
var replaceObjectList = data.TrialObjectNameList;
|
||||
|
||||
var sheet = workbook.GetSheetAt(0);
|
||||
|
||||
int rowCount = sheet.PhysicalNumberOfRows;
|
||||
|
||||
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)
|
||||
{
|
||||
var row = sheet.GetRow(rowIndex);
|
||||
|
||||
if (row != null)
|
||||
{
|
||||
|
||||
var colums = row.LastCellNum;
|
||||
|
||||
for (int colIndex = 0; colIndex < colums; colIndex++)
|
||||
{
|
||||
var cell = row.GetCell(colIndex);
|
||||
|
||||
// 只处理字符串类型的单元格
|
||||
if (cell != null)
|
||||
{
|
||||
var cellValue = cell.StringCellValue;
|
||||
|
||||
var find = replaceObjectList.FirstOrDefault(t => t.Name == cellValue);
|
||||
if (find != null)
|
||||
{
|
||||
cell.SetCellValue(find.TrialName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
using (var memoryStream2 = new MemoryStream())
|
||||
{
|
||||
workbook.Write(memoryStream2, true);
|
||||
|
||||
memoryStream2.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
templateStream = memoryStream2;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 文件名称 从sheet里面取
|
||||
//fileNmae = workbook.GetSheetName(0);
|
||||
#endregion
|
||||
|
||||
#region MiniExcel
|
||||
|
||||
var memoryStream = new MemoryStream();
|
||||
|
||||
var config = new OpenXmlConfiguration()
|
||||
{
|
||||
IgnoreTemplateParameterMissing = true,
|
||||
};
|
||||
|
||||
await MiniExcel.SaveAsByTemplateAsync(memoryStream, templateStream.ToArray(), translateData, config);
|
||||
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
|
||||
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
{
|
||||
FileDownloadName = $"{(string.IsNullOrEmpty(exportFileNamePrefix) ? "" : exportFileNamePrefix + "_")}{Path.GetFileNameWithoutExtension(fileName)}_{DateTime.Now.ToString("yyyyMMddHHmmss")}.xlsx"
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
public class DynamicColumnConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 增加动态列开始索引 从0 开始算
|
||||
/// </summary>
|
||||
public int AutoColumnStartIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 动态列开始的行index 从0开始
|
||||
/// </summary>
|
||||
public int AutoColumnTitleRowIndex { get; set; }
|
||||
|
||||
|
||||
public int TempalteLastColumnIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 动态的列名
|
||||
/// </summary>
|
||||
public List<string> ColumnNameList { get; set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 动态翻译的字典名
|
||||
/// </summary>
|
||||
public List<string> TranslateDicNameList { get; set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 动态取数据的集合名
|
||||
/// </summary>
|
||||
public string DynamicListName { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 动态数据翻译的取字典的属性名
|
||||
/// </summary>
|
||||
public string DynamicItemDicName { get; set; }
|
||||
/// <summary>
|
||||
/// 取值的属性名
|
||||
/// </summary>
|
||||
public string DynamicItemValueName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Excel Title Name
|
||||
/// </summary>
|
||||
public string DynamicItemTitleName { get; set; }
|
||||
|
||||
public List<int> RemoveColunmIndexList { get; set; } = new List<int>();
|
||||
}
|
||||
|
||||
|
||||
public static async Task<(MemoryStream, string)> DataExport_NpoiTestAsync(string code, ExcelExportInfo data, IRepository<CommonDocument> _commonDocumentRepository, IWebHostEnvironment _hostEnvironment, IDictionaryService? _dictionaryService = null, Type? translateType = null, CriterionType? criterionType = null, DynamicColumnConfig? dynamicColumnConfig = null)
|
||||
{
|
||||
var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US;
|
||||
//判断是否有字典翻译
|
||||
|
||||
object translateData = data;
|
||||
|
||||
Dictionary<string, object> translatedDic = default;
|
||||
|
||||
if (_dictionaryService != null && translateType != null)
|
||||
{
|
||||
|
||||
//一个值 对应不同的字典翻译
|
||||
var needTranslatePropertyList = translateType.GetProperties().Where(t => t.IsDefined(typeof(DictionaryTranslateAttribute), true))
|
||||
.SelectMany(c =>
|
||||
c.GetCustomAttributes(typeof(DictionaryTranslateAttribute), false).Select(f => (DictionaryTranslateAttribute?)f).Where(t => t.CriterionType == criterionType || t.CriterionType == null)
|
||||
.Select(k => new { c.Name, k.DicParentCode, k.IsTranslateDenpendOtherProperty, k.DependPropertyName, k.DependPropertyValueStr })
|
||||
).ToList();
|
||||
|
||||
|
||||
|
||||
//字典表查询出所有需要翻译的数据
|
||||
|
||||
var translateDataList = await _dictionaryService.GetBasicDataSelect(needTranslatePropertyList.Select(t => t.DicParentCode).Distinct().ToArray());
|
||||
|
||||
var dic = data.ConvertToDictionary();
|
||||
//var dic = (JsonConvert.DeserializeObject<IDictionary<string, object>>(data.ToJsonNotIgnoreNull())).IfNullThrowException();
|
||||
|
||||
foreach (var key in dic.Keys)
|
||||
{
|
||||
//是数组 那么找到对应的属性 进行翻译
|
||||
if (dic[key] != null && dic[key].GetType().GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>)))
|
||||
//if (dic[key].GetType().IsAssignableFrom(typeof(JArray)))
|
||||
{
|
||||
|
||||
var newObjList = new List<object>();
|
||||
var no = 1;
|
||||
foreach (var item in dic[key] as IList)
|
||||
//foreach (var item in dic[key] as JArray)
|
||||
{
|
||||
//var itemDic = JsonConvert.DeserializeObject<IDictionary<string, object>>(item.ToJsonNotIgnoreNull());
|
||||
var itemDic = item.ConvertToDictionary();
|
||||
|
||||
////处理集合里面时间类型,根据当前语言将时间转变为字符串
|
||||
//foreach (var itemValuePair in itemDic)
|
||||
//{
|
||||
// if (itemValuePair.Value?.ToString().Length > 8 && DateTime.TryParse(itemValuePair.Value?.ToString(), out DateTime result))
|
||||
// {
|
||||
// itemDic[itemValuePair.Key] = ExportExcelConverterDate.DateTimeInternationalToString(result);
|
||||
// }
|
||||
//}
|
||||
|
||||
foreach (var needTranslateProperty in needTranslatePropertyList)
|
||||
{
|
||||
if (itemDic.Keys.Any(t => t == needTranslateProperty.Name))
|
||||
{
|
||||
//翻译的属性依赖其他属性
|
||||
if (needTranslateProperty.IsTranslateDenpendOtherProperty)
|
||||
{
|
||||
if (itemDic[needTranslateProperty.DependPropertyName]?.ToString().ToLower() == needTranslateProperty.DependPropertyValueStr.ToLower())
|
||||
{
|
||||
var beforeValue = itemDic[needTranslateProperty.Name]?.ToString();
|
||||
|
||||
itemDic[needTranslateProperty.Name] = translateDataList[needTranslateProperty.DicParentCode].Where(t => t.Code.ToLower() == beforeValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
|
||||
}
|
||||
}
|
||||
//普通翻译 或者某一标准翻译
|
||||
else
|
||||
{
|
||||
var beforeValue = itemDic[needTranslateProperty.Name]?.ToString();
|
||||
|
||||
|
||||
itemDic[needTranslateProperty.Name] = translateDataList[needTranslateProperty.DicParentCode].Where(t => t.Code.ToLower() == beforeValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
itemDic.Add("No", no++);
|
||||
newObjList.Add(itemDic);
|
||||
}
|
||||
|
||||
dic[key] = newObjList;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//data = dic;
|
||||
translateData = dic;
|
||||
translatedDic = dic;
|
||||
|
||||
}
|
||||
|
||||
|
||||
var (physicalPath, fileName) = await FileStoreHelper.GetCommonDocPhysicalFilePathAsync(_hostEnvironment, _commonDocumentRepository, code);
|
||||
|
||||
|
||||
//模板路径
|
||||
var tplPath = physicalPath;
|
||||
|
||||
#region 根据中英文 删除模板sheet
|
||||
|
||||
// 打开模板文件
|
||||
var templateFile = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
|
||||
|
||||
// 获取文件流
|
||||
var templateStream = new MemoryStream();
|
||||
templateFile.CopyTo(templateStream);
|
||||
templateStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var workbook = new XSSFWorkbook(templateStream);
|
||||
|
||||
int sheetCount = workbook.NumberOfSheets;
|
||||
|
||||
if (sheetCount == 2)
|
||||
{
|
||||
if (isEn_US)
|
||||
{
|
||||
workbook.RemoveSheetAt(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
workbook.RemoveSheetAt(1);
|
||||
}
|
||||
|
||||
//中文替换项目术语
|
||||
if (data.TrialObjectNameList?.Count > 0)
|
||||
{
|
||||
var replaceObjectList = data.TrialObjectNameList;
|
||||
|
||||
var sheet = workbook.GetSheetAt(0);
|
||||
|
||||
int rowCount = sheet.PhysicalNumberOfRows;
|
||||
|
||||
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)
|
||||
{
|
||||
var row = sheet.GetRow(rowIndex);
|
||||
if (row != null)
|
||||
{
|
||||
|
||||
var colums = row.LastCellNum;
|
||||
|
||||
for (int colIndex = 0; colIndex < colums; colIndex++)
|
||||
{
|
||||
var cell = row.GetCell(colIndex);
|
||||
|
||||
// 只处理字符串类型的单元格
|
||||
if (cell != null)
|
||||
{
|
||||
var cellValue = cell.StringCellValue;
|
||||
|
||||
var find = replaceObjectList.FirstOrDefault(t => t.Name == cellValue);
|
||||
if (find != null)
|
||||
{
|
||||
cell.SetCellValue(find.TrialName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dynamicColumnConfig != null)
|
||||
{
|
||||
var sheet = workbook.GetSheetAt(0);
|
||||
|
||||
var titelRow = sheet.GetRow(dynamicColumnConfig.AutoColumnTitleRowIndex);
|
||||
var templateRow = sheet.GetRow(dynamicColumnConfig.AutoColumnTitleRowIndex + 1);
|
||||
|
||||
//动态移除列的数量
|
||||
var dynamicRemoveColunmCount = dynamicColumnConfig.RemoveColunmIndexList.Count();
|
||||
|
||||
var beforeDynamicRemoveCount = dynamicColumnConfig.RemoveColunmIndexList.Where(t => t < dynamicColumnConfig.AutoColumnStartIndex).Count();
|
||||
|
||||
//动态添加列的数量
|
||||
var needAddCount = dynamicColumnConfig.ColumnNameList.Count;
|
||||
|
||||
//原始表 最终索引
|
||||
var originTotalEndIndex = dynamicColumnConfig.TempalteLastColumnIndex;
|
||||
|
||||
//减去动态移除后原始结束索引
|
||||
var originRemoveEndIndex = originTotalEndIndex - dynamicRemoveColunmCount;
|
||||
|
||||
//最终表 动态列开始索引
|
||||
var dynamicColunmStartIndex = dynamicColumnConfig.AutoColumnStartIndex - beforeDynamicRemoveCount;
|
||||
|
||||
//最终表 动态列的终止索引
|
||||
var dynamicColunmEndIndex = dynamicColunmStartIndex + needAddCount - 1;
|
||||
|
||||
//最终表 最终索引
|
||||
var totalColunmEndIndex = originTotalEndIndex + needAddCount - dynamicRemoveColunmCount;
|
||||
|
||||
|
||||
//动态列后需要移动的数量
|
||||
var backMoveCount = totalColunmEndIndex - dynamicColunmEndIndex;
|
||||
|
||||
//删除需要动态删除的列 从大到小移除,否则索引会变
|
||||
foreach (var removeIndex in dynamicColumnConfig.RemoveColunmIndexList.OrderByDescending(t => t))
|
||||
{
|
||||
//将后面的列向前移动
|
||||
for (var i = 0; i < originTotalEndIndex - removeIndex; i++)
|
||||
{
|
||||
Console.WriteLine(titelRow.GetCell(removeIndex + i + 1).StringCellValue);
|
||||
titelRow.GetCell(removeIndex + i).SetCellValue(titelRow.GetCell(removeIndex + i + 1).StringCellValue);
|
||||
templateRow.GetCell(removeIndex + i).SetCellValue(templateRow.GetCell(removeIndex + i + 1).StringCellValue);
|
||||
|
||||
//后面的数据要清空
|
||||
titelRow.GetCell(removeIndex + i + 1).SetCellValue("");
|
||||
templateRow.GetCell(removeIndex + i + 1).SetCellValue("");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//创建新的列
|
||||
for (int i = originTotalEndIndex; i < originTotalEndIndex + needAddCount; i++)
|
||||
{
|
||||
titelRow.CreateCell(i + 1);
|
||||
templateRow.CreateCell(i + 1);
|
||||
}
|
||||
//移动Title 和下面的模板标识
|
||||
|
||||
var gap = totalColunmEndIndex - originRemoveEndIndex;
|
||||
|
||||
for (int i = totalColunmEndIndex; i > dynamicColunmEndIndex; i--)
|
||||
{
|
||||
|
||||
titelRow.GetCell(i).SetCellValue(titelRow.GetCell(i - gap).StringCellValue);
|
||||
|
||||
templateRow.GetCell(i).SetCellValue(templateRow.GetCell(i - gap).StringCellValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//设置动态Tilte
|
||||
|
||||
for (int i = dynamicColunmStartIndex; i < dynamicColunmStartIndex + needAddCount; i++)
|
||||
{
|
||||
var name = dynamicColumnConfig.ColumnNameList[i - dynamicColunmStartIndex];
|
||||
|
||||
titelRow.GetCell(i).SetCellValue(name);
|
||||
templateRow.GetCell(i).SetCellValue("");
|
||||
}
|
||||
}
|
||||
|
||||
using (var memoryStream2 = new MemoryStream())
|
||||
{
|
||||
workbook.Write(memoryStream2, true);
|
||||
|
||||
memoryStream2.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
templateStream = memoryStream2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MiniExcel
|
||||
|
||||
var memoryStream = new MemoryStream();
|
||||
|
||||
var config = new OpenXmlConfiguration()
|
||||
{
|
||||
IgnoreTemplateParameterMissing = true,
|
||||
};
|
||||
|
||||
await MiniExcel.SaveAsByTemplateAsync(memoryStream, templateStream.ToArray(), translateData, config);
|
||||
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
if (dynamicColumnConfig != null)
|
||||
{
|
||||
var dynamicTranslateDataList = await _dictionaryService.GetBasicDataSelect(dynamicColumnConfig.TranslateDicNameList.ToArray());
|
||||
|
||||
// 使用NPOI 进行二次处理
|
||||
var wb = new XSSFWorkbook(memoryStream);
|
||||
var sheet = wb.GetSheetAt(0);
|
||||
|
||||
var list = translatedDic["List"] as IList;
|
||||
|
||||
foreach (var itemResult in list)
|
||||
{
|
||||
var index = list.IndexOf(itemResult);
|
||||
|
||||
//从第四行开始处理动态列
|
||||
var row = sheet.GetRow(index + dynamicColumnConfig.AutoColumnTitleRowIndex + 1);
|
||||
|
||||
var itemDic = itemResult.ToDictionary();
|
||||
|
||||
var itemList = itemDic[dynamicColumnConfig.DynamicListName] as IList;
|
||||
|
||||
//这个数组是动态的,有的多,有的少,所以在此对比Title 一致才赋值
|
||||
foreach (var itemObj in itemList)
|
||||
{
|
||||
|
||||
var iteObjDic = itemObj.ToDictionary();
|
||||
|
||||
var itemDicName = iteObjDic[dynamicColumnConfig.DynamicItemDicName]?.ToString();
|
||||
var itemValue = iteObjDic[dynamicColumnConfig.DynamicItemValueName]?.ToString();
|
||||
|
||||
//var writeIndex = itemList.IndexOf(itemObj) + dynamicColumnConfig.AutoColumnStartIndex;
|
||||
|
||||
var writeIndex = dynamicColumnConfig.ColumnNameList.IndexOf(iteObjDic[dynamicColumnConfig.DynamicItemTitleName].ToString()) + dynamicColumnConfig.AutoColumnStartIndex;
|
||||
|
||||
if (itemDicName.IsNotNullOrEmpty())
|
||||
{
|
||||
|
||||
var translatedItemData = dynamicTranslateDataList[itemDicName].Where(t => t.Code.ToLower() == itemValue?.ToLower()).Select(t => isEn_US ? t.Value : t.ValueCN).FirstOrDefault() ?? String.Empty;
|
||||
|
||||
row.GetCell(writeIndex).SetCellValue(translatedItemData);
|
||||
}
|
||||
else
|
||||
{
|
||||
row.GetCell(writeIndex).SetCellValue(itemValue);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var memoryStream2 = new MemoryStream();
|
||||
wb.Write(memoryStream2, true);
|
||||
memoryStream2.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
memoryStream = memoryStream2;
|
||||
|
||||
}
|
||||
|
||||
|
||||
return (memoryStream, fileName);
|
||||
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 导出文件模板
|
||||
/// </summary>
|
||||
/// <param name="inDto"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<FileResult> ExportTemplateAsync(ExportTemplateServiceDto inDto)
|
||||
{
|
||||
var (physicalPath, fileName) = await FileStoreHelper.GetCommonDocPhysicalFilePathAsync(inDto.hostEnvironment, inDto.commonDocumentRepository, inDto.TemplateCode);
|
||||
|
||||
|
||||
//模板路径
|
||||
var tplPath = physicalPath;
|
||||
|
||||
#region 根据中英文 删除模板sheet
|
||||
|
||||
// 打开模板文件
|
||||
var templateFile = new FileStream(tplPath, FileMode.Open, FileAccess.Read);
|
||||
|
||||
// 获取文件流
|
||||
var templateStream = new MemoryStream();
|
||||
templateFile.CopyTo(templateStream);
|
||||
templateStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var workbook = new XSSFWorkbook(templateStream);
|
||||
|
||||
int sheetCount = workbook.NumberOfSheets;
|
||||
|
||||
if (sheetCount == 2)
|
||||
{
|
||||
if (inDto.IsEnglish)
|
||||
{
|
||||
workbook.RemoveSheetAt(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
workbook.RemoveSheetAt(1);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
ISheet sheet = workbook.GetSheetAt(0); // 获取第一个工作表
|
||||
|
||||
|
||||
|
||||
for (int i = 0; i < sheet.LastRowNum + 1; i++)
|
||||
{
|
||||
IRow row = sheet.GetRow(i);
|
||||
for (int j = 0; j < sheet.LastRowNum; j++)
|
||||
{
|
||||
ICell cell = row.GetCell(j);
|
||||
foreach (var property in inDto.Data.GetType().GetProperties())
|
||||
{
|
||||
var value = property.GetValue(inDto.Data);
|
||||
if (cell != null && cell.ToString() == "{{" + property.Name + "}}")
|
||||
{
|
||||
sheet.GetRow(i).GetCell(j).SetCellValue(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
var memoryStream2 = new MemoryStream();
|
||||
workbook.Write(memoryStream2, true);
|
||||
|
||||
memoryStream2.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
templateStream = memoryStream2;
|
||||
|
||||
|
||||
return new FileStreamResult(templateStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
{
|
||||
FileDownloadName = $"{(string.IsNullOrEmpty(inDto.ExportFileName) ? "" : inDto.ExportFileName + "_")}{Path.GetFileNameWithoutExtension(fileName)}_{DateTime.Now.ToString("yyyyMMddHHmmss")}.xlsx"
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using RestSharp;
|
||||
using SharpCompress.Common;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace IRaCIS.Core.Application.Helper;
|
||||
|
||||
public class FileConvertHelper
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 镜像里面打入libreoffice 的方案
|
||||
/// </summary>
|
||||
/// <param name="inputWordFilePath"></param>
|
||||
/// <param name="outputPdfDir"></param>
|
||||
static public void ConvertWordToPdf(string inputWordFilePath, string outputPdfDir)
|
||||
{
|
||||
// 设置 libreoffice 命令行参数
|
||||
string arguments = $"--headless --invisible --convert-to pdf \"{inputWordFilePath}\" --outdir \"{outputPdfDir}\"";
|
||||
|
||||
// 启动 libreoffice 进程
|
||||
using (Process process = new Process())
|
||||
{
|
||||
process.StartInfo.FileName = "libreoffice";
|
||||
process.StartInfo.Arguments = arguments;
|
||||
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
|
||||
process.Start();
|
||||
|
||||
// 等待进程结束
|
||||
process.WaitForExit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 返回文件字节数组
|
||||
/// File.WriteAllBytes(outputFilePath, response.RawBytes); 直接将字节数组写入到本地某个路径
|
||||
/// var stream = new MemoryStream(response.RawBytes); 直接变为内存流,给其他程序用也可以
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
static public async Task<byte[]> ThirdStirling_PDFWordToPdfAsync(string filePath)
|
||||
{
|
||||
|
||||
var apiUrl = GetApiUrl();
|
||||
|
||||
var client = new RestClient(apiUrl);
|
||||
|
||||
var request = new RestRequest(apiUrl, Method.Post);
|
||||
|
||||
request.AddHeader("accept", "*/*");
|
||||
|
||||
// 直接上传文件,无需生成字节数组
|
||||
request.AddFile("fileInput", filePath);
|
||||
|
||||
return await GetPDFByteArrayAsync(client, request);
|
||||
}
|
||||
|
||||
static public async Task<byte[]> ThirdStirling_PDFWordToPdfAsync(byte[] fileBytes, string fileName)
|
||||
{
|
||||
|
||||
var apiUrl = GetApiUrl();
|
||||
var client = new RestClient(apiUrl);
|
||||
|
||||
var request = new RestRequest(apiUrl, Method.Post);
|
||||
|
||||
request.AddHeader("accept", "*/*");
|
||||
|
||||
// 添加文件流到请求
|
||||
request.AddFile("fileInput", fileBytes, fileName);
|
||||
|
||||
return await GetPDFByteArrayAsync(client, request);
|
||||
}
|
||||
|
||||
static private async Task<byte[]> GetPDFByteArrayAsync(RestClient client, RestRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
|
||||
// 发送请求并获取响应
|
||||
var response = await client.ExecuteAsync(request);
|
||||
|
||||
// 检查请求是否成功
|
||||
if (response.IsSuccessful)
|
||||
{
|
||||
return response.RawBytes ?? new byte[] { };
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"上传失败,错误代码: {response.StatusCode}, 错误消息: {response.ErrorMessage}");
|
||||
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("发生异常: " + ex.Message);
|
||||
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
}
|
||||
|
||||
static private string GetApiUrl()
|
||||
{
|
||||
|
||||
var enviromentName = Environment.GetEnvironmentVariables()["ASPNETCORE_ENVIRONMENT"]?.ToString();
|
||||
|
||||
var configuration = new ConfigurationBuilder().AddJsonFile($"appsettings.{enviromentName}.json", false, false).Build();
|
||||
|
||||
// 手动绑定配置
|
||||
var appSettings = new ServiceVerifyConfigOption();
|
||||
configuration.GetSection("BasicSystemConfig").Bind(appSettings);
|
||||
|
||||
return appSettings.ThirdPdfUrl;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -135,7 +135,7 @@ public static class FileStoreHelper
|
|||
if (doc == null)
|
||||
{
|
||||
//---数据库没有找到对应的数据模板文件,请联系系统运维人员。
|
||||
throw new BusinessValidationFailedException(StaticData.International("FileStore_TemplateFileNotFound"));
|
||||
throw new BusinessValidationFailedException(I18n.T("FileStore_TemplateFileNotFound"));
|
||||
}
|
||||
|
||||
var filePath = FileStoreHelper.GetPhysicalFilePath(_hostEnvironment, doc.Path);
|
||||
|
@ -143,7 +143,7 @@ public static class FileStoreHelper
|
|||
if (!System.IO.File.Exists(filePath))
|
||||
{
|
||||
//---数据模板文件存储路径上未找对应文件,请联系系统运维人员。
|
||||
throw new BusinessValidationFailedException(StaticData.International("FileStore_TemplateFileStoragePathInvalid"));
|
||||
throw new BusinessValidationFailedException(I18n.T("FileStore_TemplateFileStoragePathInvalid"));
|
||||
}
|
||||
|
||||
return (filePath, isEn_US ? doc.Name.Trim('/') : doc.NameCN.Trim('/'));
|
||||
|
@ -269,7 +269,7 @@ public static class FileStoreHelper
|
|||
{
|
||||
|
||||
//---解析Json文件配置出现问题
|
||||
throw new BusinessValidationFailedException(StaticData.International("SysMon_JsonConfig") + e.Message);
|
||||
throw new BusinessValidationFailedException(I18n.T("SysMon_JsonConfig") + e.Message);
|
||||
}
|
||||
|
||||
//默认存储的路径
|
|
@ -1,5 +1,7 @@
|
|||
using Hangfire;
|
||||
using IRaCIS.Core.Application.MassTransit.Consumer;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using MassTransit.Mediator;
|
||||
|
||||
namespace IRaCIS.Core.Application.Helper
|
||||
{
|
||||
|
@ -62,15 +64,21 @@ namespace IRaCIS.Core.Application.Helper
|
|||
{
|
||||
|
||||
case EmailBusinessScenario.QCTask:
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IEmailSendService>(jobId, t => t.SendTrialImageQCTaskEmailAsync(trialId), emailCron);
|
||||
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new ImageQCRecurringEvent() { TrialId = trialId }, default), emailCron);
|
||||
break;
|
||||
case EmailBusinessScenario.QCQuestion:
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IEmailSendService>(jobId, t => t.SendTrialQCQuestionEmailAsync(trialId), emailCron);
|
||||
case EmailBusinessScenario.CRCToQCQuestion:
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new CRCImageQuestionRecurringEvent() { TrialId = trialId }, default), emailCron);
|
||||
break;
|
||||
case EmailBusinessScenario.ImageQuestion:
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IEmailSendService>(jobId, t => t.SendTrialImageQuestionAsync(trialId), emailCron);
|
||||
case EmailBusinessScenario.QCToCRCImageQuestion:
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new QCImageQuestionRecurringEvent() { TrialId = trialId }, default), emailCron);
|
||||
break;
|
||||
//加急阅片 10分钟
|
||||
case EmailBusinessScenario.ExpeditedReading:
|
||||
HangfireJobHelper.AddOrUpdateCronJob<IMediator>(jobId, t => t.Send(new UrgentIRUnReadTaskRecurringEvent() { TrialId = trialId }, default), emailCron);
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue