License服务改造:平台无关、ops管理最大设备数、有效期不可变
- LICENSE审批只创建1条FeatureServiceEntity记录(不分平台) - FeatureServiceManager扩展平台无关查询到LICENSE - LicenseServiceClient新增getAppLicenseStatus/updateMaxDevices方法 - OpsController新增license管理接口(GET状态、PUT最大设备数) - AppLicenseService.update中expiresAt一旦设置不可修改 - 审批流程支持传入expiresAt参数 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
这个提交包含在:
父节点
0ed09a8229
当前提交
10f0043f15
@ -36,7 +36,8 @@ public class AppLicenseService {
|
||||
AppLicenseEntity license = getByAppKey(appKey);
|
||||
if (name != null) license.setName(name);
|
||||
if (maxDevices != null) license.setMaxDevices(maxDevices);
|
||||
if (expiresAt != null) license.setExpiresAt(expiresAt);
|
||||
// expiresAt 一旦设置不可修改
|
||||
if (expiresAt != null && license.getExpiresAt() == null) license.setExpiresAt(expiresAt);
|
||||
if (isActive != null) license.setIsActive(isActive);
|
||||
if (remark != null) license.setRemark(remark);
|
||||
license.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
@ -12,6 +12,7 @@ import com.xuqm.tenant.entity.RiskConfigEntity;
|
||||
import com.xuqm.tenant.entity.SensitiveWordEntity;
|
||||
import com.xuqm.tenant.service.OpsService;
|
||||
import com.xuqm.tenant.service.OpsPushDiagnosticsClient;
|
||||
import com.xuqm.tenant.service.LicenseServiceClient;
|
||||
import com.xuqm.tenant.service.RiskControlService;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -37,14 +38,17 @@ public class OpsController {
|
||||
private final FeatureServiceManager featureServiceManager;
|
||||
private final RiskControlService riskControlService;
|
||||
private final OpsPushDiagnosticsClient pushDiagnosticsClient;
|
||||
private final LicenseServiceClient licenseServiceClient;
|
||||
|
||||
public OpsController(OpsService opsService, FeatureServiceManager featureServiceManager,
|
||||
RiskControlService riskControlService,
|
||||
OpsPushDiagnosticsClient pushDiagnosticsClient) {
|
||||
OpsPushDiagnosticsClient pushDiagnosticsClient,
|
||||
LicenseServiceClient licenseServiceClient) {
|
||||
this.opsService = opsService;
|
||||
this.featureServiceManager = featureServiceManager;
|
||||
this.riskControlService = riskControlService;
|
||||
this.pushDiagnosticsClient = pushDiagnosticsClient;
|
||||
this.licenseServiceClient = licenseServiceClient;
|
||||
}
|
||||
|
||||
@PostMapping("/api/auth/ops/login")
|
||||
@ -112,8 +116,9 @@ public class OpsController {
|
||||
@PathVariable String requestId,
|
||||
@RequestBody(required = false) Map<String, String> body) {
|
||||
String reviewNote = body != null ? body.getOrDefault("reviewNote", "") : "";
|
||||
String expiresAt = body != null ? body.get("expiresAt") : null;
|
||||
return ResponseEntity.ok(ApiResponse.success(
|
||||
featureServiceManager.approveRequest(requestId, reviewNote)));
|
||||
featureServiceManager.approveRequest(requestId, reviewNote, expiresAt)));
|
||||
}
|
||||
|
||||
@PostMapping("/api/ops/service-requests/{requestId}/reject")
|
||||
@ -194,6 +199,35 @@ public class OpsController {
|
||||
body.getOrDefault("payload", "{}"))));
|
||||
}
|
||||
|
||||
/* ---------- License 管理 ---------- */
|
||||
@GetMapping("/api/ops/apps/{appKey}/license")
|
||||
@PreAuthorize("hasAuthority('ROLE_OPS')")
|
||||
public ResponseEntity<ApiResponse<Map<String, Object>>> getAppLicense(@PathVariable String appKey) {
|
||||
return ResponseEntity.ok(ApiResponse.success(licenseServiceClient.getAppLicenseStatus(appKey)));
|
||||
}
|
||||
|
||||
@PutMapping("/api/ops/apps/{appKey}/license/max-devices")
|
||||
@PreAuthorize("hasAuthority('ROLE_OPS')")
|
||||
public ResponseEntity<ApiResponse<Void>> updateMaxDevices(
|
||||
@PathVariable String appKey,
|
||||
@RequestBody Map<String, Object> body) {
|
||||
Object maxDevicesObj = body.get("maxDevices");
|
||||
if (maxDevicesObj == null) {
|
||||
throw new com.xuqm.common.exception.BusinessException(400, "最大设备数不能为空");
|
||||
}
|
||||
int maxDevices;
|
||||
if (maxDevicesObj instanceof Number n) {
|
||||
maxDevices = n.intValue();
|
||||
} else {
|
||||
maxDevices = Integer.parseInt(maxDevicesObj.toString());
|
||||
}
|
||||
if (maxDevices < 1) {
|
||||
throw new com.xuqm.common.exception.BusinessException(400, "最大设备数必须大于0");
|
||||
}
|
||||
licenseServiceClient.updateMaxDevices(appKey, maxDevices);
|
||||
return ResponseEntity.ok(ApiResponse.ok());
|
||||
}
|
||||
|
||||
/* ---------- 风控配置 ---------- */
|
||||
@GetMapping("/api/ops/risk/rules")
|
||||
@PreAuthorize("hasAuthority('ROLE_OPS')")
|
||||
|
||||
@ -120,6 +120,11 @@ public class FeatureServiceManager {
|
||||
*/
|
||||
@Transactional
|
||||
public ServiceActivationRequestEntity approveRequest(String requestId, String reviewNote) {
|
||||
return approveRequest(requestId, reviewNote, null);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ServiceActivationRequestEntity approveRequest(String requestId, String reviewNote, String expiresAt) {
|
||||
ServiceActivationRequestEntity req = requestRepository.findById(requestId)
|
||||
.orElseThrow(() -> new BusinessException(404, "申请不存在"));
|
||||
if (req.getStatus() != Status.PENDING) {
|
||||
@ -138,6 +143,17 @@ public class FeatureServiceManager {
|
||||
if (isAppWideService(req.getServiceType())) {
|
||||
List<FeatureServiceEntity> services = repository.findByAppKeyAndServiceType(normalizedAppId, req.getServiceType());
|
||||
if (services.isEmpty()) {
|
||||
if (req.getServiceType() == FeatureServiceEntity.ServiceType.LICENSE) {
|
||||
// LICENSE 不分平台,只创建一条记录
|
||||
FeatureServiceEntity created = new FeatureServiceEntity();
|
||||
created.setId(UUID.randomUUID().toString());
|
||||
created.setAppKey(normalizedAppId);
|
||||
created.setPlatform(FeatureServiceEntity.Platform.ANDROID);
|
||||
created.setServiceType(FeatureServiceEntity.ServiceType.LICENSE);
|
||||
created.setEnabled(true);
|
||||
created.setCreatedAt(LocalDateTime.now());
|
||||
repository.save(created);
|
||||
} else {
|
||||
for (FeatureServiceEntity.Platform platform : FeatureServiceEntity.Platform.values()) {
|
||||
FeatureServiceEntity created = new FeatureServiceEntity();
|
||||
created.setId(UUID.randomUUID().toString());
|
||||
@ -148,13 +164,14 @@ public class FeatureServiceManager {
|
||||
created.setCreatedAt(LocalDateTime.now());
|
||||
repository.save(created);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
services.forEach(service -> service.setEnabled(true));
|
||||
repository.saveAll(services);
|
||||
}
|
||||
if (req.getServiceType() == FeatureServiceEntity.ServiceType.LICENSE) {
|
||||
appRepository.findByAppKey(normalizedAppId).ifPresent(app ->
|
||||
licenseServiceClient.syncAppLicense(app.getAppKey(), app.getName(), 1));
|
||||
licenseServiceClient.syncAppLicense(app.getAppKey(), app.getName(), 1, expiresAt));
|
||||
}
|
||||
return req;
|
||||
}
|
||||
@ -197,7 +214,8 @@ public class FeatureServiceManager {
|
||||
|
||||
public FeatureServiceEntity getOrFail(String appKey, FeatureServiceEntity.Platform platform,
|
||||
FeatureServiceEntity.ServiceType serviceType) {
|
||||
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
|
||||
if (serviceType == FeatureServiceEntity.ServiceType.IM
|
||||
|| serviceType == FeatureServiceEntity.ServiceType.LICENSE) {
|
||||
return repository.findByAppKeyAndServiceType(appKey, serviceType)
|
||||
.stream()
|
||||
.findFirst()
|
||||
@ -212,7 +230,8 @@ public class FeatureServiceManager {
|
||||
FeatureServiceEntity.Platform platform,
|
||||
FeatureServiceEntity.ServiceType serviceType,
|
||||
String config) {
|
||||
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
|
||||
if (serviceType == FeatureServiceEntity.ServiceType.IM
|
||||
|| serviceType == FeatureServiceEntity.ServiceType.LICENSE) {
|
||||
List<FeatureServiceEntity> services = repository.findByAppKeyAndServiceType(appKey, serviceType);
|
||||
if (services.isEmpty()) {
|
||||
throw new BusinessException(404, "服务未配置");
|
||||
@ -568,7 +587,8 @@ public class FeatureServiceManager {
|
||||
FeatureServiceEntity.Platform platform,
|
||||
FeatureServiceEntity.ServiceType serviceType) {
|
||||
FeatureServiceEntity entity;
|
||||
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
|
||||
if (serviceType == FeatureServiceEntity.ServiceType.IM
|
||||
|| serviceType == FeatureServiceEntity.ServiceType.LICENSE) {
|
||||
entity = repository.findByAppKeyAndServiceType(appKey, serviceType)
|
||||
.stream()
|
||||
.findFirst()
|
||||
|
||||
@ -50,11 +50,44 @@ public class LicenseServiceClient {
|
||||
}
|
||||
|
||||
public void syncAppLicense(String appKey, String name, Integer maxDevices) {
|
||||
syncAppLicense(appKey, name, maxDevices, null);
|
||||
}
|
||||
|
||||
public void syncAppLicense(String appKey, String name, Integer maxDevices, String expiresAt) {
|
||||
try {
|
||||
Map<String, Object> body = new java.util.HashMap<>();
|
||||
body.put("id", appKey);
|
||||
body.put("name", name);
|
||||
body.put("maxDevices", maxDevices != null ? maxDevices : 1);
|
||||
if (expiresAt != null && !expiresAt.isBlank()) {
|
||||
body.put("expiresAt", expiresAt);
|
||||
}
|
||||
callInternal("/api/license/internal/apps", HttpMethod.POST, body);
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getAppLicenseStatus(String appKey) {
|
||||
try {
|
||||
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 objectMapper.convertValue(node.path("data"),
|
||||
new com.fasterxml.jackson.core.type.TypeReference<>() {});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
return Map.of("exists", false);
|
||||
}
|
||||
|
||||
public void updateMaxDevices(String appKey, int maxDevices) {
|
||||
try {
|
||||
Map<String, Object> body = Map.of(
|
||||
"id", appKey,
|
||||
"name", name,
|
||||
"maxDevices", maxDevices != null ? maxDevices : 1
|
||||
"maxDevices", maxDevices
|
||||
);
|
||||
callInternal("/api/license/internal/apps", HttpMethod.POST, body);
|
||||
} catch (Exception e) {
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户