첫커밋

This commit is contained in:
eld_master 2024-12-06 14:12:28 +09:00
commit f9713153d1
61 changed files with 3772 additions and 0 deletions

View File

@ -0,0 +1,12 @@
FROM python:3.8
WORKDIR /app
COPY ./app /app
RUN pip install -r requirements.txt
EXPOSE 8097
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8097"]

Binary file not shown.

View File

@ -0,0 +1,11 @@
# 서버 종류
MY_SERVER = 'SERVER2'
# JWT 키
SECRET_KEY = 'ALLSCORE'
# 이메일 정보
SMTP_USERNAME = 'aofhd0003@gmail.com'
SMTP_PASSWORD = 'jueyodpzlvisrtto'
SMTP_SERVER = 'smtp.gmail.com'
SMTP_PORT = 587

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

18
fastapi/app/db/base.py Normal file
View File

@ -0,0 +1,18 @@
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "postgresql://eldsoft:eld240510@172.31.7.121:8088/allscore"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = None
try:
db = SessionLocal()
yield db
finally:
db.close()

20
fastapi/app/db/crud.py Normal file
View File

@ -0,0 +1,20 @@
from sqlalchemy.orm import Session
from sqlalchemy import text
from db import models, schemas
from datetime import datetime
def create_card_expense(db: Session, card_expense: schemas.CardExpenseCreate):
db_card_expense = models.CardExpense(jang=card_expense.jang, yeo=card_expense.yeo, kim=card_expense.kim, choi=card_expense.choi, amount=card_expense.amount, purpose=card_expense.purpose, created_at=datetime.now())
db.add(db_card_expense)
db.commit()
db.refresh(db_card_expense)
return db_card_expense
def get_card_expenses_by_date(db: Session, year: int, month: int):
query = text("""
SELECT * FROM card_expenses
WHERE EXTRACT(YEAR FROM created_at) = :year
AND EXTRACT(MONTH FROM created_at) = :month
""")
return db.execute(query, {'year': year, 'month': month}).fetchall()

View File

@ -0,0 +1,313 @@
from sqlalchemy.orm import Session
from sqlalchemy import text
from db import models, schemas
from process.logger import logger
from db.base import get_db
# ================================================================
# 회원관리 함수
# ================================================================
# 방 생성
def create_room(data, db):
query = text(f"""
insert into room_main(user_seq, cert_type, user_info, cert_code, cert_expired)
values({user_seq}, '{cert_type}', '{user_info}', '{cert_code}', now() + INTERVAL '5 minutes')
""")
try:
db.execute(query)
db.commit()
return True
except Exception as e:
logger.error(f"sql error: {e}")
return False
# 로그인
def do_login(user_id, user_pw, db):
query = text(f"""
select
user_seq
from manage_user
where
user_id = '{user_id}'
and user_pw = (select
encode(
digest(
'{user_pw}' || (select user_pw_solt from manage_user where user_id = '{user_id}'), 'sha256'
), 'hex'
))
and withdraw_yn = 'N'
""")
return db.execute(query).fetchall()
# 마지막 로그인 시간 업데이트
def update_last_login_dt(user_seq, db):
query = text(f"""
update manage_user
set last_login_dt = now()
where user_seq = {user_seq}
and withdraw_yn = 'N'
""")
try:
db.execute(query)
db.commit()
return True
except Exception as e:
logger.error(f"sql error: {e}")
return False
# 아이디 찾기(닉네임, 이메일)
def find_id_by_name_email(nickname, user_email, db):
query = text(f"""
select
user_seq,
user_id
from manage_user
where nickname = '{nickname}'
and user_email = '{user_email}'
and withdraw_yn = 'N'
""")
return db.execute(query).fetchall()
# 아이디 찾기(user_seq)
def find_id_by_user_seq(user_seq, db):
query = text(f"""
select
user_id,
user_email
from manage_user
where user_seq = {user_seq}
and withdraw_yn = 'N'
""")
return db.execute(query).fetchall()
# 이메일 발송 3회 이상 됐는지 확인
def select_send_email_cnt(user_email, db):
query = text(f"""
select
count(user_cert_seq)
from manage_user_cert
where user_info = '{user_email}'
and cert_expired between current_date and current_date + interval '1 day'
""")
return db.execute(query).fetchall()
# 이메일 발송내역 기록(하루 3회만 발송가능하게 하기 위함)
def insert_send_email_info(user_seq, cert_type, user_info, cert_code, db):
query = text(f"""
insert into manage_user_cert(user_seq, cert_type, user_info, cert_code, cert_expired)
values({user_seq}, '{cert_type}', '{user_info}', '{cert_code}', now() + INTERVAL '5 minutes')
""")
try:
db.execute(query)
db.commit()
return True
except Exception as e:
logger.error(f"sql error: {e}")
return False
# 비밀번호 찾기
def find_password_by_id_email(user_id, user_email, db):
query = text(f"""
select
user_seq
from manage_user
where user_id = '{user_id}'
and user_email = '{user_email}'
and withdraw_yn = 'N'
""")
return db.execute(query).fetchall()
# 신규 비밀번호 업데이트
def update_new_password(user_seq, new_pw, new_solt, db):
query = text(f"""
update manage_user
set
user_pw = encode(digest(encode(digest('{new_pw}', 'sha256'), 'hex') || '{new_solt}', 'sha256'), 'hex'),
user_pw_solt = '{new_solt}'
where
user_seq = {user_seq}
and withdraw_yn = 'N'
""")
try:
db.execute(query)
db.commit()
return True
except Exception as e:
logger.error(f"sql error: {e}")
return False
# 회원가입 최종 아이디 중복 재검사
def is_valid_user_id_by_user_id(user_id, db):
query = text(f"""
select
user_seq
from manage_user
where user_id = '{user_id}'
""")
return db.execute(query).fetchall()
# 신규 유저 등록
def insert_new_user(user_info, db):
query = text(f"""
insert into
manage_user(
user_id,
user_pw,
user_pw_solt,
nickname,
profile_img,
user_email,
department,
introduce_myself,
last_login_dt,
create_dt,
update_dt,
mandatory_terms_yn,
withdraw_yn
)
values(
'{user_info['user_id']}',
(select encode(digest('{user_info['user_pw']}{user_info['user_pw_solt']}', 'sha256'), 'hex')),
'{user_info['user_pw_solt']}',
'{user_info['nickname']}',
'images/user/temp_dir/profile_img/basic_{user_info['user_id']}.png',
'{user_info['user_email']}',
'{user_info.get('department', '')}',
'{user_info.get('introduce_myself', '')}',
now(),
now(),
now(),
'{user_info.get('mandatory_terms_yn', '')}',
'N'
)
""")
try:
db.execute(query)
db.commit()
return True
except Exception as e:
logger.error(f"sql error: {e}")
return False
# user_seq로 내정보 가져오기
def get_my_info_by_user_seq(user_seq, db):
query = text(f"""
select
nickname,
user_email,
department,
profile_img,
introduce_myself
from manage_user
where user_seq = {user_seq}
""")
return db.execute(query).fetchall()
# 현재 비밀번호 일치 확인
def check_current_user_pw(user_seq, user_pw, db):
query = text(f"""
select
count(user_seq)
from manage_user
where user_seq = {user_seq}
and user_pw = (select
encode(
digest(
'{user_pw}' || (select user_pw_solt from manage_user where user_seq = '{user_seq}'), 'sha256'
), 'hex'
))
""")
return db.execute(query).fetchall()
# 신규 비밀번호 업데이트
def update_user_info(user_info, db):
if user_info['user_pw_change_yn'] == 'Y':
query = text(f"""
update manage_user
set
user_pw = encode(digest(encode(digest('{user_info['new_user_pw']}', 'sha256'), 'hex') || '{user_info['user_pw_solt']}', 'sha256'), 'hex'),
user_pw_solt = '{user_info['user_pw_solt']}'
nickname = '{user_info['nickname']}',
user_email = '{user_info['user_email']}',
department = '{user_info['department']}',
profile_img = '{user_info['profile_img']}',
introduce_myself = '{user_info['introduce_myself']}'
where
user_seq = {user_info['user_seq']}
and withdraw_yn = 'N'
""")
else:
query = text(f"""
update manage_user
set
nickname = '{user_info['nickname']}',
user_email = '{user_info['user_email']}',
department = '{user_info['department']}',
profile_img = '{user_info['profile_img']}',
introduce_myself = '{user_info['introduce_myself']}'
where
user_seq = {user_info['user_seq']}
and withdraw_yn = 'N'
""")
try:
db.execute(query)
db.commit()
return True
except Exception as e:
logger.error(f"sql error: {e}")
return False
# 회원 탈퇴 처리
def user_withdraw(user_seq, db):
query = text(f"""
delete from manage_user
where
user_seq = {user_seq}
and withdraw_yn = 'N'
""")
try:
db.execute(query)
db.commit()
return True
except Exception as e:
logger.error(f"sql error: {e}")
return False

276
fastapi/app/db/crud_user.py Normal file
View File

@ -0,0 +1,276 @@
from sqlalchemy.orm import Session
from sqlalchemy import text
from db import models, schemas
from process.logger import logger
from db.base import get_db
# ================================================================
# 회원관리 함수
# ================================================================
# 로그인
def do_login(user_id, user_pw, db):
query = text(f"""
select
user_seq
from manage_user
where
user_id = '{user_id}'
and user_pw = (select
encode(
digest(
'{user_pw}' || (select user_pw_solt from manage_user where user_id = '{user_id}'), 'sha256'
), 'hex'
))
and withdraw_yn = 'N'
""")
return db.execute(query).fetchall()
# 마지막 로그인 시간 업데이트
def update_last_login_dt(user_seq, db):
query = text(f"""
update manage_user
set last_login_dt = now()
where user_seq = {user_seq}
and withdraw_yn = 'N'
""")
try:
db.execute(query)
db.commit()
return True
except Exception as e:
logger.error(f"sql error: {e}")
return False
# 아이디 찾기(닉네임, 이메일)
def find_id_by_name_email(nickname, user_email, db):
query = text(f"""
select
user_seq,
user_id
from manage_user
where nickname = '{nickname}'
and user_email = '{user_email}'
and withdraw_yn = 'N'
""")
return db.execute(query).fetchall()
# 아이디 찾기(user_seq)
def find_id_by_user_seq(user_seq, db):
query = text(f"""
select
user_id,
user_email
from manage_user
where user_seq = {user_seq}
and withdraw_yn = 'N'
""")
return db.execute(query).fetchall()
# 이메일 발송 3회 이상 됐는지 확인
def select_send_email_cnt(user_email, db):
query = text(f"""
select
count(user_cert_seq)
from manage_user_cert
where user_info = '{user_email}'
and cert_expired between current_date and current_date + interval '1 day'
""")
return db.execute(query).fetchall()
# 이메일 발송내역 기록(하루 3회만 발송가능하게 하기 위함)
def insert_send_email_info(user_seq, cert_type, user_info, cert_code, db):
query = text(f"""
insert into manage_user_cert(user_seq, cert_type, user_info, cert_code, cert_expired)
values({user_seq}, '{cert_type}', '{user_info}', '{cert_code}', now() + INTERVAL '5 minutes')
""")
try:
db.execute(query)
db.commit()
return True
except Exception as e:
logger.error(f"sql error: {e}")
return False
# 비밀번호 찾기
def find_password_by_id_email(user_id, user_email, db):
query = text(f"""
select
user_seq
from manage_user
where user_id = '{user_id}'
and user_email = '{user_email}'
and withdraw_yn = 'N'
""")
return db.execute(query).fetchall()
# 신규 비밀번호 업데이트
def update_new_password(user_seq, new_pw, new_solt, db):
query = text(f"""
update manage_user
set
user_pw = encode(digest(encode(digest('{new_pw}', 'sha256'), 'hex') || '{new_solt}', 'sha256'), 'hex'),
user_pw_solt = '{new_solt}'
where
user_seq = {user_seq}
and withdraw_yn = 'N'
""")
try:
db.execute(query)
db.commit()
return True
except Exception as e:
logger.error(f"sql error: {e}")
return False
# 회원가입 최종 아이디 중복 재검사
def is_valid_user_id_by_user_id(user_id, db):
query = text(f"""
select
user_seq
from manage_user
where user_id = '{user_id}'
""")
return db.execute(query).fetchall()
# 신규 유저 등록
def insert_new_user(user_info, db):
query = text(f"""
insert into
manage_user(
user_id,
user_pw,
user_pw_solt,
nickname,
profile_img,
user_email,
department,
introduce_myself,
last_login_dt,
create_dt,
update_dt,
mandatory_terms_yn,
withdraw_yn
)
values(
'{user_info['user_id']}',
(select encode(digest('{user_info['user_pw']}{user_info['user_pw_solt']}', 'sha256'), 'hex')),
'{user_info['user_pw_solt']}',
'{user_info['nickname']}',
'images/user/temp_dir/profile_img/basic_{user_info['user_id']}.png',
'{user_info['user_email']}',
'{user_info.get('department', '')}',
'{user_info.get('introduce_myself', '')}',
now(),
now(),
now(),
'{user_info.get('mandatory_terms_yn', '')}',
'N'
)
""")
try:
db.execute(query)
db.commit()
return True
except Exception as e:
logger.error(f"sql error: {e}")
return False
# user_seq로 내정보 가져오기
def get_my_info_by_user_seq(user_seq, db):
query = text(f"""
select
nickname,
user_email,
department,
profile_img,
introduce_myself
from manage_user
where user_seq = {user_seq}
""")
return db.execute(query).fetchall()
# 현재 비밀번호 일치 확인
def check_current_user_pw(user_seq, user_pw, db):
query = text(f"""
select
count(user_seq)
from manage_user
where user_seq = {user_seq}
and user_pw = (select
encode(
digest(
'{user_pw}' || (select user_pw_solt from manage_user where user_seq = '{user_seq}'), 'sha256'
), 'hex'
))
""")
return db.execute(query).fetchall()
# 신규 비밀번호 업데이트
def update_user_info(user_info, db):
if user_info['user_pw_change_yn'] == 'Y':
query = text(f"""
update manage_user
set
user_pw = encode(digest(encode(digest('{user_info['new_user_pw']}', 'sha256'), 'hex') || '{user_info['user_pw_solt']}', 'sha256'), 'hex'),
user_pw_solt = '{user_info['user_pw_solt']}'
nickname = '{user_info['nickname']}',
user_email = '{user_info['user_email']}',
department = '{user_info['department']}',
profile_img = '{user_info['profile_img']}',
introduce_myself = '{user_info['introduce_myself']}'
where
user_seq = {user_info['user_seq']}
and withdraw_yn = 'N'
""")
else:
query = text(f"""
update manage_user
set
nickname = '{user_info['nickname']}',
user_email = '{user_info['user_email']}',
department = '{user_info['department']}',
profile_img = '{user_info['profile_img']}',
introduce_myself = '{user_info['introduce_myself']}'
where
user_seq = {user_info['user_seq']}
and withdraw_yn = 'N'
""")
try:
db.execute(query)
db.commit()
return True
except Exception as e:
logger.error(f"sql error: {e}")
return False
# 회원 탈퇴 처리
def user_withdraw(user_seq, db):
query = text(f"""
delete from manage_user
where
user_seq = {user_seq}
and withdraw_yn = 'N'
""")
try:
db.execute(query)
db.commit()
return True
except Exception as e:
logger.error(f"sql error: {e}")
return False

