성능개선 문자인증오류수정 텔레그램메시포함 등

This commit is contained in:
CPABONG 2025-08-24 18:31:27 +09:00
parent 0761716ef5
commit d80a0ad464
20 changed files with 556 additions and 64 deletions

Binary file not shown.

View File

@ -30,6 +30,14 @@ SECRET_KEY = 'django-insecure-kst@+h&50%!m$(d!l*qbb0l7f@z#@#me__yye^$5kg%0m%1=im
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False DEBUG = False
# SMS 테스트 모드 설정
SMS_TEST_MODE = False # True: 테스트 모드 (콘솔 출력), False: 실제 SMS 발송
# 텔레그램 봇 설정
TELEGRAM_BOT_TOKEN = "5094633886:AAF_Cy3mD-3rqKQMfbgpVO39jNZDXNFZN-o" # BotFather에서 받은 토큰
TELEGRAM_CHAT_ID = "5085456424" # getUpdates로 얻은 chat_id
TELEGRAM_API_URL = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
ALLOWED_HOSTS = ['www.sillaamp.com', 'sillaamp.com', '192.168.1.119', 'localhost', '127.0.0.1', '*'] ALLOWED_HOSTS = ['www.sillaamp.com', 'sillaamp.com', '192.168.1.119', 'localhost', '127.0.0.1', '*']
CSRF_TRUSTED_ORIGINS = [ CSRF_TRUSTED_ORIGINS = [
'http://www.sillaamp.com', 'http://www.sillaamp.com',

View File

@ -144,23 +144,81 @@ sms_service = NaverCloudSMS()
def send_verification_sms(phone_number: str, verification_code: str) -> Dict[str, Any]: def send_verification_sms(phone_number: str, verification_code: str) -> Dict[str, Any]:
"""인증번호 SMS 발송 함수 (편의 함수)""" """인증번호 SMS 발송 함수 (편의 함수)"""
# 실제 SMS 발송 시도
print(f"[DEBUG] SMS 발송 시도: {phone_number} - {verification_code}")
print(f"[DEBUG] Access Key: {sms_service.access_key}")
print(f"[DEBUG] Service ID: {sms_service.service_id}")
print(f"[DEBUG] Sender Phone: {sms_service.sender_phone}")
result = sms_service.send_verification_code(phone_number, verification_code) # SMS 테스트 모드 확인
print(f"[DEBUG] SMS 발송 결과: {result}") sms_test_mode = getattr(settings, 'SMS_TEST_MODE', False)
return result if sms_test_mode:
# 테스트 모드: 콘솔에 인증번호 출력
print("=" * 60)
print("🔔 [SMS 테스트 모드] 인증번호 발송")
print(f"📱 수신번호: {phone_number}")
print(f"🔑 인증번호: {verification_code}")
print(f"📝 메시지: [신라AMP] 인증번호는 [{verification_code}]입니다. 3분 이내에 입력해주세요.")
print("=" * 60)
return {
'success': True,
'message': 'SMS 테스트 모드: 콘솔에 인증번호가 출력되었습니다.',
'test_mode': True,
'verification_code': verification_code # 테스트 모드에서만 포함
}
else:
# 실제 SMS 발송 모드
print(f"[DEBUG] 실제 SMS 발송 시도: {phone_number} - {verification_code}")
print(f"[DEBUG] Access Key: {sms_service.access_key}")
print(f"[DEBUG] Service ID: {sms_service.service_id}")
print(f"[DEBUG] Sender Phone: {sms_service.sender_phone}")
result = sms_service.send_verification_code(phone_number, verification_code)
print(f"[DEBUG] SMS 발송 결과: {result}")
return result
def send_withdrawal_approval_sms(phone_number: str, name: str) -> Dict[str, Any]: def send_withdrawal_approval_sms(phone_number: str, name: str) -> Dict[str, Any]:
"""탈퇴 승인 SMS 발송 함수 (편의 함수)""" """탈퇴 승인 SMS 발송 함수 (편의 함수)"""
return sms_service.send_withdrawal_approval_sms(phone_number, name) sms_test_mode = getattr(settings, 'SMS_TEST_MODE', False)
if sms_test_mode:
message = f"[신라AMP] {name}님의 회원탈퇴가 승인되었습니다. 그동안 이용해주셔서 감사했습니다."
print("=" * 60)
print("🔔 [SMS 테스트 모드] 탈퇴 승인 알림")
print(f"📱 수신번호: {phone_number}")
print(f"👤 대상자: {name}")
print(f"📝 메시지: {message}")
print("=" * 60)
return {
'success': True,
'message': 'SMS 테스트 모드: 탈퇴 승인 알림이 콘솔에 출력되었습니다.',
'test_mode': True
}
else:
return sms_service.send_withdrawal_approval_sms(phone_number, name)
def send_withdrawal_rejection_sms(phone_number: str, name: str, reason: str = None) -> Dict[str, Any]: def send_withdrawal_rejection_sms(phone_number: str, name: str, reason: str = None) -> Dict[str, Any]:
"""탈퇴 거부 SMS 발송 함수 (편의 함수)""" """탈퇴 거부 SMS 발송 함수 (편의 함수)"""
return sms_service.send_withdrawal_rejection_sms(phone_number, name, reason) sms_test_mode = getattr(settings, 'SMS_TEST_MODE', False)
if sms_test_mode:
if reason:
message = f"[신라AMP] {name}님의 회원탈퇴 요청이 거부되었습니다. 사유: {reason}"
else:
message = f"[신라AMP] {name}님의 회원탈퇴 요청이 거부되었습니다. 자세한 사항은 관리자에게 문의해주세요."
print("=" * 60)
print("🔔 [SMS 테스트 모드] 탈퇴 거부 알림")
print(f"📱 수신번호: {phone_number}")
print(f"👤 대상자: {name}")
print(f"📝 메시지: {message}")
if reason:
print(f"❌ 거부 사유: {reason}")
print("=" * 60)
return {
'success': True,
'message': 'SMS 테스트 모드: 탈퇴 거부 알림이 콘솔에 출력되었습니다.',
'test_mode': True
}
else:
return sms_service.send_withdrawal_rejection_sms(phone_number, name, reason)

271
A_core/telegram_utils.py Normal file
View File

@ -0,0 +1,271 @@
"""
텔레그램 유틸리티
회원가입 알림 등을 위한 텔레그램 메시지 전송 기능
"""
import requests
import threading
from django.conf import settings
from datetime import datetime
def get_client_ip(request):
"""
클라이언트의 실제 IP 주소를 추출
프록시, 로드밸런서, CDN 등을 고려하여 실제 IP를 찾음
"""
# 프록시나 로드밸런서를 통한 실제 클라이언트 IP 확인
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
# 첫 번째 IP가 실제 클라이언트 IP (쉼표로 구분된 경우)
ip = x_forwarded_for.split(',')[0].strip()
if ip and ip != '127.0.0.1':
return ip
# Cloudflare 등 CDN에서 사용하는 헤더
cf_connecting_ip = request.META.get('HTTP_CF_CONNECTING_IP')
if cf_connecting_ip and cf_connecting_ip != '127.0.0.1':
return cf_connecting_ip
# Nginx 등 리버스 프록시에서 사용
x_real_ip = request.META.get('HTTP_X_REAL_IP')
if x_real_ip and x_real_ip != '127.0.0.1':
return x_real_ip
# 기본 REMOTE_ADDR
remote_addr = request.META.get('REMOTE_ADDR', '알 수 없음')
# 로컬 환경 표시
if remote_addr == '127.0.0.1':
return f"{remote_addr} (로컬 개발환경)"
elif remote_addr.startswith('192.168.') or remote_addr.startswith('10.') or remote_addr.startswith('172.'):
return f"{remote_addr} (내부 네트워크)"
return remote_addr
def get_device_info(user_agent):
"""User-Agent에서 기기 정보 추출"""
if not user_agent:
return "알 수 없음"
# 모바일 기기 체크
if any(mobile in user_agent for mobile in ['iPhone', 'iPad', 'Android', 'Mobile']):
if 'iPhone' in user_agent:
return "iPhone"
elif 'iPad' in user_agent:
return "iPad"
elif 'Android' in user_agent:
if 'Mobile' in user_agent:
return "Android 폰"
else:
return "Android 태블릿"
else:
return "모바일"
# 데스크톱 OS 체크
if 'Windows NT' in user_agent:
if 'Windows NT 10.0' in user_agent:
return "Windows 10/11"
elif 'Windows NT 6.3' in user_agent:
return "Windows 8.1"
elif 'Windows NT 6.1' in user_agent:
return "Windows 7"
else:
return "Windows"
elif 'Macintosh' in user_agent or 'Mac OS X' in user_agent:
return "Mac"
elif 'Linux' in user_agent and 'Android' not in user_agent:
return "Linux"
return "알 수 없음"
def get_browser_info(user_agent):
"""User-Agent에서 브라우저 정보 추출"""
if not user_agent:
return "알 수 없음"
# 브라우저 체크 (순서 중요 - Chrome이 Safari 문자열도 포함하므로)
if 'Edg/' in user_agent:
return "Microsoft Edge"
elif 'Chrome/' in user_agent and 'Safari/' in user_agent:
return "Chrome"
elif 'Firefox/' in user_agent:
return "Firefox"
elif 'Safari/' in user_agent and 'Chrome' not in user_agent:
return "Safari"
elif 'Opera' in user_agent or 'OPR/' in user_agent:
return "Opera"
return "알 수 없음"
def send_telegram_message(message, chat_id=None):
"""
텔레그램 봇으로 메시지 전송
Args:
message (str): 전송할 메시지
chat_id (str, optional): 채팅 ID. 없으면 기본 설정값 사용
Returns:
dict: {'success': bool, 'error': str}
"""
try:
if not hasattr(settings, 'TELEGRAM_BOT_TOKEN') or not settings.TELEGRAM_BOT_TOKEN:
return {'success': False, 'error': 'TELEGRAM_BOT_TOKEN이 설정되지 않았습니다.'}
if not chat_id:
chat_id = getattr(settings, 'TELEGRAM_CHAT_ID', None)
if not chat_id:
return {'success': False, 'error': 'TELEGRAM_CHAT_ID가 설정되지 않았습니다.'}
url = f"https://api.telegram.org/bot{settings.TELEGRAM_BOT_TOKEN}/sendMessage"
payload = {
'chat_id': chat_id,
'text': message,
'parse_mode': 'HTML' # HTML 포맷 지원
}
response = requests.post(url, json=payload, timeout=10)
if response.status_code == 200:
result = response.json()
if result.get('ok'):
print(f"[TELEGRAM_SUCCESS] 메시지 전송 성공: {message[:50]}...")
return {'success': True, 'error': None}
else:
error_msg = result.get('description', '알 수 없는 오류')
print(f"[TELEGRAM_ERROR] API 오류: {error_msg}")
return {'success': False, 'error': f'텔레그램 API 오류: {error_msg}'}
else:
print(f"[TELEGRAM_ERROR] HTTP 오류: {response.status_code}")
return {'success': False, 'error': f'HTTP 오류: {response.status_code}'}
except requests.exceptions.Timeout:
print("[TELEGRAM_ERROR] 요청 시간 초과")
return {'success': False, 'error': '요청 시간 초과'}
except requests.exceptions.RequestException as e:
print(f"[TELEGRAM_ERROR] 네트워크 오류: {str(e)}")
return {'success': False, 'error': f'네트워크 오류: {str(e)}'}
except Exception as e:
print(f"[TELEGRAM_ERROR] 예상치 못한 오류: {str(e)}")
return {'success': False, 'error': f'예상치 못한 오류: {str(e)}'}
def send_telegram_message_async(message, chat_id=None):
"""
비동기로 텔레그램 메시지 전송 (백그라운드)
Args:
message (str): 전송할 메시지
chat_id (str, optional): 채팅 ID
"""
def _send():
send_telegram_message(message, chat_id)
thread = threading.Thread(target=_send)
thread.daemon = True
thread.start()
def send_signup_notification(name, phone, request):
"""
회원가입 문자인증 요청 알림
Args:
name (str): 가입자 이름
phone (str): 전화번호
request: Django request 객체 (IP, User-Agent 정보)
"""
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 클라이언트 정보 추출
ip = get_client_ip(request)
user_agent = request.META.get('HTTP_USER_AGENT', '알 수 없음')
device_info = get_device_info(user_agent)
browser_info = get_browser_info(user_agent)
# 디버그 정보 출력
print(f"[TELEGRAM_DEBUG] 회원가입 알림 - IP: {ip}, 기기: {device_info}, 브라우저: {browser_info}")
# 상세 헤더 정보 (개발 환경에서만)
if ip.endswith('(로컬 개발환경)'):
print(f"[DEBUG] REMOTE_ADDR: {request.META.get('REMOTE_ADDR')}")
print(f"[DEBUG] HTTP_X_FORWARDED_FOR: {request.META.get('HTTP_X_FORWARDED_FOR')}")
print(f"[DEBUG] HTTP_X_REAL_IP: {request.META.get('HTTP_X_REAL_IP')}")
print(f"[DEBUG] HTTP_CF_CONNECTING_IP: {request.META.get('HTTP_CF_CONNECTING_IP')}")
print(f"[DEBUG] User-Agent: {user_agent[:100]}...")
message = f"""
🔔 <b>신라AMP 회원가입 알림</b>
👤 <b>이름:</b> {name}
📱 <b>전화번호:</b> {phone}
<b>요청시간:</b> {current_time}
🌐 <b>접속 정보:</b>
IP 주소: <code>{ip}</code>
기기: {device_info}
브라우저: {browser_info}
💡 새로운 회원가입 요청이 있습니다!
""".strip()
# 비동기로 전송 (사용자 대기시간 없음)
send_telegram_message_async(message)
def send_password_reset_notification(phone, request):
"""
비밀번호 찾기 문자인증 요청 알림
Args:
phone (str): 전화번호
request: Django request 객체 (IP, User-Agent 정보)
"""
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 클라이언트 정보 추출
ip = get_client_ip(request)
user_agent = request.META.get('HTTP_USER_AGENT', '알 수 없음')
device_info = get_device_info(user_agent)
browser_info = get_browser_info(user_agent)
# 디버그 정보 출력
print(f"[TELEGRAM_DEBUG] 비밀번호 찾기 알림 - IP: {ip}, 기기: {device_info}, 브라우저: {browser_info}")
message = f"""
🔑 <b>신라AMP 비밀번호 찾기 알림</b>
📱 <b>전화번호:</b> {phone}
<b>요청시간:</b> {current_time}
🌐 <b>접속 정보:</b>
IP 주소: <code>{ip}</code>
기기: {device_info}
브라우저: {browser_info}
💡 비밀번호 찾기 요청이 있습니다!
""".strip()
# 비동기로 전송 (사용자 대기시간 없음)
send_telegram_message_async(message)
def test_telegram_bot():
"""
텔레그램 연결 테스트
"""
test_message = f"🧪 <b>신라AMP 봇 테스트</b>\n\n⏰ 테스트 시간: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n✅ 봇이 정상적으로 작동하고 있습니다!"
result = send_telegram_message(test_message)
if result['success']:
print("✅ 텔레그램 봇 테스트 성공!")
return True
else:
print(f"❌ 텔레그램 봇 테스트 실패: {result['error']}")
return False

View File

@ -305,6 +305,6 @@ class PersonForm(forms.ModelForm):
}), }),
'keyword1': forms.TextInput(attrs={ 'keyword1': forms.TextInput(attrs={
'class': 'w-full px-4 py-3 rounded-xl bg-gray-700 bg-opacity-80 text-white placeholder-gray-400 border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 transition', 'class': 'w-full px-4 py-3 rounded-xl bg-gray-700 bg-opacity-80 text-white placeholder-gray-400 border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 transition',
'placeholder': '검색 키워드 (예: 회계감사)' 'placeholder': '검색 키워드 (예: 잘생김, 골프천재, 댄스머신 ...)'
}), }),
} }

