using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Threading.Tasks; using IRaCIS.Application.Interfaces; using IRaCIS.Application.Contracts; using IRaCIS.Core.API.Utility; using IRaCIS.Core.Application.Contracts.Image; using IRaCIS.Core.Infrastructure.Extention; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; using IRaCIS.Core.Application.Filter; using IRaCIS.Core.Application.Contracts; namespace IRaCIS.Api.Controllers { /// /// 文件上传 /// [Route("file")] [ApiController, Authorize, ApiExplorerSettings(GroupName = "Common")] public class FileController : ControllerBase { private readonly IFileService _fileService; private readonly IWebHostEnvironment _webHostEnvironment; private readonly IHostEnvironment _hostEnvironment; private ILogger _logger; private string _targetFilePath; private readonly long _fileSizeLimit; private readonly string[] _permittedExtensions = { ".pdf", ".doc", ".docx" }; public string trustedFileNameForFileStorage = ""; // Get the default form options so that we can use them to set the default // limits for request body data. private static readonly FormOptions _defaultFormOptions = new FormOptions(); private string defaultUploadFilePath = string.Empty; public FileController(IFileService fileService, Microsoft.Extensions.Configuration.IConfiguration config, IHostEnvironment hostEnvironment, ILogger logger, IWebHostEnvironment webHostEnvironment) { _fileService = fileService; _hostEnvironment = hostEnvironment; _webHostEnvironment = webHostEnvironment; _fileSizeLimit = config.GetValue("FileSizeLimit"); defaultUploadFilePath = Directory.GetParent(_hostEnvironment.ContentRootPath.TrimEnd('\\')).FullName; _logger = logger; _logger.LogWarning("File Path:" + defaultUploadFilePath); } /// /// 上传文件[FileUpload] /// /// 附件类型 /// 医生Id /// 返回文件信息 [HttpPost, Route("uploadFile/{attachmentType}/{doctorId}")] [DisableFormValueModelBinding] public async Task UploadOrdinaryFile(string attachmentType, Guid doctorId) { #region 官方文档方式 if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) { ModelState.AddModelError("File", $"The request couldn't be processed (Error 1)."); // Log error return BadRequest(ModelState); } var boundary = MultipartRequestHelper.GetBoundary( MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit); var reader = new MultipartReader(boundary, HttpContext.Request.Body); var section = await reader.ReadNextSectionAsync(); while (section != null) { var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse( section.ContentDisposition, out var contentDisposition); if (hasContentDispositionHeader) { // This check assumes that there's a file // present without form data. If form data // is present, this method immediately fails // and returns the model error. if (!MultipartRequestHelper .HasFileContentDisposition(contentDisposition)) { ModelState.AddModelError("File", $"The request couldn't be processed (Error 2)."); // Log error return BadRequest(ModelState); } else { // Don't trust the file name sent by the client. To display // the file name, HTML-encode the value. var trustedFileNameForDisplay = WebUtility.HtmlEncode( contentDisposition.FileName.Value); //var trustedFileNameForFileStorage = Path.GetRandomFileName(); trustedFileNameForFileStorage = contentDisposition.FileName.Value; // **WARNING!** // In the following example, the file is saved without // scanning the file's contents. In most production // scenarios, an anti-virus/anti-malware scanner API // is used on the file before making the file available // for download or for use by other systems. // For more information, see the topic that accompanies // this sample. var streamedFileContent = await FileHelpers.ProcessStreamedFile( section, contentDisposition, ModelState, _permittedExtensions, _fileSizeLimit); if (!ModelState.IsValid) { return BadRequest(ModelState); } //实际文件处理 string uploadFolderPath = Path.Combine(defaultUploadFilePath, "UploadFile/" + doctorId + "/"); var doctorAttachmentUploadFolder = Path.Combine(uploadFolderPath, attachmentType); _targetFilePath = doctorAttachmentUploadFolder; if (!Directory.Exists(doctorAttachmentUploadFolder)) Directory.CreateDirectory(doctorAttachmentUploadFolder); using (var targetStream = System.IO.File.Create( Path.Combine(_targetFilePath, trustedFileNameForFileStorage))) { await targetStream.WriteAsync(streamedFileContent); var attachmentPath = $"/UploadFile/{doctorId}/{attachmentType}/{trustedFileNameForFileStorage}"; return new JsonResult(ResponseOutput.Ok(new UploadFileInfoDTO() { FilePath = attachmentPath, FullFilePath = attachmentPath + "?access_token=" + HttpContext.Request.Headers["Authorization"].ToString().Substring(7) })); } } } // Drain any remaining section body that hasn't been consumed and // read the headers for the next section. section = await reader.ReadNextSectionAsync(); } return Created(nameof(FileController), null); #endregion } /// /// 上传文件( 不是医生个人的文件)[FileUpload] /// 例如:阅片章程等 /// /// 文件类型 /// [HttpPost, Route("uploadNonDoctorFile/{type}")] [DisableFormValueModelBinding] public async Task UploadNonDoctorFile(string type) { #region New Test 实测OK //上传根路径 string uploadFolderPath = Path.Combine(defaultUploadFilePath, "UploadFile"); if (uploadFolderPath != null) { //文件类型路径处理 var uploadTypePath = Path.Combine(uploadFolderPath, type); if (!Directory.Exists(uploadTypePath)) Directory.CreateDirectory(uploadTypePath); ////实际文件处理 _targetFilePath = uploadTypePath; //获取boundary #region 方式二 var boundary = MultipartRequestHelper.GetBoundary( MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit); #endregion #region 方式一 //var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value; #endregion //得到reader var reader = new MultipartReader(boundary, HttpContext.Request.Body); //{ BodyLengthLimit = 2000 };// var section = await reader.ReadNextSectionAsync(); //读取section while (section != null) { var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition); if (hasContentDispositionHeader) { trustedFileNameForFileStorage = contentDisposition.FileName.Value; await WriteFileAsync(section.Body, Path.Combine(_targetFilePath, trustedFileNameForFileStorage)); #region 方式二 //using (var targetStream = System.IO.File.Create( // Path.Combine(_targetFilePath, trustedFileNameForFileStorage))) //{ // using (var memoryStream = new MemoryStream()) // { // await section.Body.CopyToAsync(memoryStream); // await targetStream.WriteAsync(memoryStream.ToArray()); // } //} #endregion var attachmentPath = $"/UploadFile/{type}/{trustedFileNameForFileStorage}"; return new JsonResult(ResponseOutput.Ok(new UploadFileInfoDTO() { FilePath = attachmentPath, FullFilePath = attachmentPath + "?access_token=" + HttpContext.Request.Headers["Authorization"].ToString().Substring(7) })); } section = await reader.ReadNextSectionAsync(); } return Created(nameof(FileController), null); } return new JsonResult(ResponseOutput.NotOk("服务器端映射路径操作失败")); #endregion } /// /// 写文件导到磁盘 /// /// 流 /// 文件保存路径 /// public static async Task WriteFileAsync(System.IO.Stream stream, string path) { const int FILE_WRITE_SIZE = 84975;//写出缓冲区大小 int writeCount = 0; using (FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Write, FILE_WRITE_SIZE, true)) { byte[] byteArr = new byte[FILE_WRITE_SIZE]; int readCount = 0; while ((readCount = await stream.ReadAsync(byteArr, 0, byteArr.Length)) > 0) { await fileStream.WriteAsync(byteArr, 0, readCount); writeCount += readCount; } } return writeCount; } #region 上传无影响部分 /// /// 下载多个医生的所有附件 /// /// /// [HttpPost, Route("downloadDoctorAttachments")] public async Task> DownloadAttachment(Guid[] doctorIds) { var path = await _fileService.CreateDoctorsAllAttachmentZip(doctorIds); return ResponseOutput.Ok(new UploadFileInfoDTO { FilePath = path, FullFilePath = path + "?access_token=" + HttpContext.Request.Headers["Authorization"].ToString().Substring(7) }); } /// /// 下载医生官方简历 /// /// /// /// [HttpPost, Route("downloadOfficialCV/{language}")] public async Task> DownloadOfficialResume(int language, Guid[] doctorIds) { var path = _fileService.CreateDoctorsAllAttachmentZip(doctorIds); return ResponseOutput.Ok(new UploadFileInfoDTO { FilePath = await _fileService.CreateOfficialResumeZip(language, doctorIds), FullFilePath = path + "?access_token=" + HttpContext.Request.Headers["Authorization"].ToString().Substring(7) }); } /// /// 下载指定医生的指定附件 /// /// 医生Id /// 要下载的附件Id /// [HttpPost, Route("downloadByAttachmentId/{doctorId}")] public async Task> DownloadAttachmentById(Guid doctorId, Guid[] attachmentIds) { var path = await _fileService.CreateZipPackageByAttachment(doctorId, attachmentIds); return ResponseOutput.Ok(new UploadFileInfoDTO { FilePath = await _fileService.CreateZipPackageByAttachment(doctorId, attachmentIds), FullFilePath = path + "?access_token=" + HttpContext.Request.Headers["Authorization"].ToString().Substring(7) }); } #endregion #region 废弃 public class UploadDTFCommand { public Guid TrialId { get; set; } public Guid SiteId { get; set; } public Guid SubjectId { get; set; } public Guid SubjectVisitId { get; set; } } /// /// 流式上传 临时文件 /// /// [HttpPost("UploadDTF/{trialId:guid}/{siteId:guid}/{subjectId:guid}/{subjectVisitId:guid}/{studyId:guid}")] [DisableFormValueModelBinding] [DisableRequestSizeLimit] [Obsolete] public async Task UploadingStream(Guid trialId, Guid siteId, Guid subjectId, Guid subjectVisitId, Guid studyId, [FromServices] IStudyDTFService studyDTFService) { //上传根路径 string uploadFolderPath = Path.Combine(defaultUploadFilePath, "Dicom"); var dtfPath = Path.Combine(uploadFolderPath, DateTime.Now.Year.ToString(), trialId.ToString(), siteId.ToString(), subjectId.ToString(), subjectVisitId.ToString()); if (!Directory.Exists(dtfPath)) { Directory.CreateDirectory(dtfPath); } #region 之前DTF 先上传临时文件,再拷贝 ////上传根路径 //string uploadFolderPath = Path.Combine(defaultUploadFilePath, "UploadFile"); ////文件类型路径处理 //var uploadTempFilePath = Path.Combine(uploadFolderPath, "TempFile"); //if (!Directory.Exists(uploadTempFilePath)) Directory.CreateDirectory(uploadTempFilePath); //var trustedFileNameForFileStorage = Guid.NewGuid().ToString() + fileNameEX; //await WriteFileAsync(section.Body, Path.Combine(uploadTempFilePath, trustedFileNameForFileStorage)); //var attachmentPath = $"{uploadTempFilePath}/{trustedFileNameForFileStorage}"; ////多个文件上传,在这里返回就不合适,需要在外层,返回所有的文件路径,实际需要时再更改 //return ResponseOutput.Ok(new UploadFileInfoDTO() { FilePath = attachmentPath }); #endregion //获取boundary var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value; //得到reader var reader = new MultipartReader(boundary, HttpContext.Request.Body); var section = await reader.ReadNextSectionAsync(); //读取section while (section != null) { var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition); if (hasContentDispositionHeader) { var realName = contentDisposition.FileName.Value; var fileNameEX = Path.GetExtension(contentDisposition.FileName.Value); var trustedFileNameForFileStorage = Guid.NewGuid().ToString() + fileNameEX; var relativePath = $"/Dicom/{DateTime.Now.Year.ToString()}/{trialId}/{siteId}/{subjectId}/{subjectVisitId}/{trustedFileNameForFileStorage}"; studyDTFService.AddStudyDTF(new Core.Application.Contracts.Dicom.DTO.StudyDTFAddOrUpdateCommand() { StudyId = studyId, FileName = realName, Path = relativePath }); await WriteFileAsync(section.Body, Path.Combine(dtfPath, trustedFileNameForFileStorage)); //仅仅返回一个文件,如果多文件上传 在最后返回多个路径 return ResponseOutput.Ok(new UploadFileInfoDTO() { FilePath = relativePath, FullFilePath = relativePath + "?access_token=" + HttpContext.Request.Headers["Authorization"].ToString().Substring(7) }); } section = await reader.ReadNextSectionAsync(); } return ResponseOutput.NotOk("Upload error"); } /// /// 流式上传 非Dicom文件 /// /// [HttpPost("UploadNoneDICOM")] [DisableFormValueModelBinding] [DisableRequestSizeLimit] [Obsolete] public async Task UploadingNoneDicomStream([FromForm] ArchiveStudyCommand archiveStudyCommand, [FromServices] IStudyService studyService) { //上传根路径 string uploadFolderPath = Path.Combine(defaultUploadFilePath, "Dicom"); //文件类型路径处理 //var noneDicomPath = Path.Combine(uploadFolderPath, DateTime.Now.Year.ToString(), archiveStudyCommand.TrialId.ToString(), // archiveStudyCommand.SiteId.ToString(), archiveStudyCommand.SubjectId.ToString(), archiveStudyCommand.SubjectVisitId.ToString(), "Data"); //var dtfPath = Path.Combine(uploadFolderPath, DateTime.Now.Year.ToString(), archiveStudyCommand.TrialId.ToString(), // archiveStudyCommand.SiteId.ToString(), archiveStudyCommand.SubjectId.ToString(), archiveStudyCommand.SubjectVisitId.ToString()); var noneDicomPath = string.Empty; var dtfPath = string.Empty; var tempDtfPath = Path.Combine(defaultUploadFilePath/*, archiveStudyCommand.DTFPath*/); if (!Directory.Exists(noneDicomPath)) { Directory.CreateDirectory(noneDicomPath); } if (!System.IO.File.Exists(tempDtfPath)) { return ResponseOutput.NotOk("DTF file can not found"); } System.IO.File.Move(tempDtfPath, dtfPath); var saveFileDic = new Dictionary(); //获取boundary var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value; //得到reader var reader = new MultipartReader(boundary, HttpContext.Request.Body); var section = await reader.ReadNextSectionAsync(); //读取section while (section != null) { var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition); if (hasContentDispositionHeader) { var fileName = contentDisposition.FileName.Value; var fileNameEX = Path.GetExtension(contentDisposition.FileName.Value); var trustedFileNameForFileStorage = Guid.NewGuid() + fileNameEX; await WriteFileAsync(section.Body, Path.Combine(noneDicomPath, trustedFileNameForFileStorage)); var attachmentPath = $"{noneDicomPath}/{trustedFileNameForFileStorage}"; saveFileDic.Add(attachmentPath, fileName); } section = await reader.ReadNextSectionAsync(); } //处理数据库操作 //studyService.DealNonDicomFile(saveFileDic, archiveStudyCommand); return ResponseOutput.Ok(saveFileDic); } #endregion } }