package com.xuqm.update.controller; import com.xuqm.common.model.ApiResponse; import com.xuqm.update.entity.RnBundleEntity; import com.xuqm.update.model.RnBundleInspectResult; import com.xuqm.update.repository.RnBundleRepository; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; import com.xuqm.update.service.UpdateAssetService; @RestController @RequestMapping("/api/v1/rn") public class RnBundleController { private final RnBundleRepository bundleRepository; private final UpdateAssetService updateAssetService; @Value("${update.base-url:https://update.dev.xuqinmin.com}") private String baseUrl; public RnBundleController(RnBundleRepository bundleRepository, UpdateAssetService updateAssetService) { this.bundleRepository = bundleRepository; this.updateAssetService = updateAssetService; } @GetMapping("/update/check") public ResponseEntity>> checkUpdate( @RequestParam String appId, @RequestParam String moduleId, @RequestParam String platform, @RequestParam String currentVersion) { RnBundleEntity.Platform p = RnBundleEntity.Platform.valueOf(platform.toUpperCase()); Optional latest = bundleRepository .findTopByAppIdAndModuleIdAndPlatformAndPublishStatusOrderByCreatedAtDesc( appId, moduleId, p, RnBundleEntity.PublishStatus.PUBLISHED); if (latest.isEmpty()) { return ResponseEntity.ok(ApiResponse.success(Map.of("needsUpdate", false))); } RnBundleEntity b = latest.get(); boolean needsUpdate = !b.getVersion().equals(currentVersion); return ResponseEntity.ok(ApiResponse.success(Map.of( "needsUpdate", needsUpdate, "latestVersion", b.getVersion(), "downloadUrl", resolvePublicBaseUrl() + "/api/v1/rn/files/" + appId + "/" + platform.toLowerCase() + "/" + moduleId, "md5", b.getMd5(), "minCommonVersion", b.getMinCommonVersion() != null ? b.getMinCommonVersion() : "0.0.0", "note", b.getNote() != null ? b.getNote() : "" ))); } @PostMapping("/upload") public ResponseEntity> upload( @RequestParam String appId, @RequestParam(required = false) String moduleId, @RequestParam(required = false) RnBundleEntity.Platform platform, @RequestParam(required = false) String version, @RequestParam(required = false) String minCommonVersion, @RequestParam(required = false) String note, @RequestParam MultipartFile bundle) throws Exception { 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(); 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"); } UpdateAssetService.StoredRnBundle stored = updateAssetService.storeRnBundle( appId, resolvedPlatform.name(), resolvedModuleId, bundle); RnBundleEntity entity = new RnBundleEntity(); entity.setId(UUID.randomUUID().toString()); entity.setAppId(appId); entity.setModuleId(resolvedModuleId); entity.setPlatform(resolvedPlatform); entity.setVersion(resolvedVersion); entity.setBundleUrl(stored.bundlePath()); entity.setMd5(stored.md5()); entity.setMinCommonVersion(resolvedMinCommonVersion); entity.setNote(note); entity.setPublishStatus(RnBundleEntity.PublishStatus.DRAFT); entity.setCreatedAt(LocalDateTime.now()); return ResponseEntity.ok(ApiResponse.success(bundleRepository.save(entity))); } @PostMapping("/inspect") public ResponseEntity> inspect(@RequestParam(required = false) MultipartFile bundle) throws Exception { return ResponseEntity.ok(ApiResponse.success(updateAssetService.inspectRnBundle(bundle))); } @GetMapping("/list") public ResponseEntity>> list( @RequestParam String appId, @RequestParam(required = false) String moduleId, @RequestParam(required = false) String platform) { List result; if (moduleId != null && platform != null) { RnBundleEntity.Platform p = RnBundleEntity.Platform.valueOf(platform.toUpperCase()); result = bundleRepository.findByAppIdAndModuleIdAndPlatformOrderByCreatedAtDesc(appId, moduleId, p); } else if (moduleId != null) { result = bundleRepository.findByAppIdAndModuleIdOrderByCreatedAtDesc(appId, moduleId); } else { result = bundleRepository.findByAppIdOrderByCreatedAtDesc(appId); } return ResponseEntity.ok(ApiResponse.success(result)); } @PostMapping("/{id}/publish") public ResponseEntity> publish(@PathVariable String id) { RnBundleEntity entity = bundleRepository.findById(id).orElseThrow(); entity.setPublishStatus(RnBundleEntity.PublishStatus.PUBLISHED); entity.setGrayEnabled(false); entity.setGrayPercent(0); return ResponseEntity.ok(ApiResponse.success(bundleRepository.save(entity))); } @PostMapping("/{id}/unpublish") public ResponseEntity> unpublish(@PathVariable String id) { RnBundleEntity entity = bundleRepository.findById(id).orElseThrow(); entity.setPublishStatus(RnBundleEntity.PublishStatus.DEPRECATED); return ResponseEntity.ok(ApiResponse.success(bundleRepository.save(entity))); } @PostMapping("/{id}/gray") public ResponseEntity> gray( @PathVariable String id, @RequestBody Map body) { RnBundleEntity entity = bundleRepository.findById(id).orElseThrow(); entity.setGrayEnabled(Boolean.TRUE.equals(body.get("enabled"))); entity.setGrayPercent(body.get("percent") instanceof Number n ? n.intValue() : 0); entity.setPublishStatus(RnBundleEntity.PublishStatus.PUBLISHED); return ResponseEntity.ok(ApiResponse.success(bundleRepository.save(entity))); } 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; } 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; } } }