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 앞으로 스키마 문제가 발생하면 위 명령 하나로 자동 해결!
200 lines
6.8 KiB
Python
200 lines
6.8 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
자동 마이그레이션 실행기 - 스키마 분석 결과를 바탕으로 자동으로 DB를 업데이트
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import psycopg2
|
||
from psycopg2.extras import RealDictCursor
|
||
from datetime import datetime
|
||
from schema_analyzer import SchemaAnalyzer
|
||
|
||
class AutoMigrator:
|
||
def __init__(self):
|
||
self.db_config = {
|
||
'host': 'localhost',
|
||
'port': 5432,
|
||
'database': 'tk_mp_bom',
|
||
'user': 'tkmp_user',
|
||
'password': 'tkmp_password'
|
||
}
|
||
self.analyzer = SchemaAnalyzer()
|
||
self.migration_log = []
|
||
|
||
def run_full_analysis_and_migration(self):
|
||
"""전체 분석 및 마이그레이션 실행"""
|
||
print("🚀 자동 마이그레이션 시작")
|
||
|
||
# 1. 스키마 분석
|
||
print("\n1️⃣ 스키마 분석 중...")
|
||
self.analyzer.analyze_code_models()
|
||
self.analyzer.analyze_db_schema()
|
||
analysis_result = self.analyzer.compare_schemas()
|
||
|
||
# 2. 분석 결과 확인
|
||
if not analysis_result['missing_tables'] and not analysis_result['missing_columns']:
|
||
print("✅ 스키마가 이미 최신 상태입니다!")
|
||
return True
|
||
|
||
# 3. 마이그레이션 실행
|
||
print("\n2️⃣ 마이그레이션 실행 중...")
|
||
success = self._execute_migrations(analysis_result)
|
||
|
||
# 4. 결과 로깅
|
||
self._log_migration_result(success, analysis_result)
|
||
|
||
return success
|
||
|
||
def _execute_migrations(self, analysis_result) -> bool:
|
||
"""마이그레이션 실행"""
|
||
try:
|
||
conn = psycopg2.connect(**self.db_config)
|
||
cursor = conn.cursor()
|
||
|
||
# 트랜잭션 시작
|
||
conn.autocommit = False
|
||
|
||
# 1. 누락된 테이블 생성
|
||
for table_name in analysis_result['missing_tables']:
|
||
success = self._create_missing_table(cursor, table_name)
|
||
if not success:
|
||
conn.rollback()
|
||
return False
|
||
|
||
# 2. 누락된 컬럼 추가
|
||
for missing_col in analysis_result['missing_columns']:
|
||
success = self._add_missing_column(cursor, missing_col)
|
||
if not success:
|
||
conn.rollback()
|
||
return False
|
||
|
||
# 커밋
|
||
conn.commit()
|
||
cursor.close()
|
||
conn.close()
|
||
|
||
print("✅ 마이그레이션 성공!")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"❌ 마이그레이션 실패: {e}")
|
||
if 'conn' in locals():
|
||
conn.rollback()
|
||
return False
|
||
|
||
def _create_missing_table(self, cursor, table_name: str) -> bool:
|
||
"""누락된 테이블 생성"""
|
||
try:
|
||
create_sql = self.analyzer._generate_create_table_sql(table_name)
|
||
print(f" 📋 테이블 생성: {table_name}")
|
||
cursor.execute(create_sql)
|
||
|
||
self.migration_log.append({
|
||
'type': 'CREATE_TABLE',
|
||
'table': table_name,
|
||
'sql': create_sql,
|
||
'timestamp': datetime.now().isoformat()
|
||
})
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f" ❌ 테이블 생성 실패 {table_name}: {e}")
|
||
return False
|
||
|
||
def _add_missing_column(self, cursor, missing_col: dict) -> bool:
|
||
"""누락된 컬럼 추가"""
|
||
try:
|
||
alter_sql = self.analyzer._generate_add_column_sql(missing_col)
|
||
print(f" 🔧 컬럼 추가: {missing_col['table']}.{missing_col['column']}")
|
||
cursor.execute(alter_sql)
|
||
|
||
self.migration_log.append({
|
||
'type': 'ADD_COLUMN',
|
||
'table': missing_col['table'],
|
||
'column': missing_col['column'],
|
||
'sql': alter_sql,
|
||
'timestamp': datetime.now().isoformat()
|
||
})
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f" ❌ 컬럼 추가 실패 {missing_col['table']}.{missing_col['column']}: {e}")
|
||
return False
|
||
|
||
def _log_migration_result(self, success: bool, analysis_result: dict):
|
||
"""마이그레이션 결과 로깅"""
|
||
log_entry = {
|
||
'timestamp': datetime.now().isoformat(),
|
||
'success': success,
|
||
'analysis_result': analysis_result,
|
||
'migration_log': self.migration_log
|
||
}
|
||
|
||
# 로그 파일에 저장
|
||
import json
|
||
log_filename = f"migration_log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
||
|
||
with open(log_filename, 'w', encoding='utf-8') as f:
|
||
json.dump(log_entry, f, indent=2, ensure_ascii=False)
|
||
|
||
print(f"📄 마이그레이션 로그 저장: {log_filename}")
|
||
|
||
def fix_immediate_issues(self):
|
||
"""즉시 해결이 필요한 문제들 수정"""
|
||
print("🔧 즉시 해결 필요한 문제들 수정 중...")
|
||
|
||
immediate_fixes = [
|
||
{
|
||
'description': 'users 테이블에 status 컬럼 추가',
|
||
'sql': "ALTER TABLE users ADD COLUMN status VARCHAR(20) DEFAULT 'active'",
|
||
'check_sql': "SELECT column_name FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'status'"
|
||
}
|
||
]
|
||
|
||
try:
|
||
conn = psycopg2.connect(**self.db_config)
|
||
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
||
|
||
for fix in immediate_fixes:
|
||
# 이미 존재하는지 확인
|
||
cursor.execute(fix['check_sql'])
|
||
if cursor.fetchone():
|
||
print(f" ✅ 이미 존재: {fix['description']}")
|
||
continue
|
||
|
||
# 수정 실행
|
||
cursor.execute(fix['sql'])
|
||
conn.commit()
|
||
print(f" 🔧 수정 완료: {fix['description']}")
|
||
|
||
cursor.close()
|
||
conn.close()
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"❌ 즉시 수정 실패: {e}")
|
||
return False
|
||
|
||
def main():
|
||
migrator = AutoMigrator()
|
||
|
||
# 1. 즉시 해결 필요한 문제들 수정
|
||
migrator.fix_immediate_issues()
|
||
|
||
# 2. 전체 분석 및 마이그레이션
|
||
success = migrator.run_full_analysis_and_migration()
|
||
|
||
if success:
|
||
print("\n🎉 모든 마이그레이션이 성공적으로 완료되었습니다!")
|
||
else:
|
||
print("\n💥 마이그레이션 중 오류가 발생했습니다. 로그를 확인해주세요.")
|
||
|
||
return success
|
||
|
||
if __name__ == "__main__":
|
||
main()
|