XuqmGroup-Server/tenant-service/src/main/java/com/xuqm/tenant/service/FeatureServiceManager.java

683 行
30 KiB
Java

2026-04-21 22:07:29 +08:00
package com.xuqm.tenant.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
2026-04-21 22:07:29 +08:00
import com.xuqm.common.exception.BusinessException;
import com.xuqm.tenant.entity.FeatureServiceEntity;
import com.xuqm.tenant.entity.ServiceActivationRequestEntity;
import com.xuqm.tenant.entity.ServiceActivationRequestEntity.Status;
import com.xuqm.tenant.repository.AppRepository;
2026-04-21 22:07:29 +08:00
import com.xuqm.tenant.repository.FeatureServiceRepository;
import com.xuqm.tenant.repository.ServiceActivationRequestRepository;
2026-04-21 22:07:29 +08:00
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
2026-04-21 22:07:29 +08:00
import java.time.LocalDateTime;
import java.util.ArrayList;
2026-04-21 22:07:29 +08:00
import java.util.List;
import java.util.UUID;
@Service
public class FeatureServiceManager {
private final FeatureServiceRepository repository;
private final ServiceActivationRequestRepository requestRepository;
private final AppRepository appRepository;
private final ObjectMapper objectMapper;
2026-04-21 22:07:29 +08:00
public FeatureServiceManager(FeatureServiceRepository repository,
ServiceActivationRequestRepository requestRepository,
AppRepository appRepository,
ObjectMapper objectMapper) {
2026-04-21 22:07:29 +08:00
this.repository = repository;
this.requestRepository = requestRepository;
this.appRepository = appRepository;
this.objectMapper = objectMapper;
2026-04-21 22:07:29 +08:00
}
2026-05-07 19:39:42 +08:00
public List<FeatureServiceEntity> listByApp(String appKey) {
List<FeatureServiceEntity> services = repository.findByAppId(appKey);
if (services.isEmpty()) {
return services;
}
List<FeatureServiceEntity> normalized = new ArrayList<>();
for (FeatureServiceEntity.ServiceType serviceType : List.of(
FeatureServiceEntity.ServiceType.IM,
FeatureServiceEntity.ServiceType.PUSH,
FeatureServiceEntity.ServiceType.UPDATE)) {
services.stream()
.filter(service -> service.getServiceType() == serviceType)
.findFirst()
.ifPresent(normalized::add);
}
return normalized.isEmpty() ? services : normalized;
2026-04-21 22:07:29 +08:00
}
/**
* Submit an activation request. Disabling is immediate; enabling requires ops approval.
* IM / PUSH / UPDATE are app-wide, so duplicate checks ignore platform.
*/
@Transactional
public ServiceActivationRequestEntity submitActivationRequest(
2026-05-07 19:39:42 +08:00
String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType,
String applyReason) {
if (isAppWideService(serviceType)) {
2026-05-07 19:39:42 +08:00
requestRepository.findFirstByAppIdAndServiceTypeOrderByCreatedAtDesc(appKey, serviceType)
.ifPresent(req -> {
if (req.getStatus() == Status.PENDING) {
throw new BusinessException(400, "已有待审核的开通申请,请等待运营人员处理");
}
});
}
ServiceActivationRequestEntity req = new ServiceActivationRequestEntity();
req.setId(UUID.randomUUID().toString());
2026-05-07 19:39:42 +08:00
req.setAppId(appKey);
req.setPlatform(platform);
req.setServiceType(serviceType);
req.setStatus(Status.PENDING);
req.setApplyReason(applyReason);
req.setCreatedAt(LocalDateTime.now());
return requestRepository.save(req);
}
/**
* Disable a service immediately (no approval needed).
*/
@Transactional
2026-05-07 19:39:42 +08:00
public FeatureServiceEntity disable(String appKey, FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
if (isAppWideService(serviceType)) {
2026-05-07 19:39:42 +08:00
List<FeatureServiceEntity> services = repository.findByAppIdAndServiceType(appKey, serviceType);
if (services.isEmpty()) {
throw new BusinessException(404, "服务未开通");
}
services.forEach(service -> service.setEnabled(false));
repository.saveAll(services);
return services.get(0);
}
2026-04-21 22:07:29 +08:00
FeatureServiceEntity entity = repository
2026-05-07 19:39:42 +08:00
.findByAppIdAndPlatformAndServiceType(appKey, platform, serviceType)
.orElseThrow(() -> new BusinessException(404, "服务未开通"));
entity.setEnabled(false);
return repository.save(entity);
}
/**
* Called by ops when approving an activation request.
*/
@Transactional
public ServiceActivationRequestEntity approveRequest(String requestId, String reviewNote) {
ServiceActivationRequestEntity req = requestRepository.findById(requestId)
.orElseThrow(() -> new BusinessException(404, "申请不存在"));
if (req.getStatus() != Status.PENDING) {
throw new BusinessException(400, "申请已处理");
}
req.setStatus(Status.APPROVED);
req.setReviewNote(reviewNote);
req.setReviewedAt(LocalDateTime.now());
requestRepository.save(req);
// Normalize to appKey so SdkConfigController queries are consistent
String normalizedAppId = appRepository.findById(req.getAppId())
.map(app -> app.getAppKey())
.orElse(req.getAppId());
if (isAppWideService(req.getServiceType())) {
List<FeatureServiceEntity> services = repository.findByAppIdAndServiceType(normalizedAppId, req.getServiceType());
if (services.isEmpty()) {
for (FeatureServiceEntity.Platform platform : FeatureServiceEntity.Platform.values()) {
FeatureServiceEntity created = new FeatureServiceEntity();
created.setId(UUID.randomUUID().toString());
created.setAppId(normalizedAppId);
created.setPlatform(platform);
created.setServiceType(req.getServiceType());
created.setEnabled(true);
created.setCreatedAt(LocalDateTime.now());
repository.save(created);
}
} else {
services.forEach(service -> service.setEnabled(true));
repository.saveAll(services);
}
return req;
}
FeatureServiceEntity entity = repository
.findByAppIdAndPlatformAndServiceType(normalizedAppId, req.getPlatform(), req.getServiceType())
2026-04-21 22:07:29 +08:00
.orElseGet(() -> {
FeatureServiceEntity e = new FeatureServiceEntity();
e.setId(UUID.randomUUID().toString());
e.setAppId(normalizedAppId);
e.setPlatform(req.getPlatform());
e.setServiceType(req.getServiceType());
2026-04-21 22:07:29 +08:00
e.setCreatedAt(LocalDateTime.now());
return e;
});
entity.setEnabled(true);
repository.save(entity);
return req;
}
/**
* Called by ops when rejecting an activation request.
*/
@Transactional
public ServiceActivationRequestEntity rejectRequest(String requestId, String reviewNote) {
ServiceActivationRequestEntity req = requestRepository.findById(requestId)
.orElseThrow(() -> new BusinessException(404, "申请不存在"));
if (req.getStatus() != Status.PENDING) {
throw new BusinessException(400, "申请已处理");
}
req.setStatus(Status.REJECTED);
req.setReviewNote(reviewNote);
req.setReviewedAt(LocalDateTime.now());
return requestRepository.save(req);
}
2026-05-07 19:39:42 +08:00
public List<ServiceActivationRequestEntity> listRequestsByApp(String appKey) {
return requestRepository.findByAppIdOrderByCreatedAtDesc(appKey);
2026-04-21 22:07:29 +08:00
}
2026-05-07 19:39:42 +08:00
public FeatureServiceEntity getOrFail(String appKey, FeatureServiceEntity.Platform platform,
2026-04-21 22:07:29 +08:00
FeatureServiceEntity.ServiceType serviceType) {
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
2026-05-07 19:39:42 +08:00
return repository.findByAppIdAndServiceType(appKey, serviceType)
.stream()
.findFirst()
.orElseThrow(() -> new BusinessException(404, "服务未配置"));
}
2026-05-07 19:39:42 +08:00
return repository.findByAppIdAndPlatformAndServiceType(appKey, platform, serviceType)
2026-04-21 22:07:29 +08:00
.orElseThrow(() -> new BusinessException(404, "服务未配置"));
}
@Transactional
2026-05-07 19:39:42 +08:00
public FeatureServiceEntity updateConfig(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType,
String config) {
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
2026-05-07 19:39:42 +08:00
List<FeatureServiceEntity> services = repository.findByAppIdAndServiceType(appKey, serviceType);
if (services.isEmpty()) {
throw new BusinessException(404, "服务未配置");
}
services.forEach(service -> service.setConfig(config));
repository.saveAll(services);
return services.get(0);
}
2026-05-07 19:39:42 +08:00
FeatureServiceEntity entity = getOrFail(appKey, platform, serviceType);
entity.setConfig(config);
return repository.save(entity);
}
2026-05-07 19:39:42 +08:00
public FeatureServiceEntity getByPlatform(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
2026-05-07 19:39:42 +08:00
return getOrFail(appKey, platform, serviceType);
}
2026-05-07 19:39:42 +08:00
public boolean allowStrangerMessage(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
2026-05-07 19:39:42 +08:00
return readConfigNode(appKey, platform, serviceType).path("allowStrangerMessage").asBoolean(false);
}
2026-05-07 19:39:42 +08:00
public boolean allowFriendRequest(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
2026-05-07 19:39:42 +08:00
return readConfigNode(appKey, platform, serviceType).path("allowFriendRequest").asBoolean(true);
}
2026-05-07 19:39:42 +08:00
public boolean allowGroupJoinRequest(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
2026-05-07 19:39:42 +08:00
return readConfigNode(appKey, platform, serviceType).path("allowGroupJoinRequest").asBoolean(true);
}
2026-05-07 19:39:42 +08:00
public String friendRequestMode(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
2026-05-07 19:39:42 +08:00
String mode = readConfigNode(appKey, platform, serviceType).path("friendRequestMode").asText("");
return switch (mode == null ? "" : mode.trim().toUpperCase()) {
case "DIRECT_ACCEPT", "DISALLOW" -> mode.trim().toUpperCase();
default -> "REQUIRE_CONFIRM";
};
}
2026-05-07 19:39:42 +08:00
public boolean blacklistSendSuccess(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
2026-05-07 19:39:42 +08:00
return readConfigNode(appKey, platform, serviceType).path("blacklistSendSuccess").asBoolean(true);
}
2026-05-07 19:39:42 +08:00
public int messageRecallMinutes(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
2026-05-07 19:39:42 +08:00
int minutes = readConfigNode(appKey, platform, serviceType).path("messageRecallMinutes").asInt(2);
return Math.max(minutes, 0);
}
2026-05-07 19:39:42 +08:00
public int conversationPullLimit(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
2026-05-07 19:39:42 +08:00
int limit = readConfigNode(appKey, platform, serviceType).path("conversationPullLimit").asInt(100);
return Math.min(Math.max(limit, 1), 500);
}
2026-05-07 19:39:42 +08:00
public int historyRetentionDays(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
2026-05-07 19:39:42 +08:00
int days = readConfigNode(appKey, platform, serviceType).path("historyRetentionDays").asInt(7);
return Math.max(days, 1);
}
2026-05-07 19:39:42 +08:00
public String buildImConfig(String appKey,
FeatureServiceEntity.Platform platform,
Boolean allowStrangerMessage,
Boolean allowFriendRequest,
String friendRequestMode,
Boolean allowGroupJoinRequest,
Boolean blacklistSendSuccess,
Integer messageRecallMinutes,
Integer historyRetentionDays,
Integer conversationPullLimit,
Boolean allowMultiDeviceLogin,
Boolean multiClientConversationDeleteSync) {
2026-05-07 19:39:42 +08:00
ObjectNode node = readConfigNode(appKey, platform, FeatureServiceEntity.ServiceType.IM).deepCopy();
if (!node.has("allowStrangerMessage")) {
node.put("allowStrangerMessage", false);
}
if (!node.has("allowFriendRequest")) {
node.put("allowFriendRequest", true);
}
if (!node.has("friendRequestMode")) {
node.put("friendRequestMode", "REQUIRE_CONFIRM");
}
if (!node.has("allowGroupJoinRequest")) {
node.put("allowGroupJoinRequest", true);
}
if (!node.has("blacklistSendSuccess")) {
node.put("blacklistSendSuccess", true);
}
if (!node.has("messageRecallMinutes")) {
node.put("messageRecallMinutes", 2);
}
if (!node.has("historyRetentionDays")) {
node.put("historyRetentionDays", 7);
}
if (!node.has("conversationPullLimit")) {
node.put("conversationPullLimit", 100);
}
if (!node.has("multiClientConversationDeleteSync")) {
node.put("multiClientConversationDeleteSync", false);
}
if (!node.has("allowMultiDeviceLogin")) {
node.put("allowMultiDeviceLogin", true);
}
if (allowStrangerMessage != null) {
node.put("allowStrangerMessage", allowStrangerMessage);
}
if (allowFriendRequest != null) {
node.put("allowFriendRequest", allowFriendRequest);
}
String effectiveFriendRequestMode = null;
if (friendRequestMode != null && !friendRequestMode.isBlank()) {
effectiveFriendRequestMode = normalizeFriendRequestMode(friendRequestMode);
} else if (allowFriendRequest != null && !allowFriendRequest) {
effectiveFriendRequestMode = "DISALLOW";
}
if (effectiveFriendRequestMode != null) {
node.put("friendRequestMode", effectiveFriendRequestMode);
if ("DISALLOW".equals(effectiveFriendRequestMode)) {
node.put("allowFriendRequest", false);
}
}
if (allowGroupJoinRequest != null) {
node.put("allowGroupJoinRequest", allowGroupJoinRequest);
}
if (blacklistSendSuccess != null) {
node.put("blacklistSendSuccess", blacklistSendSuccess);
}
if (messageRecallMinutes != null) {
node.put("messageRecallMinutes", Math.max(messageRecallMinutes, 0));
}
if (historyRetentionDays != null) {
node.put("historyRetentionDays", Math.max(historyRetentionDays, 1));
}
if (conversationPullLimit != null) {
node.put("conversationPullLimit", Math.min(Math.max(conversationPullLimit, 1), 500));
}
if (allowMultiDeviceLogin != null) {
node.put("allowMultiDeviceLogin", allowMultiDeviceLogin);
}
if (multiClientConversationDeleteSync != null) {
node.put("multiClientConversationDeleteSync", multiClientConversationDeleteSync);
}
return node.toString();
}
public String buildAllowStrangerConfig(boolean allowStrangerMessage) {
ObjectNode node = objectMapper.createObjectNode();
node.put("allowStrangerMessage", allowStrangerMessage);
node.put("allowFriendRequest", true);
node.put("friendRequestMode", "REQUIRE_CONFIRM");
node.put("allowGroupJoinRequest", true);
node.put("blacklistSendSuccess", true);
node.put("messageRecallMinutes", 2);
node.put("historyRetentionDays", 7);
node.put("conversationPullLimit", 100);
node.put("allowMultiDeviceLogin", true);
node.put("multiClientConversationDeleteSync", false);
return node.toString();
}
2026-05-07 19:39:42 +08:00
public String buildUpdateConfig(String appKey,
FeatureServiceEntity.Platform platform,
List<String> defaultStoreTargets,
String defaultPublishMode,
Boolean defaultPublishImmediately,
String defaultScheduledPublishAt,
Boolean defaultAutoPublishAfterReview,
String defaultWebhookUrl,
Boolean defaultForceUpdate,
Boolean defaultGrayEnabled,
Integer defaultGrayPercent,
String defaultPackageName,
String defaultAppStoreUrl,
String defaultMarketUrl) {
2026-05-07 19:39:42 +08:00
ObjectNode node = readConfigNode(appKey, platform, FeatureServiceEntity.ServiceType.UPDATE).deepCopy();
if (!node.has("defaultStoreTargets")) {
node.putArray("defaultStoreTargets");
}
if (!node.has("defaultPublishMode")) {
node.put("defaultPublishMode", "MANUAL");
}
if (!node.has("defaultPublishImmediately")) {
node.put("defaultPublishImmediately", false);
}
if (!node.has("defaultScheduledPublishAt")) {
node.put("defaultScheduledPublishAt", "");
}
if (!node.has("defaultAutoPublishAfterReview")) {
node.put("defaultAutoPublishAfterReview", false);
}
if (!node.has("defaultWebhookUrl")) {
node.put("defaultWebhookUrl", "");
}
if (!node.has("defaultForceUpdate")) {
node.put("defaultForceUpdate", false);
}
if (!node.has("defaultGrayEnabled")) {
node.put("defaultGrayEnabled", false);
}
if (!node.has("defaultGrayPercent")) {
node.put("defaultGrayPercent", 0);
}
if (!node.has("defaultPackageName")) {
node.put("defaultPackageName", "");
}
if (!node.has("defaultAppStoreUrl")) {
node.put("defaultAppStoreUrl", "");
}
if (!node.has("defaultMarketUrl")) {
node.put("defaultMarketUrl", "");
}
if (defaultStoreTargets != null) {
node.remove("defaultStoreTargets");
ArrayNode array = node.putArray("defaultStoreTargets");
defaultStoreTargets.stream()
.filter(v -> v != null && !v.isBlank())
.map(v -> v.trim().toUpperCase())
.forEach(array::add);
}
if (defaultPublishMode != null && !defaultPublishMode.isBlank()) {
node.put("defaultPublishMode", normalizeReleaseMode(defaultPublishMode));
}
if (defaultPublishImmediately != null) {
node.put("defaultPublishImmediately", defaultPublishImmediately);
}
if (defaultScheduledPublishAt != null) {
node.put("defaultScheduledPublishAt", defaultScheduledPublishAt.trim());
}
if (defaultAutoPublishAfterReview != null) {
node.put("defaultAutoPublishAfterReview", defaultAutoPublishAfterReview);
}
if (defaultWebhookUrl != null) {
node.put("defaultWebhookUrl", defaultWebhookUrl.trim());
}
if (defaultForceUpdate != null) {
node.put("defaultForceUpdate", defaultForceUpdate);
}
if (defaultGrayEnabled != null) {
node.put("defaultGrayEnabled", defaultGrayEnabled);
}
if (defaultGrayPercent != null) {
node.put("defaultGrayPercent", Math.min(Math.max(defaultGrayPercent, 0), 100));
}
if (defaultPackageName != null) {
node.put("defaultPackageName", defaultPackageName.trim());
}
if (defaultAppStoreUrl != null) {
node.put("defaultAppStoreUrl", defaultAppStoreUrl.trim());
}
if (defaultMarketUrl != null) {
node.put("defaultMarketUrl", defaultMarketUrl.trim());
}
return node.toString();
}
2026-05-07 19:39:42 +08:00
public String buildPushConfig(String appKey,
FeatureServiceEntity.Platform platform,
String huaweiAppId,
String huaweiAppSecret,
2026-05-07 19:39:42 +08:00
String huaweiCategory,
String xiaomiAppId,
String xiaomiAppKey,
String xiaomiAppSecret,
2026-05-07 19:39:42 +08:00
String xiaomiChannelId,
String oppoAppId,
String oppoAppKey,
String oppoMasterSecret,
2026-05-07 19:39:42 +08:00
String oppoChannelId,
String vivoAppId,
String vivoAppKey,
String vivoAppSecret,
2026-05-07 19:39:42 +08:00
String vivoCategory,
String vivoReceiptId,
String honorAppId,
String honorClientId,
String honorClientSecret,
String harmonyAppId,
String harmonyAppSecret,
String apnsTeamId,
String apnsKeyId,
String apnsBundleId,
String apnsKeyPath,
boolean apnsSandbox,
String fcmServiceAccountJson,
JsonNode channels,
JsonNode routing) {
2026-05-07 19:39:42 +08:00
ObjectNode node = readConfigNode(appKey, platform, FeatureServiceEntity.ServiceType.PUSH).deepCopy();
ensureObjectNode(node, "huawei");
ensureObjectNode(node, "xiaomi");
ensureObjectNode(node, "oppo");
ensureObjectNode(node, "vivo");
ensureObjectNode(node, "honor");
ensureObjectNode(node, "harmony");
ensureObjectNode(node, "apns");
ensureObjectNode(node, "fcm");
putText(node.with("huawei"), "appId", huaweiAppId);
putText(node.with("huawei"), "appSecret", huaweiAppSecret);
2026-05-07 19:39:42 +08:00
putText(node.with("huawei"), "category", huaweiCategory);
putText(node.with("xiaomi"), "appId", xiaomiAppId);
putText(node.with("xiaomi"), "appKey", xiaomiAppKey);
putText(node.with("xiaomi"), "appSecret", xiaomiAppSecret);
2026-05-07 19:39:42 +08:00
putText(node.with("xiaomi"), "channelId", xiaomiChannelId);
putText(node.with("oppo"), "appId", oppoAppId);
putText(node.with("oppo"), "appKey", oppoAppKey);
putText(node.with("oppo"), "masterSecret", oppoMasterSecret);
2026-05-07 19:39:42 +08:00
putText(node.with("oppo"), "channelId", oppoChannelId);
putText(node.with("vivo"), "appId", vivoAppId);
putText(node.with("vivo"), "appKey", vivoAppKey);
putText(node.with("vivo"), "appSecret", vivoAppSecret);
2026-05-07 19:39:42 +08:00
putText(node.with("vivo"), "category", vivoCategory);
putText(node.with("vivo"), "receiptId", vivoReceiptId);
putText(node.with("honor"), "appId", honorAppId);
putText(node.with("honor"), "clientId", honorClientId);
putText(node.with("honor"), "clientSecret", honorClientSecret);
putText(node.with("harmony"), "appId", harmonyAppId);
putText(node.with("harmony"), "appSecret", harmonyAppSecret);
putText(node.with("apns"), "teamId", apnsTeamId);
putText(node.with("apns"), "keyId", apnsKeyId);
putText(node.with("apns"), "bundleId", apnsBundleId);
putText(node.with("apns"), "keyPath", apnsKeyPath);
node.with("apns").put("sandbox", apnsSandbox);
putText(node.with("fcm"), "serviceAccountJson", fcmServiceAccountJson);
if (channels != null && channels.isArray()) {
node.set("channels", channels);
} else if (!node.has("channels")) {
node.set("channels", defaultPushChannels());
}
if (routing != null && routing.isObject()) {
node.set("routing", routing);
} else if (!node.has("routing")) {
node.set("routing", defaultPushRouting());
}
return node.toString();
}
private ArrayNode defaultPushChannels() {
ArrayNode channels = objectMapper.createArrayNode();
channels.add(defaultPushChannel("im_message", "xuqm_im_message", "聊天消息", "单聊、群聊和好友消息", "HIGH"));
channels.add(defaultPushChannel("system_notice", "xuqm_system_notice", "系统通知", "系统通知和业务提醒", "DEFAULT"));
return channels;
}
private ObjectNode defaultPushChannel(String key, String channelId, String name, String description, String importance) {
ObjectNode channel = objectMapper.createObjectNode();
channel.put("key", key);
channel.put("channelId", channelId);
channel.put("version", 1);
channel.put("name", name);
channel.put("description", description);
channel.put("importance", importance);
channel.put("sound", true);
channel.put("vibration", true);
channel.put("badge", true);
return channel;
}
private ObjectNode defaultPushRouting() {
ObjectNode routing = objectMapper.createObjectNode();
routing.set("IM_MESSAGE", defaultPushRoute("im_message", "MESSAGE", "HIGH"));
routing.set("FRIEND_REQUEST", defaultPushRoute("im_message", "SOCIAL", "HIGH"));
routing.set("SYSTEM_NOTICE", defaultPushRoute("system_notice", "SYSTEM", "DEFAULT"));
return routing;
}
private ObjectNode defaultPushRoute(String channel, String category, String priority) {
ObjectNode route = objectMapper.createObjectNode();
route.put("channel", channel);
route.put("category", category);
route.put("priority", priority);
return route;
}
@Transactional
public FeatureServiceEntity regenerateSecretKey(String serviceId) {
FeatureServiceEntity entity = repository.findById(serviceId)
.orElseThrow(() -> new BusinessException(404, "服务不存在"));
byte[] bytes = new byte[32];
java.security.SecureRandom random = new java.security.SecureRandom();
random.nextBytes(bytes);
entity.setSecretKey(java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(bytes));
return repository.save(entity);
}
public List<String> parseStoreTargets(String json) {
if (json == null || json.isBlank()) {
return List.of();
}
try {
return objectMapper.readValue(json, new com.fasterxml.jackson.core.type.TypeReference<List<String>>() {});
} catch (Exception e) {
return List.of();
}
}
private String normalizeFriendRequestMode(String mode) {
String normalized = mode == null ? "" : mode.trim().toUpperCase();
return switch (normalized) {
case "DIRECT_ACCEPT", "DISALLOW" -> normalized;
default -> "REQUIRE_CONFIRM";
};
}
private String normalizeReleaseMode(String mode) {
String normalized = mode == null ? "" : mode.trim().toUpperCase();
return switch (normalized) {
case "NOW", "SCHEDULED", "AUTO_REVIEW" -> normalized;
default -> "MANUAL";
};
}
private boolean isAppWideService(FeatureServiceEntity.ServiceType serviceType) {
return serviceType == FeatureServiceEntity.ServiceType.IM
|| serviceType == FeatureServiceEntity.ServiceType.PUSH
|| serviceType == FeatureServiceEntity.ServiceType.UPDATE;
}
2026-05-07 19:39:42 +08:00
private JsonNode readConfigNode(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
FeatureServiceEntity entity;
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
2026-05-07 19:39:42 +08:00
entity = repository.findByAppIdAndServiceType(appKey, serviceType)
.stream()
.findFirst()
.orElse(null);
} else {
2026-05-07 19:39:42 +08:00
entity = repository.findByAppIdAndPlatformAndServiceType(appKey, platform, serviceType)
.orElse(null);
}
if (entity == null || entity.getConfig() == null || entity.getConfig().isBlank()) {
return objectMapper.createObjectNode();
}
try {
JsonNode node = objectMapper.readTree(entity.getConfig());
return node == null ? objectMapper.createObjectNode() : node;
} catch (Exception e) {
return objectMapper.createObjectNode();
}
}
private void ensureObjectNode(ObjectNode root, String field) {
if (!root.has(field) || !root.get(field).isObject()) {
root.putObject(field);
}
}
private void putText(ObjectNode node, String field, String value) {
if (value != null) {
node.put(field, value.trim());
}
}
2026-04-21 22:07:29 +08:00
}