feat: mediabo接口实现(pages/scripts/risk-rules)+DB迁移脚本

This commit is contained in:
mediabot-pt
2026-07-04 23:35:45 +08:00
parent c0fc17cf05
commit 2ced6f5dcb
13 changed files with 572 additions and 0 deletions

View File

@@ -32,6 +32,7 @@ public class ManifestController {
private final SiteMapper siteMapper;
private final SiteConfigMapper siteConfigMapper;
private final com.par.core.mapper.SitePagesMapper sitePagesMapper;
private final SiteService siteService;
/**
@@ -66,6 +67,11 @@ public class ManifestController {
entry.put("hasScript", latest.getScriptStoragePath() != null && !latest.getScriptStoragePath().isBlank());
entry.put("updatedAt", latest.getCreatedAt());
}
// 是否配置了页面入口
entry.put("hasPages", sitePagesMapper.selectCount(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.par.core.entity.SitePages>()
.eq(com.par.core.entity.SitePages::getSiteId, site.getSiteId())
.eq(com.par.core.entity.SitePages::getDeleted, 0)) > 0);
// 参与 ETag 计算
etagBuilder.append(site.getSiteId())
.append(latest != null ? latest.getScriptStoragePath() : "none")

View File

@@ -0,0 +1,74 @@
package com.par.api.controller;
import com.par.core.dto.ApiResponse;
import com.par.core.service.RiskRuleService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 风险规则接口
*/
@Slf4j
@RestController
@RequiredArgsConstructor
public class RiskRuleController {
private final RiskRuleService riskRuleService;
/** 获取风险规则 */
@GetMapping("/api/v1/risk-rules/{siteType}")
public ResponseEntity<?> getRules(
@PathVariable String siteType,
@RequestParam(defaultValue = "0") int version) {
int currentVer = riskRuleService.getVersion(siteType);
if (currentVer <= version) {
return ResponseEntity.status(304).build();
}
Map<String, Object> data = riskRuleService.getRules(siteType);
if (data == null) return ResponseEntity.status(304).build();
return ResponseEntity.ok(data);
}
/** 提交风险规则 */
@PostMapping("/api/v1/risk-rules/{siteType}")
public ApiResponse<Void> saveRules(
@PathVariable String siteType,
@RequestBody Map<String, Object> payload) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> rules = (List<Map<String, Object>>) payload.get("rules");
if (rules == null || rules.isEmpty()) {
return ApiResponse.error(400, "rules is required");
}
riskRuleService.saveRules(siteType, rules);
return ApiResponse.success();
}
/** 管理员查看所有风险规则列表 */
@GetMapping("/api/v1/admin/risk-rules")
public ApiResponse<List<Map<String, Object>>> adminList(HttpServletRequest request) {
checkAdmin(request);
return ApiResponse.success(riskRuleService.listAll());
}
/** 管理员查看某个类型风险规则内容 */
@GetMapping("/api/v1/admin/risk-rules/{siteType}")
public ApiResponse<Map<String, Object>> adminGet(@PathVariable String siteType, HttpServletRequest request) {
checkAdmin(request);
Map<String, Object> data = riskRuleService.getRules(siteType);
if (data == null) return ApiResponse.error(404, "风险规则不存在");
return ApiResponse.success(data);
}
private void checkAdmin(HttpServletRequest request) {
var level = request.getAttribute("trustLevel");
if (level == null || !"ADMIN".equals(level.toString())) {
throw new IllegalArgumentException("Admin permission required");
}
}
}

View File

