Skip to content

opera_log_middleware method split #105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 10, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions backend/app/api/v1/login_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ async def get_all_login_logs(
db: CurrentSession,
username: Annotated[str | None, Query()] = None,
status: Annotated[bool | None, Query()] = None,
ipaddr: Annotated[str | None, Query()] = None,
ip: Annotated[str | None, Query()] = None,
):
log_select = await LoginLogService.get_select(username=username, status=status, ipaddr=ipaddr)
log_select = await LoginLogService.get_select(username=username, status=status, ip=ip)
page_data = await paging_data(db, log_select, GetAllLoginLog)
return await response_base.success(data=page_data)

Expand Down
4 changes: 2 additions & 2 deletions backend/app/api/v1/opera_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ async def get_all_opera_logs(
db: CurrentSession,
username: Annotated[str | None, Query()] = None,
status: Annotated[bool | None, Query()] = None,
ipaddr: Annotated[str | None, Query()] = None,
ip: Annotated[str | None, Query()] = None,
):
log_select = await OperaLogService.get_select(username=username, status=status, ipaddr=ipaddr)
log_select = await OperaLogService.get_select(username=username, status=status, ip=ip)
page_data = await paging_data(db, log_select, GetAllOperaLog)
return await response_base.success(data=page_data)

Expand Down
6 changes: 3 additions & 3 deletions backend/app/crud/crud_login_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@

class CRUDLoginLog(CRUDBase[LoginLog, CreateLoginLog, UpdateLoginLog]):
async def get_all(
self, username: str | None = None, status: bool | None = None, ipaddr: str | None = None
self, username: str | None = None, status: bool | None = None, ip: str | None = None
) -> Select:
se = select(self.model).order_by(desc(self.model.create_time))
where_list = []
if username:
where_list.append(self.model.username.like(f'%{username}%'))
if status is not None:
where_list.append(self.model.status == status)
if ipaddr:
where_list.append(self.model.ipaddr.like(f'%{ipaddr}%'))
if ip:
where_list.append(self.model.ip.like(f'%{ip}%'))
if where_list:
se = se.where(and_(*where_list))
return se
Expand Down
6 changes: 3 additions & 3 deletions backend/app/crud/crud_opera_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@

class CRUDOperaLogDao(CRUDBase[OperaLog, CreateOperaLog, UpdateOperaLog]):
async def get_all(
self, username: str | None = None, status: bool | None = None, ipaddr: str | None = None
self, username: str | None = None, status: bool | None = None, ip: str | None = None
) -> Select:
se = select(self.model).order_by(desc(self.model.create_time))
where_list = []
if username:
where_list.append(self.model.username.like(f'%{username}%'))
if status is not None:
where_list.append(self.model.status == status)
if ipaddr:
where_list.append(self.model.ipaddr.like(f'%{ipaddr}%'))
if ip:
where_list.append(self.model.ip.like(f'%{ip}%'))
if where_list:
se = se.where(and_(*where_list))
return se
Expand Down
104 changes: 56 additions & 48 deletions backend/app/middleware/opera_log_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@
from starlette.background import BackgroundTask
from starlette.requests import Request
from starlette.types import ASGIApp, Scope, Receive, Send
from user_agents import parse

from backend.app.common.enums import OperaLogCipherType
from backend.app.common.log import log
from backend.app.core.conf import settings
from backend.app.schemas.opera_log import CreateOperaLog
from backend.app.services.opera_log_service import OperaLogService
from backend.app.utils import request_parse
from backend.app.utils.encrypt import AESCipher, Md5Cipher
from backend.app.utils.request_parse import parse_user_agent_info, parse_ip_info


class OperaLogMiddleware:
Expand All @@ -38,17 +37,8 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
return

# 请求信息解析
ip = await request_parse.get_request_ip(request)
user_agent = request.headers.get('User-Agent')
user_agent_parsed = parse(user_agent)
os = user_agent_parsed.get_os()
browser = user_agent_parsed.get_browser()
if settings.LOCATION_PARSE == 'online':
location = await request_parse.get_location_online(ip, user_agent)
elif settings.LOCATION_PARSE == 'offline':
location = await request_parse.get_location_offline(ip)
else:
location = '未知'
user_agent, device, os, browser = await parse_user_agent_info(request)
ip, country, region, city = await parse_ip_info(request)
try:
# 此信息依赖于 jwt 中间件
username = request.user.username
Expand All @@ -65,20 +55,62 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
json_data = await request.json()
args.update(json_data)

