This commit is contained in:
fuenmao
2026-08-26 15:30:31 +08:00
parent 9ae5e0bc54
commit db3d58bc99
16 changed files with 565 additions and 39 deletions
@@ -1,14 +1,4 @@
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
//
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
// 1.请不要删除和修改根目录下的LICENSE文件。
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
namespace SimpleAdmin.Application;
namespace SimpleAdmin.Application;
/// <summary>
/// 新员工入职任务,承载候选人填写、审核、转正式员工和归档全过程。
@@ -86,4 +76,23 @@ public class ObOnboardingTask : BaseEntity
/// 业务备注。
/// </summary>
public string Remark { get; set; }
/// <summary>
/// 公司名称 (展示用)
/// </summary>
[SugarColumn(IsIgnore = true)]
public string CompanyName { get; set; }
/// <summary>
/// 岗位名称 (展示用)
/// </summary>
[SugarColumn(IsIgnore = true)]
public string PositionName { get; set; }
/// <summary>
/// 入职模板名称 (展示用)
/// </summary>
[SugarColumn(IsIgnore = true)]
public string OnboardingTemplateName { get; set; }
}
@@ -1,17 +1,7 @@
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
//
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
// 1.请不要删除和修改根目录下的LICENSE文件。
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
namespace SimpleAdmin.Application;
namespace SimpleAdmin.Application;
/// <summary>
/// 新员工专属填写链接,仅保存 Token 哈希及链接生命周期信息。
/// 新员工专属填写链接。
/// </summary>
[SugarTable("ob_task_link", TableDescription = "新员工专属填写链接")]
[Tenant(SqlSugarConst.DB_DEFAULT)]
@@ -23,7 +13,14 @@ public class ObTaskLink : BaseEntity
public long TaskId { get; set; }
/// <summary>
/// 访问 Token 的 SHA-256 哈希,不保存明文 Token。
/// 明文 Token,仅供受保护的管理端链接接口内部读取。
/// </summary>
[SugarColumn(IsNullable = true)]
[global::System.Text.Json.Serialization.JsonIgnore]
public string PlainToken { get; set; }
/// <summary>
/// 访问 Token 的 SHA-256 哈希。
/// </summary>
public string TokenHash { get; set; }
@@ -24,4 +24,9 @@ public class OnboardingMaterialUploadValidationInput
/// 待上传文件大小,单位为字节。
/// </summary>
public long FileLength { get; set; }
/// <summary>
/// 需要重新上传并替换的任务材料记录 Id;为空时按新增数量校验。
/// </summary>
public long? TaskMaterialId { get; set; }
}
@@ -53,6 +53,11 @@ public class OnboardingSaveMaterialInput
/// 系统文件 Id。
/// </summary>
public long FileId { get; set; }
/// <summary>
/// 需要重新上传并替换的任务材料记录 Id;为空时新增材料。
/// </summary>
public long? TaskMaterialId { get; set; }
}
/// <summary>
@@ -39,6 +39,20 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
var existingMaterials = await _db.Queryable<ObTaskMaterial>()
.Where(item => item.TaskId == task.Id && item.MaterialTypeId == input.MaterialTypeId)
.ToListAsync();
if (input.TaskMaterialId.HasValue)
{
var replacement = existingMaterials.FirstOrDefault(
item => item.Id == input.TaskMaterialId.Value)
?? throw Oops.Bah("需要重新上传的材料不存在");
replacement.FileId = input.FileId;
replacement.SubmitRound = task.SubmitCount + 1;
replacement.UploadedAt = DateTime.Now;
replacement.ReviewStatus = TaskMaterialReviewStatus.Pending;
replacement.ReviewRemark = null;
await _db.Updateable(replacement).ExecuteCommandAsync();
return;
}
var supplementMaterial = existingMaterials.FirstOrDefault(
item => item.ReviewStatus == TaskMaterialReviewStatus.NeedSupplement);
if (supplementMaterial != null)
@@ -184,6 +198,16 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
var materials = await _db.Queryable<ObTaskMaterial>()
.Where(item => item.TaskId == task.Id && item.MaterialTypeId == input.MaterialTypeId)
.ToListAsync();
if (input.TaskMaterialId.HasValue)
{
var replacementExists = materials.Any(
item => item.Id == input.TaskMaterialId.Value);
if (!replacementExists)
throw Oops.Bah("需要重新上传的材料不存在");
return;
}
var canReplaceSupplement = materials.Any(
item => item.ReviewStatus == TaskMaterialReviewStatus.NeedSupplement);
if (materials.Count >= requirement.MaxFileCount && !canReplaceSupplement)
@@ -246,4 +270,4 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
return task;
}
}
}
@@ -17,4 +17,10 @@ public interface IOnboardingPublicTaskService : ITransient
/// 校验任务归属后返回合同模板源文件 Id。
/// </summary>
Task<long> GetContractSourceFileId(OnboardingPublicContractSourceInput input);
/// <summary>
/// 校验 Token 和任务归属后返回已上传材料的文件信息。
/// </summary>
Task<OnboardingPublicTaskMaterialOutput> GetMaterialSource(
OnboardingPublicMaterialSourceInput input);
}
@@ -38,6 +38,36 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
var contracts = await _db.Queryable<ObTaskContract>()
.Where(item => item.TaskId == task.Id)
.ToListAsync();
var materials = await _db.Queryable<ObTaskMaterial>()
.Where(item => item.TaskId == task.Id)
.ToListAsync();
var materialFileIds = materials
.Select(item => item.FileId)
.Distinct()
.ToList();
var materialFiles = materialFileIds.Count == 0
? new List<SysFile>()
: await _db.Queryable<SysFile>()
.Where(item => materialFileIds.Contains(item.Id))
.ToListAsync();
var materialFileMap = materialFiles.ToDictionary(item => item.Id);
var materialOutputs = materials.Select(item =>
{
materialFileMap.TryGetValue(item.FileId, out var file);
return new OnboardingPublicTaskMaterialOutput
{
Id = item.Id,
MaterialTypeId = item.MaterialTypeId,
MaterialName = item.MaterialName,
FileId = item.FileId,
FileName = file?.Name,
FileSuffix = file?.Suffix,
FileSizeInfo = file?.SizeInfo,
UploadedAt = item.UploadedAt,
ReviewStatus = item.ReviewStatus,
ReviewRemark = item.ReviewRemark
};
}).ToList();
var templateVersionIds = contracts
.Select(item => item.TemplateVersionId)
.Distinct()
@@ -69,9 +99,7 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
.FirstAsync(item => item.TaskId == task.Id),
Contracts = contracts,
DynamicValues = new List<ObTaskFormValue>(),
Materials = await _db.Queryable<ObTaskMaterial>()
.Where(item => item.TaskId == task.Id)
.ToListAsync(),
Materials = materialOutputs,
FormFields = new List<OnboardingPublicFormFieldOutput>(),
MaterialRequirements = materialRequirements.Select(requirement => new OnboardingPublicMaterialOutput
{
@@ -79,6 +107,7 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
MaterialName = requirement.MaterialName,
AllowedExtensions = requirement.AllowedExtensions,
MaxFileCount = requirement.MaxFileCount,
MaxFileSizeMb = requirement.MaxFileSizeMb,
Required = requirement.Required
}).ToList(),
ContractVariables = contractVariables
@@ -155,6 +184,34 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
return fileId;
}
/// <summary>
/// 校验一次性链接与材料归属,避免通过文件 Id 越权读取其他任务文件。
/// </summary>
public async Task<OnboardingPublicTaskMaterialOutput> GetMaterialSource(
OnboardingPublicMaterialSourceInput input)
{
var link = await GetActiveLink(input.Token);
var material = await _db.Queryable<ObTaskMaterial>()
.FirstAsync(item => item.Id == input.TaskMaterialId && item.TaskId == link.TaskId)
?? throw Oops.Bah("上传材料不存在");
var file = await _db.Queryable<SysFile>().InSingleAsync(material.FileId)
?? throw Oops.Bah("上传文件不存在");
return new OnboardingPublicTaskMaterialOutput
{
Id = material.Id,
MaterialTypeId = material.MaterialTypeId,
MaterialName = material.MaterialName,
FileId = material.FileId,
FileName = file.Name,
FileSuffix = file.Suffix,
FileSizeInfo = file.SizeInfo,
UploadedAt = material.UploadedAt,
ReviewStatus = material.ReviewStatus,
ReviewRemark = material.ReviewRemark
};
}
private async Task<ObTaskLink> GetActiveLink(string token)
{
if (string.IsNullOrWhiteSpace(token))
@@ -171,4 +228,4 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
return link;
}
}
}
@@ -30,6 +30,24 @@ public class OnboardingPublicContractSourceInput
public long TaskContractId { get; set; }
}
/// <summary>
/// 员工端预览已上传材料参数。
/// </summary>
public class OnboardingPublicMaterialSourceInput
{
/// <summary>
/// 员工填写链接中的明文 Token。
/// </summary>
[global::System.ComponentModel.DataAnnotations.Required]
public string Token { get; set; }
/// <summary>
/// 任务材料记录 Id。
/// </summary>
[IdNotNull(ErrorMessage = "任务材料Id不能为空")]
public long TaskMaterialId { get; set; }
}
/// <summary>
/// 员工端保存固定基础信息参数。
/// </summary>
@@ -162,12 +180,73 @@ public class OnboardingPublicMaterialOutput
/// </summary>
public int MaxFileCount { get; set; }
/// <summary>
/// 单个文件最大大小,单位为 MB;为空表示不限制。
/// </summary>
public int? MaxFileSizeMb { get; set; }
/// <summary>
/// 是否必填、必传或必签。
/// </summary>
public bool Required { get; set; }
}
/// <summary>
/// 员工端已上传材料展示信息。
/// </summary>
public class OnboardingPublicTaskMaterialOutput
{
/// <summary>
/// 任务材料记录 Id。
/// </summary>
public long Id { get; set; }
/// <summary>
/// 材料类型 Id。
/// </summary>
public long MaterialTypeId { get; set; }
/// <summary>
/// 材料显示名称。
/// </summary>
public string MaterialName { get; set; }
/// <summary>
/// 系统文件 Id。
/// </summary>
public long FileId { get; set; }
/// <summary>
/// 用户上传时的原始文件名。
/// </summary>
public string FileName { get; set; }
/// <summary>
/// 文件扩展名,不包含点号。
/// </summary>
public string FileSuffix { get; set; }
/// <summary>
/// 格式化后的文件大小。
/// </summary>
public string FileSizeInfo { get; set; }
/// <summary>
/// 材料上传时间。
/// </summary>
public DateTime UploadedAt { get; set; }
/// <summary>
/// 材料审核状态。
/// </summary>
public string ReviewStatus { get; set; }
/// <summary>
/// 材料审核意见。
/// </summary>
public string ReviewRemark { get; set; }
}
/// <summary>
/// 员工端入职任务完整详情。
/// </summary>
@@ -206,7 +285,7 @@ public class OnboardingTaskPublicDetailOutput
/// <summary>
/// 候选人已上传材料列表。
/// </summary>
public List<ObTaskMaterial> Materials { get; set; }
public List<OnboardingPublicTaskMaterialOutput> Materials { get; set; }
/// <summary>
/// 合同中由员工填写的扩展变量。
@@ -112,6 +112,7 @@ public class OnboardingTaskService : IOnboardingTaskService
{
Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
TaskId = taskId,
PlainToken = token,
TokenHash = Convert.ToHexString(global::System.Security.Cryptography.SHA256.HashData(
global::System.Text.Encoding.UTF8.GetBytes(token))),
ExpiresAt = expiresAt
@@ -14,4 +14,9 @@ public interface IOnboardingTaskQueryService : ITransient
/// 查询任务基础信息、人员资料、合同、材料、表单值、链接和审核记录。
/// </summary>
Task<OnboardingTaskDetailOutput> Detail(BaseIdInput input);
/// <summary>
/// 获取或重新生成任务有效填写链接。
/// </summary>
Task<OnboardingTaskCreatedOutput> GetTaskLink(BaseIdInput input);
}
@@ -1,4 +1,4 @@
namespace SimpleAdmin.Application;
namespace SimpleAdmin.Application;
/// <summary>
/// 实现入职任务查询业务能力。
@@ -6,23 +6,32 @@ namespace SimpleAdmin.Application;
public class OnboardingTaskQueryService : IOnboardingTaskQueryService
{
private readonly SqlSugarScope _db;
private readonly IConfigService _configService;
public OnboardingTaskQueryService()
public OnboardingTaskQueryService(IConfigService configService)
{
_db = DbContext.DB;
_configService = configService;
}
/// <summary>
/// 分页查询后台入职任务。
/// </summary>
public Task<SqlSugarPagedList<ObOnboardingTask>> Page(OnboardingTaskPageInput input)
public async Task<SqlSugarPagedList<ObOnboardingTask>> Page(OnboardingTaskPageInput input)
{
return _db.Queryable<ObOnboardingTask>()
var list = await _db.Queryable<ObOnboardingTask>()
.WhereIF(!string.IsNullOrWhiteSpace(input.TaskNo), item => item.TaskNo.Contains(input.TaskNo))
.WhereIF(!string.IsNullOrWhiteSpace(input.TaskStatus), item => item.TaskStatus == input.TaskStatus)
.WhereIF(input.CompanyId.HasValue, item => item.CompanyId == input.CompanyId.Value)
.OrderByDescending(item => item.CreateTime)
.ToPagedListAsync(input.PageNum, input.PageSize);
if (list.List != null && list.List.Any())
{
await FillTaskMetadata(list.List);
}
return list;
}
/// <summary>
@@ -33,6 +42,8 @@ public class OnboardingTaskQueryService : IOnboardingTaskQueryService
var task = await _db.Queryable<ObOnboardingTask>().InSingleAsync(input.Id)
?? throw Oops.Bah("入职任务不存在");
await FillTaskMetadata(new List<ObOnboardingTask> { task });
var employeeInfo = await _db.Queryable<ObTaskEmployeeInfo>()
.FirstAsync(item => item.TaskId == task.Id);
var contracts = await _db.Queryable<ObTaskContract>()
@@ -79,4 +90,143 @@ public class OnboardingTaskQueryService : IOnboardingTaskQueryService
Audits = audits
};
}
/// <summary>
/// 获取或重新生成任务有效填写链接。
/// </summary>
public async Task<OnboardingTaskCreatedOutput> GetTaskLink(BaseIdInput input)
{
var task = await _db.Queryable<ObOnboardingTask>().InSingleAsync(input.Id)
?? throw Oops.Bah("入职任务不存在");
var canShareLink = task.TaskStatus == OnboardingTaskStatus.PendingFill
|| task.TaskStatus == OnboardingTaskStatus.Rejected;
if (!canShareLink)
{
throw Oops.Bah("仅待填写或已退回的入职任务可以获取填写链接");
}
var activeLink = await _db.Queryable<ObTaskLink>()
.Where(item => item.TaskId == task.Id
&& item.LinkStatus == OnboardingLinkStatus.Active
&& item.ExpiresAt > DateTime.Now
&& !string.IsNullOrWhiteSpace(item.PlainToken))
.OrderByDescending(item => item.CreateTime)
.FirstAsync();
if (activeLink != null)
{
return new OnboardingTaskCreatedOutput
{
TaskId = task.Id,
TaskNo = task.TaskNo,
LinkToken = activeLink.PlainToken,
ExpiresAt = activeLink.ExpiresAt
};
}
var expiryHours = await GetLinkExpiryHours();
var currentTime = DateTime.Now;
var token = Guid.NewGuid().ToString("N");
var expiresAt = currentTime.AddHours(expiryHours);
var newLink = new ObTaskLink
{
Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
TaskId = task.Id,
PlainToken = token,
TokenHash = Convert.ToHexString(global::System.Security.Cryptography.SHA256.HashData(
global::System.Text.Encoding.UTF8.GetBytes(token))),
ExpiresAt = expiresAt,
LinkStatus = OnboardingLinkStatus.Active
};
await _db.Ado.BeginTranAsync();
try
{
// 历史链接虽然已超过有效期,但状态可能仍为 active,续期前统一修正为已过期。
await _db.Updateable<ObTaskLink>()
.SetColumns(item => new ObTaskLink
{
LinkStatus = OnboardingLinkStatus.Expired
})
.Where(item => item.TaskId == task.Id
&& item.LinkStatus == OnboardingLinkStatus.Active
&& item.ExpiresAt <= currentTime)
.ExecuteCommandAsync();
// 旧数据可能只有 TokenHash、没有可返回的 PlainToken,需要撤销后重新签发。
await _db.Updateable<ObTaskLink>()
.SetColumns(item => new ObTaskLink
{
LinkStatus = OnboardingLinkStatus.Revoked,
RevokedAt = currentTime
})
.Where(item => item.TaskId == task.Id
&& item.LinkStatus == OnboardingLinkStatus.Active)
.ExecuteCommandAsync();
await _db.Insertable(newLink).ExecuteCommandAsync();
await _db.Updateable<ObOnboardingTask>()
.SetColumns(item => item.LinkExpireAt == expiresAt)
.Where(item => item.Id == task.Id)
.ExecuteCommandAsync();
await _db.Ado.CommitTranAsync();
}
catch
{
await _db.Ado.RollbackTranAsync();
throw;
}
return new OnboardingTaskCreatedOutput
{
TaskId = task.Id,
TaskNo = task.TaskNo,
LinkToken = token,
ExpiresAt = expiresAt
};
}
private async Task FillTaskMetadata(IEnumerable<ObOnboardingTask> tasks)
{
var taskList = tasks.ToList();
var companyIds = taskList.Select(t => t.CompanyId).Distinct().ToList();
var positionIds = taskList.Select(t => t.PositionId).Distinct().ToList();
var templateIds = taskList.Select(t => t.OnboardingTemplateId).Distinct().ToList();
var companies = await _db.Queryable<ObCompany>().Where(c => companyIds.Contains(c.Id)).ToListAsync();
var positions = await _db.Queryable<ObPosition>().Where(p => positionIds.Contains(p.Id)).ToListAsync();
var templates = await _db.Queryable<ObOnboardingTemplate>().Where(t => templateIds.Contains(t.Id)).ToListAsync();
var companyMap = companies.ToDictionary(c => c.Id, c => c.CompanyName);
var positionMap = positions.ToDictionary(p => p.Id, p => p.PositionName);
var templateMap = templates.ToDictionary(t => t.Id, t => t.TemplateName);
foreach (var task in taskList)
{
if (companyMap.TryGetValue(task.CompanyId, out var cName)) task.CompanyName = cName;
if (positionMap.TryGetValue(task.PositionId, out var pName)) task.PositionName = pName;
if (templateMap.TryGetValue(task.OnboardingTemplateId, out var tName)) task.OnboardingTemplateName = tName;
}
}
/// <summary>
/// 从系统配置读取员工填写链接有效期,创建任务与续期保持同一规则。
/// </summary>
private async Task<int> GetLinkExpiryHours()
{
var expiryConfig = await _configService.GetByConfigKey(
CateGoryConst.CONFIG_BIZ_DEFINE,
"onboard.link.expireHours");
var expiryHours = 0;
var configInvalid = expiryConfig == null
|| !int.TryParse(expiryConfig.ConfigValue, out expiryHours)
|| expiryHours <= 0;
if (configInvalid)
{
throw Oops.Bah("请在系统配置中设置 onboard.link.expireHours(正整数小时)");
}
return expiryHours;
}
}
@@ -554,6 +554,21 @@
业务备注。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObOnboardingTask.CompanyName">
<summary>
公司名称 (展示用)
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObOnboardingTask.PositionName">
<summary>
岗位名称 (展示用)
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObOnboardingTask.OnboardingTemplateName">
<summary>
入职模板名称 (展示用)
</summary>
</member>
<member name="T:SimpleAdmin.Application.ObOnboardingTemplate">
<summary>
入职模板,统一组织公司、岗位、合同、补充字段和上传材料。
@@ -1151,7 +1166,7 @@
</member>
<member name="T:SimpleAdmin.Application.ObTaskLink">
<summary>
新员工专属填写链接,仅保存 Token 哈希及链接生命周期信息。
新员工专属填写链接。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskLink.TaskId">
@@ -1159,9 +1174,14 @@
所属入职任务 Id。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskLink.PlainToken">
<summary>
明文 Token,仅供受保护的管理端链接接口内部读取。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskLink.TokenHash">
<summary>
访问 Token 的 SHA-256 哈希,不保存明文 Token。
访问 Token 的 SHA-256 哈希。
</summary>
</member>
<member name="P:SimpleAdmin.Application.ObTaskLink.LinkStatus">
@@ -2977,6 +2997,11 @@
待上传文件大小,单位为字节。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingMaterialUploadValidationInput.TaskMaterialId">
<summary>
需要重新上传并替换的任务材料记录 Id;为空时按新增数量校验。
</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingDynamicValueInput">
<summary>
单个动态字段填写值。
@@ -3027,6 +3052,11 @@
系统文件 Id。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingSaveMaterialInput.TaskMaterialId">
<summary>
需要重新上传并替换的任务材料记录 Id;为空时新增材料。
</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingDeleteMaterialInput">
<summary>
删除员工在当前可编辑任务中上传的一条材料。
@@ -3157,6 +3187,11 @@
校验任务归属后返回合同模板源文件 Id。
</summary>
</member>
<member name="M:SimpleAdmin.Application.IOnboardingPublicTaskService.GetMaterialSource(SimpleAdmin.Application.OnboardingPublicMaterialSourceInput)">
<summary>
校验 Token 和任务归属后返回已上传材料的文件信息。
</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingPublicTaskService">
<summary>
员工手机端公开入口,仅凭一次性任务链接访问入职资料。
@@ -3177,6 +3212,11 @@
根据员工当前已填写信息生成合同预览,并返回已替换变量的 DOCX 文件 Id。
</summary>
</member>
<member name="M:SimpleAdmin.Application.OnboardingPublicTaskService.GetMaterialSource(SimpleAdmin.Application.OnboardingPublicMaterialSourceInput)">
<summary>
校验一次性链接与材料归属,避免通过文件 Id 越权读取其他任务文件。
</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingTaskContractVariableQueryInput">
<summary>
查询任务合同变量参数。
@@ -3407,6 +3447,11 @@
查询任务基础信息、人员资料、合同、材料、表单值、链接和审核记录。
</summary>
</member>
<member name="M:SimpleAdmin.Application.IOnboardingTaskQueryService.GetTaskLink(SimpleAdmin.Core.BaseIdInput)">
<summary>
获取或重新生成任务有效填写链接。
</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingTaskQueryService">
<summary>
实现入职任务查询业务能力。
@@ -3422,6 +3467,16 @@
查询任务基础信息、人员资料、合同、材料、表单值、链接和审核记录。
</summary>
</member>
<member name="M:SimpleAdmin.Application.OnboardingTaskQueryService.GetTaskLink(SimpleAdmin.Core.BaseIdInput)">
<summary>
获取或重新生成任务有效填写链接。
</summary>
</member>
<member name="M:SimpleAdmin.Application.OnboardingTaskQueryService.GetLinkExpiryHours">
<summary>
从系统配置读取员工填写链接有效期,创建任务与续期保持同一规则。
</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingTaskCreateInput">
<summary>
创建新员工入职任务参数。
@@ -3532,6 +3587,21 @@
任务合同 Id。
</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingPublicMaterialSourceInput">
<summary>
员工端预览已上传材料参数。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicMaterialSourceInput.Token">
<summary>
员工填写链接中的明文 Token。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicMaterialSourceInput.TaskMaterialId">
<summary>
任务材料记录 Id。
</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingTaskPublicSaveInput">
<summary>
员工端保存固定基础信息参数。
@@ -3657,11 +3727,71 @@
允许上传的最大文件数量。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicMaterialOutput.MaxFileSizeMb">
<summary>
单个文件最大大小,单位为 MB;为空表示不限制。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicMaterialOutput.Required">
<summary>
是否必填、必传或必签。
</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingPublicTaskMaterialOutput">
<summary>
员工端已上传材料展示信息。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicTaskMaterialOutput.Id">
<summary>
任务材料记录 Id。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicTaskMaterialOutput.MaterialTypeId">
<summary>
材料类型 Id。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicTaskMaterialOutput.MaterialName">
<summary>
材料显示名称。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicTaskMaterialOutput.FileId">
<summary>
系统文件 Id。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicTaskMaterialOutput.FileName">
<summary>
用户上传时的原始文件名。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicTaskMaterialOutput.FileSuffix">
<summary>
文件扩展名,不包含点号。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicTaskMaterialOutput.FileSizeInfo">
<summary>
格式化后的文件大小。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicTaskMaterialOutput.UploadedAt">
<summary>
材料上传时间。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicTaskMaterialOutput.ReviewStatus">
<summary>
材料审核状态。
</summary>
</member>
<member name="P:SimpleAdmin.Application.OnboardingPublicTaskMaterialOutput.ReviewRemark">
<summary>
材料审核意见。
</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingTaskPublicDetailOutput">
<summary>
员工端入职任务完整详情。
@@ -72,6 +72,7 @@ public class OnboardingPublicSubmissionController
public async Task UploadMaterial(
[FromQuery] string token,
[FromQuery] long materialTypeId,
[FromQuery] long? taskMaterialId,
[FromForm] IFormFile file)
{
if (file == null || file.Length == 0)
@@ -84,7 +85,8 @@ public class OnboardingPublicSubmissionController
Token = token,
MaterialTypeId = materialTypeId,
FileName = file.FileName,
FileLength = file.Length
FileLength = file.Length,
TaskMaterialId = taskMaterialId
});
var fileId = await _fileService.UploadFile(SysDictConst.FILE_ENGINE_ALIYUN_OSS, file);
@@ -92,7 +94,8 @@ public class OnboardingPublicSubmissionController
{
Token = token,
MaterialTypeId = materialTypeId,
FileId = fileId
FileId = fileId,
TaskMaterialId = taskMaterialId
});
}
}
@@ -44,4 +44,35 @@ public class OnboardingPublicTaskController
var fileId = await _service.GetContractSourceFileId(input);
return await _fileService.Download(new BaseIdInput { Id = fileId });
}
/// <summary>
/// 通过任务 Token 内联输出候选人已上传的图片材料,供手机端预览。
/// </summary>
[HttpGet("material-source")]
public async Task<FileStreamResult> MaterialSource(
[FromQuery] OnboardingPublicMaterialSourceInput input)
{
var material = await _service.GetMaterialSource(input);
var source = await _fileService.Download(new BaseIdInput { Id = material.FileId })
?? throw Oops.Bah("上传文件不存在");
return new FileStreamResult(
source.FileStream,
GetImageContentType(material.FileSuffix));
}
/// <summary>
/// 根据图片扩展名设置浏览器可内联展示的响应类型。
/// </summary>
private static string GetImageContentType(string suffix)
{
return suffix?.TrimStart('.').ToLowerInvariant() switch
{
"png" => "image/png",
"jpg" or "jpeg" => "image/jpeg",
"gif" => "image/gif",
"bmp" => "image/bmp",
"webp" => "image/webp",
_ => "application/octet-stream"
};
}
}
@@ -32,4 +32,13 @@ public class OnboardingTaskQueryController
{
return await _service.Detail(input);
}
/// <summary>
/// 获取或重新生成入职任务专属填写链接。
/// </summary>
[HttpGet("get-link")]
public async Task<dynamic> GetTaskLink([FromQuery] BaseIdInput input)
{
return await _service.GetTaskLink(input);
}
}
+16 -1
View File
@@ -276,7 +276,7 @@
提交入职资料进入审核。
</summary>
</member>
<member name="M:SimpleAdmin.Web.Core.OnboardingPublicSubmissionController.UploadMaterial(System.String,System.Int64,Microsoft.AspNetCore.Http.IFormFile)">
<member name="M:SimpleAdmin.Web.Core.OnboardingPublicSubmissionController.UploadMaterial(System.String,System.Int64,System.Nullable{System.Int64},Microsoft.AspNetCore.Http.IFormFile)">
<summary>
校验并上传员工入职材料。
</summary>
@@ -301,6 +301,16 @@
下载根据当前任务资料动态生成并已替换变量的合同文件。
</summary>
</member>
<member name="M:SimpleAdmin.Web.Core.OnboardingPublicTaskController.MaterialSource(SimpleAdmin.Application.OnboardingPublicMaterialSourceInput)">
<summary>
通过任务 Token 内联输出候选人已上传的图片材料,供手机端预览。
</summary>
</member>
<member name="M:SimpleAdmin.Web.Core.OnboardingPublicTaskController.GetImageContentType(System.String)">
<summary>
根据图片扩展名设置浏览器可内联展示的响应类型。
</summary>
</member>
<member name="T:SimpleAdmin.Web.Core.OnboardingTaskContractVariableController">
<summary>
提供任务合同变量维护相关 HTTP 接口。
@@ -361,6 +371,11 @@
查询入职任务完整详情。
</summary>
</member>
<member name="M:SimpleAdmin.Web.Core.OnboardingTaskQueryController.GetTaskLink(SimpleAdmin.Core.BaseIdInput)">
<summary>
获取或重新生成入职任务专属填写链接。
</summary>
</member>
<member name="T:SimpleAdmin.Web.Core.OnboardingTemplateController">
<summary>
提供入职模板聚合配置接口。