Compare commits
7
Commits
bba7ee3eb4
...
01e67648e0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01e67648e0 | ||
|
|
22ade06114 | ||
|
|
fc4e1c4a61 | ||
|
|
14971971a1 | ||
|
|
391e6b0066 | ||
|
|
ac177c73df | ||
|
|
08df3929b2 |
No files matched your search
@@ -136,7 +136,7 @@
|
||||
- 数据操作:libmsg `RpcClient` 调 db2 `execute` 路由(与 Python 版调 db 路径一致)。
|
||||
- 内容审核:HttpClient POST ai_review `/review`,fail-closed。
|
||||
- 异步日志:libmsg `AsyncQueue` 写入 db2 Logs 表。
|
||||
- 配置从 confs 拉取 `dnfs.ini`,环境变量覆盖,命令行参数最高优先级;详见 [dnfs2/README.md](dnfs2/README.md)。
|
||||
- 配置通过 CLI 参数 + 环境变量 + 默认值,不依赖 confs;详见 [dnfs2/README.md](dnfs2/README.md)。
|
||||
|
||||
### db(通用数据库服务)
|
||||
|
||||
|
||||
+1
-1
@@ -2,4 +2,4 @@
|
||||
|
||||
# Debian
|
||||
sudo apt update
|
||||
sudo apt install libsqlite3-dev libboost-all-dev libcurl4-gnutls-dev python3 python3-venv python3-pip g++ patchelf golang-go libportaudio-ocaml-dev libfreetype-dev
|
||||
sudo apt install libsqlite3-dev libboost-all-dev libcurl4-gnutls-dev python3 python3-venv python3-pip g++ patchelf golang-go libportaudio-ocaml-dev libfreetype-dev libyaml-cpp-dev
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
# RHEL / CentOS / Rocky / Fedora
|
||||
sudo dnf install -y boost-devel libcurl-devel python3 python3-pip gcc-c++ patchelf golang sqlite-devel freetype-devel
|
||||
sudo dnf install -y boost-devel libcurl-devel python3 python3-pip gcc-c++ patchelf golang sqlite-devel freetype-devel yaml-cpp-devel
|
||||
+1
-1
@@ -2,5 +2,5 @@
|
||||
cd "$(dirname "$0")" || exit
|
||||
source ./venv/bin/activate
|
||||
#exec ./dnfs.bin
|
||||
exec python3 ai_review.py
|
||||
exec python3 ai_review.py "$@"
|
||||
|
||||
+1
-1
@@ -3,6 +3,6 @@ cd "$(dirname "$0")" || exit
|
||||
source ./_setenv.sh
|
||||
source ./venv/bin/activate
|
||||
export PATH=${PATH}:~/.aibox/skills
|
||||
exec python aibox.py $@
|
||||
exec python aibox.py "$@"
|
||||
|
||||
|
||||
+1
-3
@@ -4,7 +4,5 @@
|
||||
# 启动时从 confs(CONFS_HOST:CONFS_PORT,默认 127.0.0.1:8010)拉取配置。
|
||||
cd "$(dirname "$0")" || exit
|
||||
|
||||
# 等待依赖服务启动(confs/db)
|
||||
sleep 2
|
||||
|
||||
# sleep 已移至 services.yaml 的 sleep_before 字段统一管理
|
||||
exec ./build/ca.out "$@"
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
sleep 1
|
||||
exec ./captcha2.bin
|
||||
exec ./captcha2.bin "$@"
|
||||
@@ -236,7 +236,6 @@ except RpcError as e:
|
||||
| 服务 | 配置文件 | 接入方式 |
|
||||
|------|---------|---------|
|
||||
| [static](../static/) | [static.ini](configs/static.ini) | confs 强依赖(pymsg 自动阻塞等待 confs 就绪)→ 环境变量覆盖(最高优先级,Docker 兼容) |
|
||||
| [dnfs](../dnfs/) | [dnfs.ini](configs/dnfs.ini) | confs 强依赖(pymsg 自动阻塞等待 confs 就绪)→ 环境变量覆盖(最高优先级,Docker 兼容) |
|
||||
| [db](../db/) | [db.ini](configs/db.ini) | confs 强依赖(pymsg 自动阻塞等待 confs 就绪)→ 环境变量覆盖(最高优先级,Docker 兼容) |
|
||||
|
||||
static 的接入实现见 [static/fsys.py](../static/fsys.py):
|
||||
@@ -248,13 +247,6 @@ static 的接入实现见 [static/fsys.py](../static/fsys.py):
|
||||
- 支持的环境变量:`LISTEN_ADDR`/`LISTEN_PORT`/`STATIC_ROOT`/`CONFS_HOST`/`CONFS_PORT`
|
||||
- 已删除 `--config` 命令行参数和本地 `config.ini` 回退逻辑
|
||||
|
||||
dnfs 的接入实现见 [dnfs/dnfs.py](../dnfs/dnfs.py):
|
||||
- 两层加载顺序:confs(dnfs.ini,强依赖)→ 环境变量覆盖(优先级最高)
|
||||
- pymsg `retry_on_refused=True` 自动阻塞等待 confs 就绪,不严格要求启动顺序
|
||||
- 配置项涵盖:`[server]` host/port、`[database]` host/port/timeout、`[ai_review]` url/timeout
|
||||
- confs 连接地址为引导配置:环境变量 `CONFS_HOST`/`CONFS_PORT` 优先,默认 `127.0.0.1:8010`
|
||||
- 支持的环境变量:`LISTEN_ADDR`/`LISTEN_PORT`/`DB_HOST`/`DB_PORT`/`REVIEW_URL`/`CONFS_HOST`/`CONFS_PORT`
|
||||
|
||||
db 的接入实现见 [db/dbv2.py](../db/dbv2.py):
|
||||
- 两层加载顺序:confs(db.ini,强依赖)→ 环境变量覆盖(优先级最高)
|
||||
- pymsg `retry_on_refused=True` 自动阻塞等待 confs 就绪,不严格要求启动顺序
|
||||
@@ -274,7 +266,6 @@ confs/
|
||||
├── configs/ # 配置文件存放目录(被分发对象)
|
||||
│ ├── example.ini # 示例配置
|
||||
│ ├── static.ini # static 服务配置(已接入分发)
|
||||
│ ├── dnfs.ini # dnfs 服务配置(已接入分发)
|
||||
│ └── db.ini # db 服务配置(已接入分发)
|
||||
├── docs/
|
||||
│ └── SECURITY_AUDIT_20260801.md # 安全审计报告
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
[server]
|
||||
host = 127.0.0.1
|
||||
port = 8006
|
||||
|
||||
[database]
|
||||
host = 127.0.0.1
|
||||
port = 8005
|
||||
timeout = 10
|
||||
|
||||
[ai_review]
|
||||
url = http://127.0.0.1:8007/review
|
||||
timeout = 10
|
||||
|
||||
[gateway]
|
||||
url = http://127.0.0.1:8443/whoami
|
||||
timeout = 5
|
||||
+8
-3
@@ -4,7 +4,7 @@ APP_NAME := nidus
|
||||
|
||||
INSTL_DIRS := /usr/local/bin
|
||||
|
||||
CONF_NAME := init.txt
|
||||
CONF_NAME := services.yaml
|
||||
|
||||
CONF_DIR := /etc/Nidus
|
||||
|
||||
@@ -17,7 +17,7 @@ CPP:= g++
|
||||
INCS:= -I include/
|
||||
|
||||
MY_LIBS := -L/usr/lib -lus_th -lus_http -lus_httpclient -lus_msg -lcurl
|
||||
LIBS := -lpthread -lssl -lcrypto -lboost_program_options -lboost_json -lboost_thread -lboost_filesystem
|
||||
LIBS := -lpthread -lssl -lcrypto -lboost_program_options -lboost_json -lboost_thread -lboost_filesystem -lyaml-cpp
|
||||
|
||||
CPPFLAGS := --std=c++20 -O3 -pipe #-Wall -Werror
|
||||
|
||||
@@ -26,7 +26,7 @@ $(shell mkdir -p $(BUILD_DIR))
|
||||
|
||||
#--------
|
||||
all: ${APP_NAME}.out
|
||||
${APP_NAME}.out: $(BUILD_DIR)/main.o $(BUILD_DIR)/PPool.o $(BUILD_DIR)/ProcessTask.o $(BUILD_DIR)/HttpTask.o $(BUILD_DIR)/ConfsTask.o $(BUILD_DIR)/Tttask.o $(BUILD_DIR)/pmc_mtd.o $(BUILD_DIR)/subsys_call.o
|
||||
${APP_NAME}.out: $(BUILD_DIR)/main.o $(BUILD_DIR)/PPool.o $(BUILD_DIR)/ProcessTask.o $(BUILD_DIR)/HttpTask.o $(BUILD_DIR)/ConfsTask.o $(BUILD_DIR)/Tttask.o $(BUILD_DIR)/pmc_mtd.o $(BUILD_DIR)/subsys_call.o $(BUILD_DIR)/InitParser.o
|
||||
$(CPP) $^ $(MY_LIBS) $(LIBS) -o $@
|
||||
|
||||
$(BUILD_DIR)/main.o: src/main.cpp
|
||||
@@ -53,6 +53,9 @@ $(BUILD_DIR)/pmc_mtd.o: src/main/pmc_mtd.cpp
|
||||
$(BUILD_DIR)/subsys_call.o: src/main/subsys_call.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/InitParser.o: src/registry/InitParser.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
|
||||
|
||||
install: $(APP_NAME).out
|
||||
@@ -65,6 +68,7 @@ install: $(APP_NAME).out
|
||||
@echo "正在配置 $(APP_NAME)"
|
||||
@mkdir -p $(CONF_DIR)
|
||||
@cp $(CONF_NAME) $(CONF_DIR)/$(CONF_NAME)
|
||||
@cp init.txt $(CONF_DIR)/init.txt
|
||||
@cp $(SRV_NAME) $(SRV_DIR)/$(SRV_NAME)
|
||||
@systemctl daemon-reload
|
||||
@systemctl enable $(SRV_NAME)
|
||||
@@ -77,6 +81,7 @@ uninstall:
|
||||
@
|
||||
@rm -f $(INSTL_DIRS)/$(APP_NAME)
|
||||
@rm -f $(CONF_DIRS)/$(CONF_NAME)
|
||||
@rm -f $(CONF_DIRS)/init.txt
|
||||
@rm -f -d ${CONF_DIRS}
|
||||
@
|
||||
@systemctl stop $(SRV_NAME)
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
export LD_LIBRARY_PATH=/lib:$LD_LIBRARY_PATH
|
||||
./nidus.out -i init.txt --serve 5200 --confs 8010
|
||||
./nidus.out -i . --serve 5200 --confs 8010
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace qing::registry {
|
||||
|
||||
/* 服务启动配置项
|
||||
*
|
||||
* 从 services.yaml 或 init.txt 解析出的单个服务声明。
|
||||
* YAML 模式下包含完整的元数据;TXT 模式下仅 cmd 有值。
|
||||
*/
|
||||
struct ServiceConfig {
|
||||
std::string name; /* 服务唯一标识 */
|
||||
std::string cmd; /* 启动命令(如 bash /opt/.../_run.sh) */
|
||||
std::vector<std::string> args; /* 传递给 _run.sh 的命令行参数 */
|
||||
std::map<std::string, std::string> env; /* 启动前设置的环境变量 */
|
||||
std::vector<std::string> depends_on; /* 依赖的服务名列表(未来 DAG 排序用) */
|
||||
int sleep_before = 0; /* 启动前等待秒数 */
|
||||
|
||||
/* 拼接完整命令:cmd + " " + args... */
|
||||
std::string toCommand() const;
|
||||
};
|
||||
|
||||
/* 启动配置解析器
|
||||
*
|
||||
* 优先读 services.yaml(结构化声明),不存在则回退 init.txt(纯命令列表)。
|
||||
* 两种格式都输出 std::vector<ServiceConfig>,调用方无需关心来源。
|
||||
*/
|
||||
class InitParser {
|
||||
public:
|
||||
/* 入口:传入配置目录(如 /etc/Nidus)
|
||||
*
|
||||
* 查找顺序:
|
||||
* 1. <dir>/services.yaml → parseYaml()
|
||||
* 2. <dir>/init.txt → parseTxt()
|
||||
* 3. 都不存在 → 返回空 vector,调用方自行处理
|
||||
*/
|
||||
static std::vector<ServiceConfig> load(const std::string& dir);
|
||||
|
||||
/* 解析 services.yaml */
|
||||
static std::vector<ServiceConfig> parseYaml(const std::string& path);
|
||||
|
||||
/* 解析 init.txt(向后兼容,每行一条命令,# 开头为注释) */
|
||||
static std::vector<ServiceConfig> parseTxt(const std::string& path);
|
||||
};
|
||||
|
||||
} // namespace qing::registry
|
||||
@@ -0,0 +1,109 @@
|
||||
# core/services.yaml
|
||||
# 声明式服务启动配置(替代 init.txt)
|
||||
# 如果本文件存在则优先使用,否则回退 init.txt
|
||||
#
|
||||
# 字段说明:
|
||||
# name 服务唯一标识(用于日志和未来注册中心)
|
||||
# cmd 启动命令(与 init.txt 每行格式一致)
|
||||
# args 传递给 _run.sh 的命令行参数列表(可选)
|
||||
# env 启动前设置的环境变量(可选)
|
||||
# depends_on 依赖的服务名列表(未来 DAG 拓扑排序用,当前仅记录)
|
||||
# sleep_before 启动前等待秒数(替代散落在 _run.sh 中的 sleep)
|
||||
|
||||
services:
|
||||
# --- 数据层(最先启动) ---
|
||||
- name: db2
|
||||
cmd: bash /opt/micro_servers/db2/_run.sh
|
||||
args: ["--instance", "db2", "--log-level", "info"]
|
||||
depends_on: []
|
||||
sleep_before: 0
|
||||
|
||||
# --- AI 审核 ---
|
||||
- name: ai_review
|
||||
cmd: bash /opt/micro_servers/ai_review/_run.sh
|
||||
args: []
|
||||
depends_on: [db2]
|
||||
sleep_before: 2
|
||||
|
||||
# --- 文件服务 ---
|
||||
- name: dnfs2
|
||||
cmd: bash /opt/micro_servers/dnfs2/_run.sh
|
||||
args:
|
||||
- "--instance"
|
||||
- "dnfs2"
|
||||
- "--log-level"
|
||||
- "info"
|
||||
- "--host"
|
||||
- "127.0.0.1"
|
||||
- "--port"
|
||||
- "8006"
|
||||
- "--db-host"
|
||||
- "127.0.0.1"
|
||||
- "--db-port"
|
||||
- "8005"
|
||||
- "--db-timeout"
|
||||
- "10"
|
||||
- "--review-url"
|
||||
- "http://127.0.0.1:8007/review"
|
||||
- "--review-timeout"
|
||||
- "10"
|
||||
- "--gateway-url"
|
||||
- "http://127.0.0.1:8443/whoami"
|
||||
- "--gateway-timeout"
|
||||
- "5"
|
||||
depends_on: [db2, ai_review]
|
||||
sleep_before: 0
|
||||
|
||||
# --- 邮件服务 ---
|
||||
- name: smtp
|
||||
cmd: bash /opt/micro_servers/smtp/_run.sh
|
||||
args: []
|
||||
depends_on: []
|
||||
sleep_before: 2
|
||||
|
||||
# --- 前端静态资源 ---
|
||||
- name: fsys
|
||||
cmd: bash /opt/micro_servers/fsys/_run.sh
|
||||
args: ["--instance", "fsys", "--log-level", "info"]
|
||||
depends_on: []
|
||||
sleep_before: 0
|
||||
|
||||
# --- 安全链路 ---
|
||||
- name: waf
|
||||
cmd: bash /opt/micro_servers/waf/_run.sh
|
||||
args: [] # -c config.yaml 保留在 _run.sh 作为默认值
|
||||
depends_on: [ca, gateway]
|
||||
sleep_before: 0
|
||||
|
||||
- name: ca
|
||||
cmd: bash /opt/micro_servers/ca/_run.sh
|
||||
args: []
|
||||
depends_on: [db2]
|
||||
sleep_before: 2
|
||||
|
||||
- name: gateway
|
||||
cmd: bash /opt/micro_servers/gateway/_run.sh
|
||||
args: []
|
||||
depends_on: [db2]
|
||||
sleep_before: 2
|
||||
|
||||
# --- 验证码 ---
|
||||
- name: captcha2
|
||||
cmd: bash /opt/micro_servers/captcha2/_run.sh
|
||||
args: []
|
||||
depends_on: []
|
||||
sleep_before: 1
|
||||
|
||||
# --- AI 工具箱 ---
|
||||
- name: aibox
|
||||
cmd: bash /opt/micro_servers/aibox/_run.sh
|
||||
args: []
|
||||
depends_on: []
|
||||
sleep_before: 0
|
||||
|
||||
# --- 端口重定向(最后启动) ---
|
||||
- name: redirect80
|
||||
cmd: bash /opt/micro_servers/redirect80/_run.sh
|
||||
args: []
|
||||
depends_on: [waf]
|
||||
sleep_before: 0
|
||||
+1
-1
@@ -149,7 +149,7 @@ int main(int argc, char** argv) {
|
||||
("list", "list process all run") /* 列出运行中的进程 */
|
||||
("kill", po::value<int>(), "kill a process") /* 杀死一个进程 */
|
||||
/********* 子系统服务模式 ********/
|
||||
("init,i", po::value<std::string>(), "self-init list") /* 输入自启动列表 */
|
||||
("init,i", po::value<std::string>(), "self-init config (dir or .yaml/.txt file)") /* 输入自启动配置 */
|
||||
("serve", po::value<int>(), "Run as http server") /* 启用http伺服器 */
|
||||
("confs", po::value<int>(), "Run embedded confs rpc server"); /* 启用内嵌confs配置服务 */
|
||||
|
||||
|
||||
+50
-20
@@ -5,6 +5,12 @@
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include "main/PPool.hh"
|
||||
#include "main/pmc_mtd.hh"
|
||||
#include "registry/InitParser.hh"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
|
||||
namespace qing {
|
||||
|
||||
@@ -87,37 +93,61 @@ catch(std::exception& exp) {
|
||||
|
||||
/* 解析自启动列表、添加自启动项
|
||||
*
|
||||
* 如果在解析过程中出错,只是打印异常? */
|
||||
* 兼容两种格式:
|
||||
* - services.yaml(结构化声明,含 args/env/depends_on/sleep_before)
|
||||
* - init.txt(纯命令列表,每行一条 bash 命令,# 开头为注释)
|
||||
*
|
||||
* path 可以是:
|
||||
* - 目录路径 → 自动查找 services.yaml 或 init.txt
|
||||
* - .yaml 文件 → 按 YAML 解析
|
||||
* - 其他文件 → 按 TXT 解析(向后兼容 init.txt)
|
||||
*
|
||||
* 如果在解析过程中出错,只是打印异常 */
|
||||
void parse_self_init_list(std::string &path)
|
||||
try {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open()) { /* ---- 没能打开自启动文件 ---- */
|
||||
std::cerr << "Failed to open self-init list" << std::endl;
|
||||
std::vector<registry::ServiceConfig> services;
|
||||
|
||||
if (fs::is_directory(path)) {
|
||||
/* 目录模式:InitParser::load 自动查找 services.yaml → init.txt */
|
||||
services = registry::InitParser::load(path);
|
||||
} else if (path.size() > 5 && path.substr(path.size() - 5) == ".yaml") {
|
||||
/* YAML 文件模式 */
|
||||
services = registry::InitParser::parseYaml(path);
|
||||
} else {
|
||||
/* TXT 文件模式(向后兼容 init.txt) */
|
||||
services = registry::InitParser::parseTxt(path);
|
||||
}
|
||||
|
||||
if (services.empty()) {
|
||||
std::cerr << "parse_self_init_list: 没有可启动的服务" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string row = "";
|
||||
char ch;
|
||||
while (file.get(ch))
|
||||
for (const auto& svc : services) {
|
||||
/* 启动前等待(替代散落在 _run.sh 中的 sleep) */
|
||||
if (svc.sleep_before > 0) {
|
||||
std::cerr << "[pmc] " << svc.name
|
||||
<< " 等待 " << svc.sleep_before << "s..." << std::endl;
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::seconds(svc.sleep_before));
|
||||
}
|
||||
|
||||
if (ch == '\n') {
|
||||
/* 设置环境变量 */
|
||||
for (const auto& [k, v] : svc.env)
|
||||
setenv(k.c_str(), v.c_str(), 1);
|
||||
|
||||
auto pos = row.find("#");
|
||||
if (pos != -1)
|
||||
row = row.substr(0, pos);
|
||||
if (row != "") { /* trim() */
|
||||
/* 清理已停止的进程,然后创建新进程 */
|
||||
ppool.clr();
|
||||
ppool.crtp(row); /* 创建进程 */
|
||||
row = "";
|
||||
|
||||
std::string full_cmd = svc.toCommand();
|
||||
std::cerr << "[pmc] 启动: " << svc.name
|
||||
<< " -> " << full_cmd << std::endl;
|
||||
ppool.crtp(full_cmd);
|
||||
}
|
||||
}
|
||||
|
||||
else row += ch;
|
||||
|
||||
file.close();
|
||||
|
||||
}
|
||||
|
||||
catch (std::exception &exp) {
|
||||
std::cerr << "Exception at: parse_self_init_list()" << std::endl;
|
||||
std::cerr << "Exception type: " << typeid(exp).name() << std::endl;
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
#include "registry/InitParser.hh"
|
||||
|
||||
#include <yaml-cpp/yaml.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
|
||||
namespace qing::registry {
|
||||
|
||||
/* -------- ServiceConfig -------- */
|
||||
|
||||
std::string ServiceConfig::toCommand() const {
|
||||
std::ostringstream oss;
|
||||
oss << cmd;
|
||||
for (const auto& a : args) {
|
||||
oss << ' ' << a;
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
/* -------- InitParser -------- */
|
||||
|
||||
std::vector<ServiceConfig> InitParser::load(const std::string& dir) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
auto yaml_path = fs::path(dir) / "services.yaml";
|
||||
auto txt_path = fs::path(dir) / "init.txt";
|
||||
|
||||
/* 优先 services.yaml */
|
||||
if (fs::exists(yaml_path)) {
|
||||
std::cerr << "[InitParser] 使用 services.yaml: " << yaml_path << std::endl;
|
||||
return parseYaml(yaml_path.string());
|
||||
}
|
||||
|
||||
/* 回退 init.txt */
|
||||
if (fs::exists(txt_path)) {
|
||||
std::cerr << "[InitParser] 回退 init.txt: " << txt_path << std::endl;
|
||||
return parseTxt(txt_path.string());
|
||||
}
|
||||
|
||||
std::cerr << "[InitParser] 未找到 services.yaml 或 init.txt 于 " << dir << std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<ServiceConfig> InitParser::parseYaml(const std::string& path) {
|
||||
std::vector<ServiceConfig> result;
|
||||
|
||||
YAML::Node root;
|
||||
try {
|
||||
root = YAML::LoadFile(path);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "[InitParser] YAML 解析失败: " << e.what() << std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!root["services"] || !root["services"].IsSequence()) {
|
||||
std::cerr << "[InitParser] services.yaml 缺少 services 序列" << std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
for (const auto& node : root["services"]) {
|
||||
ServiceConfig svc;
|
||||
|
||||
if (node["name"])
|
||||
svc.name = node["name"].as<std::string>();
|
||||
|
||||
if (node["cmd"])
|
||||
svc.cmd = node["cmd"].as<std::string>();
|
||||
|
||||
if (node["args"] && node["args"].IsSequence())
|
||||
for (const auto& a : node["args"])
|
||||
svc.args.push_back(a.as<std::string>());
|
||||
|
||||
if (node["env"] && node["env"].IsMap())
|
||||
for (const auto& kv : node["env"])
|
||||
svc.env[kv.first.as<std::string>()] = kv.second.as<std::string>();
|
||||
|
||||
if (node["depends_on"] && node["depends_on"].IsSequence())
|
||||
for (const auto& d : node["depends_on"])
|
||||
svc.depends_on.push_back(d.as<std::string>());
|
||||
|
||||
if (node["sleep_before"])
|
||||
svc.sleep_before = node["sleep_before"].as<int>(0);
|
||||
|
||||
if (svc.cmd.empty()) {
|
||||
std::cerr << "[InitParser] 跳过缺少 cmd 的服务: " << svc.name << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push_back(std::move(svc));
|
||||
}
|
||||
|
||||
std::cerr << "[InitParser] 解析完成,共 " << result.size() << " 个服务" << std::endl;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<ServiceConfig> InitParser::parseTxt(const std::string& path) {
|
||||
std::vector<ServiceConfig> result;
|
||||
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "[InitParser] 无法打开 init.txt: " << path << std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
/* 去除注释(# 之后的内容) */
|
||||
auto pos = line.find('#');
|
||||
if (pos != std::string::npos)
|
||||
line = line.substr(0, pos);
|
||||
|
||||
/* trim 空白 */
|
||||
auto first = line.find_first_not_of(" \t\r\n");
|
||||
if (first == std::string::npos)
|
||||
continue; /* 空行 */
|
||||
auto last = line.find_last_not_of(" \t\r\n");
|
||||
line = line.substr(first, last - first + 1);
|
||||
|
||||
ServiceConfig svc;
|
||||
svc.cmd = line;
|
||||
/* init.txt 模式下 name/args/env/depends_on/sleep_before 均为空/默认 */
|
||||
result.push_back(std::move(svc));
|
||||
}
|
||||
|
||||
std::cerr << "[InitParser] init.txt 解析完成,共 " << result.size() << " 个服务" << std::endl;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace qing::registry
|
||||
+62
-6
@@ -145,6 +145,11 @@ struct Config {
|
||||
// confs 地址(引导配置,不从 confs 自身获取)
|
||||
std::string confs_host = "127.0.0.1";
|
||||
int confs_port = 8010;
|
||||
|
||||
// 标准化 CLI 参数
|
||||
std::string log_level = "info"; // debug|info|warn|error
|
||||
std::string instance = "db2"; // 实例名(注册中心用)
|
||||
std::string confs_config_name = "db.ini"; // confs 拉取的配置名
|
||||
};
|
||||
|
||||
// 从 sqlite:///path 或 sqlite:////abs/path 提取文件路径
|
||||
@@ -159,21 +164,43 @@ static std::string db_url_to_path(const std::string& url) {
|
||||
static Config load_config(int argc, char* argv[]) {
|
||||
Config cfg;
|
||||
|
||||
// 0. 预扫描 --help/-h 和 --config(需在 confs 拉取前处理)
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string arg = argv[i];
|
||||
if (arg == "--help" || arg == "-h") {
|
||||
std::printf(
|
||||
"db2 — C++20 数据库 RPC 服务\n\n"
|
||||
"用法: db2 [选项]\n\n"
|
||||
"选项:\n"
|
||||
" --host <addr> 绑定地址 (别名: --addr/-a)\n"
|
||||
" --port <num> 监听端口 (别名: -p)\n"
|
||||
" --workers <num> 工作线程数 (别名: -w)\n"
|
||||
" --config <name> confs 配置名 (默认: db.ini)\n"
|
||||
" --log-level <level> debug|info|warn|error (默认: info)\n"
|
||||
" --instance <name> 实例名 (默认: db2)\n"
|
||||
" --help, -h 显示帮助并退出\n\n"
|
||||
"配置优先级: CLI > 环境变量 > confs > 默认值\n");
|
||||
std::exit(0);
|
||||
}
|
||||
if (arg == "--config" && i + 1 < argc) cfg.confs_config_name = argv[++i];
|
||||
}
|
||||
|
||||
// 1. 从环境变量读取 confs 引导地址
|
||||
if (const char* h = std::getenv("CONFS_HOST")) cfg.confs_host = h;
|
||||
if (const char* p = std::getenv("CONFS_PORT")) cfg.confs_port = safe_stoi(p, "CONFS_PORT");
|
||||
|
||||
// 2. 从 confs 服务拉取 db.ini(retry_on_refused=true,等待 confs 就绪)
|
||||
// 2. 从 confs 服务拉取配置(retry_on_refused=true,等待 confs 就绪)
|
||||
std::string ini_text;
|
||||
try {
|
||||
RpcClient confs_client(cfg.confs_host, cfg.confs_port, "db2",
|
||||
/*timeout=*/5.0, /*retry_on_refused=*/true);
|
||||
json::value data = confs_client.call("get", {{"name", "db.ini"}});
|
||||
json::value data = confs_client.call("get", {{"name", cfg.confs_config_name}});
|
||||
if (data.is_object() && data.as_object().contains("content")) {
|
||||
ini_text = std::string(data.as_object().at("content").as_string());
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::fprintf(stderr, "[db2] Failed to fetch db.ini from confs: %s\n", e.what());
|
||||
std::fprintf(stderr, "[db2] Failed to fetch %s from confs: %s\n",
|
||||
cfg.confs_config_name.c_str(), e.what());
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
@@ -223,9 +250,12 @@ static Config load_config(int argc, char* argv[]) {
|
||||
// 5. 命令行参数(最高优先级)
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string arg = argv[i];
|
||||
if ((arg == "--addr" || arg == "-a") && i + 1 < argc) cfg.listen_host = argv[++i];
|
||||
if ((arg == "--addr" || arg == "-a" || arg == "--host") && i + 1 < argc) cfg.listen_host = argv[++i];
|
||||
else if ((arg == "--port" || arg == "-p") && i + 1 < argc) cfg.listen_port = safe_stoi(argv[++i], "--port");
|
||||
else if ((arg == "--workers" || arg == "-w") && i + 1 < argc) cfg.workers = safe_stoi(argv[++i], "--workers");
|
||||
else if (arg == "--log-level" && i + 1 < argc) cfg.log_level = argv[++i];
|
||||
else if (arg == "--instance" && i + 1 < argc) cfg.instance = argv[++i];
|
||||
// --config 和 --help 已在预扫描阶段处理
|
||||
}
|
||||
|
||||
// 6. 数值配置范围校验(防误配置导致异常行为)
|
||||
@@ -250,6 +280,18 @@ static Config load_config(int argc, char* argv[]) {
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
// log_level 合法性校验
|
||||
{
|
||||
std::string lv = cfg.log_level;
|
||||
std::transform(lv.begin(), lv.end(), lv.begin(), ::tolower);
|
||||
if (lv != "debug" && lv != "info" && lv != "warn" && lv != "error") {
|
||||
std::fprintf(stderr, "[db2] invalid log-level: %s (must be debug|info|warn|error)\n",
|
||||
cfg.log_level.c_str());
|
||||
std::exit(1);
|
||||
}
|
||||
cfg.log_level = lv;
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
@@ -892,6 +934,18 @@ private:
|
||||
void _log_internal(const std::string& level, const std::string& service,
|
||||
std::string message, const std::string& trace_id,
|
||||
const std::string& extra) {
|
||||
// 日志级别过滤
|
||||
static const std::unordered_map<std::string, int> kLogLevels = {
|
||||
{"debug", 0}, {"info", 1}, {"warn", 2}, {"warning", 2}, {"error", 3},
|
||||
{"DEBUG", 0}, {"INFO", 1}, {"WARN", 2}, {"WARNING", 2}, {"ERROR", 3}
|
||||
};
|
||||
auto it_cfg = kLogLevels.find(_cfg.log_level);
|
||||
auto it_msg = kLogLevels.find(level);
|
||||
if (it_cfg != kLogLevels.end() && it_msg != kLogLevels.end()
|
||||
&& it_msg->second < it_cfg->second) {
|
||||
return; // 低于设定级别,丢弃
|
||||
}
|
||||
|
||||
if (message.size() > kMaxMessage) message.resize(kMaxMessage);
|
||||
std::string ex = extra;
|
||||
if (ex.size() > kMaxExtra) ex.resize(kMaxExtra);
|
||||
@@ -992,9 +1046,11 @@ int main(int argc, char* argv[]) {
|
||||
try {
|
||||
Config cfg = load_config(argc, argv);
|
||||
|
||||
std::printf("[db2] listen=%s:%d workers=%d db=%s\n",
|
||||
std::printf("[db2] listen=%s:%d workers=%d db=%s instance=%s log-level=%s config=%s\n",
|
||||
cfg.listen_host.c_str(), cfg.listen_port,
|
||||
cfg.workers, cfg.db_url.c_str());
|
||||
cfg.workers, cfg.db_url.c_str(),
|
||||
cfg.instance.c_str(), cfg.log_level.c_str(),
|
||||
cfg.confs_config_name.c_str());
|
||||
std::fflush(stdout);
|
||||
|
||||
DB2Server server(cfg);
|
||||
|
||||
+16
-22
@@ -33,30 +33,24 @@ dnfs2 :8006 ──pymsg──► db2 :8005 (execute 路由) ──► SQLite
|
||||
|
||||
## 配置
|
||||
|
||||
从 confs 拉取 `dnfs.ini`,环境变量覆盖,命令行参数最高优先级。
|
||||
纯 CLI + 环境变量 + 默认值,不依赖 confs。
|
||||
|
||||
```ini
|
||||
[server]
|
||||
host = 127.0.0.1
|
||||
port = 8006
|
||||
| 配置项 | CLI 参数 | 环境变量 | 默认值 |
|
||||
|--------|---------|---------|--------|
|
||||
| 绑定地址 | `--host` / `--addr` / `-a` | `LISTEN_ADDR` | 127.0.0.1 |
|
||||
| 监听端口 | `--port` / `-p` | `LISTEN_PORT` | 8006 |
|
||||
| 工作线程 | `--threads` / `-t` | — | 4 |
|
||||
| 数据库地址 | `--db-host` | `DB_HOST` | 127.0.0.1 |
|
||||
| 数据库端口 | `--db-port` | `DB_PORT` | 8005 |
|
||||
| 数据库超时 | `--db-timeout` | — | 10 |
|
||||
| AI 审核URL | `--review-url` | `REVIEW_URL` | http://127.0.0.1:8007/review |
|
||||
| AI 审核超时 | `--review-timeout` | — | 10 |
|
||||
| 网关验签URL | `--gateway-url` | `GATEWAY_URL` | http://127.0.0.1:8443/whoami |
|
||||
| 网关超时 | `--gateway-timeout` | — | 5 |
|
||||
| 日志级别 | `--log-level` | — | info |
|
||||
| 实例名 | `--instance` | — | dnfs2 |
|
||||
|
||||
[database]
|
||||
host = 127.0.0.1
|
||||
port = 8005
|
||||
timeout = 10
|
||||
|
||||
[ai_review]
|
||||
url = http://127.0.0.1:8007/review
|
||||
timeout = 10
|
||||
|
||||
[gateway]
|
||||
url = http://127.0.0.1:8443/whoami
|
||||
timeout = 5
|
||||
```
|
||||
|
||||
环境变量: `CONFS_HOST`, `CONFS_PORT`, `LISTEN_ADDR`, `LISTEN_PORT`, `DB_HOST`, `DB_PORT`, `REVIEW_URL`, `GATEWAY_URL`
|
||||
|
||||
命令行: `--addr`, `--port`, `--threads`
|
||||
优先级: CLI > 环境变量 > 默认值
|
||||
|
||||
## 构建
|
||||
|
||||
|
||||
+71
-115
@@ -9,7 +9,7 @@
|
||||
* 安全修复(相比 Python dnfs):
|
||||
* P0-4 统一封装 _call_db() 检查 success 后再访问 data
|
||||
* P1-6 server.setMaxBodySize(10MB) 限制请求体大小
|
||||
* P1-7 日志客户端复用主配置(confs 拉取的 db host/port)
|
||||
* P1-7 日志客户端复用主配置(CLI/ENV 的 db host/port)
|
||||
* P1-8 update_note 改 pid≠0 时校验父节点存在
|
||||
* P1-9 RpcClient 启用 retry_on_refused
|
||||
* P1-11 note_id 显式 as_int64() + 类型校验
|
||||
@@ -34,14 +34,11 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
@@ -106,44 +103,6 @@ static int safe_stoi(const std::string& s, const char* field_name) {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// INI 解析(白名单模式,与 db2 一致)
|
||||
// ============================================================================
|
||||
|
||||
using IniMap = std::map<std::string, std::map<std::string, std::string>>;
|
||||
|
||||
static IniMap parse_ini(const std::string& text) {
|
||||
IniMap result;
|
||||
std::string section;
|
||||
std::istringstream ss(text);
|
||||
std::string line;
|
||||
while (std::getline(ss, line)) {
|
||||
auto b = line.find_first_not_of(" \t\r\n");
|
||||
if (b == std::string::npos) continue;
|
||||
line = line.substr(b);
|
||||
auto e = line.find_last_not_of(" \t\r\n");
|
||||
if (e != std::string::npos) line = line.substr(0, e + 1);
|
||||
|
||||
if (line.empty() || line[0] == '#' || line[0] == ';') continue;
|
||||
if (line.front() == '[' && line.back() == ']') {
|
||||
section = line.substr(1, line.size() - 2);
|
||||
continue;
|
||||
}
|
||||
auto eq = line.find('=');
|
||||
if (eq == std::string::npos || section.empty()) continue;
|
||||
std::string key = line.substr(0, eq);
|
||||
std::string val = line.substr(eq + 1);
|
||||
auto trim = [](std::string& s) {
|
||||
auto bb = s.find_first_not_of(" \t");
|
||||
auto ee = s.find_last_not_of(" \t");
|
||||
s = (bb == std::string::npos) ? "" : s.substr(bb, ee - bb + 1);
|
||||
};
|
||||
trim(key); trim(val);
|
||||
result[section][key] = val;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 配置
|
||||
// ============================================================================
|
||||
@@ -163,80 +122,41 @@ struct Config {
|
||||
std::string gateway_url = "http://127.0.0.1:8443/whoami";
|
||||
double gateway_timeout = 5.0;
|
||||
|
||||
// confs 引导
|
||||
std::string confs_host = "127.0.0.1";
|
||||
int confs_port = 8010;
|
||||
// CLI 标准化参数
|
||||
std::string instance = "dnfs2";
|
||||
std::string log_level = "info"; // debug|info|warn|error
|
||||
};
|
||||
|
||||
static Config load_config(int argc, char* argv[]) {
|
||||
Config cfg;
|
||||
|
||||
// 1. confs 引导地址
|
||||
if (const char* h = std::getenv("CONFS_HOST")) cfg.confs_host = h;
|
||||
if (const char* p = std::getenv("CONFS_PORT")) {
|
||||
cfg.confs_port = safe_stoi(p, "CONFS_PORT");
|
||||
if (cfg.confs_port < 1 || cfg.confs_port > 65535) {
|
||||
std::fprintf(stderr, "[dnfs2] invalid CONFS_PORT: %d (must be 1-65535)\n", cfg.confs_port);
|
||||
std::exit(1);
|
||||
// 0. 预扫描 --help/-h(在正式解析前处理)
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string arg = argv[i];
|
||||
if (arg == "--help" || arg == "-h") {
|
||||
std::printf(
|
||||
"dnfs2 — C++20 树状网络文件系统\n\n"
|
||||
"用法: dnfs2 [选项]\n\n"
|
||||
"选项:\n"
|
||||
" --host <addr> 绑定地址(别名: --addr/-a),默认 127.0.0.1\n"
|
||||
" --port <num> 监听端口(别名: -p),默认 8006\n"
|
||||
" --threads <num> 工作线程数(别名: -t),默认 4\n"
|
||||
" --db-host <addr> 数据库地址,默认 127.0.0.1\n"
|
||||
" --db-port <num> 数据库端口,默认 8005\n"
|
||||
" --db-timeout <sec> 数据库超时秒数,默认 10\n"
|
||||
" --review-url <url> AI 审核服务 URL,默认 http://127.0.0.1:8007/review\n"
|
||||
" --review-timeout <sec> AI 审核超时秒数,默认 10\n"
|
||||
" --gateway-url <url> 网关验签 URL,默认 http://127.0.0.1:8443/whoami\n"
|
||||
" --gateway-timeout <sec> 网关超时秒数,默认 5\n"
|
||||
" --log-level <level> debug|info|warn|error,默认 info\n"
|
||||
" --instance <name> 实例名(注册中心用),默认 dnfs2\n"
|
||||
" --help, -h 显示此帮助并退出\n\n"
|
||||
"配置优先级: CLI > 环境变量 > 默认值\n");
|
||||
std::exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 从 confs 拉取 dnfs.ini(retry_on_refused=true,等待 confs 就绪)
|
||||
std::string ini_text;
|
||||
try {
|
||||
RpcClient confs_client(cfg.confs_host, cfg.confs_port, "dnfs2",
|
||||
/*timeout=*/5.0, /*retry_on_refused=*/true);
|
||||
json::value data = confs_client.call("get", {{"name", "dnfs.ini"}});
|
||||
if (data.is_object() && data.as_object().contains("content")) {
|
||||
ini_text = std::string(data.as_object().at("content").as_string());
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::fprintf(stderr, "[dnfs2] 从 confs 获取 dnfs.ini 失败: %s\n", e.what());
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
// 3. 解析 INI
|
||||
IniMap ini = parse_ini(ini_text);
|
||||
|
||||
// 校验必需配置项
|
||||
static const std::pair<const char*, const char*> required_keys[] = {
|
||||
{"server", "host"},
|
||||
{"server", "port"},
|
||||
{"database", "host"},
|
||||
{"database", "port"},
|
||||
{"database", "timeout"},
|
||||
{"ai_review", "url"},
|
||||
{"ai_review", "timeout"},
|
||||
{"gateway", "url"},
|
||||
{"gateway", "timeout"},
|
||||
};
|
||||
for (auto& [sec, key] : required_keys) {
|
||||
auto si = ini.find(sec);
|
||||
if (si == ini.end() || si->second.find(key) == si->second.end()) {
|
||||
std::fprintf(stderr, "[dnfs2] 缺少必需配置: [%s] %s\n", sec, key);
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
auto get = [&](const std::string& sec, const std::string& key,
|
||||
const std::string& def = "") -> std::string {
|
||||
auto si = ini.find(sec);
|
||||
if (si == ini.end()) return def;
|
||||
auto ki = si->second.find(key);
|
||||
return (ki == si->second.end()) ? def : ki->second;
|
||||
};
|
||||
|
||||
cfg.listen_host = get("server", "host", cfg.listen_host);
|
||||
cfg.listen_port = safe_stoi(get("server", "port", std::to_string(cfg.listen_port)), "[server] port");
|
||||
cfg.db_host = get("database", "host", cfg.db_host);
|
||||
cfg.db_port = safe_stoi(get("database", "port", std::to_string(cfg.db_port)), "[database] port");
|
||||
cfg.db_timeout = std::stod(get("database", "timeout", std::to_string(cfg.db_timeout)));
|
||||
cfg.review_url = get("ai_review", "url", cfg.review_url);
|
||||
cfg.review_timeout = std::stod(get("ai_review", "timeout", std::to_string(cfg.review_timeout)));
|
||||
cfg.gateway_url = get("gateway", "url", cfg.gateway_url);
|
||||
cfg.gateway_timeout = std::stod(get("gateway", "timeout", std::to_string(cfg.gateway_timeout)));
|
||||
|
||||
// 4. 环境变量覆盖(Docker 兼容)
|
||||
// 1. 环境变量覆盖(Docker 兼容)
|
||||
if (const char* v = std::getenv("LISTEN_ADDR")) cfg.listen_host = v;
|
||||
if (const char* v = std::getenv("LISTEN_PORT")) cfg.listen_port = safe_stoi(v, "LISTEN_PORT");
|
||||
if (const char* v = std::getenv("DB_HOST")) cfg.db_host = v;
|
||||
@@ -244,15 +164,24 @@ static Config load_config(int argc, char* argv[]) {
|
||||
if (const char* v = std::getenv("REVIEW_URL")) cfg.review_url = v;
|
||||
if (const char* v = std::getenv("GATEWAY_URL")) cfg.gateway_url = v;
|
||||
|
||||
// 5. 命令行参数(最高优先级)
|
||||
// 2. 命令行参数(最高优先级)
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string arg = argv[i];
|
||||
if ((arg == "--addr" || arg == "-a") && i + 1 < argc) cfg.listen_host = argv[++i];
|
||||
if ((arg == "--addr" || arg == "-a" || arg == "--host") && i + 1 < argc) cfg.listen_host = argv[++i];
|
||||
else if ((arg == "--port" || arg == "-p") && i + 1 < argc) cfg.listen_port = safe_stoi(argv[++i], "--port");
|
||||
else if ((arg == "--threads" || arg == "-t") && i + 1 < argc) cfg.threads = safe_stoi(argv[++i], "--threads");
|
||||
else if (arg == "--db-host" && i + 1 < argc) cfg.db_host = argv[++i];
|
||||
else if (arg == "--db-port" && i + 1 < argc) cfg.db_port = safe_stoi(argv[++i], "--db-port");
|
||||
else if (arg == "--db-timeout" && i + 1 < argc) cfg.db_timeout = std::stod(argv[++i]);
|
||||
else if (arg == "--review-url" && i + 1 < argc) cfg.review_url = argv[++i];
|
||||
else if (arg == "--review-timeout" && i + 1 < argc) cfg.review_timeout = std::stod(argv[++i]);
|
||||
else if (arg == "--gateway-url" && i + 1 < argc) cfg.gateway_url = argv[++i];
|
||||
else if (arg == "--gateway-timeout" && i + 1 < argc) cfg.gateway_timeout = std::stod(argv[++i]);
|
||||
else if (arg == "--log-level" && i + 1 < argc) cfg.log_level = argv[++i];
|
||||
else if (arg == "--instance" && i + 1 < argc) cfg.instance = argv[++i];
|
||||
}
|
||||
|
||||
// 6. 数值范围校验
|
||||
// 3. 数值范围校验
|
||||
if (cfg.listen_port < 1 || cfg.listen_port > 65535) {
|
||||
std::fprintf(stderr, "[dnfs2] invalid listen_port: %d (must be 1-65535)\n", cfg.listen_port);
|
||||
std::exit(1);
|
||||
@@ -278,6 +207,18 @@ static Config load_config(int argc, char* argv[]) {
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
// log_level 合法性校验
|
||||
{
|
||||
std::string lv = cfg.log_level;
|
||||
if (lv == "warning") lv = "warn";
|
||||
if (lv != "debug" && lv != "info" && lv != "warn" && lv != "error") {
|
||||
std::fprintf(stderr, "[dnfs2] invalid log_level: %s (must be debug|info|warn|error)\n",
|
||||
cfg.log_level.c_str());
|
||||
std::exit(1);
|
||||
}
|
||||
cfg.log_level = lv;
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
@@ -499,8 +440,9 @@ public:
|
||||
);
|
||||
_log_queue->start();
|
||||
|
||||
std::printf("[dnfs2] 配置来源: confs (dnfs.ini @ %s:%d)\n",
|
||||
_cfg.confs_host.c_str(), _cfg.confs_port);
|
||||
std::printf("[dnfs2] 配置来源: CLI + ENV + 默认值\n");
|
||||
std::printf("[dnfs2] instance=%s log-level=%s\n",
|
||||
_cfg.instance.c_str(), _cfg.log_level.c_str());
|
||||
std::printf("[dnfs2] Token 验签: gateway %s\n", _cfg.gateway_url.c_str());
|
||||
}
|
||||
|
||||
@@ -567,6 +509,19 @@ private:
|
||||
void _log(const std::string& level, const std::string& service,
|
||||
const std::string& message, const std::string& trace_id = "",
|
||||
const std::string& extra = "") {
|
||||
// 日志级别过滤
|
||||
static const std::unordered_map<std::string, int> kLogLevels = {
|
||||
{"debug", 0}, {"DEBUG", 0},
|
||||
{"info", 1}, {"INFO", 1},
|
||||
{"warn", 2}, {"WARN", 2}, {"warning", 2}, {"WARNING", 2},
|
||||
{"error", 3}, {"ERROR", 3}
|
||||
};
|
||||
auto it_lvl = kLogLevels.find(level);
|
||||
int msg_lvl = (it_lvl != kLogLevels.end()) ? it_lvl->second : 1;
|
||||
auto it_cfg = kLogLevels.find(_cfg.log_level);
|
||||
int cfg_lvl = (it_cfg != kLogLevels.end()) ? it_cfg->second : 1;
|
||||
if (msg_lvl < cfg_lvl) return;
|
||||
|
||||
LogEntry e;
|
||||
e.ts = utc_now();
|
||||
e.service = service;
|
||||
@@ -1182,8 +1137,9 @@ int main(int argc, char* argv[]) {
|
||||
try {
|
||||
Config cfg = load_config(argc, argv);
|
||||
|
||||
std::printf("[dnfs2] listen=%s:%d threads=%d\n",
|
||||
cfg.listen_host.c_str(), cfg.listen_port, cfg.threads);
|
||||
std::printf("[dnfs2] listen=%s:%d threads=%d instance=%s\n",
|
||||
cfg.listen_host.c_str(), cfg.listen_port, cfg.threads,
|
||||
cfg.instance.c_str());
|
||||
std::printf("[dnfs2] db=%s:%d ai_review=%s gateway=%s\n",
|
||||
cfg.db_host.c_str(), cfg.db_port,
|
||||
cfg.review_url.c_str(), cfg.gateway_url.c_str());
|
||||
|
||||
@@ -0,0 +1,806 @@
|
||||
# Core 启动配置 YAML 化与 CLI 参数标准化 · 开发文档
|
||||
|
||||
> 文档类型:功能开发计划
|
||||
> 模块:core(PMC / Nidus)+ 全部子服务
|
||||
> 作者:自动生成
|
||||
> 日期:2026-08-10
|
||||
> 前置关系:本文档是 `core_service_registry_dev_doc.md` P7 阶段(services.yaml)的前置拆分,聚焦"配置格式迁移 + 参数透传 + CLI 标准化"三步基础设施改造
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
### 1.1 背景与动机
|
||||
|
||||
当前 core 通过 `init.txt`(纯命令列表)拉起 11 个子服务,存在以下问题:
|
||||
|
||||
| 现状 | 问题 |
|
||||
|------|------|
|
||||
| `init.txt` 每行一条 bash 命令,无结构化字段 | 无法为每个服务指定命令行参数、环境变量、依赖关系 |
|
||||
| 5/11 个 `_run.sh` 不转发 `"$@"` | core 即便想传参也传不进去 |
|
||||
| `sleep 2` 等依赖等待逻辑散落在各 `_run.sh` 中 | 无统一管理,调整需逐个脚本修改 |
|
||||
| waf 的 `-c config.yaml` 硬编码在 `_run.sh` | 参数不可外部覆盖 |
|
||||
| 各服务 CLI 接口不统一 | ca/gateway 仅有 `--help/--version`,waf 有 `-c/-list/-test/-check`,其余 8 个服务零 CLI 支持 |
|
||||
|
||||
**本方案目标**:通过三步改造,让 core 能够以结构化 YAML 声明每个服务的启动命令和参数,参数能透传到各服务二进制/脚本,并建立统一的 CLI 参数规范——为注册中心 `services.yaml` + `depends_on` DAG 拓扑排序打下基础。
|
||||
|
||||
### 1.2 设计目标
|
||||
|
||||
1. **结构化启动声明**:`init.txt` → `services.yaml`,每个服务可声明 `cmd`、`args`、`env`、`depends_on`、`sleep_before`
|
||||
2. **参数透传**:全部 11 个 `_run.sh` 统一转发 `"$@"`,消除硬编码参数
|
||||
3. **CLI 标准化**:定义通用参数约定(`--host`/`--port`/`--config`/`--log-level`/`--instance`),各服务逐步适配
|
||||
4. **向后兼容**:`services.yaml` 不存在时回退 `init.txt`,现有部署零中断
|
||||
5. **yaml-cpp 引入**:vendored 到 `core/third_party/yaml-cpp/`,不依赖系统包管理器
|
||||
|
||||
### 1.3 与注册中心计划的关系
|
||||
|
||||
```
|
||||
本文档(三步改造) 注册中心计划(core_service_registry_dev_doc.md)
|
||||
┌─────────────────────┐ ┌─────────────────────────────┐
|
||||
│ Step 1: services.yaml│ ──────▶ │ P7: services.yaml DAG 排序 │
|
||||
│ Step 2: _run.sh 透传 │ ──────▶ │ P2: init 解析后自动填充注册表 │
|
||||
│ Step 3: CLI 标准化 │ ──────▶ │ P5: RegistrationClient SDK │
|
||||
└─────────────────────┘ └─────────────────────────────┘
|
||||
```
|
||||
|
||||
本方案是注册中心的**基础设施前置**——做完这三步,P2/P5/P7 的工作量直接减半。
|
||||
|
||||
---
|
||||
|
||||
## 2. 现状分析
|
||||
|
||||
### 2.1 init.txt 当前内容
|
||||
|
||||
```
|
||||
bash /opt/micro_servers/db2/_run.sh
|
||||
bash /opt/micro_servers/ai_review/_run.sh
|
||||
bash /opt/micro_servers/dnfs2/_run.sh
|
||||
bash /opt/micro_servers/smtp/_run.sh
|
||||
bash /opt/micro_servers/fsys/_run.sh
|
||||
bash /opt/micro_servers/waf/_run.sh
|
||||
bash /opt/micro_servers/ca/_run.sh
|
||||
bash /opt/micro_servers/gateway/_run.sh
|
||||
bash /opt/micro_servers/captcha2/_run.sh
|
||||
bash /opt/micro_servers/aibox/_run.sh
|
||||
bash /opt/micro_servers/redirect80/_run.sh
|
||||
```
|
||||
|
||||
纯命令列表,无参数、无元数据、无依赖声明。
|
||||
|
||||
### 2.2 各服务 _run.sh 现状
|
||||
|
||||
| 服务 | 语言 | `"$@"` 转发 | 硬编码参数 | 特殊处理 | sleep 等待 |
|
||||
|------|------|-------------|-----------|----------|------------|
|
||||
| db2 | C++ | ✅ `"$@"` | — | `set -e` | — |
|
||||
| dnfs2 | C++ | ✅ `"$@"` | — | `set -e` | — |
|
||||
| fsys | C++ | ✅ `"$@"` | — | `set -e` | — |
|
||||
| ca | C++ | ✅ `"$@"` | — | — | `sleep 2` |
|
||||
| gateway | C++ | ✅ `"$@"` | — | — | `sleep 2` |
|
||||
| waf | Go | ❌ | `-c config.yaml` | — | — |
|
||||
| captcha2 | C++ | ❌ | — | — | `sleep 1` |
|
||||
| aibox | Python | ⚠️ `$@`(未加引号) | — | `_setenv.sh` + venv | — |
|
||||
| ai_review | Python | ❌ | — | venv | — |
|
||||
| smtp | Python | ❌ | — | venv | `sleep 2` |
|
||||
| redirect80 | Python | ❌ | — | setcap + venv | — |
|
||||
|
||||
**统计**:5 个已转发、1 个半转发(未加引号)、5 个未转发。3 个脚本含 `sleep` 硬编码。
|
||||
|
||||
### 2.3 各服务 CLI 参数支持现状
|
||||
|
||||
| 服务 | 语言 | CLI 框架 | 实际参数 | 配置来源 |
|
||||
|------|------|----------|----------|----------|
|
||||
| db2 | C++ | 无 | 无 | 硬编码常量 |
|
||||
| dnfs2 | C++ | 无 | 无 | 硬编码 + confs |
|
||||
| fsys | C++ | 无 | 无 | 硬编码 |
|
||||
| captcha2 | C++ | 无 | 无 | 硬编码 |
|
||||
| ca | C++ | `boost::program_options` | `--help` `--version`(仅此) | confs (ca.ini) + 环境变量 |
|
||||
| gateway | C++ | `boost::program_options` | `--help` `--version`(仅此) | confs (gateway.ini) + 环境变量 |
|
||||
| waf | Go | `flag` | `-c` `-list` `-test` `-check` | YAML 配置文件 |
|
||||
| aibox | Python | `argparse`(已 import 未使用) | 无 | `_setenv.sh` + 硬编码 |
|
||||
| ai_review | Python | 无 | 无 | 硬编码 |
|
||||
| smtp | Python | 无 | 无 | 硬编码 |
|
||||
| redirect80 | Python | 无 | 无 | 硬编码 |
|
||||
|
||||
**关键发现**:
|
||||
- ca 和 gateway 已引入 `boost::program_options` 但只实现了 `--help`/`--version`,实际配置全走 confs + 环境变量
|
||||
- aibox 已 `import argparse` 但从未调用,是死代码
|
||||
- waf 是唯一有完整 CLI 接口的服务(Go `flag` 包)
|
||||
- 8/11 个服务零 CLI 参数支持
|
||||
|
||||
### 2.4 配置加载模式分类
|
||||
|
||||
当前各服务的配置获取有三种模式:
|
||||
|
||||
| 模式 | 服务 | 说明 |
|
||||
|------|------|------|
|
||||
| **confs + 环境变量** | ca, gateway, dnfs2 | 从 confs 服务拉取 ini 配置,环境变量可覆盖;`CONFS_HOST`/`CONFS_PORT` 引导 |
|
||||
| **本地配置文件** | waf | `-c config.yaml` 指定 YAML 配置 |
|
||||
| **硬编码** | db2, fsys, captcha2, aibox, ai_review, smtp, redirect80 | 常量直接写在源码中 |
|
||||
|
||||
CLI 标准化后,三种模式可统一为:**CLI 参数(最高优先级)> 环境变量 > confs/配置文件 > 硬编码默认值**。
|
||||
|
||||
---
|
||||
|
||||
## 3. 三步改造方案
|
||||
|
||||
### 3.1 总览
|
||||
|
||||
```
|
||||
Step 1: init.txt → services.yaml Step 2: _run.sh 参数透传 Step 3: CLI 参数标准化
|
||||
┌──────────────────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐
|
||||
│ • 引入 yaml-cpp (vendored) │ │ • 11 个 _run.sh 统一 │ │ • 定义通用参数约定 │
|
||||
│ • 设计 services.yaml 格式 │ ──▶ │ 末尾 exec 加 "$@" │ ──▶ │ • _run.sh 注入默认值 │
|
||||
│ • core 解析 YAML 并启动 │ │ • 移除硬编码参数 │ │ • 各服务逐步适配 │
|
||||
│ • 回退兼容 init.txt │ │ • sleep 移至 YAML │ │ • 环境变量桥接 │
|
||||
└──────────────────────────────┘ └──────────────────────┘ └──────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Step 1:init.txt → services.yaml
|
||||
|
||||
### 4.1 yaml-cpp 引入
|
||||
|
||||
采用系统包安装方式(已写入 `_setup.deb.sh` 和 `_setup.rhel.sh`):
|
||||
|
||||
| 发行版 | 包名 | 安装命令 |
|
||||
|--------|------|----------|
|
||||
| Debian/Ubuntu | `libyaml-cpp-dev` | `apt install libyaml-cpp-dev` |
|
||||
| RHEL/CentOS/Rocky | `yaml-cpp-devel` | `dnf install yaml-cpp-devel` |
|
||||
|
||||
**Makefile 集成**(已完成):
|
||||
|
||||
```makefile
|
||||
# 链接 yaml-cpp 系统库
|
||||
LIBS := ... -lyaml-cpp
|
||||
|
||||
# InitParser 编译目标
|
||||
$(BUILD_DIR)/InitParser.o: src/registry/InitParser.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
# 链接时追加
|
||||
$(APP_NAME).out: ... $(BUILD_DIR)/InitParser.o
|
||||
```
|
||||
|
||||
### 4.2 services.yaml 格式设计
|
||||
|
||||
```yaml
|
||||
# core/services.yaml
|
||||
# 声明式服务启动配置(替代 init.txt)
|
||||
# 如果本文件存在则优先使用,否则回退 init.txt
|
||||
|
||||
services:
|
||||
# --- 数据层 ---
|
||||
- name: db2
|
||||
cmd: bash /opt/micro_servers/db2/_run.sh
|
||||
args: []
|
||||
env: {}
|
||||
depends_on: []
|
||||
sleep_before: 0
|
||||
|
||||
# --- AI 服务 ---
|
||||
- name: ai_review
|
||||
cmd: bash /opt/micro_servers/ai_review/_run.sh
|
||||
args: ["--port", "8007", "--log-level", "info"]
|
||||
env:
|
||||
PYTHONUNBUFFERED: "1"
|
||||
depends_on: [db2]
|
||||
sleep_before: 2
|
||||
|
||||
# --- 文件服务 ---
|
||||
- name: dnfs2
|
||||
cmd: bash /opt/micro_servers/dnfs2/_run.sh
|
||||
args: []
|
||||
env: {}
|
||||
depends_on: [db2, ai_review]
|
||||
sleep_before: 0
|
||||
|
||||
# --- 邮件服务 ---
|
||||
- name: smtp
|
||||
cmd: bash /opt/micro_servers/smtp/_run.sh
|
||||
args: []
|
||||
env: {}
|
||||
depends_on: []
|
||||
sleep_before: 2
|
||||
|
||||
# --- 前端静态资源 ---
|
||||
- name: fsys
|
||||
cmd: bash /opt/micro_servers/fsys/_run.sh
|
||||
args: []
|
||||
env: {}
|
||||
depends_on: []
|
||||
sleep_before: 0
|
||||
|
||||
# --- 安全链路 ---
|
||||
- name: waf
|
||||
cmd: bash /opt/micro_servers/waf/_run.sh
|
||||
args: ["-c", "config.yaml"]
|
||||
env: {}
|
||||
depends_on: [ca, gateway]
|
||||
sleep_before: 0
|
||||
|
||||
- name: ca
|
||||
cmd: bash /opt/micro_servers/ca/_run.sh
|
||||
args: []
|
||||
env: {}
|
||||
depends_on: [db2]
|
||||
sleep_before: 2
|
||||
|
||||
- name: gateway
|
||||
cmd: bash /opt/micro_servers/gateway/_run.sh
|
||||
args: []
|
||||
env: {}
|
||||
depends_on: [db2]
|
||||
sleep_before: 2
|
||||
|
||||
# --- 验证码 ---
|
||||
- name: captcha2
|
||||
cmd: bash /opt/micro_servers/captcha2/_run.sh
|
||||
args: []
|
||||
env: {}
|
||||
depends_on: []
|
||||
sleep_before: 1
|
||||
|
||||
# --- AI 工具箱 ---
|
||||
- name: aibox
|
||||
cmd: bash /opt/micro_servers/aibox/_run.sh
|
||||
args: []
|
||||
env:
|
||||
PATH: "${PATH}:~/.aibox/skills"
|
||||
depends_on: []
|
||||
sleep_before: 0
|
||||
|
||||
# --- 端口重定向 ---
|
||||
- name: redirect80
|
||||
cmd: bash /opt/micro_servers/redirect80/_run.sh
|
||||
args: []
|
||||
env: {}
|
||||
depends_on: [waf]
|
||||
sleep_before: 0
|
||||
```
|
||||
|
||||
**字段说明**:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `name` | string | 是 | 服务唯一标识,用于日志和未来注册表 |
|
||||
| `cmd` | string | 是 | 启动命令(与 init.txt 每行格式一致) |
|
||||
| `args` | string[] | 否 | 传递给 `_run.sh` 的命令行参数列表 |
|
||||
| `env` | map<string,string> | 否 | 启动前设置的环境变量 |
|
||||
| `depends_on` | string[] | 否 | 依赖的服务名列表(未来 DAG 排序用,当前仅记录) |
|
||||
| `sleep_before` | int | 否 | 启动前等待秒数(替代散落在 _run.sh 中的 sleep) |
|
||||
|
||||
### 4.3 core 解析逻辑
|
||||
|
||||
新增 `include/registry/InitParser.hh` + `src/registry/InitParser.cpp`:
|
||||
|
||||
```cpp
|
||||
// include/registry/InitParser.hh
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace qing::registry {
|
||||
|
||||
struct ServiceConfig {
|
||||
std::string name;
|
||||
std::string cmd;
|
||||
std::vector<std::string> args;
|
||||
std::map<std::string, std::string> env;
|
||||
std::vector<std::string> depends_on;
|
||||
int sleep_before = 0;
|
||||
};
|
||||
|
||||
class InitParser {
|
||||
public:
|
||||
// 优先读 services.yaml,不存在则回退 init.txt
|
||||
static std::vector<ServiceConfig> load(const std::string& conf_dir);
|
||||
|
||||
// 解析 services.yaml
|
||||
static std::vector<ServiceConfig> parseYaml(const std::string& path);
|
||||
|
||||
// 解析 init.txt(向后兼容)
|
||||
static std::vector<ServiceConfig> parseTxt(const std::string& path);
|
||||
|
||||
// 将 ServiceConfig 拼成完整命令字符串(cmd + " " + args.join(" "))
|
||||
static std::string toCommand(const ServiceConfig& svc);
|
||||
};
|
||||
|
||||
} // namespace qing::registry
|
||||
```
|
||||
|
||||
**解析流程**:
|
||||
|
||||
```
|
||||
load(conf_dir):
|
||||
1. 检查 conf_dir/services.yaml 是否存在
|
||||
2. 存在 → parseYaml() → 返回 ServiceConfig 列表
|
||||
3. 不存在 → 检查 conf_dir/init.txt
|
||||
4. 存在 → parseTxt() → 每行作为 ServiceConfig{name="", cmd=line, args=[]}
|
||||
5. 都不存在 → 报错退出
|
||||
```
|
||||
|
||||
**parseYaml 核心逻辑**(使用 yaml-cpp):
|
||||
|
||||
```cpp
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
std::vector<ServiceConfig> InitParser::parseYaml(const std::string& path) {
|
||||
YAML::Node root = YAML::LoadFile(path);
|
||||
std::vector<ServiceConfig> result;
|
||||
|
||||
for (const auto& node : root["services"]) {
|
||||
ServiceConfig svc;
|
||||
svc.name = node["name"].as<std::string>("");
|
||||
svc.cmd = node["cmd"].as<std::string>();
|
||||
|
||||
if (node["args"])
|
||||
for (const auto& a : node["args"])
|
||||
svc.args.push_back(a.as<std::string>());
|
||||
|
||||
if (node["env"])
|
||||
for (const auto& kv : node["env"])
|
||||
svc.env[kv.first.as<std::string>()] = kv.second.as<std::string>();
|
||||
|
||||
if (node["depends_on"])
|
||||
for (const auto& d : node["depends_on"])
|
||||
svc.depends_on.push_back(d.as<std::string>());
|
||||
|
||||
if (node["sleep_before"])
|
||||
svc.sleep_before = node["sleep_before"].as<int>(0);
|
||||
|
||||
result.push_back(std::move(svc));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
**启动执行逻辑**(改动 `pmc_mtd::parse_self_init_list`):
|
||||
|
||||
```cpp
|
||||
// pmc_mtd.cpp 中改造
|
||||
void pmc_mtd::init_from_config(const std::string& conf_dir) {
|
||||
auto services = InitParser::load(conf_dir);
|
||||
|
||||
for (const auto& svc : services) {
|
||||
// 1. sleep_before 等待
|
||||
if (svc.sleep_before > 0)
|
||||
std::this_thread::sleep_for(std::chrono::seconds(svc.sleep_before));
|
||||
|
||||
// 2. 设置环境变量
|
||||
for (const auto& [k, v] : svc.env)
|
||||
setenv(k.c_str(), v.c_str(), 1);
|
||||
|
||||
// 3. 拼接完整命令并启动
|
||||
std::string full_cmd = InitParser::toCommand(svc);
|
||||
// full_cmd = "bash /opt/micro_servers/db2/_run.sh --port 8007 --log-level info"
|
||||
ppool.crtp(full_cmd);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 向后兼容策略
|
||||
|
||||
```
|
||||
services.yaml 存在?
|
||||
│
|
||||
┌─── 是 ──┤── 否 ───┐
|
||||
▼ ▼
|
||||
parseYaml() init.txt 存在?
|
||||
│ │
|
||||
│ ┌── 是 ──┤── 否 ───┐
|
||||
│ ▼ ▼
|
||||
│ parseTxt() 报错退出
|
||||
│ │
|
||||
└─────┬─────┘
|
||||
▼
|
||||
ServiceConfig[]
|
||||
逐个启动
|
||||
```
|
||||
|
||||
- `init.txt` 模式下,每行解析为 `{name="", cmd=line, args=[], sleep_before=0}`,行为与当前完全一致
|
||||
- `services.yaml` 模式下,享受参数透传和 sleep 管理能力
|
||||
- Makefile 的 `CONF_NAME` 可逐步从 `init.txt` 切换为 `services.yaml`
|
||||
|
||||
---
|
||||
|
||||
## 5. Step 2:_run.sh 参数透传
|
||||
|
||||
### 5.1 改造规则
|
||||
|
||||
每个 `_run.sh` 的末尾 `exec` 行必须带 `"$@"`,同时移除硬编码参数和散落的 `sleep`。
|
||||
|
||||
**改造模板**:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# <service>/_run.sh — 标准化启动脚本
|
||||
cd "$(dirname "$0")" || exit
|
||||
|
||||
# --- 服务特有前置(venv / setcap 等)保持不变 ---
|
||||
|
||||
# 启动:"$@" 透传 core/services.yaml 传入的参数
|
||||
exec <binary_or_python> "$@"
|
||||
```
|
||||
|
||||
### 5.2 逐服务改造清单
|
||||
|
||||
| 服务 | 当前 exec 行 | 改造后 | 额外改动 |
|
||||
|------|-------------|--------|----------|
|
||||
| db2 | `exec ./build/db2 "$@"` | ✅ 无需改 | — |
|
||||
| dnfs2 | `exec ./build/dnfs2 "$@"` | ✅ 无需改 | — |
|
||||
| fsys | `exec ./build/fsys "$@"` | ✅ 无需改 | — |
|
||||
| ca | `exec ./build/ca.out "$@"` | ✅ 无需改 | 移除 `sleep 2`(移交 YAML) |
|
||||
| gateway | `exec ./build/gateway.out "$@"` | ✅ 无需改 | 移除 `sleep 2`(移交 YAML) |
|
||||
| waf | `exec ./waf.bin -c config.yaml` | `exec ./waf.bin -c config.yaml "$@"` | 保留 `-c config.yaml` 作为脚本默认值,services.yaml `args: []` |
|
||||
| captcha2 | `exec ./captcha2.bin` | `exec ./captcha2.bin "$@"` | 移除 `sleep 1`(移交 YAML) |
|
||||
| aibox | `exec python aibox.py $@` | `exec python aibox.py "$@"` | `$@` → `"$@"` 加引号 |
|
||||
| ai_review | `exec python3 ai_review.py` | `exec python3 ai_review.py "$@"` | — |
|
||||
| smtp | `exec python3 smtp_v2.py` | `exec python3 smtp_v2.py "$@"` | 移除 `sleep 2`(移交 YAML) |
|
||||
| redirect80 | `exec python3 redirect80.py` | `exec python3 redirect80.py "$@"` | — |
|
||||
|
||||
**统计**:6 个无需改、5 个需改 exec 行、4 个需移除 sleep。
|
||||
|
||||
### 5.3 sleep 迁移对照
|
||||
|
||||
| 服务 | 当前 sleep 位置 | 迁移到 services.yaml |
|
||||
|------|----------------|---------------------|
|
||||
| ca | `_run.sh` 中 `sleep 2` | `sleep_before: 2` |
|
||||
| gateway | `_run.sh` 中 `sleep 2` | `sleep_before: 2` |
|
||||
| captcha2 | `_run.sh` 中 `sleep 1` | `sleep_before: 1` |
|
||||
| smtp | `_run.sh` 中 `sleep 2` | `sleep_before: 2` |
|
||||
|
||||
移除后 `_run.sh` 只负责"切换目录 + 前置准备 + exec",不负责时序控制。
|
||||
|
||||
---
|
||||
|
||||
## 6. Step 3:CLI 参数标准化
|
||||
|
||||
### 6.1 通用参数约定
|
||||
|
||||
定义一组所有服务都应支持的通用 CLI 参数:
|
||||
|
||||
| 参数 | 短选项 | 类型 | 默认值 | 说明 |
|
||||
|------|--------|------|--------|------|
|
||||
| `--host` | `-H` | string | `127.0.0.1` | 绑定地址 |
|
||||
| `--port` | `-p` | int | 服务特定 | 监听端口 |
|
||||
| `--config` | `-c` | string | 服务特定 | 配置文件路径 |
|
||||
| `--log-level` | `-l` | enum | `info` | `debug\|info\|warn\|error` |
|
||||
| `--instance` | `-i` | string | 服务名 | 实例标识(未来多实例/注册中心用) |
|
||||
| `--help` | `-h` | flag | — | 输出帮助 |
|
||||
| `--version` | `-v` | flag | — | 输出版本 |
|
||||
|
||||
**优先级规则**:`CLI 参数 > 环境变量 > confs/配置文件 > 硬编码默认值`
|
||||
|
||||
### 6.2 _run.sh 默认值注入
|
||||
|
||||
在服务二进制还不支持所有参数的过渡期,`_run.sh` 可注入默认值:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# waf/_run.sh — 标准化后
|
||||
cd "$(dirname "$0")" || exit
|
||||
|
||||
# 默认参数(可被 services.yaml args 覆盖)
|
||||
DEFAULT_ARGS=(-c config.yaml)
|
||||
|
||||
exec ./waf.bin "${DEFAULT_ARGS[@]}" "$@"
|
||||
```
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# db2/_run.sh — 标准化后
|
||||
cd "$(dirname "$0")" || exit
|
||||
set -e
|
||||
|
||||
# 默认参数
|
||||
DEFAULT_ARGS=(--host 127.0.0.1 --port 8005 --log-level info)
|
||||
|
||||
exec ./build/db2 "${DEFAULT_ARGS[@]}" "$@"
|
||||
```
|
||||
|
||||
**原理**:`"$@"` 追加在 `DEFAULT_ARGS` 之后,如果用户通过 services.yaml 传了同名参数,取决于各服务的解析逻辑(`boost::program_options` 默认后者覆盖前者;Python `argparse` 同理)。
|
||||
|
||||
### 6.3 各语言适配指南
|
||||
|
||||
#### C++ 服务(db2, dnfs2, fsys, captcha2)
|
||||
|
||||
当前这 4 个服务无 CLI 框架。建议引入 `boost::program_options`(ca/gateway 已有先例):
|
||||
|
||||
```cpp
|
||||
// db2/src/db2.cpp — 新增 CLI 解析
|
||||
#include <boost/program_options.hpp>
|
||||
namespace po = boost::program_options;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
po::options_description desc("db2 — database RPC service");
|
||||
desc.add_options()
|
||||
("help,h", "output help message")
|
||||
("version,v", "output version message")
|
||||
("host,H", po::value<std::string>()->default_value("127.0.0.1"), "bind address")
|
||||
("port,p", po::value<int>()->default_value(8005), "listen port")
|
||||
("log-level,l", po::value<std::string>()->default_value("info"),
|
||||
"debug|info|warn|error")
|
||||
("instance,i", po::value<std::string>()->default_value("db2"), "instance name")
|
||||
;
|
||||
|
||||
po::variables_map vm;
|
||||
try {
|
||||
po::store(po::parse_command_line(argc, argv, desc), vm);
|
||||
po::notify(vm);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "ERROR: " << e.what() << std::endl;
|
||||
std::cerr << desc << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (vm.count("help")) { std::cout << desc << std::endl; return 0; }
|
||||
if (vm.count("version")) { std::cout << "db2 1.0.0" << std::endl; return 0; }
|
||||
|
||||
std::string host = vm["host"].as<std::string>();
|
||||
int port = vm["port"].as<int>();
|
||||
std::string log_level = vm["log-level"].as<std::string>();
|
||||
|
||||
// 后续使用 host/port/log_level 启动服务...
|
||||
}
|
||||
```
|
||||
|
||||
#### C++ 服务(ca, gateway)— 扩展现有 program_options
|
||||
|
||||
ca 和 gateway已有 `po::options_description` 但只注册了 `--help`/`--version`,需追加通用参数:
|
||||
|
||||
```cpp
|
||||
// ca/src/main.cpp — 扩展
|
||||
desc.add_options()
|
||||
("help,h", "output help message")
|
||||
("version,v", "output version message")
|
||||
// 新增通用参数
|
||||
("host,H", po::value<std::string>()->default_value("127.0.0.1"), "bind address")
|
||||
("port,p", po::value<int>()->default_value(8443), "listen port")
|
||||
("log-level,l", po::value<std::string>()->default_value("info"),
|
||||
"debug|info|warn|error")
|
||||
("instance,i", po::value<std::string>()->default_value("ca"), "instance name")
|
||||
;
|
||||
```
|
||||
|
||||
注意:ca/gateway 当前配置走 confs + 环境变量,CLI 参数应作为**覆盖层**,不替换 confs 机制。
|
||||
|
||||
#### Go 服务(waf)— 扩展现有 flag
|
||||
|
||||
waf 已有 `flag` 包,追加通用参数:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
var (
|
||||
configFile = flag.String("c", "config.yaml", "配置文件路径")
|
||||
host = flag.String("H", "127.0.0.1", "绑定地址")
|
||||
port = flag.Int("p", 443, "监听端口")
|
||||
logLevel = flag.String("l", "info", "debug|info|warn|error")
|
||||
instance = flag.String("i", "waf", "实例名")
|
||||
doList = flag.Bool("list", false, "列出所有规则后退出")
|
||||
doTest = flag.String("test", "", "校验单个规则文件语法后退出")
|
||||
doCheck = flag.String("check", "", "用当前规则测试输入字符串后退出")
|
||||
)
|
||||
flag.Parse()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
注意:Go `flag` 包不支持短选项连写(`-H 127.0.0.1`),与 C++ `program_options` 的 `-H` 语法兼容。
|
||||
|
||||
#### Python 服务(aibox, ai_review, smtp, redirect80)
|
||||
|
||||
建议统一使用 `argparse`:
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""ai_review — AI 审核服务"""
|
||||
import argparse
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="ai_review — AI 审核服务")
|
||||
parser.add_argument("-H", "--host", default="127.0.0.1", help="绑定地址")
|
||||
parser.add_argument("-p", "--port", type=int, default=8007, help="监听端口")
|
||||
parser.add_argument("-l", "--log-level", default="info",
|
||||
choices=["debug", "info", "warn", "error"], help="日志级别")
|
||||
parser.add_argument("-i", "--instance", default="ai_review", help="实例名")
|
||||
return parser.parse_args()
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parse_args()
|
||||
# 后续使用 args.host, args.port, args.log_level...
|
||||
```
|
||||
|
||||
aibox 已有 `import argparse`(死代码),激活即可。
|
||||
|
||||
### 6.4 环境变量桥接
|
||||
|
||||
对于走 confs + 环境变量的服务(ca, gateway, dnfs2),CLI 参数应映射到环境变量,保持与现有 confs 机制兼容:
|
||||
|
||||
```bash
|
||||
# ca/_run.sh — 环境变量桥接模式
|
||||
cd "$(dirname "$0")" || exit
|
||||
|
||||
# 从命令行参数提取并导出环境变量(供 confs 加载逻辑使用)
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-H|--host) export CA_HOST="$2"; shift 2 ;;
|
||||
-p|--port) export CA_PORT="$2"; shift 2 ;;
|
||||
-l|--log-level) export LOG_LEVEL="$2"; shift 2 ;;
|
||||
*) EXTRA_ARGS+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
exec ./build/ca.out "${EXTRA_ARGS[@]}"
|
||||
```
|
||||
|
||||
这种桥接方式不需要立刻改 ca 的 C++ 源码——`_run.sh` 先把 CLI 参数翻译成环境变量,ca 现有的环境变量读取逻辑自然生效。
|
||||
|
||||
---
|
||||
|
||||
## 7. 实施计划
|
||||
|
||||
### 7.1 分步交付
|
||||
|
||||
| 步骤 | 改动范围 | 涉及文件 | 预估工时 | 风险 |
|
||||
|------|----------|----------|----------|------|
|
||||
| **S1-a** | yaml-cpp vendored + Makefile 集成 | `core/third_party/yaml-cpp/`、`core/Makefile` | 1h | 低 |
|
||||
| **S1-b** | InitParser 实现 | `core/include/registry/InitParser.hh`、`core/src/registry/InitParser.cpp` | 2h | 低 |
|
||||
| **S1-c** | pmc_mtd 改造(调用 InitParser) | `core/src/main/pmc_mtd.cpp` | 1h | 中(改核心启动路径) |
|
||||
| **S1-d** | 创建 services.yaml | `core/services.yaml` | 0.5h | 低 |
|
||||
| **S1-e** | 回退测试(删除 services.yaml 验证 init.txt 兼容) | — | 0.5h | 低 |
|
||||
| **S2-a** | 5 个 _run.sh 改 exec 行 | `waf/`、`captcha2/`、`aibox/`、`ai_review/`、`smtp/`、`redirect80/` 的 `_run.sh` | 0.5h | 极低 |
|
||||
| **S2-b** | 4 个 _run.sh 移除 sleep | `ca/`、`gateway/`、`captcha2/`、`smtp/` 的 `_run.sh` | 0.2h | 低(sleep 已移交 YAML) |
|
||||
| **S3-a** | 定义 CLI 标准文档(本文档 §6) | — | 0h(已完成) | — |
|
||||
| **S3-b** | C++ 服务适配(ca/gateway 扩展,db2/dnfs2/fsys/captcha2 新增) | 各服务 `src/*.cpp` | 4h | 中 |
|
||||
| **S3-c** | Python 服务适配(argparse) | `aibox.py`、`ai_review.py`、`smtp_v2.py`、`redirect80.py` | 2h | 低 |
|
||||
| **S3-d** | Go 服务适配(waf flag 扩展) | `waf/waf.go` | 0.5h | 低 |
|
||||
| **S3-e** | _run.sh 默认值注入 | 各服务 `_run.sh` | 0.5h | 低 |
|
||||
| **S4** | 端到端测试 | — | 1h | — |
|
||||
|
||||
**总计预估:~14 小时**
|
||||
|
||||
### 7.2 推荐实施顺序
|
||||
|
||||
```
|
||||
S1-a (yaml-cpp 集成)
|
||||
└─▶ S1-b (InitParser)
|
||||
└─▶ S1-c (pmc_mtd 改造)
|
||||
└─▶ S1-d (services.yaml)
|
||||
└─▶ S1-e (回退测试) ──▶ Step 1 完成
|
||||
│
|
||||
▼
|
||||
S2-a + S2-b ( _run.sh 改造) ──▶ Step 2 完成
|
||||
│
|
||||
▼
|
||||
S3-b/c/d (各服务 CLI 适配)
|
||||
└─▶ S3-e (默认值注入) ──▶ Step 3 完成
|
||||
│
|
||||
▼
|
||||
S4 (端到端测试)
|
||||
```
|
||||
|
||||
### 7.3 新增/改动文件清单
|
||||
|
||||
```
|
||||
core/
|
||||
├── include/registry/
|
||||
│ └── InitParser.hh # 【新增】YAML/TXT 解析器头文件
|
||||
├── src/registry/
|
||||
│ └── InitParser.cpp # 【新增】YAML/TXT 解析器实现
|
||||
├── src/main/pmc_mtd.cpp # 【改动】调用 InitParser 替代 parse_self_init_list
|
||||
├── src/main.cpp # 【改动】--init 帮助文本更新
|
||||
├── Makefile # 【改动】追加 InitParser.o 编译目标 + -lyaml-cpp 链接
|
||||
├── _run.sh # 【改动】-i init.txt → -i .(目录模式自动查找)
|
||||
├── services.yaml # 【新增】声明式服务启动配置
|
||||
└── init.txt # 【保留】向后兼容回退
|
||||
|
||||
项目根目录/
|
||||
├── _setup.deb.sh # 【改动】追加 libyaml-cpp-dev
|
||||
└── _setup.rhel.sh # 【改动】追加 yaml-cpp-devel
|
||||
|
||||
各服务 _run.sh(11 个) # 【已完成】Step 2 统一 "$@" 透传 + 移除 sleep
|
||||
|
||||
C++ 服务源码(6 个) # 【待改】Step 3 新增/扩展 program_options
|
||||
Python 服务源码(4 个) # 【待改】Step 3 新增 argparse
|
||||
Go 服务源码(1 个) # 【待改】Step 3 扩展 flag
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 验证清单
|
||||
|
||||
### 8.1 Step 1 验证
|
||||
|
||||
- [ ] `make` 编译成功,yaml-cpp 源文件正确编译链接
|
||||
- [ ] `services.yaml` 存在时,core 按 YAML 声明启动全部 11 个服务
|
||||
- [ ] `services.yaml` 不存在时,core 回退 `init.txt`,行为与改造前完全一致
|
||||
- [ ] `args` 字段的参数正确透传到 `_run.sh`
|
||||
- [ ] `env` 字段的环境变量在子进程启动前生效
|
||||
- [ ] `sleep_before` 字段正确控制启动间隔
|
||||
|
||||
### 8.2 Step 2 验证
|
||||
|
||||
- [x] 全部 11 个 `_run.sh` 末尾 `exec` 行带 `"$@"`
|
||||
- [x] waf 保留 `-c config.yaml` 作为脚本默认值,追加 `"$@"` 透传,services.yaml `args: []`
|
||||
- [x] aibox 的 `$@` 改为 `"$@"`(加引号,防空格路径问题)
|
||||
- [x] ca/gateway/captcha2/smtp 的 `sleep` 从 `_run.sh` 移除,由 `services.yaml` 的 `sleep_before` 管理
|
||||
- [ ] 手动执行 `_run.sh --help` 能透传到二进制(依赖 Step 3 CLI 适配)
|
||||
|
||||
### 8.3 Step 3 验证
|
||||
|
||||
- [ ] 全部服务支持 `--help` 输出用法
|
||||
- [ ] 全部服务支持 `--version` 输出版本
|
||||
- [ ] `--port` 能覆盖默认端口并生效
|
||||
- [ ] `--log-level` 能控制日志输出级别
|
||||
- [ ] `_run.sh` 默认值注入 + `services.yaml` 覆盖,两者叠加不冲突
|
||||
- [ ] ca/gateway 的环境变量桥接模式正确工作
|
||||
|
||||
---
|
||||
|
||||
## 9. 风险与缓解
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
|------|------|----------|
|
||||
| yaml-cpp 编译增加构建时间 | 低(~10s) | 可接受;如需优化可预编译为静态库 |
|
||||
| pmc_mtd 改造影响核心启动路径 | 高 | 保留 `init.txt` 回退路径;先在测试环境验证 |
|
||||
| _run.sh 移除 sleep 后启动时序变化 | 中 | `services.yaml` 的 `sleep_before` 提供等价等待 |
|
||||
| 各服务 CLI 参数适配工作量大 | 中 | 分批适配;_run.sh 默认值注入可过渡 |
|
||||
| Go flag 与 C++ program_options 语法差异 | 低 | 两者都支持 `-H value` 和 `--host value` 格式 |
|
||||
| Python argparse 与 C++ program_options 行为差异 | 低 | 测试覆盖;argparse 默认不覆盖已设默认值 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 附录
|
||||
|
||||
### A. 各服务端口与配置对照
|
||||
|
||||
| 服务 | 端口 | 协议 | 配置来源 | CLI 框架现状 |
|
||||
|------|------|------|----------|-------------|
|
||||
| db2 | 8005 | pymsg | 硬编码 | 无 |
|
||||
| ai_review | 8007 | http | 硬编码 | 无 |
|
||||
| dnfs2 | 8006 | http | 硬编码 + confs | 无 |
|
||||
| smtp | 8008 | pymsg | 硬编码 | 无 |
|
||||
| fsys | 8090 | http | 硬编码 | 无 |
|
||||
| waf | 443 | https | YAML 配置文件 | Go flag ✅ |
|
||||
| ca | 8443 | https | confs + 环境变量 | program_options(仅 --help/--version) |
|
||||
| gateway | 8445 | http | confs + 环境变量 | program_options(仅 --help/--version) |
|
||||
| captcha2 | 8009 | pymsg | 硬编码 | 无 |
|
||||
| aibox | 8765 | websocket | _setenv.sh + 硬编码 | argparse(已 import 未使用) |
|
||||
| redirect80 | 80 | http | 硬编码 | 无 |
|
||||
|
||||
### B. yaml-cpp 集成验证
|
||||
|
||||
已采用系统包方式(非 vendored),在 `_setup.deb.sh` / `_setup.rhel.sh` 中声明依赖:
|
||||
|
||||
```bash
|
||||
# Debian/Ubuntu
|
||||
sudo apt install libyaml-cpp-dev
|
||||
|
||||
# RHEL/CentOS
|
||||
sudo dnf install yaml-cpp-devel
|
||||
```
|
||||
|
||||
Makefile 中 `-lyaml-cpp` 链接,`InitParser.cpp` 中 `#include <yaml-cpp/yaml.h>` 引用。
|
||||
|
||||
### C. 环境变量约定(现有 + 新增)
|
||||
|
||||
| 环境变量 | 使用方 | 当前/新增 | 说明 |
|
||||
|----------|--------|-----------|------|
|
||||
| `CONFS_HOST` | ca, gateway, dnfs2 | 现有 | confs 服务地址,默认 `127.0.0.1` |
|
||||
| `CONFS_PORT` | ca, gateway, dnfs2 | 现有 | confs 服务端口,默认 `8010` |
|
||||
| `CORE_HOST` | 未来 RegistrationClient | 新增 | core 注册中心地址,默认 `127.0.0.1` |
|
||||
| `CORE_PORT` | 未来 RegistrationClient | 新增 | core 注册中心端口,默认 `5200` |
|
||||
| `LOG_LEVEL` | 全部服务(可选) | 新增 | 日志级别,可被 `--log-level` 覆盖 |
|
||||
|
||||
### D. 与注册中心计划的衔接点
|
||||
|
||||
| 本方案产出 | 注册中心计划消费点 |
|
||||
|-----------|-------------------|
|
||||
| `services.yaml` 格式 | P7 直接复用,追加 `health_endpoint`/`protocol`/`restart_max` 等字段 |
|
||||
| `InitParser` 解析器 | P2 调用 `parseYaml()` 后自动填充 `ServiceRegistry` |
|
||||
| `ServiceConfig.depends_on` | P7 DAG 拓扑排序的输入 |
|
||||
| `ServiceConfig.args` | 各服务通过 CLI 参数接收注册中心分配的端口/实例名 |
|
||||
| CLI `--instance` 参数 | P5 RegistrationClient SDK 用此值作为注册服务名 |
|
||||
| CLI `--port` 参数 | P0 ServiceEntry 的 port 字段来源 |
|
||||
+70
-6
@@ -14,7 +14,7 @@
|
||||
* 安全改进(相比 Python static):
|
||||
* F-1 server.setMaxBodySize(10MB) 限制请求体大小
|
||||
* F-2 libhttp 原生 HTTPS 支持,无需回退开发服务器
|
||||
* F-3 命令行参数 --addr/-a --port/-p --threads/-t
|
||||
* F-3 命令行参数 --host/--addr/-a --port/-p --threads/-t --log-level --instance --config --help
|
||||
* F-4 所有端口/线程数有严格边界校验
|
||||
* F-5 Boost.Beast 异步多线程替代 Flask 同步
|
||||
* F-6 消除 Python/Flask/waitress 依赖
|
||||
@@ -271,11 +271,40 @@ struct Config {
|
||||
int db_port = 8005;
|
||||
|
||||
std::string dnfs_url = "http://127.0.0.1:8006";
|
||||
|
||||
// CLI 标准化参数(Step 3)
|
||||
std::string log_level = "info"; // debug|info|warn|error
|
||||
std::string instance_name = "fsys"; // 实例名(注册中心用)
|
||||
std::string confs_config_name = "static.ini"; // confs 拉取的配置名
|
||||
};
|
||||
|
||||
static Config load_config(int argc, char* argv[]) {
|
||||
Config cfg;
|
||||
|
||||
// 0. 预扫描 --help 和 --config(需在 confs 拉取前处理)
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string arg = argv[i];
|
||||
if (arg == "--help" || arg == "-h") {
|
||||
std::printf(
|
||||
"fsys — C++20 静态文件托管服务\n\n"
|
||||
"Usage: fsys [OPTIONS]\n\n"
|
||||
"Options:\n"
|
||||
" -h, --help Show this help and exit\n"
|
||||
" -a, --addr <addr> Listen address (alias: --host)\n"
|
||||
" --host <addr> Listen address (canonical name)\n"
|
||||
" -p, --port <port> Listen port\n"
|
||||
" -t, --threads <n> Worker threads\n"
|
||||
" --config <name> confs config name (default: static.ini)\n"
|
||||
" --log-level <level> debug|info|warn|error (default: info)\n"
|
||||
" --instance <name> Instance name (default: fsys)\n\n"
|
||||
"Config priority: CLI > env > confs > defaults\n");
|
||||
std::exit(0);
|
||||
}
|
||||
if (arg == "--config" && i + 1 < argc) {
|
||||
cfg.confs_config_name = argv[++i];
|
||||
}
|
||||
}
|
||||
|
||||
// 1. confs 引导地址
|
||||
if (const char* h = std::getenv("CONFS_HOST")) cfg.confs_host = h;
|
||||
if (const char* p = std::getenv("CONFS_PORT")) {
|
||||
@@ -286,17 +315,18 @@ static Config load_config(int argc, char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 从 confs 拉取 static.ini(retry_on_refused=true)
|
||||
// 2. 从 confs 拉取配置(retry_on_refused=true)
|
||||
std::string ini_text;
|
||||
try {
|
||||
RpcClient confs_client(cfg.confs_host, cfg.confs_port, "fsys",
|
||||
/*timeout=*/5.0, /*retry_on_refused=*/true);
|
||||
json::value data = confs_client.call("get", {{"name", "static.ini"}});
|
||||
json::value data = confs_client.call("get", {{"name", cfg.confs_config_name}});
|
||||
if (data.is_object() && data.as_object().contains("content")) {
|
||||
ini_text = std::string(data.as_object().at("content").as_string());
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::fprintf(stderr, "[fsys] 从 confs 获取 static.ini 失败: %s\n", e.what());
|
||||
std::fprintf(stderr, "[fsys] 从 confs 获取 %s 失败: %s\n",
|
||||
cfg.confs_config_name.c_str(), e.what());
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
@@ -343,9 +373,12 @@ static Config load_config(int argc, char* argv[]) {
|
||||
// 5. 命令行参数(最高优先级)
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string arg = argv[i];
|
||||
if ((arg == "--addr" || arg == "-a") && i + 1 < argc) cfg.listen_host = argv[++i];
|
||||
if ((arg == "--addr" || arg == "-a" || arg == "--host") && i + 1 < argc) cfg.listen_host = argv[++i];
|
||||
else if ((arg == "--port" || arg == "-p") && i + 1 < argc) cfg.listen_port = safe_stoi(argv[++i], "--port");
|
||||
else if ((arg == "--threads" || arg == "-t") && i + 1 < argc) cfg.threads = safe_stoi(argv[++i], "--threads");
|
||||
else if (arg == "--log-level" && i + 1 < argc) cfg.log_level = argv[++i];
|
||||
else if (arg == "--instance" && i + 1 < argc) cfg.instance_name = argv[++i];
|
||||
// --config 和 --help 已在预扫描阶段处理
|
||||
}
|
||||
|
||||
// 6. 校验必需配置项
|
||||
@@ -372,6 +405,18 @@ static Config load_config(int argc, char* argv[]) {
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
// log_level 校验
|
||||
{
|
||||
std::string ll = cfg.log_level;
|
||||
std::transform(ll.begin(), ll.end(), ll.begin(), ::tolower);
|
||||
if (ll != "debug" && ll != "info" && ll != "warn" && ll != "error") {
|
||||
std::fprintf(stderr, "[fsys] invalid --log-level: %s (must be debug|info|warn|error)\n",
|
||||
cfg.log_level.c_str());
|
||||
std::exit(1);
|
||||
}
|
||||
cfg.log_level = ll;
|
||||
}
|
||||
|
||||
// 8. 路径规范化:static_root/SSL 相对路径基于当前工作目录
|
||||
// (_run.sh 以模块目录为 cwd 启动,与 Python 版 BASE_DIR 语义一致;
|
||||
// Docker 场景使用绝对路径 STATIC_ROOT,不受影响)
|
||||
@@ -654,8 +699,11 @@ public:
|
||||
_filesystem_enabled = false;
|
||||
}
|
||||
|
||||
std::printf("[fsys] 配置来源: confs (static.ini @ %s:%d)\n",
|
||||
std::printf("[fsys] 配置来源: confs (%s @ %s:%d)\n",
|
||||
_cfg.confs_config_name.c_str(),
|
||||
_cfg.confs_host.c_str(), _cfg.confs_port);
|
||||
std::printf("[fsys] 实例: %s | 日志级别: %s\n",
|
||||
_cfg.instance_name.c_str(), _cfg.log_level.c_str());
|
||||
std::printf("[fsys] 静态目录: %s\n", _cfg.static_root.c_str());
|
||||
}
|
||||
|
||||
@@ -687,6 +735,19 @@ private:
|
||||
void _log(const std::string& level, const std::string& service,
|
||||
const std::string& message, const std::string& trace_id = "",
|
||||
const std::string& extra = "") {
|
||||
// 日志级别过滤
|
||||
static const std::unordered_map<std::string, int> kLogLevels = {
|
||||
{"debug", 0}, {"DEBUG", 0},
|
||||
{"info", 1}, {"INFO", 1},
|
||||
{"warn", 2}, {"WARNING", 2},
|
||||
{"error", 3}, {"ERROR", 3}
|
||||
};
|
||||
auto cfg_it = kLogLevels.find(_cfg.log_level);
|
||||
auto msg_it = kLogLevels.find(level);
|
||||
if (cfg_it != kLogLevels.end() && msg_it != kLogLevels.end()) {
|
||||
if (msg_it->second < cfg_it->second) return;
|
||||
}
|
||||
|
||||
LogEntry e;
|
||||
e.ts = utc_now();
|
||||
e.service = service;
|
||||
@@ -1576,6 +1637,9 @@ int main(int argc, char* argv[]) {
|
||||
cfg.static_root.c_str(), cfg.ssl_enabled ? "on" : "off");
|
||||
std::printf("[fsys] db=%s:%d dnfs=%s\n",
|
||||
cfg.db_host.c_str(), cfg.db_port, cfg.dnfs_url.c_str());
|
||||
std::printf("[fsys] instance=%s log-level=%s config=%s\n",
|
||||
cfg.instance_name.c_str(), cfg.log_level.c_str(),
|
||||
cfg.confs_config_name.c_str());
|
||||
std::fflush(stdout);
|
||||
|
||||
FsysServer server(std::move(cfg));
|
||||
|
||||
+1
-3
@@ -4,7 +4,5 @@
|
||||
# 启动时从 confs(CONFS_HOST:CONFS_PORT,默认 127.0.0.1:8010)拉取配置。
|
||||
cd "$(dirname "$0")" || exit
|
||||
|
||||
# 等待依赖服务启动(confs/db)
|
||||
sleep 2
|
||||
|
||||
# sleep 已移至 services.yaml 的 sleep_before 字段统一管理
|
||||
exec ./build/gateway.out "$@"
|
||||
+1
-1
@@ -13,4 +13,4 @@ if command -v setcap &>/dev/null; then
|
||||
fi
|
||||
fi
|
||||
|
||||
exec python3 redirect80.py
|
||||
exec python3 redirect80.py "$@"
|
||||
+1
-2
@@ -2,5 +2,4 @@
|
||||
cd "$(dirname "$0")" || exit
|
||||
source ./venv/bin/activate
|
||||
#exec ./smtp.bin
|
||||
sleep 2
|
||||
exec python3 smtp_v2.py
|
||||
exec python3 smtp_v2.py "$@"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
# 运行 WAF(需先 _set.sh 编译)
|
||||
cd "$(dirname "$0")" || exit
|
||||
exec ./waf.bin -c config.yaml
|
||||
exec ./waf.bin -c config.yaml "$@"
|
||||
Reference in new issue
Block a user