63
fastapi/app/db/models.py Normal file
View File

@ -0,0 +1,63 @@
from sqlalchemy import Column, Integer, String, CHAR, TIMESTAMP, VARCHAR, FLOAT, JSON, Numeric, DateTime, func, text
from .base import Base
class RawDatas(Base):
__tablename__ = "col_test_raw_data"
raw_dt = Column(TIMESTAMP, primary_key=True)
mg_equip_rtu_seq = Column(VARCHAR, primary_key=True)
acc_pg = Column(VARCHAR)
pg = Column(VARCHAR)
class Stat_pwr_rtu_1d_test(Base):
__tablename__ = "stat_pwr_rtu_1d_test"
stat_dt = Column(TIMESTAMP, primary_key=True)
rtu_seq = Column(Integer, primary_key=True)
stat_value = Column(FLOAT)
class Stat_pwr_rtu_1d(Base):
__tablename__ = "stat_pwr_rtu_1d"
stat_dt = Column(TIMESTAMP, primary_key=True)
rtu_seq = Column(Integer, primary_key=True)
stat_value = Column(FLOAT)
class Stat_weather_icsr_dc10tca_1d(Base):
__tablename__ = "stat_weather_icsr_dc10tca_1d"
tm = Column(TIMESTAMP, primary_key=True)
icsr_station_id = Column(VARCHAR)
icsr = Column(VARCHAR)
dc10tca_station_id = Column(VARCHAR)
dc10tca = Column(VARCHAR)
update_dt = Column(TIMESTAMP, nullable=True)
class Raw_data_herit(Base):
__tablename__ = "raw_data_herit"
seq = Column(Integer, primary_key=True, autoincrement="auto")
tm = Column(TIMESTAMP)
content_type = Column(VARCHAR)
x_hit_transactionid = Column(VARCHAR)
content_length = Column(VARCHAR)
body = Column(JSON)
class CardExpense(Base):
__tablename__ = "card_expenses"
id = Column(Integer, primary_key=True, index=True)
jang = Column(String(1), nullable=False)
yeo = Column(String(1), nullable=False)
kim = Column(String(1), nullable=False)
choi = Column(String(1), nullable=False)
amount = Column(Numeric(10), nullable=False)
purpose = Column(String(255), nullable=False)
created_at = Column(DateTime, nullable=False)

54
fastapi/app/db/schemas.py Normal file
View File

@ -0,0 +1,54 @@
from pydantic import BaseModel
from datetime import datetime
class RawData(BaseModel):
raw_dt: str
mg_equip_rtu_seq: str
acc_pg: str
pg: str
class RtuGenerator(BaseModel):
month: float
day: float
hour: float
rtu_seq: float
icsr: float
dc10tca: float
class RawDataHerit(BaseModel):
tm: str
content_type: str
X_HIT_TransactionId: str
content_Length: str
body: dict
class CardExpenseCreate(BaseModel):
jang: str
yeo: str
kim: str
choi: str
amount: float
purpose: str
class CardExpense(BaseModel):
id: int
jang: str
yeo: str
kim: str
choi: str
amount: float
purpose: str
created_at: datetime
class Config:
orm_mode = True
class DatePayload(BaseModel):
year: int
month: int

View File

83
fastapi/app/main.py Normal file
View File

