Commit 09a35038 authored by wangteng's avatar wangteng

整理优化完善

parent 8cb592c8
...@@ -29,11 +29,10 @@ npm run dev ...@@ -29,11 +29,10 @@ npm run dev
## 验证与发布 ## 验证与发布
```powershell ```powershell
cd web # 正式发布:自动构建、上传、重启并检查前后端和 Oracle
npm run build python deploy/deploy_server.py
cd ..
node scripts/verify/verify_tree.cjs
python deploy/build_release.py
``` ```
发布产物生成到 `release/`,部署步骤见 [deploy/README.md](deploy/README.md)。 最后一条命令会自动构建发布包、读取 `deploy/server.local.md` 中的本机私有发布配置、上传并重启正式服务,同时执行健康检查。只构建不发布时仍可执行 `python deploy/build_release.py`;完整说明见 [deploy/README.md](deploy/README.md)。
发布前如需单独运行本地前端树校验,可执行 `node scripts/verify/verify_tree.cjs`。
...@@ -2,14 +2,36 @@ ...@@ -2,14 +2,36 @@
本目录只保存部署相关文件,业务代码不应放入此处。 本目录只保存部署相关文件,业务代码不应放入此处。
1. 在项目根目录执行 `python deploy/build_release.py`,构建产物生成到 `release/`。 ## 正式发布(一条命令)
2. 设置 `DBTOOL_PW` 后执行 `python deploy/deploy_server.py`,上传发布包并重启服务。
3. 使用 `python deploy/remote_shell.py "命令"` 执行临时远程检查;密码不会写入脚本或仓库。
`db-tool.service` 是 systemd 服务定义,`db-tool-nginx.conf` 是 Nginx 反向代理示例。 在项目根目录执行:
```powershell
python deploy/deploy_server.py
```
脚本会自动完成以下步骤:
1. 从 `deploy/server.local.md` 读取本机私有服务器地址、用户、密码和 Oracle 测试数据源 ID。
2. 执行 `build_release.py` 构建前后端发布包。
3. 上传前检查后端入口、正式配置、前端 `index.html/assets/`,并检查 Oracle `libclntsh.so` 符号链接和 ELF 实体库。
4. 备份线上版本和 SQLite 快照,上传并解压包,安装 systemd 配置,迁移数据后重启服务。
5. 检查 systemd、后端本机接口、Nginx `/db-tool/api/health`,并默认测试 Oracle 数据源连接。
`server.local.md` 已加入 `.gitignore`,只允许保存在本机,禁止提交 Git 或打入发布包。环境变量 `DBTOOL_HOST`、`DBTOOL_USER`、`DBTOOL_PW` 和 `DBTOOL_ORACLE_SOURCE_ID` 可临时覆盖文档中的值。
部署脚本默认使用现有服务器地址和 `root` 用户,也可通过 `DBTOOL_HOST`、`DBTOOL_USER` 覆盖;SSH 密码只从 `DBTOOL_PW` 读取。 ## 特殊场景
本机正式服务器凭据记录在被 Git 忽略的 `deploy/server.local.md` 中。发布前从该文件读取密码并设置 `DBTOOL_PW`,不要将凭据写入可提交文件或发布包。 ```powershell
# 只使用已有 release/,不重新构建
python deploy/deploy_server.py --skip-build
# 临时跳过 Oracle 测试(不建议正式发布使用)
python deploy/deploy_server.py --skip-oracle-check
```
如需只构建不发布,可执行 `python deploy/build_release.py`。临时远程检查使用 `python deploy/remote_shell.py "命令"`;该脚本仍从 `DBTOOL_PW` 读取密码。
`db-tool.service` 是 systemd 服务定义,`db-tool-nginx.conf` 是 Nginx 反向代理示例。
如生产环境需要 Oracle Thick 模式,请在构建前从 Oracle 官方获取 Linux Instant Client,并解压到项目根目录的 `oracle_client/linux/`。该目录是本机构建依赖,不纳入版本控制。 如生产环境需要 Oracle Thick 模式,请从 Oracle 官方获取 Linux Instant Client,并解压到项目根目录的 `oracle_client/linux/`。该目录是本机构建依赖,不纳入版本控制。Linux 服务通过 `db-tool.service` 中的 `LD_LIBRARY_PATH` 加载 Instant Client;不要改成运行时传 `lib_dir`,CentOS 7 与 python-oracledb 3.x 的组合可能因此触发原生段错误。构建脚本会把 Windows 解压产生的伪符号链接恢复为 Linux tar 符号链接。
...@@ -8,6 +8,7 @@ from __future__ import annotations ...@@ -8,6 +8,7 @@ from __future__ import annotations
import os import os
import subprocess import subprocess
import tarfile import tarfile
import time
from pathlib import Path from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent ROOT = Path(__file__).resolve().parent.parent
...@@ -28,7 +29,33 @@ def _add_tree(archive: tarfile.TarFile, root: Path, arcname: str) -> None: ...@@ -28,7 +29,33 @@ def _add_tree(archive: tarfile.TarFile, root: Path, arcname: str) -> None:
for path in root.rglob("*"): for path in root.rglob("*"):
if _skip(path) or not path.is_file(): if _skip(path) or not path.is_file():
continue continue
archive.add(path, arcname=f"{arcname}/{path.relative_to(root).as_posix()}") archive_name = f"{arcname}/{path.relative_to(root).as_posix()}"
# Windows 解压 Oracle 官方 ZIP 时,Linux 符号链接会变成内容仅为
# 目标文件名的小文本文件。恢复为 tar 符号链接,否则 Linux 加载
# libclntsh.so 时会把文本当动态库,严重时直接导致进程 SIGSEGV。
if "oracle_client" in path.parts and path.stat().st_size < 256:
try:
link_target = path.read_text(encoding="ascii").strip()
except (OSError, UnicodeDecodeError):
link_target = ""
if link_target and (path.parent / link_target).is_file():
info = tarfile.TarInfo(archive_name)
info.type = tarfile.SYMTYPE
info.linkname = link_target
info.mode = 0o777
# Windows 伪链接常带 1970 时间戳,避免 Linux 解包时出现
# "unknown date" 警告,使用当前构建时间作为链接时间。
info.mtime = int(time.time())
archive.addfile(info)
continue
info = archive.gettarinfo(str(path), arcname=archive_name)
if "oracle_client" in path.parts:
# Windows 文件系统不保存 Unix 可执行位,发布时统一补齐。
info.mode = 0o755
with path.open("rb") as source:
archive.addfile(info, source)
def main() -> None: def main() -> None:
......
...@@ -10,6 +10,7 @@ Environment=PYTHONPATH=/opt/app/db-tool ...@@ -10,6 +10,7 @@ Environment=PYTHONPATH=/opt/app/db-tool
Environment=APP_ENV=production Environment=APP_ENV=production
Environment=WEB_HOST=127.0.0.1 Environment=WEB_HOST=127.0.0.1
Environment=WEB_PORT=8767 Environment=WEB_PORT=8767
Environment=LD_LIBRARY_PATH=/opt/app/db-tool/web/oracle_client/linux/instantclient_19_32
ExecStart=/opt/app/db-tool/.venv/bin/python -m web.backend.start ExecStart=/opt/app/db-tool/.venv/bin/python -m web.backend.start
Restart=always Restart=always
RestartSec=5 RestartSec=5
......
This diff is collapsed.
...@@ -7,6 +7,10 @@ import sys ...@@ -7,6 +7,10 @@ import sys
import paramiko import paramiko
for stream in (sys.stdout, sys.stderr):
if hasattr(stream, "reconfigure"):
stream.reconfigure(encoding="utf-8", errors="replace")
HOST = os.environ.get("DBTOOL_HOST", "192.168.20.175") HOST = os.environ.get("DBTOOL_HOST", "192.168.20.175")
USER = os.environ.get("DBTOOL_USER", "root") USER = os.environ.get("DBTOOL_USER", "root")
password = os.environ.get("DBTOOL_PW") password = os.environ.get("DBTOOL_PW")
......
...@@ -338,7 +338,19 @@ def _ensure_oracle_client(lib_dir: Optional[str] = None) -> str: ...@@ -338,7 +338,19 @@ def _ensure_oracle_client(lib_dir: Optional[str] = None) -> str:
import oracledb import oracledb
bundled_dir = _bundled_oracle_client_dir(lib_dir) bundled_dir = _bundled_oracle_client_dir(lib_dir)
if bundled_dir: if bundled_dir:
oracledb.init_oracle_client(lib_dir=bundled_dir) if sys.platform.startswith("linux"):
# CentOS 7 + python-oracledb 3.x 使用 lib_dir 动态加载时可能在
# ODPI-C 内部触发 SIGSEGV。Linux 应在进程启动前通过
# LD_LIBRARY_PATH 暴露客户端,再使用无参数初始化。
library_paths = os.environ.get("LD_LIBRARY_PATH", "").split(os.pathsep)
if bundled_dir not in library_paths:
raise RuntimeError(
"Linux Oracle Thick 模式要求在进程启动前将 Instant Client "
f"加入 LD_LIBRARY_PATH:{bundled_dir}"
)
oracledb.init_oracle_client()
else:
oracledb.init_oracle_client(lib_dir=bundled_dir)
logger.info(f"[DB] Oracle Instant Client 已加载(项目内置)lib_dir={bundled_dir}") logger.info(f"[DB] Oracle Instant Client 已加载(项目内置)lib_dir={bundled_dir}")
else: else:
oracledb.init_oracle_client() # 默认搜索 PATH / 注册表 / ORACLE_HOME oracledb.init_oracle_client() # 默认搜索 PATH / 注册表 / ORACLE_HOME
...@@ -351,7 +363,6 @@ def _ensure_oracle_client(lib_dir: Optional[str] = None) -> str: ...@@ -351,7 +363,6 @@ def _ensure_oracle_client(lib_dir: Optional[str] = None) -> str:
# (Instant Client 12.x 是用 MSVC 2013 编译的,依赖 MSVCR120.dll; # (Instant Client 12.x 是用 MSVC 2013 编译的,依赖 MSVCR120.dll;
# 装 vcredist 2015-2022 不够,必须装 VC++ 2013 Redist) # 装 vcredist 2015-2022 不够,必须装 VC++ 2013 Redist)
if "DPI-1047" in err_str: if "DPI-1047" in err_str:
import os
oci_in_lib_dir = False oci_in_lib_dir = False
if lib_dir: if lib_dir:
oci_in_lib_dir = os.path.exists(os.path.join(lib_dir, "oci.dll")) oci_in_lib_dir = os.path.exists(os.path.join(lib_dir, "oci.dll"))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment