🔧 완전한 스키마 자동화 시스템 구축
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled

 주요 기능:
- 완전한 데이터베이스 스키마 분석 및 자동 마이그레이션 시스템
- 44개 테이블 완전 지원 (운영 서버 43개 + 1개 추가)
- 누락된 테이블/컬럼 자동 감지 및 생성

🔧 해결된 스키마 문제:
- users.status 컬럼 누락 → 자동 추가
- files 테이블 4개 컬럼 누락 → 자동 추가
- materials 테이블 22개 컬럼 누락 → 자동 추가
- support_details, purchase_requests, purchase_request_items 테이블 누락 → 자동 생성
- material_purchase_tracking.description, purchase_status 컬럼 누락 → 자동 추가

🚀 자동화 도구:
- schema_analyzer.py: 코드와 DB 스키마 비교 분석
- auto_migrator.py: 자동 마이그레이션 실행
- docker_migrator.py: Docker 환경용 간편 마이그레이션
- schema_monitor.py: 실시간 스키마 모니터링

📋 리비전 관리 시스템:
- 8개 카테고리별 리비전 페이지 구현
- PIPE Cutting Plan 관리 시스템
- PIPE Issue Management 시스템
- 완전한 리비전 비교 및 추적 기능

🎯 사용법:
docker exec tk-mp-backend python3 scripts/docker_migrator.py

앞으로 스키마 문제가 발생하면 위 명령 하나로 자동 해결!
This commit is contained in:
Hyungi Ahn
2025-10-21 10:34:45 +09:00
parent 9d7165bbf9
commit 8f42a1054e
55 changed files with 22443 additions and 0 deletions

View File