@ -0,0 +1,83 @@
import time
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import logging
import datetime
from router.router_api import router as router_api
from router.card_service import router as router_card
from router.image_api import router as image_api
from router.manage_user_api import router as manage_user_api
from router.room_score_api import router as room_score_api
# test
from router.test_service import router as test_service_api
app = FastAPI()
# docker로 수행하기 때문에 로그는 생략
# logger = logging.getLogger()
# logger.setLevel(logging.INFO)
# file_handler = logging.FileHandler("testtest.log")
# logger.addHandler(file_handler)
# -----------------------------------------------------------------------------------------------
# CORS 설정
origins = [
"http://localhost",
"http://localhost:3000",
"https://localhost",
"https://localhost:3000",
"http://eldsoft.com",
"http://eldsoft.com:3000",
"https://eldsoft.com",
"https://eldsoft.com:3000"
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# /api
app.include_router(router_api)
# /api/card
app.include_router(router_card)
# /images
app.include_router(image_api)
# /user
app.include_router(manage_user_api)
# /room/score
app.include_router(room_score_api)
# /test
app.include_router(test_service_api)
# /iot-kt
# app.include_router(router_iot_kt)
# /iot-lg
# app.include_router(router_iot_lg)
@app.get("/ttt")
async def test_endpoint(data: dict):
return {"get received_data": data}
@app.post("/ttt")
async def test_endpoint(data: dict):
return {"received_data": data}

View File

@ -0,0 +1,9 @@
from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.responses import Response
import time
class TimeHeaderMiddleware(BaseHTTPMiddleware) :
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
return await super().dispatch(request, call_next)

167
fastapi/app/parse.py Normal file
View File

@ -0,0 +1,167 @@
{
'device_id': 'HERIT-HC1000N4-359689092518957',
'parent_id': 'herit-gw_nbiot',
'sid': '1',
'msg': {
'o': 'n',
'e': [
{
'sv': '211',
'ti': '1686883200000',
'n': '/99/0/5'
}, {
'sv': '55,57,51',
'ti': '1686883200000',
'n': '/99/0/6'
}, {
'sv': '24,22,1,1',
'ti': '1686883200000',
'n': '/99/0/8'
}, {
'sv': '24,24,0,0',
'ti': '1686883200000',
'n': '/99/0/9'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/99/0/10'
}, {
'sv': '9778500',
'ti': '1686883200000',
'n': '/100/1/0'
}, {
'sv': '551421',
'ti': '1686883200000',
'n': '/100/1/1'
}, {
'sv': '7700',
'ti': '1686883200000',
'n': '/100/1/2'
}, {
'sv': '361',
'ti': '1686883200000',
'n': '/100/1/3'
}, {
'sv': '2413',
'ti': '1686883200000',
'n': '/100/1/4'
}, {
'sv': '700',
'ti': '1686883200000',
'n': '/100/1/5'
}, {
'sv': '291',
'ti': '1686883200000',
'n': '/100/1/6'
}, {
'sv': '9.0',
'ti': '1686883200000',
'n': '/100/1/7'
}, {
'sv': '2619.0',
'ti': '1686883200000',
'n': '/100/1/8'
}, {
'sv': '235',
'ti': '1686883200000',
'n': '/100/1/9'
}, {
'sv': '10.0',
'ti': '1686883200000',
'n': '/100/1/10'
}, {
'sv': '2413.0',
'ti': '1686883200000',
'n': '/100/1/11'
}, {
'sv': '98.1',
'ti': '1686883200000',
'n': '/100/1/12'
}, {
'sv': '60.0',
'ti': '1686883200000',
'n': '/100/1/13'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/101/1/0'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/102/1/0'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/101/1/19'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/101/1/20'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/101/1/5'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/101/1/6'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/101/1/11'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/101/1/9'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/101/1/10'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/101/1/3'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/101/1/8'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/101/1/16'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/101/1/14'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/101/1/15'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/101/1/21'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/101/1/17'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/101/1/18'
}, {
'sv': '0',
'ti': '1686883200000',
'n': '/101/1/22'
}, {
'sv': '1',
'ti': '1686883200000',
'n': '/200/1/4'
}, {
'sv': '1',
'ti': '1686883200000',
'n': '/200/1/5'
}
]
}
}

View File

@ -0,0 +1,161 @@
from fastapi import Request
import json
from process.logger import logger
import jwt
import datetime
import time
from common.config import SECRET_KEY
async def cert_process_by_token(request: Request):
# 헤더에서 토큰 찾기
auth_token = request.headers.get('auth-token')
# 헤더에 토큰이 없으면 인증 거절 처리
if auth_token == None:
return {
"result": "FAIL",
"token": ""
}
else:
# 헤더에 토큰이 있으므로 DB에서 유효한 토큰 값인지 검증하기
1
def create_jwt(user_seq: int, period: int):
# 현재 시간
now = int(time.time())
# JWT의 페이로드 (클레임)
payload = {
"user_seq": user_seq,
"exp": now + period, # 만료 시간
"iat": now, # 토큰 발행 시간
}
# JWT 생성 (HS256 알고리즘 사용)
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
return token
def decode_jwt(token: str):
try:
# 토큰 디코드
decoded_payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
return {
"result": "OK",
"data": decoded_payload
}
except jwt.ExpiredSignatureError:
return {
"result": "TOKEN_EXPIRED",
"error": "Token has expired"
}
except jwt.InvalidTokenError:
return {
"result": "FAIL",
"error": "Invalid token"
}
# 인증정보 JWT토큰화
def create_user_cert_seq_jwt(user_cert_seq: int, period: int):
# 현재 시간
now = int(time.time())
# JWT의 페이로드 (클레임)
payload = {
"user_cert_seq": user_cert_seq,
"exp": now + period, # 만료 시간
"iat": now, # 토큰 발행 시간
}
# JWT 생성 (HS256 알고리즘 사용)
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
return token
# 인증서 갱신
def renew_cert(request: Request):
try:
# 헤더에서 토큰 찾기
auth_token = request.headers.get('auth-token')
decoded_payload = decode_jwt(auth_token)
if decoded_payload['result'] == 'OK':
decoded_payload = decoded_payload['data']
user_seq = decoded_payload['user_seq']
exp = decoded_payload['exp']
iat = decoded_payload['iat']
now = int(time.time())
# 시간비교 유효한 토큰인지 확인
if iat <= now and now <= exp:
return {
"result": "OK",
"data": make_auth_data(create_jwt(user_seq=user_seq, period=3600), 'Y')
}
else:
return {
"result": "TOKEN_EXPIRED",
"data": make_auth_data('', 'N'),
"msg": '인증서 토큰이 만료 되었습니다.'
}
elif decoded_payload['result'] == 'TOKEN_EXPIRED':
return {
"result": "TOKEN_EXPIRED",
"data": make_auth_data('', 'N'),
"msg": '인증서 토큰이 만료 되었습니다.'
}
else:
logger.error(f"token renew error. message: {e}")
return {
"result": "FAIL",
"data": make_auth_data('', 'N'),
"msg": decoded_payload['error']
}
except Exception as e:
logger.error(f"token renew error. message: {e}")
return {
"result": "FAIL",
"data": make_auth_data('', 'N'),
"msg": '인증서 토큰 갱신에 실패했습니다.'
}
# 응답 auth 함수화
def make_auth_data(token, tf):
return {
"renew_yn": tf,
"token": token
}
# 토큰으로 user_seq 가져오기
def get_user_seq_by_token(token: str):
try:
payload = decode_jwt(token)
if payload['result'] == 'OK':
user_seq = payload['data']['user_seq']
return {
"result": "OK",
"data":{
"user_seq": user_seq
}
}
else:
return {
"result": "FAIL",
"msg": payload['error']
}
except Exception as e:
logger.error(f"token error. message: {e}")
return {
"result": "FAIL",
"msg": 'get_user_seq_by_token error'
}

View File

@ -0,0 +1,106 @@
from fastapi import FastAPI, HTTPException, BackgroundTasks
from pydantic import BaseModel, EmailStr
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import os
import time
import datetime
from jinja2 import Template
from process.logger import logger
from common.config import SMTP_PASSWORD, SMTP_PORT, SMTP_SERVER, SMTP_USERNAME
# HTML 템플릿을 로드하고 렌더링
def render_template(template_path, context):
with open(template_path, 'r') as file:
template = Template(file.read())
return template.render(context)
# 메일 발송 함수
def send_email(email_to: str, subject: str, html_content: str):
try:
msg = MIMEMultipart("alternative")
msg['From'] = SMTP_USERNAME
msg['To'] = email_to
msg['Subject'] = subject
msg.attach(MIMEText(html_content, 'html'))
server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
server.starttls()
server.login(SMTP_USERNAME, SMTP_PASSWORD)
# 이메일 발송
server.sendmail(SMTP_USERNAME, email_to, msg.as_string())
server.quit()
return {
"result": 'OK',
"msg": "이메일 발송에 성공했습니다."
}
except Exception as e:
logger.error(f"이메일 발송에 실패했습니다. error_msg: {e}")
return {
"result": 'FAIL',
"msg": "이메일 발송에 실패했습니다."
}
# 이메일 발송 API 엔드포인트
def send_email_api(context):
try:
html_content = render_template(f"/app/process/email/{context['template_name']}", context)
send_email_result = send_email(email_to=context['user_email'], subject=context['email_title'], html_content=html_content)
return send_email_result
except Exception as e:
logger.error(f"이메일 발송에 실패했습니다. error_msg: {e}")
return {
"result": 'FAIL',
"msg": "이메일 발송에 실패했습니다."
}
# 아이디 찾기 이메일 발송 함수
def email_find_id(user_email, info):
context = {
"email_title": "[ALLSCORE] 아이디가 발송되었습니다.",
"info": info,
"user_email": user_email,
"template_name": "find_id_template.html",
}
try:
return send_email_api(context)
except Exception as e:
logger.error(f"이메일 발송에 실패했습니다. error_msg: {e}")
return {
"result": 'FAIL',
"msg": "이메일 발송에 실패했습니다."
}
# 비밀번호 찾기 이메일 발송 함수
def email_find_password(user_email, info):
context = {
"email_title": "[유어라운드] 임시 비밀번호가 발급되었습니다.",
"info": info,
"user_email": user_email,
"template_name": "find_password_template.html",
}
try:
return send_email_api(context)
except Exception as e:
logger.error(f"이메일 발송에 실패했습니다. error_msg: {e}")
return {
"result": 'FAIL',
"msg": "이메일 발송에 실패했습니다."
}

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email Template</title>
</head>
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #ffffff; text-align: center;">
<table role="presentation" style="width: 100%; max-width: 600px; margin: 0 auto; padding: 20px; background-color: #ffffff; border-radius: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);">
<tr>
<td style="text-align: center; padding: 20px;">
<h1 style="font-size: 24px; color: #333333; margin-bottom: 10px;">고객님의 아이디는 다음과 같습니다.</h1>
<p style="font-size: 20px; font-weight: bold; color: #666666; margin-bottom: 20px;">{{ info }}</p>
</td>
</tr>
<tr>
<td style="text-align: center; padding: 20px;">
<hr style="border-top: 1px solid #d9d9d9; margin: 20px 0;">
<p style="font-size: 12px; color: #999999; margin: 5px 0;">본 메일은 발신 전용으로 회신되지 않습니다.</p>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email Template</title>
</head>
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #ffffff; text-align: center;">
<table role="presentation" style="width: 100%; max-width: 700px; margin: 0 auto; padding: 20px; background-color: #ffffff; border-radius: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);">
<tr>
<td style="text-align: center; padding: 20px;">
<h1 style="font-size: 24px; color: #333333; margin-bottom: 10px;">고객님의 임시 비밀번호는 다음과 같습니다.</h1>
<p style="font-size: 20px; font-weight: bold; color: #666666; margin-bottom: 20px;">{{ info }}</p>
<h3 style="font-size: 12px; color: #333333; margin-bottom: 10px;">임시 비밀번호로 로그인 한 후 마이페이지에서 비밀번호를 변경해 주세요.</h3>
</td>
</tr>
<tr>
<td style="text-align: center; padding: 20px;">
<hr style="border-top: 1px solid #d9d9d9; margin: 20px 0;">
<p style="font-size: 12px; color: #999999; margin: 5px 0;">본 메일은 발신 전용으로 회신되지 않습니다.</p>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,55 @@
import logging
import sys
from logging.handlers import TimedRotatingFileHandler
import os
from datetime import datetime
class CustomTimedRotatingFileHandler(TimedRotatingFileHandler):
def __init__(self, base_log_path, when="midnight", interval=1, backupCount=7):
self.base_log_path = base_log_path
# 초기화 시점에 파일 경로를 명시적으로 설정
self.update_log_path()
super().__init__(self.baseFilename, when=when, interval=interval, backupCount=backupCount)
def update_log_path(self):
current_time = datetime.now()
log_directory = os.path.join(
self.base_log_path,
current_time.strftime("%Y"),
current_time.strftime("%m")
)
log_filename = current_time.strftime("%d_allscore.log")
# 로그 파일 경로 설정
self.baseFilename = os.path.join(log_directory, log_filename)
# 디렉토리가 없으면 생성
if not os.path.exists(log_directory):
os.makedirs(log_directory)
def emit(self, record):
# 각 로그 기록 시점에서 경로를 다시 확인해 업데이트
self.update_log_path()
super().emit(record)
# 로거 설정
logger = logging.getLogger("allscore_logger")
logger.setLevel(logging.DEBUG)
# 핸들러 설정 (stdout으로 출력)
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(logging.DEBUG)
# 로그 포맷 설정
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
stream_handler.setFormatter(formatter)
# 커스텀 파일 핸들러 설정
base_log_path = "/app/logs"
file_handler = CustomTimedRotatingFileHandler(base_log_path=base_log_path, when="midnight", interval=1, backupCount=7)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
# 핸들러를 로거에 추가
logger.addHandler(stream_handler)
logger.addHandler(file_handler)

View File

@ -0,0 +1,96 @@
from fastapi import Request
import json
from process.logger import logger
import jwt
import datetime
import time
from process.certification import cert_process
from process.user import manage_user
# 성공 응답
async def ok_res(auth_token, data, db):
try:
# 토큰에서 user_seq 찾기
user_seq_result = cert_process.get_user_seq_by_token(token=auth_token['token'])
if user_seq_result['result'] == 'OK':
user_seq = user_seq_result['data']['user_seq']
else:
return make_response('OK', auth_token['renew_yn'], 'NOMAL', auth_token['token'], '성공', '성공', data)
return make_response('OK', auth_token['renew_yn'], '', auth_token['token'], '성공', '성공', data)
except Exception as e:
logger.error(f"ok response error: {e}")
return make_response('ERROR', 'N', 'NOMAL', '', '응답 에러', '서버 장애가 발생했습니다.', data)
# 실패 응답
async def fail_res(auth_token, auth_type, msg_title, msg_content, data):
try:
return make_response('FAIL', auth_token['renew_yn'], auth_type, auth_token['token'], msg_title, msg_content, data)
except Exception as e:
logger.error(f"ok response error: {e}")
return make_response('ERROR', 'N', 'NOMAL', '', '응답 에러', '서버 장애가 발생했습니다.', data)
# 에러 응답
async def error_res(auth_token, auth_type, msg_title, msg_content, data):
try:
return make_response('ERROR', auth_token['renew_yn'], auth_type, auth_token['token'], msg_title, msg_content, data)
except Exception as e:
logger.error(f"ok response error: {e}")
return make_response('ERROR', 'N', 'NOMAL', '', '응답 에러', '서버 장애가 발생했습니다.', data)
# 응답 패턴
def make_response(result, renew_yn, auth_type, token, msg_title, msg_content, data):
return {
"result": result, # OK, FAIL, ERROR
"auth": {
"renew_yn": renew_yn,
"type": auth_type,
"token": token,
},
"response_info": {
"msg_type": result, # OK, FAIL, ERROR
"msg_title": msg_title,
"msg_content": msg_content,
},
"data": data,
}
# # 기존 응답 방식
# {
# "result": "FAIL",
# "msg": fail_msg,
# "auth": {
# "auth-token": {
# "renew_yn": 'Y',
# "token": '토큰값'
# }
# },
# "data": {}
# }
# # 새로운 응답 방식
# {
# "result": "FAIL", # OK, FAIL, ERROR
# "data": {
# "auth": {
# "renew_yn": 'Y',
# "type": 'ADMIN',
# "token": '토큰값'
# },
# "response_info": {
# "msg_type": 'FAIL', # OK, FAIL, ERROR
# "msg_title": '제목',
# "msg_content": '내용',
# },
# "data": '데이터',
# }
# }

View File

@ -0,0 +1,31 @@
from fastapi import APIRouter, Depends, HTTPException, Header, Body, status, Request
from db import crud_room_score
from sqlalchemy.orm import Session
from db.base import get_db
from db import models, schemas
from process.logger import logger
# 방 만들기
async def create_room(data, db):
db_result_ori = crud_room_score.create_room(data=data, db=db)
db_result = []
for db_data in db_result_ori:
db_result.append(db_data)
# 일치하는 유저가 있는지 확인
if len(db_result) == 0:
return {
"result": "FAIL"
}
else:
return {
"result": "OK",
"user_seq": db_result[0]
}

View File

@ -0,0 +1,240 @@
from fastapi import APIRouter, Depends, HTTPException, Header, Body, status, Request
from db import crud_user
from sqlalchemy.orm import Session
from db.base import get_db
from db import models, schemas
from process.logger import logger
# 로그인 수행
async def do_login(user_id, user_pw, db):
# DB에서 유저 정보 확인
db_result_ori = crud_user.do_login(user_id=user_id, user_pw=user_pw, db=db)
db_result = []
for db_data in db_result_ori:
db_result.append(db_data)
# 일치하는 유저가 있는지 확인
if len(db_result) == 0:
return {
"result": "FAIL"
}
else:
return {
"result": "OK",
"user_seq": db_result[0]
}
# 마지막 로그인 시간 업데이트
async def update_last_login_dt(user_seq, db):
# 마지막 로그인 시간 업데이트
db_result = crud_user.update_last_login_dt(user_seq=user_seq, db=db)
if db_result:
return {
"result": "OK"
}
else:
return {
"result": "FAIL",
}
# 아이디 찾기(닉네임, 이메일)
async def find_id_by_name_email(nickname, user_email, db):
# DB에서 회원정보 찾기
db_result_ori = crud_user.find_id_by_name_email(nickname=nickname, user_email=user_email, db=db)
db_result = []
for db_data in db_result_ori:
db_result.append(db_data)
# 일치하는 유저가 있는지 확인
if len(db_result) == 0:
return {
"result": "FAIL"
}
else:
db_result = db_result[0]
return {
"result": "OK",
"user_seq": db_result[0],
"user_id": db_result[1]
}
# 아이디 찾기(user_seq)
async def find_id_by_user_seq(user_seq, db):
# DB에서 회원정보 찾기
db_result_ori = crud_user.find_id_by_user_seq(user_seq=user_seq, db=db)
db_result = []
for db_data in db_result_ori:
db_result.append(db_data)
# 일치하는 유저가 있는지 확인
if len(db_result) == 0:
return {
"result": "FAIL"
}
else:
db_result = db_result[0]
return {
"result": "OK",
"user_id": db_result[0],
"user_email": db_result[1]
}
# 이메일 발송 3회 이상 됐는지 확인
async def select_send_email_cnt(user_email, db):
db_result_ori = crud_user.select_send_email_cnt(user_email=user_email, db=db)
db_result = []
for db_data in db_result_ori:
db_result.append(db_data)
db_result = db_result[0][0]
return {
"result": "OK",
"send_email_cnt": db_result
}
# 이메일 발송내역 기록(하루 3회만 발송가능하게 하기 위함)
async def insert_send_email_info(user_seq, cert_type, user_info, cert_code, db):
db_result = crud_user.insert_send_email_info(user_seq=user_seq, cert_type=cert_type, user_info=user_info, cert_code=cert_code, db=db)
if db_result:
return {
"result": "OK"
}
else:
return {
"result": "FAIL",
}
# 비밀번호 찾기 수행
async def find_password_by_id_email(user_id, user_email, db):
# DB에서 회원정보 찾기
db_result_ori = crud_user.find_password_by_id_email(user_id=user_id, user_email=user_email, db=db)
db_result = []
for db_data in db_result_ori:
db_result.append(db_data)
# 일치하는 유저가 있는지 확인
if len(db_result) == 0:
return {
"result": "FAIL"
}
else:
return {
"result": "OK",
"user_seq": db_result[0]
}
# 비밀번호 업데이트 수행
async def update_new_password(user_seq, new_pw, new_solt, db):
# 회원정보 업데이트
db_result = crud_user.update_new_password(user_seq=user_seq, new_pw=new_pw, new_solt=new_solt, db=db)
if db_result:
return {
"result": "OK"
}
else:
return {
"result": "FAIL",
}
# 유저 회원가입 진행
async def insert_new_user(user_info, db):
# DB에서 신규 유저 등록
db_result_manage_user = crud_user.insert_new_user(user_info=user_info, db=db)
# 인증코드 입력 결과
if not db_result_manage_user:
return {
"result": "FAIL"
}
else:
return {
"result": "OK",
}
# 마이페이지 - 유저 정보 가져오기
async def get_my_info_by_user_seq(user_seq, db):
# DB에서 유저 정보 가져오기
db_result_ori = crud_user.get_my_info_by_user_seq(user_seq=user_seq, db=db)
db_result = []
for db_data in db_result_ori:
db_result.append(db_data)
# 데이터 1개만 존재하는지 확인
if len(db_result) == 1:
# 데이터 key value 구분해주기
db_result = db_result[0]
return {
"result": "OK",
"data": {
"nickname": db_result[0],
"user_email": db_result[1],
"department": db_result[2],
"profile_img": db_result[3],
"introduce_myself": db_result[4]
}
}
return {
"result": "FAIL"
}
# 현재 비밀번호 일치 확인
async def check_current_user_pw(user_seq, user_pw, db):
db_result_ori = crud_user.check_current_user_pw(user_seq=user_seq, user_pw=user_pw, db=db)
db_result = []
for db_data in db_result_ori:
db_result.append(db_data)
if db_result[0][0] == 1:
return {
"result": "OK"
}
else:
return {
"result": "FAIL",
}
# 회원정보 업데이트
async def update_user_info(user_info, db):
# 회원정보 업데이트
db_result = crud_user.update_user_info(user_info=user_info, db=db)
if db_result:
return {
"result": "OK"
}
else:
return {
"result": "FAIL",
}
# 회원 탈퇴 처리
async def user_withdraw(user_seq, db):
db_result = crud_user.user_withdraw(user_seq=user_seq, db=db)
if db_result:
return {
"result": "OK"
}
else:
return {
"result": "FAIL",
}

View File

@ -0,0 +1,116 @@
from fastapi import APIRouter, Depends, HTTPException, Header, Body, status, Request
from db import crud_user
from sqlalchemy.orm import Session
from db import models, schemas
from process.logger import logger
import re
# 회원가입 입력항목 확인
def check_mandatory_insert_list(user_info):
# user_id 확인
if not validate_user_id(user_id=user_info['user_id']):
return '아이디 규칙이 맞지 않습니다.'
# user_pw 확인
if not validate_user_pw(user_pw=user_info['user_pw']):
return '비밀번호 암호화가 되지 않았습니다.'
# nickname 확인
if not validate_nickname(nickname=user_info['nickname']):
return '사용자 닉네임 규칙이 맞지 않습니다.'
# user_email 확인
if not validate_user_email(user_email=user_info['user_email']):
return '이메일 규칙이 맞지 않습니다.'
# introduce_myself 확인
if len(user_info['introduce_myself']) > 2000:
return '자기소개 글은 2000byte까지만 입력 가능합니다.'
# mandatory_terms 확인
if user_info['mandatory_terms_yn'] != 'Y':
return '필수약관항목에 대해 동의 하지 않았습니다.'
# 이상없음
return None
# 회원정보 수정 입력값 검증
def check_new_data(user_info):
# user_pw 확인
if user_info['user_pw_change_yn'] == 'Y':
if not validate_user_pw(user_pw=user_info['user_pw']):
return '비밀번호 암호화가 되지 않았습니다.'
# nickname 확인
if not validate_nickname(nickname=user_info['nickname']):
return '사용자 닉네임 규칙이 맞지 않습니다.'
# user_email 확인
if not validate_user_email(user_email=user_info['user_email']):
return '이메일 규칙이 맞지 않습니다.'
# profile_img 확인
if not starts_with_user_dir(user_info['profile_img']):
return '프로필 이미지 경로가 정확하지 않습니다.'
# introduce_myself 확인
if len(user_info['introduce_myself']) > 2000:
return '자기소개 글은 2000byte까지만 입력 가능합니다.'
# 이상없음
return None
# user_id 정규식 검증
def validate_user_id(user_id: str) -> bool:
pattern = r'^[a-zA-Z0-9]{6,20}$'
return bool(re.match(pattern, user_id))
# user_pw sha256 검증
def validate_user_pw(user_pw: str) -> bool:
return True if len(user_pw) == 64 else False
# nickname 정규식 검증
def validate_nickname(nickname: str) -> bool:
# 바이트 계산
byte_length = sum(2 if ord(char) > 127 else 1 for char in nickname)
# 정규식으로 한글, 영어, 숫자만 허용
pattern = re.compile(r'^[가-힣a-zA-Z0-9]+$')
if pattern.match(nickname) and 2 <= byte_length <= 20:
return True
return False
# user_email 정규식 검증
def validate_user_email(user_email: str) -> bool:
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, user_email))
# user_phone 정규식 검증
def validate_user_phone(user_phone: str) -> bool:
pattern = r'^010\d{8}$'
return bool(re.match(pattern, user_phone))
# 유저 아이디 중복 검사
async def is_valid_user_id_by_user_id(user_id, db):
# DB에서 user_id 중복 확인하기
db_result_ori = crud_user.is_valid_user_id_by_user_id(user_id=user_id, db=db)
db_result = []
for db_data in db_result_ori:
db_result.append(db_data)
return True if len(db_result) == 0 else False
# profile_img 파일명이 /user/temp_dir/profile_img/IMG 로 시작하는지 검증
def starts_with_user_dir(value: str) -> bool:
prefix = '/user/temp_dir/profile_img/IMG'
return value.startswith(prefix)

View File

