package com.talentcard.server.service; import java.net.URI; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import com.talentcard.server.common.BusinessException; import com.talentcard.server.common.ErrorCode; import com.talentcard.server.mapper.TransportCarWashServiceMapper; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class CarWashJumpConfigPortalService { private final TransportCarWashServiceMapper serviceMapper; private final Set allowedWebHosts; public CarWashJumpConfigPortalService( TransportCarWashServiceMapper serviceMapper, @Value("${app.external-web.allowed-hosts:}") String allowedWebHosts ) { this.serviceMapper = serviceMapper; this.allowedWebHosts = Arrays.stream(allowedWebHosts.split(",")) .map(String::trim) .filter(value -> !value.isEmpty()) .map(value -> value.toLowerCase(Locale.ROOT)) .collect(Collectors.toUnmodifiableSet()); } @Transactional(readOnly = true) public Map jumpConfig(Long merchantId) { Long id = requireMerchantId(merchantId); Map row = serviceMapper.selectPortalJumpConfig(id); if (row == null || row.isEmpty()) { throw new BusinessException(ErrorCode.NOT_FOUND, "洗车商家不存在"); } boolean merchantEnabled = intValue(row, "merchantStatus", "merchant_status") == 1; boolean serviceConfigured = value(row, "configId", "config_id") != null; boolean serviceEnabled = intValue(row, "serviceStatus", "service_status") == 1; boolean available = merchantEnabled && serviceConfigured && serviceEnabled; Map result = new LinkedHashMap<>(); result.put("merchantId", value(row, "merchantId", "merchant_id")); result.put("merchantName", text(row, "merchantName", "merchant_name")); result.put("available", available); result.put("unavailableReason", unavailableReason(row, available, merchantEnabled, serviceConfigured, serviceEnabled)); applyExternalLink(result, row, available); result.put("updatedAt", value(row, "updatedAt", "updated_at")); return result; } private void applyExternalLink(Map result, Map row, boolean available) { if (available) { String type = text(row, "externalLinkType", "external_link_type"); if ("miniProgram".equals(type)) { String appId = text(row, "externalMiniProgramAppId", "external_mini_program_app_id"); if (appId.matches("^wx[a-zA-Z0-9]{16}$")) { result.put("externalLinkType", "miniProgram"); result.put("externalMiniProgramAppId", appId); result.put("externalMiniProgramPath", text(row, "externalMiniProgramPath", "external_mini_program_path")); result.put("externalWebUrl", ""); return; } } if ("web".equals(type)) { String webUrl = text(row, "externalWebUrl", "external_web_url"); if (isAllowedWebUrl(webUrl)) { result.put("externalLinkType", "web"); result.put("externalMiniProgramAppId", ""); result.put("externalMiniProgramPath", ""); result.put("externalWebUrl", webUrl); return; } } } result.put("externalLinkType", ""); result.put("externalMiniProgramAppId", ""); result.put("externalMiniProgramPath", ""); result.put("externalWebUrl", ""); } private String unavailableReason(Map row, boolean available, boolean merchantEnabled, boolean serviceConfigured, boolean serviceEnabled) { if (available) { return ""; } if (!merchantEnabled) { return "洗车商家当前不可用"; } if (!serviceConfigured) { return "洗车服务配置不存在"; } if (!serviceEnabled) { String configuredReason = text(row, "unavailableReason", "unavailable_reason"); return configuredReason.isBlank() ? "洗车服务当前不可用" : configuredReason; } return "洗车跳转配置不可用"; } private boolean isAllowedWebUrl(String value) { if (value.isBlank() || allowedWebHosts.isEmpty()) { return false; } try { URI uri = URI.create(value); String host = uri.getHost(); return "https".equalsIgnoreCase(uri.getScheme()) && host != null && uri.getUserInfo() == null && allowedWebHosts.contains(host.toLowerCase(Locale.ROOT)); } catch (IllegalArgumentException exception) { return false; } } private Long requireMerchantId(Long merchantId) { if (merchantId == null || merchantId <= 0) { throw new BusinessException(ErrorCode.BAD_REQUEST, "洗车商家ID不能为空"); } return merchantId; } private Object value(Map row, String... keys) { for (String key : keys) { if (row.containsKey(key)) { return row.get(key); } } return null; } private String text(Map row, String... keys) { Object value = value(row, keys); return value == null ? "" : String.valueOf(value).trim(); } private int intValue(Map row, String... keys) { Object value = value(row, keys); return value instanceof Number number ? number.intValue() : 0; } }