diff --git a/SimpleAdmin.Application/Services/Onboarding/ContractTemplate/DocxTemplateProcessor.cs b/SimpleAdmin.Application/Services/Onboarding/ContractTemplate/DocxTemplateProcessor.cs
index 0927f21..66b3828 100644
--- a/SimpleAdmin.Application/Services/Onboarding/ContractTemplate/DocxTemplateProcessor.cs
+++ b/SimpleAdmin.Application/Services/Onboarding/ContractTemplate/DocxTemplateProcessor.cs
@@ -35,6 +35,7 @@ public static class DocxTemplateProcessor
foreach (var paragraph in document.Descendants(WordNamespace + "p"))
{
var text = string.Concat(paragraph.Descendants(WordNamespace + "t").Select(node => node.Value));
+ ValidatePlaceholderSyntax(text);
foreach (global::System.Text.RegularExpressions.Match match in PlaceholderRegex.Matches(text))
codes.Add(match.Groups["code"].Value.Trim());
}
@@ -52,6 +53,72 @@ public static class DocxTemplateProcessor
}
}
+ ///
+ /// 占位符提取正则只会匹配正确格式,因此必须先主动检查错误格式,
+ /// 避免缺少花括号或变量编码写错时被静默忽略并继续创建版本。
+ ///
+ private static void ValidatePlaceholderSyntax(string text)
+ {
+ if (string.IsNullOrEmpty(text))
+ return;
+
+ if (text.Contains("{{", StringComparison.Ordinal)
+ || text.Contains("}}", StringComparison.Ordinal))
+ {
+ throw Oops.Bah(
+ "DOCX 占位符格式错误:检测到中文全角花括号。请使用英文半角双花括号,例如 {{employee.EmployeeName}}。");
+ }
+
+ if (text.Contains("{{{", StringComparison.Ordinal)
+ || text.Contains("}}}", StringComparison.Ordinal))
+ {
+ throw Oops.Bah(
+ "DOCX 占位符格式错误:检测到多余的花括号。占位符必须使用成对的英文半角双花括号,例如 {{employee.EmployeeName}}。");
+ }
+
+ var searchStart = 0;
+ while (searchStart < text.Length)
+ {
+ var openIndex = text.IndexOf("{{", searchStart, StringComparison.Ordinal);
+ var closeIndex = text.IndexOf("}}", searchStart, StringComparison.Ordinal);
+
+ if (closeIndex >= 0 && (openIndex < 0 || closeIndex < openIndex))
+ {
+ throw Oops.Bah(
+ $"DOCX 占位符格式错误:{BuildPlaceholderPreview(text, closeIndex)} 缺少对应的起始符“{{{{”。正确格式示例:{{{{employee.EmployeeName}}}}。");
+ }
+
+ if (openIndex < 0)
+ return;
+
+ closeIndex = text.IndexOf("}}", openIndex + 2, StringComparison.Ordinal);
+ if (closeIndex < 0)
+ {
+ throw Oops.Bah(
+ $"DOCX 占位符格式错误:{BuildPlaceholderPreview(text, openIndex)} 缺少结束符“}}}}”。正确格式示例:{{{{employee.EmployeeName}}}}。");
+ }
+
+ var placeholder = text.Substring(openIndex, closeIndex + 2 - openIndex);
+ var match = PlaceholderRegex.Match(placeholder);
+ if (!match.Success || match.Length != placeholder.Length)
+ {
+ throw Oops.Bah(
+ $"DOCX 占位符格式错误:“{placeholder}”。变量编码只能包含英文字母、数字、下划线、点和短横线,并且必须使用成对的英文半角双花括号。");
+ }
+
+ searchStart = closeIndex + 2;
+ }
+ }
+
+ private static string BuildPlaceholderPreview(string text, int markerIndex)
+ {
+ const int maxLength = 60;
+ var preview = text[markerIndex..].Trim();
+ if (preview.Length > maxLength)
+ preview = preview[..maxLength] + "…";
+ return $"“{preview}”";
+ }
+
///
/// 使用任务变量快照替换 DOCX 中的占位符,支持占位符跨多个 Word 文本节点。
///
diff --git a/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingContractTemplateController.cs b/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingContractTemplateController.cs
index d511b2e..04ebfe3 100644
--- a/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingContractTemplateController.cs
+++ b/SimpleAdmin.Web.Core/Controllers/Application/Onboarding/OnboardingContractTemplateController.cs
@@ -37,6 +37,11 @@ public class OnboardingContractTemplateController
if (!string.Equals(suffix, ".docx", StringComparison.OrdinalIgnoreCase))
throw Oops.Bah("合同模板仅支持 DOCX 文件");
+ await using var input = file.OpenReadStream();
+ using var bytes = new MemoryStream();
+ await input.CopyToAsync(bytes);
+ DocxTemplateProcessor.ExtractVariableCodes(bytes.ToArray());
+
return await _fileService.UploadFile(SysDictConst.FILE_ENGINE_ALIYUN_OSS, file);
}