@ -0,0 +1,17 @@
fastapi>=0.68.0,<0.69.0
pydantic>=1.8.0,<2.0.0
uvicorn>=0.15.0,<0.16.0
SQLAlchemy==1.3.15
psycopg2==2.9.3
matplotlib==3.5.0
numpy==1.21.4
pandas==1.3.5
typing
kafka-python==2.0.2
openpyxl==3.0.7
aiofiles==0.6.0
pyjwt==2.4.0
pydantic[email]>=1.8.0,<2.0.0
Jinja2>=3.1.2,<4.0.0
requests>=2.25.0,<3.0.0
python-multipart==0.0.6 # python-multipart 추가

View File

@ -0,0 +1,70 @@
from fastapi import APIRouter, Depends, HTTPException, Header, Body, status, Request
from sqlalchemy.orm import Session
from fastapi.security import APIKeyHeader
from typing import Union, Optional, List
from typing_extensions import Annotated
from db import models, schemas, crud
import json
import logging
import datetime
from kafka import KafkaProducer
from fastapi.responses import FileResponse, StreamingResponse
import io
import openpyxl
from db.schemas import RawData, RtuGenerator
from db.base import get_db
from db.models import RawDatas, Raw_data_herit
import pandas as pd
KST = datetime.timezone(datetime.timedelta(hours=9))
router = APIRouter(
prefix="/api/card",
tags=["api", "card"],
responses={404: {"description": "Not found"}},
)
# -----------------------------------------------------------------------------------------------
async def get_body(request: Request):
return await request.body()
@router.post("/regist", response_model=schemas.CardExpense)
def create_card_expense(card_expense: schemas.CardExpenseCreate, db: Session = Depends(get_db)):
return crud.create_card_expense(db=db, card_expense=card_expense)
@router.post("/excel", response_model=schemas.CardExpense)
async def create_excel(payload: schemas.DatePayload, db: Session = Depends(get_db)):
results = crud.get_card_expenses_by_date(db, payload.year, payload.month)
if not results:
raise HTTPException(status_code=404, detail="No data found for the specified month")
# Convert results to pandas DataFrame
df = pd.DataFrame([dict(result) for result in results])
# Create an Excel file in memory
output = io.BytesIO()
writer = pd.ExcelWriter(output, engine='openpyxl')
df.to_excel(writer, index=False, sheet_name='Summary')
writer.save()
output.seek(0)
# Return the Excel file
response = StreamingResponse(output, media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response.headers["Content-Disposition"] = "attachment; filename=summary.xlsx"
return response
# 테스트 URL
@router.post("/test")
async def col_raw_data_from_herit(
body: bytes = Depends(get_body)
):
return {
"reason": "OK2",
"body": body
}

View File

@ -0,0 +1,57 @@
from fastapi import APIRouter, Depends, HTTPException, Header, Body, status, Request
# from db import crud_main
from sqlalchemy.orm import Session
from fastapi.security import APIKeyHeader
from typing import Union, Optional, List
from typing_extensions import Annotated
from db import models, schemas
import datetime
from fastapi.responses import FileResponse
import os
from process.logger import logger
from db.base import get_db
import pandas as pd
KST = datetime.timezone(datetime.timedelta(hours=9))
router = APIRouter(
prefix="/images",
tags=["main"],
responses={404: {"description": "Not found"}},
)
# -----------------------------------------------------------------------------------------------
async def get_body(request: Request):
return await request.body()
# 이미지 제공
@router.post("/{path1}/{path2}/{path3}/{file_name}")
async def get_image(path1: str, path2: str, path3: str, file_name: str):
# 이미지 파일 경로 설정 (예: 서버의 특정 디렉토리)
image_path = os.path.join("images", path1, path2, path3, file_name)
# 이미지 파일이 존재하는지 확인
if not os.path.exists(image_path):
return {"error": "Image not found"}
# FileResponse를 사용하여 이미지 파일 반환
return FileResponse(image_path)
# 이미지 제공
@router.get("/{path1}/{path2}/{path3}/{file_name}")
async def get_image1(path1: str, path2: str, path3: str, file_name: str):
# if path1 != 'email':
# return {"error": "path is not allow"}
# 이미지 파일 경로 설정 (예: 서버의 특정 디렉토리)
image_path = os.path.join("images", path1, path2, path3, file_name)
# 이미지 파일이 존재하는지 확인
if not os.path.exists(image_path):
return {"error": "Image not found"}
# FileResponse를 사용하여 이미지 파일 반환
return FileResponse(image_path)

View File

@ -0,0 +1,557 @@
from fastapi import APIRouter, Depends, HTTPException, Header, Body, status, Request, File, UploadFile, Form
from sqlalchemy.orm import Session
from fastapi.security import APIKeyHeader
from typing import Union, Optional, List
from typing_extensions import Annotated
from db import models, schemas, crud
import json
import logging
import datetime
from kafka import KafkaProducer
from fastapi.responses import FileResponse, StreamingResponse
import os
import openpyxl
import time
import random
import string
from db.schemas import RawData, RtuGenerator
from db.base import get_db
from process.logger import logger
from process.certification import cert_process
from process.response import response
from process.user import manage_user
from process.user import manage_user_pattern
from process.email import email_user
KST = datetime.timezone(datetime.timedelta(hours=9))
router = APIRouter(
prefix="/user",
tags=["user"],
responses={404: {"description": "Not found"}},
)
# -----------------------------------------------------------------------------------------------
async def get_body(request: Request):
return await request.body()
#==================================================================================================
# 로그인
#==================================================================================================
@router.post("/login")
async def login(request: Request, body: bytes = Depends(get_body), db: Session = Depends(get_db)):
try:
# 인증서 갱신
auth_token = cert_process.renew_cert(request=request)
auth_token = auth_token['data']
# body에서 ID, PW 추출
try:
body = json.loads(body)
except json.JSONDecodeError as e:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='데이터 에러', msg_content='데이터 처리 장애가 발생했습니다. 요청정보를 정확히 입력했는지 확인해주세요.', data={})
user_id = body['user_id']
user_pw = body['user_pw']
# ID, PW로 로그인 시도
login_result = await manage_user.do_login(user_id= user_id, user_pw= user_pw, db=db)
# 로그인 결과에 따른 응답
if login_result['result'] == 'OK':
user_seq = (login_result['user_seq'])[0]
# 마지막 로그인 시간 업데이트
last_login_dt_result = await manage_user.update_last_login_dt(user_seq=user_seq, db=db)
if last_login_dt_result['result'] == 'OK':
auth_token = {
"renew_yn": 'Y',
"token": cert_process.create_jwt(user_seq=user_seq, period=3600)
}
return await response.ok_res(auth_token=auth_token, data={}, db=db)
else:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='로그인 실패', msg_content='로그인 정보 업데이트에 실패했습니다.', data={})
else:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='로그인 실패', msg_content='회원정보를 다시 확인해주세요.', data={})
except Exception as e:
logger.error(f"request error. URL: /user/login\nerror message: {e}")
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='로그인 에러', msg_content='로그인 처리중 에러가 발생했습니다.', data={})
#==================================================================================================
# 아이디 찾기
#==================================================================================================
@router.post("/find/id")
async def find_id(request: Request, body: bytes = Depends(get_body), db: Session = Depends(get_db)):
try:
# 인증서 갱신
auth_token = cert_process.renew_cert(request=request)
auth_token = auth_token['data']
# body에서 ID, PW 추출
try:
body = json.loads(body)
except json.JSONDecodeError as e:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='데이터 에러', msg_content='데이터 처리 장애가 발생했습니다. 요청정보를 정확히 입력했는지 확인해주세요.', data={})
nickname = body['nickname']
user_email = body['user_email']
# 닉네임 패턴 검증
if not manage_user_pattern.validate_nickname(nickname=nickname):
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='이름 확인', msg_content='이름을 정확히 입력해주세요.', data={})
# 이메일 패턴 검증
if not manage_user_pattern.validate_user_email(user_email=user_email):
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='이메일 확인', msg_content='이메일 주소를 정확히 입력해주세요.', data={})
# 닉네임, EMAIL로 아이디 찾기 시도
find_id_result = await manage_user.find_id_by_name_email(nickname=nickname, user_email=user_email, db=db)
# 아이디 찾기 시도 결과에 따른 응답
if find_id_result['result'] == 'OK':
# user_seq, user_id 확인
user_seq = find_id_result['user_seq']
user_id = find_id_result['user_id']
# ID 마스킹 처리
user_id = user_id[:int(len(user_id)/2)] + ''.join(['*' for _ in range(int(len(user_id)/2))])
data = {
"auth": cert_process.create_jwt(user_seq=user_seq, period=600),
"user_id": user_id,
}
return await response.ok_res(auth_token=auth_token, data=data, db=db)
else:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='아이디 찾기 실패', msg_content='회원정보를 다시 확인해주세요.', data={})
except Exception as e:
logger.error(f"request error. URL: /user/find/id\nerror message: {e}")
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='로그인 에러', msg_content='로그인 처리중 에러가 발생했습니다.', data={})
#==================================================================================================
# 아이디 이메일 전송
#==================================================================================================
@router.post("/find/id/full")
async def find_receive_by_eamil(request: Request, body: bytes = Depends(get_body), db: Session = Depends(get_db)):
try:
# 인증서 갱신
auth_token = cert_process.renew_cert(request=request)
auth_token = auth_token['data']
# body에서 ID, PW 추출
try:
body = json.loads(body)
except json.JSONDecodeError as e:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='데이터 에러', msg_content='데이터 처리 장애가 발생했습니다. 요청정보를 정확히 입력했는지 확인해주세요.', data={})
auth = body['auth']
auth_data = cert_process.decode_jwt(auth)
if auth_data['result'] == 'OK':
auth_data = auth_data['data']
user_seq = auth_data['user_seq']
exp = auth_data['exp']
iat = auth_data['iat']
current_time = int(time.time())
if not(iat <= current_time and current_time <= exp):
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='이메일 발송 실패', msg_content='토큰 유효기간이 만료되었습니다.', data={})
else:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='토큰 에러', msg_content='토큰 정보가 정확하지 않습니다.', data={})
# user_seq로 아이디 찾기 시도
find_id_result = await manage_user.find_id_by_user_seq(user_seq=user_seq, db=db)
# 아이디 찾기 시도 결과에 따른 응답
if find_id_result['result'] == 'OK':
# user_id, user_email 확인
user_id = find_id_result['user_id']
user_email = find_id_result['user_email']
# 이메일 발송 3회 이상 됐는지 확인
send_email_cnt_result = await manage_user.select_send_email_cnt(user_email=user_email, db=db)
if send_email_cnt_result['result'] == 'OK':
send_email_cnt = send_email_cnt_result['send_email_cnt']
if send_email_cnt >= 3:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='이메일 발송 실패', msg_content='이메일 발송은 하루 3번까지 가능합니다.', data={})
else:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='이메일 발송 실패', msg_content='이메일 발송 실패했습니다.', data={})
# 이메일 발송 인증 내역 입력
insert_email_cert_result = await manage_user.insert_send_email_info(user_seq=user_seq, cert_type='email', user_info=user_email, cert_code=user_id, db=db)
if insert_email_cert_result['result'] == 'FAIL':
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='이메일 발송 실패', msg_content='이메일 발송 실패했습니다.', data={})
# 이메일로 유저 아이디 발송해주기
send_email_result = email_user.email_find_id(user_email=user_email, info=user_id)
if send_email_result['result'] == 'OK':
return await response.ok_res(auth_token=auth_token, data={"send_email_result": True}, db=db)
else:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='이메일 발송 실패', msg_content=send_email_result['msg'], data={})
else:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='이메일 발송 실패', msg_content='이메일 발송 실패했습니다.', data={})
except Exception as e:
logger.error(f"request error. URL: /user/find/id/full\nerror message: {e}")
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='아이디 찾기 에러', msg_content='아이디 찾기 이메일 발송 처리중 에러가 발생했습니다.', data={})
#==================================================================================================
# 비밀번호 찾기
#==================================================================================================
@router.post("/find/password")
async def find_pw(request: Request, body: bytes = Depends(get_body), db: Session = Depends(get_db)):
try:
# 인증서 갱신
auth_token = cert_process.renew_cert(request=request)
auth_token = auth_token['data']
# body에서 ID, PW 추출
try:
body = json.loads(body)
except json.JSONDecodeError as e:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='데이터 에러', msg_content='데이터 처리 장애가 발생했습니다. 요청정보를 정확히 입력했는지 확인해주세요.', data={})
user_id = body['user_id']
user_email = body['user_email']
# 아이디 패턴 검증
if not manage_user_pattern.validate_user_id(user_id=user_id):
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='아이디 확인', msg_content='아이디를 정확히 입력해주세요.', data={})
# 이메일 패턴 검증
if not manage_user_pattern.validate_user_email(user_email=user_email):
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='이메일 확인', msg_content='이메일 주소를 정확히 입력해주세요.', data={})
# 이메일 발송 3회 이상 됐는지 확인
send_email_cnt_result = await manage_user.select_send_email_cnt(user_email=user_email, db=db)
if send_email_cnt_result['result'] == 'OK':
send_email_cnt = send_email_cnt_result['send_email_cnt']
if send_email_cnt >= 3:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='이메일 발송 실패', msg_content='이메일 발송은 하루 3번까지 가능합니다.', data={})
# ID, EMAIL로 비밀번호 찾기 시도
find_password_result = await manage_user.find_password_by_id_email(user_id= user_id, user_email= user_email, db=db)
logger.debug(f"test1")
# 비밀번호 찾기 시도 결과에 따른 응답
if find_password_result['result'] == 'OK':
# user_seq 확인, 신규 PW&SOLT 생성하기
user_seq = (find_password_result['user_seq'])[0]
new_pw = generate_random_string(20)
new_solt = generate_random_string(10)
# DB에 신규로 생성된 비밀번호 업데이트 하기
update_pw_result = await manage_user.update_new_password(user_seq=user_seq, new_pw=new_pw, new_solt=new_solt, db=db)
logger.debug(f"test2")
if update_pw_result['result'] == 'OK':
# DB 업데이트가 성공하면 이메일로 유저 임시 비밀번호 발송해주기
send_email_result = email_user.email_find_password(user_email=user_email, info=new_pw)
logger.debug(f"test3")
if send_email_result['result'] == 'OK':
return await response.ok_res(auth_token=auth_token, data={"send_email_result": send_email_result}, db=db)
else:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='이메일 발송 실패', msg_content=send_email_result['msg'], data={})
else:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='비밀번호 업데이트 실패', msg_content='임시 비밀번호 업데이트에 실패했습니다.', data={})
else:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='비밀번호 찾기 실패', msg_content='회원정보를 다시 확인해주세요.', data={})
except Exception as e:
logger.error(f"request error. URL: /user/find/password\nerror message: {e}")
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='로그인 에러', msg_content='로그인 처리중 에러가 발생했습니다.', data={})
#==================================================================================================
# 회원가입
#==================================================================================================
@router.post("/signup")
async def signup(request: Request, body: bytes = Depends(get_body), db: Session = Depends(get_db)):
try:
# 인증서 갱신
auth_token = cert_process.renew_cert(request=request)
auth_token = auth_token['data']
# body에서 ID 추출
try:
body = json.loads(body)
except json.JSONDecodeError as e:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='데이터 에러', msg_content='데이터 처리 장애가 발생했습니다. 요청정보를 정확히 입력했는지 확인해주세요.', data={})
# user_id 유효성 검사
is_valid_user_id = await manage_user_pattern.is_valid_user_id_by_user_id(user_id=body['user_id'], db=db)
fail_msg = None if is_valid_user_id else '아이디가 이미 존재합니다.'
if fail_msg is not None:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='회원가입 실패', msg_content=fail_msg, data={})
# 필수 입력항목 패턴 확인
mandatory_list_verify_msg = manage_user_pattern.check_mandatory_insert_list(user_info=body)
if mandatory_list_verify_msg is not None:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='회원가입 실패', msg_content=mandatory_list_verify_msg, data={})
# 회원가입 DB 입력
body['user_pw_solt'] = generate_random_string(10) # 비밀번호 SOLT 생성
insert_new_user_result = await manage_user.insert_new_user(user_info=body, db=db)
if insert_new_user_result['result'] == 'OK':
return await response.ok_res(auth_token=auth_token, data={}, db=db)
else:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='회원가입 실패', msg_content='회원가입에 실패했습니다. 입력하신 정보를 다시 확인해주세요.', data={})
except Exception as e:
logger.error(f"request error. URL: /user/signup\nerror message: {e}")
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='로그인 에러', msg_content='로그인 처리중 에러가 발생했습니다.', data={})
#==================================================================================================
# 프로필 이미지 등록
#==================================================================================================
@router.post("/update/profile/img")
async def update_profile_img(request: Request, body: str = Form(...), file: UploadFile = File(...), db: Session = Depends(get_db)):
try:
# 인증서 갱신
auth_token = cert_process.renew_cert(request=request)
if auth_token['result'] == 'OK':
auth_token = auth_token['data']
elif auth_token['result'] == 'TOKEN_EXPIRED':
raise token_expired_return_process(auth_token['msg'])
else:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='토큰 에러', msg_content='토큰 정보가 정확하지 않습니다.', data={})
# body에서 ID 추출
try:
body = json.loads(body)
except json.JSONDecodeError as e:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='데이터 에러', msg_content='데이터 처리 장애가 발생했습니다. 요청정보를 정확히 입력했는지 확인해주세요.', data={})
# 파일 읽기
content = await file.read()
# 파일 크기 제한
MAX_FILE_SIZE = 5 * 1024 * 1024 # 5MB
file_size = len(content) # 파일 크기 확인
if file_size > MAX_FILE_SIZE:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='이미지 업로드 실패', msg_content='이미지 파일 크기는 5MB까지 가능합니다.', data={})
# body에 들어있는 이미지명 처리
user_seq_result = cert_process.get_user_seq_by_token(auth_token['token'])
if user_seq_result['result'] == 'OK':
# user_seq 추출
user_seq = user_seq_result['data']['user_seq']
# 현재 시간 가져오기 YYYYMMDDHHMMSSsss
now_time = get_now_time_str()
# 확장자 확인하고 텍스트 만들기
extension = os.path.splitext(file.filename)[1].lower()
if extension not in ['.jpg', 'jpeg', '.png', 'bmp']:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='이미지 업로드 실패', msg_content='지원하지 않는 확장자입니다.', data={})
# IMG_(user_seq)_현재시간 으로 파일명 만들기
file_name = f"IMG_{user_seq}_{now_time}{extension}"
# 파일 업로드 코드 시작
# 경로 존재하지 않으면 생성하기
UPLOAD_DIRECTORY = f"images/user/temp_dir/profile_img/"
if not os.path.exists(UPLOAD_DIRECTORY):
os.makedirs(UPLOAD_DIRECTORY)
# 파일 저장 경로 생성
file_path = os.path.join(UPLOAD_DIRECTORY, file_name)
# 파일을 서버에 저장
with open(file_path, "wb") as buffer:
buffer.write(content)
# 실제로 잘 저장됐는지 확인
if os.path.exists(file_path) and os.path.getsize(file_path) > 0:
return await response.ok_res(auth_token=auth_token, data={"img_src": '/'+file_path}, db=db)
else:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='이미지 업로드 실패', msg_content='이미지 업로드에 실패했습니다.', data={})
else:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='토큰 에러', msg_content='토큰 정보가 정확하지 않습니다.', data={})
except Exception as e:
logger.error(f"request error. URL: /user/update/profile/img\nerror message: {e}")
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='로그인 에러', msg_content='로그인 처리중 에러가 발생했습니다.', data={})
#==================================================================================================
# 내 정보 가져오기
#==================================================================================================
@router.post("/myinfo")
async def update_user_info(request: Request, body: bytes = Depends(get_body), db: Session = Depends(get_db)):
try:
# 인증서 갱신
auth_token = cert_process.renew_cert(request=request)
if auth_token['result'] == 'OK':
auth_token = auth_token['data']
elif auth_token['result'] == 'TOKEN_EXPIRED':
raise token_expired_return_process(auth_token['msg'])
else:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='토큰 에러', msg_content='토큰 정보가 정확하지 않습니다.', data={})
# body에서 ID 추출
try:
body = json.loads(body)
except json.JSONDecodeError as e:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='데이터 에러', msg_content='데이터 처리 장애가 발생했습니다. 요청정보를 정확히 입력했는지 확인해주세요.', data={})
user_seq_result = cert_process.get_user_seq_by_token(token=auth_token['token'])
if user_seq_result["result"] == 'OK':
user_seq = user_seq_result['data']['user_seq']
# 유저 정보 가져오기
user_data = await manage_user.get_my_info_by_user_seq(user_seq=user_seq, db=db)
if user_data['result'] == 'OK':
return await response.ok_res(auth_token=auth_token, data=user_data['data'], db=db)
logger.error(f"request error. URL: /user/myinfo\nerror message: {e}")
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='마이페이지 내정보 가져오기 에러', msg_content='마이페이지 데이터 조회중 에러가 발생했습니다.', data={})
except Exception as e:
logger.error(f"request error. URL: /user/myinfo\nerror message: {e}")
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='로그인 에러', msg_content='로그인 처리중 에러가 발생했습니다.', data={})
#==================================================================================================
# 유저 정보 수정하기
#==================================================================================================
@router.post("/update/user/info")
async def update_user_info(request: Request, body: bytes = Depends(get_body), db: Session = Depends(get_db)):
try:
# 인증서 갱신
auth_token = cert_process.renew_cert(request=request)
if auth_token['result'] == 'OK':
auth_token = auth_token['data']
elif auth_token['result'] == 'TOKEN_EXPIRED':
raise token_expired_return_process(auth_token['msg'])
else:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='토큰 에러', msg_content='토큰 정보가 정확하지 않습니다.', data={})
# body에서 ID 추출
try:
body = json.loads(body)
except json.JSONDecodeError as e:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='데이터 에러', msg_content='데이터 처리 장애가 발생했습니다. 요청정보를 정확히 입력했는지 확인해주세요.', data={})
user_seq_result = cert_process.get_user_seq_by_token(token=auth_token['token'])
if user_seq_result["result"] == 'OK':
user_seq = user_seq_result['data']['user_seq']
body['user_seq'] = user_seq
# 현재 비밀번호 일치하는지 확인
current_pw_correct_result = await manage_user.check_current_user_pw(user_seq=user_seq, user_pw=body['user_pw'], db=db)
if current_pw_correct_result['result'] == 'FAIL':
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='현재 비밀번호 인증 실패', msg_content='현재 비밀번호가 일치하지 않습니다.', data={})
# 입력된 값 패턴 검증
new_data_pattern_result_msg = manage_user_pattern.check_new_data(user_info=body)
if new_data_pattern_result_msg is not None:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='회원정보 수정 실패', msg_content=new_data_pattern_result_msg, data={})
# 유저 정보 가져오기
user_data = await manage_user.get_my_info_by_user_seq(user_seq=user_seq, db=db)
if user_data['result'] == 'OK':
user_data = user_data['data']
else:
return response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='정보 가져오기 에러', msg_content='유저 정보 가져오기에 실패했습니다.', data={})
# 변경된 항목들 확인하기
# user_pw 변경 됐는지 확인
if body['user_pw_change_yn'] == 'Y':
body['user_pw_solt'] = generate_random_string(10)
# nickname 변경 됐는지 확인
body['nickname'] = body['nickname'] if body['nickname'] != user_data['nickname'] else user_data['nickname']
# user_email 변경 됐는지 확인
body['user_email'] = body['user_email'] if body['user_email'] != user_data['user_email'] else user_data['user_email']
# department 변경 됐는지 확인
body['department'] = body['department'] if body['department'] != user_data['department'] else user_data['department']
# profile_img 변경 됐는지 확인
body['profile_img'] = body['profile_img'] if body['profile_img'] != user_data['profile_img'] else user_data['profile_img']
# profile_img 변경 됐는지 확인
body['introduce_myself'] = body['introduce_myself'] if body['introduce_myself'] != user_data['introduce_myself'] else user_data['introduce_myself']
# 변경된 정보에 대해서만 업데이트하기
update_result = await manage_user.update_user_info(user_info=body, db=db)
if update_result['result'] == 'OK':
return await response.ok_res(auth_token=auth_token, data={}, db=db)
else:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='회원정보 변경 실패', msg_content='회원정보 변경 실패했습니다. 관리자에게 문의해주세요.', data={})
else:
logger.error(f"request error. URL: /user/update/user/info\nerror message: {e}")
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='토큰 에러', msg_content='토큰 정보가 정확하지 않습니다.', data={})
except Exception as e:
logger.error(f"request error. URL: /user/update/user/info\nerror message: {e}")
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='로그인 에러', msg_content='로그인 처리중 에러가 발생했습니다.', data={})
#==================================================================================================
# 회원탈퇴
#==================================================================================================
@router.post("/withdraw/user")
async def withdraw_user(request: Request, body: bytes = Depends(get_body), db: Session = Depends(get_db)):
try:
# 인증서 갱신
auth_token = cert_process.renew_cert(request=request)
if auth_token['result'] == 'OK':
auth_token = auth_token['data']
elif auth_token['result'] == 'TOKEN_EXPIRED':
raise token_expired_return_process(auth_token['msg'])
else:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='토큰 에러', msg_content='토큰 정보가 정확하지 않습니다.', data={})
# body에서 ID 추출
try:
body = json.loads(body)
except json.JSONDecodeError as e:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='데이터 에러', msg_content='데이터 처리 장애가 발생했습니다. 요청정보를 정확히 입력했는지 확인해주세요.', data={})
user_pw = body['user_pw']
user_seq_result = cert_process.get_user_seq_by_token(token=auth_token['token'])
if user_seq_result["result"] == 'OK':
user_seq = user_seq_result['data']['user_seq']
# 현재 비밀번호 일치하는지 확인
current_pw_correct_result = await manage_user.check_current_user_pw(user_seq=user_seq, user_pw=user_pw, db=db)
if current_pw_correct_result['result'] == 'OK':
# 회원 탈퇴 처리
withdraw_result = await manage_user.user_withdraw(user_seq=user_seq, db=db)
if withdraw_result['result'] == 'OK':
return await response.ok_res(auth_token=auth_token, data={}, db=db)
else:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='회원탈퇴 에러', msg_content='회원탈퇴 처리중 에러가 발생헀습니다. 관리자에게 문의해주세요.', data={})
else:
return await response.fail_res(auth_token=auth_token, auth_type='NOMAL', msg_title='현재 비밀번호 인증 실패', msg_content='현재 비밀번호가 일치하지 않습니다.', data={})
else:
logger.error(f"request error. URL: /user/withdraw/user\nerror message: {e}")
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='토큰 에러', msg_content='토큰 정보가 정확하지 않습니다.', data={})
except Exception as e:
logger.error(f"request error. URL: /user/withdraw/user\nerror message: {e}")
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='로그인 에러', msg_content='로그인 처리중 에러가 발생했습니다.', data={})
#==================================================================================================
# 필요한 함수
#==================================================================================================
# N자 대문자, 소문자, 숫자 조합 랜덤 발생기
def generate_random_string(length=10):
# 영문 대문자, 소문자, 숫자를 모두 포함한 문자 집합
characters = string.ascii_letters + string.digits
# 지정된 길이만큼 랜덤하게 선택하여 문자열 생성
random_string = ''.join(random.choice(characters) for _ in range(length))
return random_string
# N자 숫자 조합 랜덤 발생기
def generate_random_number(length=6):
characters = string.digits
# 지정된 길이만큼 랜덤하게 선택하여 문자열 생성
random_string = ''.join(random.choice(characters) for _ in range(length))
return random_string
def token_expired_return_process(fail_msg):
logger.error(f"request fail: {fail_msg}")
return HTTPException(
status_code=401,
detail=f"{fail_msg}"
)
# 현재 시간 STR로 가져오기
def get_now_time_str():
# 현재 시간 가져오기
now = datetime.datetime.now()
# YYYYMMDDHHMMSSsss 형식으로 포맷
formatted_time = now.strftime('%Y%m%d%H%M%S%f')[:17]
return formatted_time

