package com.talentcard.server.business.portal;
|
|
import java.net.URI;
|
import java.util.Arrays;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Locale;
|
import java.util.Map;
|
import java.util.Set;
|
import java.util.stream.Collectors;
|
|
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.TypeReference;
|
import com.talentcard.server.business.DatabaseRows;
|
import com.talentcard.server.common.BusinessException;
|
import com.talentcard.server.common.ErrorCode;
|
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
@Service
|
public class CarWashMerchantPortalService {
|
private final JdbcTemplate jdbc;
|
private final Set<String> allowedWebHosts;
|
|
public CarWashMerchantPortalService(
|
JdbcTemplate jdbc,
|
@Value("${app.external-web.allowed-hosts:}") String allowedWebHosts
|
) {
|
this.jdbc = jdbc;
|
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<String, Object> detail(Long merchantId, Long talentCardId) {
|
Map<String, Object> merchant = one("""
|
select id, merchant_name, contact_mobile, address, cover_url, service_label, rights_text, merchant_rights_config, status
|
from merchant
|
where id = ? and merchant_type_code = 'carWash' and deleted = 0
|
""", merchantId);
|
List<Map<String, Object>> serviceRows = query("""
|
select id, talent_levels, service_point_text, tip_text, external_link_type,
|
external_mini_program_app_id, external_mini_program_path, external_web_url,
|
unavailable_reason, status
|
from transport_car_wash_service
|
where merchant_id = ? and deleted = 0
|
order by id asc limit 1
|
""", merchantId);
|
Map<String, Object> service = serviceRows.isEmpty() ? Map.of() : serviceRows.get(0);
|
String cardLevel = talentCardId == null ? "" : talentCardLevel(talentCardId);
|
List<Map<String, Object>> rights = parseList(merchant.get("merchantRightsConfig"));
|
Map<String, Object> right = matchingRight(rights, cardLevel);
|
boolean hasCardRightsConfig = !rights.isEmpty() && !cardLevel.isBlank();
|
boolean available = number(merchant.get("status")) == 1
|
&& (serviceRows.isEmpty() || number(service.get("status")) == 1)
|
&& (!hasCardRightsConfig || right != null)
|
&& (right == null || booleanValue(right.get("enabled")));
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("merchantId", merchant.get("id"));
|
result.put("merchantName", merchant.get("merchantName"));
|
result.put("merchantType", "carWash");
|
result.put("coverUrl", merchant.get("coverUrl"));
|
result.put("available", available);
|
result.put("unavailableReason", available ? "" : unavailableReason(merchant, service, right));
|
result.put("applicableCards", applicableCards(right, service));
|
result.put("servicePointText", servicePointText(right, service));
|
result.put("tipText", tipText(right, service));
|
result.put("rightsText", rightsText(merchant, right, service));
|
applyExternalLink(result, merchant, service);
|
return result;
|
}
|
|
private void applyExternalLink(Map<String, Object> result, Map<String, Object> merchant, Map<String, Object> service) {
|
Map<String, Object> config = carWashJumpConfig(merchant);
|
if (config != null) {
|
String type = text(config.get("externalLinkType"), "");
|
if ("miniProgram".equals(type)) {
|
String shortLink = text(config.get("externalMiniProgramShortLink"), "");
|
if (!shortLink.isBlank()) {
|
result.put("externalLinkType", "miniProgram");
|
result.put("externalMiniProgramAppId", "");
|
result.put("externalMiniProgramShortLink", shortLink);
|
result.put("externalMiniProgramPath", text(config.get("externalMiniProgramPath"), ""));
|
result.put("externalWebUrl", "");
|
return;
|
}
|
}
|
if ("web".equals(type)) {
|
String webUrl = text(config.get("externalWebUrl"), "");
|
if (isAllowedWebUrl(webUrl)) {
|
result.put("externalLinkType", "web");
|
result.put("externalMiniProgramAppId", "");
|
result.put("externalMiniProgramShortLink", "");
|
result.put("externalMiniProgramPath", "");
|
result.put("externalWebUrl", webUrl);
|
return;
|
}
|
}
|
result.put("externalLinkType", "");
|
result.put("externalMiniProgramAppId", "");
|
result.put("externalMiniProgramShortLink", "");
|
result.put("externalMiniProgramPath", "");
|
result.put("externalWebUrl", "");
|
return;
|
}
|
String legacyType = text(service.get("externalLinkType"), "");
|
if ("miniProgram".equals(legacyType)) {
|
String shortLink = text(service.get("externalMiniProgramShortLink"), "");
|
if (!shortLink.isBlank()) {
|
result.put("externalLinkType", "miniProgram");
|
result.put("externalMiniProgramAppId", "");
|
result.put("externalMiniProgramShortLink", shortLink);
|
result.put("externalMiniProgramPath", text(service.get("externalMiniProgramPath"), ""));
|
result.put("externalWebUrl", "");
|
return;
|
}
|
}
|
if ("web".equals(legacyType)) {
|
String webUrl = text(service.get("externalWebUrl"), "");
|
if (isAllowedWebUrl(webUrl)) {
|
result.put("externalLinkType", "web");
|
result.put("externalMiniProgramAppId", "");
|
result.put("externalMiniProgramShortLink", "");
|
result.put("externalMiniProgramPath", "");
|
result.put("externalWebUrl", webUrl);
|
return;
|
}
|
}
|
result.put("externalLinkType", "");
|
result.put("externalMiniProgramAppId", "");
|
result.put("externalMiniProgramShortLink", "");
|
result.put("externalMiniProgramPath", "");
|
result.put("externalWebUrl", "");
|
}
|
|
private String applicableCards(Map<String, Object> right, Map<String, Object> service) {
|
if (right != null) {
|
String level = text(right.get("level"), "");
|
if (!level.isBlank()) return level;
|
}
|
return text(service.get("talentLevels"), "请咨询商户");
|
}
|
|
private String servicePointText(Map<String, Object> right, Map<String, Object> service) {
|
if (right != null) {
|
String detail = text(right.get("detail"), "");
|
if (!detail.isBlank()) return detail;
|
}
|
return text(service.get("servicePointText"), "请咨询商户");
|
}
|
|
private String tipText(Map<String, Object> right, Map<String, Object> service) {
|
if (right != null) {
|
String consumeDesc = text(right.get("consumeDesc"), "");
|
if (!consumeDesc.isBlank()) return consumeDesc;
|
}
|
return text(service.get("tipText"), "到店后按现场指引操作,可直接享受洗车权益。");
|
}
|
|
private String rightsText(Map<String, Object> merchant, Map<String, Object> right, Map<String, Object> service) {
|
if (right != null) {
|
String detail = text(right.get("detail"), "");
|
if (!detail.isBlank()) return detail;
|
String consumeDesc = text(right.get("consumeDesc"), "");
|
if (!consumeDesc.isBlank()) return consumeDesc;
|
String tag = text(right.get("tag"), "");
|
if (!tag.isBlank()) return tag;
|
}
|
String merchantRightsText = text(merchant.get("rightsText"), "");
|
if (!merchantRightsText.isBlank()) return merchantRightsText;
|
return text(service.get("servicePointText"), "请咨询商户");
|
}
|
|
private String unavailableReason(Map<String, Object> merchant, Map<String, Object> service, Map<String, Object> right) {
|
if (number(merchant.get("status")) != 1) return "洗车服务当前不可用";
|
if (number(service.get("status")) != 1) return "洗车服务当前不可用";
|
if (right == null) return "当前人才卡暂无可用洗车权益";
|
return "洗车权益当前不可用";
|
}
|
|
private Map<String, Object> matchingRight(List<Map<String, Object>> rights, String cardLevel) {
|
if (rights.isEmpty() || cardLevel == null || cardLevel.isBlank()) return null;
|
String normalizedCardLevel = normalizedCardLevel(cardLevel);
|
for (Map<String, Object> right : rights) {
|
if (normalizedCardLevel.equals(normalizedCardLevel(text(right.get("level"), "")))) return right;
|
}
|
return null;
|
}
|
|
private String talentCardLevel(Long talentCardId) {
|
List<Map<String, Object>> rows = query("select card_level from talent_card where id = ? and deleted = 0 limit 1", talentCardId);
|
return rows.isEmpty() ? "" : text(rows.get(0).get("cardLevel"), "");
|
}
|
|
private List<Map<String, Object>> parseList(Object value) {
|
if (value instanceof List<?> list) {
|
return list.stream().filter(Map.class::isInstance).map(item -> copyMap((Map<?, ?>) item)).toList();
|
}
|
String text = value == null ? "" : String.valueOf(value).trim();
|
if (text.isBlank()) return List.of();
|
try {
|
List<Map<String, Object>> rows = JSON.parseObject(text, new TypeReference<List<Map<String, Object>>>() {});
|
return rows == null ? List.of() : rows.stream().map(this::copyMap).toList();
|
} catch (Exception ignored) {
|
return List.of();
|
}
|
}
|
|
private Map<String, Object> copyMap(Map<?, ?> source) {
|
Map<String, Object> result = new LinkedHashMap<>();
|
for (Map.Entry<?, ?> entry : source.entrySet()) {
|
result.put(String.valueOf(entry.getKey()), entry.getValue());
|
}
|
return result;
|
}
|
|
private Map<String, Object> carWashJumpConfig(Map<String, Object> merchant) {
|
List<Map<String, Object>> rights = parseList(merchant.get("merchantRightsConfig"));
|
for (Map<String, Object> right : rights) {
|
if (right.containsKey("externalLinkType")
|
|| right.containsKey("externalMiniProgramShortLink")
|
|| right.containsKey("externalMiniProgramPath")
|
|| right.containsKey("externalWebUrl")) {
|
return right;
|
}
|
}
|
return null;
|
}
|
|
private String normalizedCardLevel(String value) {
|
String raw = value == null ? "" : value.trim();
|
String upper = raw.toUpperCase(Locale.ROOT);
|
if (upper.contains("EXCELLENT") || raw.contains("卓越")) return "卓越人才卡";
|
if (upper.contains("OUTSTANDING") || raw.contains("杰出")) return "杰出人才卡";
|
if (upper.contains("ELITE") || raw.contains("菁英") || raw.contains("精英")) return "菁英人才卡";
|
return raw;
|
}
|
|
private boolean isAllowedWebUrl(String value) {
|
if (value.isEmpty() || 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 ignored) {
|
return false;
|
}
|
}
|
|
private Map<String, Object> one(String sql, Object... args) {
|
List<Map<String, Object>> rows = jdbc.queryForList(sql, args).stream().map(DatabaseRows::normalize).toList();
|
if (rows.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "自主洗车服务配置不存在");
|
return rows.get(0);
|
}
|
|
private List<Map<String, Object>> query(String sql, Object... args) {
|
return jdbc.queryForList(sql, args).stream().map(DatabaseRows::normalize).toList();
|
}
|
|
private int number(Object value) {
|
return value instanceof Number number ? number.intValue() : 0;
|
}
|
|
private boolean booleanValue(Object value) {
|
if (value instanceof Boolean bool) return bool;
|
if (value instanceof Number number) return number.intValue() != 0;
|
String text = value == null ? "" : String.valueOf(value).trim();
|
return "true".equalsIgnoreCase(text) || "1".equals(text) || "yes".equalsIgnoreCase(text);
|
}
|
|
private String text(Object value, String fallback) {
|
if (value == null) return fallback;
|
String text = String.valueOf(value).trim();
|
return text.isEmpty() ? fallback : text;
|
}
|
}
|