feat: 站点提交/返回统一支持多名称多地址数组

- POST /sites/{id}/submit: names[]/urls[] 数组,兼容旧 name/baseUrl 单值
- 已有站点合并新名称/地址(去重+前置)
- 站点列表返回 names[]/urls[]
- API 文档和代码示例同步更新
This commit is contained in:
mediabot-pt
2026-06-30 13:47:56 +08:00
parent 28e6c2882f
commit 04f4af1f2c
4 changed files with 51 additions and 37 deletions

View File

@@ -164,13 +164,14 @@ curl -H "If-None-Match: a1b2c3d4..." http://par:9012/api/v1/manifest
#### POST /api/v1/sites/{siteId}/submit
```json
// Request
// Request (多名称/多地址)
{
"name": "PThome", // 必填
"baseUrl": "https://pthome.net", // 必填
"names": ["PThome", "PThome镜像"], // 必填
"urls": ["https://pthome.net", "https://pthome.mirror"], // 必填
// 或向后兼容单值: "name": "PThome", "baseUrl": "https://..."
"description": "PT 资源站",
"iconUrl": "https://...",
"siteType": "private" // private/public
"siteType": "private"
}
// Response 200
@@ -179,8 +180,8 @@ curl -H "If-None-Match: a1b2c3d4..." http://par:9012/api/v1/manifest
"data": {
"id": 1,
"siteId": "pthome",
"name": "PThome",
"baseUrl": "https://pthome.net",
"names": ["PThome", "PThome镜像"],
"urls": ["https://pthome.net", "https://pthome.mirror"],
"siteType": "private",
"schemaVersion": "1.0.0",
"status": "active",
@@ -282,9 +283,9 @@ curl -H "If-None-Match: a1b2c3d4..." http://par:9012/api/v1/manifest
|------|------|------|
| id | Long | 内部 ID |
| siteId | String | 站点唯一标识(如 `pthome`) |
| name | String | 显示名称 |
| names | String[] | 站点名称列表 |
| urls | String[] | 站点地址列表 |
| description | String | 描述 |
| baseUrl | String | 站点 URL |
| iconUrl | String | 图标 URL |
| siteType | String | `private` / `public` |
| schemaVersion | String | 当前 Schema 版本 |
@@ -372,10 +373,10 @@ def hmac_headers(method, path):
"X-Email": EMAIL
}
def submit_site(site_id, name, base_url, site_type="private"):
def submit_site(site_id, names, urls, site_type="private"):
path = f"/api/v1/sites/{site_id}/submit"
return requests.post(f"{BASE}{path}",
json={"name": name, "baseUrl": base_url, "siteType": site_type},
json={"names": names, "urls": urls, "siteType": site_type},
headers=hmac_headers("POST", path)
).json()
@@ -396,7 +397,7 @@ def download_config(site_id, version):
return resp.json()
# ---- 使用 ----
submit_site("pthome", "PThome", "https://pthome.net")
submit_site("pthome", ["PThome", "PThome镜像"], ["https://pthome.net", "https://pthome.mirror"])
submit_config("pthome", "1.0.0", '{"rules":[...]}')
print(list_configs("pthome"))
```
@@ -429,12 +430,12 @@ async function hmacHeaders(method, path) {
};
}
async function submitSite(siteId, name, baseUrl, siteType = 'private') {
async function submitSite(siteId, names, urls, siteType = 'private') {
const path = `/api/v1/sites/${siteId}/submit`;
const res = await fetch(`${BASE}${path}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...await hmacHeaders('POST', path) },
body: JSON.stringify({ name, baseUrl, siteType })
body: JSON.stringify({ names, urls, siteType })
});
return res.json();
}

View File

@@ -66,8 +66,8 @@ public class SiteController {
}
/**
* 提交站点收录(需 HMAC 鉴权)
* 创建或更新站点信息,自动设置为活跃状态
* 提交站点收录(需 PSK 鉴权)
* 支持 names[]/urls[] 数组或 name/baseUrl 单值(向后兼容)
*/
@PostMapping("/{siteId}/submit")
public ApiResponse<SiteDTO> submitSite(
@@ -75,22 +75,31 @@ public class SiteController {
@RequestBody Map<String, Object> payload,
HttpServletRequest request) {
String name = (String) payload.get("name");
@SuppressWarnings("unchecked")
List<String> names = (List<String>) payload.get("names");
@SuppressWarnings("unchecked")
List<String> urls = (List<String>) payload.get("urls");
// 向后兼容单值
if ((names == null || names.isEmpty()) && payload.containsKey("name")) {
names = List.of((String) payload.get("name"));
}
if ((urls == null || urls.isEmpty()) && payload.containsKey("baseUrl")) {
urls = List.of((String) payload.get("baseUrl"));
}
if (names == null || names.isEmpty()) {
return ApiResponse.error(400, "names or name is required");
}
if (urls == null || urls.isEmpty()) {
return ApiResponse.error(400, "urls or baseUrl is required");
}
String description = (String) payload.get("description");
String baseUrl = (String) payload.get("baseUrl");
String iconUrl = (String) payload.get("iconUrl");
String siteType = (String) payload.get("siteType");
if (name == null || name.isBlank()) {
return ApiResponse.error(400, "name is required");
}
if (baseUrl == null || baseUrl.isBlank()) {
return ApiResponse.error(400, "baseUrl is required");
}
Long submitterId = (Long) request.getAttribute("accountId");
Site site = siteService.submitSite(siteId, name, description, baseUrl, iconUrl, siteType, submitterId);
Site site = siteService.submitSite(siteId, names, urls, description, iconUrl, siteType, submitterId);
return ApiResponse.success(siteService.toDTO(site));
}

View File

@@ -29,7 +29,7 @@ public interface SiteService {
/**
* 提交站点信息(创建 pending,更新保持原状态)
*/
Site submitSite(String siteId, String name, String description, String baseUrl,
Site submitSite(String siteId, List<String> names, List<String> urls, String description,
String iconUrl, String siteType, Long submitterId);
/**

View File

@@ -90,18 +90,22 @@ public class SiteServiceImpl implements SiteService {
}
@Override
public Site submitSite(String siteId, String name, String description, String baseUrl,
String iconUrl, String siteType, Long submitterId) {
public Site submitSite(String siteId, List<String> names, List<String> urls,
String description, String iconUrl, String siteType, Long submitterId) {
Site existing = siteMapper.selectBySiteId(siteId);
if (existing != null) {
if (name != null && !name.isBlank()) {
List<String> names = fromJsonArray(existing.getNamesJson());
if (!names.contains(name)) { names.add(0, name); existing.setNamesJson(toJsonArray(names)); }
// 合并名称:新名称置前,去重
List<String> existingNames = fromJsonArray(existing.getNamesJson());
for (String n : names) {
if (n != null && !n.isBlank() && !existingNames.contains(n)) existingNames.add(0, n);
}
if (baseUrl != null && !baseUrl.isBlank()) {
List<String> urls = fromJsonArray(existing.getUrlsJson());
if (!urls.contains(baseUrl)) { urls.add(0, baseUrl); existing.setUrlsJson(toJsonArray(urls)); }
existing.setNamesJson(toJsonArray(existingNames));
// 合并地址
List<String> existingUrls = fromJsonArray(existing.getUrlsJson());
for (String u : urls) {
if (u != null && !u.isBlank() && !existingUrls.contains(u)) existingUrls.add(0, u);
}
existing.setUrlsJson(toJsonArray(existingUrls));
if (description != null) existing.setDescription(description);
if (iconUrl != null) existing.setIconUrl(iconUrl);
if (siteType != null) existing.setSiteType(SiteType.fromValue(siteType));
@@ -111,8 +115,8 @@ public class SiteServiceImpl implements SiteService {
Site site = new Site();
site.setSiteId(siteId);
site.setNamesJson(toJsonArray(name != null ? List.of(name) : List.of()));
site.setUrlsJson(toJsonArray(baseUrl != null ? List.of(baseUrl) : List.of()));
site.setNamesJson(toJsonArray(names.stream().filter(n -> n != null && !n.isBlank()).toList()));
site.setUrlsJson(toJsonArray(urls.stream().filter(u -> u != null && !u.isBlank()).toList()));
site.setDescription(description);
site.setIconUrl(iconUrl);
site.setSiteType(siteType != null ? SiteType.fromValue(siteType) : SiteType.UNKNOWN);