diff --git a/confs/README.md b/confs/README.md index e1b232f..686d31d 100644 --- a/confs/README.md +++ b/confs/README.md @@ -237,6 +237,7 @@ except RpcError as e: |------|---------|---------| | [static](../static/) | [static.ini](configs/static.ini) | 三层 fail-safe:内置默认值 → confs(优先)→ 本地 `config.ini` 回退 | | [dnfs](../dnfs/) | [dnfs.ini](configs/dnfs.ini) | 三层 fail-safe:内置默认值 → confs(优先)→ 环境变量覆盖 | +| [db](../db/) | [db.ini](configs/db.ini) | 三层 fail-safe:内置默认值 → confs(优先)→ 环境变量覆盖(最高优先级,Docker 兼容) | static 的接入实现见 [static/fsys.py](../static/fsys.py): - `_fetch_from_confs()` 通过 `RpcClient` 调 `get` 路由取 `static.ini`,3 秒超时 @@ -249,7 +250,14 @@ dnfs 的接入实现见 [dnfs/dnfs.py](../dnfs/dnfs.py): - 支持的环境变量:`LISTEN_ADDR`/`LISTEN_PORT`/`DB_HOST`/`DB_PORT`/`REVIEW_URL`/`CONFS_HOST`/`CONFS_PORT` - Docker 环境通过环境变量传入服务名(db/ai_review),覆盖 confs 中的 127.0.0.1 默认值 -其他服务(gateway / waf / ai_review / smtp / db)尚未接入,仍读各自本地配置。 +db 的接入实现见 [db/dbv2.py](../db/dbv2.py): +- 三层加载顺序:内置默认值 → confs(db.ini)→ 环境变量覆盖(优先级最高) +- 配置项涵盖:`[server]` host/port/workers、`[database]` url、`[logs]` retention_days/max_records/cleanup_interval、`[confs]` host/port +- 支持的环境变量:`LISTEN_ADDR`/`LISTEN_PORT`/`WORKERS`/`DATABASE_URL`/`CONFS_HOST`/`CONFS_PORT` +- 注意:db 先于 confs 启动(启动顺序 db → confs → ai_review/...),db 启动时若 confs 不可用会静默 fail-safe 使用默认值,配置来源会打印到控制台 +- 独立运维工具 `db_tools.py`(backup/export/import 等)仍直接读 `DATABASE_URL` 环境变量,不依赖 confs,避免运维脚本依赖服务就绪 + +其他服务(gateway / waf / ai_review / smtp)尚未接入,仍读各自本地配置。 ## 项目结构 @@ -260,7 +268,8 @@ confs/ ├── configs/ # 配置文件存放目录(被分发对象) │ ├── example.ini # 示例配置 │ ├── static.ini # static 服务配置(已接入分发) -│ └── dnfs.ini # dnfs 服务配置(已接入分发) +│ ├── dnfs.ini # dnfs 服务配置(已接入分发) +│ └── db.ini # db 服务配置(已接入分发) ├── docs/ │ └── SECURITY_AUDIT_20260801.md # 安全审计报告 ├── _set.sh # 初始化脚本(建 venv + 安装依赖) diff --git a/confs/configs/db.ini b/confs/configs/db.ini new file mode 100644 index 0000000..bd2a2d3 --- /dev/null +++ b/confs/configs/db.ini @@ -0,0 +1,16 @@ +[server] +host = 0.0.0.0 +port = 8005 +workers = 4 + +[database] +url = sqlite:///database.db + +[logs] +retention_days = 30 +max_records = 1000000 +cleanup_interval = 3600 + +[confs] +host = 127.0.0.1 +port = 8010 diff --git a/db/README.md b/db/README.md index 67b6baa..0397c2f 100644 --- a/db/README.md +++ b/db/README.md @@ -25,10 +25,60 @@ ./_run.sh # venv 中执行 python3 dbv2.py ``` -服务监听 `tcp://127.0.0.1:8005`。生产环境由 core(Nidus)按 init.txt 统一拉起,守护靠 systemd。 +服务监听 `tcp://0.0.0.0:8005`(默认)。生产环境由 core(Nidus)按 init.txt 统一拉起,守护靠 systemd。 **注意**:`execute` 路由依赖表已存在(通过反射获取结构)。建表见 `create_sqlite3.sql`(Users / Notes;Logs 表由服务启动时自建)。 +## 配置管理 + +db 已接入 [confs](../confs/) 统一配置服务,采用三层 fail-safe 加载模式(与 static/dnfs 一致): + +1. **内置默认值** — 硬编码在 `dbv2.py` 中,保证服务在任何环境都能启动 +2. **confs 统一配置服务**(优先)— 通过 pymsg RpcClient 从 `confs` 拉取 `db.ini` +3. **环境变量覆盖**(最高优先级)— Docker/部署环境通过环境变量覆盖,保持向后兼容 + +### confs 配置文件(db.ini) + +配置文件托管于 [confs/configs/db.ini](../confs/configs/db.ini): + +```ini +[server] +host = 0.0.0.0 +port = 8005 +workers = 4 + +[database] +url = sqlite:///database.db + +[logs] +retention_days = 30 +max_records = 1000000 +cleanup_interval = 3600 + +[confs] +host = 127.0.0.1 +port = 8010 +``` + +### 环境变量(最高优先级) + +| 环境变量 | 说明 | 对应 confs 配置项 | +|---------|------|------------------| +| `LISTEN_ADDR` | 监听地址 | `[server] host` | +| `LISTEN_PORT` | 监听端口 | `[server] port` | +| `WORKERS` | 回调并发线程数 | `[server] workers` | +| `DATABASE_URL` | 数据库连接 URL | `[database] url` | +| `CONFS_HOST` | confs 服务地址 | `[confs] host` | +| `CONFS_PORT` | confs 服务端口 | `[confs] port` | + +### 启动顺序注意 + +根据 micro_servers 的启动顺序:**db → confs → ai_review/...**,db 先于 confs 启动。因此: +- db 启动时若 confs 尚未就绪,`_fetch_from_confs()` 连接失败会静默 fail-safe,使用内置默认值 + 环境变量正常启动 +- 启动时控制台会打印配置来源:`配置来源: confs 统一配置服务 (db.ini)` 或 `警告: confs 不可用,使用内置默认值`,最后 `start()` 前会再次打印最终来源 +- 独立运维工具 `db_tools.py`(backup/export/import/vacuum 等)仍直接读 `DATABASE_URL` 环境变量,不依赖 confs 服务就绪 +- Docker 环境通过 `docker-entrypoint.sh` 传入环境变量(见 [Dockerfile](Dockerfile)),保持与之前完全兼容,无需额外配置 + ## 协议与 API ### 传输协议(pymsg) @@ -232,10 +282,10 @@ python3 _view_db.sqlite3.py -s "SELECT service, COUNT(*) FROM Logs GROUP BY serv ## 切换数据库 -只需修改环境变量 `DATABASE_URL`,并安装对应驱动: +推荐方式:修改 confs 的 [db.ini](../confs/configs/db.ini) 中 `[database] url`,然后通过 confs `reload` 路由重载(或重启 db)。也可通过环境变量 `DATABASE_URL` 覆盖(Docker 部署时常用): -- SQLite(默认):`sqlite:///database.db` -- PostgreSQL:`postgresql://user:pass@localhost/dbname`(需 `pip install psycopg2`) +- SQLite(默认):`sqlite:///database.db`(相对路径)或 `sqlite:////app/data/database.db`(绝对路径,Docker 中使用) +- PostgreSQL:`postgresql://user:pass@localhost/dbname`(需 `pip install psycopg2-binary`) - MySQL:`mysql+pymysql://user:pass@localhost/dbname`(需 `pip install pymysql`) SQLAlchemy 会自动生成适配各数据库的 SQL 方言,无需修改接口调用。 diff --git a/db/dbv2.py b/db/dbv2.py index 771b206..ccd2121 100644 --- a/db/dbv2.py +++ b/db/dbv2.py @@ -27,6 +27,7 @@ dbv2 — 基于 pymsg 协议(MsgServer)的数据库服务。 响应中的日期时间一律序列化为 ISO 8601(T 分隔)。 """ +import configparser import json import os import threading @@ -47,9 +48,70 @@ import sys as _sys _sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from pymsg import RpcServer # noqa: E402 -# ── 数据库配置 ────────────────────────────────────────────────────────── +# ── 配置加载(三层 fail-safe:内置默认值 → confs → 环境变量覆盖)────── +# 加载顺序:内置默认值 → confs 统一配置服务(db.ini,优先); +# confs 不可用时 fail-safe 使用默认值;环境变量优先级最高(Docker 兼容)。 -DATABASE_URL = os.environ.get("DATABASE_URL", "sqlite:///database.db") +_db_config = { + 'server': {'host': '0.0.0.0', 'port': '8005', 'workers': '4'}, + 'database': {'url': 'sqlite:///database.db'}, + 'logs': { + 'retention_days': '30', + 'max_records': '1000000', + 'cleanup_interval': '3600', + }, + 'confs': {'host': '127.0.0.1', 'port': '8010'}, +} + + +def _merge_ini(text): + """把 ini 文本合并进 _db_config(仅覆盖已知的 section/key)。""" + ini = configparser.ConfigParser(interpolation=None) + ini.read_string(text) + for section in _db_config: + if ini.has_section(section): + for key, value in ini[section].items(): + if key in _db_config[section]: + _db_config[section][key] = value + + +def _fetch_from_confs(): + """从 confs 统一配置服务拉取 db.ini 全文,失败返回 None(fail-safe)。""" + try: + from pymsg import RpcClient + host = os.environ.get('CONFS_HOST', _db_config['confs']['host']) + port = int(os.environ.get('CONFS_PORT', _db_config['confs']['port'])) + data = RpcClient(host, port, 'db', timeout=3).call( + 'get', {'name': 'db.ini'}) + return data.get('content') + except Exception: + return None + + +_confs_text = _fetch_from_confs() +_config_source = 'built-in defaults' +if _confs_text is not None: + _merge_ini(_confs_text) + _config_source = 'confs (db.ini)' + print(f'[dbv2] 配置来源: confs 统一配置服务 (db.ini)') +else: + print(f'[dbv2] 警告: confs 不可用,使用内置默认值') + +# 环境变量覆盖(最高优先级,保持 Docker 兼容) +if os.environ.get('LISTEN_ADDR'): + _db_config['server']['host'] = os.environ['LISTEN_ADDR'] +if os.environ.get('LISTEN_PORT'): + _db_config['server']['port'] = os.environ['LISTEN_PORT'] +if os.environ.get('WORKERS'): + _db_config['server']['workers'] = os.environ['WORKERS'] +if os.environ.get('DATABASE_URL'): + _db_config['database']['url'] = os.environ['DATABASE_URL'] + +# 解析为最终常量 +DATABASE_URL = _db_config['database']['url'] +DEFAULT_RETENTION_DAYS = int(_db_config['logs']['retention_days']) +DEFAULT_MAX_RECORDS = int(_db_config['logs']['max_records']) +DEFAULT_CLEANUP_INTERVAL = int(_db_config['logs']['cleanup_interval']) # ── SQLite 连接配置(针对 Docker/多线程环境优化)────────────────────── @@ -76,10 +138,6 @@ def _configure_sqlite(dbapi_connection, connection_record): MAX_MESSAGE_LEN = 8192 # message 上限 8KB MAX_EXTRA_LEN = 4096 # extra 上限 4KB -DEFAULT_RETENTION_DAYS = 30 -DEFAULT_MAX_RECORDS = 1_000_000 -DEFAULT_CLEANUP_INTERVAL = 3600 # 秒 - #: where/values 中的哨兵值:服务端当前 UTC 时间 NOW_SENTINEL = "$now" @@ -456,17 +514,19 @@ class DBServer: if __name__ == "__main__": import argparse - default_addr = os.environ.get("LISTEN_ADDR", "0.0.0.0") - default_port = int(os.environ.get("LISTEN_PORT", "8005")) + default_addr = _db_config['server']['host'] + default_port = int(_db_config['server']['port']) + default_workers = int(_db_config['server']['workers']) parser = argparse.ArgumentParser(description="dbv2 — pymsg 数据库服务") parser.add_argument("--addr", default=default_addr, help="监听地址") parser.add_argument("--port", type=int, default=default_port, help="监听端口") - parser.add_argument("--workers", type=int, default=4, + parser.add_argument("--workers", type=int, default=default_workers, help="回调并发线程数(0=同步串行模式)") args = parser.parse_args() server = DBServer(args.addr, args.port, workers=args.workers) + print(f'[dbv2] 配置来源: {_config_source}') try: server.start() except KeyboardInterrupt: