2026-04-21 22:07:29 +08:00
|
|
|
package com.xuqm.update.controller;
|
|
|
|
|
|
|
|
|
|
import com.xuqm.common.model.ApiResponse;
|
|
|
|
|
import com.xuqm.update.entity.RnBundleEntity;
|
2026-04-29 12:33:25 +08:00
|
|
|
import com.xuqm.update.model.RnBundleInspectResult;
|
2026-04-21 22:07:29 +08:00
|
|
|
import com.xuqm.update.repository.RnBundleRepository;
|
2026-04-29 19:08:13 +08:00
|
|
|
import com.xuqm.update.service.PublishConfigService;
|
2026-04-30 09:49:05 +08:00
|
|
|
import com.xuqm.update.service.UpdateOperationLogService;
|
2026-04-21 22:07:29 +08:00
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
|
|
import org.springframework.http.ResponseEntity;
|
2026-04-24 16:16:33 +08:00
|
|
|
import org.springframework.web.bind.annotation.*;
|
2026-04-21 22:07:29 +08:00
|
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
|
|
|
|
|
|
|
|
import java.time.LocalDateTime;
|
2026-04-24 16:16:33 +08:00
|
|
|
import java.util.List;
|
2026-04-21 22:07:29 +08:00
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.Optional;
|
|
|
|
|
import java.util.UUID;
|
2026-04-28 21:05:06 +08:00
|
|
|
import com.xuqm.update.service.UpdateAssetService;
|
2026-04-21 22:07:29 +08:00
|
|
|
|
|
|
|
|
@RestController
|
|
|
|
|
@RequestMapping("/api/v1/rn")
|
|
|
|
|
public class RnBundleController {
|
|
|
|
|
|
|
|
|
|
private final RnBundleRepository bundleRepository;
|
2026-04-28 21:05:06 +08:00
|
|
|
private final UpdateAssetService updateAssetService;
|
2026-04-29 19:08:13 +08:00
|
|
|
private final PublishConfigService publishConfigService;
|
2026-04-30 09:49:05 +08:00
|
|
|
private final UpdateOperationLogService operationLogService;
|
2026-04-21 22:07:29 +08:00
|
|
|
|
2026-04-27 19:23:11 +08:00
|
|
|
@Value("${update.base-url:https://update.dev.xuqinmin.com}")
|
2026-04-21 22:07:29 +08:00
|
|
|
private String baseUrl;
|
|
|
|
|
|
2026-04-29 19:08:13 +08:00
|
|
|
public RnBundleController(RnBundleRepository bundleRepository,
|
|
|
|
|
UpdateAssetService updateAssetService,
|
2026-04-30 09:49:05 +08:00
|
|
|
PublishConfigService publishConfigService,
|
|
|
|
|
UpdateOperationLogService operationLogService) {
|
2026-04-21 22:07:29 +08:00
|
|
|
this.bundleRepository = bundleRepository;
|
2026-04-28 21:05:06 +08:00
|
|
|
this.updateAssetService = updateAssetService;
|
2026-04-29 19:08:13 +08:00
|
|
|
this.publishConfigService = publishConfigService;
|
2026-04-30 09:49:05 +08:00
|
|
|
this.operationLogService = operationLogService;
|
2026-04-21 22:07:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@GetMapping("/update/check")
|
|
|
|
|
public ResponseEntity<ApiResponse<Map<String, Object>>> checkUpdate(
|
2026-05-07 19:39:42 +08:00
|
|
|
@RequestParam String appKey,
|
2026-04-21 22:07:29 +08:00
|
|
|
@RequestParam String moduleId,
|
|
|
|
|
@RequestParam String platform,
|
2026-04-29 15:46:40 +08:00
|
|
|
@RequestParam String currentVersion,
|
2026-05-08 12:00:33 +08:00
|
|
|
@RequestParam(required = false) String packageName,
|
|
|
|
|
@RequestParam(required = false) String userId) {
|
|
|
|
|
|
|
|
|
|
boolean allowAnonymousCheck = publishConfigService.allowAnonymousUpdateCheck(appKey);
|
|
|
|
|
if (!allowAnonymousCheck && (userId == null || userId.isBlank())) {
|
|
|
|
|
return ResponseEntity.ok(ApiResponse.success(Map.of("needsUpdate", false)));
|
|
|
|
|
}
|
2026-04-21 22:07:29 +08:00
|
|
|
|
|
|
|
|
RnBundleEntity.Platform p = RnBundleEntity.Platform.valueOf(platform.toUpperCase());
|
|
|
|
|
Optional<RnBundleEntity> latest = bundleRepository
|
|
|
|
|
.findTopByAppIdAndModuleIdAndPlatformAndPublishStatusOrderByCreatedAtDesc(
|
2026-05-07 19:39:42 +08:00
|
|
|
appKey, moduleId, p, RnBundleEntity.PublishStatus.PUBLISHED);
|
2026-04-21 22:07:29 +08:00
|
|
|
|
|
|
|
|
if (latest.isEmpty()) {
|
|
|
|
|
return ResponseEntity.ok(ApiResponse.success(Map.of("needsUpdate", false)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RnBundleEntity b = latest.get();
|
|
|
|
|
boolean needsUpdate = !b.getVersion().equals(currentVersion);
|
2026-05-08 12:00:33 +08:00
|
|
|
if (!allowAnonymousCheck && b.isGrayEnabled() && userId != null && !userId.isBlank()) {
|
|
|
|
|
boolean inGray = false;
|
|
|
|
|
if ("MEMBERS".equals(b.getGrayMode()) && b.getGrayMemberIds() != null) {
|
|
|
|
|
inGray = b.getGrayMemberIds().contains(userId);
|
|
|
|
|
} else {
|
|
|
|
|
int hash = Math.abs(userId.hashCode()) % 100;
|
|
|
|
|
inGray = hash < b.getGrayPercent();
|
|
|
|
|
}
|
|
|
|
|
if (!inGray) {
|
|
|
|
|
needsUpdate = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-21 22:07:29 +08:00
|
|
|
return ResponseEntity.ok(ApiResponse.success(Map.of(
|
|
|
|
|
"needsUpdate", needsUpdate,
|
2026-04-29 19:08:13 +08:00
|
|
|
"bundleVersion", parseBundleVersion(b.getVersion()),
|
2026-04-21 22:07:29 +08:00
|
|
|
"latestVersion", b.getVersion(),
|
2026-05-07 19:39:42 +08:00
|
|
|
"downloadUrl", resolvePublicBaseUrl() + "/api/v1/rn/files/" + appKey + "/" + platform.toLowerCase() + "/" + moduleId,
|
2026-04-21 22:07:29 +08:00
|
|
|
"md5", b.getMd5(),
|
|
|
|
|
"minCommonVersion", b.getMinCommonVersion() != null ? b.getMinCommonVersion() : "0.0.0",
|
2026-04-29 15:46:40 +08:00
|
|
|
"note", b.getNote() != null ? b.getNote() : "",
|
|
|
|
|
"packageName", b.getPackageName() != null ? b.getPackageName() : "",
|
|
|
|
|
"packageMatched", packageName == null || packageName.isBlank() || b.getPackageName() == null || b.getPackageName().isBlank()
|
|
|
|
|
|| b.getPackageName().equals(packageName)
|
2026-04-21 22:07:29 +08:00
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@PostMapping("/upload")
|
|
|
|
|
public ResponseEntity<ApiResponse<RnBundleEntity>> upload(
|
2026-05-07 19:39:42 +08:00
|
|
|
@RequestParam String appKey,
|
2026-04-29 12:33:25 +08:00
|
|
|
@RequestParam(required = false) String moduleId,
|
|
|
|
|
@RequestParam(required = false) RnBundleEntity.Platform platform,
|
|
|
|
|
@RequestParam(required = false) String version,
|
2026-04-21 22:07:29 +08:00
|
|
|
@RequestParam(required = false) String minCommonVersion,
|
2026-04-29 15:46:40 +08:00
|
|
|
@RequestParam(required = false) String packageName,
|
2026-04-21 22:07:29 +08:00
|
|
|
@RequestParam(required = false) String note,
|
|
|
|
|
@RequestParam MultipartFile bundle) throws Exception {
|
2026-04-29 12:33:25 +08:00
|
|
|
RnBundleInspectResult inspected = updateAssetService.inspectRnBundle(bundle);
|
|
|
|
|
String resolvedModuleId = hasText(moduleId) ? moduleId : inspected.moduleId();
|
|
|
|
|
String resolvedVersion = hasText(version) ? version : inspected.version();
|
|
|
|
|
String resolvedMinCommonVersion = hasText(minCommonVersion) ? minCommonVersion : inspected.minCommonVersion();
|
2026-04-29 15:46:40 +08:00
|
|
|
String resolvedPackageName = hasText(packageName) ? packageName : inspected.packageName();
|
2026-04-29 12:33:25 +08:00
|
|
|
RnBundleEntity.Platform resolvedPlatform = platform != null ? platform : parsePlatform(inspected.platform());
|
|
|
|
|
if (!hasText(resolvedModuleId) || !hasText(resolvedVersion) || resolvedPlatform == null) {
|
|
|
|
|
throw new IllegalArgumentException("moduleId, version and platform are required or must be readable from the bundle name");
|
|
|
|
|
}
|
2026-04-28 21:05:06 +08:00
|
|
|
UpdateAssetService.StoredRnBundle stored = updateAssetService.storeRnBundle(
|
2026-05-07 19:39:42 +08:00
|
|
|
appKey, resolvedPlatform.name(), resolvedModuleId, bundle);
|
2026-04-21 22:07:29 +08:00
|
|
|
|
|
|
|
|
RnBundleEntity entity = new RnBundleEntity();
|
|
|
|
|
entity.setId(UUID.randomUUID().toString());
|
2026-05-07 19:39:42 +08:00
|
|
|
entity.setAppId(appKey);
|
2026-04-29 12:33:25 +08:00
|
|
|
entity.setModuleId(resolvedModuleId);
|
|
|
|
|
entity.setPlatform(resolvedPlatform);
|
|
|
|
|
entity.setVersion(resolvedVersion);
|
2026-04-28 21:05:06 +08:00
|
|
|
entity.setBundleUrl(stored.bundlePath());
|
|
|
|
|
entity.setMd5(stored.md5());
|
2026-04-29 12:33:25 +08:00
|
|
|
entity.setMinCommonVersion(resolvedMinCommonVersion);
|
2026-04-29 15:46:40 +08:00
|
|
|
entity.setPackageName(resolvedPackageName);
|
2026-04-21 22:07:29 +08:00
|
|
|
entity.setNote(note);
|
|
|
|
|
entity.setPublishStatus(RnBundleEntity.PublishStatus.DRAFT);
|
2026-04-29 19:08:13 +08:00
|
|
|
entity.setPublishMode("MANUAL");
|
|
|
|
|
entity.setScheduledPublishAt(null);
|
|
|
|
|
entity.setGrayMode("PERCENT");
|
|
|
|
|
entity.setGrayMemberIds(null);
|
2026-04-21 22:07:29 +08:00
|
|
|
entity.setCreatedAt(LocalDateTime.now());
|
2026-04-30 09:49:05 +08:00
|
|
|
RnBundleEntity saved = bundleRepository.save(entity);
|
|
|
|
|
operationLogService.record(
|
|
|
|
|
saved.getAppId(),
|
|
|
|
|
"RN_BUNDLE",
|
|
|
|
|
saved.getId(),
|
|
|
|
|
"UPLOAD",
|
|
|
|
|
null,
|
|
|
|
|
Map.of(
|
|
|
|
|
"moduleId", saved.getModuleId(),
|
|
|
|
|
"platform", saved.getPlatform().name(),
|
|
|
|
|
"version", saved.getVersion(),
|
|
|
|
|
"minCommonVersion", saved.getMinCommonVersion() == null ? "" : saved.getMinCommonVersion(),
|
|
|
|
|
"packageName", saved.getPackageName() == null ? "" : saved.getPackageName()
|
|
|
|
|
));
|
|
|
|
|
return ResponseEntity.ok(ApiResponse.success(saved));
|
2026-04-21 22:07:29 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-29 17:35:52 +08:00
|
|
|
@RequestMapping(value = "/inspect", method = {RequestMethod.GET, RequestMethod.POST})
|
2026-04-29 12:33:25 +08:00
|
|
|
public ResponseEntity<ApiResponse<RnBundleInspectResult>> inspect(@RequestParam(required = false) MultipartFile bundle) throws Exception {
|
|
|
|
|
return ResponseEntity.ok(ApiResponse.success(updateAssetService.inspectRnBundle(bundle)));
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-24 16:16:33 +08:00
|
|
|
@GetMapping("/list")
|
|
|
|
|
public ResponseEntity<ApiResponse<List<RnBundleEntity>>> list(
|
2026-05-07 19:39:42 +08:00
|
|
|
@RequestParam String appKey,
|
2026-04-24 16:16:33 +08:00
|
|
|
@RequestParam(required = false) String moduleId,
|
|
|
|
|
@RequestParam(required = false) String platform) {
|
|
|
|
|
List<RnBundleEntity> result;
|
|
|
|
|
if (moduleId != null && platform != null) {
|
|
|
|
|
RnBundleEntity.Platform p = RnBundleEntity.Platform.valueOf(platform.toUpperCase());
|
2026-05-07 19:39:42 +08:00
|
|
|
result = bundleRepository.findByAppIdAndModuleIdAndPlatformOrderByCreatedAtDesc(appKey, moduleId, p);
|
2026-04-24 16:16:33 +08:00
|
|
|
} else if (moduleId != null) {
|
2026-05-07 19:39:42 +08:00
|
|
|
result = bundleRepository.findByAppIdAndModuleIdOrderByCreatedAtDesc(appKey, moduleId);
|
2026-04-24 16:16:33 +08:00
|
|
|
} else {
|
2026-05-07 19:39:42 +08:00
|
|
|
result = bundleRepository.findByAppIdOrderByCreatedAtDesc(appKey);
|
2026-04-24 16:16:33 +08:00
|
|
|
}
|
|
|
|
|
return ResponseEntity.ok(ApiResponse.success(result));
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 22:07:29 +08:00
|
|
|
@PostMapping("/{id}/publish")
|
2026-04-29 19:08:13 +08:00
|
|
|
public ResponseEntity<ApiResponse<RnBundleEntity>> publish(
|
|
|
|
|
@PathVariable String id,
|
|
|
|
|
@RequestBody(required = false) Map<String, Object> body) {
|
2026-04-21 22:07:29 +08:00
|
|
|
RnBundleEntity entity = bundleRepository.findById(id).orElseThrow();
|
2026-04-29 19:08:13 +08:00
|
|
|
boolean publishImmediately = body == null || !Boolean.FALSE.equals(body.get("publishImmediately"));
|
|
|
|
|
String scheduledPublishAt = body != null && body.get("scheduledPublishAt") != null
|
|
|
|
|
? body.get("scheduledPublishAt").toString() : null;
|
|
|
|
|
entity.setPublishMode(publishImmediately && (scheduledPublishAt == null || scheduledPublishAt.isBlank())
|
|
|
|
|
? "NOW" : "SCHEDULED");
|
|
|
|
|
if (publishImmediately && (scheduledPublishAt == null || scheduledPublishAt.isBlank())) {
|
|
|
|
|
entity.setPublishStatus(RnBundleEntity.PublishStatus.PUBLISHED);
|
|
|
|
|
entity.setScheduledPublishAt(null);
|
|
|
|
|
} else {
|
|
|
|
|
entity.setPublishStatus(RnBundleEntity.PublishStatus.DRAFT);
|
|
|
|
|
if (scheduledPublishAt != null && !scheduledPublishAt.isBlank()) {
|
|
|
|
|
entity.setScheduledPublishAt(LocalDateTime.parse(scheduledPublishAt));
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-24 16:16:33 +08:00
|
|
|
entity.setGrayEnabled(false);
|
|
|
|
|
entity.setGrayPercent(0);
|
2026-04-29 19:08:13 +08:00
|
|
|
entity.setGrayMode("PERCENT");
|
|
|
|
|
entity.setGrayMemberIds(null);
|
2026-04-30 09:49:05 +08:00
|
|
|
RnBundleEntity saved = bundleRepository.save(entity);
|
|
|
|
|
operationLogService.record(
|
|
|
|
|
saved.getAppId(),
|
|
|
|
|
"RN_BUNDLE",
|
|
|
|
|
saved.getId(),
|
|
|
|
|
publishImmediately && (scheduledPublishAt == null || scheduledPublishAt.isBlank()) ? "PUBLISH" : "SCHEDULE_PUBLISH",
|
|
|
|
|
null,
|
|
|
|
|
Map.of(
|
|
|
|
|
"moduleId", saved.getModuleId(),
|
|
|
|
|
"version", saved.getVersion(),
|
|
|
|
|
"publishMode", saved.getPublishMode(),
|
|
|
|
|
"scheduledPublishAt", saved.getScheduledPublishAt() == null ? "" : saved.getScheduledPublishAt().toString()
|
|
|
|
|
));
|
|
|
|
|
return ResponseEntity.ok(ApiResponse.success(saved));
|
2026-04-24 16:16:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@PostMapping("/{id}/unpublish")
|
2026-04-30 09:49:05 +08:00
|
|
|
public ResponseEntity<ApiResponse<RnBundleEntity>> unpublish(
|
|
|
|
|
@PathVariable String id,
|
|
|
|
|
@RequestBody(required = false) Map<String, Object> body) {
|
2026-04-24 16:16:33 +08:00
|
|
|
RnBundleEntity entity = bundleRepository.findById(id).orElseThrow();
|
2026-04-30 09:49:05 +08:00
|
|
|
String reason = body != null && body.get("reason") != null ? body.get("reason").toString().trim() : "";
|
|
|
|
|
if (reason.isBlank()) {
|
|
|
|
|
throw new IllegalArgumentException("unpublish reason is required");
|
|
|
|
|
}
|
2026-04-24 16:16:33 +08:00
|
|
|
entity.setPublishStatus(RnBundleEntity.PublishStatus.DEPRECATED);
|
2026-04-30 09:49:05 +08:00
|
|
|
RnBundleEntity saved = bundleRepository.save(entity);
|
|
|
|
|
operationLogService.record(
|
|
|
|
|
saved.getAppId(),
|
|
|
|
|
"RN_BUNDLE",
|
|
|
|
|
saved.getId(),
|
|
|
|
|
"UNPUBLISH",
|
|
|
|
|
reason,
|
|
|
|
|
Map.of(
|
|
|
|
|
"moduleId", saved.getModuleId(),
|
|
|
|
|
"version", saved.getVersion()
|
|
|
|
|
));
|
|
|
|
|
return ResponseEntity.ok(ApiResponse.success(saved));
|
2026-04-24 16:16:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@PostMapping("/{id}/gray")
|
|
|
|
|
public ResponseEntity<ApiResponse<RnBundleEntity>> gray(
|
2026-05-08 12:00:33 +08:00
|
|
|
@PathVariable String id,
|
|
|
|
|
@RequestBody Map<String, Object> body) throws Exception {
|
2026-04-24 16:16:33 +08:00
|
|
|
RnBundleEntity entity = bundleRepository.findById(id).orElseThrow();
|
2026-05-08 12:00:33 +08:00
|
|
|
if (publishConfigService.allowAnonymousUpdateCheck(entity.getAppId())) {
|
|
|
|
|
throw new com.xuqm.common.exception.BusinessException(400, "允许免登录检查更新的应用不支持灰度发布");
|
|
|
|
|
}
|
2026-04-29 19:08:13 +08:00
|
|
|
boolean enabled = Boolean.TRUE.equals(body.get("enabled"));
|
|
|
|
|
String grayMode = body.get("grayMode") == null ? "PERCENT" : body.get("grayMode").toString().trim().toUpperCase();
|
|
|
|
|
entity.setGrayEnabled(enabled);
|
|
|
|
|
if (!enabled) {
|
|
|
|
|
entity.setGrayPercent(0);
|
|
|
|
|
entity.setGrayMode("PERCENT");
|
|
|
|
|
entity.setGrayMemberIds(null);
|
|
|
|
|
} else if ("MEMBERS".equals(grayMode)) {
|
|
|
|
|
List<String> memberIds = extractMemberIds(body.get("memberIds"));
|
|
|
|
|
String selectionSource = body.get("selectionSource") == null ? "LOCAL"
|
|
|
|
|
: body.get("selectionSource").toString().trim().toUpperCase();
|
|
|
|
|
if (memberIds.isEmpty() && "CALLBACK".equals(selectionSource)) {
|
|
|
|
|
memberIds = publishConfigService.resolveGrayMembers(entity.getAppId(), body);
|
|
|
|
|
}
|
|
|
|
|
entity.setGrayMode("MEMBERS");
|
|
|
|
|
entity.setGrayMemberIds(toJson(memberIds));
|
|
|
|
|
entity.setGrayPercent(0);
|
|
|
|
|
} else {
|
|
|
|
|
entity.setGrayMode("PERCENT");
|
|
|
|
|
entity.setGrayPercent(body.get("percent") instanceof Number n ? n.intValue() : 0);
|
|
|
|
|
entity.setGrayMemberIds(null);
|
|
|
|
|
}
|
2026-04-24 16:16:33 +08:00
|
|
|
entity.setPublishStatus(RnBundleEntity.PublishStatus.PUBLISHED);
|
2026-04-30 09:49:05 +08:00
|
|
|
RnBundleEntity saved = bundleRepository.save(entity);
|
|
|
|
|
operationLogService.record(
|
|
|
|
|
saved.getAppId(),
|
|
|
|
|
"RN_BUNDLE",
|
|
|
|
|
saved.getId(),
|
|
|
|
|
"GRAY_UPDATE",
|
|
|
|
|
null,
|
|
|
|
|
Map.of(
|
|
|
|
|
"moduleId", saved.getModuleId(),
|
|
|
|
|
"version", saved.getVersion(),
|
|
|
|
|
"grayMode", saved.getGrayMode(),
|
|
|
|
|
"grayPercent", saved.getGrayPercent(),
|
|
|
|
|
"memberCount", saved.getGrayMemberIds() == null ? 0 : extractMemberIds(saved.getGrayMemberIds()).size()
|
|
|
|
|
));
|
|
|
|
|
return ResponseEntity.ok(ApiResponse.success(saved));
|
2026-04-21 22:07:29 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-24 10:42:11 +08:00
|
|
|
private String resolvePublicBaseUrl() {
|
|
|
|
|
String normalized = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
|
|
|
|
|
String suffix = "/api/v1/updates";
|
|
|
|
|
if (normalized.endsWith(suffix)) {
|
|
|
|
|
return normalized.substring(0, normalized.length() - suffix.length());
|
|
|
|
|
}
|
|
|
|
|
return normalized;
|
|
|
|
|
}
|
2026-04-29 12:33:25 +08:00
|
|
|
|
|
|
|
|
private boolean hasText(String value) {
|
|
|
|
|
return value != null && !value.isBlank();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private RnBundleEntity.Platform parsePlatform(String platform) {
|
|
|
|
|
if (!hasText(platform)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
return RnBundleEntity.Platform.valueOf(platform.toUpperCase());
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-29 19:08:13 +08:00
|
|
|
|
|
|
|
|
private int parseBundleVersion(String version) {
|
|
|
|
|
if (!hasText(version)) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
return Integer.parseInt(version.trim());
|
|
|
|
|
} catch (Exception ignored) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String toJson(List<String> values) {
|
|
|
|
|
try {
|
|
|
|
|
return new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(values == null ? List.of() : values);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
return "[]";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<String> extractMemberIds(Object raw) {
|
|
|
|
|
if (raw == null) {
|
|
|
|
|
return List.of();
|
|
|
|
|
}
|
|
|
|
|
if (raw instanceof List<?> list) {
|
|
|
|
|
return list.stream()
|
|
|
|
|
.map(String::valueOf)
|
|
|
|
|
.filter(this::hasText)
|
|
|
|
|
.map(String::trim)
|
|
|
|
|
.toList();
|
|
|
|
|
}
|
|
|
|
|
if (raw instanceof String s) {
|
|
|
|
|
if (!hasText(s)) {
|
|
|
|
|
return List.of();
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
return java.util.Arrays.stream(s.split(","))
|
|
|
|
|
.map(String::trim)
|
|
|
|
|
.filter(this::hasText)
|
|
|
|
|
.toList();
|
|
|
|
|
} catch (Exception ignored) {
|
|
|
|
|
return List.of();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return List.of();
|
|
|
|
|
}
|
2026-04-21 22:07:29 +08:00
|
|
|
}
|