XuqmGroup-Server/tenant-service/src/main/java/com/xuqm/tenant/service/SdkAppProvisioningService.java
XuqmGroup f5a1eb4470 docs(sdk): 添加 React Native SDK 文档和 Android/HarmonyOS 发版脚本
- 新增 XuqmGroup React Native SDK 使用文档,包含安装、初始化、HTTP客户端、IM模块、推送模块、版本管理等功能说明
- 添加 Android Gradle 发版任务脚本,支持构建发布 APK 并上传到更新服务
- 添加 HarmonyOS hvigorw 发版任务脚本,支持 HAP 包构建和上传功能
- 实现多平台版本检查、自动重连、灰度发布等发版流程自动化
- 集成商店提交、定时发布、Webhook 回调等发布后处理功能
2026-04-29 17:35:52 +08:00

177 行
8.4 KiB
Java

package com.xuqm.tenant.service;
import com.xuqm.common.exception.BusinessException;
import com.xuqm.tenant.entity.AppEntity;
import com.xuqm.tenant.entity.FeatureServiceEntity;
import com.xuqm.tenant.entity.TenantEntity;
import com.xuqm.tenant.repository.AppRepository;
import com.xuqm.tenant.repository.FeatureServiceRepository;
import com.xuqm.tenant.repository.TenantRepository;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.List;
import java.util.UUID;
@Service
public class SdkAppProvisioningService {
private static final SecureRandom RANDOM = new SecureRandom();
private final AppRepository appRepository;
private final TenantRepository tenantRepository;
private final FeatureServiceRepository featureServiceRepository;
private final PasswordEncoder passwordEncoder;
@Value("${sdk.bootstrap-app-key:ak_demo_chat}")
private String bootstrapAppKey;
@Value("${sdk.bootstrap-app-name:Demo Chat}")
private String bootstrapAppName;
@Value("${sdk.bootstrap-app-package:com.xuqm.demo}")
private String bootstrapAppPackage;
@Value("${sdk.bootstrap-app-description:XuqmGroup demo app}")
private String bootstrapAppDescription;
public SdkAppProvisioningService(AppRepository appRepository,
TenantRepository tenantRepository,
FeatureServiceRepository featureServiceRepository,
PasswordEncoder passwordEncoder) {
this.appRepository = appRepository;
this.tenantRepository = tenantRepository;
this.featureServiceRepository = featureServiceRepository;
this.passwordEncoder = passwordEncoder;
}
@Transactional
public AppEntity ensureBootstrapApp() {
return ensureApp(bootstrapAppKey, true);
}
@Transactional
public AppEntity resolveApp(String appId) {
return appRepository.findByAppKey(appId)
.or(() -> appRepository.findById(appId))
.orElseGet(() -> {
if (!bootstrapAppKey.equals(appId)) {
throw new BusinessException(404, "App not found: " + appId);
}
return ensureApp(appId, true);
});
}
@Transactional
public AppEntity ensureApp(String appKey, boolean bootstrapDefaults) {
AppEntity existing = appRepository.findByAppKey(appKey).orElse(null);
if (existing != null) {
if (bootstrapDefaults) {
ensureFeatureDefaults(existing);
}
return existing;
}
TenantEntity owner = resolveOwnerTenant();
AppEntity app = new AppEntity();
app.setId(UUID.randomUUID().toString());
app.setTenantId(owner.getId());
app.setPackageName(bootstrapAppPackage);
app.setName(bootstrapAppName);
app.setDescription(bootstrapAppDescription);
app.setIconUrl(null);
app.setAppKey(appKey);
app.setAppSecret(generateSecret());
app.setCreatedAt(LocalDateTime.now());
app = appRepository.save(app);
if (bootstrapDefaults) {
ensureFeatureDefaults(app);
}
return app;
}
private TenantEntity resolveOwnerTenant() {
return tenantRepository.findFirstByOrderByCreatedAtAsc()
.orElseGet(this::createBootstrapTenant);
}
private TenantEntity createBootstrapTenant() {
TenantEntity tenant = new TenantEntity();
tenant.setId(UUID.randomUUID().toString());
tenant.setUsername("system");
tenant.setPassword(passwordEncoder.encode(generateSecret()));
tenant.setEmail("system@xuqinmin.com");
tenant.setNickname("System");
tenant.setPhone(null);
tenant.setType(TenantEntity.Type.MAIN);
tenant.setStatus(TenantEntity.Status.ACTIVE);
tenant.setParentId(null);
tenant.setCreatedAt(LocalDateTime.now());
return tenantRepository.save(tenant);
}
private void ensureFeatureDefaults(AppEntity app) {
featureServiceRepository.findByAppIdAndServiceType(app.getAppKey(), FeatureServiceEntity.ServiceType.IM)
.stream()
.findFirst()
.orElseGet(() -> {
FeatureServiceEntity feature = new FeatureServiceEntity();
feature.setId(UUID.randomUUID().toString());
feature.setAppId(app.getAppKey());
feature.setPlatform(FeatureServiceEntity.Platform.ANDROID);
feature.setServiceType(FeatureServiceEntity.ServiceType.IM);
feature.setEnabled(true);
feature.setConfig("""
{"allowStrangerMessage":false,"allowFriendRequest":true,"friendRequestMode":"REQUIRE_CONFIRM","allowGroupJoinRequest":true,"blacklistSendSuccess":true,"messageRecallMinutes":2,"historyRetentionDays":7,"conversationPullLimit":100,"multiClientConversationDeleteSync":false}
""".trim());
feature.setCreatedAt(LocalDateTime.now());
return featureServiceRepository.save(feature);
});
for (FeatureServiceEntity.Platform platform : List.of(
FeatureServiceEntity.Platform.ANDROID,
FeatureServiceEntity.Platform.IOS,
FeatureServiceEntity.Platform.HARMONY)) {
for (FeatureServiceEntity.ServiceType serviceType : List.of(
FeatureServiceEntity.ServiceType.PUSH,
FeatureServiceEntity.ServiceType.UPDATE)) {
featureServiceRepository.findByAppIdAndPlatformAndServiceType(app.getAppKey(), platform, serviceType)
.orElseGet(() -> {
FeatureServiceEntity feature = new FeatureServiceEntity();
feature.setId(UUID.randomUUID().toString());
feature.setAppId(app.getAppKey());
feature.setPlatform(platform);
feature.setServiceType(serviceType);
feature.setEnabled(true);
feature.setConfig(serviceType == FeatureServiceEntity.ServiceType.UPDATE
? switch (platform) {
case ANDROID -> """
{"defaultStoreTargets":[],"defaultPublishMode":"MANUAL","defaultPublishImmediately":false,"defaultScheduledPublishAt":"","defaultAutoPublishAfterReview":false,"defaultWebhookUrl":"","defaultForceUpdate":false,"defaultGrayEnabled":false,"defaultGrayPercent":0,"defaultPackageName":"","defaultAppStoreUrl":"","defaultMarketUrl":""}
""".trim();
case IOS -> """
{"defaultStoreTargets":["APP_STORE"],"defaultPublishMode":"MANUAL","defaultPublishImmediately":false,"defaultScheduledPublishAt":"","defaultAutoPublishAfterReview":false,"defaultWebhookUrl":"","defaultForceUpdate":false,"defaultGrayEnabled":false,"defaultGrayPercent":0,"defaultPackageName":"","defaultAppStoreUrl":"","defaultMarketUrl":""}
""".trim();
case HARMONY -> """
{"defaultStoreTargets":[],"defaultPublishMode":"MANUAL","defaultPublishImmediately":false,"defaultScheduledPublishAt":"","defaultAutoPublishAfterReview":false,"defaultWebhookUrl":"","defaultForceUpdate":false,"defaultGrayEnabled":false,"defaultGrayPercent":0,"defaultPackageName":"","defaultAppStoreUrl":"","defaultMarketUrl":""}
""".trim();
} : null);
feature.setCreatedAt(LocalDateTime.now());
return featureServiceRepository.save(feature);
});
}
}
}
private String generateSecret() {
byte[] bytes = new byte[32];
RANDOM.nextBytes(bytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
}
}