package com.xuqm.im.service; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; @Component public class ImFeatureConfigClient { private static final Logger log = LoggerFactory.getLogger(ImFeatureConfigClient.class); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private final RestTemplate restTemplate; @Value("${im.tenant-service-url:http://127.0.0.1:8081}") private String tenantServiceUrl; @Value("${im.internal-token:xuqm-internal-token}") private String internalToken; public ImFeatureConfigClient() { this.restTemplate = new RestTemplate(); } public boolean allowStrangerMessage(String appId) { return readConfig(appId).path("allowStrangerMessage").asBoolean(false); } public boolean allowFriendRequest(String appId) { return readConfig(appId).path("allowFriendRequest").asBoolean(true); } public String friendRequestMode(String appId) { JsonNode node = readConfig(appId); String mode = node.path("friendRequestMode").asText(""); String normalized = mode == null ? "" : mode.trim().toUpperCase(); return switch (normalized) { case "DIRECT_ACCEPT", "DISALLOW", "REQUIRE_CONFIRM" -> normalized; default -> node.path("allowFriendRequest").asBoolean(true) ? "REQUIRE_CONFIRM" : "DISALLOW"; }; } public boolean allowGroupJoinRequest(String appId) { return readConfig(appId).path("allowGroupJoinRequest").asBoolean(true); } public boolean blacklistSendSuccess(String appId) { return readConfig(appId).path("blacklistSendSuccess").asBoolean(true); } public int messageRecallMinutes(String appId) { return Math.max(readConfig(appId).path("messageRecallMinutes").asInt(2), 0); } public int historyRetentionDays(String appId) { return Math.max(readConfig(appId).path("historyRetentionDays").asInt(7), 1); } public int conversationPullLimit(String appId) { return Math.min(Math.max(readConfig(appId).path("conversationPullLimit").asInt(100), 1), 500); } public boolean multiClientConversationDeleteSync(String appId) { return readConfig(appId).path("multiClientConversationDeleteSync").asBoolean(false); } public boolean allowMultiDeviceLogin(String appId) { return readConfig(appId).path("allowMultiDeviceLogin").asBoolean(true); } private JsonNode readConfig(String appId) { String url = UriComponentsBuilder.fromHttpUrl(tenantServiceUrl) .path("/api/internal/sdk/apps/{appId}/services/{platform}/{serviceType}") .buildAndExpand(appId, "ANDROID", "IM") .toUriString(); HttpHeaders headers = new HttpHeaders(); headers.set("X-Internal-Token", internalToken); try { ResponseEntity response = restTemplate.exchange( url, HttpMethod.GET, new HttpEntity<>(headers), JsonNode.class ); JsonNode body = response.getBody(); if (response.getStatusCode().is2xxSuccessful() && body != null && body.path("code").asInt() == 200) { String config = body.path("data").path("config").asText(""); if (!config.isBlank()) { JsonNode node = OBJECT_MAPPER.readTree(config); return node == null ? OBJECT_MAPPER.createObjectNode() : node; } } } catch (Exception e) { log.warn("Failed to read IM feature config for appId={}: {}", appId, e.getMessage()); } return OBJECT_MAPPER.createObjectNode(); } }