diff --git a/license-service/src/main/java/com/xuqm/license/controller/LicenseInternalController.java b/license-service/src/main/java/com/xuqm/license/controller/LicenseInternalController.java index de73dd7..83f25c9 100644 --- a/license-service/src/main/java/com/xuqm/license/controller/LicenseInternalController.java +++ b/license-service/src/main/java/com/xuqm/license/controller/LicenseInternalController.java @@ -73,7 +73,10 @@ public class LicenseInternalController { req.maxDevices(), req.expiresAt(), req.isActive(), - req.remark()); + req.remark(), + req.androidPackageName(), + req.iosBundleId(), + req.harmonyBundleName()); return ResponseEntity.ok(ApiResponse.success(license)); } @@ -99,6 +102,9 @@ public class LicenseInternalController { Integer maxDevices, LocalDateTime expiresAt, Boolean isActive, - String remark + String remark, + String androidPackageName, + String iosBundleId, + String harmonyBundleName ) {} } diff --git a/license-service/src/main/java/com/xuqm/license/controller/LicensePublicController.java b/license-service/src/main/java/com/xuqm/license/controller/LicensePublicController.java index dd176d3..6fb8bd1 100644 --- a/license-service/src/main/java/com/xuqm/license/controller/LicensePublicController.java +++ b/license-service/src/main/java/com/xuqm/license/controller/LicensePublicController.java @@ -29,6 +29,7 @@ public class LicensePublicController { public ResponseEntity>> register(@Valid @RequestBody RegisterRequest req) { DeviceService.RegisterResult result = deviceService.register( req.appKey(), + req.packageName(), req.deviceId(), req.deviceName(), req.deviceModel(), @@ -46,7 +47,7 @@ public class LicensePublicController { @PostMapping("/verify") public ResponseEntity>> verify(@Valid @RequestBody VerifyRequest req) { - DeviceService.VerifyResult result = deviceService.verify(req.appKey(), req.deviceId(), req.token(), req.userInfo()); + DeviceService.VerifyResult result = deviceService.verify(req.appKey(), req.packageName(), req.deviceId(), req.token(), req.userInfo()); Map data = new java.util.LinkedHashMap<>(); data.put("valid", result.valid()); if (result.error() != null) { @@ -57,6 +58,7 @@ public class LicensePublicController { public record RegisterRequest( @NotBlank String appKey, + @NotBlank @JsonProperty("packageName") @JsonAlias("package_name") String packageName, @NotBlank @JsonProperty("deviceId") @JsonAlias("device_id") String deviceId, @JsonProperty("deviceName") @JsonAlias("device_name") String deviceName, @JsonProperty("deviceModel") @JsonAlias("device_model") String deviceModel, @@ -67,6 +69,7 @@ public class LicensePublicController { public record VerifyRequest( @NotBlank String appKey, + @NotBlank @JsonProperty("packageName") @JsonAlias("package_name") String packageName, @NotBlank @JsonProperty("deviceId") @JsonAlias("device_id") String deviceId, @NotBlank String token, @JsonProperty("userInfo") @JsonAlias("user_info") JsonNode userInfo diff --git a/license-service/src/main/java/com/xuqm/license/entity/AppLicenseEntity.java b/license-service/src/main/java/com/xuqm/license/entity/AppLicenseEntity.java index 33a7bb5..9284bc7 100644 --- a/license-service/src/main/java/com/xuqm/license/entity/AppLicenseEntity.java +++ b/license-service/src/main/java/com/xuqm/license/entity/AppLicenseEntity.java @@ -17,6 +17,15 @@ public class AppLicenseEntity { @Column(nullable = false, length = 255) private String name; + @Column(name = "android_package_name", length = 128) + private String androidPackageName; + + @Column(name = "ios_bundle_id", length = 128) + private String iosBundleId; + + @Column(name = "harmony_bundle_name", length = 128) + private String harmonyBundleName; + @Column(nullable = false, name = "max_devices") private Integer maxDevices = 1; @@ -44,6 +53,15 @@ public class AppLicenseEntity { public String getName() { return name; } public void setName(String name) { this.name = name; } + public String getAndroidPackageName() { return androidPackageName; } + public void setAndroidPackageName(String androidPackageName) { this.androidPackageName = androidPackageName; } + + public String getIosBundleId() { return iosBundleId; } + public void setIosBundleId(String iosBundleId) { this.iosBundleId = iosBundleId; } + + public String getHarmonyBundleName() { return harmonyBundleName; } + public void setHarmonyBundleName(String harmonyBundleName) { this.harmonyBundleName = harmonyBundleName; } + public Integer getMaxDevices() { return maxDevices; } public void setMaxDevices(Integer maxDevices) { this.maxDevices = maxDevices; } diff --git a/license-service/src/main/java/com/xuqm/license/service/AppLicenseService.java b/license-service/src/main/java/com/xuqm/license/service/AppLicenseService.java index a35fe53..5eeee2f 100644 --- a/license-service/src/main/java/com/xuqm/license/service/AppLicenseService.java +++ b/license-service/src/main/java/com/xuqm/license/service/AppLicenseService.java @@ -24,15 +24,19 @@ public class AppLicenseService { @Transactional public AppLicenseEntity upsert(String appKey, String name, Integer maxDevices, - LocalDateTime expiresAt, Boolean isActive, String remark) { + LocalDateTime expiresAt, Boolean isActive, String remark, + String androidPackageName, String iosBundleId, String harmonyBundleName) { return repository.findById(appKey) - .map(license -> update(appKey, name, maxDevices, expiresAt, false, isActive, remark)) - .orElseGet(() -> create(appKey, name, maxDevices, expiresAt, isActive, remark)); + .map(license -> update(appKey, name, maxDevices, expiresAt, false, isActive, remark, + androidPackageName, iosBundleId, harmonyBundleName)) + .orElseGet(() -> create(appKey, name, maxDevices, expiresAt, isActive, remark, + androidPackageName, iosBundleId, harmonyBundleName)); } @Transactional public AppLicenseEntity update(String appKey, String name, Integer maxDevices, - LocalDateTime expiresAt, boolean clearExpiresAt, Boolean isActive, String remark) { + LocalDateTime expiresAt, boolean clearExpiresAt, Boolean isActive, String remark, + String androidPackageName, String iosBundleId, String harmonyBundleName) { AppLicenseEntity license = getByAppKey(appKey); if (name != null) license.setName(name); if (maxDevices != null) license.setMaxDevices(maxDevices); @@ -43,6 +47,9 @@ public class AppLicenseService { } if (isActive != null) license.setIsActive(isActive); if (remark != null) license.setRemark(remark); + if (androidPackageName != null) license.setAndroidPackageName(androidPackageName); + if (iosBundleId != null) license.setIosBundleId(iosBundleId); + if (harmonyBundleName != null) license.setHarmonyBundleName(harmonyBundleName); license.setUpdatedAt(LocalDateTime.now()); return repository.save(license); } @@ -73,7 +80,8 @@ public class AppLicenseService { } private AppLicenseEntity create(String appKey, String name, Integer maxDevices, - LocalDateTime expiresAt, Boolean isActive, String remark) { + LocalDateTime expiresAt, Boolean isActive, String remark, + String androidPackageName, String iosBundleId, String harmonyBundleName) { AppLicenseEntity license = new AppLicenseEntity(); license.setAppKey(appKey); license.setName(name); @@ -82,6 +90,9 @@ public class AppLicenseService { license.setExpiresAt(expiresAt); license.setIsActive(isActive != null ? isActive : true); license.setRemark(remark); + license.setAndroidPackageName(androidPackageName); + license.setIosBundleId(iosBundleId); + license.setHarmonyBundleName(harmonyBundleName); license.setCreatedAt(LocalDateTime.now()); license.setUpdatedAt(LocalDateTime.now()); return repository.save(license); diff --git a/license-service/src/main/java/com/xuqm/license/service/DeviceService.java b/license-service/src/main/java/com/xuqm/license/service/DeviceService.java index 13b3539..b61baed 100644 --- a/license-service/src/main/java/com/xuqm/license/service/DeviceService.java +++ b/license-service/src/main/java/com/xuqm/license/service/DeviceService.java @@ -43,9 +43,11 @@ public class DeviceService { } @Transactional - public RegisterResult register(String appKey, String deviceId, String deviceName, + public RegisterResult register(String appKey, String packageName, String deviceId, String deviceName, String deviceModel, String deviceVendor, String osVersion, JsonNode userInfo) { + validatePackageName(appKey, packageName); + // Check if device already registered Optional existingOpt = findByDeviceId(deviceId); if (existingOpt.isPresent()) { @@ -108,7 +110,12 @@ public class DeviceService { } @Transactional - public VerifyResult verify(String appKey, String deviceId, String token, JsonNode userInfo) { + public VerifyResult verify(String appKey, String packageName, String deviceId, String token, JsonNode userInfo) { + try { + validatePackageName(appKey, packageName); + } catch (BusinessException e) { + return new VerifyResult(false, e.getMessage()); + } if (!licenseAuthService.verifyTokenPayload(token, appKey, deviceId)) { return new VerifyResult(false, "Token mismatch"); } @@ -155,6 +162,32 @@ public class DeviceService { appLicenseService.incrementRegisteredDevices(device.getAppKey()); } + private void validatePackageName(String appKey, String packageName) { + if (packageName == null || packageName.isBlank()) { + throw new BusinessException(403, "packageName is required"); + } + AppLicenseEntity license; + try { + license = appLicenseService.getByAppKey(appKey); + } catch (BusinessException e) { + throw new BusinessException(403, "App license not found"); + } + String android = license.getAndroidPackageName(); + String ios = license.getIosBundleId(); + String harmony = license.getHarmonyBundleName(); + boolean anyConfigured = hasText(android) || hasText(ios) || hasText(harmony); + if (anyConfigured) { + boolean matches = packageName.equals(android) || packageName.equals(ios) || packageName.equals(harmony); + if (!matches) { + throw new BusinessException(403, "包名与应用配置不匹配"); + } + } + } + + private static boolean hasText(String s) { + return s != null && !s.isBlank(); + } + public static String hashToken(String token) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); diff --git a/tenant-service/src/main/java/com/xuqm/tenant/controller/SdkConfigController.java b/tenant-service/src/main/java/com/xuqm/tenant/controller/SdkConfigController.java index 79eadde..fb4f75f 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/controller/SdkConfigController.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/controller/SdkConfigController.java @@ -2,6 +2,7 @@ package com.xuqm.tenant.controller; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.xuqm.common.exception.BusinessException; import com.xuqm.common.model.ApiResponse; import com.xuqm.tenant.config.PrivateDeploymentProperties; import com.xuqm.tenant.entity.AppEntity; @@ -57,9 +58,11 @@ public class SdkConfigController { @GetMapping("/config") public ResponseEntity> getConfig( @RequestParam String appKey, + @RequestParam String packageName, @RequestParam(required = false, defaultValue = "ANDROID") FeatureServiceEntity.Platform platform) { AppEntity app = sdkAppProvisioningService.resolveApp(appKey); + validatePackageName(app, platform, packageName); List features = featureServiceRepository.findByAppKey(app.getAppKey()); // In private deployments, intersect DB feature flags with deployment-level service availability @@ -129,6 +132,17 @@ public class SdkConfigController { JsonNode pushConfig ) {} + private void validatePackageName(AppEntity app, FeatureServiceEntity.Platform platform, String packageName) { + String registered = switch (platform) { + case IOS -> app.getIosBundleId(); + case HARMONY -> app.getHarmonyBundleName(); + default -> app.getPackageName(); + }; + if (registered != null && !registered.isBlank() && !registered.equals(packageName)) { + throw new BusinessException(403, "包名与应用配置不匹配"); + } + } + private JsonNode parseConfig(String config) { if (config == null || config.isBlank()) { return objectMapper.createObjectNode(); diff --git a/tenant-service/src/main/java/com/xuqm/tenant/service/FeatureServiceManager.java b/tenant-service/src/main/java/com/xuqm/tenant/service/FeatureServiceManager.java index 7f3e182..b4204f6 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/service/FeatureServiceManager.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/service/FeatureServiceManager.java @@ -192,7 +192,8 @@ public class FeatureServiceManager { } if (req.getServiceType() == FeatureServiceEntity.ServiceType.LICENSE) { appRepository.findByAppKey(normalizedAppId).ifPresent(app -> - licenseServiceClient.syncAppLicense(app.getAppKey(), app.getName(), 1, expiresAt)); + licenseServiceClient.syncAppLicense(app.getAppKey(), app.getName(), 1, expiresAt, + app.getPackageName(), app.getIosBundleId(), app.getHarmonyBundleName())); } sendActivationImNotification(req.getAppKey(), req.getServiceType().name(), "APPROVED", reviewNote); return req; diff --git a/tenant-service/src/main/java/com/xuqm/tenant/service/LicenseServiceClient.java b/tenant-service/src/main/java/com/xuqm/tenant/service/LicenseServiceClient.java index 8bb1752..e0edea7 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/service/LicenseServiceClient.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/service/LicenseServiceClient.java @@ -49,10 +49,15 @@ public class LicenseServiceClient { } public void syncAppLicense(String appKey, String name, Integer maxDevices) { - syncAppLicense(appKey, name, maxDevices, null); + syncAppLicense(appKey, name, maxDevices, null, null, null, null); } public void syncAppLicense(String appKey, String name, Integer maxDevices, String expiresAt) { + syncAppLicense(appKey, name, maxDevices, expiresAt, null, null, null); + } + + public void syncAppLicense(String appKey, String name, Integer maxDevices, String expiresAt, + String androidPackageName, String iosBundleId, String harmonyBundleName) { try { Map body = new java.util.HashMap<>(); body.put("id", appKey); @@ -61,6 +66,9 @@ public class LicenseServiceClient { if (expiresAt != null && !expiresAt.isBlank()) { body.put("expiresAt", expiresAt); } + if (androidPackageName != null) body.put("androidPackageName", androidPackageName); + if (iosBundleId != null) body.put("iosBundleId", iosBundleId); + if (harmonyBundleName != null) body.put("harmonyBundleName", harmonyBundleName); callInternal("/api/license/internal/apps", HttpMethod.POST, body); } catch (Exception e) { // ignore