diff --git a/par-api/src/main/java/com/par/api/controller/ConfigController.java b/par-api/src/main/java/com/par/api/controller/ConfigController.java index b85f84b..a7e3b23 100644 --- a/par-api/src/main/java/com/par/api/controller/ConfigController.java +++ b/par-api/src/main/java/com/par/api/controller/ConfigController.java @@ -52,7 +52,34 @@ public class ConfigController { } /** - * 下载指定版本配置(支持 ETag 条件请求) + * 查看配置内容(管理员用,返回 ApiResponse 包装) + */ + @GetMapping("/{siteId}/{version}/content") + public ApiResponse viewConfigContent( + @PathVariable("siteId") String siteId, + @PathVariable("version") String version) { + + SiteConfig config = configService.getConfig(siteId, version); + if (config == null) { + return ApiResponse.error(404, "配置不存在: " + siteId + " v" + version); + } + + String content; + try { + content = storageService.read(config.getStoragePath()); + } catch (Exception e) { + log.error("Failed to read config: siteId={}, version={}", siteId, version, e); + return ApiResponse.error(500, "读取配置文件失败: " + e.getMessage()); + } + if (content == null) { + return ApiResponse.error(404, "配置文件不存在: " + config.getStoragePath()); + } + + return ApiResponse.success(content); + } + + /** + * 下载指定版本配置(支持 ETag 条件请求,供 mediabot 客户端使用) */ @GetMapping("/{siteId}/{version}") public ResponseEntity downloadConfig( diff --git a/par-api/src/main/java/com/par/api/controller/SiteController.java b/par-api/src/main/java/com/par/api/controller/SiteController.java index b2d6caf..a7aae4c 100644 --- a/par-api/src/main/java/com/par/api/controller/SiteController.java +++ b/par-api/src/main/java/com/par/api/controller/SiteController.java @@ -38,6 +38,29 @@ public class SiteController { return ApiResponse.success(siteService.toDTO(siteService.findBySiteId(siteId))); } + /** + * 编辑站点基本信息(需 JWT 认证,管理员操作) + */ + @PutMapping("/{siteId}") + public ApiResponse updateSite( + @PathVariable("siteId") String siteId, + @RequestBody Map payload, + HttpServletRequest request) { + + String name = (String) payload.get("name"); + String description = (String) payload.get("description"); + String baseUrl = (String) payload.get("baseUrl"); + String siteType = (String) payload.get("siteType"); + Long operatorId = (Long) request.getAttribute("accountId"); + + try { + Site site = siteService.updateSite(siteId, name, description, baseUrl, siteType, operatorId); + return ApiResponse.success(siteService.toDTO(site)); + } catch (IllegalArgumentException e) { + return ApiResponse.error(404, e.getMessage()); + } + } + /** * 提交站点收录(需 HMAC 鉴权) * 创建或更新站点信息,自动设置为活跃状态 diff --git a/par-core/src/main/java/com/par/core/dto/SiteConfigDTO.java b/par-core/src/main/java/com/par/core/dto/SiteConfigDTO.java index 9aeb0b1..a6743d0 100644 --- a/par-core/src/main/java/com/par/core/dto/SiteConfigDTO.java +++ b/par-core/src/main/java/com/par/core/dto/SiteConfigDTO.java @@ -19,5 +19,6 @@ public class SiteConfigDTO { private String schemaVersion; private ConfigStatus status; private Boolean isLatest; + private Long submitterId; private LocalDateTime createdAt; } diff --git a/par-core/src/main/java/com/par/core/service/SiteService.java b/par-core/src/main/java/com/par/core/service/SiteService.java index 05d2e21..c8ec6af 100644 --- a/par-core/src/main/java/com/par/core/service/SiteService.java +++ b/par-core/src/main/java/com/par/core/service/SiteService.java @@ -37,6 +37,12 @@ public interface SiteService { Site updateSiteInfo(String siteId, String status, List names, List urls, String notes, Long submitterId); + /** + * 管理员编辑站点基本信息 + */ + Site updateSite(String siteId, String name, String description, String baseUrl, + String siteType, Long operatorId); + /** 待审核站点列表 */ List listPendingSites(); diff --git a/par-core/src/main/java/com/par/core/service/impl/ConfigServiceImpl.java b/par-core/src/main/java/com/par/core/service/impl/ConfigServiceImpl.java index 9a65d8e..8a9bceb 100644 --- a/par-core/src/main/java/com/par/core/service/impl/ConfigServiceImpl.java +++ b/par-core/src/main/java/com/par/core/service/impl/ConfigServiceImpl.java @@ -106,6 +106,7 @@ public class ConfigServiceImpl implements ConfigService { dto.setSchemaVersion(config.getSchemaVersion()); dto.setStatus(config.getStatus()); dto.setIsLatest(config.getIsLatest()); + dto.setSubmitterId(config.getSubmitterId()); dto.setCreatedAt(config.getCreatedAt()); return dto; } diff --git a/par-core/src/main/java/com/par/core/service/impl/SiteServiceImpl.java b/par-core/src/main/java/com/par/core/service/impl/SiteServiceImpl.java index 9f15610..503000d 100644 --- a/par-core/src/main/java/com/par/core/service/impl/SiteServiceImpl.java +++ b/par-core/src/main/java/com/par/core/service/impl/SiteServiceImpl.java @@ -152,6 +152,26 @@ public class SiteServiceImpl implements SiteService { return site; } + @Override + 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 (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) {} + } + siteMapper.updateById(site); + log.info("站点编辑: siteId={} name={}", siteId, name); + return site; + } + @Override public List listPendingSites() { return siteMapper.selectList( diff --git a/web/src/api/index.js b/web/src/api/index.js index 0e023c8..a09fb2d 100644 --- a/web/src/api/index.js +++ b/web/src/api/index.js @@ -41,6 +41,7 @@ export default { pending: () => api.get('/configs/pending'), bySite: (siteId) => api.get(`/configs/${siteId}`), download: (siteId, version) => api.get(`/configs/${siteId}/${version}`), + content: (siteId, version) => api.get(`/configs/${siteId}/${version}/content`), approve: (id, comment) => api.post(`/admin/reviews/${id}/approve`, null, { params: { comment } }), reject: (id, comment) => api.post(`/admin/reviews/${id}/reject`, null, { params: { comment } }) }, diff --git a/web/src/views/Configs.vue b/web/src/views/Configs.vue index 106326c..67aefee 100644 --- a/web/src/views/Configs.vue +++ b/web/src/views/Configs.vue @@ -81,19 +81,29 @@ const handleView = async (row) => { showHistory.value = false viewVisible.value = true try { - const res = await api.configs.download(row.siteId, row.version) - configContent.value = typeof res === 'string' ? res : JSON.stringify(res, null, 2) - } catch { - configContent.value = '(加载失败)' + const resp = await api.configs.content(row.siteId, row.version) + const content = resp.data + if (content) { + configContent.value = typeof content === 'string' ? content : JSON.stringify(content, null, 2) + } else { + configContent.value = '(无配置内容)' + } + } catch (e) { + configContent.value = '(加载失败: ' + (e.response?.data?.message || e.message || '未知错误') + ')' } } const loadHistoryConfig = async (h) => { try { - const res = await api.configs.download(h.siteId, h.version) - configContent.value = typeof res === 'string' ? res : JSON.stringify(res, null, 2) - } catch { - configContent.value = '(加载失败)' + const resp = await api.configs.content(h.siteId, h.version) + const content = resp.data + if (content) { + configContent.value = typeof content === 'string' ? content : JSON.stringify(content, null, 2) + } else { + configContent.value = '(无配置内容)' + } + } catch (e) { + configContent.value = '(加载失败: ' + (e.response?.data?.message || e.message || '未知错误') + ')' } } diff --git a/web/src/views/Sites.vue b/web/src/views/Sites.vue index 6954562..bb5de5e 100644 --- a/web/src/views/Sites.vue +++ b/web/src/views/Sites.vue @@ -22,6 +22,31 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -30,6 +55,9 @@ import api from '../api' const sites = ref([]) const loading = ref(false) +const editVisible = ref(false) +const saving = ref(false) +const editForm = ref({ id: null, siteId: '', name: '', baseUrl: '', siteType: '', description: '' }) const fetchSites = async () => { loading.value = true @@ -44,7 +72,38 @@ const fetchSites = async () => { } const handleEdit = (row) => { - ElMessage.info(`编辑站点: ${row.name}`) + editForm.value = { + id: row.id, + siteId: row.siteId, + name: row.name || '', + baseUrl: row.baseUrl || '', + siteType: row.siteType || '', + description: row.description || '', + } + editVisible.value = true +} + +const handleSave = async () => { + if (!editForm.value.name.trim() || !editForm.value.baseUrl.trim()) { + ElMessage.warning('名称和基础URL不能为空') + return + } + saving.value = true + try { + await api.sites.update(editForm.value.siteId, { + name: editForm.value.name.trim(), + baseUrl: editForm.value.baseUrl.trim(), + siteType: editForm.value.siteType.trim() || 'unknown', + description: editForm.value.description.trim() || null, + }) + ElMessage.success('保存成功') + editVisible.value = false + fetchSites() + } catch (e) { + ElMessage.error(e.response?.data?.message || '保存失败') + } finally { + saving.value = false + } } onMounted(fetchSites)