From bc1165d22e5519498cbbaccd923462f5ecf26dfe Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Fri, 15 May 2026 21:42:10 +0800 Subject: [PATCH] Align license service with app model --- .../controller/LicenseAdminController.java | 60 ++------ .../controller/LicenseInternalController.java | 42 +++--- .../controller/LicensePublicController.java | 8 +- ...mpanyEntity.java => AppLicenseEntity.java} | 12 +- .../com/xuqm/license/entity/DeviceEntity.java | 8 +- .../repository/AppLicenseRepository.java | 9 ++ .../license/repository/CompanyRepository.java | 12 -- .../license/repository/DeviceRepository.java | 4 +- .../license/service/AppLicenseService.java | 85 ++++++++++++ .../xuqm/license/service/CompanyService.java | 115 --------------- .../xuqm/license/service/DeviceService.java | 46 +++--- .../license/service/LicenseAuthService.java | 10 +- .../tenant/config/LicenseMigrationRunner.java | 131 ------------------ .../tenant/service/FeatureServiceManager.java | 2 +- .../tenant/service/LicenseServiceClient.java | 10 +- .../src/main/resources/application.yml | 3 - 16 files changed, 175 insertions(+), 382 deletions(-) rename license-service/src/main/java/com/xuqm/license/entity/{CompanyEntity.java => AppLicenseEntity.java} (89%) create mode 100644 license-service/src/main/java/com/xuqm/license/repository/AppLicenseRepository.java delete mode 100644 license-service/src/main/java/com/xuqm/license/repository/CompanyRepository.java create mode 100644 license-service/src/main/java/com/xuqm/license/service/AppLicenseService.java delete mode 100644 license-service/src/main/java/com/xuqm/license/service/CompanyService.java delete mode 100644 tenant-service/src/main/java/com/xuqm/tenant/config/LicenseMigrationRunner.java 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 7c1a4d0..d85ec18 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 @@ -1,16 +1,13 @@ package com.xuqm.license.controller; import com.xuqm.common.model.ApiResponse; -import com.xuqm.license.entity.CompanyEntity; +import com.xuqm.license.entity.AppLicenseEntity; import com.xuqm.license.entity.DeviceEntity; -import com.xuqm.license.service.CompanyService; +import com.xuqm.license.service.AppLicenseService; import com.xuqm.license.service.DeviceService; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.time.LocalDateTime; import java.util.List; import java.util.Map; @@ -18,47 +15,24 @@ import java.util.Map; @RequestMapping("/api/license/admin") public class LicenseAdminController { - private final CompanyService companyService; + private final AppLicenseService appLicenseService; private final DeviceService deviceService; - public LicenseAdminController(CompanyService companyService, DeviceService deviceService) { - this.companyService = companyService; + public LicenseAdminController(AppLicenseService appLicenseService, DeviceService deviceService) { + this.appLicenseService = appLicenseService; this.deviceService = deviceService; } - @GetMapping("/companies") - public ResponseEntity>> listCompanies() { - return ResponseEntity.ok(ApiResponse.success(companyService.listAll())); - } - - @PostMapping("/companies") - public ResponseEntity> createCompany(@RequestBody CreateCompanyRequest req) { - CompanyEntity company = companyService.create(req.name(), req.maxDevices(), req.expiresAt(), req.remark()); - return ResponseEntity.ok(ApiResponse.success(company)); - } - - @GetMapping("/companies/{id}") - public ResponseEntity>> getCompany(@PathVariable String id) { - CompanyEntity company = companyService.getById(id); - List devices = deviceService.listByCompany(id); + @GetMapping("/apps/{appKey}") + public ResponseEntity>> getAppLicense(@PathVariable String appKey) { + AppLicenseEntity license = appLicenseService.getByAppKey(appKey); + List devices = deviceService.listByApp(appKey); Map data = new java.util.LinkedHashMap<>(); - data.put("company", company); + data.put("license", license); data.put("devices", devices); return ResponseEntity.ok(ApiResponse.success(data)); } - @PutMapping("/companies/{id}") - public ResponseEntity> updateCompany(@PathVariable String id, @RequestBody UpdateCompanyRequest req) { - CompanyEntity company = companyService.update(id, req.name(), req.maxDevices(), req.expiresAt(), req.isActive(), req.remark()); - return ResponseEntity.ok(ApiResponse.success(company)); - } - - @DeleteMapping("/companies/{id}") - public ResponseEntity> deleteCompany(@PathVariable String id) { - companyService.delete(id); - return ResponseEntity.ok(ApiResponse.ok()); - } - @DeleteMapping("/devices/{id}") public ResponseEntity> revokeDevice(@PathVariable String id) { deviceService.revoke(id); @@ -71,18 +45,4 @@ public class LicenseAdminController { return ResponseEntity.ok(ApiResponse.ok()); } - public record CreateCompanyRequest( - @NotBlank String name, - @NotNull Integer maxDevices, - LocalDateTime expiresAt, - String remark - ) {} - - public record UpdateCompanyRequest( - String name, - Integer maxDevices, - LocalDateTime expiresAt, - Boolean isActive, - String remark - ) {} } 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 6253ac7..de73dd7 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 @@ -1,9 +1,9 @@ package com.xuqm.license.controller; import com.xuqm.common.model.ApiResponse; -import com.xuqm.license.entity.CompanyEntity; +import com.xuqm.license.entity.AppLicenseEntity; import com.xuqm.license.entity.DeviceEntity; -import com.xuqm.license.service.CompanyService; +import com.xuqm.license.service.AppLicenseService; import com.xuqm.license.service.DeviceService; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; @@ -18,63 +18,63 @@ import java.util.Map; @RequestMapping("/api/license/internal") public class LicenseInternalController { - private final CompanyService companyService; + private final AppLicenseService appLicenseService; private final DeviceService deviceService; @Value("${license.internal-token:xuqm-license-internal-token}") private String internalToken; - public LicenseInternalController(CompanyService companyService, DeviceService deviceService) { - this.companyService = companyService; + public LicenseInternalController(AppLicenseService appLicenseService, DeviceService deviceService) { + this.appLicenseService = appLicenseService; this.deviceService = deviceService; } - @GetMapping("/companies/{appKey}/status") - public ResponseEntity>> getCompanyStatus( + @GetMapping("/apps/{appKey}/status") + public ResponseEntity>> getAppLicenseStatus( @RequestHeader(value = "X-Internal-Token", required = false) String token, @PathVariable String appKey) { if (!isAllowed(token)) { return ResponseEntity.status(403).body(ApiResponse.error(403, "Forbidden")); } try { - CompanyEntity company = companyService.getById(appKey); + AppLicenseEntity license = appLicenseService.getByAppKey(appKey); Map data = new HashMap<>(); data.put("exists", true); - data.put("active", companyService.isCompanyValid(company)); - data.put("maxDevices", company.getMaxDevices()); - data.put("registeredDevices", company.getRegisteredDevices()); - data.put("expiresAt", company.getExpiresAt()); + data.put("active", appLicenseService.isValid(license)); + data.put("maxDevices", license.getMaxDevices()); + data.put("registeredDevices", license.getRegisteredDevices()); + data.put("expiresAt", license.getExpiresAt()); return ResponseEntity.ok(ApiResponse.success(data)); } catch (Exception e) { return ResponseEntity.ok(ApiResponse.success(Map.of("exists", false))); } } - @GetMapping("/companies/{appKey}/devices") - public ResponseEntity>> listDevicesByCompany( + @GetMapping("/apps/{appKey}/devices") + public ResponseEntity>> listDevicesByApp( @RequestHeader(value = "X-Internal-Token", required = false) String token, @PathVariable String appKey) { if (!isAllowed(token)) { return ResponseEntity.status(403).body(ApiResponse.error(403, "Forbidden")); } - return ResponseEntity.ok(ApiResponse.success(deviceService.listByCompany(appKey))); + return ResponseEntity.ok(ApiResponse.success(deviceService.listByApp(appKey))); } - @PostMapping("/companies") - public ResponseEntity> upsertCompany( + @PostMapping("/apps") + public ResponseEntity> upsertAppLicense( @RequestHeader(value = "X-Internal-Token", required = false) String token, - @RequestBody UpsertCompanyRequest req) { + @RequestBody UpsertAppLicenseRequest req) { if (!isAllowed(token)) { return ResponseEntity.status(403).body(ApiResponse.error(403, "Forbidden")); } - CompanyEntity company = companyService.upsert( + AppLicenseEntity license = appLicenseService.upsert( req.id(), req.name(), req.maxDevices(), req.expiresAt(), req.isActive(), req.remark()); - return ResponseEntity.ok(ApiResponse.success(company)); + return ResponseEntity.ok(ApiResponse.success(license)); } @GetMapping("/devices/{deviceId}") @@ -93,7 +93,7 @@ public class LicenseInternalController { return token != null && internalToken.equals(token); } - public record UpsertCompanyRequest( + public record UpsertAppLicenseRequest( String id, String name, Integer maxDevices, 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 c9af7eb..dd176d3 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 @@ -28,7 +28,7 @@ public class LicensePublicController { @PostMapping("/register") public ResponseEntity>> register(@Valid @RequestBody RegisterRequest req) { DeviceService.RegisterResult result = deviceService.register( - req.companyId(), + req.appKey(), req.deviceId(), req.deviceName(), req.deviceModel(), @@ -46,7 +46,7 @@ public class LicensePublicController { @PostMapping("/verify") public ResponseEntity>> verify(@Valid @RequestBody VerifyRequest req) { - DeviceService.VerifyResult result = deviceService.verify(req.companyId(), req.deviceId(), req.token(), req.userInfo()); + DeviceService.VerifyResult result = deviceService.verify(req.appKey(), req.deviceId(), req.token(), req.userInfo()); Map data = new java.util.LinkedHashMap<>(); data.put("valid", result.valid()); if (result.error() != null) { @@ -56,7 +56,7 @@ public class LicensePublicController { } public record RegisterRequest( - @NotBlank @JsonProperty("companyId") @JsonAlias("company_id") String companyId, + @NotBlank String appKey, @NotBlank @JsonProperty("deviceId") @JsonAlias("device_id") String deviceId, @JsonProperty("deviceName") @JsonAlias("device_name") String deviceName, @JsonProperty("deviceModel") @JsonAlias("device_model") String deviceModel, @@ -66,7 +66,7 @@ public class LicensePublicController { ) {} public record VerifyRequest( - @NotBlank @JsonProperty("companyId") @JsonAlias("company_id") String companyId, + @NotBlank String appKey, @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/CompanyEntity.java b/license-service/src/main/java/com/xuqm/license/entity/AppLicenseEntity.java similarity index 89% rename from license-service/src/main/java/com/xuqm/license/entity/CompanyEntity.java rename to license-service/src/main/java/com/xuqm/license/entity/AppLicenseEntity.java index bf421b0..33a7bb5 100644 --- a/license-service/src/main/java/com/xuqm/license/entity/CompanyEntity.java +++ b/license-service/src/main/java/com/xuqm/license/entity/AppLicenseEntity.java @@ -7,12 +7,12 @@ import jakarta.persistence.Table; import java.time.LocalDateTime; @Entity -@Table(name = "companies") -public class CompanyEntity { +@Table(name = "app_licenses") +public class AppLicenseEntity { @Id - @Column(length = 36) - private String id; + @Column(name = "app_key", length = 64) + private String appKey; @Column(nullable = false, length = 255) private String name; @@ -38,8 +38,8 @@ public class CompanyEntity { @Column(nullable = false, name = "updated_at") private LocalDateTime updatedAt; - public String getId() { return id; } - public void setId(String id) { this.id = id; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getName() { return name; } public void setName(String name) { this.name = name; } diff --git a/license-service/src/main/java/com/xuqm/license/entity/DeviceEntity.java b/license-service/src/main/java/com/xuqm/license/entity/DeviceEntity.java index e4fbee6..82603a5 100644 --- a/license-service/src/main/java/com/xuqm/license/entity/DeviceEntity.java +++ b/license-service/src/main/java/com/xuqm/license/entity/DeviceEntity.java @@ -14,8 +14,8 @@ public class DeviceEntity { @Column(length = 36) private String id; - @Column(nullable = false, name = "company_id", length = 36) - private String companyId; + @Column(nullable = false, name = "app_key", length = 64) + private String appKey; @Column(nullable = false, name = "device_id", length = 255, unique = true) private String deviceId; @@ -68,8 +68,8 @@ public class DeviceEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getCompanyId() { return companyId; } - public void setCompanyId(String companyId) { this.companyId = companyId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getDeviceId() { return deviceId; } public void setDeviceId(String deviceId) { this.deviceId = deviceId; } diff --git a/license-service/src/main/java/com/xuqm/license/repository/AppLicenseRepository.java b/license-service/src/main/java/com/xuqm/license/repository/AppLicenseRepository.java new file mode 100644 index 0000000..95fec49 --- /dev/null +++ b/license-service/src/main/java/com/xuqm/license/repository/AppLicenseRepository.java @@ -0,0 +1,9 @@ +package com.xuqm.license.repository; + +import com.xuqm.license.entity.AppLicenseEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AppLicenseRepository extends JpaRepository { +} diff --git a/license-service/src/main/java/com/xuqm/license/repository/CompanyRepository.java b/license-service/src/main/java/com/xuqm/license/repository/CompanyRepository.java deleted file mode 100644 index 08cb9c5..0000000 --- a/license-service/src/main/java/com/xuqm/license/repository/CompanyRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.xuqm.license.repository; - -import com.xuqm.license.entity.CompanyEntity; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public interface CompanyRepository extends JpaRepository { - List findAllByOrderByCreatedAtDesc(); -} diff --git a/license-service/src/main/java/com/xuqm/license/repository/DeviceRepository.java b/license-service/src/main/java/com/xuqm/license/repository/DeviceRepository.java index 59741e6..3bff6de 100644 --- a/license-service/src/main/java/com/xuqm/license/repository/DeviceRepository.java +++ b/license-service/src/main/java/com/xuqm/license/repository/DeviceRepository.java @@ -10,6 +10,6 @@ import java.util.Optional; @Repository public interface DeviceRepository extends JpaRepository { Optional findByDeviceId(String deviceId); - List findByCompanyIdOrderByRegisteredAtDesc(String companyId); - long countByCompanyIdAndIsActiveTrue(String companyId); + List findByAppKeyOrderByRegisteredAtDesc(String appKey); + long countByAppKeyAndIsActiveTrue(String appKey); } 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 new file mode 100644 index 0000000..c9efe51 --- /dev/null +++ b/license-service/src/main/java/com/xuqm/license/service/AppLicenseService.java @@ -0,0 +1,85 @@ +package com.xuqm.license.service; + +import com.xuqm.common.exception.BusinessException; +import com.xuqm.license.entity.AppLicenseEntity; +import com.xuqm.license.repository.AppLicenseRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@Service +public class AppLicenseService { + + private final AppLicenseRepository repository; + + public AppLicenseService(AppLicenseRepository repository) { + this.repository = repository; + } + + public AppLicenseEntity getByAppKey(String appKey) { + return repository.findById(appKey) + .orElseThrow(() -> new BusinessException(404, "App license not found")); + } + + @Transactional + public AppLicenseEntity upsert(String appKey, String name, Integer maxDevices, + LocalDateTime expiresAt, Boolean isActive, String remark) { + return repository.findById(appKey) + .map(license -> update(appKey, name, maxDevices, expiresAt, isActive, remark)) + .orElseGet(() -> create(appKey, name, maxDevices, expiresAt, isActive, remark)); + } + + @Transactional + public AppLicenseEntity update(String appKey, String name, Integer maxDevices, + LocalDateTime expiresAt, Boolean isActive, String remark) { + AppLicenseEntity license = getByAppKey(appKey); + if (name != null) license.setName(name); + if (maxDevices != null) license.setMaxDevices(maxDevices); + if (expiresAt != null) license.setExpiresAt(expiresAt); + if (isActive != null) license.setIsActive(isActive); + if (remark != null) license.setRemark(remark); + license.setUpdatedAt(LocalDateTime.now()); + return repository.save(license); + } + + @Transactional + public void incrementRegisteredDevices(String appKey) { + AppLicenseEntity license = getByAppKey(appKey); + license.setRegisteredDevices(license.getRegisteredDevices() + 1); + license.setUpdatedAt(LocalDateTime.now()); + repository.save(license); + } + + @Transactional + public void decrementRegisteredDevices(String appKey) { + AppLicenseEntity license = getByAppKey(appKey); + if (license.getRegisteredDevices() > 0) { + license.setRegisteredDevices(license.getRegisteredDevices() - 1); + license.setUpdatedAt(LocalDateTime.now()); + repository.save(license); + } + } + + public boolean isValid(AppLicenseEntity license) { + if (license == null || !Boolean.TRUE.equals(license.getIsActive())) { + return false; + } + return license.getExpiresAt() == null || !LocalDateTime.now().isAfter(license.getExpiresAt()); + } + + private AppLicenseEntity create(String appKey, String name, Integer maxDevices, + LocalDateTime expiresAt, Boolean isActive, String remark) { + AppLicenseEntity license = new AppLicenseEntity(); + license.setAppKey(appKey); + license.setName(name); + license.setMaxDevices(maxDevices != null ? maxDevices : 1); + license.setRegisteredDevices(0); + license.setExpiresAt(expiresAt); + license.setIsActive(isActive != null ? isActive : true); + license.setRemark(remark); + license.setCreatedAt(LocalDateTime.now()); + license.setUpdatedAt(LocalDateTime.now()); + return repository.save(license); + } +} diff --git a/license-service/src/main/java/com/xuqm/license/service/CompanyService.java b/license-service/src/main/java/com/xuqm/license/service/CompanyService.java deleted file mode 100644 index d19cacf..0000000 --- a/license-service/src/main/java/com/xuqm/license/service/CompanyService.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.xuqm.license.service; - -import com.xuqm.common.exception.BusinessException; -import com.xuqm.license.entity.CompanyEntity; -import com.xuqm.license.repository.CompanyRepository; -import com.xuqm.license.repository.DeviceRepository; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.UUID; - -@Service -public class CompanyService { - - private final CompanyRepository companyRepository; - private final DeviceRepository deviceRepository; - - public CompanyService(CompanyRepository companyRepository, DeviceRepository deviceRepository) { - this.companyRepository = companyRepository; - this.deviceRepository = deviceRepository; - } - - public List listAll() { - return companyRepository.findAllByOrderByCreatedAtDesc(); - } - - public CompanyEntity getById(String id) { - return companyRepository.findById(id) - .orElseThrow(() -> new BusinessException(404, "Company not found")); - } - - @Transactional - public CompanyEntity create(String name, Integer maxDevices, LocalDateTime expiresAt, String remark) { - return createWithId(UUID.randomUUID().toString(), name, maxDevices, expiresAt, remark); - } - - @Transactional - public CompanyEntity createWithId(String id, String name, Integer maxDevices, LocalDateTime expiresAt, String remark) { - CompanyEntity company = new CompanyEntity(); - company.setId(id); - company.setName(name); - company.setMaxDevices(maxDevices != null ? maxDevices : 1); - company.setRegisteredDevices(0); - company.setExpiresAt(expiresAt); - company.setIsActive(true); - company.setRemark(remark); - company.setCreatedAt(LocalDateTime.now()); - company.setUpdatedAt(LocalDateTime.now()); - return companyRepository.save(company); - } - - @Transactional - public CompanyEntity upsert(String id, String name, Integer maxDevices, LocalDateTime expiresAt, Boolean isActive, String remark) { - return companyRepository.findById(id) - .map(company -> update(id, name, maxDevices, expiresAt, isActive, remark)) - .orElseGet(() -> { - CompanyEntity created = createWithId(id, name, maxDevices, expiresAt, remark); - if (isActive != null) { - created.setIsActive(isActive); - created.setUpdatedAt(LocalDateTime.now()); - return companyRepository.save(created); - } - return created; - }); - } - - @Transactional - public CompanyEntity update(String id, String name, Integer maxDevices, LocalDateTime expiresAt, Boolean isActive, String remark) { - CompanyEntity company = getById(id); - if (name != null) company.setName(name); - if (maxDevices != null) company.setMaxDevices(maxDevices); - if (expiresAt != null) company.setExpiresAt(expiresAt); - if (isActive != null) company.setIsActive(isActive); - if (remark != null) company.setRemark(remark); - company.setUpdatedAt(LocalDateTime.now()); - return companyRepository.save(company); - } - - @Transactional - public void delete(String id) { - CompanyEntity company = getById(id); - deviceRepository.deleteAll(deviceRepository.findByCompanyIdOrderByRegisteredAtDesc(id)); - companyRepository.delete(company); - } - - @Transactional - public void incrementRegisteredDevices(String companyId) { - CompanyEntity company = getById(companyId); - company.setRegisteredDevices(company.getRegisteredDevices() + 1); - company.setUpdatedAt(LocalDateTime.now()); - companyRepository.save(company); - } - - @Transactional - public void decrementRegisteredDevices(String companyId) { - CompanyEntity company = getById(companyId); - if (company.getRegisteredDevices() > 0) { - company.setRegisteredDevices(company.getRegisteredDevices() - 1); - company.setUpdatedAt(LocalDateTime.now()); - companyRepository.save(company); - } - } - - public boolean isCompanyValid(CompanyEntity company) { - if (company == null || !Boolean.TRUE.equals(company.getIsActive())) { - return false; - } - if (company.getExpiresAt() != null && LocalDateTime.now().isAfter(company.getExpiresAt())) { - return false; - } - return true; - } -} 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 6751420..45fec5a 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 @@ -3,7 +3,7 @@ package com.xuqm.license.service; import com.xuqm.common.exception.BusinessException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.xuqm.license.entity.CompanyEntity; +import com.xuqm.license.entity.AppLicenseEntity; import com.xuqm.license.entity.DeviceEntity; import com.xuqm.license.repository.DeviceRepository; import org.springframework.stereotype.Service; @@ -22,14 +22,14 @@ import java.util.UUID; public class DeviceService { private final DeviceRepository deviceRepository; - private final CompanyService companyService; + private final AppLicenseService appLicenseService; private final LicenseAuthService licenseAuthService; private final ObjectMapper objectMapper; - public DeviceService(DeviceRepository deviceRepository, CompanyService companyService, + public DeviceService(DeviceRepository deviceRepository, AppLicenseService appLicenseService, LicenseAuthService licenseAuthService, ObjectMapper objectMapper) { this.deviceRepository = deviceRepository; - this.companyService = companyService; + this.appLicenseService = appLicenseService; this.licenseAuthService = licenseAuthService; this.objectMapper = objectMapper; } @@ -38,12 +38,12 @@ public class DeviceService { return deviceRepository.findByDeviceId(deviceId); } - public List listByCompany(String companyId) { - return deviceRepository.findByCompanyIdOrderByRegisteredAtDesc(companyId); + public List listByApp(String appKey) { + return deviceRepository.findByAppKeyOrderByRegisteredAtDesc(appKey); } @Transactional - public RegisterResult register(String companyId, String deviceId, String deviceName, + public RegisterResult register(String appKey, String deviceId, String deviceName, String deviceModel, String deviceVendor, String osVersion, JsonNode userInfo) { // Check if device already registered @@ -54,7 +54,7 @@ public class DeviceService { throw new BusinessException(403, "Device has been deactivated"); } // Re-issue token - String token = licenseAuthService.generateToken(companyId, deviceId, existing.getId()); + String token = licenseAuthService.generateToken(appKey, deviceId, existing.getId()); String tokenHash = hashToken(token); existing.setDeviceName(firstNonBlank(deviceName, existing.getDeviceName())); existing.setDeviceModel(firstNonBlank(deviceModel, existing.getDeviceModel())); @@ -69,22 +69,22 @@ public class DeviceService { } // Validate company - CompanyEntity company = companyService.getById(companyId); - if (!companyService.isCompanyValid(company)) { - throw new BusinessException(403, "Company license is inactive or expired"); + AppLicenseEntity license = appLicenseService.getByAppKey(appKey); + if (!appLicenseService.isValid(license)) { + throw new BusinessException(403, "App license is inactive or expired"); } - if (company.getRegisteredDevices() >= company.getMaxDevices()) { - throw new BusinessException(403, "Device limit reached. Max allowed: " + company.getMaxDevices()); + if (license.getRegisteredDevices() >= license.getMaxDevices()) { + throw new BusinessException(403, "Device limit reached. Max allowed: " + license.getMaxDevices()); } // Create device record String recordId = UUID.randomUUID().toString(); - String token = licenseAuthService.generateToken(companyId, deviceId, recordId); + String token = licenseAuthService.generateToken(appKey, deviceId, recordId); String tokenHash = hashToken(token); DeviceEntity device = new DeviceEntity(); device.setId(recordId); - device.setCompanyId(companyId); + device.setAppKey(appKey); device.setDeviceId(deviceId); device.setDeviceName(deviceName); device.setDeviceModel(deviceModel); @@ -98,14 +98,14 @@ public class DeviceService { device.setUpdatedAt(LocalDateTime.now()); deviceRepository.save(device); - companyService.incrementRegisteredDevices(companyId); + appLicenseService.incrementRegisteredDevices(appKey); return new RegisterResult(true, token, null); } @Transactional - public VerifyResult verify(String companyId, String deviceId, String token, JsonNode userInfo) { - if (!licenseAuthService.verifyTokenPayload(token, companyId, deviceId)) { + public VerifyResult verify(String appKey, String deviceId, String token, JsonNode userInfo) { + if (!licenseAuthService.verifyTokenPayload(token, appKey, deviceId)) { return new VerifyResult(false, "Token mismatch"); } @@ -118,9 +118,9 @@ public class DeviceService { return new VerifyResult(false, "Token revoked"); } - CompanyEntity company = companyService.getById(companyId); - if (!companyService.isCompanyValid(company)) { - return new VerifyResult(false, "Company license inactive or expired"); + AppLicenseEntity license = appLicenseService.getByAppKey(appKey); + if (!appLicenseService.isValid(license)) { + return new VerifyResult(false, "App license inactive or expired"); } device.setLastVerifiedAt(LocalDateTime.now()); @@ -138,7 +138,7 @@ public class DeviceService { device.setIsActive(false); device.setUpdatedAt(LocalDateTime.now()); deviceRepository.save(device); - companyService.decrementRegisteredDevices(device.getCompanyId()); + appLicenseService.decrementRegisteredDevices(device.getAppKey()); } @Transactional @@ -148,7 +148,7 @@ public class DeviceService { device.setIsActive(true); device.setUpdatedAt(LocalDateTime.now()); deviceRepository.save(device); - companyService.incrementRegisteredDevices(device.getCompanyId()); + appLicenseService.incrementRegisteredDevices(device.getAppKey()); } public static String hashToken(String token) { diff --git a/license-service/src/main/java/com/xuqm/license/service/LicenseAuthService.java b/license-service/src/main/java/com/xuqm/license/service/LicenseAuthService.java index b011672..9789646 100644 --- a/license-service/src/main/java/com/xuqm/license/service/LicenseAuthService.java +++ b/license-service/src/main/java/com/xuqm/license/service/LicenseAuthService.java @@ -14,20 +14,20 @@ public class LicenseAuthService { this.jwtUtil = jwtUtil; } - public String generateToken(String companyId, String deviceId, String recordId) { + public String generateToken(String appKey, String deviceId, String recordId) { return jwtUtil.generate(deviceId, Map.of( - "companyId", companyId, + "appKey", appKey, "deviceId", deviceId, "recordId", recordId )); } - public boolean verifyTokenPayload(String token, String companyId, String deviceId) { + public boolean verifyTokenPayload(String token, String appKey, String deviceId) { try { var claims = jwtUtil.parse(token); - String claimCompanyId = firstNonNull(claims.get("companyId", String.class), claims.get("company_id", String.class)); + String claimAppKey = claims.get("appKey", String.class); String claimDeviceId = firstNonNull(claims.get("deviceId", String.class), claims.get("device_id", String.class)); - return companyId.equals(claimCompanyId) && deviceId.equals(claimDeviceId); + return appKey.equals(claimAppKey) && deviceId.equals(claimDeviceId); } catch (Exception e) { return false; } diff --git a/tenant-service/src/main/java/com/xuqm/tenant/config/LicenseMigrationRunner.java b/tenant-service/src/main/java/com/xuqm/tenant/config/LicenseMigrationRunner.java deleted file mode 100644 index 6d10df5..0000000 --- a/tenant-service/src/main/java/com/xuqm/tenant/config/LicenseMigrationRunner.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.xuqm.tenant.config; - -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 com.xuqm.tenant.service.LicenseServiceClient; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -import java.security.SecureRandom; -import java.time.LocalDateTime; -import java.util.Base64; -import java.util.List; -import java.util.UUID; - -@Component -public class LicenseMigrationRunner implements ApplicationRunner { - - private final LicenseServiceClient licenseClient; - private final AppRepository appRepository; - private final FeatureServiceRepository featureServiceRepository; - private final TenantRepository tenantRepository; - - @Value("${license.migration.enabled:true}") - private boolean migrationEnabled; - - @Value("${license.migration.app-name:临床知识库}") - private String migrationAppName; - - private static final SecureRandom RANDOM = new SecureRandom(); - - public LicenseMigrationRunner(LicenseServiceClient licenseClient, - AppRepository appRepository, - FeatureServiceRepository featureServiceRepository, - TenantRepository tenantRepository) { - this.licenseClient = licenseClient; - this.appRepository = appRepository; - this.featureServiceRepository = featureServiceRepository; - this.tenantRepository = tenantRepository; - } - - @Override - @Transactional - public void run(ApplicationArguments args) { - if (!migrationEnabled) { - return; - } - - // 检查是否已有 LICENSE 类型的 FeatureService,如果有说明已经迁移过 - List allServices = featureServiceRepository.findAll(); - boolean hasLicense = allServices.stream() - .anyMatch(s -> s.getServiceType() == FeatureServiceEntity.ServiceType.LICENSE); - if (hasLicense) { - return; - } - - // 获取系统租户(第一个创建的租户) - TenantEntity systemTenant = tenantRepository.findFirstByOrderByCreatedAtAsc() - .orElse(null); - if (systemTenant == null) { - return; - } - - // 目前只有一个公司 f713d051-0fbe-4f2d-bec9-bf7b96fc9ce4 - // company_id 直接作为 appKey - String companyId = "f713d051-0fbe-4f2d-bec9-bf7b96fc9ce4"; - - // 检查是否已存在该 appKey 的应用 - if (appRepository.findByAppKey(companyId).isPresent()) { - // 应用已存在,只需确保 LICENSE 服务已开通 - ensureLicenseFeatureService(companyId); - return; - } - - // 创建应用,appKey = company_id(直接复用) - AppEntity app = new AppEntity(); - app.setId(UUID.randomUUID().toString()); - app.setTenantId(systemTenant.getId()); - app.setName(migrationAppName); - app.setPackageName("com.xuqm.clinical"); - app.setAppKey(companyId); // 直接复用 company_id 作为 appKey - app.setAppSecret(generateSecret()); - app.setCreatedAt(LocalDateTime.now()); - appRepository.save(app); - - // 自动开通 LICENSE 服务(所有平台) - ensureLicenseFeatureService(companyId); - - // 自动开通 FILE 服务(与创建应用时一致) - for (FeatureServiceEntity.Platform platform : FeatureServiceEntity.Platform.values()) { - FeatureServiceEntity entity = new FeatureServiceEntity(); - entity.setId(UUID.randomUUID().toString()); - entity.setAppKey(companyId); - entity.setPlatform(platform); - entity.setServiceType(FeatureServiceEntity.ServiceType.FILE); - entity.setEnabled(true); - entity.setCreatedAt(LocalDateTime.now()); - featureServiceRepository.save(entity); - } - } - - private void ensureLicenseFeatureService(String appKey) { - for (FeatureServiceEntity.Platform platform : FeatureServiceEntity.Platform.values()) { - featureServiceRepository - .findByAppKeyAndPlatformAndServiceType(appKey, platform, FeatureServiceEntity.ServiceType.LICENSE) - .orElseGet(() -> { - FeatureServiceEntity feature = new FeatureServiceEntity(); - feature.setId(UUID.randomUUID().toString()); - feature.setAppKey(appKey); - feature.setPlatform(platform); - feature.setServiceType(FeatureServiceEntity.ServiceType.LICENSE); - feature.setEnabled(true); - feature.setConfig("{\"maxDevices\":1}"); - feature.setCreatedAt(LocalDateTime.now()); - return featureServiceRepository.save(feature); - }); - } - } - - private String generateSecret() { - byte[] bytes = new byte[32]; - RANDOM.nextBytes(bytes); - return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); - } -} 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 8c3b976..2978174 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 @@ -154,7 +154,7 @@ public class FeatureServiceManager { } if (req.getServiceType() == FeatureServiceEntity.ServiceType.LICENSE) { appRepository.findByAppKey(normalizedAppId).ifPresent(app -> - licenseServiceClient.syncCompany(app.getAppKey(), app.getName(), 1)); + licenseServiceClient.syncAppLicense(app.getAppKey(), app.getName(), 1)); } 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 ad4c168..dad7cfb 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 @@ -23,9 +23,9 @@ public class LicenseServiceClient { private final RestTemplate restTemplate = new RestTemplate(); private final ObjectMapper objectMapper = new ObjectMapper(); - public boolean isCompanyExists(String appKey) { + public boolean isAppLicenseExists(String appKey) { try { - ResponseEntity response = callInternal("/api/license/internal/companies/" + appKey + "/status", HttpMethod.GET, null); + ResponseEntity response = callInternal("/api/license/internal/apps/" + appKey + "/status", HttpMethod.GET, null); if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { JsonNode node = objectMapper.readTree(response.getBody()); return node.path("data").path("exists").asBoolean(false); @@ -38,7 +38,7 @@ public class LicenseServiceClient { public List> listDevices(String appKey) { try { - ResponseEntity response = callInternal("/api/license/internal/companies/" + appKey + "/devices", HttpMethod.GET, null); + ResponseEntity response = callInternal("/api/license/internal/apps/" + appKey + "/devices", HttpMethod.GET, null); if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { JsonNode node = objectMapper.readTree(response.getBody()); return objectMapper.convertValue(node.path("data"), new com.fasterxml.jackson.core.type.TypeReference<>() {}); @@ -49,14 +49,14 @@ public class LicenseServiceClient { return List.of(); } - public void syncCompany(String appKey, String name, Integer maxDevices) { + public void syncAppLicense(String appKey, String name, Integer maxDevices) { try { Map body = Map.of( "id", appKey, "name", name, "maxDevices", maxDevices != null ? maxDevices : 1 ); - callInternal("/api/license/internal/companies", HttpMethod.POST, body); + callInternal("/api/license/internal/apps", HttpMethod.POST, body); } catch (Exception e) { // ignore } diff --git a/tenant-service/src/main/resources/application.yml b/tenant-service/src/main/resources/application.yml index 8d85ccb..150d95b 100644 --- a/tenant-service/src/main/resources/application.yml +++ b/tenant-service/src/main/resources/application.yml @@ -62,9 +62,6 @@ license: base-url: ${LICENSE_SERVICE_BASE_URL:http://license-service:8085} public-base-url: ${LICENSE_PUBLIC_BASE_URL:https://auth.dev.xuqinmin.com/} internal-token: ${LICENSE_INTERNAL_TOKEN:xuqm-license-internal-token} - migration: - enabled: true - app-name: 临床知识库 captcha: expire-seconds: 300