# 法律事务管理系统 - Backend Makefile # ============================================================ check: lint typecheck ## 运行所有检查 # ============================================================ # GitHub CI 镜像检查(推送前必跑,避免反复修 7 轮) # ============================================================ ci-check: ## 模拟 GitHub backend (3.12) job 的核心检查(changed-files 模式) @echo "$(GREEN)=== GitHub CI 预检 (changed-files 模式) ===$(NC)" @echo "" @if [ -n "$$CI_CHECK_BASE" ]; then \ BASE="$$CI_CHECK_BASE"; \ else \ UPSTREAM=$$(git rev-parse --abbrev-ref @{u} 2>/dev/null | sed 's|origin/||' || true); \ if [ -n "$$UPSTREAM" ]; then \ BASE="$$UPSTREAM"; \ else \ BASE="main"; \ fi; \ fi; \ if ! git rev-parse "$$BASE" &>/dev/null 2>&1; then \ echo "⚠️ 基准 '$$BASE' 不存在,尝试 merge-base..."; \ BASE=$$(git merge-base HEAD HEAD~1 2>/dev/null || echo ""); \ fi; \ echo "📋 基准: $(YELLOW)$$BASE$(NC) → $(YELLOW)HEAD$(NC)"; \ CHANGED=$$(git diff --name-only --diff-filter=ACMRT $$BASE HEAD 2>/dev/null | grep -E '^apps/.*\.py$$' | sed 's|^apps/||' || true); \ if [ -z "$$CHANGED" ]; then \ echo "✅ 无变更 Python 文件"; \ else \ echo "🔍 [1/3] Ruff (changed files): $$CHANGED"; \ $(PYTHON) -m ruff check $$CHANGED --config ruff.toml; \ echo ""; \ echo "🔍 [2/3] Mypy (changed files, follow-imports=skip): $$CHANGED"; \ PYTHONPATH=apiSystem:. $(PYTHON) -m mypy --config-file mypy.ini --follow-imports=skip $$CHANGED; \ echo ""; \ fi @echo "🔍 [3/3] 测试收集 (structure smoke):" $(TEST_ENV) $(PYTEST) tests/ci/structure/ --co -q 2>&1 | tail -3 @echo "" @echo "$(GREEN)✅ GitHub CI 预检通过! 可以放心推送了$(NC)" ci-check-full: ## 完整 CI 预检 (含 unit/integration/property smoke) $(MAKE) ci-check @echo "🧪 运行 CI 测试套件..." $(TEST_ENV) $(PYTEST) tests/ci/unit/ -q $(ARGS) $(TEST_ENV) $(PYTEST) tests/ci/integration/ -q $(ARGS) $(TEST_ENV) HYPOTHESIS_PROFILE=default $(PYTEST) tests/ci/property/ -q $(ARGS) .PHONY: help install run test migrate shell lint clean ci-check ci-check-full qcluster-dev # 默认 Python 和虚拟环境 VENV_DIR ?= .venv UV := uv PYTHON := $(VENV_DIR)/bin/python PYTEST := $(VENV_DIR)/bin/pytest MANAGE := $(PYTHON) apiSystem/manage.py UVICORN := $(VENV_DIR)/bin/uvicorn WATCHFILES := $(VENV_DIR)/bin/watchfiles PORT ?= 8002 RUN_DEV_FORCE_POLLING ?= true RUN_DEV_POLL_DELAY_MS ?= 300 QCLUSTER_DEV_FORCE_POLLING ?= true QCLUSTER_DEV_POLL_DELAY_MS ?= 300 QCLUSTER_WATCH_PATHS ?= apps apiSystem # 数据库配置(默认 PostgreSQL,可通过 DB_ENGINE=sqlite 切换) DB_ENGINE ?= postgresql DATABASE_PATH ?= DB_NAME ?= fachuan_dev DB_USER ?= postgres DB_PASSWORD ?= postgres DB_HOST ?= 127.0.0.1 DB_PORT ?= 5432 # 测试数据库配置 TEST_DB_ENGINE ?= postgresql TEST_DB_PATH ?= /tmp/fachuan_pytest.sqlite3 TEST_DB_NAME ?= fachuan_test TEST_DB_USER ?= postgres TEST_DB_PASSWORD ?= postgres TEST_DB_HOST ?= 127.0.0.1 TEST_DB_PORT ?= 5432 export DB_ENGINE DATABASE_PATH DB_NAME DB_USER DB_PASSWORD DB_HOST DB_PORT # 颜色定义 GREEN := \033[0;32m YELLOW := \033[0;33m NC := \033[0m # No Color TEST_ENV = DJANGO_DEBUG=1 DB_ENGINE=$(TEST_DB_ENGINE) DATABASE_PATH=$(TEST_DB_PATH) DB_NAME=$(TEST_DB_NAME) DB_USER=$(TEST_DB_USER) DB_PASSWORD=$(TEST_DB_PASSWORD) DB_HOST=$(TEST_DB_HOST) DB_PORT=$(TEST_DB_PORT) PYTHONPATH=apiSystem:. help: ## 显示帮助信息 @echo "$(GREEN)法律事务管理系统 - Backend$(NC)" @echo "" @echo "可用命令:" @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " $(YELLOW)%-15s$(NC) %s\n", $$1, $$2}' # ============================================================ # 安装和配置 # ============================================================ install: ## 安装生产依赖 $(UV) sync --no-dev install-dev: ## 安装全部依赖(含 dev + test) $(UV) sync venv: ## 创建虚拟环境 $(UV) venv $(VENV_DIR) --python 3.12 @echo "$(GREEN)虚拟环境已创建,请运行: source $(VENV_DIR)/bin/activate$(NC)" # ============================================================ # 开发服务器 # ============================================================ run: migrate ## 启动开发服务器 (默认端口 8002, 使用 uvicorn, 可通过 PORT=8003 修改) cd apiSystem && ../$(UVICORN) apiSystem.asgi:application --host 0.0.0.0 --port $(PORT) run-dev: migrate ## 启动开发服务器 (热重载稳定模式:默认启用 polling,避免与 qcluster 并行时卡住) cd apiSystem && WATCHFILES_FORCE_POLLING=$(RUN_DEV_FORCE_POLLING) WATCHFILES_POLL_DELAY_MS=$(RUN_DEV_POLL_DELAY_MS) ../$(UVICORN) apiSystem.asgi:application --host 0.0.0.0 --port $(PORT) --reload --reload-delay 0.5 --reload-dir ../apps --reload-dir . --reload-include '*.html' run-port: migrate ## 启动开发服务器 (自定义端口, 使用 PORT=8001) cd apiSystem && ../$(UVICORN) apiSystem.asgi:application --host 0.0.0.0 --port $(PORT) # ============================================================ # 数据库 # ============================================================ migrate: ## 运行数据库迁移 $(MANAGE) migrate makemigrations: ## 创建迁移文件 $(MANAGE) makemigrations migrations: makemigrations migrate ## 创建并运行迁移 showmigrations: ## 显示迁移状态 $(MANAGE) showmigrations resetdb: ## 重置数据库 (危险!) @echo "$(YELLOW)警告: 这将删除所有数据!$(NC)" @read -p "确认继续? [y/N] " confirm && [ "$$confirm" = "y" ] @if [ "$(DB_ENGINE)" = "sqlite" ]; then \ rm -f apiSystem/db.sqlite3; \ $(MANAGE) migrate; \ elif [ "$(DB_ENGINE)" = "postgresql" ]; then \ $(MANAGE) flush --noinput; \ $(MANAGE) migrate; \ else \ echo "$(YELLOW)未知 DB_ENGINE=$(DB_ENGINE),使用 flush + migrate 兜底$(NC)"; \ $(MANAGE) flush --noinput; \ $(MANAGE) migrate; \ fi @echo "$(GREEN)数据库已重置 (DB_ENGINE=$(DB_ENGINE))$(NC)" # ============================================================ # 用户管理 # ============================================================ superuser: ## 创建超级用户 $(MANAGE) createsuperuser # ============================================================ # 测试 # ============================================================ test: ## 运行测试 $(TEST_ENV) $(PYTEST) tests -v --tb=short $(ARGS) test-cov: ## 运行测试 (带覆盖率) $(TEST_ENV) $(PYTEST) tests -v --tb=short --cov=apps --cov-report=html --cov-report=term test-fast: ## 快速测试 (不显示详情) $(TEST_ENV) $(PYTEST) tests -q test-unit: ## 运行单元测试 $(TEST_ENV) $(PYTEST) tests/unit -v --tb=short $(ARGS) test-integration: ## 运行集成测试 $(TEST_ENV) $(PYTEST) tests/integration -v --tb=short $(ARGS) test-property: ## 运行 Property-Based Tests $(TEST_ENV) $(PYTEST) tests/property -v --tb=short $(ARGS) test-structure: ## 运行结构验证测试 $(TEST_ENV) $(PYTEST) tests/structure -v --tb=short --no-cov $(ARGS) test-admin: ## 运行 Admin 测试 $(TEST_ENV) $(PYTEST) tests/admin -v --tb=short $(ARGS) # ============================================================ # 代码质量 # ============================================================ RUFF_RATCHET_PATHS = \ apps/core/security/scrub.py \ apps/core/infrastructure/cache.py \ apps/automation/models/token.py \ apps/automation/services/automation_service_adapter.py \ apps/automation/services/scraper/core/token_service.py \ apps/automation/services/token/cache_manager.py \ apps/automation/services/token/history_recorder.py \ apps/automation/services/token/token_acquisition_orchestrator.py \ apps/automation/services/token/trigger_reasons.py \ apps/client/services/wiring.py \ apps/chat_records/services/wiring.py \ apps/core/services/wiring.py \ apps/core/services/conversation_service.py \ apps/core/services/cause_court_initialization_service.py \ apps/core/services/court_tokens/baoquan_token_service.py \ apps/litigation_ai/services/wiring.py \ apps/litigation_ai/agent/factory.py \ apps/litigation_ai/agent/llm_provider.py \ apps/litigation_ai/agent/middleware.py \ apps/litigation_ai/chains/document_type_parse_chain.py \ apps/litigation_ai/chains/litigation_draft_chain.py \ apps/litigation_ai/chains/litigation_goal_intake_chain.py \ apps/litigation_ai/chains/user_choice_parse_chain.py lint-ratchet: ## 代码检查(仅棘轮路径) $(PYTHON) -m ruff check $(RUFF_RATCHET_PATHS) --config ruff.toml lint: ## 全量代码检查 apps/ $(PYTHON) -m ruff check apps --config ruff.toml lint-full: lint ## 全量代码检查(lint 别名) format: ## 格式化代码 $(PYTHON) -m black apps --line-length=120 $(PYTHON) -m isort apps typecheck-ratchet: ## 类型检查(仅棘轮路径) $(PYTHON) -m mypy \ apps/core/infrastructure/throttling.py \ apps/core/middleware_request_id.py \ apps/core/exceptions_handlers.py \ apps/core/httpx_clients.py \ apps/core/config/django_runtime.py \ apps/core/protocols/common_protocols.py \ apps/core/dto/auth.py \ apps/core/llm/config.py \ apps/core/llm/service.py \ apps/core/llm/router.py \ apps/core/llm/backends/base.py \ apps/core/services/wiring.py \ apps/core/services/system_config_service.py \ apps/core/security/secret_codec.py \ apps/core/dependencies/automation_sms.py \ apps/core/security/scrub.py \ apps/automation/services/document/document_processing_service_adapter.py \ apps/automation/services/token/trigger_reasons.py \ apps/automation/services/scraper/core/token_service.py \ apps/cases/services/case/case_service.py \ apps/contracts/services/contract/contract_service_adapter.py \ apps/organization/services/org_access_computation_service.py \ apps/organization/services/wiring.py \ --follow-imports=skip \ --config-file mypy.ini typecheck: ## 全量类型检查 apps/ $(PYTHON) -m mypy apps --config-file mypy.ini typecheck-full: typecheck ## 全量类型检查(typecheck 别名) check: lint typecheck ## 运行所有检查 # ============================================================ # Django 工具 # ============================================================ shell: ## Django Shell $(MANAGE) shell dbshell: ## 数据库 Shell $(MANAGE) dbshell collectstatic: ## 收集静态文件 $(MANAGE) collectstatic --noinput # ============================================================ # 后台任务 # ============================================================ qcluster: migrate ## 启动 Django-Q 任务队列(与 Django Web 启动顺序不限) @pkill -9 -f 'python.*manage.py qcluster' 2>/dev/null || true @sleep 1 $(MANAGE) qcluster qcluster-start: migrate ## 启动 Django-Q 并处理待处理任务(与 Django Web 启动顺序不限) @pkill -9 -f 'python.*manage.py qcluster' 2>/dev/null || true @sleep 1 $(MANAGE) process_pending_tasks --reset $(MANAGE) qcluster qcluster-dev: migrate ## 启动 Django-Q(文件变化自动重启) @echo "$(GREEN)启动 qcluster-dev: 检测 Python 文件变化后自动重启 qcluster$(NC)" @echo "$(YELLOW)监听目录: $(QCLUSTER_WATCH_PATHS)$(NC)" @WATCHFILES_FORCE_POLLING=$(QCLUSTER_DEV_FORCE_POLLING) WATCHFILES_POLL_DELAY_MS=$(QCLUSTER_DEV_POLL_DELAY_MS) $(WATCHFILES) --target-type command --filter python --ignore-paths ".venv,node_modules,.git,__pycache__,media,staticfiles,logs,htmlcov,.pytest_cache" "$(MANAGE) qcluster" $(QCLUSTER_WATCH_PATHS) process-tasks: ## 处理待处理的爬虫任务 $(MANAGE) process_pending_tasks process-tasks-reset: ## 重置卡住任务并处理待处理任务 $(MANAGE) process_pending_tasks --reset process-tasks-dry: ## 显示待处理任务(不执行) $(MANAGE) process_pending_tasks --dry-run # ============================================================ # 爬虫测试 # ============================================================ test-court-login: ## 测试法院"一张网"登录 $(PYTHON) apps/automation/tests/test_court_zxfw_login.py check-admin: ## 检查 Admin 配置是否正确 $(PYTHON) scripts/check_admin_config.py # ============================================================ # Token 管理 # ============================================================ token-example: ## 运行 Token 使用示例 $(PYTHON) scripts/example_use_token.py test-token: ## 测试 Token 服务 PYTHONPATH=apiSystem:. $(PYTEST) apps/automation/tests/test_token_service.py -v migrate-token: ## 执行 Token 相关的数据库迁移 $(MANAGE) migrate automation @echo "$(GREEN)Token 数据库迁移完成$(NC)" debug-token: ## 调试 Token 捕获问题 $(PYTHON) scripts/debug_token_capture.py # ============================================================ # 清理 # ============================================================ clean: ## 清理临时文件 find . -type f -name "*.pyc" -delete find . -type d -name "__pycache__" -delete find . -type d -name ".pytest_cache" -exec rm -rf {} + 2>/dev/null || true find . -type f -name ".DS_Store" -delete rm -rf htmlcov/ rm -rf .coverage @echo "$(GREEN)清理完成$(NC)" clean-logs: ## 清理日志文件 rm -f logs/*.log @echo "$(GREEN)日志已清理$(NC)" # ============================================================ # Docker (可选) # ============================================================ docker-build: ## 构建 Docker 镜像 docker build -t lawfirm-backend . docker-run: ## 运行 Docker 容器 docker run -p 8000:8000 lawfirm-backend # ============================================================ # 健康检查 # ============================================================ health: ## 检查服务健康状态 @curl -sf http://localhost:$(PORT)/health/ && echo " OK" || echo "服务未运行" health-detail: ## 详细健康检查 @curl -sf http://localhost:$(PORT)/health/ && echo " OK (db connected)" || echo "服务未运行或数据库不可用" # ============================================================ # 国际化 (i18n) # ============================================================ messages: ## 提取翻译字符串,生成 .po 文件 cd apiSystem && ../$(PYTHON) manage.py makemessages -l en -l zh_Hans --ignore='.venv/*' --ignore='venv312/*' compilemessages: ## 编译 .po 为 .mo 文件 cd apiSystem && ../$(PYTHON) manage.py compilemessages