namespace SimpleAdmin.Web.Core; /// /// 提供员工端资料提交相关 HTTP 接口。 /// [ApiDescriptionSettings("Application", Tag = "员工入职提交")] [Route("open/onboarding")] [AllowAnonymous] public class OnboardingPublicSubmissionController { private readonly IOnboardingPublicSubmissionService _service; private readonly IFileService _fileService; public OnboardingPublicSubmissionController(IOnboardingPublicSubmissionService service, IFileService fileService) { _service = service; _fileService = fileService; } /// /// 删除当前入职任务中的一条已上传材料。 /// [HttpPost("delete-material")] public async Task DeleteMaterial( [FromBody] OnboardingDeleteMaterialInput input) { await _service.DeleteMaterial(input); } /// /// 确认已阅读指定合同。 /// [HttpPost("confirm-contract")] public async Task ConfirmContract([FromBody] OnboardingConfirmContractInput input) { await _service.ConfirmContract(input); } /// /// 提交入职资料进入审核。 /// [HttpPost("submit")] public async Task Submit([FromBody] OnboardingSubmitInput input) { await _service.Submit(input); } /// /// 校验并上传员工入职材料。 /// [HttpPost("upload-material")] [Consumes("multipart/form-data")] [RequestSizeLimit(21 * 1024 * 1024)] public async Task UploadMaterial( [FromQuery] string token, [FromQuery] long materialTypeId, [FromQuery] long? taskMaterialId, [FromForm] IFormFile file) { if (file == null || file.Length == 0) { throw Oops.Bah("请选择需要上传的文件"); } await _service.ValidateMaterialUpload(new OnboardingMaterialUploadValidationInput { Token = token, MaterialTypeId = materialTypeId, FileName = file.FileName, FileLength = file.Length, TaskMaterialId = taskMaterialId }); await ValidateMaterialFileContent(file); var fileId = await _fileService.UploadFile(SysDictConst.FILE_ENGINE_ALIYUN_OSS, file); await _service.SaveMaterial(new OnboardingSaveMaterialInput { Token = token, MaterialTypeId = materialTypeId, FileId = fileId, TaskMaterialId = taskMaterialId }); } private static async Task ValidateMaterialFileContent(IFormFile file) { var suffix = global::System.IO.Path.GetExtension(file.FileName).TrimStart('.').ToLowerInvariant(); var supported = suffix is "png" or "jpg" or "jpeg" or "gif" or "bmp" or "webp" or "pdf"; if (!supported) throw Oops.Bah("仅允许上传图片或PDF文件"); var header = new byte[12]; var bytesRead = 0; await using (var stream = file.OpenReadStream()) { while (bytesRead < header.Length) { var read = await stream.ReadAsync(header.AsMemory(bytesRead, header.Length - bytesRead)); if (read == 0) break; bytesRead += read; } } var contentMatchesSuffix = suffix switch { "jpg" or "jpeg" => bytesRead >= 3 && header[0] == 0xFF && header[1] == 0xD8 && header[2] == 0xFF, "png" => bytesRead >= 8 && header[0] == 0x89 && header[1] == 0x50 && header[2] == 0x4E && header[3] == 0x47 && header[4] == 0x0D && header[5] == 0x0A && header[6] == 0x1A && header[7] == 0x0A, "gif" => bytesRead >= 6 && header[0] == 0x47 && header[1] == 0x49 && header[2] == 0x46 && header[3] == 0x38 && (header[4] == 0x37 || header[4] == 0x39) && header[5] == 0x61, "bmp" => bytesRead >= 2 && header[0] == 0x42 && header[1] == 0x4D, "webp" => bytesRead >= 12 && header[0] == 0x52 && header[1] == 0x49 && header[2] == 0x46 && header[3] == 0x46 && header[8] == 0x57 && header[9] == 0x45 && header[10] == 0x42 && header[11] == 0x50, "pdf" => bytesRead >= 5 && header[0] == 0x25 && header[1] == 0x50 && header[2] == 0x44 && header[3] == 0x46 && header[4] == 0x2D, _ => false }; if (!contentMatchesSuffix) throw Oops.Bah("文件真实格式与扩展名不一致,请上传有效的图片或PDF文件"); if (suffix == "pdf") return; await using var imageStream = file.OpenReadStream(); SkiaSharp.SKCodec codec; try { codec = SkiaSharp.SKCodec.Create(imageStream); } catch { throw Oops.Bah("图片文件已损坏或格式不受支持"); } using (codec) { if (codec == null || codec.Info.Width <= 0 || codec.Info.Height <= 0) throw Oops.Bah("图片文件已损坏或格式不受支持"); if ((long)codec.Info.Width * codec.Info.Height > 40_000_000L) throw Oops.Bah("图片分辨率过大,请压缩后重新上传"); } } }