package com.xuqm.update.controller; import com.xuqm.common.model.ApiResponse; import com.xuqm.update.entity.AppVersionEntity; import com.xuqm.update.repository.AppVersionRepository; import com.xuqm.update.model.AppPackageInspectResult; 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/updates") public class AppVersionController { private final AppVersionRepository versionRepository; private final UpdateAssetService updateAssetService; public AppVersionController(AppVersionRepository versionRepository, UpdateAssetService updateAssetService) { this.versionRepository = versionRepository; this.updateAssetService = updateAssetService; } @GetMapping("/app/check") public ResponseEntity>> checkUpdate( @RequestParam String appId, @RequestParam AppVersionEntity.Platform platform, @RequestParam int currentVersionCode) { Optional latest = versionRepository .findTopByAppIdAndPlatformAndPublishStatusOrderByVersionCodeDesc( appId, platform, AppVersionEntity.PublishStatus.PUBLISHED); if (latest.isEmpty() || latest.get().getVersionCode() <= currentVersionCode) { return ResponseEntity.ok(ApiResponse.success(Map.of("needsUpdate", false))); } AppVersionEntity v = latest.get(); return ResponseEntity.ok(ApiResponse.success(Map.of( "needsUpdate", true, "versionName", v.getVersionName(), "versionCode", v.getVersionCode(), "downloadUrl", v.getDownloadUrl() != null ? v.getDownloadUrl() : "", "changeLog", v.getChangeLog() != null ? v.getChangeLog() : "", "forceUpdate", v.isForceUpdate(), "appStoreUrl", v.getAppStoreUrl() != null ? v.getAppStoreUrl() : "", "marketUrl", v.getMarketUrl() != null ? v.getMarketUrl() : "" ))); } @PostMapping("/app/upload") public ResponseEntity> upload( @RequestParam String appId, @RequestParam AppVersionEntity.Platform platform, @RequestParam(required = false) String versionName, @RequestParam(required = false) Integer versionCode, @RequestParam(required = false) String changeLog, @RequestParam(defaultValue = "false") boolean forceUpdate, @RequestParam(required = false) MultipartFile apkFile, @RequestParam(required = false) String scheduledPublishAt, @RequestParam(required = false) String webhookUrl, @RequestParam(required = false) String storeSubmitTargets, @RequestParam(defaultValue = "false") boolean autoPublishAfterReview, @RequestParam(defaultValue = "false") boolean publishImmediately, @RequestParam(required = false) String packageName, @RequestParam(required = false) String appStoreUrl, @RequestParam(required = false) String marketUrl) throws Exception { AppPackageInspectResult inspected = apkFile != null && !apkFile.isEmpty() ? updateAssetService.inspectAppPackage(apkFile) : null; String resolvedVersionName = hasText(versionName) ? versionName : (inspected != null ? inspected.versionName() : null); Integer resolvedVersionCode = versionCode != null ? versionCode : (inspected != null ? inspected.versionCode() : null); String resolvedPackageName = hasText(packageName) ? packageName : (inspected != null ? inspected.packageName() : null); if (!hasText(resolvedVersionName) || resolvedVersionCode == null) { throw new IllegalArgumentException("versionName and versionCode are required or must be readable from the uploaded package"); } if (platform != AppVersionEntity.Platform.HARMONY && (apkFile == null || apkFile.isEmpty())) { throw new IllegalArgumentException("apkFile is required for ANDROID and IOS releases"); } if (platform == AppVersionEntity.Platform.HARMONY && !hasText(marketUrl)) { throw new IllegalArgumentException("marketUrl is required for HARMONY releases"); } if (platform == AppVersionEntity.Platform.HARMONY && !hasText(resolvedPackageName)) { throw new IllegalArgumentException("packageName is required for HARMONY releases"); } AppVersionEntity entity = new AppVersionEntity(); entity.setId(UUID.randomUUID().toString()); entity.setAppId(appId); entity.setPlatform(platform); entity.setVersionName(resolvedVersionName); entity.setVersionCode(resolvedVersionCode); entity.setDownloadUrl(platform == AppVersionEntity.Platform.HARMONY ? null : updateAssetService.storeAppPackage(apkFile)); entity.setChangeLog(changeLog); entity.setForceUpdate(forceUpdate); entity.setPublishStatus(AppVersionEntity.PublishStatus.DRAFT); entity.setCreatedAt(LocalDateTime.now()); if (scheduledPublishAt != null && !scheduledPublishAt.isBlank()) { entity.setScheduledPublishAt(LocalDateTime.parse(scheduledPublishAt)); } entity.setWebhookUrl(webhookUrl); entity.setStoreSubmitTargets(storeSubmitTargets); entity.setAutoPublishAfterReview(autoPublishAfterReview); entity.setPackageName(resolvedPackageName); entity.setAppStoreUrl(appStoreUrl); entity.setMarketUrl(marketUrl); if (publishImmediately) { entity.setPublishStatus(AppVersionEntity.PublishStatus.PUBLISHED); entity.setGrayEnabled(false); entity.setGrayPercent(0); } return ResponseEntity.ok(ApiResponse.success(versionRepository.save(entity))); } @PostMapping("/app/inspect") public ResponseEntity> inspect(@RequestParam(required = false) MultipartFile apkFile) throws Exception { return ResponseEntity.ok(ApiResponse.success(updateAssetService.inspectAppPackage(apkFile))); } @PostMapping("/app/{id}/publish") public ResponseEntity> publish(@PathVariable String id) { AppVersionEntity entity = versionRepository.findById(id).orElseThrow(); entity.setPublishStatus(AppVersionEntity.PublishStatus.PUBLISHED); entity.setGrayEnabled(false); entity.setGrayPercent(0); return ResponseEntity.ok(ApiResponse.success(versionRepository.save(entity))); } @PostMapping("/app/{id}/unpublish") public ResponseEntity> unpublish(@PathVariable String id) { AppVersionEntity entity = versionRepository.findById(id).orElseThrow(); entity.setPublishStatus(AppVersionEntity.PublishStatus.DEPRECATED); return ResponseEntity.ok(ApiResponse.success(versionRepository.save(entity))); } @PostMapping("/app/{id}/gray") public ResponseEntity> gray( @PathVariable String id, @RequestBody Map body) { AppVersionEntity entity = versionRepository.findById(id).orElseThrow(); entity.setGrayEnabled(Boolean.TRUE.equals(body.get("enabled"))); entity.setGrayPercent(body.get("percent") instanceof Number n ? n.intValue() : 0); entity.setPublishStatus(AppVersionEntity.PublishStatus.PUBLISHED); return ResponseEntity.ok(ApiResponse.success(versionRepository.save(entity))); } @GetMapping("/app/list") public ResponseEntity>> list( @RequestParam String appId, @RequestParam AppVersionEntity.Platform platform) { return ResponseEntity.ok(ApiResponse.success( versionRepository.findByAppIdAndPlatformOrderByVersionCodeDesc(appId, platform))); } private boolean hasText(String value) { return value != null && !value.isBlank(); } }