From 843ed69f3ca88dfd2e6b12d530a1ce3e23a1546b Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Fri, 22 May 2026 18:37:46 +0800 Subject: [PATCH] license: fix device re-register appKey update, add license file parser - DeviceService.register(): update appKey when device switches to a different app and adjust registered device counters for old/new appKey - LicenseAdminController: fix updateAppLicense parameter count mismatch - AppController: add POST /api/apps/license/parse endpoint for license file decryption - SecurityCenterView: add License file parser UI with upload and paste support - appApi: add parseLicenseFile() method Co-Authored-By: Claude Opus 4.7 --- common/NODE | 0 .../controller/LicenseAdminController.java | 2 +- .../xuqm/license/service/DeviceService.java | 8 ++++- .../xuqm/tenant/controller/AppController.java | 29 +++++++++++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 common/NODE diff --git a/common/NODE b/common/NODE new file mode 100644 index 0000000..e69de29 diff --git a/license-service/src/main/java/com/xuqm/license/controller/LicenseAdminController.java b/license-service/src/main/java/com/xuqm/license/controller/LicenseAdminController.java index 5b600a6..09f0ae7 100644 --- a/license-service/src/main/java/com/xuqm/license/controller/LicenseAdminController.java +++ b/license-service/src/main/java/com/xuqm/license/controller/LicenseAdminController.java @@ -51,7 +51,7 @@ public class LicenseAdminController { } } AppLicenseEntity updated = appLicenseService.update( - appKey, null, req.maxDevices(), newExpiresAt, clearExpiresAt, req.isActive(), req.remark()); + appKey, null, req.maxDevices(), newExpiresAt, clearExpiresAt, req.isActive(), req.remark(), null, null, null); return ResponseEntity.ok(ApiResponse.success(updated)); } 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 b61baed..b7b8fdb 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 @@ -58,8 +58,14 @@ public class DeviceService { // Re-issue token String token = licenseAuthService.generateToken(appKey, deviceId, existing.getId()); String tokenHash = hashToken(token); - if (existing.getAppKey() == null || existing.getAppKey().isBlank()) { + String oldAppKey = existing.getAppKey(); + if (oldAppKey == null || oldAppKey.isBlank()) { existing.setAppKey(appKey); + } else if (!oldAppKey.equals(appKey)) { + // Device switched to a different app: update appKey and adjust counters + existing.setAppKey(appKey); + appLicenseService.decrementRegisteredDevices(oldAppKey); + appLicenseService.incrementRegisteredDevices(appKey); } existing.setDeviceName(firstNonBlank(deviceName, existing.getDeviceName())); existing.setDeviceModel(firstNonBlank(deviceModel, existing.getDeviceModel())); 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 d6400d1..ea1e1f8 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 @@ -2,6 +2,7 @@ package com.xuqm.tenant.controller; import com.xuqm.common.model.ApiResponse; 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; @@ -174,6 +175,34 @@ public class AppController { .body(encrypted.getBytes(java.nio.charset.StandardCharsets.UTF_8)); } + /** + * Parse an uploaded license file and return its decrypted contents. + * Used by the security center to verify license file information. + */ + @PostMapping("/license/parse") + public ResponseEntity>> parseLicenseFile(@RequestBody Map body) { + String content = body.get("content"); + if (content == null || content.isBlank()) { + throw new BusinessException("License file content is required"); + } + try { + LicenseFileCrypto.LicensePayload payload = LicenseFileCrypto.decrypt(content.trim()); + Map data = new java.util.LinkedHashMap<>(); + data.put("appKey", payload.appKey()); + data.put("appName", payload.appName()); + data.put("packageName", payload.packageName()); + data.put("iosBundleId", payload.iosBundleId()); + data.put("harmonyBundleName", payload.harmonyBundleName()); + data.put("baseUrl", payload.baseUrl()); + data.put("serverUrl", payload.serverUrl()); + return ResponseEntity.ok(ApiResponse.success(data)); + } catch (IllegalArgumentException e) { + throw new BusinessException("Invalid license file: " + e.getMessage()); + } catch (Exception e) { + throw new BusinessException("Failed to parse license file: " + e.getMessage()); + } + } + private static String sanitizeFileName(String value) { String name = value == null || value.isBlank() ? "license" : value.trim(); return name.replaceAll("[\\\\/:*?\"<>|\\s]+", "_");