feat: Manifest 接口支持 ETag 条件请求
- ETag = SHA256(所有 siteId + configHash 拼接) - 客户端传 If-None-Match 命中返回 304 无数据 - 替代新增 /latest 接口,同一端点高效轮询
This commit is contained in:
@@ -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<Map<String, Object>> getManifest() {
|
||||
public ResponseEntity<ApiResponse<Map<String, Object>>> getManifest(
|
||||
@RequestHeader(value = "If-None-Match", required = false) String ifNoneMatch) {
|
||||
|
||||
List<Site> sites = siteMapper.selectList(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Site>()
|
||||
.eq(Site::getDeleted, 0)
|
||||
);
|
||||
|
||||
// 构建 entries 并计算 ETag
|
||||
StringBuilder etagBuilder = new StringBuilder();
|
||||
List<Map<String, Object>> entries = sites.stream().map(site -> {
|
||||
Map<String, Object> 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<String, Object> 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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user