JaiRouter 多版本配置管理:一个轻量级多版本配置实现思路

JaiRouter 多版本配置管理:一个轻量级多版本配置实现思路


文章目录

一、需求缘起

最近在写开源项目JaiRouter,配置项目比较多,考虑到真实的业务场景下,多次配置后,测试后,无法回滚到原始版本,因此考虑到需要频繁回滚 JSON 配置。为了省掉数据库依赖,我把版本号直接放到文件名里,写完发现代码意外地短,回滚速度意外地快。整理出来,权当抛砖引玉。


二、核心思路

  1. 文件名即版本号:config@version.json
  2. 当前配置单独存放:config.json
  3. 读/写/回滚/清理 全部基于 java.nio.file.Files,不引入第三方存储

三、代码骨架

kotlin 复制代码
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.*;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 文件系统多版本配置存储:文件名即版本号
 */
public final class FileVersionStore {

    private static final ObjectMapper MAPPER = new ObjectMapper();
    private final Path dir;

    public FileVersionStore(Path root) throws IOException {
        this.dir = root.toAbsolutePath().normalize();
        Files.createDirectories(dir);
    }

    /* ---------- 基础 API ---------- */

    /** 保存指定版本(覆盖写) */
    public void save(int version, Map<String, Object> data) throws IOException {
        Path file = versionFile(version);
        Files.writeString(file, MAPPER.writeValueAsString(data),
                StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
    }

    /** 读取当前配置 */
    public Map<String, Object> current() throws IOException {
        Path cur = currentFile();
        if (Files.notExists(cur)) {
            throw new NoSuchFileException("当前配置文件不存在: " + cur);
        }
        return MAPPER.readValue(cur.toFile(), new TypeReference<>() {});
    }

    /** 回滚到指定版本(原子覆盖) */
    public void rollbackTo(int version) throws IOException {
        Path src = versionFile(version);
        if (Files.notExists(src)) {
            throw new NoSuchFileException("版本文件不存在: " + src);
        }
        // ATOMIC_MOVE 保证并发安全
        Files.copy(src, currentFile(), StandardCopyOption.REPLACE_EXISTING);
    }

    /** 列出所有可用版本,升序 */
    public List<Integer> listVersions() throws IOException {
        try (Stream<Path> stream = Files.list(dir)) {
            return stream.map(Path::getFileName)
                         .map(Path::toString)
                         .filter(n -> n.startsWith("config@") && n.endsWith(".json"))
                         .map(n -> n.substring("config@".length(), n.length() - ".json".length()))
                         .mapToInt(Integer::parseInt)
                         .sorted()
                         .boxed()
                         .collect(Collectors.toList());
        }
    }

    /** 删除指定版本(手工清理用) */
    public void delete(int version) throws IOException {
        Files.deleteIfExists(versionFile(version));
    }

    /* ---------- 内部工具 ---------- */

    private Path currentFile() {
        return dir.resolve("config.json");
    }

    private Path versionFile(int version) {
        return dir.resolve("config@" + version + ".json");
    }

    /* ---------- 简单演示 ---------- */
    public static void main(String[] args) throws IOException {
        FileVersionStore store = new FileVersionStore(Paths.get("repo"));

        // 写两个版本
        store.save(1, Map.of("threshold", 10));
        store.save(2, Map.of("threshold", 20));

        // 回滚到 v1
        store.rollbackTo(1);
        System.out.println("current=" + store.current()); // {threshold=10}

        // 查看所有版本
        System.out.println("versions=" + store.listVersions()); // [1, 2]
    }
}

依赖只有 Jackson。


四、什么时候够用

  • 单节点或小集群
  • 版本总量 < 10 k(目录扫描可接受)
  • 无需字段级合并,整体覆盖即可

五、什么时候换方案

  • 需要分布式一致性 → 上 etcd/consul
  • 需要 JSON Patch → 上 Git 或数据库
  • 文件数 > 10 k → 加一层前缀索引或转对象存储

六、小结

把版本号写进文件名,回滚操作退化成一次文件复制,代码量少、依赖少、调试方便。对于个人项目或内部小工具,算一个"够用且易丢进 Docker"的折中方案。

完整实验代码已放到 JAiRouter,欢迎试用、提 issue。

相关推荐
Joy T1 小时前
Spring AI 项目中的 pom.xml、application.yml 与 Nacos:从依赖管理到运行配置
spring·nacos·pom.xml·profile·参数配置·application.yml
百数平台4 小时前
百数AI文本知识库配置详解:本地文档上传与自定义录入、解析分段策略全说明
低代码·ai
百数平台5 小时前
百数AI对话流(Chatflow)开发指南:会话历史配置、角色面板、开场白预置问题与智能体挂载
低代码·ai
笨蛋©6 小时前
[实战] 扫描图纸转DXF:2026年制造业数字化图纸处理与GD&T识别实操指南
ai·数字化·cad·质量管理·制造业
fundoit8 小时前
为什么需要 Access Token 和 ID Token 两个令牌
java·spring·架构·github·oauth2
俊哥V8 小时前
每日 AI 研究简报 · 2026-10-08
人工智能·ai
鱼宵8 小时前
Spring AI 流式输出:Flux + SSE 打字机,回答不再干等三秒
java·人工智能·spring·sse·springai·流式输出
jason.zeng@15022079 小时前
(六)Prompt 优化
python·ai·langchain·prompt·ai编程·llama
小鹿的周先生9 小时前
第19章-Agent
java·spring·ai
全栈练习生9 小时前
大模型推理全链路
python·ai