对于指提供个别字段的 config 的更新;对于前端 pageQuery title 因为缓存缘故,当 siteConfig...

等与标题有关的配置修改后,查询结果不变更的问题;mermaid 本地渲染配置文件路径 bug
This commit is contained in:
2025-12-30 22:12:57 +08:00
parent 73a006943c
commit a731d0c996
9 changed files with 81 additions and 22 deletions

View File

@@ -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 {
/**
* 保存配置项统一接口
* <p>
* 注意该接口和系统初始化配置接口不一样,虽然从 json 中恢复配置,
* 但只会替换 json 中指明的字段的值, 其他值不会进行置换
* </p>
* @param configField 注解 @ConfigInfo 的 field
* @param config 配置 json
*/
@PostMapping({"/{configField}", "/{configField}/", "/{configField}/index"})
@ResponseBody
public <T extends IConfig<T>> R<?> saveConfig(@PathVariable String configField, @RequestBody JSONObject config) {
public <T extends IConfig<T>> R<?> saveConfig(@PathVariable String configField, @RequestBody JsonNode config) throws Exception {
Class<T> clazz = configService.getConfigClassByField(configField);
if (clazz == null) {
throw new PageNotFoundException();
}
return R.judge(
configService.saveOrUpdate((T)config.to(clazz)),
"保存失败");
return R.judgeThrow(() -> {
// 找出其中有的字段
Set<String> 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();
});
}
}

View File

@@ -74,8 +74,9 @@ public class MailConfig implements Serializable, IConfig<MailConfig> {
}
@Override
public void afterSaving() {
public MailConfig afterSaving() {
SpringContextHolder.getBean(MailService.class).resetTaskDelay();
return this;
}
}

View File

@@ -24,7 +24,7 @@ public class MarkdownConfig implements Serializable, IConfig<MarkdownConfig> {
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<MarkdownConfig> {
log.warn("Save mermaid self-defined css failed", e);
}
}
return this;
}
}

View File

@@ -121,8 +121,9 @@ public class NetworkConfig implements Serializable, IConfig<NetworkConfig> {
return trustedCidrBlockList;
}
public void afterSaving() {
public NetworkConfig afterSaving() {
trustedIpList = null;
trustedCidrBlockList = null;
return this;
}
}

View File

@@ -65,11 +65,12 @@ public class PageConfig implements Serializable, IConfig<PageConfig>, 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() {

View File

@@ -103,11 +103,12 @@ public class SiteConfig implements Serializable, IConfig<SiteConfig>, 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() {

View File

@@ -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,11 +169,13 @@ public class MermaidNodeRenderer implements NodeRenderer {
configPath = Paths.get(themeService.getCurrentTheme().getDirectory(), "conf/mermaid/config.json");
}
if (configPath != null) {
File configFile = configPath.toFile();
if (configFile.exists()) {
cmd.add("-c");
cmd.add(configPath.toString());
}
}
try {
ProcessBuilder pb = new ProcessBuilder(cmd);

View File

@@ -18,8 +18,13 @@ public interface IConfig<T extends IConfig<T>> {
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;
}
}

View File

@@ -510,6 +510,9 @@ public abstract class AbstractPostService<S extends AbstractPostService<S, C>, C
@UpdatePosts(postViews = true, commentsCount = true)
public <R> PageQuery<R> conditionPage(PageQuery<R> pageQueryCondition) {
PageQuery<R> result = proxy().cacheableConditionPage(pageQueryCondition, isLogin());
// result 可能是缓存的title 又可能是和 siteConfig... 有关的
// 获取到结果以后要把与查询无关的内容更新上去
result.setTitle(pageQueryCondition.getTitle());
return result;
}