diff --git a/par-api/src/main/java/com/par/api/controller/ManifestController.java b/par-api/src/main/java/com/par/api/controller/ManifestController.java index 8b337a2..9e8e9af 100644 --- a/par-api/src/main/java/com/par/api/controller/ManifestController.java +++ b/par-api/src/main/java/com/par/api/controller/ManifestController.java @@ -1,12 +1,16 @@ package com.par.api.controller; +import com.par.common.util.HmacUtil; import com.par.core.dto.ApiResponse; import com.par.core.entity.Site; import com.par.core.entity.SiteConfig; import com.par.core.mapper.SiteConfigMapper; import com.par.core.mapper.SiteMapper; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -17,7 +21,7 @@ import java.util.stream.Collectors; /** * Manifest 控制器 - * 返回所有站点最新配置的索引 + * 返回所有站点最新配置的索引,支持 ETag 条件请求 */ @RestController @RequestMapping("/api/v1/manifest") @@ -28,15 +32,20 @@ public class ManifestController { private final SiteConfigMapper siteConfigMapper; /** - * 获取全局 Manifest + * 获取全局 Manifest(支持 ETag) + * 客户端传 If-None-Match 避免重复传输 */ @GetMapping - public ApiResponse> getManifest() { + public ResponseEntity>> getManifest( + @RequestHeader(value = "If-None-Match", required = false) String ifNoneMatch) { + List sites = siteMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(Site::getDeleted, 0) ); + // 构建 entries 并计算 ETag + StringBuilder etagBuilder = new StringBuilder(); List> entries = sites.stream().map(site -> { Map entry = new HashMap<>(); entry.put("siteId", site.getSiteId()); @@ -45,21 +54,31 @@ public class ManifestController { entry.put("schemaVersion", site.getSchemaVersion()); entry.put("status", site.getStatus() != null ? site.getStatus().getValue() : null); - // 获取最新已批准配置 SiteConfig latest = siteConfigMapper.selectLatestBySiteId(site.getSiteId()); if (latest != null) { entry.put("latestVersion", latest.getVersion()); entry.put("configHash", latest.getConfigHash()); entry.put("updatedAt", latest.getCreatedAt()); } + // 参与 ETag 计算 + etagBuilder.append(site.getSiteId()) + .append(latest != null ? latest.getConfigHash() : "none") + .append(";"); return entry; }).collect(Collectors.toList()); + String etag = HmacUtil.sha256(etagBuilder.toString()); + + // ETag 匹配 → 304 + if (ifNoneMatch != null && ifNoneMatch.equals(etag)) { + return ResponseEntity.status(HttpStatus.NOT_MODIFIED).eTag(etag).build(); + } + Map manifest = new HashMap<>(); manifest.put("version", "1.0"); manifest.put("sites", entries); - return ApiResponse.success(manifest); + return ResponseEntity.ok().eTag(etag).body(ApiResponse.success(manifest)); } }