# 设置附加请求信息
# 设置附加请求信息(可选)
request.state.ip = ip
request.state.location = location
request.state.country = country
request.state.region = region
request.state.city = city
request.state.user_agent = user_agent
request.state.os = os
request.state.browser = browser
request.state.device = device

# 执行请求
start_time = datetime.now()
code, msg, status, err = await self.execute_request(request, send)
end_time = datetime.now()
cost_time = (end_time - start_time).total_seconds() * 1000.0

summary = request.scope.get('route').summary
title = summary if summary != '' else request.scope.get('route').summary
args.update(request.path_params)
# 脱敏处理
args = self.desensitization(args)

# 日志创建
opera_log_in = CreateOperaLog(
username=username,
method=method,
title=title,
path=path,
ip=ip,
country=country,
region=region,
city=city,
user_agent=user_agent,
os=os,
browser=browser,
device=device,
args=args,
status=status,
code=code,
msg=msg,
cost_time=cost_time,
opera_time=start_time,
)
back = BackgroundTask(OperaLogService.create, obj_in=opera_log_in)
await back()

# 错误抛出
if err:
raise err from None

async def execute_request(self, request: Request, send: Send) -> tuple:
# 预置响应信息
code: int = 200
msg: str = 'Success'
status: bool = True
err: Any = None

# 执行请求
start_time = datetime.now()
try:
# 详见 https://github.com/tiangolo/fastapi/discussions/8385#discussioncomment-6117967
async def wrapped_rcv_gen():
Expand All @@ -99,10 +131,11 @@ async def wrapped_rcv_gen():
msg = getattr(e, 'msg', str(e) or 'Internal Server Error')
status = False
err = e
end_time = datetime.now()
summary = request.scope.get('route').summary
title = summary if summary != '' else request.scope.get('route').summary
args.update(request.path_params)

return code, msg, status, err

@staticmethod
def desensitization(args: dict):
if len(args) > 0:
match settings.OPERA_LOG_ENCRYPT:
case OperaLogCipherType.aes:
Expand All @@ -123,29 +156,4 @@ async def wrapped_rcv_gen():
for key in args.keys():
if key in settings.OPERA_LOG_ENCRYPT_INCLUDE:
args[key] = '******'
args = args if len(args) > 0 else None
cost_time = (end_time - start_time).total_seconds() * 1000.0

# 日志创建
opera_log_in = CreateOperaLog(
username=username,
method=method,
title=title,
path=path,
ipaddr=ip,
location=location,
os=os,
browser=browser,
args=args,
status=status,
code=code,
msg=msg,
cost_time=cost_time,
opera_time=start_time,
)
back = BackgroundTask(OperaLogService.create, obj_in=opera_log_in)
await back()

