feat: mediabo接口实现(pages/scripts/risk-rules)+DB迁移脚本
This commit is contained in:
42
docs/migration_v2.sql
Normal file
42
docs/migration_v2.sql
Normal file
@@ -0,0 +1,42 @@
|
||||
-- PAR v2 新增表
|
||||
-- 站点页面配置(mediabot 页面入口)
|
||||
CREATE TABLE IF NOT EXISTS site_pages (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
site_id VARCHAR(64) NOT NULL,
|
||||
version VARCHAR(20) NOT NULL DEFAULT '1.0.0',
|
||||
entries_json LONGTEXT NOT NULL,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_site_id (site_id, deleted)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- 风险保号规则(按站点类型组织)
|
||||
CREATE TABLE IF NOT EXISTS risk_rules (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
site_type VARCHAR(32) NOT NULL,
|
||||
version INT NOT NULL DEFAULT 1,
|
||||
rules_json LONGTEXT NOT NULL,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_site_type (site_type, deleted)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- 示例数据:NexusPHP 页面入口
|
||||
INSERT INTO site_pages (site_id, version, entries_json) VALUES
|
||||
('default-nexusphp', '1.0.0', '[
|
||||
{"key":"home", "label":"首页", "url":"{base}", "method":"GET"},
|
||||
{"key":"search", "label":"搜索页", "url":"{base}/torrents.php", "method":"GET"},
|
||||
{"key":"detail", "label":"种子详情", "url":"{base}/details.php?id=", "method":"GET"},
|
||||
{"key":"signIn", "label":"签到页", "url":"{base}/attendance.php", "method":"POST"},
|
||||
{"key":"user", "label":"用户详情", "url":"{base}/userdetails.php?id=","method":"GET"}
|
||||
]');
|
||||
|
||||
-- 示例数据:NexusPHP 风险规则
|
||||
INSERT INTO risk_rules (site_type, version, rules_json) VALUES
|
||||
('nexusphp', 1, '[
|
||||
{"type":"KILL","name":"登录间隔超过30天","metric":"daysSinceLastLogin","threshold":30,"unit":"DAY","condition":"GT","level":"CRITICAL"},
|
||||
{"type":"RATIO","name":"分享率低于0.3","metric":"shareRatio","threshold":0.3,"condition":"LT","level":"CRITICAL","applicable":{"daysSinceRegistration":30}},
|
||||
{"type":"TRAFFIC","name":"超过5天无流量","metric":"daysSinceLastTraffic","threshold":5,"unit":"DAY","condition":"GT","level":"WARN","applicable":{"daysSinceRegistration":30}}
|
||||
]');
|
||||
@@ -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")
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
36
par-core/src/main/java/com/par/core/entity/RiskRule.java
Normal file
36
par-core/src/main/java/com/par/core/entity/RiskRule.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package com.par.core.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 风险规则实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("risk_rules")
|
||||
public class RiskRule {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@TableField("site_type")
|
||||
private String siteType;
|
||||
|
||||
@TableField("version")
|
||||
private Integer version;
|
||||
|
||||
@TableField("rules_json")
|
||||
private String rulesJson;
|
||||
|
||||
@TableLogic
|
||||
@TableField("deleted")
|
||||
private Integer deleted;
|
||||
|
||||
@TableField(value = "created_at", fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField(value = "updated_at", fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
36
par-core/src/main/java/com/par/core/entity/SitePages.java
Normal file
36
par-core/src/main/java/com/par/core/entity/SitePages.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package com.par.core.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 站点页面配置实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("site_pages")
|
||||
public class SitePages {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@TableField("site_id")
|
||||
private String siteId;
|
||||
|
||||
@TableField("version")
|
||||
private String version;
|
||||
|
||||
@TableField("entries_json")
|
||||
private String entriesJson;
|
||||
|
||||
@TableLogic
|
||||
@TableField("deleted")
|
||||
private Integer deleted;
|
||||
|
||||
@TableField(value = "created_at", fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField(value = "updated_at", fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.par.core.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.par.core.entity.RiskRule;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface RiskRuleMapper extends BaseMapper<RiskRule> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.par.core.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.par.core.entity.SitePages;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface SitePagesMapper extends BaseMapper<SitePages> {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.par.core.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface RiskRuleService {
|
||||
/** 获取指定站点类型的风险规则 */
|
||||
Map<String, Object> getRules(String siteType);
|
||||
/** 获取规则版本号 */
|
||||
int getVersion(String siteType);
|
||||
/** 保存规则 */
|
||||
void saveRules(String siteType, List<Map<String, Object>> rules);
|
||||
/** 获取所有规则列表(管理员) */
|
||||
List<Map<String, Object>> listAll();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.par.core.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface SitePagesService {
|
||||
/** 获取站点页面配置 */
|
||||
Map<String, Object> getPages(String siteId);
|
||||
/** 获取页面配置版本号 */
|
||||
String getVersion(String siteId);
|
||||
/** 保存/更新页面配置 */
|
||||
void savePages(String siteId, List<Map<String, Object>> entries);
|
||||
/** 获取所有站点页面配置列表(管理员) */
|
||||
List<Map<String, Object>> listAll();
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.par.core.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.par.core.entity.RiskRule;
|
||||
import com.par.core.mapper.RiskRuleMapper;
|
||||
import com.par.core.service.RiskRuleService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class RiskRuleServiceImpl implements RiskRuleService {
|
||||
|
||||
private final RiskRuleMapper mapper;
|
||||
private static final ObjectMapper om = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getRules(String siteType) {
|
||||
RiskRule rr = mapper.selectOne(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<RiskRule>()
|
||||
.eq(RiskRule::getSiteType, siteType).eq(RiskRule::getDeleted, 0)
|
||||
.orderByDesc(RiskRule::getId).last("limit 1"));
|
||||
if (rr == null) return null;
|
||||
try {
|
||||
List<Map<String, Object>> rules = om.readValue(rr.getRulesJson(),
|
||||
new TypeReference<List<Map<String, Object>>>() {});
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("version", rr.getVersion());
|
||||
result.put("updatedAt", rr.getUpdatedAt());
|
||||
result.put("rules", rules);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("解析风险规则失败 siteType={}", siteType, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion(String siteType) {
|
||||
RiskRule rr = mapper.selectOne(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<RiskRule>()
|
||||
.eq(RiskRule::getSiteType, siteType).eq(RiskRule::getDeleted, 0)
|
||||
.orderByDesc(RiskRule::getId).last("limit 1"));
|
||||
return rr != null ? rr.getVersion() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveRules(String siteType, List<Map<String, Object>> rules) {
|
||||
try {
|
||||
String json = om.writeValueAsString(rules);
|
||||
int nextVer = getVersion(siteType) + 1;
|
||||
RiskRule rr = new RiskRule();
|
||||
rr.setSiteType(siteType);
|
||||
rr.setVersion(nextVer);
|
||||
rr.setRulesJson(json);
|
||||
mapper.insert(rr);
|
||||
log.info("风险规则已保存: siteType={}, version={}", siteType, nextVer);
|
||||
} catch (Exception e) {
|
||||
log.error("保存风险规则失败 siteType={}", siteType, e);
|
||||
throw new RuntimeException("保存失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> listAll() {
|
||||
List<RiskRule> all = mapper.selectList(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<RiskRule>()
|
||||
.eq(RiskRule::getDeleted, 0));
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (RiskRule rr : all) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("id", rr.getId());
|
||||
m.put("siteType", rr.getSiteType());
|
||||
m.put("version", rr.getVersion());
|
||||
m.put("updatedAt", rr.getUpdatedAt());
|
||||
result.add(m);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.par.core.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.par.core.entity.SitePages;
|
||||
import com.par.core.mapper.SitePagesMapper;
|
||||
import com.par.core.service.SitePagesService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SitePagesServiceImpl implements SitePagesService {
|
||||
|
||||
private final SitePagesMapper mapper;
|
||||
private static final ObjectMapper om = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getPages(String siteId) {
|
||||
SitePages sp = mapper.selectOne(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SitePages>()
|
||||
.eq(SitePages::getSiteId, siteId).eq(SitePages::getDeleted, 0)
|
||||
.orderByDesc(SitePages::getId).last("limit 1"));
|
||||
if (sp == null) return null;
|
||||
try {
|
||||
List<Map<String, Object>> entries = om.readValue(sp.getEntriesJson(),
|
||||
new TypeReference<List<Map<String, Object>>>() {});
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("siteId", siteId);
|
||||
result.put("version", sp.getVersion());
|
||||
result.put("entries", entries);
|
||||
result.put("updatedAt", sp.getUpdatedAt());
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("解析页面配置失败 siteId={}", siteId, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion(String siteId) {
|
||||
SitePages sp = mapper.selectOne(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SitePages>()
|
||||
.eq(SitePages::getSiteId, siteId).eq(SitePages::getDeleted, 0)
|
||||
.orderByDesc(SitePages::getId).last("limit 1"));
|
||||
return sp != null ? sp.getVersion() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void savePages(String siteId, List<Map<String, Object>> entries) {
|
||||
try {
|
||||
String json = om.writeValueAsString(entries);
|
||||
// 版本号递增
|
||||
String currentVer = getVersion(siteId);
|
||||
int nextVer = 1;
|
||||
if (currentVer != null) {
|
||||
try { nextVer = Integer.parseInt(currentVer) + 1; } catch (NumberFormatException e) { /* keep 1 */ }
|
||||
}
|
||||
SitePages sp = new SitePages();
|
||||
sp.setSiteId(siteId);
|
||||
sp.setVersion(String.valueOf(nextVer));
|
||||
sp.setEntriesJson(json);
|
||||
mapper.insert(sp);
|
||||
log.info("页面配置已保存: siteId={}, version={}", siteId, nextVer);
|
||||
} catch (Exception e) {
|
||||
log.error("保存页面配置失败 siteId={}", siteId, e);
|
||||
throw new RuntimeException("保存页面配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> listAll() {
|
||||
List<SitePages> all = mapper.selectList(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SitePages>()
|
||||
.eq(SitePages::getDeleted, 0));
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (SitePages sp : all) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("id", sp.getId());
|
||||
m.put("siteId", sp.getSiteId());
|
||||
m.put("version", sp.getVersion());
|
||||
m.put("updatedAt", sp.getUpdatedAt());
|
||||
result.add(m);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user