From db3d58bc99a201dbc7b5f667f67e6f652efcc77f Mon Sep 17 00:00:00 2001
From: fuenmao <980740792@qq.com>
Date: Wed, 26 Aug 2026 15:30:31 +0800
Subject: [PATCH] 1111
---
.../Entity/Onboarding/ObOnboardingTask.cs | 31 ++--
.../Entity/Onboarding/ObTaskLink.cs | 23 ++-
...OnboardingMaterialUploadValidationInput.cs | 5 +
.../Dto/OnboardingPublicSubmissionInput.cs | 5 +
.../OnboardingPublicSubmissionService.cs | 26 ++-
.../IOnboardingPublicTaskService.cs | 6 +
.../PublicTask/OnboardingPublicTaskService.cs | 65 ++++++-
.../Task/Dto/OnboardingTaskPublicInput.cs | 81 ++++++++-
.../Onboarding/Task/OnboardingTaskService.cs | 1 +
.../TaskQuery/IOnboardingTaskQueryService.cs | 5 +
.../TaskQuery/OnboardingTaskQueryService.cs | 158 +++++++++++++++++-
.../SimpleAdmin.Application.xml | 134 ++++++++++++++-
.../OnboardingPublicSubmissionController.cs | 7 +-
.../OnboardingPublicTaskController.cs | 31 ++++
.../OnboardingTaskQueryController.cs | 9 +
SimpleAdmin.Web.Core/SimpleAdmin.Web.Core.xml | 17 +-
16 files changed, 565 insertions(+), 39 deletions(-)
diff --git a/SimpleAdmin.Application/Entity/Onboarding/ObOnboardingTask.cs b/SimpleAdmin.Application/Entity/Onboarding/ObOnboardingTask.cs
index dd0cf51..a990480 100644
--- a/SimpleAdmin.Application/Entity/Onboarding/ObOnboardingTask.cs
+++ b/SimpleAdmin.Application/Entity/Onboarding/ObOnboardingTask.cs
@@ -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;
///
/// 新员工入职任务,承载候选人填写、审核、转正式员工和归档全过程。
@@ -86,4 +76,23 @@ public class ObOnboardingTask : BaseEntity
/// 业务备注。
///
public string Remark { get; set; }
+
+ ///
+ /// 公司名称 (展示用)
+ ///
+ [SugarColumn(IsIgnore = true)]
+ public string CompanyName { get; set; }
+
+ ///
+ /// 岗位名称 (展示用)
+ ///
+ [SugarColumn(IsIgnore = true)]
+ public string PositionName { get; set; }
+
+ ///
+ /// 入职模板名称 (展示用)
+ ///
+ [SugarColumn(IsIgnore = true)]
+ public string OnboardingTemplateName { get; set; }
+
}
diff --git a/SimpleAdmin.Application/Entity/Onboarding/ObTaskLink.cs b/SimpleAdmin.Application/Entity/Onboarding/ObTaskLink.cs
index 7de1398..9ff8391 100644
--- a/SimpleAdmin.Application/Entity/Onboarding/ObTaskLink.cs
+++ b/SimpleAdmin.Application/Entity/Onboarding/ObTaskLink.cs
@@ -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;
///
-/// 新员工专属填写链接,仅保存 Token 哈希及链接生命周期信息。
+/// 新员工专属填写链接。
///
[SugarTable("ob_task_link", TableDescription = "新员工专属填写链接")]
[Tenant(SqlSugarConst.DB_DEFAULT)]
@@ -23,7 +13,14 @@ public class ObTaskLink : BaseEntity
public long TaskId { get; set; }
///
- /// 访问 Token 的 SHA-256 哈希,不保存明文 Token。
+ /// 明文 Token,仅供受保护的管理端链接接口内部读取。
+ ///
+ [SugarColumn(IsNullable = true)]
+ [global::System.Text.Json.Serialization.JsonIgnore]
+ public string PlainToken { get; set; }
+
+ ///
+ /// 访问 Token 的 SHA-256 哈希。
///
public string TokenHash { get; set; }
diff --git a/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/Dto/OnboardingMaterialUploadValidationInput.cs b/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/Dto/OnboardingMaterialUploadValidationInput.cs
index e78792b..87d5630 100644
--- a/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/Dto/OnboardingMaterialUploadValidationInput.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/Dto/OnboardingMaterialUploadValidationInput.cs
@@ -24,4 +24,9 @@ public class OnboardingMaterialUploadValidationInput
/// 待上传文件大小,单位为字节。
///
public long FileLength { get; set; }
+
+ ///
+ /// 需要重新上传并替换的任务材料记录 Id;为空时按新增数量校验。
+ ///
+ public long? TaskMaterialId { get; set; }
}
diff --git a/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/Dto/OnboardingPublicSubmissionInput.cs b/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/Dto/OnboardingPublicSubmissionInput.cs
index 4f500fb..07a3272 100644
--- a/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/Dto/OnboardingPublicSubmissionInput.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/Dto/OnboardingPublicSubmissionInput.cs
@@ -53,6 +53,11 @@ public class OnboardingSaveMaterialInput
/// 系统文件 Id。
///
public long FileId { get; set; }
+
+ ///
+ /// 需要重新上传并替换的任务材料记录 Id;为空时新增材料。
+ ///
+ public long? TaskMaterialId { get; set; }
}
///
diff --git a/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/OnboardingPublicSubmissionService.cs b/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/OnboardingPublicSubmissionService.cs
index 053074e..9776b42 100644
--- a/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/OnboardingPublicSubmissionService.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/PublicSubmission/OnboardingPublicSubmissionService.cs
@@ -39,6 +39,20 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
var existingMaterials = await _db.Queryable()
.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()
.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;
}
-}
\ No newline at end of file
+}
diff --git a/SimpleAdmin.Application/Services/Onboarding/PublicTask/IOnboardingPublicTaskService.cs b/SimpleAdmin.Application/Services/Onboarding/PublicTask/IOnboardingPublicTaskService.cs
index 1ad6d39..0c5dbaa 100644
--- a/SimpleAdmin.Application/Services/Onboarding/PublicTask/IOnboardingPublicTaskService.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/PublicTask/IOnboardingPublicTaskService.cs
@@ -17,4 +17,10 @@ public interface IOnboardingPublicTaskService : ITransient
/// 校验任务归属后返回合同模板源文件 Id。
///
Task GetContractSourceFileId(OnboardingPublicContractSourceInput input);
+
+ ///
+ /// 校验 Token 和任务归属后返回已上传材料的文件信息。
+ ///
+ Task GetMaterialSource(
+ OnboardingPublicMaterialSourceInput input);
}
diff --git a/SimpleAdmin.Application/Services/Onboarding/PublicTask/OnboardingPublicTaskService.cs b/SimpleAdmin.Application/Services/Onboarding/PublicTask/OnboardingPublicTaskService.cs
index aca0113..ed59ffd 100644
--- a/SimpleAdmin.Application/Services/Onboarding/PublicTask/OnboardingPublicTaskService.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/PublicTask/OnboardingPublicTaskService.cs
@@ -38,6 +38,36 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
var contracts = await _db.Queryable()
.Where(item => item.TaskId == task.Id)
.ToListAsync();
+ var materials = await _db.Queryable()
+ .Where(item => item.TaskId == task.Id)
+ .ToListAsync();
+ var materialFileIds = materials
+ .Select(item => item.FileId)
+ .Distinct()
+ .ToList();
+ var materialFiles = materialFileIds.Count == 0
+ ? new List()
+ : await _db.Queryable()
+ .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(),
- Materials = await _db.Queryable()
- .Where(item => item.TaskId == task.Id)
- .ToListAsync(),
+ Materials = materialOutputs,
FormFields = new List(),
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;
}
+ ///
+ /// 校验一次性链接与材料归属,避免通过文件 Id 越权读取其他任务文件。
+ ///
+ public async Task GetMaterialSource(
+ OnboardingPublicMaterialSourceInput input)
+ {
+ var link = await GetActiveLink(input.Token);
+ var material = await _db.Queryable()
+ .FirstAsync(item => item.Id == input.TaskMaterialId && item.TaskId == link.TaskId)
+ ?? throw Oops.Bah("上传材料不存在");
+ var file = await _db.Queryable().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 GetActiveLink(string token)
{
if (string.IsNullOrWhiteSpace(token))
@@ -171,4 +228,4 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
return link;
}
-}
\ No newline at end of file
+}
diff --git a/SimpleAdmin.Application/Services/Onboarding/Task/Dto/OnboardingTaskPublicInput.cs b/SimpleAdmin.Application/Services/Onboarding/Task/Dto/OnboardingTaskPublicInput.cs
index 8742350..f4475de 100644
--- a/SimpleAdmin.Application/Services/Onboarding/Task/Dto/OnboardingTaskPublicInput.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/Task/Dto/OnboardingTaskPublicInput.cs
@@ -30,6 +30,24 @@ public class OnboardingPublicContractSourceInput
public long TaskContractId { get; set; }
}
+///
+/// 员工端预览已上传材料参数。
+///
+public class OnboardingPublicMaterialSourceInput
+{
+ ///
+ /// 员工填写链接中的明文 Token。
+ ///
+ [global::System.ComponentModel.DataAnnotations.Required]
+ public string Token { get; set; }
+
+ ///
+ /// 任务材料记录 Id。
+ ///
+ [IdNotNull(ErrorMessage = "任务材料Id不能为空")]
+ public long TaskMaterialId { get; set; }
+}
+
///
/// 员工端保存固定基础信息参数。
///
@@ -162,12 +180,73 @@ public class OnboardingPublicMaterialOutput
///
public int MaxFileCount { get; set; }
+ ///
+ /// 单个文件最大大小,单位为 MB;为空表示不限制。
+ ///
+ public int? MaxFileSizeMb { get; set; }
+
///
/// 是否必填、必传或必签。
///
public bool Required { get; set; }
}
+///
+/// 员工端已上传材料展示信息。
+///
+public class OnboardingPublicTaskMaterialOutput
+{
+ ///
+ /// 任务材料记录 Id。
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// 材料类型 Id。
+ ///
+ public long MaterialTypeId { get; set; }
+
+ ///
+ /// 材料显示名称。
+ ///
+ public string MaterialName { get; set; }
+
+ ///
+ /// 系统文件 Id。
+ ///
+ public long FileId { get; set; }
+
+ ///
+ /// 用户上传时的原始文件名。
+ ///
+ public string FileName { get; set; }
+
+ ///
+ /// 文件扩展名,不包含点号。
+ ///
+ public string FileSuffix { get; set; }
+
+ ///
+ /// 格式化后的文件大小。
+ ///
+ public string FileSizeInfo { get; set; }
+
+ ///
+ /// 材料上传时间。
+ ///
+ public DateTime UploadedAt { get; set; }
+
+ ///
+ /// 材料审核状态。
+ ///
+ public string ReviewStatus { get; set; }
+
+ ///
+ /// 材料审核意见。
+ ///
+ public string ReviewRemark { get; set; }
+}
+
///
/// 员工端入职任务完整详情。
///
@@ -206,7 +285,7 @@ public class OnboardingTaskPublicDetailOutput
///
/// 候选人已上传材料列表。
///
- public List Materials { get; set; }
+ public List Materials { get; set; }
///
/// 合同中由员工填写的扩展变量。
diff --git a/SimpleAdmin.Application/Services/Onboarding/Task/OnboardingTaskService.cs b/SimpleAdmin.Application/Services/Onboarding/Task/OnboardingTaskService.cs
index 20baa88..fb36245 100644
--- a/SimpleAdmin.Application/Services/Onboarding/Task/OnboardingTaskService.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/Task/OnboardingTaskService.cs
@@ -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
diff --git a/SimpleAdmin.Application/Services/Onboarding/TaskQuery/IOnboardingTaskQueryService.cs b/SimpleAdmin.Application/Services/Onboarding/TaskQuery/IOnboardingTaskQueryService.cs
index 2a11668..9d9371f 100644
--- a/SimpleAdmin.Application/Services/Onboarding/TaskQuery/IOnboardingTaskQueryService.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/TaskQuery/IOnboardingTaskQueryService.cs
@@ -14,4 +14,9 @@ public interface IOnboardingTaskQueryService : ITransient
/// 查询任务基础信息、人员资料、合同、材料、表单值、链接和审核记录。
///
Task Detail(BaseIdInput input);
+
+ ///
+ /// 获取或重新生成任务有效填写链接。
+ ///
+ Task GetTaskLink(BaseIdInput input);
}
diff --git a/SimpleAdmin.Application/Services/Onboarding/TaskQuery/OnboardingTaskQueryService.cs b/SimpleAdmin.Application/Services/Onboarding/TaskQuery/OnboardingTaskQueryService.cs
index e6b2dd4..beda0dc 100644
--- a/SimpleAdmin.Application/Services/Onboarding/TaskQuery/OnboardingTaskQueryService.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/TaskQuery/OnboardingTaskQueryService.cs
@@ -1,4 +1,4 @@
-namespace SimpleAdmin.Application;
+namespace SimpleAdmin.Application;
///
/// 实现入职任务查询业务能力。
@@ -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;
}
///
/// 分页查询后台入职任务。
///
- public Task> Page(OnboardingTaskPageInput input)
+ public async Task> Page(OnboardingTaskPageInput input)
{
- return _db.Queryable()
+ var list = await _db.Queryable()
.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;
}
///
@@ -33,6 +42,8 @@ public class OnboardingTaskQueryService : IOnboardingTaskQueryService
var task = await _db.Queryable().InSingleAsync(input.Id)
?? throw Oops.Bah("入职任务不存在");
+ await FillTaskMetadata(new List { task });
+
var employeeInfo = await _db.Queryable()
.FirstAsync(item => item.TaskId == task.Id);
var contracts = await _db.Queryable()
@@ -79,4 +90,143 @@ public class OnboardingTaskQueryService : IOnboardingTaskQueryService
Audits = audits
};
}
+
+ ///
+ /// 获取或重新生成任务有效填写链接。
+ ///
+ public async Task GetTaskLink(BaseIdInput input)
+ {
+ var task = await _db.Queryable().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()
+ .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()
+ .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()
+ .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()
+ .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 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().Where(c => companyIds.Contains(c.Id)).ToListAsync();
+ var positions = await _db.Queryable().Where(p => positionIds.Contains(p.Id)).ToListAsync();
+ var templates = await _db.Queryable().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;
+ }
+ }
+
+ ///
+ /// 从系统配置读取员工填写链接有效期,创建任务与续期保持同一规则。
+ ///
+ private async Task 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;
+ }
}
diff --git a/SimpleAdmin.Application/SimpleAdmin.Application.xml b/SimpleAdmin.Application/SimpleAdmin.Application.xml
index b9057ee..bb56b7f 100644
--- a/SimpleAdmin.Application/SimpleAdmin.Application.xml
+++ b/SimpleAdmin.Application/SimpleAdmin.Application.xml
@@ -554,6 +554,21 @@
业务备注。
+
+
+ 公司名称 (展示用)
+
+
+
+
+ 岗位名称 (展示用)
+
+
+
+
+ 入职模板名称 (展示用)
+
+
入职模板,统一组织公司、岗位、合同、补充字段和上传材料。
@@ -1151,7 +1166,7 @@
- 新员工专属填写链接,仅保存 Token 哈希及链接生命周期信息。
+ 新员工专属填写链接。
@@ -1159,9 +1174,14 @@
所属入职任务 Id。
+
+
+ 明文 Token,仅供受保护的管理端链接接口内部读取。
+
+
- 访问 Token 的 SHA-256 哈希,不保存明文 Token。
+ 访问 Token 的 SHA-256 哈希。
@@ -2977,6 +2997,11 @@
待上传文件大小,单位为字节。
+
+
+ 需要重新上传并替换的任务材料记录 Id;为空时按新增数量校验。
+
+
单个动态字段填写值。
@@ -3027,6 +3052,11 @@
系统文件 Id。
+
+
+ 需要重新上传并替换的任务材料记录 Id;为空时新增材料。
+
+
删除员工在当前可编辑任务中上传的一条材料。
@@ -3157,6 +3187,11 @@
校验任务归属后返回合同模板源文件 Id。
+
+
+ 校验 Token 和任务归属后返回已上传材料的文件信息。
+
+
员工手机端公开入口,仅凭一次性任务链接访问入职资料。
@@ -3177,6 +3212,11 @@
根据员工当前已填写信息生成合同预览,并返回已替换变量的 DOCX 文件 Id。
+
+
+ 校验一次性链接与材料归属,避免通过文件 Id 越权读取其他任务文件。
+
+
查询任务合同变量参数。
@@ -3407,6 +3447,11 @@
查询任务基础信息、人员资料、合同、材料、表单值、链接和审核记录。
+
+
+ 获取或重新生成任务有效填写链接。
+
+
实现入职任务查询业务能力。
@@ -3422,6 +3467,16 @@
查询任务基础信息、人员资料、合同、材料、表单值、链接和审核记录。
+
+
+ 获取或重新生成任务有效填写链接。
+
+
+
+
+ 从系统配置读取员工填写链接有效期,创建任务与续期保持同一规则。
+
+
创建新员工入职任务参数。
@@ -3532,6 +3587,21 @@
任务合同 Id。
+
+
+ 员工端预览已上传材料参数。
+
+
+
+
+ 员工填写链接中的明文 Token。
+
+
+
+
+ 任务材料记录 Id。
+
+
员工端保存固定基础信息参数。
@@ -3657,11 +3727,71 @@
允许上传的最大文件数量。
+
+
+ 单个文件最大大小,单位为 MB;为空表示不限制。
+
+
是否必填、必传或必签。
+
+
+ 员工端已上传材料展示信息。
+
+
+
+
+ 任务材料记录 Id。
+
+
+
+
+ 材料类型 Id。
+
+
+
+
+ 材料显示名称。
+
+
+
+
+ 系统文件 Id。
+
+
+
+
+ 用户上传时的原始文件名。
+
+
+
+
+ 文件扩展名,不包含点号。
+
+
+
+
+ 格式化后的文件大小。
+
+
+
+
+ 材料上传时间。
+
+
+
+
+ 材料审核状态。
+
+
+
+
+ 材料审核意见。
+
+
员工端入职任务完整详情。
diff --git a/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingPublicSubmissionController.cs b/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingPublicSubmissionController.cs
index 873f916..998fef0 100644
--- a/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingPublicSubmissionController.cs
+++ b/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingPublicSubmissionController.cs
@@ -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
});
}
}
diff --git a/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingPublicTaskController.cs b/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingPublicTaskController.cs
index 7692220..e63ba17 100644
--- a/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingPublicTaskController.cs
+++ b/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingPublicTaskController.cs
@@ -44,4 +44,35 @@ public class OnboardingPublicTaskController
var fileId = await _service.GetContractSourceFileId(input);
return await _fileService.Download(new BaseIdInput { Id = fileId });
}
+
+ ///
+ /// 通过任务 Token 内联输出候选人已上传的图片材料,供手机端预览。
+ ///
+ [HttpGet("material-source")]
+ public async Task 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));
+ }
+
+ ///
+ /// 根据图片扩展名设置浏览器可内联展示的响应类型。
+ ///
+ 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"
+ };
+ }
}
diff --git a/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingTaskQueryController.cs b/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingTaskQueryController.cs
index b8a90fb..5eae2e6 100644
--- a/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingTaskQueryController.cs
+++ b/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingTaskQueryController.cs
@@ -32,4 +32,13 @@ public class OnboardingTaskQueryController
{
return await _service.Detail(input);
}
+
+ ///
+ /// 获取或重新生成入职任务专属填写链接。
+ ///
+ [HttpGet("get-link")]
+ public async Task GetTaskLink([FromQuery] BaseIdInput input)
+ {
+ return await _service.GetTaskLink(input);
+ }
}
diff --git a/SimpleAdmin.Web.Core/SimpleAdmin.Web.Core.xml b/SimpleAdmin.Web.Core/SimpleAdmin.Web.Core.xml
index 018d789..123c174 100644
--- a/SimpleAdmin.Web.Core/SimpleAdmin.Web.Core.xml
+++ b/SimpleAdmin.Web.Core/SimpleAdmin.Web.Core.xml
@@ -276,7 +276,7 @@
提交入职资料进入审核。
-
+
校验并上传员工入职材料。
@@ -301,6 +301,16 @@
下载根据当前任务资料动态生成并已替换变量的合同文件。
+
+
+ 通过任务 Token 内联输出候选人已上传的图片材料,供手机端预览。
+
+
+
+
+ 根据图片扩展名设置浏览器可内联展示的响应类型。
+
+
提供任务合同变量维护相关 HTTP 接口。
@@ -361,6 +371,11 @@
查询入职任务完整详情。
+
+
+ 获取或重新生成入职任务专属填写链接。
+
+
提供入职模板聚合配置接口。