feat: sites表支持多名称/多地址(names/urls JSON列), DTO/Service/前端全部适配

This commit is contained in:
mediabot-pt
2026-06-30 08:53:22 +08:00
parent b37b5cc5de
commit aa1e3e9b28
6 changed files with 122 additions and 77 deletions

View File

@@ -5,6 +5,7 @@ import com.par.core.enums.SiteType;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* 站点信息 DTO
@@ -14,9 +15,10 @@ public class SiteDTO {
private Long id;
private String siteId;
private String name;
private List<String> names;
private String description;
private String baseUrl;
private String notes;
private List<String> urls;
private String iconUrl;
private SiteType siteType;
private String schemaVersion;

View File

@@ -20,15 +20,26 @@ public class Site {
@TableField("site_id")
private String siteId;
/** @deprecated 字段保留用于 Flyway 数据迁移,业务逻辑改用 names */
@TableField("name")
private String name;
@TableField("names")
private String namesJson;
@TableField("description")
private String description;
@TableField("notes")
private String notes;
/** @deprecated 字段保留用于 Flyway 数据迁移,业务逻辑改用 urls */
@TableField("base_url")
private String baseUrl;
@TableField("urls")
private String urlsJson;
@TableField("icon_url")
private String iconUrl;

View File

@@ -1,6 +1,7 @@
package com.par.core.service.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.par.core.dto.SiteDTO;
import com.par.core.entity.Site;
@@ -12,14 +13,10 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.LinkedHashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 站点服务实现
*/
@Slf4j
@Service
@RequiredArgsConstructor
@@ -28,6 +25,31 @@ public class SiteServiceImpl implements SiteService {
private final SiteMapper siteMapper;
private static final ObjectMapper objectMapper = new ObjectMapper();
// ==================== 工具方法 ====================
private String toJsonArray(List<String> list) {
if (list == null || list.isEmpty()) return null;
try { return objectMapper.writeValueAsString(list); } catch (JsonProcessingException e) { return null; }
}
private List<String> fromJsonArray(String json) {
if (json == null || json.isBlank()) return new ArrayList<>();
try { return objectMapper.readValue(json, new TypeReference<List<String>>() {}); }
catch (Exception e) { return new ArrayList<>(); }
}
private String firstName(Site site) {
List<String> names = fromJsonArray(site.getNamesJson());
return names.isEmpty() ? site.getSiteId() : names.get(0);
}
private String firstUrl(Site site) {
List<String> urls = fromJsonArray(site.getUrlsJson());
return urls.isEmpty() ? "" : urls.get(0);
}
// ==================== 业务方法 ====================
@Override
public List<SiteDTO> listActiveSites() {
return siteMapper.selectList(
@@ -48,9 +70,10 @@ public class SiteServiceImpl implements SiteService {
SiteDTO dto = new SiteDTO();
dto.setId(site.getId());
dto.setSiteId(site.getSiteId());
dto.setName(site.getName());
dto.setNames(fromJsonArray(site.getNamesJson()));
dto.setDescription(site.getDescription());
dto.setBaseUrl(site.getBaseUrl());
dto.setNotes(site.getNotes());
dto.setUrls(fromJsonArray(site.getUrlsJson()));
dto.setIconUrl(site.getIconUrl());
dto.setSiteType(site.getSiteType());
dto.setSchemaVersion(site.getSchemaVersion());
@@ -63,29 +86,32 @@ public class SiteServiceImpl implements SiteService {
@Override
public Site submitSite(String siteId, String name, String description, String baseUrl,
String iconUrl, String siteType, Long submitterId) {
// Phase 1: 简化实现,直接创建/更新
Site existing = siteMapper.selectBySiteId(siteId);
if (existing != null) {
existing.setName(name);
existing.setDescription(description);
existing.setBaseUrl(baseUrl);
existing.setIconUrl(iconUrl);
if (siteType != null) {
existing.setSiteType(com.par.core.enums.SiteType.fromValue(siteType));
if (name != null && !name.isBlank()) {
List<String> names = fromJsonArray(existing.getNamesJson());
if (!names.contains(name)) { names.add(0, name); existing.setNamesJson(toJsonArray(names)); }
}
if (baseUrl != null && !baseUrl.isBlank()) {
List<String> urls = fromJsonArray(existing.getUrlsJson());
if (!urls.contains(baseUrl)) { urls.add(0, baseUrl); existing.setUrlsJson(toJsonArray(urls)); }
}
if (description != null) existing.setDescription(description);
if (iconUrl != null) existing.setIconUrl(iconUrl);
if (siteType != null) existing.setSiteType(SiteType.fromValue(siteType));
siteMapper.updateById(existing);
return existing;
}
Site site = new Site();
site.setSiteId(siteId);
site.setName(name);
site.setNamesJson(toJsonArray(name != null ? List.of(name) : List.of()));
site.setUrlsJson(toJsonArray(baseUrl != null ? List.of(baseUrl) : List.of()));
site.setDescription(description);
site.setBaseUrl(baseUrl);
site.setIconUrl(iconUrl);
site.setSiteType(siteType != null ? com.par.core.enums.SiteType.fromValue(siteType) : com.par.core.enums.SiteType.UNKNOWN);
site.setSiteType(siteType != null ? SiteType.fromValue(siteType) : SiteType.UNKNOWN);
site.setSchemaVersion("1.0.0");
site.setStatus(SiteStatus.PENDING); // 新站点需审核
site.setStatus(SiteStatus.PENDING);
site.setSubmitterId(submitterId);
siteMapper.insert(site);
return site;
@@ -94,42 +120,13 @@ public class SiteServiceImpl implements SiteService {
@Override
public Site updateSiteInfo(String siteId, String status, List<String> names,
List<String> urls, String notes, Long submitterId) {
// 映射状态:正常→ACTIVE, 关停/已迁移→DEPRECATED, 维护中→ACTIVE(带备注)
SiteStatus siteStatus;
if (status == null) {
siteStatus = SiteStatus.ACTIVE;
} else {
switch (status) {
case "关停":
case "已迁移":
siteStatus = SiteStatus.DEPRECATED;
break;
case "维护中":
case "正常":
default:
siteStatus = SiteStatus.ACTIVE;
break;
}
}
// 构建额外信息 JSON
Map<String, Object> extraInfo = new LinkedHashMap<>();
if (names != null && !names.isEmpty()) extraInfo.put("names", names);
if (urls != null && !urls.isEmpty()) extraInfo.put("urls", urls);
if (notes != null && !notes.isBlank()) extraInfo.put("notes", notes);
String extraJson = null;
try {
if (!extraInfo.isEmpty()) extraJson = objectMapper.writeValueAsString(extraInfo);
} catch (JsonProcessingException ignored) {}
SiteStatus siteStatus = mapStatus(status);
Site existing = siteMapper.selectBySiteId(siteId);
if (existing != null) {
// 更新已有站点
if (names != null && !names.isEmpty()) existing.setName(names.get(0));
if (urls != null && !urls.isEmpty()) existing.setBaseUrl(urls.get(0));
if (extraJson != null) existing.setDescription(
existing.getDescription() != null && !existing.getDescription().isBlank()
? existing.getDescription() : extraJson);
if (names != null && !names.isEmpty()) existing.setNamesJson(toJsonArray(names));
if (urls != null && !urls.isEmpty()) existing.setUrlsJson(toJsonArray(urls));
if (notes != null && !notes.isBlank()) existing.setNotes(notes);
existing.setStatus(siteStatus);
existing.setSubmitterId(submitterId != null ? submitterId : existing.getSubmitterId());
siteMapper.updateById(existing);
@@ -137,12 +134,11 @@ public class SiteServiceImpl implements SiteService {
return existing;
}
// 创建新站点(PENDING 状态)
Site site = new Site();
site.setSiteId(siteId);
site.setName(names != null && !names.isEmpty() ? names.get(0) : siteId);
site.setBaseUrl(urls != null && !urls.isEmpty() ? urls.get(0) : "");
site.setDescription(extraJson);
site.setNamesJson(toJsonArray(names != null && !names.isEmpty() ? names : List.of(siteId)));
site.setUrlsJson(toJsonArray(urls != null && !urls.isEmpty() ? urls : List.of()));
site.setNotes(notes);
site.setSiteType(SiteType.UNKNOWN);
site.setSchemaVersion("1.0.0");
site.setStatus(SiteStatus.PENDING);
@@ -156,19 +152,26 @@ public class SiteServiceImpl implements SiteService {
public Site updateSite(String siteId, String name, String description, String baseUrl,
String siteType, Long operatorId) {
Site site = siteMapper.selectBySiteId(siteId);
if (site == null) {
throw new IllegalArgumentException("站点不存在: " + siteId);
if (site == null) throw new IllegalArgumentException("站点不存在: " + siteId);
if (name != null && !name.isBlank()) {
List<String> names = fromJsonArray(site.getNamesJson());
if (names.isEmpty()) names.add(name);
else names.set(0, name);
site.setNamesJson(toJsonArray(names));
}
if (baseUrl != null && !baseUrl.isBlank()) {
List<String> urls = fromJsonArray(site.getUrlsJson());
if (urls.isEmpty()) urls.add(baseUrl);
else urls.set(0, baseUrl);
site.setUrlsJson(toJsonArray(urls));
}
if (name != null && !name.isBlank()) site.setName(name);
if (baseUrl != null && !baseUrl.isBlank()) site.setBaseUrl(baseUrl);
if (description != null) site.setDescription(description);
if (siteType != null && !siteType.isBlank()) {
try {
site.setSiteType(SiteType.fromValue(siteType));
} catch (Exception ignored) {}
try { site.setSiteType(SiteType.fromValue(siteType)); } catch (Exception ignored) {}
}
siteMapper.updateById(site);
log.info("站点编辑: siteId={} name={}", siteId, name);
log.info("站点编辑: siteId={}", siteId);
return site;
}
@@ -199,6 +202,14 @@ public class SiteServiceImpl implements SiteService {
@Override
public void rejectSite(Long id) {
siteMapper.deleteById(id); // 物理删除
siteMapper.deleteById(id);
}
private static SiteStatus mapStatus(String status) {
if (status == null) return SiteStatus.ACTIVE;
return switch (status) {
case "关停", "已迁移" -> SiteStatus.DEPRECATED;
default -> SiteStatus.ACTIVE;
};
}
}

View File

@@ -0,0 +1,9 @@
-- 支持多站点名称和多地址
ALTER TABLE sites
ADD COLUMN names TEXT COMMENT '站点名称列表(JSON数组)' AFTER name,
ADD COLUMN urls TEXT COMMENT '站点地址列表(JSON数组)' AFTER base_url,
ADD COLUMN notes TEXT COMMENT '备注/其他信息' AFTER description;
-- 迁移现有单值数据到JSON数组
UPDATE sites SET names = JSON_ARRAY(name) WHERE name IS NOT NULL AND name != '';
UPDATE sites SET urls = JSON_ARRAY(base_url) WHERE base_url IS NOT NULL AND base_url != '';