package com.ruoyi.user.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.ruoyi.common.core.exception.ServiceException; import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.user.domain.AccountCertification; import com.ruoyi.user.domain.AccountCertificationApply; import com.ruoyi.user.domain.AccountUser; import com.ruoyi.user.domain.dto.CompanyCertificationApplyDto; import com.ruoyi.user.domain.vo.AccountCertificationApplyDetailVo; import com.ruoyi.user.domain.vo.AccountCertificationApplyVo; import com.ruoyi.user.domain.vo.AccountCertificationStatusVo; import com.ruoyi.user.domain.vo.CompanyCertificationStatusVo; import com.ruoyi.user.mapper.AccountCertificationApplyMapper; import com.ruoyi.user.mapper.AccountCertificationMapper; import com.ruoyi.user.mapper.AccountUserMapper; import com.ruoyi.user.service.IAccountCertificationApplyService; /** * 会员主体认证申请 业务层实现 * * @author ruoyi */ @Service public class AccountCertificationApplyServiceImpl implements IAccountCertificationApplyService { /** 审核结果:通过 */ private static final String AUDIT_PASS = "1"; /** 审核结果:驳回 */ private static final String AUDIT_REJECT = "2"; /** 审核状态:待审核(提交后的初始状态) */ private static final String AUDIT_PENDING = "0"; /** account_user.auth_status:审核中 */ private static final String AUTH_AUDITING = "1"; /** account_user.auth_status:已认证 */ private static final String AUTH_VERIFIED = "2"; /** account_user.auth_status:认证失败 */ private static final String AUTH_FAILED = "3"; /** 主体类型:个人(C端实名认证固定为该类型) */ private static final String SUBJECT_PERSONAL = "1"; /** 主体类型:个体工商户 */ private static final String SUBJECT_INDIVIDUAL = "2"; /** 主体类型:企业 */ private static final String SUBJECT_COMPANY = "3"; /** 有效期类型:长期(此时起止日期可空) */ private static final String VALID_TYPE_LONG_TERM = "1"; /** 有效期类型:短期(此时起止日期必填) */ private static final String VALID_TYPE_SHORT_TERM = "2"; @Autowired private AccountCertificationApplyMapper certificationApplyMapper; @Autowired private AccountCertificationMapper certificationMapper; @Autowired private AccountUserMapper accountUserMapper; @Override public List selectCertificationApplyList(AccountCertificationApplyVo apply) { return certificationApplyMapper.selectCertificationApplyList(apply); } @Override public AccountCertificationApplyDetailVo selectCertificationApplyDetail(Long id) { AccountCertificationApply apply = certificationApplyMapper.selectCertificationApplyById(id); if (StringUtils.isNull(apply)) { return null; } AccountCertificationApplyDetailVo detail = new AccountCertificationApplyDetailVo(); detail.setApply(apply); // 用户信息/关系取 account_user 的实时值而非申请快照:审核要看会员当下的系统等级与 // 推荐/供货关系,这两项申请快照里根本没有。 detail.setUser(accountUserMapper.selectAccountUserVoById(apply.getUserId())); return detail; } @Override @Transactional public int auditCertificationApply(Long id, String auditStatus, String auditRemark) { boolean pass = AUDIT_PASS.equals(auditStatus); if (!pass && !AUDIT_REJECT.equals(auditStatus)) { throw new ServiceException("审核结果只能是「通过」或「驳回」"); } // 驳回不写原因,会员端只看到「认证失败」无从修改,等于把人卡死 if (!pass && StringUtils.isEmpty(auditRemark)) { throw new ServiceException("驳回时必须填写审核意见"); } AccountCertificationApply apply = certificationApplyMapper.selectCertificationApplyById(id); if (StringUtils.isNull(apply)) { throw new ServiceException("认证申请不存在"); } String operator = SecurityUtils.getUsername(); AccountCertificationApply update = new AccountCertificationApply(); update.setId(id); update.setAuditStatus(auditStatus); update.setAuditBy(operator); update.setAuditRemark(auditRemark); update.setUpdateBy(operator); // 更新条件带 audit_status='0',重复审核(或两人同时点)会更新 0 行,据此抛异常回滚, // 避免下面几步用陈旧状态把 auth_status 改错。 if (certificationApplyMapper.auditCertificationApply(update) == 0) { throw new ServiceException("该申请已审核,不能重复处理"); } // 同步认证状态到 account_user:个人写 auth_status,企业/个体写 company_auth_status,两列互不覆盖。 AccountUser user = new AccountUser(); user.setUserId(apply.getUserId()); user.setUpdateBy(operator); if (isCompanySubject(apply.getSubjectType())) { user.setCompanyAuthStatus(pass ? AUTH_VERIFIED : AUTH_FAILED); // 审核通过时才把主体类型落为 2/3;驳回不动,避免未通过审核就被标成企业主体 if (pass) { user.setSubjectType(apply.getSubjectType()); } accountUserMapper.updateUserCompanyAuthStatus(user); } else { user.setAuthStatus(pass ? AUTH_VERIFIED : AUTH_FAILED); accountUserMapper.updateUserAuthStatus(user); } if (pass) { saveCertificationSnapshot(apply, operator); } return 1; } @Override @Transactional public void submitPersonalCertification(Long userId, String realName, String idCard) { // STEP 1: 校验会员ID(登录态缺失时直接失败,避免无归属写入) if (StringUtils.isNull(userId)) { throw new ServiceException("提交实名认证失败"); } // STEP 2: 姓名/身份证号二次校验:Controller 已用 @Validated 拦过一遍, // 这里是服务层兜底,防止绕过 Controller 直接调用服务时落脏数据 if (StringUtils.isEmpty(realName) || StringUtils.isEmpty(idCard)) { throw new ServiceException("姓名与身份证号不能为空"); } // STEP 3: 防重闸门——该会员已有待审核申请时拒绝,避免后台审核列表堆积重复单 if (certificationApplyMapper.countPendingPersonalApplyByUserId(userId) > 0) { throw new ServiceException("已有实名认证申请正在审核中,请勿重复提交"); } // STEP 4: 组装申请快照并落库(主体类型固定「个人」;审核状态置「待审核」; // 身份证号统一转大写,兼容末位小写 x) String operator = SecurityUtils.getUsername(); AccountCertificationApply apply = new AccountCertificationApply(); apply.setUserId(userId); apply.setSubjectType(SUBJECT_PERSONAL); apply.setRealName(realName.trim()); apply.setIdCard(idCard.trim().toUpperCase()); apply.setAuditStatus(AUDIT_PENDING); apply.setCreateBy(operator); if (certificationApplyMapper.insertCertificationApply(apply) <= 0) { throw new ServiceException("提交实名认证失败,请稍后重试"); } // STEP 5: 联动 account_user.auth_status 为「审核中」,个人中心与会员列表据此显示 AccountUser user = new AccountUser(); user.setUserId(userId); user.setAuthStatus(AUTH_AUDITING); user.setUpdateBy(operator); accountUserMapper.updateUserAuthStatus(user); } @Override public AccountCertificationStatusVo getCertificationStatus(Long userId) { AccountCertificationStatusVo vo = new AccountCertificationStatusVo(); // 登录态缺失视为从未提交:返回空对象,前端按「未认证」渲染空表单(不返回 null) if (StringUtils.isNull(userId)) { return vo; } AccountCertificationApply apply = certificationApplyMapper.selectLatestPersonalApplyByUserId(userId); if (StringUtils.isNull(apply)) { return vo; } // 回填最近一次申请的资料与状态:认证失败时前端据 auditRemark 展示驳回原因 vo.setRealName(apply.getRealName()); vo.setIdCard(apply.getIdCard()); vo.setAuditStatus(apply.getAuditStatus()); vo.setAuditRemark(apply.getAuditRemark()); return vo; } @Override @Transactional public void submitCompanyCertification(Long userId, CompanyCertificationApplyDto dto) { // STEP 1: 校验会员ID(登录态缺失时直接失败,避免无归属写入) if (StringUtils.isNull(userId)) { throw new ServiceException("提交企业认证失败"); } // STEP 2: 入参兜底:Controller 已用 @Validated 拦过,这里防止绕过 Controller 直接落脏数据 if (StringUtils.isNull(dto)) { throw new ServiceException("认证资料不能为空"); } // STEP 3: 有效期必填兜底——「短期」必须填起止日期,「长期」允许为空(前端此时隐藏该输入) checkValidPeriod(dto.getLicenseValidType(), dto.getLicenseValidPeriod(), "营业执照"); checkValidPeriod(dto.getLegalIdValidType(), dto.getLegalIdValidPeriod(), "法人身份证"); // STEP 4: 防重闸门——该会员已有待审核的企业申请时拒绝,避免后台审核列表堆积重复单 if (certificationApplyMapper.countPendingCompanyApplyByUserId(userId) > 0) { throw new ServiceException("已有企业认证申请正在审核中,请勿重复提交"); } // STEP 5: 组装申请快照(主体类型取入参 2/3;审核状态置「待审核」) String operator = SecurityUtils.getUsername(); AccountCertificationApply apply = new AccountCertificationApply(); apply.setUserId(userId); apply.setSubjectType(dto.getSubjectType()); apply.setCompanyName(dto.getCompanyName().trim()); apply.setLicenseImage(dto.getLicenseImage()); apply.setCreditCode(dto.getCreditCode().trim()); apply.setLicenseValidType(dto.getLicenseValidType()); apply.setLicenseValidPeriod(dto.getLicenseValidPeriod()); apply.setRegisteredRegion(dto.getRegisteredRegion()); apply.setCompanyAddress(dto.getCompanyAddress()); apply.setLegalPerson(dto.getLegalPerson().trim()); apply.setLegalIdCardFront(dto.getLegalIdCardFront()); apply.setLegalIdCardBack(dto.getLegalIdCardBack()); apply.setLegalIdValidType(dto.getLegalIdValidType()); apply.setLegalIdValidPeriod(dto.getLegalIdValidPeriod()); apply.setLegalIdAddress(dto.getLegalIdAddress()); apply.setContactName(dto.getContactName().trim()); apply.setContactPhone(dto.getContactPhone().trim()); apply.setContactEmail(dto.getContactEmail().trim()); apply.setPayeeType(dto.getPayeeType()); apply.setBankCardImage(dto.getBankCardImage()); apply.setBankCardNo(dto.getBankCardNo().trim()); apply.setBankCity(dto.getBankCity()); apply.setAuditStatus(AUDIT_PENDING); apply.setCreateBy(operator); // STEP 6: 落库 if (certificationApplyMapper.insertCertificationApply(apply) <= 0) { throw new ServiceException("提交企业认证失败,请稍后重试"); } // STEP 7: 联动 account_user.company_auth_status 为「审核中」(与个人 auth_status 相互独立) AccountUser user = new AccountUser(); user.setUserId(userId); user.setCompanyAuthStatus(AUTH_AUDITING); user.setUpdateBy(operator); accountUserMapper.updateUserCompanyAuthStatus(user); } @Override public CompanyCertificationStatusVo getCompanyCertificationStatus(Long userId) { CompanyCertificationStatusVo vo = new CompanyCertificationStatusVo(); // 登录态缺失视为从未提交:返回空对象,前端按「未认证」渲染空表单(不返回 null) if (StringUtils.isNull(userId)) { return vo; } AccountCertificationApply apply = certificationApplyMapper.selectLatestCompanyApplyByUserId(userId); if (StringUtils.isNull(apply)) { return vo; } // 一次性回填整份资料与审核状态:审核中进占位页、已驳回展示原因并允许修改重提 vo.setSubjectType(apply.getSubjectType()); vo.setCompanyName(apply.getCompanyName()); vo.setLicenseImage(apply.getLicenseImage()); vo.setCreditCode(apply.getCreditCode()); vo.setLicenseValidType(apply.getLicenseValidType()); vo.setLicenseValidPeriod(apply.getLicenseValidPeriod()); vo.setRegisteredRegion(apply.getRegisteredRegion()); vo.setCompanyAddress(apply.getCompanyAddress()); vo.setLegalPerson(apply.getLegalPerson()); vo.setLegalIdCardFront(apply.getLegalIdCardFront()); vo.setLegalIdCardBack(apply.getLegalIdCardBack()); vo.setLegalIdValidType(apply.getLegalIdValidType()); vo.setLegalIdValidPeriod(apply.getLegalIdValidPeriod()); vo.setLegalIdAddress(apply.getLegalIdAddress()); vo.setContactName(apply.getContactName()); vo.setContactPhone(apply.getContactPhone()); vo.setContactEmail(apply.getContactEmail()); vo.setPayeeType(apply.getPayeeType()); vo.setBankCardImage(apply.getBankCardImage()); vo.setBankCardNo(apply.getBankCardNo()); vo.setBankCity(apply.getBankCity()); vo.setAuditStatus(apply.getAuditStatus()); vo.setAuditRemark(apply.getAuditRemark()); return vo; } /** * 判定主体类型是否为企业/个体工商户(subject_type ∈ {2,3})。 */ private boolean isCompanySubject(String subjectType) { return SUBJECT_INDIVIDUAL.equals(subjectType) || SUBJECT_COMPANY.equals(subjectType); } /** * 有效期必填兜底:类型为「短期」时,起止日期不得为空。 * * @param validType 有效期类型(1长期 2短期) * @param validPeriod 有效期起止(文本) * @param label 字段中文名,用于拼接错误提示 */ private void checkValidPeriod(String validType, String validPeriod, String label) { if (VALID_TYPE_SHORT_TERM.equals(validType) && StringUtils.isEmpty(validPeriod)) { throw new ServiceException("请选择" + label + "有效期起止日期"); } } /** * 审核通过后,把申请快照落成 account_certification 的当前有效资料 * * 详情页「主体资料」读的是 account_certification,不落这一步会出现 * "审核已通过但详情页主体资料仍为空"的割裂。 */ private void saveCertificationSnapshot(AccountCertificationApply apply, String operator) { AccountCertification old = certificationMapper.selectCertificationByUserId(apply.getUserId()); if (StringUtils.isNotNull(old)) { // 旧行逻辑删除后新插一行,而不是原地 update:updateCertification 的 会跳过 // 空值列,个人主体的企业字段(company_name 等为 null)会残留上一份企业资料的值。 // 表上刻意没建 UNIQUE(user_id),本就允许留历史行。 certificationMapper.deleteCertificationByIds(new Long[] { old.getId() }); } AccountCertification certification = new AccountCertification(); certification.setUserId(apply.getUserId()); certification.setRealName(apply.getRealName()); certification.setIdCard(apply.getIdCard()); certification.setCompanyName(apply.getCompanyName()); certification.setCreditCode(apply.getCreditCode()); certification.setLegalPerson(apply.getLegalPerson()); certification.setRegisteredRegion(apply.getRegisteredRegion()); certification.setCompanyAddress(apply.getCompanyAddress()); certification.setRegisterDate(apply.getRegisterDate()); certification.setLicenseValidPeriod(apply.getLicenseValidPeriod()); certification.setLicenseImage(apply.getLicenseImage()); certification.setLicenseValidType(apply.getLicenseValidType()); certification.setLegalIdCardFront(apply.getLegalIdCardFront()); certification.setLegalIdCardBack(apply.getLegalIdCardBack()); certification.setLegalIdValidType(apply.getLegalIdValidType()); certification.setLegalIdValidPeriod(apply.getLegalIdValidPeriod()); certification.setLegalIdAddress(apply.getLegalIdAddress()); certification.setContactName(apply.getContactName()); certification.setContactPhone(apply.getContactPhone()); certification.setContactEmail(apply.getContactEmail()); certification.setPayeeType(apply.getPayeeType()); certification.setBankCardImage(apply.getBankCardImage()); certification.setBankCardNo(apply.getBankCardNo()); certification.setBankCity(apply.getBankCity()); certification.setCreateBy(operator); certification.setRemark("由认证申请(申请号 " + apply.getId() + ")审核通过生成"); certificationMapper.insertCertification(certification); } }