feat: private deployment server-side capabilities (P2)
- PrivateDeploymentProperties: DEPLOYMENT_MODE/ENABLE_*/TENANT_BOOTSTRAP_ENABLED config binding - PrivateTenantBootstrapInitializer: auto-create main tenant and app from env vars when PRIVATE mode, idempotent - AuthService: block registration with XUQM_PRIVATE_2001 when TENANT_REGISTER_ENABLED=false - EmailService: block REGISTER email verification in private mode - SdkConfigController: intersect DB feature flags with ENABLE_* deployment flags for runtime degradation - PrivateDeploymentController: GET /api/private/deployment/status public endpoint - SecurityConfig: permit /api/private/deployment/status without auth - application.yml: add deployment.* and tenant.bootstrap.* config sections with env var bindings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
父节点
4d54d2a4a4
当前提交
e5f0e7faea
@ -0,0 +1,50 @@
|
|||||||
|
package com.xuqm.tenant.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "deployment")
|
||||||
|
public class PrivateDeploymentProperties {
|
||||||
|
|
||||||
|
private String mode = "PUBLIC";
|
||||||
|
private boolean tenantRegisterEnabled = true;
|
||||||
|
private boolean tenantBootstrapEnabled = false;
|
||||||
|
private boolean enableIm = false;
|
||||||
|
private boolean enablePush = false;
|
||||||
|
private boolean enableUpdate = false;
|
||||||
|
private boolean enableLicense = false;
|
||||||
|
private boolean enableFile = true;
|
||||||
|
|
||||||
|
public boolean isPrivate() {
|
||||||
|
return "PRIVATE".equalsIgnoreCase(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMode() { return mode; }
|
||||||
|
public void setMode(String mode) { this.mode = mode; }
|
||||||
|
|
||||||
|
public boolean isTenantRegisterEnabled() { return tenantRegisterEnabled; }
|
||||||
|
public void setTenantRegisterEnabled(boolean tenantRegisterEnabled) {
|
||||||
|
this.tenantRegisterEnabled = tenantRegisterEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTenantBootstrapEnabled() { return tenantBootstrapEnabled; }
|
||||||
|
public void setTenantBootstrapEnabled(boolean tenantBootstrapEnabled) {
|
||||||
|
this.tenantBootstrapEnabled = tenantBootstrapEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnableIm() { return enableIm; }
|
||||||
|
public void setEnableIm(boolean enableIm) { this.enableIm = enableIm; }
|
||||||
|
|
||||||
|
public boolean isEnablePush() { return enablePush; }
|
||||||
|
public void setEnablePush(boolean enablePush) { this.enablePush = enablePush; }
|
||||||
|
|
||||||
|
public boolean isEnableUpdate() { return enableUpdate; }
|
||||||
|
public void setEnableUpdate(boolean enableUpdate) { this.enableUpdate = enableUpdate; }
|
||||||
|
|
||||||
|
public boolean isEnableLicense() { return enableLicense; }
|
||||||
|
public void setEnableLicense(boolean enableLicense) { this.enableLicense = enableLicense; }
|
||||||
|
|
||||||
|
public boolean isEnableFile() { return enableFile; }
|
||||||
|
public void setEnableFile(boolean enableFile) { this.enableFile = enableFile; }
|
||||||
|
}
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
package com.xuqm.tenant.config;
|
||||||
|
|
||||||
|
import com.xuqm.tenant.entity.AppEntity;
|
||||||
|
import com.xuqm.tenant.entity.TenantEntity;
|
||||||
|
import com.xuqm.tenant.repository.AppRepository;
|
||||||
|
import com.xuqm.tenant.repository.TenantRepository;
|
||||||
|
import com.xuqm.tenant.service.SdkAppProvisioningService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.ApplicationArguments;
|
||||||
|
import org.springframework.boot.ApplicationRunner;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs only when DEPLOYMENT_MODE=PRIVATE.
|
||||||
|
* Ensures the bootstrap main tenant and app exist without overwriting existing data.
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Order(20)
|
||||||
|
public class PrivateTenantBootstrapInitializer implements ApplicationRunner {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(PrivateTenantBootstrapInitializer.class);
|
||||||
|
|
||||||
|
private final PrivateDeploymentProperties deployProps;
|
||||||
|
private final TenantRepository tenantRepository;
|
||||||
|
private final AppRepository appRepository;
|
||||||
|
private final SdkAppProvisioningService provisioningService;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Value("${tenant.bootstrap.email:admin@customer.com}")
|
||||||
|
private String bootstrapEmail;
|
||||||
|
|
||||||
|
@Value("${tenant.bootstrap.password:ChangeMe@2026}")
|
||||||
|
private String bootstrapPassword;
|
||||||
|
|
||||||
|
@Value("${tenant.bootstrap.username:admin}")
|
||||||
|
private String bootstrapUsername;
|
||||||
|
|
||||||
|
@Value("${sdk.bootstrap-app-key:ak_private_default}")
|
||||||
|
private String bootstrapAppKey;
|
||||||
|
|
||||||
|
public PrivateTenantBootstrapInitializer(PrivateDeploymentProperties deployProps,
|
||||||
|
TenantRepository tenantRepository,
|
||||||
|
AppRepository appRepository,
|
||||||
|
SdkAppProvisioningService provisioningService,
|
||||||
|
PasswordEncoder passwordEncoder) {
|
||||||
|
this.deployProps = deployProps;
|
||||||
|
this.tenantRepository = tenantRepository;
|
||||||
|
this.appRepository = appRepository;
|
||||||
|
this.provisioningService = provisioningService;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void run(ApplicationArguments args) {
|
||||||
|
if (!deployProps.isPrivate() || !deployProps.isTenantBootstrapEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TenantEntity tenant = tenantRepository.findByEmail(bootstrapEmail)
|
||||||
|
.or(() -> tenantRepository.findByUsername(bootstrapUsername))
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
if (tenant == null) {
|
||||||
|
tenant = new TenantEntity();
|
||||||
|
tenant.setId(UUID.randomUUID().toString());
|
||||||
|
tenant.setUsername(bootstrapUsername);
|
||||||
|
tenant.setPassword(passwordEncoder.encode(bootstrapPassword));
|
||||||
|
tenant.setEmail(bootstrapEmail);
|
||||||
|
tenant.setNickname("Admin");
|
||||||
|
tenant.setType(TenantEntity.Type.MAIN);
|
||||||
|
tenant.setStatus(TenantEntity.Status.ACTIVE);
|
||||||
|
tenant.setCreatedAt(LocalDateTime.now());
|
||||||
|
tenantRepository.save(tenant);
|
||||||
|
log.info("[PRIVATE] Bootstrap tenant created: {}", bootstrapEmail);
|
||||||
|
} else {
|
||||||
|
log.info("[PRIVATE] Bootstrap tenant already exists: {}", bootstrapEmail);
|
||||||
|
}
|
||||||
|
|
||||||
|
AppEntity app = appRepository.findByAppKey(bootstrapAppKey).orElse(null);
|
||||||
|
if (app == null) {
|
||||||
|
provisioningService.ensureApp(bootstrapAppKey, true);
|
||||||
|
log.info("[PRIVATE] Bootstrap app created: {}", bootstrapAppKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -37,6 +37,7 @@ public class SecurityConfig {
|
|||||||
"/api/sdk/**",
|
"/api/sdk/**",
|
||||||
"/api/internal/sdk/**",
|
"/api/internal/sdk/**",
|
||||||
"/api/internal/im/**",
|
"/api/internal/im/**",
|
||||||
|
"/api/private/deployment/status",
|
||||||
"/actuator/health",
|
"/actuator/health",
|
||||||
"/actuator/info"
|
"/actuator/info"
|
||||||
).permitAll()
|
).permitAll()
|
||||||
|
|||||||
@ -0,0 +1,70 @@
|
|||||||
|
package com.xuqm.tenant.controller;
|
||||||
|
|
||||||
|
import com.xuqm.common.model.ApiResponse;
|
||||||
|
import com.xuqm.tenant.config.PrivateDeploymentProperties;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public endpoint — returns deployment mode and enabled service capabilities.
|
||||||
|
* Safe to expose without authentication: contains no credentials.
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/private/deployment")
|
||||||
|
public class PrivateDeploymentController {
|
||||||
|
|
||||||
|
private final PrivateDeploymentProperties deployProps;
|
||||||
|
|
||||||
|
@Value("${sdk.im-api-url:}")
|
||||||
|
private String imApiUrl;
|
||||||
|
|
||||||
|
@Value("${sdk.file-service-url:}")
|
||||||
|
private String fileServiceUrl;
|
||||||
|
|
||||||
|
@Value("${deployment.im-domain:}")
|
||||||
|
private String imDomain;
|
||||||
|
|
||||||
|
@Value("${deployment.push-domain:}")
|
||||||
|
private String pushDomain;
|
||||||
|
|
||||||
|
@Value("${deployment.update-domain:}")
|
||||||
|
private String updateDomain;
|
||||||
|
|
||||||
|
@Value("${deployment.license-domain:}")
|
||||||
|
private String licenseDomain;
|
||||||
|
|
||||||
|
public PrivateDeploymentController(PrivateDeploymentProperties deployProps) {
|
||||||
|
this.deployProps = deployProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/status")
|
||||||
|
public ResponseEntity<ApiResponse<Map<String, Object>>> status() {
|
||||||
|
Map<String, Object> services = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
services.put("file", serviceStatus(deployProps.isEnableFile(), fileServiceUrl));
|
||||||
|
services.put("im", serviceStatus(deployProps.isEnableIm(), imDomain));
|
||||||
|
services.put("push", serviceStatus(deployProps.isEnablePush(), pushDomain));
|
||||||
|
services.put("update", serviceStatus(deployProps.isEnableUpdate(), updateDomain));
|
||||||
|
services.put("license", serviceStatus(deployProps.isEnableLicense(), licenseDomain));
|
||||||
|
|
||||||
|
Map<String, Object> body = new LinkedHashMap<>();
|
||||||
|
body.put("mode", deployProps.getMode());
|
||||||
|
body.put("tenantRegisterEnabled", deployProps.isTenantRegisterEnabled());
|
||||||
|
body.put("services", services);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> serviceStatus(boolean enabled, String baseUrl) {
|
||||||
|
Map<String, Object> m = new LinkedHashMap<>();
|
||||||
|
m.put("enabled", enabled);
|
||||||
|
m.put("baseUrl", enabled ? baseUrl : null);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@ package com.xuqm.tenant.controller;
|
|||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.xuqm.common.model.ApiResponse;
|
import com.xuqm.common.model.ApiResponse;
|
||||||
|
import com.xuqm.tenant.config.PrivateDeploymentProperties;
|
||||||
import com.xuqm.tenant.entity.AppEntity;
|
import com.xuqm.tenant.entity.AppEntity;
|
||||||
import com.xuqm.tenant.entity.FeatureServiceEntity;
|
import com.xuqm.tenant.entity.FeatureServiceEntity;
|
||||||
import com.xuqm.tenant.repository.FeatureServiceRepository;
|
import com.xuqm.tenant.repository.FeatureServiceRepository;
|
||||||
@ -24,6 +25,7 @@ public class SdkConfigController {
|
|||||||
private final FeatureServiceRepository featureServiceRepository;
|
private final FeatureServiceRepository featureServiceRepository;
|
||||||
private final SdkAppProvisioningService sdkAppProvisioningService;
|
private final SdkAppProvisioningService sdkAppProvisioningService;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
private final PrivateDeploymentProperties deployProps;
|
||||||
|
|
||||||
@Value("${sdk.im-ws-url:wss://im.dev.xuqinmin.com/ws/im}")
|
@Value("${sdk.im-ws-url:wss://im.dev.xuqinmin.com/ws/im}")
|
||||||
private String imWsUrl;
|
private String imWsUrl;
|
||||||
@ -36,10 +38,12 @@ public class SdkConfigController {
|
|||||||
|
|
||||||
public SdkConfigController(FeatureServiceRepository featureServiceRepository,
|
public SdkConfigController(FeatureServiceRepository featureServiceRepository,
|
||||||
SdkAppProvisioningService sdkAppProvisioningService,
|
SdkAppProvisioningService sdkAppProvisioningService,
|
||||||
ObjectMapper objectMapper) {
|
ObjectMapper objectMapper,
|
||||||
|
PrivateDeploymentProperties deployProps) {
|
||||||
this.featureServiceRepository = featureServiceRepository;
|
this.featureServiceRepository = featureServiceRepository;
|
||||||
this.sdkAppProvisioningService = sdkAppProvisioningService;
|
this.sdkAppProvisioningService = sdkAppProvisioningService;
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
|
this.deployProps = deployProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,9 +62,10 @@ public class SdkConfigController {
|
|||||||
AppEntity app = sdkAppProvisioningService.resolveApp(appKey);
|
AppEntity app = sdkAppProvisioningService.resolveApp(appKey);
|
||||||
List<FeatureServiceEntity> features = featureServiceRepository.findByAppKey(app.getAppKey());
|
List<FeatureServiceEntity> features = featureServiceRepository.findByAppKey(app.getAppKey());
|
||||||
|
|
||||||
boolean imEnabled = features.stream()
|
// In private deployments, intersect DB feature flags with deployment-level service availability
|
||||||
|
boolean imEnabled = deployProps.isEnableIm() && features.stream()
|
||||||
.anyMatch(f -> f.getServiceType() == FeatureServiceEntity.ServiceType.IM && f.isEnabled());
|
.anyMatch(f -> f.getServiceType() == FeatureServiceEntity.ServiceType.IM && f.isEnabled());
|
||||||
boolean pushEnabled = features.stream()
|
boolean pushEnabled = deployProps.isEnablePush() && features.stream()
|
||||||
.anyMatch(f -> f.getServiceType() == FeatureServiceEntity.ServiceType.PUSH && f.isEnabled());
|
.anyMatch(f -> f.getServiceType() == FeatureServiceEntity.ServiceType.PUSH && f.isEnabled());
|
||||||
JsonNode updateConfig = featureServiceRepository
|
JsonNode updateConfig = featureServiceRepository
|
||||||
.findByAppKeyAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.UPDATE)
|
.findByAppKeyAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.UPDATE)
|
||||||
@ -70,7 +75,7 @@ public class SdkConfigController {
|
|||||||
.findByAppKeyAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.PUSH)
|
.findByAppKeyAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.PUSH)
|
||||||
.map(feature -> parseConfig(feature.getConfig()))
|
.map(feature -> parseConfig(feature.getConfig()))
|
||||||
.orElseGet(objectMapper::createObjectNode);
|
.orElseGet(objectMapper::createObjectNode);
|
||||||
boolean updateEnabled = featureServiceRepository
|
boolean updateEnabled = deployProps.isEnableUpdate() && featureServiceRepository
|
||||||
.findByAppKeyAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.UPDATE)
|
.findByAppKeyAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.UPDATE)
|
||||||
.map(FeatureServiceEntity::isEnabled)
|
.map(FeatureServiceEntity::isEnabled)
|
||||||
.orElse(false);
|
.orElse(false);
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.xuqm.tenant.service;
|
|||||||
|
|
||||||
import com.xuqm.common.exception.BusinessException;
|
import com.xuqm.common.exception.BusinessException;
|
||||||
import com.xuqm.common.security.JwtUtil;
|
import com.xuqm.common.security.JwtUtil;
|
||||||
|
import com.xuqm.tenant.config.PrivateDeploymentProperties;
|
||||||
import com.xuqm.tenant.dto.LoginRequest;
|
import com.xuqm.tenant.dto.LoginRequest;
|
||||||
import com.xuqm.tenant.dto.RegisterRequest;
|
import com.xuqm.tenant.dto.RegisterRequest;
|
||||||
import com.xuqm.tenant.entity.TenantEntity;
|
import com.xuqm.tenant.entity.TenantEntity;
|
||||||
@ -23,19 +24,25 @@ public class AuthService {
|
|||||||
private final JwtUtil jwtUtil;
|
private final JwtUtil jwtUtil;
|
||||||
private final EmailService emailService;
|
private final EmailService emailService;
|
||||||
private final StringRedisTemplate redis;
|
private final StringRedisTemplate redis;
|
||||||
|
private final PrivateDeploymentProperties deployProps;
|
||||||
|
|
||||||
private static final String CAPTCHA_PREFIX = "captcha:";
|
private static final String CAPTCHA_PREFIX = "captcha:";
|
||||||
|
|
||||||
public AuthService(TenantRepository tenantRepository, PasswordEncoder passwordEncoder,
|
public AuthService(TenantRepository tenantRepository, PasswordEncoder passwordEncoder,
|
||||||
JwtUtil jwtUtil, EmailService emailService, StringRedisTemplate redis) {
|
JwtUtil jwtUtil, EmailService emailService, StringRedisTemplate redis,
|
||||||
|
PrivateDeploymentProperties deployProps) {
|
||||||
this.tenantRepository = tenantRepository;
|
this.tenantRepository = tenantRepository;
|
||||||
this.passwordEncoder = passwordEncoder;
|
this.passwordEncoder = passwordEncoder;
|
||||||
this.jwtUtil = jwtUtil;
|
this.jwtUtil = jwtUtil;
|
||||||
this.emailService = emailService;
|
this.emailService = emailService;
|
||||||
this.redis = redis;
|
this.redis = redis;
|
||||||
|
this.deployProps = deployProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void register(RegisterRequest req) {
|
public void register(RegisterRequest req) {
|
||||||
|
if (!deployProps.isTenantRegisterEnabled()) {
|
||||||
|
throw new BusinessException(403, "[XUQM_PRIVATE_2001] 私有化部署不支持注册新租户,请联系管理员");
|
||||||
|
}
|
||||||
emailService.verify(req.email(), req.emailCode(), "REGISTER");
|
emailService.verify(req.email(), req.emailCode(), "REGISTER");
|
||||||
|
|
||||||
if (tenantRepository.existsByUsername(req.username())) {
|
if (tenantRepository.existsByUsername(req.username())) {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package com.xuqm.tenant.service;
|
package com.xuqm.tenant.service;
|
||||||
|
|
||||||
import com.xuqm.common.exception.BusinessException;
|
import com.xuqm.common.exception.BusinessException;
|
||||||
|
import com.xuqm.tenant.config.PrivateDeploymentProperties;
|
||||||
import com.xuqm.tenant.entity.EmailVerificationEntity;
|
import com.xuqm.tenant.entity.EmailVerificationEntity;
|
||||||
import com.xuqm.tenant.repository.EmailVerificationRepository;
|
import com.xuqm.tenant.repository.EmailVerificationRepository;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
@ -18,6 +19,7 @@ public class EmailService {
|
|||||||
|
|
||||||
private final JavaMailSender mailSender;
|
private final JavaMailSender mailSender;
|
||||||
private final EmailVerificationRepository verificationRepository;
|
private final EmailVerificationRepository verificationRepository;
|
||||||
|
private final PrivateDeploymentProperties deployProps;
|
||||||
|
|
||||||
@Value("${spring.mail.username}")
|
@Value("${spring.mail.username}")
|
||||||
private String fromAddress;
|
private String fromAddress;
|
||||||
@ -27,13 +29,18 @@ public class EmailService {
|
|||||||
|
|
||||||
private static final SecureRandom random = new SecureRandom();
|
private static final SecureRandom random = new SecureRandom();
|
||||||
|
|
||||||
public EmailService(JavaMailSender mailSender, EmailVerificationRepository verificationRepository) {
|
public EmailService(JavaMailSender mailSender, EmailVerificationRepository verificationRepository,
|
||||||
|
PrivateDeploymentProperties deployProps) {
|
||||||
this.mailSender = mailSender;
|
this.mailSender = mailSender;
|
||||||
this.verificationRepository = verificationRepository;
|
this.verificationRepository = verificationRepository;
|
||||||
|
this.deployProps = deployProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void sendVerificationCode(String email, String purpose) {
|
public void sendVerificationCode(String email, String purpose) {
|
||||||
|
if ("REGISTER".equals(purpose) && !deployProps.isTenantRegisterEnabled()) {
|
||||||
|
throw new BusinessException(403, "[XUQM_PRIVATE_2001] 私有化部署不支持注册新租户");
|
||||||
|
}
|
||||||
String code = String.format("%06d", random.nextInt(1_000_000));
|
String code = String.format("%06d", random.nextInt(1_000_000));
|
||||||
|
|
||||||
EmailVerificationEntity entity = new EmailVerificationEntity();
|
EmailVerificationEntity entity = new EmailVerificationEntity();
|
||||||
|
|||||||
@ -97,3 +97,23 @@ sdk:
|
|||||||
im-platform-events-recipient-user: ${SDK_IM_PLATFORM_EVENTS_RECIPIENT_USER:platform}
|
im-platform-events-recipient-user: ${SDK_IM_PLATFORM_EVENTS_RECIPIENT_USER:platform}
|
||||||
im-platform-events-admin-user: ${SDK_IM_PLATFORM_EVENTS_ADMIN_USER:admin}
|
im-platform-events-admin-user: ${SDK_IM_PLATFORM_EVENTS_ADMIN_USER:admin}
|
||||||
im-platform-app-key: ${SDK_IM_PLATFORM_APP_KEY:ak_409e217e4aa14254ad73ad3c}
|
im-platform-app-key: ${SDK_IM_PLATFORM_APP_KEY:ak_409e217e4aa14254ad73ad3c}
|
||||||
|
|
||||||
|
deployment:
|
||||||
|
mode: ${DEPLOYMENT_MODE:PUBLIC}
|
||||||
|
tenant-register-enabled: ${TENANT_REGISTER_ENABLED:true}
|
||||||
|
tenant-bootstrap-enabled: ${TENANT_BOOTSTRAP_ENABLED:false}
|
||||||
|
enable-im: ${ENABLE_IM:false}
|
||||||
|
enable-push: ${ENABLE_PUSH:false}
|
||||||
|
enable-update: ${ENABLE_UPDATE:false}
|
||||||
|
enable-license: ${ENABLE_LICENSE:false}
|
||||||
|
enable-file: ${ENABLE_FILE:true}
|
||||||
|
im-domain: ${IM_DOMAIN:}
|
||||||
|
push-domain: ${PUSH_DOMAIN:}
|
||||||
|
update-domain: ${UPDATE_DOMAIN:}
|
||||||
|
license-domain: ${LICENSE_DOMAIN:}
|
||||||
|
|
||||||
|
tenant:
|
||||||
|
bootstrap:
|
||||||
|
email: ${TENANT_BOOTSTRAP_EMAIL:admin@customer.com}
|
||||||
|
username: ${TENANT_BOOTSTRAP_USERNAME:admin}
|
||||||
|
password: ${TENANT_BOOTSTRAP_PASSWORD:ChangeMe@2026}
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户