View File

@ -0,0 +1,93 @@
from fastapi import APIRouter, Depends, HTTPException, Header, Body, status, Request
from sqlalchemy.orm import Session
from fastapi.security import APIKeyHeader
from typing import Union, Optional, List
from typing_extensions import Annotated
from db import models, schemas, crud
import json
import logging
import datetime
from kafka import KafkaProducer
from fastapi.responses import FileResponse, StreamingResponse
import io
import openpyxl
import time
from db.schemas import RawData, RtuGenerator
from db.base import get_db
from db.models import RawDatas, Raw_data_herit
import pandas as pd
from process.logger import logger
from process.certification import cert_process
from process.response import response
from process.room import room_score
KST = datetime.timezone(datetime.timedelta(hours=9))
router = APIRouter(
prefix="/room/score",
tags=["room", "score"],
responses={404: {"description": "Not found"}},
)
# -----------------------------------------------------------------------------------------------
async def get_body(request: Request):
return await request.body()
#==================================================================================================
# 방 생성하기
#==================================================================================================
@router.post("/create/room")
async def login(request: Request, body: bytes = Depends(get_body), db: Session = Depends(get_db)):
try:
# 인증서 갱신
auth_token = cert_process.renew_cert(request=request)
if auth_token['result'] == 'OK':
auth_token = auth_token['data']
elif auth_token['result'] == 'TOKEN_EXPIRED':
raise token_expired_return_process(auth_token['msg'])
else:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='토큰 에러', msg_content='토큰 정보가 정확하지 않습니다.', data={})
# body에서 ID 추출
try:
body = json.loads(body)
except json.JSONDecodeError as e:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='데이터 에러', msg_content='데이터 처리 장애가 발생했습니다. 요청정보를 정확히 입력했는지 확인해주세요.', data={})
user_seq_result = cert_process.get_user_seq_by_token(token=auth_token['token'])
if user_seq_result["result"] == 'OK':
user_seq = user_seq_result['data']['user_seq']
body['user_seq'] = user_seq
# 방 생성
create_room_result = await room_score.create_room(data=body)
if create_room_result['result'] == 'OK':
return await response.ok_res(auth_token=auth_token, data={}, db=db)
else:
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='방생성 에러', msg_content='방 생성중 에러가 발생했습니다. 관리자에게 문의해주세요.', data={})
else:
logger.error(f"request error. URL: /user/withdraw/user\nerror message: {e}")
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='토큰 에러', msg_content='토큰 정보가 정확하지 않습니다.', data={})
except Exception as e:
logger.error(f"request error. URL: /room/score/ \nerror message: {e}")
return await response.error_res(auth_token=auth_token, auth_type='NOMAL', msg_title='로그인 에러', msg_content='로그인 처리중 에러가 발생했습니다.', data={})
#==================================================================================================
# 필요한 함수
#==================================================================================================
# 401 에러 발생
def token_expired_return_process(fail_msg):
logger.error(f"request fail: {fail_msg}")
return HTTPException(
status_code=401,
detail=f"{fail_msg}"
)