@@ -0,0 +1,82 @@
package com.par.api.controller;
import com.par.core.adapter.GroovyScriptEngine;
import com.par.core.dto.ApiResponse;
import com.par.core.entity.SiteConfig;
import com.par.core.entity.Site;
import com.par.core.mapper.SiteMapper;
import com.par.core.service.ConfigService;
import com.par.core.storage.CachedStorageService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* Groovy 脚本接口(mediabot 使用)
*/
@Slf4j
@RestController
@RequiredArgsConstructor
public class ScriptController {
private final ConfigService configService;
private final CachedStorageService storageService;
private final SiteMapper siteMapper;
/** 下载最新 Groovy 脚本 */
@GetMapping("/api/v1/scripts/{siteId}")
public ResponseEntity<String> downloadScript(@PathVariable String siteId) {
SiteConfig latest = configService.getLatestConfig(siteId);
// 优先自定义脚本
if (latest != null && latest.getScriptStoragePath() != null
&& !latest.getScriptStoragePath().isBlank()) {
try {
String script = storageService.read(latest.getScriptStoragePath());
if (script != null && !script.isBlank()) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_TYPE, "text/plain;charset=UTF-8")
.body(script);
}
} catch (Exception ignored) {}
}
// 回退到默认模板
var site = siteMapper.selectBySiteId(siteId);
String siteType = site != null && site.getSiteType() != null
? site.getSiteType().getValue() : null;
String defaultScript = GroovyScriptEngine.loadDefaultByType(siteType);
if (defaultScript != null) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_TYPE, "text/plain;charset=UTF-8")
.body(defaultScript);
}
return ResponseEntity.notFound().build();
}
/** 获取脚本版本 */
@GetMapping("/api/v1/scripts/{siteId}/version")
public ApiResponse<Map<String, String>> getVersion(@PathVariable String siteId) {
SiteConfig latest = configService.getLatestConfig(siteId);
String v = latest != null ? latest.getVersion() : "0";
return ApiResponse.success(Map.of("version", v));
}
/** 上传 Groovy 脚本(PSK 签名认证) */
@PutMapping("/api/v1/scripts/{siteId}")
public ApiResponse<?> uploadScript(
@PathVariable String siteId,
@RequestBody String script,
HttpServletRequest request) {
if (script == null || script.isBlank()) {
return ApiResponse.error(400, "script body is required");
}
// 版本号由服务端管理
Long submitterId = (Long) request.getAttribute("accountId");
SiteConfig config = configService.submitConfig(siteId, "1", script, submitterId);
return ApiResponse.success(Map.of("version", config.getVersion()));
}
}

View File

@@ -0,0 +1,76 @@
package com.par.api.controller;
import com.par.core.dto.ApiResponse;
import com.par.core.service.SitePagesService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 站点页面配置接口
*/
@Slf4j
@RestController
@RequiredArgsConstructor
public class SitePagesController {
private final SitePagesService sitePagesService;
/** 获取站点页面配置 */
@GetMapping("/api/v1/sites/{siteId}/pages")
public ApiResponse<Map<String, Object>> getPages(@PathVariable String siteId) {
Map<String, Object> data = sitePagesService.getPages(siteId);
if (data == null) return ApiResponse.error(404, "页面配置不存在");
return ApiResponse.success(data);
}
/** 获取页面配置版本 */
@GetMapping("/api/v1/sites/{siteId}/pages/version")
public ApiResponse<Map<String, String>> getVersion(@PathVariable String siteId) {
String v = sitePagesService.getVersion(siteId);
return ApiResponse.success(Map.of("version", v != null ? v : "0"));
}
/** 上传/更新页面配置(PSK 签名认证) */
@PutMapping("/api/v1/sites/{siteId}/pages")
public ApiResponse<Void> savePages(
@PathVariable String siteId,
@RequestBody Map<String, Object> payload,
HttpServletRequest request) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> entries = (List<Map<String, Object>>) payload.get("entries");
if (entries == null || entries.isEmpty()) {
return ApiResponse.error(400, "entries is required");
}
sitePagesService.savePages(siteId, entries);
return ApiResponse.success();
}
/** 管理员查看所有站点页面配置列表 */
@GetMapping("/api/v1/admin/site-pages")
public ApiResponse<List<Map<String, Object>>> adminList(HttpServletRequest request) {
checkAdmin(request);
return ApiResponse.success(sitePagesService.listAll());
}
/** 管理员查看某站点页面配置内容 */
@GetMapping("/api/v1/admin/site-pages/{siteId}")
public ApiResponse<Map<String, Object>> adminGet(@PathVariable String siteId, HttpServletRequest request) {
checkAdmin(request);
Map<String, Object> data = sitePagesService.getPages(siteId);
if (data == null) return ApiResponse.error(404, "页面配置不存在");
return ApiResponse.success(data);
}
private void checkAdmin(HttpServletRequest request) {
var level = request.getAttribute("trustLevel");
if (level == null || !"ADMIN".equals(level.toString())) {
throw new IllegalArgumentException("Admin permission required");
}
}
}