| | |
| | | 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; |
| | |
| | | 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, |
| | |
| | | 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)); |
| | |
| | | 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( |
| | |
| | | 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()))) { |