重构前保存现有 apps/ 结构基线,供后续各阶段回滚。 Co-Authored-By: Claude <noreply@anthropic.com>
118 lines
3.9 KiB
Python
118 lines
3.9 KiB
Python
import logging
|
|
|
|
from sqlalchemy import and_
|
|
|
|
from apps.constants.constants import OPERATION_SOURCE_LIST, OPERATION_STATUS_LIST
|
|
from apps.constants.message import PAGE_SIZE
|
|
from apps.models.operation_log import OperationLog
|
|
from extensions.sqlalchemy import commit_independently
|
|
from extensions import get_db_session
|
|
from utils import R
|
|
from utils.ip import get_client_ip
|
|
from utils.ip2region import get_ip_location
|
|
from utils.ua import parse_user_agent
|
|
from utils.request import like_contains, parse_id_list
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def record_operation_log(request, title, **kwargs):
|
|
"""
|
|
记录操作日志
|
|
|
|
使用独立session写入,不受请求事务commit/rollback影响。
|
|
日志是审计数据,无论业务成功或失败都应持久化。
|
|
|
|
Args:
|
|
request: FastAPI Request 对象
|
|
title: 操作标题
|
|
**kwargs: 其他可选参数
|
|
- type: 请求类型
|
|
- source: 请求来源(默认0-后台)
|
|
- method: 请求方法
|
|
- request_method: 请求方式
|
|
- url: 请求URL
|
|
- param: 请求参数
|
|
- result: 请求结果
|
|
- consume_time: 请求耗时(毫秒)
|
|
- status: 操作状态(0-正常,1-异常)
|
|
- error: 错误描述
|
|
"""
|
|
try:
|
|
# 从 request 提取信息
|
|
ip = get_client_ip(request)
|
|
location = get_ip_location(ip)
|
|
ua = request.headers.get('User-Agent', '')
|
|
os_name, browser_name = parse_user_agent(ua)
|
|
|
|
log = OperationLog(
|
|
title=title,
|
|
type=kwargs.get('type', 0),
|
|
source=kwargs.get('source', 0),
|
|
method=kwargs.get('method', ''),
|
|
request_method=kwargs.get('request_method', request.method),
|
|
url=kwargs.get('url', str(request.url)),
|
|
param=kwargs.get('param', ''),
|
|
result=kwargs.get('result', ''),
|
|
ip=ip,
|
|
location=location,
|
|
os=os_name,
|
|
browser=browser_name,
|
|
consume_time=kwargs.get('consume_time', 0),
|
|
status=kwargs.get('status', 0),
|
|
error=kwargs.get('error', ''),
|
|
)
|
|
|
|
commit_independently(log)
|
|
return log
|
|
|
|
except Exception as e:
|
|
logger.error(f"记录操作日志异常: {str(e)}")
|
|
return None
|
|
|
|
|
|
def get_operation_log_page(request):
|
|
db = get_db_session()
|
|
# 页码
|
|
page = int(request.query_params.get("pageNo", 1))
|
|
# 每页数
|
|
limit = int(request.query_params.get("pageSize", PAGE_SIZE))
|
|
# 实例化查询对象
|
|
query = db.query(OperationLog).filter(OperationLog.is_delete == 0)
|
|
# 请求标题
|
|
title = request.query_params.get('title')
|
|
if title:
|
|
query = query.filter(OperationLog.title.like(like_contains(title), escape='\\'))
|
|
# 请求方式
|
|
request_method = request.query_params.get('request_method')
|
|
if request_method:
|
|
query = query.filter(OperationLog.request_method == request_method)
|
|
# 操作状态
|
|
status = request.query_params.get('status')
|
|
if status:
|
|
query = query.filter(OperationLog.status == status)
|
|
# 排序
|
|
query = query.order_by(OperationLog.id.desc())
|
|
# 记录总数
|
|
count = query.count()
|
|
# 分页查询
|
|
log_list = query.limit(limit).offset((page - 1) * limit).all()
|
|
# 若当前页超出末页(数据被并发删除),自动回退到第一页
|
|
if page > 1 and not log_list:
|
|
page = 1
|
|
count = query.count()
|
|
log_list = query.limit(limit).offset(0).all()
|
|
# 实例化结果
|
|
result = []
|
|
if len(log_list) > 0:
|
|
for item in log_list:
|
|
data = item.to_dict()
|
|
data['source_name'] = OPERATION_SOURCE_LIST.get(item.source)
|
|
data['status_name'] = OPERATION_STATUS_LIST.get(item.status)
|
|
result.append(data)
|
|
return R.ok(data=result, count=count)
|
|
|
|
|
|
def delete_operation_log(ids_str):
|
|
return OperationLog.batch_delete_r(ids_str)
|