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("图片分辨率过大,请压缩后重新上传");
}
}
}