diff --git a/src/main/java/me/qwq/doghouse/controller/admin/ConfigController.java b/src/main/java/me/qwq/doghouse/controller/admin/ConfigController.java index 81e63ec..f084abc 100644 --- a/src/main/java/me/qwq/doghouse/controller/admin/ConfigController.java +++ b/src/main/java/me/qwq/doghouse/controller/admin/ConfigController.java @@ -1,7 +1,9 @@ package me.qwq.doghouse.controller.admin; -import com.alibaba.fastjson2.JSONObject; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.validation.UnexpectedTypeException; import lombok.extern.slf4j.Slf4j; import me.qwq.doghouse.annotation.ConfigInfo; import me.qwq.doghouse.controller.common.BaseController; @@ -10,10 +12,20 @@ import me.qwq.doghouse.interfaces.IConfig; import me.qwq.doghouse.pojo.dto.R; import me.qwq.doghouse.service.ConfigService; -import org.reflections.Reflections; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.MethodParameter; import org.springframework.stereotype.Controller; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.validation.Validator; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.*; +import org.springframework.validation.BindingResult; /** @@ -26,10 +38,10 @@ import org.springframework.web.bind.annotation.*; public class ConfigController extends BaseController { @Autowired - ConfigService configService; + Validator validator; @Autowired - Reflections reflections; + ConfigService configService; /** * 前端设置页面接口,其设置项必须实现 ConfigInterface 接口,且 resources/templates/admin/config 下有对应的 html 模板,例: @@ -57,20 +69,52 @@ public class ConfigController extends BaseController { /** * 保存配置项统一接口 + *

+ * 注意该接口和系统初始化配置接口不一样,虽然从 json 中恢复配置, + * 但只会替换 json 中指明的字段的值, 其他值不会进行置换 + *

+ * @param configField 注解 @ConfigInfo 的 field + * @param config 配置 json */ @PostMapping({"/{configField}", "/{configField}/", "/{configField}/index"}) @ResponseBody - public > R saveConfig(@PathVariable String configField, @RequestBody JSONObject config) { + public > R saveConfig(@PathVariable String configField, @RequestBody JsonNode config) throws Exception { Class clazz = configService.getConfigClassByField(configField); if (clazz == null) { throw new PageNotFoundException(); } - return R.judge( - configService.saveOrUpdate((T)config.to(clazz)), - "保存失败"); + return R.judgeThrow(() -> { + // 找出其中有的字段 + Set jsonFields = new HashSet<>(); + config.fieldNames().forEachRemaining(jsonFields::add); + + T newConfig = (T)new ObjectMapper().treeToValue(config, clazz); + T oldConfig = configService.getConfig(clazz); + // 把除了本次更新以外的字段复制过去 + BeanUtils.copyProperties(oldConfig, newConfig, ArrayUtils.toStringArray(jsonFields.toArray())); + + Method method = this.getClass().getMethod("saveConfig", String.class, JsonNode.class); + MethodParameter methodParameter = new MethodParameter(method, 1); + + if (validator.supports(clazz)) { + try { + BindingResult bindingResult = new BeanPropertyBindingResult(newConfig, clazz.getSimpleName()); + validator.validate(newConfig, bindingResult); + + if (bindingResult.hasErrors()) { + throw new MethodArgumentNotValidException(methodParameter, bindingResult); + } + } + catch (UnexpectedTypeException e) { + // 对指定类型未找到 validator,忽略之 + log.debug("Cannot find a validator for {}", clazz.getName()); + } + } + + return newConfig.saveOrUpdate(); + }); } - } diff --git a/src/main/java/me/qwq/doghouse/entity/config/MailConfig.java b/src/main/java/me/qwq/doghouse/entity/config/MailConfig.java index d130c9d..78f5b2a 100644 --- a/src/main/java/me/qwq/doghouse/entity/config/MailConfig.java +++ b/src/main/java/me/qwq/doghouse/entity/config/MailConfig.java @@ -74,8 +74,9 @@ public class MailConfig implements Serializable, IConfig { } @Override - public void afterSaving() { + public MailConfig afterSaving() { SpringContextHolder.getBean(MailService.class).resetTaskDelay(); + return this; } } diff --git a/src/main/java/me/qwq/doghouse/entity/config/MarkdownConfig.java b/src/main/java/me/qwq/doghouse/entity/config/MarkdownConfig.java index cd0a991..33d71fe 100644 --- a/src/main/java/me/qwq/doghouse/entity/config/MarkdownConfig.java +++ b/src/main/java/me/qwq/doghouse/entity/config/MarkdownConfig.java @@ -24,7 +24,7 @@ public class MarkdownConfig implements Serializable, IConfig { private String mermaidConfig = DEFAULT_MERMAID_CONFIG; - public void afterSaving() { + public MarkdownConfig afterSaving() { if (StringUtils.isNotBlank(mermaidConfig)) { try { JSONObject jo = JSONObject.parse(mermaidConfig); @@ -33,6 +33,7 @@ public class MarkdownConfig implements Serializable, IConfig { log.warn("Save mermaid self-defined css failed", e); } } + return this; } } diff --git a/src/main/java/me/qwq/doghouse/entity/config/NetworkConfig.java b/src/main/java/me/qwq/doghouse/entity/config/NetworkConfig.java index c3fe925..8f97af6 100644 --- a/src/main/java/me/qwq/doghouse/entity/config/NetworkConfig.java +++ b/src/main/java/me/qwq/doghouse/entity/config/NetworkConfig.java @@ -121,8 +121,9 @@ public class NetworkConfig implements Serializable, IConfig { return trustedCidrBlockList; } - public void afterSaving() { + public NetworkConfig afterSaving() { trustedIpList = null; trustedCidrBlockList = null; + return this; } } diff --git a/src/main/java/me/qwq/doghouse/entity/config/PageConfig.java b/src/main/java/me/qwq/doghouse/entity/config/PageConfig.java index 77b16aa..ad611ce 100644 --- a/src/main/java/me/qwq/doghouse/entity/config/PageConfig.java +++ b/src/main/java/me/qwq/doghouse/entity/config/PageConfig.java @@ -65,11 +65,12 @@ public class PageConfig implements Serializable, IConfig, IPostTypeC private Boolean imageCompress = false; @Override - public void afterSaving() { + public PageConfig afterSaving() { // 缓存驱逐:GET_JUMP_TO_URL // 因为具体是哪一页是和每页评论数有关的,每页评论数又在 SiteConfig 里定义 // 一旦更改,那么评论对应页的缓存得驱逐 ((CacheManager)SpringContextHolder.getBean("cacheManager")).getCache(CacheConstants.Comments.GET_JUMP_TO_URL).clear(); + return this; } public PostTypeEnum getPostType() { diff --git a/src/main/java/me/qwq/doghouse/entity/config/SiteConfig.java b/src/main/java/me/qwq/doghouse/entity/config/SiteConfig.java index c28da58..41d26d7 100644 --- a/src/main/java/me/qwq/doghouse/entity/config/SiteConfig.java +++ b/src/main/java/me/qwq/doghouse/entity/config/SiteConfig.java @@ -103,11 +103,12 @@ public class SiteConfig implements Serializable, IConfig, IPostTypeC private Boolean imageCompress = false; @Override - public void afterSaving() { + public SiteConfig afterSaving() { // 缓存驱逐:GET_JUMP_TO_URL // 因为具体是哪一页是和每页评论数有关的,每页评论数又在 SiteConfig 里定义 // 一旦更改,那么评论对应页的缓存得驱逐 ((CacheManager)SpringContextHolder.getBean("cacheManager")).getCache(CacheConstants.Comments.GET_JUMP_TO_URL).clear(); + return this; } public PostTypeEnum getPostType() { diff --git a/src/main/java/me/qwq/doghouse/flexmark/ext/mermaid/MermaidNodeRenderer.java b/src/main/java/me/qwq/doghouse/flexmark/ext/mermaid/MermaidNodeRenderer.java index a8016ca..952ead1 100644 --- a/src/main/java/me/qwq/doghouse/flexmark/ext/mermaid/MermaidNodeRenderer.java +++ b/src/main/java/me/qwq/doghouse/flexmark/ext/mermaid/MermaidNodeRenderer.java @@ -153,7 +153,7 @@ public class MermaidNodeRenderer implements NodeRenderer { if (theme.isInternal()) { // 内部文件要释放到临时目录使用 - Resource configResource = RESOURCE_LOADER.getResource(theme.getTemplateString("conf/mermaid/config.json")); + Resource configResource = RESOURCE_LOADER.getResource("classpath:templates/" + theme.getTemplateString("conf/mermaid/config.json")); if (configResource.exists()) { configPath = tmpPath.resolve("config.json"); try (InputStream in = configResource.getInputStream()) { @@ -169,10 +169,12 @@ public class MermaidNodeRenderer implements NodeRenderer { configPath = Paths.get(themeService.getCurrentTheme().getDirectory(), "conf/mermaid/config.json"); } - File configFile = configPath.toFile(); - if (configFile.exists()) { - cmd.add("-c"); - cmd.add(configPath.toString()); + if (configPath != null) { + File configFile = configPath.toFile(); + if (configFile.exists()) { + cmd.add("-c"); + cmd.add(configPath.toString()); + } } try { diff --git a/src/main/java/me/qwq/doghouse/interfaces/IConfig.java b/src/main/java/me/qwq/doghouse/interfaces/IConfig.java index fb210ce..f4edcda 100644 --- a/src/main/java/me/qwq/doghouse/interfaces/IConfig.java +++ b/src/main/java/me/qwq/doghouse/interfaces/IConfig.java @@ -18,8 +18,13 @@ public interface IConfig> { return SpringContextHolder.getBean(ConfigService.class).saveOrUpdate((T)this); } - public default void afterSaving() {} - - public default void beforeSaving() {} + @SuppressWarnings("unchecked") + public default T afterSaving() { + return (T) this; + } + @SuppressWarnings("unchecked") + public default T beforeSaving() { + return (T) this; + } } diff --git a/src/main/java/me/qwq/doghouse/service/post/AbstractPostService.java b/src/main/java/me/qwq/doghouse/service/post/AbstractPostService.java index dab248a..6fa11a3 100644 --- a/src/main/java/me/qwq/doghouse/service/post/AbstractPostService.java +++ b/src/main/java/me/qwq/doghouse/service/post/AbstractPostService.java @@ -510,6 +510,9 @@ public abstract class AbstractPostService, C @UpdatePosts(postViews = true, commentsCount = true) public PageQuery conditionPage(PageQuery pageQueryCondition) { PageQuery result = proxy().cacheableConditionPage(pageQueryCondition, isLogin()); + // result 可能是缓存的,title 又可能是和 siteConfig... 有关的 + // 获取到结果以后要把与查询无关的内容更新上去 + result.setTitle(pageQueryCondition.getTitle()); return result; }