View File

@ -0,0 +1,140 @@
from fastapi import APIRouter, Depends, HTTPException, Header, Body, status, Request
from fastapi.security import APIKeyHeader
from typing import Union, Optional, List
from typing_extensions import Annotated
import json
import logging
import datetime
from kafka import KafkaProducer
KST = datetime.timezone(datetime.timedelta(hours=9))
router = APIRouter(
prefix="/api",
tags=["api", "collect"],
responses={404: {"description": "Not found"}},
)
# -----------------------------------------------------------------------------------------------
# 비정상 요청 로그 기록
logger = logging.getLogger()
#2 logger의 level을 가장 낮은 수준인 DEBUG로 설정해둔다.
logger.setLevel(logging.ERROR)
#3 formatter 지정
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
#4 handler instance 생성
console = logging.StreamHandler()
file_handler = logging.FileHandler(filename="error_request.log")
#5 handler 별로 다른 level 설정
console.setLevel(logging.ERROR)
file_handler.setLevel(logging.ERROR)
#6 handler 출력 format 지정
console.setFormatter(formatter)
file_handler.setFormatter(formatter)
#7 logger에 handler 추가
logger.addHandler(console)
logger.addHandler(file_handler)
KAFKA_IP_DEV = "183.107.250.208:9092"
KAFKA_IP = "10.0.1.10:9092"
KAFKA_TOPIC = "boryeong-herit"
api_key_header = APIKeyHeader(name="X-HIT-TransactionId")
async def get_body(request: Request):
return await request.body()
async def get_header(header: str = Depends(api_key_header)):
return await header
# 헤더의 토큰 확인하는
async def api_token(token: str = Depends(api_key_header)):
if len(token) == 0 or token.count('.') != 3:
logger.error("The value of 'header[X-HIT-TransactionId]' is not determined by a specific rule.")
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
token = token.split('.')
if len(token) != 4 or token[0] !='T' or len(token[3]) != 20:
logger.error("The value of 'header[X-HIT-TransactionId]' is not determined by a specific rule.")
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
@router.post("/herit", dependencies=[Depends(api_token)])
async def col_raw_data_from_herit(
content_Type: Optional[str] = Header(None),
X_HIT_TransactionId: Optional[str] = Header(None),
content_Length: Optional[str] = Header(None),
body: bytes = Depends(get_body)
):
data = {
'content_Type': str(content_Type),
'X_HIT_TransactionId': str(X_HIT_TransactionId),
'content_Length': str(content_Length),
'body': str(body.decode("UTF-8"))
}
message_producer = MessageProducer(KAFKA_IP, KAFKA_TOPIC)
# message_producer_dev = MessageProducer(KAFKA_IP_DEV, KAFKA_TOPIC)
msg = {
"data_type": 'data_herit',
"data_herit": {
"content_type": data['content_Type'],
"x_hit_transactionid": data['X_HIT_TransactionId'],
"content_length": data['content_Length'],
"body": data['body']
}
}
res = message_producer.send_message(msg)
# res_dev = message_producer_dev.send_message(msg)
if res['status_code'] != status.HTTP_200_OK:
return {"reason": "SERVER ERROR"}
else :
return {"reason": "OK"}
# 테스트 URL
@router.post("/test")
async def col_raw_data_from_herit(
body: bytes = Depends(get_body)
):
return {
"reason": "OK2",
"body": body
}
# -----------------------------------------------------------------------------------------------
# Kafka로 전달하기위한 클래스
class MessageProducer:
broker = ""
topic = ""
producer = None
def __init__(self, broker, topic):
self.broker = broker
self.topic = topic
self.producer = KafkaProducer(bootstrap_servers=self.broker,
value_serializer=lambda x: json.dumps(x).encode('utf-8'),
acks=0,
api_version=(2,5,0),
retries=3
)
def send_message(self, msg):
try:
future = self.producer.send(self.topic, msg)
self.producer.flush() # 비우는 작업
future.get(timeout=60)
return {'status_code': 200, 'error': None}
except Exception as e:
logging.error(f"kafka process error: {e}")
return e

View File

@ -0,0 +1,132 @@
from fastapi import APIRouter, Depends, HTTPException, Header, Body, status, Request
from sqlalchemy.orm import Session
import json
import datetime
import requests
import jwt
import time
from process.logger import logger
from db.base import get_db
import pandas as pd
from common.config import SECRET_KEY
KST = datetime.timezone(datetime.timedelta(hours=9))
router = APIRouter(
prefix="/test",
tags=["test"],
responses={404: {"description": "Not found"}},
)
# -----------------------------------------------------------------------------------------------
async def get_body(request: Request):
return await request.body()
# user_seq로 토큰 생성기
@router.post("/make/token/{user_seq}")
async def make_new_token(user_seq: str, body: bytes = Depends(get_body), db: Session = Depends(get_db)):
try:
user_token = create_jwt(user_seq=int(user_seq), period=3600)
return {
"result": "OK",
"data": {
"token": user_token
}
}
except Exception as e:
return {
"result": "FAIL",
"msg": e
}
# user_seq로 만료된 토큰 생성기
@router.post("/make/token/expired/{user_seq}")
async def make_new_token_expired(user_seq: str, body: bytes = Depends(get_body), db: Session = Depends(get_db)):
try:
user_token = create_jwt(user_seq=int(user_seq), period=0)
return {
"result": "OK",
"data": {
"token": user_token
}
}
except Exception as e:
return {
"result": "FAIL",
"msg": e
}
# 토큰 정보 확인
@router.post("/check/token")
async def check_token_data(body: bytes = Depends(get_body), db: Session = Depends(get_db)):
try:
# body에서 ID 추출
try:
body = json.loads(body)
except json.JSONDecodeError as e:
return logger.error(f"json.loads error: Invalid JSON format in request body\nerror message: {e}")
token = body['token']
token_decode = decode_jwt(token=token)
return {
"result": "OK",
"data": {
"user_seq": token_decode['user_seq'],
"exp": change_time(token_decode['exp']),
"iat": change_time(token_decode['iat']),
}
}
except Exception as e:
return {
"result": "FAIL",
"msg": e
}
def create_jwt(user_seq: int, period: int):
# 현재 시간
now = int(time.time())
# JWT의 페이로드 (클레임)
payload = {
"user_seq": user_seq,
"exp": now + period, # 만료 시간
"iat": now, # 토큰 발행 시간
}
# JWT 생성 (HS256 알고리즘 사용)
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
return token
def decode_jwt(token: str):
try:
# 토큰 디코드
decoded_payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
return decoded_payload
except jwt.ExpiredSignatureError:
return {"error": "Token has expired"}
except jwt.InvalidTokenError:
return {"error": "Invalid token"}
# 현재 시간 YYYY-MM-DD hh:mm:ss로 변환 함수
def change_time(time_data):
dt_object = datetime.datetime.fromtimestamp(time_data)
formatted_time = dt_object.strftime('%Y-%m-%d %H:%M:%S')
return formatted_time

0
fastapi/app/server.crt Normal file
View File

0
fastapi/app/server.key Normal file
View File

View File

@ -0,0 +1,4 @@
#!/bin/bash
sudo docker build -t fastapi:v1 -f ./Dockerfile-fastapi .

View File

@ -0,0 +1,26 @@
version: "3.8"
services:
api:
image: fastapi:v1
environment:
- TZ=Asia/Seoul # 한국 시간대 설정
volumes:
- /home/ec2-user/eld/card_service/backend/fastapi/app:/app
command: >
uvicorn main:app --host 0.0.0.0 --port 8098
expose:
- "8098"
nginx:
image: nginx:latest
environment:
- TZ=Asia/Seoul # 한국 시간대 설정
ports:
- "8097:8097" # 외부에서 8097 포트로 접근
volumes:
- /home/ec2-user/eld/card_service/backend/fastapi/nginx.conf:/etc/nginx/nginx.conf
- /home/ec2-user/eld/card_service/backend/fastapi/eld.crt:/etc/nginx/ssl/eld.crt
- /home/ec2-user/eld/card_service/backend/fastapi/eld.key:/etc/nginx/ssl/eld.key
depends_on:
- api

35
fastapi/eld.crt Normal file
View File

@ -0,0 +1,35 @@
-----BEGIN CERTIFICATE-----
MIIGCDCCBPCgAwIBAgIQWwEQbfqkdYEA35goU9JMADANBgkqhkiG9w0BAQsFADBM
MQswCQYDVQQGEwJMVjENMAsGA1UEBxMEUmlnYTERMA8GA1UEChMIR29HZXRTU0wx
GzAZBgNVBAMTEkdvR2V0U1NMIFJTQSBEViBDQTAeFw0yNDA3MjMwMDAwMDBaFw0y
NTA3MjMyMzU5NTlaMBYxFDASBgNVBAMTC2VsZHNvZnQuY29tMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkYA7IYc330ya/BFBhcG8QQrdKuV5GPV5rh0K
NIeCU0yWl5ydYzpXhWuFE/qnQfVRK5g+1jAfBK4UHTZdpdvWzdDlZFkJBiQO0MS2
/ujWpTWSEYqalgRxZylTDSOG+KAt+Tpo0ZWv/T6okjW54+J2vi4E/2QGPzi/Dr5c
d9hlMBB3VREsmDqDScVnHOlxbMVLf/EgIEP5CbGwdV71/R5Tpcdkr9ubw5s1RdMz
GEYfQEJM56zdlAwhG7ucadoIVXRlP6BWmyrk9uZgcDg1waUoFekyCGg/gwhdzoH5
xglDwb1tL/b+jxhgm8Cu0Av5GwaD45LSc1B1+AHM99qnoETuKQIDAQABo4IDGjCC
AxYwHwYDVR0jBBgwFoAU+ftQxItnu2dk/oMhpqnOP1WEk5kwHQYDVR0OBBYEFLxH
VZ+saqBmJr8jEwI3UhYLh4DLMA4GA1UdDwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAA
MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBLBgNVHSAERDBCMDYGCysG
AQQBsjEBAgJAMCcwJQYIKwYBBQUHAgEWGWh0dHBzOi8vY3BzLnVzZXJ0cnVzdC5j
b20wCAYGZ4EMAQIBMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly9jcmwudXNlcnRy
dXN0LmNvbS9Hb0dldFNTTFJTQURWQ0EuY3JsMG8GCCsGAQUFBwEBBGMwYTA4Bggr
BgEFBQcwAoYsaHR0cDovL2NydC51c2VydHJ1c3QuY29tL0dvR2V0U1NMUlNBRFZD
QS5jcnQwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wFgYD
VR0RBA8wDYILZWxkc29mdC5jb20wggGABgorBgEEAdZ5AgQCBIIBcASCAWwBagB2
AN3cyjSV1+EWBeeVMvrHn/g9HFDf2wA6FBJ2Ciysu8gqAAABkN5FaiIAAAQDAEcw
RQIgHTbwtmQN6xLMETdF+8eqQ7AKqXO8N2f+bD7Fx7tzlo4CIQDc3kmta7RofW+L
jZxQtI4p3b4hFUFCDw85ibQgCSpH6QB3AA3h8jAr0w3BQGISCepVLvxHdHyx1+kw
7w5CHrR+Tqo0AAABkN5Fae8AAAQDAEgwRgIhAI2ZgaTuuAjcfrg6o2KeX+O8ga8e
tGY7+NZRt/yRXuyXAiEA5bEkR2LchqMV3vYr5Eo/7nKm8eTXNBhduxNIPxNzRoAA
dwAS8U40vVNyTIQGGcOPP3oT+Oe1YoeInG0wBYTr5YYmOgAAAZDeRWnNAAAEAwBI
MEYCIQDT4+jvqlZboMs6AeqxZRu0TyKB/zA5UzGfJdaCaWQ8iAIhAKgg7Q+W0sd/
Gxaa2pHXpHSxP3sfkT9o5Br8S4pILca3MA0GCSqGSIb3DQEBCwUAA4IBAQA51hYf
mw+kueMcWmPUlUikrK0/KuLpSfWPvHOj+r84Y5AS9JxZmi9b+9W18p1VwO0YXR6U
ho4PyHjuIviT2LviAWyvRTFc9Il+e920+RyMiAdfQ/Af15xdcAJOjSMlZu3xkqvW
gkgE1kPvIQevqCmEEzUqsUVEn7ftoDcT9SEJBpLFwB6FAFBfw0pvJ0qUr9LAin4p
bklWGaY2laI2py7MXZNUO35rahA0DNS/Y2sY3TbH/WXilJ/sbJ9sO83/XC2T7vgC
Le2cv9gNFJPiTW++OROxybXzarwIwWFHO7aPc5Ehv9U54/XlzBIzrRsJuvtXkc0I
0nuiAfpjOom89Z1W
-----END CERTIFICATE-----

17
fastapi/eld.csr Normal file
View File

@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICrzCCAZcCAQAwbDEUMBIGA1UEAwwLZWxkc29mdC5jb20xHzAdBgNVBAoMFuyj
vOyLne2ajOyCrCDsnbTsl5jrlJQxEjAQBgNVBAgMCeuMgOyghOyLnDESMBAGA1UE
BwwJ7Jyg7ISx6rWsMQswCQYDVQQGEwJLUjCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAJGAOyGHN99MmvwRQYXBvEEK3SrleRj1ea4dCjSHglNMlpecnWM6
V4VrhRP6p0H1USuYPtYwHwSuFB02XaXb1s3Q5WRZCQYkDtDEtv7o1qU1khGKmpYE
cWcpUw0jhvigLfk6aNGVr/0+qJI1uePidr4uBP9kBj84vw6+XHfYZTAQd1URLJg6
g0nFZxzpcWzFS3/xICBD+QmxsHVe9f0eU6XHZK/bm8ObNUXTMxhGH0BCTOes3ZQM
IRu7nGnaCFV0ZT+gVpsq5PbmYHA4NcGlKBXpMghoP4MIXc6B+cYJQ8G9bS/2/o8Y
YJvArtAL+RsGg+OS0nNQdfgBzPfap6BE7ikCAwEAATANBgkqhkiG9w0BAQsFAAOC
AQEAZhOyuc//t8Vp+Iog/C12fFSIB4baHBS/GV3C/xm1gvmn/Vl8ynaswPA3ei0U
rvoXidZHYt7gdcBq1DlPDGxbnEwNYZhNFR0Sv622EO9MrsiUsje6yWm2xmCgM8zw
ChhifmEulSBEFcOQGi9eKA19jJHKEqRx5QhlZHsRdVD+glxyESpOM0cfUXGsYXhn
Pvm9EvlZfYyoVBRjIwO3CIKSaGsgg2FKUKnmgMo28BKVPYM1/gNC47ozhwMOgDrO
fg1KZ3CtOMn3ZG7OiN2HamnBNKQDDp6mZJT1aoq1GsENYFNJUfW768G2rmAUGNfw
My3PBjidoiuChvimG1Fqpnioxw==
-----END CERTIFICATE REQUEST-----