View File

@ -304,7 +304,7 @@
// 소개글 설정 // 소개글 설정
if (intro && intro.trim() !== '') { if (intro && intro.trim() !== '') {
modalIntro.textContent = intro; modalIntro.innerHTML = intro.replace(/\n/g, '<br>');
modalIntroSection.classList.remove('hidden'); modalIntroSection.classList.remove('hidden');
modalNoIntro.classList.add('hidden'); modalNoIntro.classList.add('hidden');
} else { } else {

View File

@ -1,34 +1,38 @@
{% load static %} {% load static %}
<div class="flex bg-white dark:bg-gray-800 rounded-lg shadow-md p-4 h-[210px] relative border border-gray-200 dark:border-gray-700 transition-colors duration-300"> <div class="flex bg-white dark:bg-gray-800 rounded-lg shadow-md p-4 min-h-[210px] relative border border-gray-200 dark:border-gray-700 transition-colors duration-300">
<!-- 좌측: 사진 + 버튼 --> <!-- 좌측: 사진 + 버튼 -->
<div class="flex flex-col items-center mr-4 w-[150px]"> <div class="flex flex-col mr-4 w-[150px]">
{% if person.사진 and person.사진.url and 'media/' in person.사진.url %} <div class="flex flex-col items-center flex-grow">
<img {% if person.사진 and person.사진.url and 'media/' in person.사진.url %}
src="{{ person.사진.url }}" <img
alt="{{ person.이름 }}" src="{{ person.사진.url }}"
class="w-[150px] h-[150px] object-cover rounded-lg border border-gray-300 dark:border-gray-600 mb-2 cursor-pointer transition-colors duration-300" alt="{{ person.이름 }}"
onclick="openProfileModal('{{ person.이름|escapejs }}', this.src, '{{ person.소개글|default:""|escapejs }}')" class="w-[150px] h-[150px] object-cover rounded-lg border border-gray-300 dark:border-gray-600 mb-2 cursor-pointer transition-colors duration-300"
> onclick="openProfileModal('{{ person.이름|escapejs }}', this.src, '{{ person.소개글|default:""|escapejs }}')"
{% else %}
<img
src="{% static 'B_main/images/default_user.png' %}"
alt="{{ person.이름 }}"
class="w-[150px] h-[150px] object-cover rounded-lg border border-gray-300 dark:border-gray-600 mb-2 cursor-pointer transition-colors duration-300"
onclick="openProfileModal('{{ person.이름|escapejs }}', this.src, '{{ person.소개글|default:""|escapejs }}')"
>
{% endif %}
{% if person.이름 %}
<a
href="{% url 'vcard_download' person.이름 %}"
class="inline-block bg-blue-600 dark:bg-blue-900 text-white text-xs px-3 py-1 rounded hover:bg-blue-700 dark:hover:bg-blue-800 text-center transition-colors duration-200"
> >
📇연락처저장 {% else %}
</a> <img
src="{% static 'B_main/images/default_user.png' %}"
alt="{{ person.이름 }}"
class="w-[150px] h-[150px] object-cover rounded-lg border border-gray-300 dark:border-gray-600 mb-2 cursor-pointer transition-colors duration-300"
onclick="openProfileModal('{{ person.이름|escapejs }}', this.src, '{{ person.소개글|default:""|escapejs }}')"
>
{% endif %}
</div>
{% if person.이름 %}
<div class="mt-auto">
<a
href="{% url 'vcard_download' person.이름 %}"
class="inline-block bg-blue-600 dark:bg-blue-900 text-white text-xs px-3 py-1 rounded hover:bg-blue-700 dark:hover:bg-blue-800 text-center transition-colors duration-200 w-full"
>
📇연락처저장
</a>
</div>
{% endif %} {% endif %}
</div> </div>
<!-- 우측: 텍스트 정보 --> <!-- 우측: 텍스트 정보 -->
<div class="flex flex-col justify-start w-full overflow-hidden"> <div class="flex flex-col w-full overflow-hidden">
<div class="flex justify-between items-baseline"> <div class="flex justify-between items-baseline">
<div class="mb-4"> <div class="mb-4">
<h2 class="text-lg font-semibold truncate leading-tight flex-1 {% if person.user %}text-gray-900 dark:text-gray-100{% else %}text-blue-600 dark:text-blue-400{% endif %}"> <h2 class="text-lg font-semibold truncate leading-tight flex-1 {% if person.user %}text-gray-900 dark:text-gray-100{% else %}text-blue-600 dark:text-blue-400{% endif %}">
@ -55,27 +59,40 @@
{% endif %} {% endif %}
</div> </div>
<div class="mt-1 text-xs leading-snug overflow-hidden max-h-[138px] flex flex-col gap-y-1"> <div class="flex-grow flex flex-col">
<div class="flex mb-1"> <div class="text-xs leading-tight flex flex-col gap-y-0.5">
<span class="w-[60px] text-gray-500 dark:text-gray-400">소속:</span> <div class="flex mb-0.5">
<span class="flex-1 truncate text-gray-800 dark:text-gray-200">{{ person.소속 }}</span> <span class="w-[60px] text-gray-500 dark:text-gray-400">소속:</span>
<span class="flex-1 truncate text-gray-800 dark:text-gray-200">{{ person.소속 }}</span>
</div>
<div class="flex mb-0.5">
<span class="w-[60px] text-gray-500 dark:text-gray-400">직책:</span>
<span class="flex-1 truncate text-gray-800 dark:text-gray-200">{{ person.직책 }}</span>
</div>
<div class="flex mb-0.5">
<span class="w-[60px] text-gray-500 dark:text-gray-400">연락처:</span>
<span class="flex-1 truncate text-gray-800 dark:text-gray-200">{{ person.연락처 }}</span>
</div>
<div class="flex">
{% if person.이름 == '허남식' %}
<span class="w-[60px] text-gray-500 dark:text-gray-400">이메일:</span>
{% else %}
<span class="w-[60px] text-gray-500 dark:text-gray-400">주소:</span>
{% endif %}
<span class="flex-1 text-gray-800 dark:text-gray-200">{{ person.주소 }}</span>
</div>
</div> </div>
<div class="flex mb-1">
<span class="w-[60px] text-gray-500 dark:text-gray-400">직책:</span> <!-- 소개글 박스 추가 (하단 정렬) -->
<span class="flex-1 truncate text-gray-800 dark:text-gray-200">{{ person.직책 }}</span> {% if person.소개글 %}
</div> <div class="mt-auto pt-1">
<div class="flex mb-1"> <div class="bg-gray-100 dark:bg-gray-700 rounded-md px-2 py-1">
<span class="w-[60px] text-gray-500 dark:text-gray-400">연락처:</span> <div class="text-gray-700 dark:text-gray-300 text-xs leading-tight line-clamp-2">
<span class="flex-1 truncate text-gray-800 dark:text-gray-200">{{ person.연락처 }}</span> {{ person.소개글 }}
</div> </div>
<div class="flex"> </div>
{% if person.이름 == '허남식' %}
<span class="w-[60px] text-gray-500 dark:text-gray-400">이메일:</span>
{% else %}
<span class="w-[60px] text-gray-500 dark:text-gray-400">주소:</span>
{% endif %}
<span class="flex-1 text-gray-800 dark:text-gray-200">{{ person.주소 }}</span>
</div> </div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>

View File

@ -30,6 +30,12 @@
{% if message %} {% if message %}
<div class="text-green-400 text-sm mb-4 text-center">{{ message }}</div> <div class="text-green-400 text-sm mb-4 text-center">{{ message }}</div>
{% endif %} {% endif %}
{% if success_message %}
<div class="text-green-400 text-sm mb-4 text-center bg-green-900 bg-opacity-30 p-3 rounded-lg border border-green-600">
{{ success_message }}
</div>
{% endif %}
{% if step == 1 %} {% if step == 1 %}
<form method="POST" class="space-y-4" id="signup-form-step1"> <form method="POST" class="space-y-4" id="signup-form-step1">

View File

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="ko" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>텔레그램 봇 테스트 | 신라 AMP</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'sans-serif']
}
}
}
};
</script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet" />
</head>
<body class="bg-gradient-to-br from-gray-900 via-gray-800 to-black text-white min-h-screen flex items-center justify-center px-4 font-sans">
<div class="bg-gray-800 bg-opacity-70 backdrop-blur-lg p-8 rounded-2xl shadow-2xl w-full max-w-md transition-all">
<div class="text-center mb-6">
<h1 class="text-3xl font-bold tracking-tight text-white">📱 텔레그램 봇 테스트</h1>
<p class="text-sm text-gray-400 mt-2">관리자 전용 기능</p>
</div>
<!-- Django 메시지 표시 -->
{% if messages %}
{% for message in messages %}
<div class="p-4 rounded-lg mb-4 {% if message.tags == 'success' %}bg-green-600{% else %}bg-red-600{% endif %} text-white">
{{ message }}
</div>
{% endfor %}
{% endif %}
<div class="space-y-4">
<div class="bg-gray-700 bg-opacity-50 p-4 rounded-xl">
<h3 class="text-lg font-semibold mb-2">🤖 봇 정보</h3>
<div class="text-sm text-gray-300 space-y-1">
<p><strong>토큰:</strong> {{ settings.TELEGRAM_BOT_TOKEN|slice:":10" }}...</p>
<p><strong>채팅 ID:</strong> {{ settings.TELEGRAM_CHAT_ID }}</p>
</div>
</div>
<form method="POST" class="space-y-4">
{% csrf_token %}
<button type="submit"
class="w-full py-3 bg-blue-600 hover:bg-blue-700 active:bg-blue-800 rounded-xl text-white font-semibold text-base transition duration-200 shadow-md hover:shadow-lg">
🚀 테스트 메시지 전송
</button>
</form>
<div class="text-center">
<a href="{% url 'main' %}" class="text-gray-400 hover:text-gray-300 transition text-sm">
← 메인 페이지로 돌아가기
</a>
</div>
</div>
</div>
<script>
// 메시지 자동 숨기기
setTimeout(function() {
const messages = document.querySelectorAll('.bg-green-600, .bg-red-600');
messages.forEach(function(message) {
message.style.transition = 'opacity 0.5s';
message.style.opacity = '0';
setTimeout(function() {
message.remove();
}, 500);
});
}, 3000);
</script>
</body>
</html>

