比较提交
2 次代码提交
ccb976c605
...
1a0ef7d886
| 作者 | SHA1 | 提交日期 | |
|---|---|---|---|
|
|
1a0ef7d886 | ||
|
|
843ed69f3c |
0
common/NODE
普通文件
0
common/NODE
普通文件
@ -51,7 +51,7 @@ public class LicenseAdminController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
AppLicenseEntity updated = appLicenseService.update(
|
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));
|
return ResponseEntity.ok(ApiResponse.success(updated));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -81,15 +81,17 @@ public class LicenseInternalController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/devices/{deviceId}")
|
@GetMapping("/devices/{deviceId}")
|
||||||
public ResponseEntity<ApiResponse<DeviceEntity>> getDevice(
|
public ResponseEntity<ApiResponse<List<DeviceEntity>>> getDevice(
|
||||||
@RequestHeader(value = "X-Internal-Token", required = false) String token,
|
@RequestHeader(value = "X-Internal-Token", required = false) String token,
|
||||||
@PathVariable String deviceId) {
|
@PathVariable String deviceId) {
|
||||||
if (!isAllowed(token)) {
|
if (!isAllowed(token)) {
|
||||||
return ResponseEntity.status(403).body(ApiResponse.error(403, "Forbidden"));
|
return ResponseEntity.status(403).body(ApiResponse.error(403, "Forbidden"));
|
||||||
}
|
}
|
||||||
return deviceService.findByDeviceId(deviceId)
|
List<DeviceEntity> devices = deviceService.findByDeviceId(deviceId);
|
||||||
.map(d -> ResponseEntity.ok(ApiResponse.success(d)))
|
if (devices.isEmpty()) {
|
||||||
.orElse(ResponseEntity.ok(ApiResponse.error(404, "Device not found")));
|
return ResponseEntity.ok(ApiResponse.error(404, "Device not found"));
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(devices));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAllowed(String token) {
|
private boolean isAllowed(String token) {
|
||||||
|
|||||||
@ -4,10 +4,12 @@ import jakarta.persistence.Column;
|
|||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
|
import jakarta.persistence.UniqueConstraint;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "devices")
|
@Table(name = "devices",
|
||||||
|
uniqueConstraints = @UniqueConstraint(name = "uk_app_key_device_id", columnNames = {"app_key", "device_id"}))
|
||||||
public class DeviceEntity {
|
public class DeviceEntity {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
|
|||||||
@ -9,7 +9,8 @@ import java.util.Optional;
|
|||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface DeviceRepository extends JpaRepository<DeviceEntity, String> {
|
public interface DeviceRepository extends JpaRepository<DeviceEntity, String> {
|
||||||
Optional<DeviceEntity> findByDeviceId(String deviceId);
|
Optional<DeviceEntity> findByAppKeyAndDeviceId(String appKey, String deviceId);
|
||||||
|
List<DeviceEntity> findByDeviceId(String deviceId);
|
||||||
List<DeviceEntity> findByAppKeyOrderByRegisteredAtDesc(String appKey);
|
List<DeviceEntity> findByAppKeyOrderByRegisteredAtDesc(String appKey);
|
||||||
long countByAppKeyAndIsActiveTrue(String appKey);
|
long countByAppKeyAndIsActiveTrue(String appKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,7 +34,7 @@ public class DeviceService {
|
|||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<DeviceEntity> findByDeviceId(String deviceId) {
|
public List<DeviceEntity> findByDeviceId(String deviceId) {
|
||||||
return deviceRepository.findByDeviceId(deviceId);
|
return deviceRepository.findByDeviceId(deviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,8 +48,8 @@ public class DeviceService {
|
|||||||
JsonNode userInfo) {
|
JsonNode userInfo) {
|
||||||
validatePackageName(appKey, packageName);
|
validatePackageName(appKey, packageName);
|
||||||
|
|
||||||
// Check if device already registered
|
// Check if device already registered for this app
|
||||||
Optional<DeviceEntity> existingOpt = findByDeviceId(deviceId);
|
Optional<DeviceEntity> existingOpt = deviceRepository.findByAppKeyAndDeviceId(appKey, deviceId);
|
||||||
if (existingOpt.isPresent()) {
|
if (existingOpt.isPresent()) {
|
||||||
DeviceEntity existing = existingOpt.get();
|
DeviceEntity existing = existingOpt.get();
|
||||||
if (!Boolean.TRUE.equals(existing.getIsActive())) {
|
if (!Boolean.TRUE.equals(existing.getIsActive())) {
|
||||||
@ -58,8 +58,14 @@ public class DeviceService {
|
|||||||
// Re-issue token
|
// Re-issue token
|
||||||
String token = licenseAuthService.generateToken(appKey, deviceId, existing.getId());
|
String token = licenseAuthService.generateToken(appKey, deviceId, existing.getId());
|
||||||
String tokenHash = hashToken(token);
|
String tokenHash = hashToken(token);
|
||||||
if (existing.getAppKey() == null || existing.getAppKey().isBlank()) {
|
String oldAppKey = existing.getAppKey();
|
||||||
|
if (oldAppKey == null || oldAppKey.isBlank()) {
|
||||||
existing.setAppKey(appKey);
|
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.setDeviceName(firstNonBlank(deviceName, existing.getDeviceName()));
|
||||||
existing.setDeviceModel(firstNonBlank(deviceModel, existing.getDeviceModel()));
|
existing.setDeviceModel(firstNonBlank(deviceModel, existing.getDeviceModel()));
|
||||||
@ -120,7 +126,7 @@ public class DeviceService {
|
|||||||
return new VerifyResult(false, "Token mismatch");
|
return new VerifyResult(false, "Token mismatch");
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceEntity device = findByDeviceId(deviceId).orElse(null);
|
DeviceEntity device = deviceRepository.findByAppKeyAndDeviceId(appKey, deviceId).orElse(null);
|
||||||
if (device == null || !Boolean.TRUE.equals(device.getIsActive())) {
|
if (device == null || !Boolean.TRUE.equals(device.getIsActive())) {
|
||||||
return new VerifyResult(false, "Device not found or deactivated");
|
return new VerifyResult(false, "Device not found or deactivated");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.xuqm.tenant.controller;
|
|||||||
|
|
||||||
import com.xuqm.common.model.ApiResponse;
|
import com.xuqm.common.model.ApiResponse;
|
||||||
import com.xuqm.common.exception.BusinessException;
|
import com.xuqm.common.exception.BusinessException;
|
||||||
|
import com.xuqm.common.security.LicenseFileCrypto;
|
||||||
import com.xuqm.tenant.dto.CreateAppRequest;
|
import com.xuqm.tenant.dto.CreateAppRequest;
|
||||||
import com.xuqm.tenant.entity.AppEntity;
|
import com.xuqm.tenant.entity.AppEntity;
|
||||||
import com.xuqm.tenant.entity.FeatureServiceEntity;
|
import com.xuqm.tenant.entity.FeatureServiceEntity;
|
||||||
@ -174,6 +175,34 @@ public class AppController {
|
|||||||
.body(encrypted.getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
.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<ApiResponse<Map<String, Object>>> parseLicenseFile(@RequestBody Map<String, String> 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<String, Object> 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) {
|
private static String sanitizeFileName(String value) {
|
||||||
String name = value == null || value.isBlank() ? "license" : value.trim();
|
String name = value == null || value.isBlank() ? "license" : value.trim();
|
||||||
return name.replaceAll("[\\\\/:*?\"<>|\\s]+", "_");
|
return name.replaceAll("[\\\\/:*?\"<>|\\s]+", "_");
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户