27
fastapi/eld.key Normal file
View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAkYA7IYc330ya/BFBhcG8QQrdKuV5GPV5rh0KNIeCU0yWl5yd
YzpXhWuFE/qnQfVRK5g+1jAfBK4UHTZdpdvWzdDlZFkJBiQO0MS2/ujWpTWSEYqa
lgRxZylTDSOG+KAt+Tpo0ZWv/T6okjW54+J2vi4E/2QGPzi/Dr5cd9hlMBB3VREs
mDqDScVnHOlxbMVLf/EgIEP5CbGwdV71/R5Tpcdkr9ubw5s1RdMzGEYfQEJM56zd
lAwhG7ucadoIVXRlP6BWmyrk9uZgcDg1waUoFekyCGg/gwhdzoH5xglDwb1tL/b+
jxhgm8Cu0Av5GwaD45LSc1B1+AHM99qnoETuKQIDAQABAoIBAAhxYU0CYoe4s2AX
1L5hFe5MxfVqpCaifOVxbjGK4PE6Nx1UU04qGSDG8s2MXIb/aA7AanoFiBE+lDB3
QoMgsPPXsK3sXDGQ51KuLYO4aVckFwYhTbPRjW6L53OyWW9FJTHKdcFenxwR9hho
2XDrp9IEi9nxeQrTXUPK4ET8h6+cmHgEDEXJ18xyn3EMrXbBTOBaAGJwdA/kE947
o85Yu+o3sKA+3JsuuMijSy2XSqvslNNeyOvlcFcq88eDY5pXuDGbRQPnIQe3t0Fc
DMtIF2E2jOtIbMuBdcdZaUduiPqVRT3ojW2LaDc58H7+piAgBUVGJCLuqIgujBa0
t3n9M3ECgYEA1oe+oVG8+OVvcg9IJyQmnZOIR3hopuDvZOCYuSOsOS8DMo+p066z
6rSKpvtIcJJ8l0mM8Rw4WifEbEYOgkt9yGVK80mMI2BkSwmq5U4hOWg/xxqATTz5
PBgz0bBwZ6ZgW/3RD+jWybX3vy+beifAL3v52q+hnZdZf1q1rBYYWXECgYEAraB/
C/a4RO0LygrncUgn9MX4mGe2zRVAszZcbcX2iG4o1mseoZ16HO1ipS4Q/kI/znpY
r7XT4QgF98Fdkcl8MYKP+snUv9TG7zOFiYOe0JeaafBuukMDPUqeQXk/d9rSrJg9
lqiw1LYYyxPnh8aTEfwMBPqaRhLiMeD63PsHRDkCgYEAzwflUi1dnx1b9ckFqrBa
i8tqwv5SkGmW3dVZzaG9fNn/zfWSwPRiMOjWvdrWx7y2fBHA8JZ5U5f5GTxqmBde
ZdxK/opFsYY+g6PqxqwlqA8RLYZHt0JWjEYXDA+oCn8nkt9ZuG7NiZAQbPL2qmZe
M/UC5KaF413CQwM5O79+9CECgYBT/D+YNOabiKJcP/wGAuY48441gm2dNDuQtKnu
+4QuKEMevMAbYwZPedBuoCLeKoOcx/egPu7Xej8QwfsV6wVlGYe1wu1jQXRc/moI
w58NvVeXCRM2i/XELxTwDMtTmYiwrg+UkdK/gbnqeZ1UQwye9XGG8wWvAbFieTY/
sDmqmQKBgQCUyu98Gpatoe8MdyHZf9h+QfYcUkpLuhJOvWCGQt9QyfiIal5eleXG
YPNaQBk3GtSPyuB1nDrjtHaIlyw1ScjRCfzth3VJvwWnqz9y7XP2pow2+TnY0Jco
lvMRY9n55TgAPdTdgHshUyDRAM1/aUbiBIPPOP/XAcmONZ1lpayZ7A==
-----END RSA PRIVATE KEY-----

22
fastapi/nginx.conf Normal file
View File