View File

@ -13,4 +13,5 @@ urlpatterns = [
path('signup/', views.signup_view, name='signup'), path('signup/', views.signup_view, name='signup'),
path('privacy-policy/', views.privacy_policy, name='privacy_policy'), path('privacy-policy/', views.privacy_policy, name='privacy_policy'),
path('test-500/', views.test_500_error, name='test_500_error'), path('test-500/', views.test_500_error, name='test_500_error'),
path('test-telegram/', views.test_telegram_bot, name='test_telegram_bot'),
] ]

View File

@ -268,13 +268,26 @@ def signup_view(request):
from .forms import is_allowed_person from .forms import is_allowed_person
from django.contrib.auth import login from django.contrib.auth import login
# 강제 리셋 파라미터 확인 (로그인 페이지에서 오는 경우)
force_reset = request.GET.get('reset', '').lower() == 'true'
# GET 요청 시 세션 초기화 (새로운 회원가입 시작) # GET 요청 시 세션 초기화 (새로운 회원가입 시작)
# 단, 인증번호 확인 후 리다이렉트된 경우는 세션 유지 # 단, 인증번호 확인 후 리다이렉트된 경우는 세션 유지
if request.method == 'GET' and not request.session.get('signup_verified'): # 또한 step이 2이고 verified가 True인 경우도 세션 유지
for key in ['signup_code', 'signup_name', 'signup_phone', 'signup_verified', 'signup_step']: current_step = request.session.get('signup_step', 1)
request.session.pop(key, None) current_verified = request.session.get('signup_verified', False)
request.session['signup_step'] = 1
request.session['signup_verified'] = False if request.method == 'GET' and (force_reset or not (current_verified and current_step == 2)):
# 강제 리셋이거나 인증되지 않았거나 step이 2가 아닌 경우에만 세션 초기화
if force_reset or not current_verified:
for key in ['signup_code', 'signup_name', 'signup_phone', 'signup_verified', 'signup_step']:
request.session.pop(key, None)
request.session['signup_step'] = 1
request.session['signup_verified'] = False
# 강제 리셋인 경우 디버그 메시지 출력
if force_reset:
print("[DEBUG] 회원가입 세션이 강제로 리셋되었습니다.")
step = request.session.get('signup_step', 1) step = request.session.get('signup_step', 1)
name = request.session.get('signup_name') name = request.session.get('signup_name')
@ -308,6 +321,10 @@ def signup_view(request):
# 전화번호 인증 로그 기록 # 전화번호 인증 로그 기록
log_phone_verification(request, phone) log_phone_verification(request, phone)
# 텔레그램 알림 전송 (비동기)
from A_core.telegram_utils import send_signup_notification
send_signup_notification(name, phone, request)
return render(request, 'B_main/signup.html', { return render(request, 'B_main/signup.html', {
'step': 1, 'form1': form, 'code_sent': True, 'message': '인증번호가 발송되었습니다.' 'step': 1, 'form1': form, 'code_sent': True, 'message': '인증번호가 발송되었습니다.'
}) })
@ -349,7 +366,15 @@ def signup_view(request):
# 인증 성공 # 인증 성공
request.session['signup_verified'] = True request.session['signup_verified'] = True
request.session['signup_step'] = 2 request.session['signup_step'] = 2
return redirect('signup') # 인증 성공 메시지와 함께 2단계로 직접 이동
form2 = Step2AccountForm()
return render(request, 'B_main/signup.html', {
'step': 2,
'form2': form2,
'name': name,
'phone': phone,
'success_message': '인증이 완료되었습니다. 비밀번호를 설정해주세요.'
})
else: else:
return render(request, 'B_main/signup.html', { return render(request, 'B_main/signup.html', {
'step': 1, 'form1': form, 'code_sent': True, 'error': '인증번호가 올바르지 않습니다.' 'step': 1, 'form1': form, 'code_sent': True, 'error': '인증번호가 올바르지 않습니다.'
@ -409,4 +434,21 @@ def privacy_policy(request):
def test_500_error(request): def test_500_error(request):
"""500 에러 페이지 테스트용 뷰""" """500 에러 페이지 테스트용 뷰"""
# 강제로 에러를 발생시킵니다 # 강제로 에러를 발생시킵니다
raise Exception("500 에러 페이지 테스트를 위한 의도적인 에러입니다.") raise Exception("500 에러 페이지 테스트를 위한 의도적인 에러입니다.")
def test_telegram_bot(request):
"""텔레그램 봇 테스트용 뷰 (관리자만 접근 가능)"""
if not request.user.is_superuser:
return redirect('/')
from A_core.telegram_utils import test_telegram_bot
if request.method == 'POST':
success = test_telegram_bot()
if success:
messages.success(request, '✅ 텔레그램 봇 테스트 성공! 메시지가 전송되었습니다.')
else:
messages.error(request, '❌ 텔레그램 봇 테스트 실패! 설정을 확인해주세요.')
return redirect('test_telegram_bot')
return render(request, 'B_main/test_telegram.html')

View File

@ -233,6 +233,10 @@ def password_reset(request):
message = '인증번호가 발송되었습니다.' message = '인증번호가 발송되었습니다.'
code_sent = True code_sent = True
print(f"[DEBUG] 비밀번호 찾기 SMS 발송 성공: {phone} - {verification_code}") print(f"[DEBUG] 비밀번호 찾기 SMS 발송 성공: {phone} - {verification_code}")
# 텔레그램 알림 전송 (비동기)
from A_core.telegram_utils import send_password_reset_notification
send_password_reset_notification(phone, request)
else: else:
error = '인증번호 발송에 실패했습니다. 잠시 후 다시 시도해주세요.' error = '인증번호 발송에 실패했습니다. 잠시 후 다시 시도해주세요.'
print(f"[DEBUG] 비밀번호 찾기 SMS 발송 실패: {sms_result['error']}") print(f"[DEBUG] 비밀번호 찾기 SMS 발송 실패: {sms_result['error']}")
@ -252,7 +256,14 @@ def password_reset(request):
elif input_code == stored_code: elif input_code == stored_code:
request.session['password_reset_verified'] = True request.session['password_reset_verified'] = True
request.session['password_reset_step'] = 2 request.session['password_reset_step'] = 2
return redirect('accounts:password_reset')
print(f"[DEBUG] 비밀번호 찾기 인증 성공! 2단계로 직접 이동: {phone}")
# 리다이렉트 대신 직접 2단계 렌더링
form2 = ForcePasswordSetForm()
return render(request, 'C_accounts/password_reset.html', {
'step': 2, 'form2': form2, 'phone': phone
})
else: else:
error = '인증번호가 일치하지 않습니다.' error = '인증번호가 일치하지 않습니다.'
else: else:

Binary file not shown.

View File

@ -97,7 +97,7 @@
<div class="mt-6 text-center text-sm"> <div class="mt-6 text-center text-sm">
{% if not request.session.authenticated and not user.is_authenticated %} {% if not request.session.authenticated and not user.is_authenticated %}
계정이 없으신가요? 계정이 없으신가요?
<a href="{% url 'account_signup' %}" class="text-green-400 hover:text-green-500 transition">회원가입</a> <a href="{% url 'signup' %}?reset=true" class="text-green-400 hover:text-green-500 transition">회원가입</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>