fix(file): 封面 URL /upload/ 段逐轮叠加致门户裂图——出入口折叠自愈
build / build-push (push) Successful in 51s

- 生产 public-base-url 以 /upload/ 结尾而库内相对值以 /upload/ 开头,
  buildUrl 盲拼每轮"绝对化→存库"多叠一层(部署态实测叠到 4 层 404)。
- FileUtil.squashDuplicateUploadSeg:/upload/upload/ 永远非法(upload 非
  法类型名),buildUrl/absolutize 出口与 toRelativeIfLocal 存库侧统一折叠——
  发版即恢复显示无需清库,库内损伤值下次保存永久收敛。
- adminGet 封面回显绝对化:修管理端编辑页相对路径 404 被误当"没保存上"。
- 新增 FileUtilTest 8 用例锁行为(不叠加/损伤自愈/透传/往返幂等)。
This commit is contained in:
chenxing
2026-09-21 08:52:02 +08:00
parent 8504ca970c
commit 27e19923fb
3 changed files with 129 additions and 5 deletions
@@ -146,7 +146,12 @@ public class ServiceItemService {
public SvcItemEditVo adminGet(BoaUser user, String siteCode, Integer id) {
SiteInfo site = siteGuard.requireSiteManager(user, siteCode);
return SvcItemEditVo.of(requireItem(site, id));
ServiceItem item = requireItem(site, id);
SvcItemEditVo vo = SvcItemEditVo.of(item);
// 封面出参绝对化(与他端列表/详情口径一致),否则管理端编辑页 <img> 拿相对路径 404,看着像"封面没保存上";
// 保存侧 toRelativeIfLocal 会再相对化,往返幂等
vo.setCoverImage(FileUtil.absolutizePlain(item.getCoverImage()));
return vo;
}
/** 保存:itemCode 唯一/字典键归属/部门快照/applyUrl 白名单/表单与流程绑定校验;指南附件全量重绑 */
@@ -801,13 +806,13 @@ public class ServiceItemService {
return StringUtils.hasText(value) ? value : null;
}
/** 封面:本应用上传的相对路径原样入库;外部完整 URL 截取 /upload/ 相对段(与附件同口径) */
/** 封面:本应用上传的相对路径原样入库;外部完整 URL 截取 /upload/ 相对段(与附件同口径);折叠历史叠加段自愈 */
private static String toRelativeIfLocal(String coverImage) {
if (!StringUtils.hasText(coverImage)) {
return null;
}
int idx = coverImage.indexOf("/upload/");
return idx >= 0 ? coverImage.substring(idx) : coverImage;
return idx >= 0 ? FileUtil.squashDuplicateUploadSeg(coverImage.substring(idx)) : coverImage;
}
private static Map<String, Long> countMap(List<Object[]> rows) {
@@ -80,7 +80,23 @@ public final class FileUtil {
}
String base = publicBaseUrl.endsWith("/") ? publicBaseUrl : publicBaseUrl + "/";
String rel = relativeUrl.startsWith("/") ? relativeUrl.substring(1) : relativeUrl;
return base + rel;
return squashDuplicateUploadSeg(base + rel);
}
/**
* 折叠连续 {@code /upload/} 叠加段。存储布局是 {@code /upload/{image|file}/uuid.ext},
* "upload" 不是合法类型名,{@code /upload/upload/} 必为历史损伤:生产 public-base-url 以
* /upload/ 结尾而库内相对值以 /upload/ 开头,旧拼接每经一轮"绝对化→存库"多叠一层
* (实测部署态封面叠到四层致门户裂图)。出口统一折叠即可对已损伤库值自愈。
*/
public static String squashDuplicateUploadSeg(String url) {
if (url == null || url.isEmpty()) {
return url;
}
while (url.contains("/upload/upload/")) {
url = url.replace("/upload/upload/", "/upload/");
}
return url;
}
/**
@@ -93,10 +109,11 @@ public final class FileUtil {
return html;
}
String base = publicBaseUrl.endsWith("/") ? publicBaseUrl : publicBaseUrl + "/";
return html.replace("src=\"/upload/", "src=\"" + base + "upload/")
String replaced = html.replace("src=\"/upload/", "src=\"" + base + "upload/")
.replace("href=\"/upload/", "href=\"" + base + "upload/")
.replace("src='/upload/", "src='" + base + "upload/")
.replace("href='/upload/", "href='" + base + "upload/");
return squashDuplicateUploadSeg(replaced);
}
/** 裸相对封面值 → 完整 URL(空值原样;绝对地址原样) */