@ -0,0 +1,22 @@
events {
worker_connections 2048;
}
http {
server {
listen 8097 ssl;
server_name eldsoft.com;
ssl_certificate /etc/nginx/ssl/eld.crt;
ssl_certificate_key /etc/nginx/ssl/eld.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://api:8098;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}

10
fastapi/restart.sh Normal file
View File

@ -0,0 +1,10 @@
#!/bin/bash
docker-compose -f /home/ec2-user/eld/card_service/backend/fastapi/docker-compose-fastapi.yml down
docker-compose -f /home/ec2-user/eld/card_service/backend/fastapi/docker-compose-fastapi.yml up -d
sleep 1
docker-compose -f /home/ec2-user/eld/card_service/backend/fastapi/docker-compose-fastapi.yml ps

44
kafka/Dockerfile-kafka Normal file
View File

@ -0,0 +1,44 @@
FROM azul/zulu-openjdk-alpine:8u292-8.54.0.21
ARG kafka_version=2.7.1
ARG scala_version=2.13
ARG glibc_version=2.31-r0
ARG vcs_ref=unspecified
ARG build_date=unspecified
LABEL org.label-schema.name="kafka" \
org.label-schema.description="Apache Kafka" \
org.label-schema.build-date="${build_date}" \
org.label-schema.vcs-url="https://github.com/wurstmeister/kafka-docker" \
org.label-schema.vcs-ref="${vcs_ref}" \
org.label-schema.version="${scala_version}_${kafka_version}" \
org.label-schema.schema-version="1.0" \
maintainer="wurstmeister"
ENV KAFKA_VERSION=$kafka_version \
SCALA_VERSION=$scala_version \
KAFKA_HOME=/opt/kafka \
GLIBC_VERSION=$glibc_version
ENV PATH=${PATH}:${KAFKA_HOME}/bin
COPY ./kafka/download-kafka.sh ./kafka/start-kafka.sh ./kafka/broker-list.sh ./kafka/create-topics.sh ./kafka/versions.sh /tmp/
RUN apk add --no-cache bash curl jq docker \
&& chmod a+x /tmp/*.sh \
&& mv /tmp/start-kafka.sh /tmp/broker-list.sh /tmp/create-topics.sh /tmp/versions.sh /usr/bin \
&& sync && /tmp/download-kafka.sh \
&& tar xfz /tmp/kafka_${SCALA_VERSION}-${KAFKA_VERSION}.tgz -C /opt \
&& rm /tmp/kafka_${SCALA_VERSION}-${KAFKA_VERSION}.tgz \
&& ln -s /opt/kafka_${SCALA_VERSION}-${KAFKA_VERSION} ${KAFKA_HOME} \
&& rm /tmp/* \
&& wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VERSION}/glibc-${GLIBC_VERSION}.apk \
&& apk add --no-cache --allow-untrusted glibc-${GLIBC_VERSION}.apk \
&& rm glibc-${GLIBC_VERSION}.apk
COPY ./kafka/overrides /opt/overrides
VOLUME ["/kafka"]
# Use "exec" form so that it runs as PID 1 (useful for graceful shutdown)
CMD ["start-kafka.sh"]

View File

@ -0,0 +1,18 @@
version: '2'
services:
zookeeper:
image: wurstmeister/zookeeper
ports:
- "2181:2181"
kafka:
build:
context: .
dockerfile: ./Dockerfile-kafka
ports:
- "9092:9092"
environment:
KAFKA_ADVERTISED_HOST_NAME: 10.0.1.20
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_CREATE_TOPICS: "cheonan-kt:1:1,cheonan-lg:1:1"
volumes:
- /var/run/docker_kafka.sock:/var/run/docker.sock

View File

@ -0,0 +1,5 @@
#!/bin/bash
CONTAINERS=$(docker ps | grep 9092 | awk '{print $1}')
BROKERS=$(for CONTAINER in ${CONTAINERS}; do docker port "$CONTAINER" 9092 | sed -e "s/0.0.0.0:/$HOST_IP:/g"; done)
echo "${BROKERS//$'\n'/,}"

View File

@ -0,0 +1,57 @@
#!/bin/bash
if [[ -z "$KAFKA_CREATE_TOPICS" ]]; then
exit 0
fi
if [[ -z "$START_TIMEOUT" ]]; then
START_TIMEOUT=600
fi
start_timeout_exceeded=false
count=0
step=10
while netstat -lnt | awk '$4 ~ /:'"$KAFKA_PORT"'$/ {exit 1}'; do
echo "waiting for kafka to be ready"
sleep $step;
count=$((count + step))
if [ $count -gt $START_TIMEOUT ]; then
start_timeout_exceeded=true
break
fi
done
if $start_timeout_exceeded; then
echo "Not able to auto-create topic (waited for $START_TIMEOUT sec)"
exit 1
fi
# introduced in 0.10. In earlier versions, this will fail because the topic already exists.
# shellcheck disable=SC1091
source "/usr/bin/versions.sh"
if [[ "$MAJOR_VERSION" == "0" && "$MINOR_VERSION" -gt "9" ]] || [[ "$MAJOR_VERSION" -gt "0" ]]; then
KAFKA_0_10_OPTS="--if-not-exists"
fi
# Expected format:
# name:partitions:replicas:cleanup.policy
IFS="${KAFKA_CREATE_TOPICS_SEPARATOR-,}"; for topicToCreate in $KAFKA_CREATE_TOPICS; do
echo "creating topics: $topicToCreate"
IFS=':' read -r -a topicConfig <<< "$topicToCreate"
config=
if [ -n "${topicConfig[3]}" ]; then
config="--config=cleanup.policy=${topicConfig[3]}"
fi
COMMAND="JMX_PORT='' ${KAFKA_HOME}/bin/kafka-topics.sh \\
--create \\
--zookeeper ${KAFKA_ZOOKEEPER_CONNECT} \\
--topic ${topicConfig[0]} \\
--partitions ${topicConfig[1]} \\
--replication-factor ${topicConfig[2]} \\
${config} \\
${KAFKA_0_10_OPTS} &"
eval "${COMMAND}"
done
wait

View File

@ -0,0 +1,18 @@
#!/bin/sh -e
# shellcheck disable=SC1091
source "/usr/bin/versions.sh"
FILENAME="kafka_${SCALA_VERSION}-${KAFKA_VERSION}.tgz"
url=$(curl --stderr /dev/null "https://www.apache.org/dyn/closer.cgi?path=/kafka/${KAFKA_VERSION}/${FILENAME}&as_json=1" | jq -r '"\(.preferred)\(.path_info)"')
# Test to see if the suggested mirror has this version, currently pre 2.1.1 versions
# do not appear to be actively mirrored. This may also be useful if closer.cgi is down.
if [[ ! $(curl -f -s -r 0-1 "${url}") ]]; then
echo "Mirror does not have desired version, downloading direct from Apache"
url="https://archive.apache.org/dist/kafka/${KAFKA_VERSION}/${FILENAME}"
fi
echo "Downloading Kafka from $url"
wget "${url}" -O "/tmp/${FILENAME}"

View File

@ -0,0 +1,6 @@
#!/bin/bash -e
# Kafka 0.9.x.x has a 'listeners' config by default. We need to remove this
# as the user may be configuring via the host.name / advertised.host.name properties
echo "Removing 'listeners' from server.properties pre-bootstrap"
sed -i -e '/^listeners=/d' "$KAFKA_HOME/config/server.properties"

149
kafka/kafka/start-kafka.sh Normal file
View File

@ -0,0 +1,149 @@
#!/bin/bash -e
# Allow specific kafka versions to perform any unique bootstrap operations
OVERRIDE_FILE="/opt/overrides/${KAFKA_VERSION}.sh"
if [[ -x "$OVERRIDE_FILE" ]]; then
echo "Executing override file $OVERRIDE_FILE"
eval "$OVERRIDE_FILE"
fi
# Store original IFS config, so we can restore it at various stages
ORIG_IFS=$IFS
if [[ -z "$KAFKA_ZOOKEEPER_CONNECT" ]]; then
echo "ERROR: missing mandatory config: KAFKA_ZOOKEEPER_CONNECT"
exit 1
fi
if [[ -z "$KAFKA_PORT" ]]; then
export KAFKA_PORT=9092
fi
create-topics.sh &
unset KAFKA_CREATE_TOPICS
if [[ -z "$KAFKA_ADVERTISED_PORT" && \
-z "$KAFKA_LISTENERS" && \
-z "$KAFKA_ADVERTISED_LISTENERS" && \
-S /var/run/docker.sock ]]; then
KAFKA_ADVERTISED_PORT=$(docker port "$(hostname)" $KAFKA_PORT | sed -r 's/.*:(.*)/\1/g' | head -n1)
export KAFKA_ADVERTISED_PORT
fi
if [[ -z "$KAFKA_BROKER_ID" ]]; then
if [[ -n "$BROKER_ID_COMMAND" ]]; then
KAFKA_BROKER_ID=$(eval "$BROKER_ID_COMMAND")
export KAFKA_BROKER_ID
else
# By default auto allocate broker ID
export KAFKA_BROKER_ID=-1
fi
fi
if [[ -z "$KAFKA_LOG_DIRS" ]]; then
export KAFKA_LOG_DIRS="/kafka/kafka-logs-$HOSTNAME"
fi
if [[ -n "$KAFKA_HEAP_OPTS" ]]; then
sed -r -i 's/(export KAFKA_HEAP_OPTS)="(.*)"/\1="'"$KAFKA_HEAP_OPTS"'"/g' "$KAFKA_HOME/bin/kafka-server-start.sh"
unset KAFKA_HEAP_OPTS
fi
if [[ -n "$HOSTNAME_COMMAND" ]]; then
HOSTNAME_VALUE=$(eval "$HOSTNAME_COMMAND")
# Replace any occurrences of _{HOSTNAME_COMMAND} with the value
IFS=$'\n'
for VAR in $(env); do
if [[ $VAR =~ ^KAFKA_ && "$VAR" =~ "_{HOSTNAME_COMMAND}" ]]; then
eval "export ${VAR//_\{HOSTNAME_COMMAND\}/$HOSTNAME_VALUE}"
fi
done
IFS=$ORIG_IFS
fi
if [[ -n "$PORT_COMMAND" ]]; then
PORT_VALUE=$(eval "$PORT_COMMAND")
# Replace any occurrences of _{PORT_COMMAND} with the value
IFS=$'\n'
for VAR in $(env); do
if [[ $VAR =~ ^KAFKA_ && "$VAR" =~ "_{PORT_COMMAND}" ]]; then
eval "export ${VAR//_\{PORT_COMMAND\}/$PORT_VALUE}"
fi
done
IFS=$ORIG_IFS
fi
if [[ -n "$RACK_COMMAND" && -z "$KAFKA_BROKER_RACK" ]]; then
KAFKA_BROKER_RACK=$(eval "$RACK_COMMAND")
export KAFKA_BROKER_RACK
fi
# Try and configure minimal settings or exit with error if there isn't enough information
if [[ -z "$KAFKA_ADVERTISED_HOST_NAME$KAFKA_LISTENERS" ]]; then
if [[ -n "$KAFKA_ADVERTISED_LISTENERS" ]]; then
echo "ERROR: Missing environment variable KAFKA_LISTENERS. Must be specified when using KAFKA_ADVERTISED_LISTENERS"
exit 1
elif [[ -z "$HOSTNAME_VALUE" ]]; then
echo "ERROR: No listener or advertised hostname configuration provided in environment."
echo " Please define KAFKA_LISTENERS / (deprecated) KAFKA_ADVERTISED_HOST_NAME"
exit 1
fi
# Maintain existing behaviour
# If HOSTNAME_COMMAND is provided, set that to the advertised.host.name value if listeners are not defined.
export KAFKA_ADVERTISED_HOST_NAME="$HOSTNAME_VALUE"
fi
#Issue newline to config file in case there is not one already
echo "" >> "$KAFKA_HOME/config/server.properties"
(
function updateConfig() {
key=$1
value=$2
file=$3
# Omit $value here, in case there is sensitive information
echo "[Configuring] '$key' in '$file'"
# If config exists in file, replace it. Otherwise, append to file.
if grep -E -q "^#?$key=" "$file"; then
sed -r -i "s@^#?$key=.*@$key=$value@g" "$file" #note that no config values may contain an '@' char
else
echo "$key=$value" >> "$file"
fi
}
# Fixes #312
# KAFKA_VERSION + KAFKA_HOME + grep -rohe KAFKA[A-Z0-0_]* /opt/kafka/bin | sort | uniq | tr '\n' '|'
EXCLUSIONS="|KAFKA_VERSION|KAFKA_HOME|KAFKA_DEBUG|KAFKA_GC_LOG_OPTS|KAFKA_HEAP_OPTS|KAFKA_JMX_OPTS|KAFKA_JVM_PERFORMANCE_OPTS|KAFKA_LOG|KAFKA_OPTS|"
# Read in env as a new-line separated array. This handles the case of env variables have spaces and/or carriage returns. See #313
IFS=$'\n'
for VAR in $(env)
do
env_var=$(echo "$VAR" | cut -d= -f1)
if [[ "$EXCLUSIONS" = *"|$env_var|"* ]]; then
echo "Excluding $env_var from broker config"
continue
fi
if [[ $env_var =~ ^KAFKA_ ]]; then
kafka_name=$(echo "$env_var" | cut -d_ -f2- | tr '[:upper:]' '[:lower:]' | tr _ .)
updateConfig "$kafka_name" "${!env_var}" "$KAFKA_HOME/config/server.properties"
fi
if [[ $env_var =~ ^LOG4J_ ]]; then
log4j_name=$(echo "$env_var" | tr '[:upper:]' '[:lower:]' | tr _ .)
updateConfig "$log4j_name" "${!env_var}" "$KAFKA_HOME/config/log4j.properties"
fi
done
)
if [[ -n "$CUSTOM_INIT_SCRIPT" ]] ; then
eval "$CUSTOM_INIT_SCRIPT"
fi
exec "$KAFKA_HOME/bin/kafka-server-start.sh" "$KAFKA_HOME/config/server.properties"

7
kafka/kafka/versions.sh Normal file
View File

@ -0,0 +1,7 @@
#!/bin/bash -e
MAJOR_VERSION=$(echo "$KAFKA_VERSION" | cut -d. -f1)
export MAJOR_VERSION
MINOR_VERSION=$(echo "$KAFKA_VERSION" | cut -d. -f2)
export MINOR_VERSION

12
kafka/restart-kafka.sh Normal file
View File

@ -0,0 +1,12 @@
#!/bin/bash
docker-compose -f /data/cheonan_ep/docker/col_server/kafka/docker-compose-kafka.yml down
docker-compose -f /data/cheonan_ep/docker/col_server/kafka/docker-compose-kafka.yml up -d
sleep 1
docker-compose -f /data/cheonan_ep/docker/col_server/kafka/docker-compose-kafka.yml ps

View File

@ -0,0 +1,10 @@
FROM docker.elastic.co/logstash/logstash-oss:7.15.2
ENV TZ=Asia/Seoul
COPY lib_logstash/postgresql-42.2.24.jar /usr/share/logstash/postgresql-42.2.24.jar
RUN rm -f /usr/share/logstash/pipeline/logstash_kt.conf && \
rm -f /usr/share/logstash/pipeline/logstash_lg.conf && \
bin/logstash-plugin install logstash-output-jdbc

View File

@ -0,0 +1,49 @@
input {
kafka {
bootstrap_servers => "10.0.1.10:9092"
group_id => "cheonan"
topics => ["cheonan-kt"]
codec => json {
charset=>"UTF-8"
}
}
}
# filter {
# mutate {
# split => {"message" => " "}
# }
# }
output {
# stdout {
# codec => rubydebug
# }
# stdout { codec => line { format => 'tttttttttttttttttttttttt' } }
if [data_type] == "cheonan_kt" {
jdbc {
connection_string => "jdbc:postgresql://pg-219s5.vpc-cdb-kr.gov-ntruss.com:5432/cheonan_ep?user=cheonan&password=cjsdkstl2023!#"
driver_jar_path => "/usr/share/logstash/postgresql-42.2.24.jar"
max_pool_size => 2
connection_timeout => 25000
statement => [
"INSERT INTO energy.raw_data_kt (
tm,
request_type,
client_host,
body
) VALUES (
now(),
?,
?,
?
)",
"[cheonan_kt][request_type]",
"[cheonan_kt][client_host]",
"[cheonan_kt][body]"
]
}
}
}

View File

@ -0,0 +1,49 @@
input {
kafka {
bootstrap_servers => "10.0.1.10:9092"
group_id => "cheonan"
topics => ["cheonan-lg"]
codec => json {
charset=>"UTF-8"
}
}
}
# filter {
# mutate {
# split => {"message" => " "}
# }
# }
output {
# stdout {
# codec => rubydebug
# }
# stdout { codec => line { format => 'tttttttttttttttttttttttt' } }
if [data_type] == "cheonan_lg" {
jdbc {
connection_string => "jdbc:postgresql://pg-219s5.vpc-cdb-kr.gov-ntruss.com:5432/cheonan_ep?user=cheonan&password=cjsdkstl2023!#"
driver_jar_path => "/usr/share/logstash/postgresql-42.2.24.jar"
max_pool_size => 2
connection_timeout => 25000
statement => [
"INSERT INTO energy.raw_data_lg (
tm,
request_type,
client_host,
body
) VALUES (
now(),
?,
?,
?
)",
"[cheonan_lg][request_type]",
"[cheonan_lg][client_host]",
"[cheonan_lg][body]"
]
}
}
}

View File

@ -0,0 +1,15 @@
version: '3'
services:
logstash-herit:
container_name: logstash
build:
context: .
dockerfile: ./Dockerfile-logstash
image: logstash:latest
volumes:
- /data/cheonan_ep/docker/col_server/logstash/conf/logstash_iot:/usr/share/logstash/pipeline
logging:
driver: "json-file"
options:
max-file: "5"
max-size: "100m"

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,12 @@
#!/bin/bash
docker-compose -f /data/cheonan_ep/docker/col_server/logstash/docker-compose-logstash.yml down
docker-compose -f /data/cheonan_ep/docker/col_server/logstash/docker-compose-logstash.yml up -d
sleep 1
docker-compose -f /data/cheonan_ep/docker/col_server/logstash/docker-compose-logstash.yml ps

View File

@ -0,0 +1,234 @@
import logging
import sys
from logging.handlers import TimedRotatingFileHandler
import os
import stat
import shutil
import datetime
import psycopg2 # PostgreSQL 데이터베이스 연결 라이브러리
import re
# Custom handler class definition
class CustomTimedRotatingFileHandler(TimedRotatingFileHandler):
def __init__(self, base_log_path, when="midnight", interval=1, backupCount=7):
self.base_log_path = base_log_path
# Initialize log file path explicitly during instantiation
self.update_log_path()
super().__init__(self.baseFilename, when=when, interval=interval, backupCount=backupCount)
def update_log_path(self):
current_time = datetime.datetime.now()
log_directory = os.path.join(
self.base_log_path,
current_time.strftime("%Y"),
current_time.strftime("%m")
)
log_filename = current_time.strftime("%d_allscore.log")
# Set log file path
self.baseFilename = os.path.join(log_directory, log_filename)
# Create directory if not exists
if not os.path.exists(log_directory):
os.makedirs(log_directory)
def emit(self, record):
# Update the log path at each log entry point
self.update_log_path()
super().emit(record)
# Logger configuration
logger = logging.getLogger("allscore_logger")
logger.setLevel(logging.DEBUG)
# Stream handler for stdout
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(logging.DEBUG)
# Log format configuration
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
stream_handler.setFormatter(formatter)
# Custom file handler configuration
base_log_path = "/home/ec2-user/eld/card_service/backend/organize_image_files/logs"
file_handler = CustomTimedRotatingFileHandler(base_log_path=base_log_path, when="midnight", interval=1, backupCount=7)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
# Add handlers to the logger
logger.addHandler(stream_handler)
logger.addHandler(file_handler)
# 파일명을 갖고 뉴스룸 SEQ찾기
def get_user_seq(file_name):
try:
# PostgreSQL connection configuration
conn = psycopg2.connect(
dbname='allscore',
user='eldsoft',
password='eld240510',
host='3.34.123.25', # e.g., 'localhost' or IP address
port='8088' # e.g., '5432'
)
cur = conn.cursor()
# Execute SQL query
query = f"""
select user_seq
from manage_user
where profile_img like '%{file_name}%'
"""
cur.execute(query)
result = cur.fetchall()
# Close the cursor and the connection
cur.close()
conn.close()
return result
except Exception as e:
logger.error(f"get_user_seq(파일명을 갖고 user_seq 찾기) error: {e}")
# 서버 임시 디렉토리에서 하루전 파일들 목록 가져오기
def get_file_name():
try:
directory_path = f"/home/ec2-user/eld/card_service/backend/fastapi/app/images/user/temp_dir/profile_img/"
file_list = os.listdir(directory_path)
file_list = [f for f in file_list if os.path.isfile(os.path.join(directory_path, f))]
target_file_list = []
for file_name in file_list:
if extract_datetime_from_filename(file_name):
target_file_list.append(file_name)
return file_list
except Exception as e:
logger.error(f"임시 이미지 디렉토리에서 이미지 파일 가져오기 실패\nerror msg: {e}")
def extract_datetime_from_filename(filename):
# 정규 표현식으로 파일명에서 날짜 부분 추출 (확장자는 .jpg, .jpeg, .png, .bmp만 허용)
match = re.search(r'IMG_\d+_(\d+)\.(jpg|jpeg|png|bmp)', filename, re.IGNORECASE)
target_dt = match.group(1) # 추출된 날짜 부분 반환
reference_date = get_yesterday_time_str()
if target_dt < reference_date:
return True
else:
return False
# 전날 00시 이전 파일명 갖고 오기위한 시간 str얻는 함수
def get_yesterday_time_str():
# 어제 시간 가져오기
now = datetime.datetime.now() - datetime.timedelta(days=1)
# YYYYMMDDHHMMSSsss 형식으로 포맷
formatted_time = now.strftime('%Y%m%d%H%M%S%f')[:17]
return formatted_time[:8] + '000000000'
# 파일 이동
def move_file(user_seq, file_name):
try:
source_path = f"/home/ec2-user/eld/card_service/backend/fastapi/app/images/user/temp_dir/profile_img/{file_name}"
destination_dir = f"/home/ec2-user/eld/card_service/backend/fastapi/app/images/user/{user_seq}/profile_img/"
# 대상 디렉토리가 존재하지 않으면 생성
if not os.path.exists(destination_dir):
os.makedirs(destination_dir, mode=0o775)
# 파일 쓰기 권한 부여
os.chmod(source_path, stat.S_IWUSR)
# 파일 이동
shutil.move(source_path, destination_dir)
logger.debug(f"파일이 성공적으로 {destination_dir}로 이동되었습니다.")
except Exception as e:
logger.error(f"파일 이동 중 오류가 발생했습니다: {e}")
# 파일 삭제
def delete_file(file_name):
try:
file_path = f"/home/ec2-user/eld/card_service/backend/fastapi/app/images/user/temp_dir/profile_img/{file_name}"
# 파일이 존재하는지 확인 후 삭제
if os.path.exists(file_path):
# 파일 쓰기 권한 부여
os.chmod(file_path, stat.S_IWUSR)
# 파일 삭제
os.remove(file_path)
logger.debug(f"{file_path} 파일이 성공적으로 삭제되었습니다.")
else:
logger.debug(f"{file_path} 파일이 존재하지 않습니다.")
except Exception as e:
logger.debug(f"파일 삭제 중 오류가 발생했습니다: {e}")
# 이동된 파일명 맞게 DB 수정하기
def update_new_dir(user_seq, file_name):
try:
# PostgreSQL connection configuration
conn = psycopg2.connect(
dbname='allscore',
user='eldsoft',
password='eld240510',
host='3.34.123.25', # e.g., 'localhost' or IP address
port='8088' # e.g., '5432'
)
cur = conn.cursor()
logger.info("Connected to the database successfully.")
source_path = f"/user/temp_dir/profile_img/{file_name}"
destination_dir = f"/user/{user_seq}/profile_img/{file_name}"
# Execute SQL query
query = f"""
update manage_user
set
profile_img = '{destination_dir}'
where user_seq = {user_seq}
"""
logger.debug(f"query: {query}")
cur.execute(query)
# Commit the transaction
conn.commit()
# Close the cursor and the connection
cur.close()
conn.close()
except Exception as e:
logger.error(f"update_new_dir(이동된 파일명 맞게 DB 수정하기) error: {e}")
# 회원 프로필이미지 임시 이미지 파일 정리 수행
def manage_profile_img():
# 임시 디렉토리에서 어제보다 전에 만들어진 파일명을 다 가져오기
target_file_name = get_file_name()
logger.info(f"대상 파일 개수: {len(target_file_name)}")
# 각 파일명별로 for문 수행
for file_name in target_file_name:
# 해당 파일명이 포함된 게시글의 seq찾기
user_seq = get_user_seq(file_name)
user_seq = '0' if len(user_seq) == 0 else user_seq[0][0]
logger.info(f"seq: {user_seq}")
# 해당 seq에 맞게 파일을 이동시키기, seq가 0인건(DB조회 되지 않은건 0으로 처리했기에) 삭제
if user_seq == '0':
# seq가 0인건(DB조회 되지 않은건 0으로 처리했기에) 삭제
delete_file(user_seq, file_name)
else:
# 해당 seq에 맞게 파일을 이동시키기
move_file(user_seq, file_name)
# 이동시킨 디렉토리명에 맞춰서 DB내용(src)도 수정해주기
update_new_dir(user_seq, file_name)
# Main execution
if __name__ == "__main__":
manage_profile_img()