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

393 行
18 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.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;
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 ObjectMapper objectMapper;
2026-04-21 22:07:29 +08:00
public FeatureServiceManager(FeatureServiceRepository repository,
ServiceActivationRequestRepository requestRepository,
ObjectMapper objectMapper) {
2026-04-21 22:07:29 +08:00
this.repository = repository;
this.requestRepository = requestRepository;
this.objectMapper = objectMapper;
2026-04-21 22:07:29 +08:00
}
public List<FeatureServiceEntity> listByApp(String appId) {
List<FeatureServiceEntity> services = repository.findByAppId(appId);
if (services.isEmpty()) {
return services;
}
List<FeatureServiceEntity> normalized = new ArrayList<>();
services.stream()
.filter(service -> service.getServiceType() == FeatureServiceEntity.ServiceType.IM)
.findFirst()
.ifPresent(normalized::add);
services.stream()
.filter(service -> service.getServiceType() != FeatureServiceEntity.ServiceType.IM)
.forEach(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 is app-wide, so duplicate checks ignore platform.
*/
@Transactional
public ServiceActivationRequestEntity submitActivationRequest(
String appId,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType,
String applyReason) {
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
requestRepository.findFirstByAppIdAndServiceTypeOrderByCreatedAtDesc(appId, serviceType)
.ifPresent(req -> {
if (req.getStatus() == Status.PENDING) {
throw new BusinessException(400, "已有待审核的开通申请,请等待运营人员处理");
}
});
} else {
requestRepository.findFirstByAppIdAndPlatformAndServiceTypeOrderByCreatedAtDesc(appId, platform, serviceType)
.ifPresent(req -> {
if (req.getStatus() == Status.PENDING) {
throw new BusinessException(400, "已有待审核的开通申请,请等待运营人员处理");
}
});
}
ServiceActivationRequestEntity req = new ServiceActivationRequestEntity();
req.setId(UUID.randomUUID().toString());
req.setAppId(appId);
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
public FeatureServiceEntity disable(String appId, FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
List<FeatureServiceEntity> services = repository.findByAppIdAndServiceType(appId, 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
.findByAppIdAndPlatformAndServiceType(appId, 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);
if (req.getServiceType() == FeatureServiceEntity.ServiceType.IM) {
List<FeatureServiceEntity> services = repository.findByAppIdAndServiceType(req.getAppId(), req.getServiceType());
if (services.isEmpty()) {
FeatureServiceEntity created = new FeatureServiceEntity();
created.setId(UUID.randomUUID().toString());
created.setAppId(req.getAppId());
created.setPlatform(req.getPlatform());
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(req.getAppId(), req.getPlatform(), req.getServiceType())
2026-04-21 22:07:29 +08:00
.orElseGet(() -> {
FeatureServiceEntity e = new FeatureServiceEntity();
e.setId(UUID.randomUUID().toString());
e.setAppId(req.getAppId());
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);
}
public List<ServiceActivationRequestEntity> listRequestsByApp(String appId) {
return requestRepository.findByAppIdOrderByCreatedAtDesc(appId);
2026-04-21 22:07:29 +08:00
}
public FeatureServiceEntity getOrFail(String appId, FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
return repository.findByAppIdAndServiceType(appId, serviceType)
.stream()
.findFirst()
.orElseThrow(() -> new BusinessException(404, "服务未配置"));
}
2026-04-21 22:07:29 +08:00
return repository.findByAppIdAndPlatformAndServiceType(appId, platform, serviceType)
.orElseThrow(() -> new BusinessException(404, "服务未配置"));
}
@Transactional
public FeatureServiceEntity updateConfig(String appId,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType,
String config) {
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
List<FeatureServiceEntity> services = repository.findByAppIdAndServiceType(appId, serviceType);
if (services.isEmpty()) {
throw new BusinessException(404, "服务未配置");
}
services.forEach(service -> service.setConfig(config));
repository.saveAll(services);
return services.get(0);
}
FeatureServiceEntity entity = getOrFail(appId, platform, serviceType);
entity.setConfig(config);
return repository.save(entity);
}
public boolean allowStrangerMessage(String appId,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
return readConfigNode(appId, platform, serviceType).path("allowStrangerMessage").asBoolean(false);
}
public boolean allowFriendRequest(String appId,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
return readConfigNode(appId, platform, serviceType).path("allowFriendRequest").asBoolean(true);
}
public boolean allowGroupJoinRequest(String appId,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
return readConfigNode(appId, platform, serviceType).path("allowGroupJoinRequest").asBoolean(true);
}
public String friendRequestMode(String appId,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
String mode = readConfigNode(appId, platform, serviceType).path("friendRequestMode").asText("");
return switch (mode == null ? "" : mode.trim().toUpperCase()) {
case "DIRECT_ACCEPT", "DISALLOW" -> mode.trim().toUpperCase();
default -> "REQUIRE_CONFIRM";
};
}
public boolean blacklistSendSuccess(String appId,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
return readConfigNode(appId, platform, serviceType).path("blacklistSendSuccess").asBoolean(true);
}
public int messageRecallMinutes(String appId,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
int minutes = readConfigNode(appId, platform, serviceType).path("messageRecallMinutes").asInt(2);
return Math.max(minutes, 0);
}
public int conversationPullLimit(String appId,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
int limit = readConfigNode(appId, platform, serviceType).path("conversationPullLimit").asInt(100);
return Math.min(Math.max(limit, 1), 500);
}
public int historyRetentionDays(String appId,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
int days = readConfigNode(appId, platform, serviceType).path("historyRetentionDays").asInt(7);
return Math.max(days, 1);
}
public String buildImConfig(String appId,
FeatureServiceEntity.Platform platform,
Boolean allowStrangerMessage,
Boolean allowFriendRequest,
String friendRequestMode,
Boolean allowGroupJoinRequest,
Boolean blacklistSendSuccess,
Integer messageRecallMinutes,
Integer historyRetentionDays,
Integer conversationPullLimit,
Boolean multiClientConversationDeleteSync) {
ObjectNode node = readConfigNode(appId, 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 (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 (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("multiClientConversationDeleteSync", false);
return node.toString();
}
private String normalizeFriendRequestMode(String mode) {
String normalized = mode == null ? "" : mode.trim().toUpperCase();
return switch (normalized) {
case "DIRECT_ACCEPT", "DISALLOW" -> normalized;
default -> "REQUIRE_CONFIRM";
};
}
private JsonNode readConfigNode(String appId,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
FeatureServiceEntity entity;
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
entity = repository.findByAppIdAndServiceType(appId, serviceType)
.stream()
.findFirst()
.orElse(null);
} else {
entity = repository.findByAppIdAndPlatformAndServiceType(appId, 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();
}
}
2026-04-21 22:07:29 +08:00
}