From 657e0c1a7861dd0cc1626278772c1a3df1a00f98 Mon Sep 17 00:00:00 2001 From: mediabot-pt <295750538+mediabot-pt@users.noreply.github.com> Date: Tue, 30 Jun 2026 08:57:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20site=5Fsubmissions?= =?UTF-8?q?=20=E5=AE=A1=E6=A0=B8=E8=A1=A8,=20=E6=8F=90=E4=BA=A4=E5=85=88?= =?UTF-8?q?=E8=BF=9B=E5=AE=A1=E6=A0=B8=E5=86=8D=E5=86=99=E5=85=A5sites?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../par/api/controller/AdminController.java | 22 +-- .../par/api/controller/SiteController.java | 4 +- .../com/par/core/dto/SiteSubmissionDTO.java | 22 +++ .../com/par/core/entity/SiteSubmission.java | 41 +++++ .../par/core/mapper/SiteSubmissionMapper.java | 19 +++ .../com/par/core/service/SiteService.java | 17 +- .../core/service/impl/SiteServiceImpl.java | 152 +++++++++++++----- .../db/migration/V7__site_submissions.sql | 16 ++ web/src/api/index.js | 5 +- web/src/views/Sites.vue | 85 +++++++--- 10 files changed, 292 insertions(+), 91 deletions(-) create mode 100644 par-core/src/main/java/com/par/core/dto/SiteSubmissionDTO.java create mode 100644 par-core/src/main/java/com/par/core/entity/SiteSubmission.java create mode 100644 par-core/src/main/java/com/par/core/mapper/SiteSubmissionMapper.java create mode 100644 par-core/src/main/resources/db/migration/V7__site_submissions.sql diff --git a/par-api/src/main/java/com/par/api/controller/AdminController.java b/par-api/src/main/java/com/par/api/controller/AdminController.java index 3b6bbab..2cf5a49 100644 --- a/par-api/src/main/java/com/par/api/controller/AdminController.java +++ b/par-api/src/main/java/com/par/api/controller/AdminController.java @@ -266,26 +266,26 @@ public class AdminController { } @GetMapping("/sites/pending") - public ApiResponse> listPendingSites(HttpServletRequest request) { + public ApiResponse> listPendingSubmissions(HttpServletRequest request) { checkAdmin(request); - return ApiResponse.success(siteService.listPendingSites().stream() - .map(siteService::toDTO).toList()); + return ApiResponse.success(siteService.listPendingSubmissions()); } - @PostMapping("/sites/{id}/approve") - public ApiResponse approveSite(@PathVariable Long id, HttpServletRequest request) { + @PostMapping("/submissions/{id}/approve") + public ApiResponse approveSubmission(@PathVariable Long id, HttpServletRequest request) { checkAdmin(request); Long reviewerId = (Long) request.getAttribute("accountId"); - siteService.approveSite(id, reviewerId); - log.info("Site approved: id={}, reviewer={}", id, reviewerId); + siteService.approveSubmission(id, reviewerId); + log.info("Submission approved: id={}, reviewer={}", id, reviewerId); return ApiResponse.success(); } - @PostMapping("/sites/{id}/reject") - public ApiResponse rejectSite(@PathVariable Long id, HttpServletRequest request) { + @PostMapping("/submissions/{id}/reject") + public ApiResponse rejectSubmission(@PathVariable Long id, HttpServletRequest request) { checkAdmin(request); - siteService.rejectSite(id); - log.info("Site rejected: id={}", id); + Long reviewerId = (Long) request.getAttribute("accountId"); + siteService.rejectSubmission(id, reviewerId); + log.info("Submission rejected: id={}", id); return ApiResponse.success(); } 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 a7aae4c..e2cdf2c 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 @@ -115,7 +115,7 @@ public class SiteController { String notes = (String) payload.get("notes"); Long submitterId = (Long) request.getAttribute("accountId"); - Site site = siteService.updateSiteInfo(siteId, status, names, urls, notes, submitterId); - return ApiResponse.success(siteService.toDTO(site)); + siteService.submitSiteInfo(siteId, status, names, urls, notes, submitterId); + return ApiResponse.success(Map.of("message", "站点信息已提交审核")); } } diff --git a/par-core/src/main/java/com/par/core/dto/SiteSubmissionDTO.java b/par-core/src/main/java/com/par/core/dto/SiteSubmissionDTO.java new file mode 100644 index 0000000..b5d989c --- /dev/null +++ b/par-core/src/main/java/com/par/core/dto/SiteSubmissionDTO.java @@ -0,0 +1,22 @@ +package com.par.core.dto; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +public class SiteSubmissionDTO { + private Long id; + private String siteId; + private Long submitterId; + private List names; + private List urls; + private String notes; + private String siteStatus; // 正常/关停/维护中/已迁移 + private String status; // pending/approved/rejected + private Long reviewerId; + private String reviewComment; + private LocalDateTime reviewedAt; + private LocalDateTime createdAt; +} diff --git a/par-core/src/main/java/com/par/core/entity/SiteSubmission.java b/par-core/src/main/java/com/par/core/entity/SiteSubmission.java new file mode 100644 index 0000000..9f6a15d --- /dev/null +++ b/par-core/src/main/java/com/par/core/entity/SiteSubmission.java @@ -0,0 +1,41 @@ +package com.par.core.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("site_submissions") +public class SiteSubmission { + + @TableId(type = IdType.AUTO) + private Long id; + + @TableField("site_id") + private String siteId; + + @TableField("submitter_id") + private Long submitterId; + + @TableField("raw_data") + private String rawData; + + @TableField("status") + private String status; // pending / approved / rejected + + @TableField("reviewer_id") + private Long reviewerId; + + @TableField("review_comment") + private String reviewComment; + + @TableField("reviewed_at") + private LocalDateTime reviewedAt; + + @TableField("site_record_id") + private Long siteRecordId; + + @TableField(value = "created_at", fill = FieldFill.INSERT) + private LocalDateTime createdAt; +} diff --git a/par-core/src/main/java/com/par/core/mapper/SiteSubmissionMapper.java b/par-core/src/main/java/com/par/core/mapper/SiteSubmissionMapper.java new file mode 100644 index 0000000..1eb8d93 --- /dev/null +++ b/par-core/src/main/java/com/par/core/mapper/SiteSubmissionMapper.java @@ -0,0 +1,19 @@ +package com.par.core.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.par.core.entity.SiteSubmission; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +@Mapper +public interface SiteSubmissionMapper extends BaseMapper { + + @Select("SELECT * FROM site_submissions WHERE status = #{status} ORDER BY created_at DESC") + List selectByStatus(@Param("status") String status); + + @Select("SELECT * FROM site_submissions WHERE site_id = #{siteId} ORDER BY created_at DESC") + List selectBySiteId(@Param("siteId") String siteId); +} 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 b4e0176..cd3eb59 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 @@ -1,6 +1,7 @@ package com.par.core.service; import com.par.core.dto.SiteDTO; +import com.par.core.dto.SiteSubmissionDTO; import com.par.core.entity.Site; import java.util.List; @@ -32,9 +33,9 @@ public interface SiteService { String iconUrl, String siteType, Long submitterId); /** - * 提交站点信息反馈(状态、多名称、多地址、备注) + * 提交站点信息反馈 → 写入 site_submissions 审核表 */ - Site updateSiteInfo(String siteId, String status, List names, + void submitSiteInfo(String siteId, String status, List names, List urls, String notes, Long submitterId); /** @@ -46,12 +47,12 @@ public interface SiteService { /** 所有站点列表(管理员用) */ List listAllSites(); - /** 待审核站点列表 */ - List listPendingSites(); + /** 待审核提交列表 */ + List listPendingSubmissions(); - /** 审核通过 */ - void approveSite(Long id, Long reviewerId); + /** 审核通过 → 写入 sites 表 */ + Site approveSubmission(Long submissionId, Long reviewerId); - /** 拒绝(删除) */ - void rejectSite(Long id); + /** 拒绝提交 */ + void rejectSubmission(Long submissionId, Long reviewerId); } 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 68ab140..51c686a 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 @@ -4,17 +4,22 @@ 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.dto.SiteSubmissionDTO; import com.par.core.entity.Site; +import com.par.core.entity.SiteSubmission; import com.par.core.enums.SiteStatus; import com.par.core.enums.SiteType; import com.par.core.mapper.SiteMapper; +import com.par.core.mapper.SiteSubmissionMapper; import com.par.core.service.SiteService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @Slf4j @@ -23,6 +28,7 @@ import java.util.stream.Collectors; public class SiteServiceImpl implements SiteService { private final SiteMapper siteMapper; + private final SiteSubmissionMapper submissionMapper; private static final ObjectMapper objectMapper = new ObjectMapper(); // ==================== 工具方法 ==================== @@ -118,34 +124,25 @@ public class SiteServiceImpl implements SiteService { } @Override - public Site updateSiteInfo(String siteId, String status, List names, + public void submitSiteInfo(String siteId, String status, List names, List urls, String notes, Long submitterId) { - SiteStatus siteStatus = mapStatus(status); + Map raw = new LinkedHashMap<>(); + raw.put("names", names != null ? names : List.of()); + raw.put("urls", urls != null ? urls : List.of()); + raw.put("notes", notes != null ? notes : ""); + raw.put("siteStatus", status != null ? status : "正常"); - Site existing = siteMapper.selectBySiteId(siteId); - if (existing != null) { - 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); - log.info("站点信息反馈更新: siteId={} status={}", siteId, siteStatus.getValue()); - return existing; + SiteSubmission sub = new SiteSubmission(); + sub.setSiteId(siteId); + sub.setSubmitterId(submitterId); + sub.setStatus("pending"); + try { + sub.setRawData(objectMapper.writeValueAsString(raw)); + } catch (JsonProcessingException e) { + throw new RuntimeException("序列化提交数据失败", e); } - - Site site = new Site(); - site.setSiteId(siteId); - 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); - site.setSubmitterId(submitterId); - siteMapper.insert(site); - log.info("站点信息反馈创建: siteId={} status=PENDING", siteId); - return site; + submissionMapper.insert(sub); + log.info("站点信息提交审核: siteId={} submissionId={}", siteId, sub.getId()); } @Override @@ -184,32 +181,101 @@ public class SiteServiceImpl implements SiteService { } @Override - public List listPendingSites() { - return siteMapper.selectList( - new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() - .eq(Site::getStatus, SiteStatus.PENDING) - .eq(Site::getDeleted, 0)); + public List listPendingSubmissions() { + return submissionMapper.selectByStatus("pending").stream() + .map(this::toSubmissionDTO).collect(Collectors.toList()); } @Override - public void approveSite(Long id, Long reviewerId) { - Site site = siteMapper.selectById(id); - if (site != null) { - site.setStatus(SiteStatus.ACTIVE); - siteMapper.updateById(site); + public Site approveSubmission(Long submissionId, Long reviewerId) { + SiteSubmission sub = submissionMapper.selectById(submissionId); + if (sub == null) throw new IllegalArgumentException("提交记录不存在"); + if (!"pending".equals(sub.getStatus())) throw new IllegalArgumentException("该提交已处理"); + + // 解析提交数据 + Map raw; + try { + raw = objectMapper.readValue(sub.getRawData(), new TypeReference>() {}); + } catch (Exception e) { + throw new RuntimeException("解析提交数据失败", e); } + + @SuppressWarnings("unchecked") + List names = (List) raw.getOrDefault("names", List.of()); + @SuppressWarnings("unchecked") + List urls = (List) raw.getOrDefault("urls", List.of()); + String notes = (String) raw.getOrDefault("notes", ""); + + // 写入 sites 表 + Site existing = siteMapper.selectBySiteId(sub.getSiteId()); + Site site; + if (existing != null) { + existing.setNamesJson(toJsonArray(names)); + existing.setUrlsJson(toJsonArray(urls)); + if (notes != null && !notes.isBlank()) existing.setNotes(notes); + existing.setStatus(SiteStatus.ACTIVE); + siteMapper.updateById(existing); + site = existing; + } else { + site = new Site(); + site.setSiteId(sub.getSiteId()); + site.setNamesJson(toJsonArray(names)); + site.setUrlsJson(toJsonArray(urls)); + site.setNotes(notes); + site.setSiteType(SiteType.UNKNOWN); + site.setSchemaVersion("1.0.0"); + site.setStatus(SiteStatus.ACTIVE); + site.setSubmitterId(sub.getSubmitterId()); + siteMapper.insert(site); + } + + // 更新提交状态 + sub.setStatus("approved"); + sub.setReviewerId(reviewerId); + sub.setReviewedAt(java.time.LocalDateTime.now()); + sub.setSiteRecordId(site.getId()); + submissionMapper.updateById(sub); + log.info("站点审核通过: siteId={} submissionId={}", sub.getSiteId(), submissionId); + return site; } @Override - public void rejectSite(Long id) { - siteMapper.deleteById(id); + public void rejectSubmission(Long submissionId, Long reviewerId) { + SiteSubmission sub = submissionMapper.selectById(submissionId); + if (sub == null) throw new IllegalArgumentException("提交记录不存在"); + if (!"pending".equals(sub.getStatus())) throw new IllegalArgumentException("该提交已处理"); + sub.setStatus("rejected"); + sub.setReviewerId(reviewerId); + sub.setReviewedAt(java.time.LocalDateTime.now()); + submissionMapper.updateById(sub); + log.info("站点审核拒绝: siteId={} submissionId={}", sub.getSiteId(), submissionId); } - private static SiteStatus mapStatus(String status) { - if (status == null) return SiteStatus.ACTIVE; - return switch (status) { - case "关停", "已迁移" -> SiteStatus.DEPRECATED; - default -> SiteStatus.ACTIVE; - }; + private SiteSubmissionDTO toSubmissionDTO(SiteSubmission sub) { + SiteSubmissionDTO dto = new SiteSubmissionDTO(); + dto.setId(sub.getId()); + dto.setSiteId(sub.getSiteId()); + dto.setSubmitterId(sub.getSubmitterId()); + dto.setStatus(sub.getStatus()); + dto.setReviewerId(sub.getReviewerId()); + dto.setReviewComment(sub.getReviewComment()); + dto.setReviewedAt(sub.getReviewedAt()); + dto.setCreatedAt(sub.getCreatedAt()); + + try { + Map raw = objectMapper.readValue(sub.getRawData(), new TypeReference>() {}); + @SuppressWarnings("unchecked") + List names = (List) raw.getOrDefault("names", List.of()); + @SuppressWarnings("unchecked") + List urls = (List) raw.getOrDefault("urls", List.of()); + dto.setNames(names); + dto.setUrls(urls); + dto.setNotes((String) raw.getOrDefault("notes", "")); + dto.setSiteStatus((String) raw.getOrDefault("siteStatus", "正常")); + } catch (Exception ignored) { + dto.setNames(List.of()); + dto.setUrls(List.of()); + } + return dto; } } diff --git a/par-core/src/main/resources/db/migration/V7__site_submissions.sql b/par-core/src/main/resources/db/migration/V7__site_submissions.sql new file mode 100644 index 0000000..e9bc0a1 --- /dev/null +++ b/par-core/src/main/resources/db/migration/V7__site_submissions.sql @@ -0,0 +1,16 @@ +CREATE TABLE site_submissions ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + site_id VARCHAR(64) NOT NULL COMMENT '站点标识', + submitter_id BIGINT UNSIGNED COMMENT '提交者账户ID', + raw_data JSON NOT NULL COMMENT '原始提交数据 {names,urls,notes,site_status}', + status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '审核状态: pending/approved/rejected', + reviewer_id BIGINT UNSIGNED COMMENT '审核人ID', + review_comment VARCHAR(500) COMMENT '审核备注', + reviewed_at DATETIME COMMENT '审核时间', + site_record_id BIGINT UNSIGNED COMMENT '审核通过后关联的 sites.id', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + KEY idx_site_id (site_id), + KEY idx_status (status), + KEY idx_submitter (submitter_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='站点提交审核表'; diff --git a/web/src/api/index.js b/web/src/api/index.js index 42380b1..e7d2a17 100644 --- a/web/src/api/index.js +++ b/web/src/api/index.js @@ -36,8 +36,9 @@ export default { get: (id) => api.get(`/sites/${id}`), create: (data) => api.post('/sites', data), update: (id, data) => api.put(`/sites/${id}`, data), - approve: (id) => api.post(`/admin/sites/${id}/approve`), - reject: (id) => api.post(`/admin/sites/${id}/reject`), + pendingSubmissions: () => api.get('/admin/sites/pending'), + approveSubmission: (id) => api.post(`/admin/submissions/${id}/approve`), + rejectSubmission: (id) => api.post(`/admin/submissions/${id}/reject`), }, configs: { list: () => api.get('/configs'), diff --git a/web/src/views/Sites.vue b/web/src/views/Sites.vue index 9b85ec9..bdaf5e3 100644 --- a/web/src/views/Sites.vue +++ b/web/src/views/Sites.vue @@ -1,8 +1,48 @@