package com.xuqm.tenant.controller; import com.xuqm.common.exception.BusinessException; import com.xuqm.common.model.ApiResponse; import com.xuqm.tenant.config.PrivateDeploymentProperties; import com.xuqm.tenant.dto.MigrateExportData; 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.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.time.LocalDateTime; 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 static final String SYSTEM_APP_KEY = "ak_409e217e4aa14254ad73ad3c"; private final PrivateDeploymentProperties deployProps; private final TenantRepository tenantRepository; private final AppRepository appRepository; private final FeatureServiceRepository featureServiceRepository; @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, TenantRepository tenantRepository, AppRepository appRepository, FeatureServiceRepository featureServiceRepository) { this.deployProps = deployProps; this.tenantRepository = tenantRepository; this.appRepository = appRepository; this.featureServiceRepository = featureServiceRepository; } @GetMapping("/status") public ResponseEntity>> status() { Map 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 body = new LinkedHashMap<>(); body.put("mode", deployProps.getMode()); body.put("tenantRegisterEnabled", deployProps.isTenantRegisterEnabled()); body.put("services", services); return ResponseEntity.ok(ApiResponse.success(body)); } /** Import tenant data exported from the public platform. PRIVATE mode only, no JWT required. */ @PostMapping("/migrate/import") @Transactional public ResponseEntity> importData(@RequestBody MigrateExportData data) { if (!deployProps.isPrivate()) { throw new BusinessException(403, "此接口仅在私有化部署可用"); } MigrateExportData.TenantData td = data.getTenant(); if (td == null) { throw new BusinessException("迁移数据不完整:缺少租户信息"); } // Clear existing tenant data, preserving the system app featureServiceRepository.deleteAllExcept(SYSTEM_APP_KEY); appRepository.deleteAllExcept(SYSTEM_APP_KEY); tenantRepository.deleteAll(); // Import tenant TenantEntity tenant = new TenantEntity(); tenant.setId(td.getId()); tenant.setUsername(td.getUsername()); tenant.setEmail(td.getEmail()); tenant.setNickname(td.getNickname()); tenant.setPhone(td.getPhone()); tenant.setPassword(td.getPasswordHash()); tenant.setType(TenantEntity.Type.MAIN); tenant.setStatus(TenantEntity.Status.ACTIVE); tenant.setCreatedAt(LocalDateTime.parse(td.getCreatedAt())); tenantRepository.save(tenant); // Bind system app to the imported tenant appRepository.findByAppKey(SYSTEM_APP_KEY).ifPresent(app -> { app.setTenantId(td.getId()); appRepository.save(app); }); // Import apps if (data.getApps() != null) { for (MigrateExportData.AppData ad : data.getApps()) { AppEntity app = new AppEntity(); app.setId(ad.getId()); app.setTenantId(td.getId()); app.setAppKey(ad.getAppKey()); app.setAppSecret(ad.getAppSecret()); app.setName(ad.getName()); app.setPackageName(ad.getPackageName()); app.setIosBundleId(ad.getIosBundleId()); app.setHarmonyBundleName(ad.getHarmonyBundleName()); app.setDescription(ad.getDescription()); app.setIconUrl(ad.getIconUrl()); app.setCreatedAt(LocalDateTime.parse(ad.getCreatedAt())); appRepository.save(app); } } // Import feature services if (data.getFeatureServices() != null) { for (MigrateExportData.FeatureServiceData fd : data.getFeatureServices()) { FeatureServiceEntity fs = new FeatureServiceEntity(); fs.setId(fd.getId()); fs.setAppKey(fd.getAppKey()); fs.setPlatform(FeatureServiceEntity.Platform.valueOf(fd.getPlatform())); fs.setServiceType(FeatureServiceEntity.ServiceType.valueOf(fd.getServiceType())); fs.setEnabled(fd.isEnabled()); fs.setSecretKey(fd.getSecretKey()); fs.setConfig(fd.getConfig()); fs.setCreatedAt(LocalDateTime.parse(fd.getCreatedAt())); featureServiceRepository.save(fs); } } return ResponseEntity.ok(ApiResponse.ok()); } private Map serviceStatus(boolean enabled, String baseUrl) { Map m = new LinkedHashMap<>(); m.put("enabled", enabled); m.put("baseUrl", enabled ? baseUrl : null); return m; } }