longz
2026-07-08 700f9d0da314a4ea701fe9f68d305b67d72ec5fb
backend/src/main/java/com/sinata/appointment/service/MiniVisitorApplicationService.java
@@ -1,6 +1,7 @@
package com.sinata.appointment.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.sinata.appointment.constants.AppointmentFieldCodes;
import com.sinata.appointment.constants.BizErrorCode;
import com.sinata.appointment.dto.MiniVisitorCreateRequest;
@@ -46,6 +47,7 @@
                        req.getIdCard(),
                        req.getGender(),
                        requiredCodes);
        assertIdCardUniqueUnderUser(current.getId(), input.idCard(), null);
        MpVisitor row = new MpVisitor();
        row.setMpUserId(current.getId());
@@ -58,6 +60,7 @@
                        ? SensitiveDataUtil.encryptIdCard(input.idCard())
                        : "");
        row.setGender(input.gender() == null ? "" : input.gender());
        row.setIsDefault(hasAnyActiveVisitor(current.getId()) ? 0 : 1);
        mpVisitorMapper.insert(row);
    }
@@ -74,6 +77,7 @@
                        req.getIdCard(),
                        req.getGender(),
                        requiredCodes);
        assertIdCardUniqueUnderUser(current.getId(), input.idCard(), row.getId());
        row.setVisitorName(input.name() == null ? "" : input.name());
        row.setVisitorMobile(input.mobile() == null ? "" : input.mobile());
        row.setVisitorCompany(input.company() == null ? "" : input.company());
@@ -83,6 +87,31 @@
                        : "");
        row.setGender(input.gender() == null ? "" : input.gender());
        mpVisitorMapper.updateById(row);
    }
    @Transactional(rollbackFor = Exception.class)
    public void setDefault(Long id, String uuid) {
        MpUser current = miniAuthApplicationService.currentMpUser();
        MpVisitor row = loadOwnedVisitor(id, uuid, current.getId());
        mpVisitorMapper.update(
                null,
                new LambdaUpdateWrapper<MpVisitor>()
                        .eq(MpVisitor::getMpUserId, current.getId())
                        .eq(MpVisitor::getDelFlag, 0)
                        .set(MpVisitor::getIsDefault, 0));
        row.setIsDefault(1);
        mpVisitorMapper.updateById(row);
    }
    @Transactional(rollbackFor = Exception.class)
    public void delete(Long id, String uuid) {
        MpUser current = miniAuthApplicationService.currentMpUser();
        MpVisitor row = loadOwnedVisitor(id, uuid, current.getId());
        boolean wasDefault = isDefault(row);
        mpVisitorMapper.deleteById(row.getId());
        if (wasDefault) {
            resetEarliestVisitorAsDefault(current.getId());
        }
    }
    public MiniVisitorDetailVo detail(Long id, String uuid) {
@@ -99,6 +128,7 @@
                        ? SensitiveDataUtil.decryptIdCard(row.getIdCardCipher())
                        : "");
        vo.setGender(row.getGender());
        vo.setIsDefault(isDefault(row));
        return vo;
    }
@@ -110,6 +140,7 @@
                        new LambdaQueryWrapper<MpVisitor>()
                                .eq(MpVisitor::getMpUserId, current.getId())
                                .eq(MpVisitor::getDelFlag, 0)
                                .orderByDesc(MpVisitor::getIsDefault)
                                .orderByDesc(MpVisitor::getCreateTime)
                                .orderByDesc(MpVisitor::getId));
        return rows.stream().map(row -> toListVo(row, requiredCodes)).collect(Collectors.toList());
@@ -128,8 +159,36 @@
            vo.setIdCardMasked(null);
        }
        vo.setVisitorCompany(row.getVisitorCompany());
        vo.setIsDefault(isDefault(row));
        vo.setMissingFieldCodes(resolveMissingFieldCodes(row, requiredCodes));
        return vo;
    }
    private boolean isDefault(MpVisitor row) {
        return row != null && row.getIsDefault() != null && row.getIsDefault() == 1;
    }
    private boolean hasAnyActiveVisitor(Long mpUserId) {
        return mpVisitorMapper.selectCount(
                        new LambdaQueryWrapper<MpVisitor>()
                                .eq(MpVisitor::getMpUserId, mpUserId)
                                .eq(MpVisitor::getDelFlag, 0))
                > 0;
    }
    private void resetEarliestVisitorAsDefault(Long mpUserId) {
        MpVisitor next =
                mpVisitorMapper.selectOne(
                        new LambdaQueryWrapper<MpVisitor>()
                                .eq(MpVisitor::getMpUserId, mpUserId)
                                .eq(MpVisitor::getDelFlag, 0)
                                .orderByAsc(MpVisitor::getCreateTime)
                                .orderByAsc(MpVisitor::getId)
                                .last("limit 1"));
        if (next != null) {
            next.setIsDefault(1);
            mpVisitorMapper.updateById(next);
        }
    }
    private List<String> resolveMissingFieldCodes(MpVisitor row, Set<String> requiredCodes) {
@@ -221,8 +280,8 @@
        if (requiredCodes.contains(AppointmentFieldCodes.MOBILE) && !StringUtils.hasText(mobile)) {
            throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "来访人电话不能为空");
        }
        if (StringUtils.hasText(mobile) && !mobile.matches("^\\d{11}$")) {
            throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "来访人电话须为11位数字");
        if (StringUtils.hasText(mobile) && !mobile.matches("^1[3-9]\\d{9}$")) {
            throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "来访人电话格式不正确");
        }
        if (requiredCodes.contains(AppointmentFieldCodes.COMPANY) && !StringUtils.hasText(company)) {
@@ -245,6 +304,35 @@
        return new VisitorNormalizedInput(name, mobile, company, idCardPlain, genderText);
    }
    private void assertIdCardUniqueUnderUser(Long mpUserId, String idCardPlain, Long excludeVisitorId) {
        String normalizedIdCard = normalizeIdCard(idCardPlain);
        if (!StringUtils.hasText(normalizedIdCard) || mpUserId == null) {
            return;
        }
        List<MpVisitor> rows =
                mpVisitorMapper.selectList(
                        new LambdaQueryWrapper<MpVisitor>()
                                .eq(MpVisitor::getMpUserId, mpUserId)
                                .eq(MpVisitor::getDelFlag, 0));
        for (MpVisitor row : rows) {
            if (excludeVisitorId != null && excludeVisitorId.equals(row.getId())) {
                continue;
            }
            if (!StringUtils.hasText(row.getIdCardCipher())) {
                continue;
            }
            String existing = normalizeIdCard(SensitiveDataUtil.decryptIdCard(row.getIdCardCipher()));
            if (normalizedIdCard.equals(existing)) {
                throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "同一账号下身份证号码不可重复");
            }
        }
    }
    private String normalizeIdCard(String idCard) {
        String value = trimToNull(idCard);
        return value == null ? null : value.toUpperCase();
    }
    private record VisitorNormalizedInput(
            String name, String mobile, String company, String idCard, String gender) {}
}