From 2a3c3cdbf2398695d61633e072f98ee2757e9e27 Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期二, 11 八月 2026 11:20:26 +0800
Subject: [PATCH] feat(appointment): 添加访客预约系统解除绑定功能
---
backend/src/main/java/com/sinata/appointment/service/IntegrationHikvisionService.java | 102 +++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 95 insertions(+), 7 deletions(-)
diff --git a/backend/src/main/java/com/sinata/appointment/service/IntegrationHikvisionService.java b/backend/src/main/java/com/sinata/appointment/service/IntegrationHikvisionService.java
index 39f7c84..379fed5 100644
--- a/backend/src/main/java/com/sinata/appointment/service/IntegrationHikvisionService.java
+++ b/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,
@@ -55,10 +92,7 @@
payload.put("visitorIdCard", trimToEmpty(visitorIdCardPlain));
payload.put("visitorIdCardType", VISITOR_ID_CARD_TYPE);
payload.put("visitorPhone", firstNonBlank(visitor.getMobile(), valueOf(visitorProfile, MpVisitor::getVisitorMobile)));
- payload.put("workUnit", firstNonBlank(valueOf(visitorProfile, MpVisitor::getVisitorCompany), order.getVisitorOrg()));
payload.put("carNumber", trimToEmpty(visitor.getPlateNo()));
- payload.put("visitPurpose", trimToEmpty(visitPurpose));
- payload.put("jobNo", valueOf(visitedUser, MpUser::getStaffNo));
payload.put("beVisitorPhone", valueOf(visitedUser, MpUser::getMobile));
payload.put("startTime", formatDateTime(startTime, QR_TIME_FORMATTER));
payload.put("endTime", formatDateTime(endTime, QR_TIME_FORMATTER));
@@ -67,9 +101,6 @@
payload.put("date", formatDateTime(qrGenerateTime, QR_DATE_FORMATTER));
payload.put("validTime", VALID_TIME_PREFIX + Math.max(validMinutes, 1));
payload.put("codeId", trimToEmpty(codeId));
- payload.put("customa", EMPTY);
- payload.put("customb", EMPTY);
- payload.put("customc", EMPTY);
try {
String json = objectMapper.writeValueAsString(payload);
log.info(
@@ -79,13 +110,70 @@
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);
+ if (safe.containsKey("visitorIdCard")) {
+ safe.put("visitorIdCard", maskIdCardForLog(safe.get("visitorIdCard")));
+ }
+ if (safe.containsKey("visitorPhone")) {
+ safe.put("visitorPhone", maskMobileForLog(safe.get("visitorPhone")));
+ }
+ if (safe.containsKey("beVisitorPhone")) {
+ 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()))) {
--
Gitblit v1.7.1