diff --git a/tenant-service/src/main/java/com/xuqm/tenant/controller/AppController.java b/tenant-service/src/main/java/com/xuqm/tenant/controller/AppController.java index ecce96f..d6400d1 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/controller/AppController.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/controller/AppController.java @@ -11,11 +11,8 @@ import com.xuqm.tenant.service.AppService; import com.xuqm.tenant.service.AppUserClient; import com.xuqm.tenant.service.EmailService; import com.xuqm.tenant.service.FeatureServiceManager; -import com.xuqm.common.security.LicenseFileCrypto; -import com.xuqm.tenant.config.PrivateDeploymentProperties; import com.xuqm.tenant.service.OperationLogService; import jakarta.validation.Valid; -import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ContentDisposition; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -44,24 +41,18 @@ public class AppController { private final TenantRepository tenantRepository; private final FeatureServiceManager featureServiceManager; private final AppUserClient appUserClient; - private final PrivateDeploymentProperties deployProps; - - @Value("${license.public-base-url:https://auth.dev.xuqinmin.com/}") - private String licensePublicBaseUrl; public AppController(AppService appService, EmailService emailService, OperationLogService operationLogService, TenantRepository tenantRepository, FeatureServiceManager featureServiceManager, - AppUserClient appUserClient, - PrivateDeploymentProperties deployProps) { + AppUserClient appUserClient) { this.appService = appService; this.emailService = emailService; this.operationLogService = operationLogService; this.tenantRepository = tenantRepository; this.featureServiceManager = featureServiceManager; this.appUserClient = appUserClient; - this.deployProps = deployProps; } @GetMapping @@ -172,22 +163,10 @@ public class AppController { public ResponseEntity downloadLicenseFile(@PathVariable String appKey, @AuthenticationPrincipal String tenantId) { AppEntity app = appService.getByAppKey(appKey, tenantId); - Map payload = new java.util.LinkedHashMap<>(); - payload.put("appKey", app.getAppKey()); - payload.put("appName", app.getName()); - payload.put("packageName", app.getPackageName()); - if (app.getIosBundleId() != null && !app.getIosBundleId().isBlank()) { - payload.put("iosBundleId", app.getIosBundleId()); + String encrypted = app.getLicenseFileContent(); + if (encrypted == null || encrypted.isBlank()) { + throw new BusinessException("License file not generated yet"); } - if (app.getHarmonyBundleName() != null && !app.getHarmonyBundleName().isBlank()) { - payload.put("harmonyBundleName", app.getHarmonyBundleName()); - } - payload.put("baseUrl", normalizeBaseUrl(licensePublicBaseUrl)); - if (deployProps.isPrivate()) { - payload.put("serverUrl", normalizeBaseUrl(licensePublicBaseUrl)); - } - payload.put("issuedAt", java.time.Instant.now().toString()); - String encrypted = LicenseFileCrypto.encrypt(new com.fasterxml.jackson.databind.ObjectMapper().valueToTree(payload).toString()); String filename = sanitizeFileName(app.getName()) + ".xuqmlicense"; return ResponseEntity.ok() .contentType(MediaType.APPLICATION_OCTET_STREAM) @@ -195,11 +174,6 @@ public class AppController { .body(encrypted.getBytes(java.nio.charset.StandardCharsets.UTF_8)); } - private static String normalizeBaseUrl(String value) { - String baseUrl = value == null || value.isBlank() ? "https://auth.dev.xuqinmin.com/" : value.trim(); - return baseUrl.endsWith("/") ? baseUrl : baseUrl + "/"; - } - private static String sanitizeFileName(String value) { String name = value == null || value.isBlank() ? "license" : value.trim(); return name.replaceAll("[\\\\/:*?\"<>|\\s]+", "_"); diff --git a/tenant-service/src/main/java/com/xuqm/tenant/entity/AppEntity.java b/tenant-service/src/main/java/com/xuqm/tenant/entity/AppEntity.java index 9dc26c3..91627d2 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/entity/AppEntity.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/entity/AppEntity.java @@ -43,6 +43,9 @@ public class AppEntity { @Column(nullable = false) private LocalDateTime createdAt; + @Column(name = "license_file_content", length = 4096) + private String licenseFileContent; + @Column(name = "is_default", columnDefinition = "BIT(1) DEFAULT 0") private boolean isDefault; @@ -87,4 +90,7 @@ public class AppEntity { public boolean isDeletable() { return deletable; } public void setDeletable(boolean deletable) { this.deletable = deletable; } + + public String getLicenseFileContent() { return licenseFileContent; } + public void setLicenseFileContent(String licenseFileContent) { this.licenseFileContent = licenseFileContent; } } diff --git a/tenant-service/src/main/java/com/xuqm/tenant/service/AppService.java b/tenant-service/src/main/java/com/xuqm/tenant/service/AppService.java index 3d61943..471506a 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/service/AppService.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/service/AppService.java @@ -1,11 +1,16 @@ package com.xuqm.tenant.service; import com.xuqm.common.exception.BusinessException; +import com.xuqm.common.security.LicenseFileCrypto; import com.xuqm.tenant.dto.CreateAppRequest; import com.xuqm.tenant.entity.AppEntity; import com.xuqm.tenant.entity.FeatureServiceEntity; import com.xuqm.tenant.repository.AppRepository; import com.xuqm.tenant.repository.FeatureServiceRepository; +import com.xuqm.tenant.repository.TenantRepository; +import com.xuqm.tenant.config.PrivateDeploymentProperties; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.security.SecureRandom; @@ -22,14 +27,24 @@ public class AppService { private final AppRepository appRepository; private final OperationLogService operationLogService; private final FeatureServiceRepository featureServiceRepository; + private final PrivateDeploymentProperties deployProps; + private final TenantRepository tenantRepository; private static final SecureRandom random = new SecureRandom(); + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Value("${license.public-base-url:https://auth.dev.xuqinmin.com/}") + private String licensePublicBaseUrl; public AppService(AppRepository appRepository, OperationLogService operationLogService, - FeatureServiceRepository featureServiceRepository) { + FeatureServiceRepository featureServiceRepository, + PrivateDeploymentProperties deployProps, + TenantRepository tenantRepository) { this.appRepository = appRepository; this.operationLogService = operationLogService; this.featureServiceRepository = featureServiceRepository; + this.deployProps = deployProps; + this.tenantRepository = tenantRepository; } public List listByTenant(String tenantId) { @@ -63,6 +78,7 @@ public class AppService { app.setAppKey(generateAppKey()); app.setAppSecret(generateSecret()); app.setCreatedAt(LocalDateTime.now()); + app.setLicenseFileContent(generateLicenseFileContent(app)); AppEntity saved = appRepository.save(app); autoEnableFileService(saved.getAppKey()); operationLogService.record(tenantId, "APP", "APP", saved.getAppKey(), "CREATE_APP", Map.of( @@ -85,6 +101,7 @@ public class AppService { app.setName(req.name()); app.setDescription(req.description()); app.setIconUrl(req.iconUrl()); + app.setLicenseFileContent(generateLicenseFileContent(app)); AppEntity saved = appRepository.save(app); Map after = new LinkedHashMap<>(); after.put("name", saved.getName()); @@ -143,4 +160,32 @@ public class AppService { random.nextBytes(bytes); return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); } + + private String generateLicenseFileContent(AppEntity app) { + Map payload = new LinkedHashMap<>(); + payload.put("appKey", app.getAppKey()); + payload.put("appName", app.getName()); + payload.put("packageName", app.getPackageName()); + if (app.getIosBundleId() != null && !app.getIosBundleId().isBlank()) { + payload.put("iosBundleId", app.getIosBundleId()); + } + if (app.getHarmonyBundleName() != null && !app.getHarmonyBundleName().isBlank()) { + payload.put("harmonyBundleName", app.getHarmonyBundleName()); + } + payload.put("baseUrl", normalizeBaseUrl(licensePublicBaseUrl)); + if (deployProps.isPrivate()) { + payload.put("serverUrl", normalizeBaseUrl(licensePublicBaseUrl)); + } + payload.put("issuedAt", java.time.Instant.now().toString()); + try { + return LicenseFileCrypto.encrypt(MAPPER.valueToTree(payload).toString()); + } catch (Exception e) { + throw new IllegalStateException("Failed to generate license file", e); + } + } + + private static String normalizeBaseUrl(String value) { + String baseUrl = value == null || value.isBlank() ? "https://auth.dev.xuqinmin.com/" : value.trim(); + return baseUrl.endsWith("/") ? baseUrl : baseUrl + "/"; + } } diff --git a/tenant-service/src/main/java/com/xuqm/tenant/service/SystemUpdateService.java b/tenant-service/src/main/java/com/xuqm/tenant/service/SystemUpdateService.java index 69effa7..1590c81 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/service/SystemUpdateService.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/service/SystemUpdateService.java @@ -178,14 +178,30 @@ public class SystemUpdateService { if (consoleDomain == null) consoleDomain = ""; String anchor = " SDK_TENANT_SERVICE_URL: \"http://tenant-service:9001\"\n"; - if (!content.contains(anchor)) { - emit.accept(" [跳过] docker-compose update-service 补丁锚点未找到,请手动检查"); + // Fallback anchor for older docker-compose that may not have SDK_TENANT_SERVICE_URL + String fallbackAnchor = " update-service:\n"; + String envBlock = " FILE_BASE_URL: \"" + consoleDomain + "\"\n" + + " FILE_SERVICE_INTERNAL_URL: \"http://file-service:8086\"\n"; + String patched; + if (content.contains(anchor)) { + patched = content.replace(anchor, anchor + envBlock); + } else if (content.contains(fallbackAnchor)) { + // Inject env block into update-service's environment section by finding its image line + String imageAnchor = " image: ${REGISTRY}/update-service:${IMAGE_TAG}\n"; + if (!content.contains(imageAnchor)) { + emit.accept(" [跳过] docker-compose update-service 补丁锚点未找到,请手动检查"); + return; + } + String envAnchor = imageAnchor + " environment:\n"; + if (!content.contains(envAnchor)) { + emit.accept(" [跳过] docker-compose update-service environment 段未找到,请手动检查"); + return; + } + patched = content.replace(envAnchor, envAnchor + envBlock); + } else { + emit.accept(" [跳过] docker-compose update-service 段未找到,请手动检查"); return; } - String injection = anchor - + " FILE_BASE_URL: \"" + consoleDomain + "\"\n" - + " FILE_SERVICE_INTERNAL_URL: \"http://file-service:8086\"\n"; - String patched = content.replace(anchor, injection); Files.writeString(composeFile, patched, StandardOpenOption.TRUNCATE_EXISTING); emit.accept(" [已修复] docker-compose: 补齐 update-service 的 FILE_BASE_URL 和 FILE_SERVICE_INTERNAL_URL"); } catch (IOException e) {