init: 初始化 dpb 桃育种系统代码库

前后端 + 后端 FastAPI 全量源码、部署脚本与文档。
This commit is contained in:
34047007@qq.com
2026-08-06 00:17:49 +08:00
commit b95053c52c
1469 changed files with 322298 additions and 0 deletions
+86
View File
@@ -0,0 +1,86 @@
"""波次5.4:业务必填列 NOT NULL 对齐(幂等,仅 PostgreSQL)。
背景:ORM 声明 NOT NULL、线上库却允许 NULL 的业务必填列(多为父记录外键),
与「严谨的业务逻辑关系」相悖。本脚本将这类列收紧为 NOT NULL,使库约束与 ORM 一致。
安全前提:先校验该列无 NULL 值(有 NULL 则跳过并上报),无 NULL 才执行 ALTER,
故 SET NOT NULL 不会因存量数据失败。相关外键均为 NO ACTION / CASCADE(无 SET NULL),
与 NOT NULL 兼容。
幂等:已 NOT NULL 的列跳过;可重复执行。
用法(backend/ 目录):
python scripts/align_columns.py --check # 只读预览
python scripts/align_columns.py # 执行
"""
import argparse
import os
import sys
BACKEND_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if BACKEND_DIR not in sys.path:
sys.path.insert(0, BACKEND_DIR)
from sqlalchemy import create_engine, text
from app.config.setting import settings
# ORM 声明必填(NOT NULL)、线上库却可空的业务列 → 收紧
COLUMNS = [
("bre_pedigree", "combination_id"),
("bre_pedigree", "child_code"),
("bre_selection_result", "tree_id"),
("bre_selection_rule", "target_id"),
("bre_prediction_value", "prediction_id"),
("bre_treatment", "trial_study_id"),
("bre_tree_evaluation", "tree_id"),
("bre_trial_study", "trial_id"),
("bre_trial_study_entry", "trial_study_id"),
("bre_trial_study_entry", "entry_number"),
("bre_trial_study_entry", "germplasm_id"),
]
def main() -> None:
parser = argparse.ArgumentParser(description="业务必填列 NOT NULL 对齐")
parser.add_argument("--check", action="store_true", help="仅打印将执行的 DDL,不落库")
args = parser.parse_args()
engine = create_engine(settings.DB_URI)
print(f"# 连接: {settings.DB_URI}")
# 每列用单事务:读锁(count)与 ALTERACCESS EXCLUSIVE)在同一事务内升级,
# 避免跨连接自锁(读连接持锁阻塞另一连接的 ALTER)。
for t, c in COLUMNS:
with engine.begin() as tx:
try:
row = tx.execute(text(
"SELECT is_nullable FROM information_schema.columns "
"WHERE table_name=:t AND column_name=:c"
), {"t": t, "c": c}).fetchone()
except Exception as e: # noqa: BLE001
print(f"# [跳过] {t}.{c}: 查列失败 {e!s}")
continue
if row is None:
print(f"# [跳过] {t}.{c}: 列不存在")
continue
if row[0] == "NO":
print(f"# [已存在] {t}.{c} 已 NOT NULL")
continue
nulls = tx.execute(text(f"SELECT count(*) FROM {t} WHERE {c} IS NULL")).scalar()
if nulls:
print(f"# [跳过] {t}.{c}: 存在 {nulls} 个 NULL 值,拒绝收紧")
continue
ddl = f"ALTER TABLE {t} ALTER COLUMN {c} SET NOT NULL"
print(" " + ddl)
if not args.check:
try:
tx.execute(text(ddl))
print("OK " + ddl)
except Exception as e: # noqa: BLE001
print("ERR " + ddl + " -> " + str(e)[:160])
print("# --check 模式:未执行任何变更。" if args.check else "# 完成。")
if __name__ == "__main__":
main()