# 错误抛出
if err:
raise err from None
return args or None
12 changes: 8 additions & 4 deletions backend/app/models/sys_login_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ class LoginLog(DataClassBase):
user_uuid: Mapped[str] = mapped_column(String(50), comment='用户UUID')
username: Mapped[str] = mapped_column(String(20), comment='用户名')
status: Mapped[bool] = mapped_column(insert_default=0, comment='登录状态(0失败 1成功)')
ipaddr: Mapped[str] = mapped_column(String(50), comment='登录IP地址')
location: Mapped[str] = mapped_column(String(50), comment='归属地')
browser: Mapped[str] = mapped_column(String(50), comment='浏览器')
os: Mapped[str] = mapped_column(String(50), comment='操作系统')
ip: Mapped[str] = mapped_column(String(50), comment='登录IP地址')
country: Mapped[str | None] = mapped_column(String(50), comment='国家')
region: Mapped[str | None] = mapped_column(String(50), comment='地区')
city: Mapped[str | None] = mapped_column(String(50), comment='城市')
user_agent: Mapped[str] = mapped_column(String(255), comment='请求头')
os: Mapped[str | None] = mapped_column(String(50), comment='操作系统')
browser: Mapped[str | None] = mapped_column(String(50), comment='浏览器')
device: Mapped[str | None] = mapped_column(String(50), comment='设备')
msg: Mapped[str] = mapped_column(String(255), comment='提示消息')
login_time: Mapped[datetime] = mapped_column(comment='登录时间')
create_time: Mapped[datetime] = mapped_column(init=False, default=func.now(), comment='创建时间')
12 changes: 8 additions & 4 deletions backend/app/models/sys_opera_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@ class OperaLog(DataClassBase):
method: Mapped[str] = mapped_column(String(20), comment='请求类型')
title: Mapped[str] = mapped_column(String(255), comment='操作模块')
path: Mapped[str] = mapped_column(String(500), comment='请求路径')
ipaddr: Mapped[str] = mapped_column(String(50), comment='IP地址')
location: Mapped[str] = mapped_column(String(50), comment='归属地')
os: Mapped[str] = mapped_column(String(50), comment='操作系统')
browser: Mapped[str] = mapped_column(String(50), comment='浏览器')
ip: Mapped[str] = mapped_column(String(50), comment='IP地址')
country: Mapped[str | None] = mapped_column(String(50), comment='国家')
region: Mapped[str | None] = mapped_column(String(50), comment='地区')
city: Mapped[str | None] = mapped_column(String(50), comment='城市')
user_agent: Mapped[str] = mapped_column(String(255), comment='请求头')
os: Mapped[str | None] = mapped_column(String(50), comment='操作系统')
browser: Mapped[str | None] = mapped_column(String(50), comment='浏览器')
device: Mapped[str | None] = mapped_column(String(50), comment='设备')
args: Mapped[str | None] = mapped_column(JSON(), comment='请求参数')
status: Mapped[bool] = mapped_column(comment='操作状态(0异常 1正常)')
code: Mapped[int] = mapped_column(insert_default=200, comment='操作状态码')
Expand Down
12 changes: 8 additions & 4 deletions backend/app/schemas/login_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ class LoginLogBase(BaseModel):
user_uuid: str
username: str
status: bool
ipaddr: str
location: str
browser: str
os: str
ip: str
country: str | None
region: str | None
city: str | None
user_agent: str
browser: str | None
os: str | None
device: str | None
msg: str
login_time: datetime

Expand Down
12 changes: 8 additions & 4 deletions backend/app/schemas/opera_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ class OperaLogBase(BaseModel):
method: str
title: str
path: str
ipaddr: str
location: str
os: str
browser: str
ip: str
country: str | None = None
region: str | None = None
city: str | None = None
user_agent: str
os: str | None = None
browser: str | None = None
device: str | None = None
args: dict | None = None
status: bool
code: int
Expand Down
25 changes: 12 additions & 13 deletions backend/app/services/login_log_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,31 @@
from backend.app.database.db_mysql import async_db_session
from backend.app.models import User
from backend.app.schemas.login_log import CreateLoginLog
from backend.app.utils import request_parse


class LoginLogService:
@staticmethod
async def get_select(*, username: str, status: bool, ipaddr: str) -> Select:
return await LoginLogDao.get_all(username=username, status=status, ipaddr=ipaddr)
async def get_select(*, username: str, status: bool, ip: str) -> Select:
return await LoginLogDao.get_all(username=username, status=status, ip=ip)

@staticmethod
async def create(
*, db: AsyncSession, request: Request, user: User, login_time: datetime, status: bool, msg: str
*, db: AsyncSession, request: Request, user: User, login_time: datetime, status: bool, msg: str
) -> NoReturn:
try:
ip = await request_parse.get_request_ip(request)
# 来自 opera log 中间件定义的扩展参数,详见 opera_log_middleware.py
location = request.state.location
browser = request.state.browser
os = request.state.os
# request.state 来自 opera log 中间件定义的扩展参数,详见 opera_log_middleware.py
obj_in = CreateLoginLog(
user_uuid=user.user_uuid,
username=user.username,
status=status,
ipaddr=ip,
location=location,
browser=browser,
os=os,
ip=request.state.ip,
country=request.state.country,
region=request.state.region,
city=request.state.city,
user_agent=request.state.user_agent,
browser=request.state.browser,
os=request.state.os,
device=request.state.device,
msg=msg,
login_time=login_time,
)
Expand Down
4 changes: 2 additions & 2 deletions backend/app/services/opera_log_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

class OperaLogService:
@staticmethod
async def get_select(*, username: str | None = None, status: bool | None = None, ipaddr: str | None = None):
return await OperaLogDao.get_all(username=username, status=status, ipaddr=ipaddr)
async def get_select(*, username: str | None = None, status: bool | None = None, ip: str | None = None):
return await OperaLogDao.get_all(username=username, status=status, ip=ip)

@staticmethod
async def create(*, obj_in: CreateOperaLog):
Expand Down
Loading