- 新增 XuqmGroup React Native SDK 使用文档,包含安装、初始化、HTTP客户端、IM模块、推送模块、版本管理等功能说明 - 添加 Android Gradle 发版任务脚本,支持构建发布 APK 并上传到更新服务 - 添加 HarmonyOS hvigorw 发版任务脚本,支持 HAP 包构建和上传功能 - 实现多平台版本检查、自动重连、灰度发布等发版流程自动化 - 集成商店提交、定时发布、Webhook 回调等发布后处理功能
177 行
8.4 KiB
Java
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);
|
|
}
|
|
}
|