longz
2026-06-29 9d4cbff7464ebfd7803c2ce53ded49d0f73fe011
backend/src/main/java/com/sinata/appointment/service/IntegrationHikvisionService.java
@@ -3,16 +3,26 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sinata.appointment.constants.AppointmentBizConstants;
import com.sinata.appointment.constants.BizErrorCode;
import com.sinata.appointment.config.HikvisionQrcodeProperties;
import com.sinata.appointment.entity.AppointmentOrder;
import com.sinata.appointment.entity.AppointmentVisitor;
import com.sinata.appointment.entity.MpUser;
import com.sinata.appointment.entity.MpVisitor;
import com.sinata.appointment.exception.BizException;
import com.sinata.appointment.util.MobileMaskUtil;
import com.sinata.appointment.util.SensitiveDataUtil;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -35,8 +45,35 @@
            DateTimeFormatter.ofPattern("yyMMddHHmmss.SSS");
    private final ObjectMapper objectMapper;
    private final HikvisionQrcodeProperties properties;
    public String requestQrcodePayload(
            AppointmentOrder order,
            AppointmentVisitor visitor,
            MpVisitor visitorProfile,
            MpUser visitedUser,
            String visitPurpose,
            String areas,
            LocalDateTime qrGenerateTime,
            LocalDateTime qrExpireTime,
            int validMinutes,
            String codeId,
            String visitorIdCardPlain) {
        return buildPlainQrcodePayload(
                order,
                visitor,
                visitorProfile,
                visitedUser,
                visitPurpose,
                areas,
                qrGenerateTime,
                qrExpireTime,
                validMinutes,
                codeId,
                visitorIdCardPlain);
    }
    public String buildPlainQrcodePayload(
            AppointmentOrder order,
            AppointmentVisitor visitor,
            MpVisitor visitorProfile,
@@ -79,13 +116,64 @@
                    codeId,
                    qrGenerateTime,
                    qrExpireTime,
                    json);
                    toSafePayloadLog(payload));
            return json;
        } catch (JsonProcessingException ex) {
            throw new IllegalStateException("二维码内容生成失败", ex);
        }
    }
    public String encryptQrcodePayload(String plainPayload) {
        if (!StringUtils.hasText(plainPayload) || !plainPayload.trim().startsWith("{")) {
            return plainPayload;
        }
        if (!properties.isEncryptEnabled()) {
            return plainPayload;
        }
        if (!isValidAesConfig()) {
            throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "二维码加密配置无效");
        }
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKeySpec keySpec =
                    new SecretKeySpec(properties.getAesKey().getBytes(StandardCharsets.UTF_8), "AES");
            IvParameterSpec ivSpec =
                    new IvParameterSpec(properties.getAesIv().getBytes(StandardCharsets.UTF_8));
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
            byte[] encrypted = cipher.doFinal(plainPayload.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(encrypted);
        } catch (BizException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "二维码加密配置无效");
        }
    }
    private boolean isValidAesConfig() {
        return properties.getAesKey() != null
                && properties.getAesIv() != null
                && properties.getAesKey().getBytes(StandardCharsets.UTF_8).length == 16
                && properties.getAesIv().getBytes(StandardCharsets.UTF_8).length == 16;
    }
    private Map<String, Object> toSafePayloadLog(Map<String, Object> payload) {
        Map<String, Object> safe = new LinkedHashMap<>(payload);
        safe.put("visitorIdCard", maskIdCardForLog(safe.get("visitorIdCard")));
        safe.put("visitorPhone", maskMobileForLog(safe.get("visitorPhone")));
        safe.put("beVisitorPhone", maskMobileForLog(safe.get("beVisitorPhone")));
        return safe;
    }
    private String maskIdCardForLog(Object value) {
        String text = value == null ? "" : String.valueOf(value).trim();
        return StringUtils.hasText(text) ? SensitiveDataUtil.maskIdCard(text) : EMPTY;
    }
    private String maskMobileForLog(Object value) {
        String text = value == null ? "" : String.valueOf(value).trim();
        return StringUtils.hasText(text) ? MobileMaskUtil.mask(text) : EMPTY;
    }
    private LocalDateTime resolveVisitStartTime(AppointmentOrder order) {
        LocalDate visitDate = firstNonNull(order.getVisitStartDate(), order.getVisitEndDate(), LocalDate.now());
        if (!AppointmentBizConstants.TIME_MODE_MULTI.equalsIgnoreCase(trimToEmpty(order.getVisitTimeMode()))) {