mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-19 12:06:40 +08:00
refactor(config): 统一共享配置模板来源
This commit is contained in:
+7
-1
@@ -1,6 +1,6 @@
|
|||||||
# BlueArchiveToolkit 当前工作区状态
|
# BlueArchiveToolkit 当前工作区状态
|
||||||
|
|
||||||
- **更新时间**:2026-09-13
|
- **更新时间**:2026-09-19
|
||||||
- **状态来源**:本地工作区盘点、代码验证和最新提交
|
- **状态来源**:本地工作区盘点、代码验证和最新提交
|
||||||
- **状态分支**:`experiment`
|
- **状态分支**:`experiment`
|
||||||
- **最新已推送功能提交**:以当前 `git log --oneline -1` 为准
|
- **最新已推送功能提交**:以当前 `git log --oneline -1` 为准
|
||||||
@@ -188,6 +188,12 @@ marker 存在时才保持兼容 key,无法证明归属时拒绝 cleanup;完
|
|||||||
- `FileSystemCasRepository` 作为 `bat-core::CasRepository` 适配层。
|
- `FileSystemCasRepository` 作为 `bat-core::CasRepository` 适配层。
|
||||||
- `InMemoryResourceRepository`。
|
- `InMemoryResourceRepository`。
|
||||||
- `SqliteResourceRepository`。
|
- `SqliteResourceRepository`。
|
||||||
|
- `ResourceQuery.path_pattern` 使用 `bat-core` 共享的 Unicode scalar 动态规划 matcher;
|
||||||
|
`*` / `**` / `?`、路径分隔符和字面量特殊字符的语义在 InMemory/SQLite 间一致。
|
||||||
|
SQLite 只做候选过滤并由最终 matcher 判定,带路径或 metadata 后置过滤的
|
||||||
|
`count()` 与 `list()` 精确相等。
|
||||||
|
- Rust `bat` 与 Go `bat-api` 都从 `internal/configtemplate/config.toml.example`
|
||||||
|
生成共享配置模板,仓库内 `cmd/bat-api/config.toml.example` 由一致性测试锁定为相同字节。
|
||||||
- 官方 pull plan 构建。
|
- 官方 pull plan 构建。
|
||||||
- `OfficialResourcePullService`:官方 URL 拒绝策略、目标路径映射、下载 manifest、下载 quarantine、`.part` 续传、curl 代理配置、403/404/5xx 分类重试、ZIP 结构校验、官方 seed `.hash` 校验、本地全量 verify。
|
- `OfficialResourcePullService`:官方 URL 拒绝策略、目标路径映射、下载 manifest、下载 quarantine、`.part` 续传、curl 代理配置、403/404/5xx 分类重试、ZIP 结构校验、官方 seed `.hash` 校验、本地全量 verify。
|
||||||
- `OfficialUpdateService`:官方 metadata auto-discover、bootstrap cache、snapshot diff、marker diff、本地 audit/repair、失败 staging 恢复。
|
- `OfficialUpdateService`:官方 metadata auto-discover、bootstrap cache、snapshot diff、marker diff、本地 audit/repair、失败 staging 恢复。
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Go 侧进度与边界(权威)
|
# Go 侧进度与边界(权威)
|
||||||
|
|
||||||
- **更新时间**:2026-09-12
|
- **更新时间**:2026-09-19
|
||||||
- **用途**:统一 Go module `bat-api` 的产品边界、既有约定和组件进度;其他文档与此冲突时以本文为准。
|
- **用途**:统一 Go module `bat-api` 的产品边界、既有约定和组件进度;其他文档与此冲突时以本文为准。
|
||||||
- **关联缺口**:G-009(资源 bootstrap/分发);相关契约见 `docs/architecture/official-resource-backend.md` §7 和 `docs/guides/bat-api-local-live-smoke.md`
|
- **关联缺口**:G-009(资源 bootstrap/分发);相关契约见 `docs/architecture/official-resource-backend.md` §7 和 `docs/guides/bat-api-local-live-smoke.md`
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
|
|
||||||
| ID | 约定 |
|
| ID | 约定 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| K | 共享 `config.toml` 的 `[api]` / 进程环境变量 / CLI:端口、public base、RPC socket、RPC 刷新周期;**预留** database/redis。应用不读取二进制旁 `.env` |
|
| K | 共享 `config.toml` 的 `[api]` / 进程环境变量 / CLI:端口、public base、RPC socket、RPC 刷新周期;模板唯一来源为 `internal/configtemplate/config.toml.example`,`cmd/bat-api/config.toml.example` 由测试校验字节一致;**预留** database/redis。应用不读取二进制旁 `.env` |
|
||||||
| L | 管理面 / bootstrap:`/healthz`、`/readyz`、`/v1/bootstrap`、`/v1/release`、`/v1/resources`、`/openapi.yaml`、`/admin/` 控制入口 |
|
| L | 管理面 / bootstrap:`/healthz`、`/readyz`、`/v1/bootstrap`、`/v1/release`、`/v1/resources`、`/openapi.yaml`、`/admin/` 控制入口 |
|
||||||
| M | CDN:`GET/HEAD /prod-clientpatch.bluearchiveyostar.com/...`,支持 Range、ETag、Last-Modified、长期缓存头 |
|
| M | CDN:`GET/HEAD /prod-clientpatch.bluearchiveyostar.com/...`,支持 Range、ETag、Last-Modified、长期缓存头 |
|
||||||
| N | server-info 可选;**只改 AddressablesCatalogUrlRoot** |
|
| N | server-info 可选;**只改 AddressablesCatalogUrlRoot** |
|
||||||
|
|||||||
@@ -13,94 +13,8 @@ use std::time::Duration;
|
|||||||
pub(crate) const CONFIG_FILE_NAME: &str = "config.toml";
|
pub(crate) const CONFIG_FILE_NAME: &str = "config.toml";
|
||||||
pub(crate) const CONFIG_EXAMPLE_FILE_NAME: &str = "config.toml.example";
|
pub(crate) const CONFIG_EXAMPLE_FILE_NAME: &str = "config.toml.example";
|
||||||
|
|
||||||
const CONFIG_TEMPLATE: &str = r#"# BlueArchive Toolkit 配置文件(bat 首次启动自动生成)
|
const CONFIG_TEMPLATE: &str =
|
||||||
#
|
include_str!("../../../../internal/configtemplate/config.toml.example");
|
||||||
# `config.toml` 位于 bat 二进制所在目录。存在时会被读取并生效。
|
|
||||||
# 优先级:CLI > 进程环境变量 > config.toml > 内置默认值。
|
|
||||||
# `config.toml.example` 只是模板,程序不会自动读取它作为实际配置。
|
|
||||||
#
|
|
||||||
# 字符串建议使用单引号,路径/URL 更容易直接复制。
|
|
||||||
|
|
||||||
[runtime]
|
|
||||||
state_dir = '/tmp/bat-pid'
|
|
||||||
interval_seconds = 3600
|
|
||||||
error_retry_seconds = 60
|
|
||||||
quiet_up_to_date = false
|
|
||||||
output_format = 'human'
|
|
||||||
banner = true
|
|
||||||
progress = true
|
|
||||||
tail_lines = 200
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
output_root = './bat-resources'
|
|
||||||
auto_discover = true
|
|
||||||
app_version = ''
|
|
||||||
connection_group = ''
|
|
||||||
launcher_version = '1.7.2'
|
|
||||||
platforms = ['windows', 'android']
|
|
||||||
snapshot_path = ''
|
|
||||||
dry_run = false
|
|
||||||
plan = false
|
|
||||||
force = false
|
|
||||||
audit_local = true
|
|
||||||
repair = true
|
|
||||||
|
|
||||||
[resource.server_info]
|
|
||||||
kind = 'none'
|
|
||||||
value = ''
|
|
||||||
|
|
||||||
[localized]
|
|
||||||
output_root = './bat-localized'
|
|
||||||
|
|
||||||
[repository]
|
|
||||||
import_repository = false
|
|
||||||
import_cas_root = ''
|
|
||||||
import_resource_repository_path = ''
|
|
||||||
|
|
||||||
[network]
|
|
||||||
curl_command = 'curl'
|
|
||||||
proxy = 'auto'
|
|
||||||
unzip_command = 'unzip'
|
|
||||||
zip_command = 'zip'
|
|
||||||
download_concurrency = 8
|
|
||||||
|
|
||||||
[translation.worker]
|
|
||||||
provider = 'mock'
|
|
||||||
fixture = ''
|
|
||||||
translation_memory_path = ''
|
|
||||||
glossary_path = ''
|
|
||||||
concurrency = 8
|
|
||||||
max_attempts = 3
|
|
||||||
lease_seconds = 300
|
|
||||||
retry_backoff_seconds = 5
|
|
||||||
max_tasks = ''
|
|
||||||
worker_id = ''
|
|
||||||
|
|
||||||
# Go bat-api consumes this shared section. Rust bat ignores it.
|
|
||||||
[api]
|
|
||||||
listen = ':18080'
|
|
||||||
public_base_url = 'http://127.0.0.1:18080'
|
|
||||||
state_dir = '/tmp/bat-pid'
|
|
||||||
socket_path = ''
|
|
||||||
resource_root = ''
|
|
||||||
server_info_file = ''
|
|
||||||
require_indexed = true
|
|
||||||
verify_size = true
|
|
||||||
rpc_timeout = '30s'
|
|
||||||
refresh_interval = '1m'
|
|
||||||
auth_token = ''
|
|
||||||
auth_query_param = 'bat_token'
|
|
||||||
auth_exempt_paths = []
|
|
||||||
trust_proxy_headers = false
|
|
||||||
access_log = false
|
|
||||||
rate_limit_rps = 0
|
|
||||||
rate_limit_burst = 0
|
|
||||||
max_resource_page_limit = 1000
|
|
||||||
database_url = ''
|
|
||||||
database_password = ''
|
|
||||||
redis_url = ''
|
|
||||||
redis_password = ''
|
|
||||||
"#;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub(crate) struct BatConfigFile {
|
pub(crate) struct BatConfigFile {
|
||||||
@@ -1068,6 +982,14 @@ mod tests {
|
|||||||
assert!(options.config.server_info_source.is_none());
|
assert!(options.config.server_info_source.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shared_template_matches_checked_in_bat_api_example() {
|
||||||
|
assert_eq!(
|
||||||
|
CONFIG_TEMPLATE,
|
||||||
|
include_str!("../../../../cmd/bat-api/config.toml.example")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn config_file_is_written_when_missing() {
|
fn config_file_is_written_when_missing() {
|
||||||
let temp = TempDir::new().unwrap();
|
let temp = TempDir::new().unwrap();
|
||||||
@@ -1076,10 +998,7 @@ mod tests {
|
|||||||
let example = temp.path().join(CONFIG_EXAMPLE_FILE_NAME);
|
let example = temp.path().join(CONFIG_EXAMPLE_FILE_NAME);
|
||||||
assert!(example.exists());
|
assert!(example.exists());
|
||||||
let content = fs::read_to_string(example).unwrap();
|
let content = fs::read_to_string(example).unwrap();
|
||||||
let parsed = parse_config_document(&content).unwrap();
|
assert_eq!(content, CONFIG_TEMPLATE);
|
||||||
let mut options = CliOptions::default();
|
|
||||||
parsed.apply_to_options(&mut options).unwrap();
|
|
||||||
assert_eq!(options.config.output_root, PathBuf::from("./bat-resources"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -8,100 +8,13 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"bat-api/internal/configtemplate"
|
||||||
"github.com/pelletier/go-toml/v2"
|
"github.com/pelletier/go-toml/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfigTOMLTemplate is the only template generated by bat-api. Rust bat
|
// ConfigTOMLTemplate is the canonical template generated by bat-api. Rust
|
||||||
// accepts the same file and ignores the [api] section.
|
// bat consumes the same file and ignores the [api] section.
|
||||||
const ConfigTOMLTemplate = `# BlueArchive Toolkit shared application configuration.
|
var ConfigTOMLTemplate = configtemplate.Template
|
||||||
# Priority: CLI flags > process environment > config.toml > built-in defaults.
|
|
||||||
# This file is the application configuration. The application never reads .env.
|
|
||||||
#
|
|
||||||
# Rust bat consumes [runtime], [resource], [localized], [repository], [network]
|
|
||||||
# and [translation.worker]. Go bat-api consumes [api]. Each binary ignores the
|
|
||||||
# other application's section.
|
|
||||||
|
|
||||||
[runtime]
|
|
||||||
state_dir = '/tmp/bat-pid'
|
|
||||||
interval_seconds = 3600
|
|
||||||
error_retry_seconds = 60
|
|
||||||
quiet_up_to_date = false
|
|
||||||
output_format = 'human'
|
|
||||||
banner = true
|
|
||||||
progress = true
|
|
||||||
tail_lines = 200
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
output_root = './bat-resources'
|
|
||||||
auto_discover = true
|
|
||||||
app_version = ''
|
|
||||||
connection_group = ''
|
|
||||||
launcher_version = '1.7.2'
|
|
||||||
platforms = ['windows', 'android']
|
|
||||||
snapshot_path = ''
|
|
||||||
dry_run = false
|
|
||||||
plan = false
|
|
||||||
force = false
|
|
||||||
audit_local = true
|
|
||||||
repair = true
|
|
||||||
|
|
||||||
[resource.server_info]
|
|
||||||
kind = 'none'
|
|
||||||
value = ''
|
|
||||||
|
|
||||||
[localized]
|
|
||||||
output_root = './bat-localized'
|
|
||||||
|
|
||||||
[repository]
|
|
||||||
import_repository = false
|
|
||||||
import_cas_root = ''
|
|
||||||
import_resource_repository_path = ''
|
|
||||||
|
|
||||||
[network]
|
|
||||||
curl_command = 'curl'
|
|
||||||
proxy = 'auto'
|
|
||||||
unzip_command = 'unzip'
|
|
||||||
zip_command = 'zip'
|
|
||||||
download_concurrency = 8
|
|
||||||
|
|
||||||
[translation.worker]
|
|
||||||
provider = 'mock'
|
|
||||||
fixture = ''
|
|
||||||
translation_memory_path = ''
|
|
||||||
glossary_path = ''
|
|
||||||
concurrency = 8
|
|
||||||
max_attempts = 3
|
|
||||||
lease_seconds = 300
|
|
||||||
retry_backoff_seconds = 5
|
|
||||||
max_tasks = ''
|
|
||||||
worker_id = ''
|
|
||||||
|
|
||||||
[api]
|
|
||||||
listen = ':18080'
|
|
||||||
public_base_url = 'http://127.0.0.1:18080'
|
|
||||||
state_dir = '/tmp/bat-pid'
|
|
||||||
socket_path = ''
|
|
||||||
resource_root = ''
|
|
||||||
server_info_file = ''
|
|
||||||
require_indexed = true
|
|
||||||
verify_size = true
|
|
||||||
rpc_timeout = '30s'
|
|
||||||
refresh_interval = '1m'
|
|
||||||
auth_token = ''
|
|
||||||
auth_query_param = 'bat_token'
|
|
||||||
auth_exempt_paths = []
|
|
||||||
trust_proxy_headers = false
|
|
||||||
access_log = false
|
|
||||||
rate_limit_rps = 0
|
|
||||||
rate_limit_burst = 0
|
|
||||||
max_resource_page_limit = 1000
|
|
||||||
|
|
||||||
# Reserved for a future API persistence layer.
|
|
||||||
database_url = ''
|
|
||||||
database_password = ''
|
|
||||||
redis_url = ''
|
|
||||||
redis_password = ''
|
|
||||||
`
|
|
||||||
|
|
||||||
// LoadConfigFromCurrentExe loads config.toml next to the running binary.
|
// LoadConfigFromCurrentExe loads config.toml next to the running binary.
|
||||||
func LoadConfigFromCurrentExe(cfg *Config) error {
|
func LoadConfigFromCurrentExe(cfg *Config) error {
|
||||||
|
|||||||
@@ -27,12 +27,31 @@ func TestLoadConfigFromBinaryDirFirstLaunchCreatesExampleOnly(t *testing.T) {
|
|||||||
if _, err := os.Stat(filepath.Join(dir, ConfigExampleName)); err != nil {
|
if _, err := os.Stat(filepath.Join(dir, ConfigExampleName)); err != nil {
|
||||||
t.Fatalf("config.toml.example was not created: %v", err)
|
t.Fatalf("config.toml.example was not created: %v", err)
|
||||||
}
|
}
|
||||||
|
examplePath := filepath.Join(dir, ConfigExampleName)
|
||||||
|
example, err := os.ReadFile(examplePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(example) != ConfigTOMLTemplate {
|
||||||
|
t.Fatal("first-launch example differs from the canonical runtime template")
|
||||||
|
}
|
||||||
if _, err := os.Stat(filepath.Join(dir, ".env")); !os.IsNotExist(err) {
|
if _, err := os.Stat(filepath.Join(dir, ".env")); !os.IsNotExist(err) {
|
||||||
t.Fatalf("unexpected .env after first launch, err=%v", err)
|
t.Fatalf("unexpected .env after first launch, err=%v", err)
|
||||||
}
|
}
|
||||||
if cfg.Listen != DefaultListen || cfg.RPCTimeout != 30*time.Second {
|
if cfg.Listen != DefaultListen || cfg.RPCTimeout != 30*time.Second {
|
||||||
t.Fatalf("first launch changed defaults: %+v", cfg)
|
t.Fatalf("first launch changed defaults: %+v", cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := LoadConfigFromBinaryDir(dir, &cfg); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
unchanged, err := os.ReadFile(examplePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(unchanged) != string(example) {
|
||||||
|
t.Fatal("existing config.toml.example was modified")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckedInBatAPIConfigExampleMatchesRuntimeTemplate(t *testing.T) {
|
func TestCheckedInBatAPIConfigExampleMatchesRuntimeTemplate(t *testing.T) {
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# BlueArchive Toolkit shared application configuration.
|
||||||
|
# Priority: CLI flags > process environment > config.toml > built-in defaults.
|
||||||
|
# This file is the application configuration. The application never reads .env.
|
||||||
|
#
|
||||||
|
# Rust bat consumes [runtime], [resource], [localized], [repository], [network]
|
||||||
|
# and [translation.worker]. Go bat-api consumes [api]. Each binary ignores the
|
||||||
|
# other application's section.
|
||||||
|
|
||||||
|
[runtime]
|
||||||
|
state_dir = '/tmp/bat-pid'
|
||||||
|
interval_seconds = 3600
|
||||||
|
error_retry_seconds = 60
|
||||||
|
quiet_up_to_date = false
|
||||||
|
output_format = 'human'
|
||||||
|
banner = true
|
||||||
|
progress = true
|
||||||
|
tail_lines = 200
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
output_root = './bat-resources'
|
||||||
|
auto_discover = true
|
||||||
|
app_version = ''
|
||||||
|
connection_group = ''
|
||||||
|
launcher_version = '1.7.2'
|
||||||
|
platforms = ['windows', 'android']
|
||||||
|
snapshot_path = ''
|
||||||
|
dry_run = false
|
||||||
|
plan = false
|
||||||
|
force = false
|
||||||
|
audit_local = true
|
||||||
|
repair = true
|
||||||
|
|
||||||
|
[resource.server_info]
|
||||||
|
kind = 'none'
|
||||||
|
value = ''
|
||||||
|
|
||||||
|
[localized]
|
||||||
|
output_root = './bat-localized'
|
||||||
|
|
||||||
|
[repository]
|
||||||
|
import_repository = false
|
||||||
|
import_cas_root = ''
|
||||||
|
import_resource_repository_path = ''
|
||||||
|
|
||||||
|
[network]
|
||||||
|
curl_command = 'curl'
|
||||||
|
proxy = 'auto'
|
||||||
|
unzip_command = 'unzip'
|
||||||
|
zip_command = 'zip'
|
||||||
|
download_concurrency = 8
|
||||||
|
|
||||||
|
[translation.worker]
|
||||||
|
provider = 'mock'
|
||||||
|
fixture = ''
|
||||||
|
translation_memory_path = ''
|
||||||
|
glossary_path = ''
|
||||||
|
concurrency = 8
|
||||||
|
max_attempts = 3
|
||||||
|
lease_seconds = 300
|
||||||
|
retry_backoff_seconds = 5
|
||||||
|
max_tasks = ''
|
||||||
|
worker_id = ''
|
||||||
|
|
||||||
|
[api]
|
||||||
|
listen = ':18080'
|
||||||
|
public_base_url = 'http://127.0.0.1:18080'
|
||||||
|
state_dir = '/tmp/bat-pid'
|
||||||
|
socket_path = ''
|
||||||
|
resource_root = ''
|
||||||
|
server_info_file = ''
|
||||||
|
require_indexed = true
|
||||||
|
verify_size = true
|
||||||
|
rpc_timeout = '30s'
|
||||||
|
refresh_interval = '1m'
|
||||||
|
auth_token = ''
|
||||||
|
auth_query_param = 'bat_token'
|
||||||
|
auth_exempt_paths = []
|
||||||
|
trust_proxy_headers = false
|
||||||
|
access_log = false
|
||||||
|
rate_limit_rps = 0
|
||||||
|
rate_limit_burst = 0
|
||||||
|
max_resource_page_limit = 1000
|
||||||
|
|
||||||
|
# Reserved for a future API persistence layer.
|
||||||
|
database_url = ''
|
||||||
|
database_password = ''
|
||||||
|
redis_url = ''
|
||||||
|
redis_password = ''
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// Package configtemplate provides the shared runtime configuration template.
|
||||||
|
package configtemplate
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
// Template is the canonical config.toml.example content consumed by both
|
||||||
|
// Rust bat and Go bat-api.
|
||||||
|
//
|
||||||
|
//go:embed config.toml.example
|
||||||
|
var Template string
|
||||||
Reference in New Issue
Block a user