namespace SimpleAdmin.Application; /// /// 统一校验并规范化员工端固定入职信息。 /// internal static class OnboardingEmployeeInfoValidator { private static readonly int[] IdCardWeights = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 }; private static readonly char[] IdCardCheckCodes = { '1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2' }; /// /// 规范化文本,并按照任务冻结配置校验固定登记表必填项及字段格式。 /// public static void ValidateAndNormalize( ObTaskEmployeeInfo input, IReadOnlyCollection fieldRequirements = null) { input.EmployeeName = input.EmployeeName?.Trim(); input.Phone = input.Phone?.Trim(); input.Email = input.Email?.Trim(); input.IdCardNumber = input.IdCardNumber?.Trim().ToUpperInvariant(); input.BankCardNumber = global::System.Text.RegularExpressions.Regex.Replace( input.BankCardNumber ?? string.Empty, @"[\s-]", string.Empty); input.BankName = input.BankName?.Trim(); input.BankAccountName = input.EmployeeName; ValidateRequiredFields(input, fieldRequirements); // 电子签约实名认证始终依赖以下三项,不能通过后台配置关闭。 if (string.IsNullOrWhiteSpace(input.EmployeeName) || input.EmployeeName.Length > 100) throw Oops.Bah("员工姓名不能为空且不能超过 100 个字符"); if (!string.IsNullOrWhiteSpace(input.Gender) && input.Gender != "男" && input.Gender != "女") { throw Oops.Bah("请选择正确的性别"); } if (!global::System.Text.RegularExpressions.Regex.IsMatch( input.Phone ?? string.Empty, @"^1[3-9]\d{9}$")) { throw Oops.Bah("请输入正确的 11 位手机号码"); } if (!TryValidateIdCard(input.IdCardNumber, out var idCardBirthday, out var idCardError)) throw Oops.Bah(idCardError); ValidateBirthday(input.Birthday, idCardBirthday); ValidateOptionalEmail(input.Email); ValidateOptionalNumber(input.Height, "身高", 100M, 250M, "厘米"); ValidateOptionalNumber(input.Weight, "体重", 30M, 300M, "公斤"); if (!string.IsNullOrWhiteSpace(input.BankCardNumber)) ValidateBankCard(input.BankCardNumber); if (!string.IsNullOrWhiteSpace(input.BankName) && input.BankName.Length > 100) throw Oops.Bah("开户银行名称不能超过 100 个字符"); ValidateOptionalContactPhone(input.EmergencyPhone); ValidateEducationHistory(input.EducationHistoryJson); ValidateWorkHistory(input.WorkHistoryJson); ValidateFamilyMembers(input.FamilyMembersJson); ValidateJson(input.CustomVariablesJson, global::System.Text.Json.JsonValueKind.Object, "员工补充变量"); } /// /// 按任务快照校验后台配置为必填的内置员工字段。 /// private static void ValidateRequiredFields( ObTaskEmployeeInfo input, IReadOnlyCollection fieldRequirements) { if (fieldRequirements == null || fieldRequirements.Count == 0) return; foreach (var requirement in fieldRequirements.Where(item => item.Required && OnboardingEmployeeFieldRequirementSnapshot.IsEmployeeField(item))) { var separatorIndex = requirement.FieldCode.LastIndexOf('.'); var propertyName = separatorIndex >= 0 ? requirement.FieldCode[(separatorIndex + 1)..] : requirement.FieldCode; var property = typeof(ObTaskEmployeeInfo).GetProperty( propertyName, global::System.Reflection.BindingFlags.Instance | global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.IgnoreCase); if (property == null) { throw Oops.Bah( $"内置员工变量“{requirement.FieldCode}”没有对应的入职登记表字段,请联系管理员"); } var value = property.GetValue(input)?.ToString(); if (string.IsNullOrWhiteSpace(value)) throw Oops.Bah($"请填写入职登记表“{requirement.FieldName}”"); } } private static bool TryValidateIdCard( string idCardNumber, out DateTime birthday, out string errorMessage) { birthday = default; errorMessage = null; var structureIsValid = global::System.Text.RegularExpressions.Regex.IsMatch( idCardNumber ?? string.Empty, "^\\d{17}[0-9X]$"); if (!structureIsValid) { errorMessage = "请输入正确的 18 位身份证号码"; return false; } var birthdayText = idCardNumber.Substring(6, 8); var birthdayIsValid = DateTime.TryParseExact( birthdayText, "yyyyMMdd", global::System.Globalization.CultureInfo.InvariantCulture, global::System.Globalization.DateTimeStyles.None, out birthday); if (!birthdayIsValid || birthday.Date > DateTime.Today) { errorMessage = "身份证号码中的出生日期不正确"; return false; } var total = 0; for (var index = 0; index < IdCardWeights.Length; index++) { total += (idCardNumber[index] - '0') * IdCardWeights[index]; } if (IdCardCheckCodes[total % 11] == idCardNumber[17]) return true; errorMessage = "身份证号码校验位不正确"; return false; } private static void ValidateBirthday(string birthdayText, DateTime? idCardBirthday) { if (string.IsNullOrWhiteSpace(birthdayText)) return; var birthdayIsValid = DateTime.TryParseExact( birthdayText.Trim(), "yyyy-MM-dd", global::System.Globalization.CultureInfo.InvariantCulture, global::System.Globalization.DateTimeStyles.None, out var birthday); if (!birthdayIsValid || birthday.Date > DateTime.Today) throw Oops.Bah("请输入正确的出生日期"); if (idCardBirthday.HasValue && birthday.Date != idCardBirthday.Value.Date) throw Oops.Bah("出生日期与身份证号码不一致"); } private static void ValidateOptionalEmail(string email) { if (string.IsNullOrWhiteSpace(email)) return; var emailIsValid = email.Length <= 100 && global::System.Net.Mail.MailAddress.TryCreate(email, out _); if (!emailIsValid) throw Oops.Bah("请输入正确的电子邮箱"); } private static void ValidateOptionalNumber( string value, string fieldName, decimal min, decimal max, string unit) { if (string.IsNullOrWhiteSpace(value)) return; var numberIsValid = decimal.TryParse( value.Trim(), global::System.Globalization.NumberStyles.Number, global::System.Globalization.CultureInfo.InvariantCulture, out var number); if (!numberIsValid || number < min || number > max) { throw Oops.Bah($"{fieldName}应为 {min:0} 至 {max:0} {unit}之间的数字"); } } private static void ValidateBankCard(string bankCardNumber) { var bankCardIsValid = global::System.Text.RegularExpressions.Regex.IsMatch( bankCardNumber ?? string.Empty, "^\\d{12,19}$"); if (!bankCardIsValid) throw Oops.Bah("请输入 12 至 19 位银行卡号"); } private static void ValidateOptionalContactPhone(string phone) { if (string.IsNullOrWhiteSpace(phone)) return; var contactPhoneIsValid = global::System.Text.RegularExpressions.Regex.IsMatch( phone.Trim(), "^(1[3-9]\\d{9}|0\\d{2,3}-?\\d{7,8})$"); if (!contactPhoneIsValid) throw Oops.Bah("请输入正确的紧急联系人手机号码或座机号码"); } private static void ValidateEducationHistory(string json) { ValidateHistoryRows(json, "教育经历", (row, rowNumber) => { GetRequiredRowValue(row, "timeRange", rowNumber, "教育经历", "时间范围"); GetRequiredRowValue(row, "location", rowNumber, "教育经历", "学校或机构"); GetRequiredRowValue(row, "content", rowNumber, "教育经历", "专业或学习内容"); GetRequiredRowValue(row, "result", rowNumber, "教育经历", "结果或学位"); }); } private static void ValidateWorkHistory(string json) { ValidateHistoryRows(json, "工作经历", (row, rowNumber) => { GetRequiredRowValue(row, "timeRange", rowNumber, "工作经历", "起止时间"); GetRequiredRowValue(row, "companyName", rowNumber, "工作经历", "工作单位"); GetRequiredRowValue(row, "position", rowNumber, "工作经历", "担任职务"); GetRequiredRowValue(row, "leaveReason", rowNumber, "工作经历", "离职原因"); }); } private static void ValidateFamilyMembers(string json) { ValidateHistoryRows(json, "家庭成员", (row, rowNumber) => { GetRequiredRowValue(row, "name", rowNumber, "家庭成员", "姓名"); GetRequiredRowValue(row, "relationship", rowNumber, "家庭成员", "关系"); var ageText = GetRequiredRowValue(row, "age", rowNumber, "家庭成员", "年龄"); var phone = GetRequiredRowValue(row, "phone", rowNumber, "家庭成员", "联系电话"); var ageIsValid = int.TryParse(ageText, out var age) && age >= 0 && age <= 120; if (!ageIsValid) throw Oops.Bah($"第 {rowNumber} 条家庭成员的年龄应为 0 至 120 之间的整数"); var phoneIsValid = global::System.Text.RegularExpressions.Regex.IsMatch( phone, "^(1[3-9]\\d{9}|0\\d{2,3}-?\\d{7,8})$"); if (!phoneIsValid) throw Oops.Bah($"第 {rowNumber} 条家庭成员的联系电话格式不正确"); }); } private static void ValidateHistoryRows( string json, string fieldName, Action validateRow) { if (string.IsNullOrWhiteSpace(json)) return; try { using var document = global::System.Text.Json.JsonDocument.Parse(json); if (document.RootElement.ValueKind != global::System.Text.Json.JsonValueKind.Array) throw new global::System.Text.Json.JsonException(); var rowNumber = 0; foreach (var row in document.RootElement.EnumerateArray()) { rowNumber++; if (row.ValueKind != global::System.Text.Json.JsonValueKind.Object) throw Oops.Bah($"第 {rowNumber} 条{fieldName}格式不正确,请删除后重新添加"); validateRow(row, rowNumber); } } catch (global::System.Text.Json.JsonException) { throw Oops.Bah($"{fieldName}格式不正确,请刷新页面后重新填写"); } } private static string GetRequiredRowValue( global::System.Text.Json.JsonElement row, string propertyName, int rowNumber, string fieldName, string displayName) { if (!row.TryGetProperty(propertyName, out var property) || property.ValueKind is global::System.Text.Json.JsonValueKind.Null or global::System.Text.Json.JsonValueKind.Undefined) { throw Oops.Bah($"第 {rowNumber} 条{fieldName}请填写{displayName}"); } var value = property.ValueKind == global::System.Text.Json.JsonValueKind.String ? property.GetString() : property.ToString(); if (string.IsNullOrWhiteSpace(value)) throw Oops.Bah($"第 {rowNumber} 条{fieldName}请填写{displayName}"); return value.Trim(); } private static void ValidateJson( string json, global::System.Text.Json.JsonValueKind expectedKind, string fieldName) { if (string.IsNullOrWhiteSpace(json)) return; try { using var document = global::System.Text.Json.JsonDocument.Parse(json); if (document.RootElement.ValueKind != expectedKind) throw new global::System.Text.Json.JsonException(); } catch (global::System.Text.Json.JsonException) { throw Oops.Bah($"{fieldName}格式不正确,请刷新页面后重新填写"); } } }