diff --git a/safety-eval-app/src/main/java/org/qinan/safetyeval/app/support/OrgPersonnelExcelImporter.java b/safety-eval-app/src/main/java/org/qinan/safetyeval/app/support/OrgPersonnelExcelImporter.java index f6bea737..849a3629 100644 --- a/safety-eval-app/src/main/java/org/qinan/safetyeval/app/support/OrgPersonnelExcelImporter.java +++ b/safety-eval-app/src/main/java/org/qinan/safetyeval/app/support/OrgPersonnelExcelImporter.java @@ -1,9 +1,12 @@ package org.qinan.safetyeval.app.support; import cn.hutool.core.lang.Pair; +import cn.hutool.core.util.IdcardUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.validation.ValidationUtil; import com.alibaba.excel.EasyExcel; import com.jjb.saas.framework.auth.utils.AuthContext; +import com.jjb.saas.framework.core.utils.ValidatorUtils; import com.jjb.saas.system.client.user.request.RoleDeptAddCmd; import com.jjb.saas.system.client.user.request.UserAddCmd; import com.zcloud.gbscommon.utils.MD5; @@ -31,6 +34,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import org.springframework.validation.ValidationUtils; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; @@ -57,6 +61,9 @@ public class OrgPersonnelExcelImporter { private static final DateTimeFormatter SLASH_DATE_FORMATTER = new DateTimeFormatterBuilder().appendPattern("yyyy/M/d").toFormatter(); + /** 手机号格式:11位纯数字、1开头 */ + private static final String MOBILE_PHONE_REGEX = "^1\\d{10}$"; + @Resource private OrgDepartmentGateway orgDepartmentGateway; @@ -220,11 +227,12 @@ public class OrgPersonnelExcelImporter { } row.setRowIndex(i + 2); - // 文件内手机号重复:剔除该行并记录提示(空账号不参与去重) - String account = trim(row.getAccount()); - if (StringUtils.hasText(account) && !seenAccounts.add(account)) { - errors.add(new OrgPersonnelImportErrorCO(row.getRowIndex(), account, trim(row.getUserName()), - "账号在文件内重复,已剔除该行")); + // 账号(手机号)校验:必填、格式、文件内去重 + if (!validateAccount(row, seenAccounts, errors)) { + continue; + } + // 字段级校验:必填、长度、格式 + if (!validateRowFields(row, errors)) { continue; } rows.add(row); @@ -232,6 +240,154 @@ public class OrgPersonnelExcelImporter { return rows; } + /** + * 校验账号(手机号):必填、格式、文件内去重。 + * + * @param row 当前行 + * @param seenAccounts 已出现的账号集合(用于文件内去重) + * @param errors 异常收集列表 + * @return true 通过,false 失败(已记录异常) + */ + private boolean validateAccount(OrgPersonnelImportRow row, Set seenAccounts, + List errors) { + Integer rowIndex = row.getRowIndex(); + String account = trim(row.getAccount()); + String userName = trim(row.getUserName()); + + // 账号必填(DB: account NOT NULL) + if (!StringUtils.hasText(account)) { + errors.add(new OrgPersonnelImportErrorCO(rowIndex, account, userName, "账号不能为空")); + return false; + } + // 手机号格式校验:11位纯数字、1开头 + if (!account.matches(MOBILE_PHONE_REGEX)) { + errors.add(new OrgPersonnelImportErrorCO(rowIndex, account, userName, + "手机号格式不正确,应为11位数字且以1开头")); + return false; + } + // 文件内去重 + if (!seenAccounts.add(account)) { + errors.add(new OrgPersonnelImportErrorCO(rowIndex, account, userName, + "账号在文件内重复,已剔除该行")); + return false; + } + return true; + } + + /** + * 校验行数据的各字段:必填、长度、格式/类型。 + *

+ * 覆盖直接存入数据库的字段,校验规则依据 {@code org_personnel} 表结构: + *

+ * + * @param row 当前行 + * @param errors 异常收集列表 + * @return true 通过,false 失败(已记录异常) + */ + private boolean validateRowFields(OrgPersonnelImportRow row, List errors) { + Integer rowIndex = row.getRowIndex(); + String account = trim(row.getAccount()); + String userName = trim(row.getUserName()); + + // 姓名:必填(DB: user_name varchar(50) NOT NULL) + if (!StringUtils.hasText(userName)) { + errors.add(new OrgPersonnelImportErrorCO(rowIndex, account, userName, "姓名不能为空")); + return false; + } + if (userName.length() > 50) { + errors.add(new OrgPersonnelImportErrorCO(rowIndex, account, userName, "姓名长度不能超过50个字符")); + return false; + } + + // 身份证号:可选,格式校验(DB: id_card_no varchar(18)) + String idCardNo = trim(row.getIdCardNo()); + if (StringUtils.hasText(idCardNo) && !IdcardUtil.isValidCard(idCardNo)) { + errors.add(new OrgPersonnelImportErrorCO(rowIndex, account, userName, + "身份证号格式不正确,应为15或18位有效身份证号")); + return false; + } + + // 性别名称:最大10(DB: gender_name varchar(10)) + String gender = trim(row.getGender()); + if (StringUtils.hasText(gender) && gender.length() > 10) { + errors.add(new OrgPersonnelImportErrorCO(rowIndex, account, userName, "性别长度不能超过10个字符")); + return false; + } + + // 出生日期:日期格式校验(DB: birth_date date) + String birthDate = trim(row.getBirthDate()); + if (StringUtils.hasText(birthDate) && parseDate(birthDate) == null) { + errors.add(new OrgPersonnelImportErrorCO(rowIndex, account, userName, + "出生日期格式不正确,应为yyyy-MM-dd或yyyy/M/d")); + return false; + } + + // 参加工作时间:日期格式校验(DB: join_work_date date) + String joinWorkDate = trim(row.getJoinWorkDate()); + if (StringUtils.hasText(joinWorkDate) && parseDate(joinWorkDate) == null) { + errors.add(new OrgPersonnelImportErrorCO(rowIndex, account, userName, + "参加工作时间格式不正确,应为yyyy-MM-dd或yyyy/M/d")); + return false; + } + + // 学历名称:最大50(DB: education_name varchar(50)) + String education = trim(row.getEducation()); + if (StringUtils.hasText(education) && education.length() > 50) { + errors.add(new OrgPersonnelImportErrorCO(rowIndex, account, userName, "学历长度不能超过50个字符")); + return false; + } + + // 毕业院校:最大200(DB: graduate_school varchar(200)) + String graduateSchool = trim(row.getGraduateSchool()); + if (StringUtils.hasText(graduateSchool) && graduateSchool.length() > 200) { + errors.add(new OrgPersonnelImportErrorCO(rowIndex, account, userName, "毕业院校长度不能超过200个字符")); + return false; + } + + // 所学专业:最大100(DB: major varchar(100)) + String major = trim(row.getMajor()); + if (StringUtils.hasText(major) && major.length() > 100) { + errors.add(new OrgPersonnelImportErrorCO(rowIndex, account, userName, "所学专业长度不能超过100个字符")); + return false; + } + + // 现住地址:最大500(DB: current_address varchar(500)) + String currentAddress = trim(row.getCurrentAddress()); + if (StringUtils.hasText(currentAddress) && currentAddress.length() > 500) { + errors.add(new OrgPersonnelImportErrorCO(rowIndex, account, userName, "现住地址长度不能超过500个字符")); + return false; + } + + // 办公地址:最大500(DB: office_address varchar(500)) + String officeAddress = trim(row.getOfficeAddress()); + if (StringUtils.hasText(officeAddress) && officeAddress.length() > 500) { + errors.add(new OrgPersonnelImportErrorCO(rowIndex, account, userName, "办公地址长度不能超过500个字符")); + return false; + } + + // 出版学术专著等:最大1000(DB: publications varchar(1000)) + String publications = trim(row.getPublications()); + if (StringUtils.hasText(publications) && publications.length() > 1000) { + errors.add(new OrgPersonnelImportErrorCO(rowIndex, account, userName, + "出版学术专著等信息长度不能超过1000个字符")); + return false; + } + + // 自我申报专业能力:最大1000(DB: ability_declaration varchar(1000)) + String abilityDeclaration = trim(row.getAbilityDeclaration()); + if (StringUtils.hasText(abilityDeclaration) && abilityDeclaration.length() > 1000) { + errors.add(new OrgPersonnelImportErrorCO(rowIndex, account, userName, + "自我申报的专业能力信息长度不能超过1000个字符")); + return false; + } + + return true; + } + /** * 批量校验部门层级是否在当前机构下存在且层级关系正确。 *