Align license service with app model
这个提交包含在:
父节点
4f59fead0a
当前提交
bc1165d22e
@ -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<ApiResponse<List<CompanyEntity>>> listCompanies() {
|
||||
return ResponseEntity.ok(ApiResponse.success(companyService.listAll()));
|
||||
}
|
||||
|
||||
@PostMapping("/companies")
|
||||
public ResponseEntity<ApiResponse<CompanyEntity>> 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<ApiResponse<Map<String, Object>>> getCompany(@PathVariable String id) {
|
||||
CompanyEntity company = companyService.getById(id);
|
||||
List<DeviceEntity> devices = deviceService.listByCompany(id);
|
||||
@GetMapping("/apps/{appKey}")
|
||||
public ResponseEntity<ApiResponse<Map<String, Object>>> getAppLicense(@PathVariable String appKey) {
|
||||
AppLicenseEntity license = appLicenseService.getByAppKey(appKey);
|
||||
List<DeviceEntity> devices = deviceService.listByApp(appKey);
|
||||
Map<String, Object> 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<ApiResponse<CompanyEntity>> 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<ApiResponse<Void>> deleteCompany(@PathVariable String id) {
|
||||
companyService.delete(id);
|
||||
return ResponseEntity.ok(ApiResponse.ok());
|
||||
}
|
||||
|
||||
@DeleteMapping("/devices/{id}")
|
||||
public ResponseEntity<ApiResponse<Void>> 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
|
||||
) {}
|
||||
}
|
||||
|
||||
@ -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<ApiResponse<Map<String, Object>>> getCompanyStatus(
|
||||
@GetMapping("/apps/{appKey}/status")
|
||||
public ResponseEntity<ApiResponse<Map<String, Object>>> 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<String, Object> 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<ApiResponse<List<DeviceEntity>>> listDevicesByCompany(
|
||||
@GetMapping("/apps/{appKey}/devices")
|
||||
public ResponseEntity<ApiResponse<List<DeviceEntity>>> 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<ApiResponse<CompanyEntity>> upsertCompany(
|
||||
@PostMapping("/apps")
|
||||
public ResponseEntity<ApiResponse<AppLicenseEntity>> 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,
|
||||
|
||||
@ -28,7 +28,7 @@ public class LicensePublicController {
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<ApiResponse<Map<String, Object>>> 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<ApiResponse<Map<String, Object>>> 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<String, Object> 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
|
||||
|
||||
@ -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; }
|
||||
@ -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; }
|
||||
|
||||
@ -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<AppLicenseEntity, String> {
|
||||
}
|
||||
@ -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<CompanyEntity, String> {
|
||||
List<CompanyEntity> findAllByOrderByCreatedAtDesc();
|
||||
}
|
||||
@ -10,6 +10,6 @@ import java.util.Optional;
|
||||
@Repository
|
||||
public interface DeviceRepository extends JpaRepository<DeviceEntity, String> {
|
||||
Optional<DeviceEntity> findByDeviceId(String deviceId);
|
||||
List<DeviceEntity> findByCompanyIdOrderByRegisteredAtDesc(String companyId);
|
||||
long countByCompanyIdAndIsActiveTrue(String companyId);
|
||||
List<DeviceEntity> findByAppKeyOrderByRegisteredAtDesc(String appKey);
|
||||
long countByAppKeyAndIsActiveTrue(String appKey);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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<CompanyEntity> 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;
|
||||
}
|
||||
}
|
||||
@ -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<DeviceEntity> listByCompany(String companyId) {
|
||||
return deviceRepository.findByCompanyIdOrderByRegisteredAtDesc(companyId);
|
||||
public List<DeviceEntity> 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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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<FeatureServiceEntity> 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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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<String> response = callInternal("/api/license/internal/companies/" + appKey + "/status", HttpMethod.GET, null);
|
||||
ResponseEntity<String> 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<Map<String, Object>> listDevices(String appKey) {
|
||||
try {
|
||||
ResponseEntity<String> response = callInternal("/api/license/internal/companies/" + appKey + "/devices", HttpMethod.GET, null);
|
||||
ResponseEntity<String> 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<String, Object> 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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户