yeg
2026-09-16 3a024caa497b8923635c02e0c8c0df07f2918f81
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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<String> 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<String, Object> jumpConfig(Long merchantId) {
        Long id = requireMerchantId(merchantId);
        Map<String, Object> 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<String, Object> 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<String, Object> result, Map<String, Object> 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<String, Object> 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<String, Object> row, String... keys) {
        for (String key : keys) {
            if (row.containsKey(key)) {
                return row.get(key);
            }
        }
        return null;
    }
 
    private String text(Map<String, Object> row, String... keys) {
        Object value = value(row, keys);
        return value == null ? "" : String.valueOf(value).trim();
    }
 
    private int intValue(Map<String, Object> row, String... keys) {
        Object value = value(row, keys);
        return value instanceof Number number ? number.intValue() : 0;
    }
}