## P1 数据库基础 - zqyy_app: 创建 auth/biz schema、FDW 连接 etl_feiqiu - etl_feiqiu: 创建 app schema RLS 视图、商品库存预警表 - 清理 assistant_abolish 残留数据 ## P2 ETL/DWS 扩展 - 新增 DWS 助教订单贡献度表 (dws.assistant_order_contribution) - 新增 assistant_order_contribution_task 任务及 RLS 视图 - member_consumption 增加充值字段、assistant_daily 增加处罚字段 - 更新 ODS/DWD/DWS 任务文档及业务规则文档 - 更新 consistency_checker、flow_runner、task_registry 等核心模块 ## P3 小程序鉴权系统 - 新增 xcx_auth 路由/schema(微信登录 + JWT) - 新增 wechat/role/matching/application 服务层 - zqyy_app 鉴权表迁移 + 角色权限种子数据 - auth/dependencies.py 支持小程序 JWT 鉴权 ## 文档与审计 - 新增 DOCUMENTATION-MAP 文档导航 - 新增 7 份 BD_Manual 数据库变更文档 - 更新 DDL 基线快照(etl_feiqiu 6 schema + zqyy_app auth) - 新增全栈集成审计记录、部署检查清单更新 - 新增 BACKLOG 路线图、FDW→Core 迁移计划 ## Kiro 工程化 - 新增 5 个 Spec(P1/P2/P3/全栈集成/核心业务) - 新增审计自动化脚本(agent_on_stop/build_audit_context/compliance_prescan) - 新增 6 个 Hook(合规检查/会话日志/提交审计等) - 新增 doc-map steering 文件 ## 运维与测试 - 新增 ops 脚本:迁移验证/API 健康检查/ETL 监控/集成报告 - 新增属性测试:test_dws_contribution / test_auth_system - 清理过期 export 报告文件 - 更新 .gitignore 排除规则
246 lines
7.7 KiB
Python
246 lines
7.7 KiB
Python
"""
|
||
服务器环境初始化脚本:删除 skip-worktree 排除的文件/目录 + 创建 export 目录树。
|
||
|
||
在服务器上 git clone + setup-server-git.py 之后运行。
|
||
1. 删除已被 skip-worktree 标记的目录和散文件(释放磁盘空间)
|
||
2. 创建完整的 export/ 目录树(ETL/SYSTEM/BACKEND 三大类)
|
||
|
||
用法:
|
||
python scripts/server/init-server-env.py # 初始化 test + prod(默认)
|
||
python scripts/server/init-server-env.py --envs test # 只初始化 test
|
||
python scripts/server/init-server-env.py --envs prod # 只初始化 prod
|
||
python scripts/server/init-server-env.py --envs test prod
|
||
"""
|
||
|
||
import argparse
|
||
import os
|
||
import shutil
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
# ============================================================================
|
||
# 配置
|
||
# ============================================================================
|
||
|
||
SERVER_ROOT = Path(r"D:\NeoZQYY")
|
||
|
||
ENV_PATHS: dict[str, Path] = {
|
||
"test": SERVER_ROOT / "test" / "repo",
|
||
"prod": SERVER_ROOT / "prod" / "repo",
|
||
}
|
||
|
||
# skip-worktree 后可安全删除的目录(与 setup-server-git.py DELETABLE_DIRS 一致)
|
||
DELETABLE_DIRS = [
|
||
"export",
|
||
"docs",
|
||
"tests",
|
||
"samples",
|
||
"infra",
|
||
".kiro",
|
||
".hypothesis",
|
||
".pytest_cache",
|
||
r"apps\miniprogram",
|
||
r"scripts\ops",
|
||
r"scripts\audit",
|
||
r"scripts\migrate",
|
||
]
|
||
|
||
# 可安全删除的根目录散文件
|
||
DELETABLE_FILES = [
|
||
"coach-detail-full.png",
|
||
"customer-detail-full.png",
|
||
"perf-records-current.png",
|
||
"white-screen-debug.png",
|
||
"NeoZQYY.code-workspace",
|
||
"start-admin.bat",
|
||
".kiroignore",
|
||
]
|
||
|
||
# export 目录树(所有环境通用)
|
||
EXPORT_DIRS = [
|
||
r"export\ETL-Connectors\feiqiu\JSON",
|
||
r"export\ETL-Connectors\feiqiu\LOGS",
|
||
r"export\ETL-Connectors\feiqiu\REPORTS",
|
||
r"export\SYSTEM\LOGS",
|
||
r"export\SYSTEM\REPORTS\dataflow_analysis",
|
||
r"export\SYSTEM\REPORTS\field_audit",
|
||
r"export\SYSTEM\REPORTS\full_dataflow_doc",
|
||
r"export\SYSTEM\CACHE\api_samples",
|
||
r"export\BACKEND\LOGS",
|
||
]
|
||
|
||
|
||
# ============================================================================
|
||
# 颜色输出(Windows 终端支持 ANSI)
|
||
# ============================================================================
|
||
|
||
class C:
|
||
"""ANSI 颜色码"""
|
||
RED = "\033[91m"
|
||
GREEN = "\033[92m"
|
||
YELLOW = "\033[93m"
|
||
CYAN = "\033[96m"
|
||
MAGENTA = "\033[95m"
|
||
WHITE = "\033[97m"
|
||
GRAY = "\033[90m"
|
||
RESET = "\033[0m"
|
||
|
||
|
||
def cprint(msg: str, color: str = C.WHITE) -> None:
|
||
print(f"{color}{msg}{C.RESET}")
|
||
|
||
|
||
# ============================================================================
|
||
# 函数
|
||
# ============================================================================
|
||
|
||
def get_dir_size_mb(path: Path) -> float:
|
||
"""递归计算目录大小(MB)"""
|
||
total = 0
|
||
try:
|
||
for f in path.rglob("*"):
|
||
if f.is_file():
|
||
total += f.stat().st_size
|
||
except OSError:
|
||
pass
|
||
return round(total / (1024 * 1024), 1)
|
||
|
||
|
||
def get_file_size_mb(path: Path) -> float:
|
||
try:
|
||
return round(path.stat().st_size / (1024 * 1024), 2)
|
||
except OSError:
|
||
return 0.0
|
||
|
||
|
||
def remove_skip_worktree_items(repo_path: Path) -> None:
|
||
"""删除指定 repo 下已被 skip-worktree 标记的目录和散文件。"""
|
||
cprint("\n [删除] 清理 skip-worktree 排除的目录和文件...", C.YELLOW)
|
||
freed_mb = 0.0
|
||
|
||
# 删除目录
|
||
for d in DELETABLE_DIRS:
|
||
full = repo_path / d
|
||
if full.exists() and full.is_dir():
|
||
size = get_dir_size_mb(full)
|
||
shutil.rmtree(full, ignore_errors=True)
|
||
cprint(f" 已删除: {d}/ ({size} MB)", C.RED)
|
||
freed_mb += size
|
||
else:
|
||
cprint(f" 跳过: {d}/ (不存在)", C.GRAY)
|
||
|
||
# 删除散文件
|
||
for f in DELETABLE_FILES:
|
||
full = repo_path / f
|
||
if full.exists() and full.is_file():
|
||
size = get_file_size_mb(full)
|
||
full.unlink()
|
||
cprint(f" 已删除: {f} ({size} MB)", C.RED)
|
||
freed_mb += size
|
||
|
||
# 删除根目录下所有 .png 文件
|
||
for png in repo_path.glob("*.png"):
|
||
if png.is_file():
|
||
size = get_file_size_mb(png)
|
||
png.unlink()
|
||
cprint(f" 已删除: {png.name} ({size} MB)", C.RED)
|
||
freed_mb += size
|
||
|
||
cprint(f" 共释放: {freed_mb:.1f} MB", C.GREEN)
|
||
|
||
|
||
def create_export_tree(repo_path: Path) -> None:
|
||
"""在指定 repo 下创建完整的 export 目录树。"""
|
||
cprint("\n [创建] 初始化 export 目录树...", C.YELLOW)
|
||
|
||
for d in EXPORT_DIRS:
|
||
full = repo_path / d
|
||
if not full.exists():
|
||
full.mkdir(parents=True, exist_ok=True)
|
||
cprint(f" 已创建: {d}/", C.CYAN)
|
||
else:
|
||
cprint(f" 已存在: {d}/", C.GRAY)
|
||
|
||
|
||
def test_git_setup(repo_path: Path) -> bool:
|
||
"""检查 setup-server-git.py 是否已运行。"""
|
||
exclude_file = repo_path / ".git" / "info" / "exclude"
|
||
if not exclude_file.exists():
|
||
return False
|
||
try:
|
||
content = exclude_file.read_text(encoding="utf-8", errors="ignore")
|
||
return "server-exclude" in content
|
||
except OSError:
|
||
return False
|
||
|
||
|
||
# ============================================================================
|
||
# 主流程
|
||
# ============================================================================
|
||
|
||
def main() -> None:
|
||
parser = argparse.ArgumentParser(description="NeoZQYY 服务器环境初始化")
|
||
parser.add_argument(
|
||
"--envs",
|
||
nargs="+",
|
||
default=["test", "prod"],
|
||
choices=["test", "prod"],
|
||
help="要初始化的环境列表(默认: test prod)",
|
||
)
|
||
args = parser.parse_args()
|
||
|
||
# Windows 终端启用 ANSI 颜色
|
||
if sys.platform == "win32":
|
||
os.system("") # 触发 ANSI 支持
|
||
|
||
cprint("=" * 44, C.WHITE)
|
||
cprint(" NeoZQYY 服务器环境初始化", C.WHITE)
|
||
cprint(f" 目标环境: {', '.join(args.envs)}", C.WHITE)
|
||
cprint("=" * 44, C.WHITE)
|
||
|
||
for env in args.envs:
|
||
repo_path = ENV_PATHS.get(env)
|
||
if not repo_path:
|
||
cprint(f"\n[错误] 未知环境: {env}(可选: test, prod)", C.RED)
|
||
continue
|
||
|
||
cprint(f"\n{'=' * 10} 环境: {env} {'=' * 10}", C.MAGENTA)
|
||
cprint(f" 路径: {repo_path}")
|
||
|
||
# 检查 repo 是否存在
|
||
if not repo_path.exists():
|
||
cprint(" [警告] 目录不存在,跳过。请先 git clone。", C.YELLOW)
|
||
continue
|
||
|
||
# 检查 setup-server-git.py 是否已运行
|
||
if not test_git_setup(repo_path):
|
||
cprint(" [警告] 未检测到 setup-server-git.py 的配置。", C.YELLOW)
|
||
cprint(" 建议先运行: python scripts/server/setup-server-git.py", C.YELLOW)
|
||
answer = input(" 是否继续删除操作?(y/N) ").strip()
|
||
if answer.lower() != "y":
|
||
cprint(f" 已跳过 {env} 环境的删除操作。", C.GRAY)
|
||
# 仍然创建 export 目录
|
||
create_export_tree(repo_path)
|
||
continue
|
||
|
||
# 步骤 1:删除排除的文件/目录
|
||
remove_skip_worktree_items(repo_path)
|
||
|
||
# 步骤 2:创建 export 目录树
|
||
create_export_tree(repo_path)
|
||
|
||
cprint(f"\n [完成] {env} 环境初始化完毕。", C.GREEN)
|
||
|
||
cprint("\n" + "=" * 44, C.WHITE)
|
||
cprint(" 全部完成。", C.GREEN)
|
||
print()
|
||
cprint(" 后续步骤:", C.WHITE)
|
||
cprint(" 1. 手动创建各环境的 .env 文件(参考 .env.template)", C.WHITE)
|
||
cprint(" 2. 确认 .env 中的 export 路径指向 repo/export/ 下对应子目录", C.WHITE)
|
||
cprint(" 3. 运行 uv sync --all-packages 安装依赖", C.WHITE)
|
||
cprint("=" * 44, C.WHITE)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|