@@ -0,0 +1,278 @@
#!/usr/bin/env python3
"""
스키마 모니터링 시스템 - 코드 변경사항을 감지하고 스키마 분석을 자동으로 실행
"""
import os
import sys
import time
import json
from datetime import datetime
from typing import Dict, List
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from schema_analyzer import SchemaAnalyzer
from auto_migrator import AutoMigrator
class SchemaMonitor(FileSystemEventHandler):
def __init__(self):
self.analyzer = SchemaAnalyzer()
self.migrator = AutoMigrator()
self.last_check = datetime.now()
self.monitored_files = [
'backend/app/models.py',
'backend/app/auth/models.py'
]
self.change_log = []
def on_modified(self, event):
"""파일 변경 감지"""
if event.is_directory:
return
# 모니터링 대상 파일인지 확인
file_path = event.src_path.replace('\\', '/')
if not any(monitored in file_path for monitored in self.monitored_files):
return
print(f"📝 파일 변경 감지: {file_path}")
# 변경 로그 기록
self.change_log.append({
'file': file_path,
'timestamp': datetime.now().isoformat(),
'event_type': 'modified'
})
# 스키마 분석 실행 (디바운싱 적용)
self._schedule_schema_check()
def _schedule_schema_check(self):
"""스키마 체크 스케줄링 (디바운싱)"""
# 마지막 체크로부터 5초 후에 실행
time.sleep(5)
current_time = datetime.now()
if (current_time - self.last_check).seconds >= 5:
self.last_check = current_time
self._run_schema_check()
def _run_schema_check(self):
"""스키마 체크 실행"""
print("\n🔍 스키마 변경사항 체크 중...")
# 분석 실행
self.analyzer.analyze_code_models()
self.analyzer.analyze_db_schema()
analysis_result = self.analyzer.compare_schemas()
# 변경사항이 있는지 확인
has_changes = (
analysis_result['missing_tables'] or
analysis_result['missing_columns']
)
if has_changes:
print("⚠️ 스키마 불일치 발견!")
self.analyzer.print_summary()
# 자동 마이그레이션 실행 여부 확인
self._handle_schema_changes(analysis_result)
else:
print("✅ 스키마가 동기화되어 있습니다.")
def _handle_schema_changes(self, analysis_result: Dict):
"""스키마 변경사항 처리"""
print("\n🤖 자동 마이그레이션을 실행하시겠습니까?")
print("1. 자동 실행")
print("2. SQL 파일만 생성")
print("3. 무시")
# 개발 환경에서는 자동으로 실행
choice = "1" # 자동 실행
if choice == "1":
print("🚀 자동 마이그레이션 실행 중...")
success = self.migrator._execute_migrations(analysis_result)
if success:
print("✅ 마이그레이션 완료!")
self._notify_migration_success(analysis_result)
else:
print("❌ 마이그레이션 실패!")
self._notify_migration_failure(analysis_result)
elif choice == "2":
migration_sql = self.analyzer.generate_migration_sql()
filename = f"migration_{datetime.now().strftime('%Y%m%d_%H%M%S')}.sql"
with open(filename, 'w', encoding='utf-8') as f:
f.write(migration_sql)
print(f"📝 마이그레이션 SQL 생성: {filename}")
def _notify_migration_success(self, analysis_result: Dict):
"""마이그레이션 성공 알림"""
notification = {
'type': 'MIGRATION_SUCCESS',
'timestamp': datetime.now().isoformat(),
'changes': {
'tables_created': len(analysis_result['missing_tables']),
'columns_added': len(analysis_result['missing_columns'])
}
}
self._save_notification(notification)
def _notify_migration_failure(self, analysis_result: Dict):
"""마이그레이션 실패 알림"""
notification = {
'type': 'MIGRATION_FAILURE',
'timestamp': datetime.now().isoformat(),
'analysis_result': analysis_result
}
self._save_notification(notification)
def _save_notification(self, notification: Dict):
"""알림 저장"""
notifications_file = 'schema_notifications.json'
notifications = []
if os.path.exists(notifications_file):
with open(notifications_file, 'r', encoding='utf-8') as f:
notifications = json.load(f)
notifications.append(notification)
# 최근 100개만 유지
notifications = notifications[-100:]
with open(notifications_file, 'w', encoding='utf-8') as f:
json.dump(notifications, f, indent=2, ensure_ascii=False)
def start_monitoring(self):
"""모니터링 시작"""
print("👀 스키마 모니터링 시작...")
print("모니터링 대상 파일:")
for file_path in self.monitored_files:
print(f" - {file_path}")
observer = Observer()
observer.schedule(self, 'backend/app', recursive=True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
print("\n🛑 모니터링 중단")
observer.join()
class SchemaValidator:
"""스키마 검증 도구"""
def __init__(self):
self.analyzer = SchemaAnalyzer()
def validate_deployment_readiness(self) -> bool:
"""배포 준비 상태 검증"""
print("🔍 배포 준비 상태 검증 중...")
# 스키마 분석
self.analyzer.analyze_code_models()
self.analyzer.analyze_db_schema()
analysis_result = self.analyzer.compare_schemas()
# 검증 결과
is_ready = not (
analysis_result['missing_tables'] or
analysis_result['missing_columns']
)
if is_ready:
print("✅ 배포 준비 완료! 스키마가 완전히 동기화되어 있습니다.")
else:
print("❌ 배포 준비 미완료! 스키마 불일치가 발견되었습니다.")
self.analyzer.print_summary()
return is_ready
def generate_deployment_migration(self) -> str:
"""배포용 마이그레이션 스크립트 생성"""
print("📝 배포용 마이그레이션 스크립트 생성 중...")
self.analyzer.analyze_code_models()
self.analyzer.analyze_db_schema()
analysis_result = self.analyzer.compare_schemas()
if not analysis_result['missing_tables'] and not analysis_result['missing_columns']:
print("✅ 마이그레이션이 필요하지 않습니다.")
return ""
# 배포용 마이그레이션 스크립트 생성
migration_sql = self.analyzer.generate_migration_sql()
# 안전성 체크 추가
safe_migration = self._add_safety_checks(migration_sql)
filename = f"deployment_migration_{datetime.now().strftime('%Y%m%d_%H%M%S')}.sql"
with open(filename, 'w', encoding='utf-8') as f:
f.write(safe_migration)
print(f"📄 배포용 마이그레이션 생성: {filename}")
return filename
def _add_safety_checks(self, migration_sql: str) -> str:
"""마이그레이션에 안전성 체크 추가"""
safety_header = """-- 배포용 마이그레이션 스크립트
-- 생성 시간: {timestamp}
-- 주의: 프로덕션 환경에서 실행하기 전에 백업을 수행하세요!
-- 트랜잭션 시작
BEGIN;
-- 백업 테이블 생성 (필요시)
-- CREATE TABLE users_backup AS SELECT * FROM users;
""".format(timestamp=datetime.now())
safety_footer = """
-- 검증 쿼리 (필요시 주석 해제)
-- SELECT COUNT(*) FROM users WHERE status IS NOT NULL;
-- 모든 것이 정상이면 커밋, 문제가 있으면 ROLLBACK 실행
COMMIT;
-- ROLLBACK;
"""
return safety_header + migration_sql + safety_footer
def main():
import argparse
parser = argparse.ArgumentParser(description='TK-MP-Project 스키마 모니터링 도구')
parser.add_argument('--mode', choices=['monitor', 'validate', 'deploy'],
default='monitor', help='실행 모드')
args = parser.parse_args()
if args.mode == 'monitor':
monitor = SchemaMonitor()
monitor.start_monitoring()
elif args.mode == 'validate':
validator = SchemaValidator()
is_ready = validator.validate_deployment_readiness()
sys.exit(0 if is_ready else 1)
elif args.mode == 'deploy':
validator = SchemaValidator()
migration_file = validator.generate_deployment_migration()
if migration_file:
print(f"배포 시 다음 명령으로 실행: psql -f {migration_file}")
if __name__ == "__main__":
main()