diff --git a/src/common/utils/ip.py b/src/common/utils/ip.py index 039ee66..5bbf50f 100644 --- a/src/common/utils/ip.py +++ b/src/common/utils/ip.py @@ -2,14 +2,13 @@ IP地址工具模块 ============================================================= -提供客户端IP获取和地理位置查询: -- get_client_ip: 获取客户端真实IP(需配合Flask ProxyFix使用) -- get_ip_location: 调用第三方API查询IP地理位置 +提供客户端IP获取功能: +- get_client_ip: 获取客户端真实IP(支持反向代理场景) + +地理位置查询请使用 ip2region 模块(离线数据库,高性能)。 """ -import json import logging -import urllib.request logger = logging.getLogger(__name__) @@ -18,38 +17,28 @@ def get_client_ip(): """ 获取客户端真实IP地址 - 通过Flask ProxyFix中间件由PROXY_NUM配置自动设置 - request.remote_addr为真实客户端IP。未配置ProxyFix时, - request.remote_addr即为直连IP,避免客户端伪造。 + 优先级: + 1. X-Forwarded-For 头(反向代理场景,取第一个非内网IP) + 2. X-Real-IP 头(Nginx 常用配置) + 3. remote_addr(直连场景) Returns: str: 客户端IP地址 """ from flask import request + # 优先从 X-Forwarded-For 获取(反向代理场景) + forwarded_for = request.headers.get("X-Forwarded-For", "") + if forwarded_for: + # 格式: client, proxy1, proxy2 → 取第一个 + first_ip = forwarded_for.split(",")[0].strip() + if first_ip and first_ip not in ("127.0.0.1", "localhost", "0.0.0.0", "::1"): + return first_ip + + # 回退到 X-Real-IP(Nginx proxy_set_header X-Real-IP $remote_addr) + real_ip = request.headers.get("X-Real-IP", "").strip() + if real_ip and real_ip not in ("127.0.0.1", "localhost", "0.0.0.0", "::1"): + return real_ip + + # 最后使用 remote_addr return request.remote_addr - - -def get_ip_location(ip: str) -> str: - """ - 根据IP获取地理位置(使用第三方API) - - Args: - ip: IP地址 - - Returns: - 地理位置信息,格式:"国家 省份 城市" - """ - if ip in ['127.0.0.1', 'localhost'] or ip.startswith('192.168.') or ip.startswith('10.'): - return "内网IP" - - try: - url = f"http://ip-api.com/json/{ip}?lang=zh-CN" - with urllib.request.urlopen(url, timeout=3) as response: - data = json.loads(response.read().decode('utf-8')) - if data.get('status') == 'success': - return f"{data.get('country', '')} {data.get('regionName', '')} {data.get('city', '')}" - except Exception as e: - logger.warning(f"获取IP地理位置失败: {e}") - - return "未知" diff --git a/src/core/redis_client.py b/src/core/redis_client.py index 9465212..b8f797c 100644 --- a/src/core/redis_client.py +++ b/src/core/redis_client.py @@ -68,7 +68,7 @@ from flask_redis import FlaskRedis # FlaskRedis 扩展实例 # ============================================================ # 在应用工厂中通过 init_redis(app) 完成绑定 -redis_client = FlaskRedis(decode_responses=True, protocol=2) +redis_client = FlaskRedis(decode_responses=True) def init_redis(app: Flask): diff --git a/src/middleware/operation_log.py b/src/middleware/operation_log.py index b9cf76d..20b88e2 100644 --- a/src/middleware/operation_log.py +++ b/src/middleware/operation_log.py @@ -135,8 +135,11 @@ def register_operation_log(app): 1. 请求方法为 POST/PUT/DELETE/PATCH 2. 路径不在排除列表中 3. HTTP 状态码为 2xx - 4. 业务 code 为 0(成功) - 5. 非内部调用 + 4. 非内部调用 + + 记录范围: + - 成功操作(code=0):status=0(正常) + - 失败操作(code!=0,如演示环境拦截):status=1(异常),记录错误信息 """ # 只记录写操作 if request.method not in ("POST", "PUT", "DELETE", "PATCH"): @@ -147,24 +150,28 @@ def register_operation_log(app): if _is_excluded_path(path): return response - # 只记录成功操作(HTTP 2xx 且业务 code=0) + # HTTP 非 2xx 不记录(如 404、500 等) if not (200 <= response.status_code < 300): return response - try: - data = response.get_json(silent=True) - if data and data.get("code", 0) != 0: - return response - except Exception: - pass - # 内部调用不记录 if getattr(g, "_internal_call", False): return response - # 异步记录,不阻塞响应 + # 判断业务状态:code=0 为成功,其他为失败 + status = 0 # 默认成功 + error_msg = "" try: - _write_log(response) + data = response.get_json(silent=True) + if data and data.get("code", 0) != 0: + status = 1 # 业务失败 + error_msg = data.get("msg", "操作失败") + except Exception: + pass + + # 记录日志(成功和失败都记录) + try: + _write_log(response, status=status, error_msg=error_msg) except Exception as e: logger.warning(f"记录操作日志失败: {str(e)}") @@ -173,7 +180,7 @@ def register_operation_log(app): logger.info("操作日志中间件注册成功") -def _write_log(response): +def _write_log(response, status=0, error_msg=""): """ 写入操作日志 @@ -181,6 +188,8 @@ def _write_log(response): Args: response: Flask 响应对象 + status: 操作状态(0-正常,1-异常) + error_msg: 错误信息(失败时) """ from src.core.security import get_username from src.common.utils.ip import get_client_ip @@ -217,7 +226,7 @@ def _write_log(response): title=title, log_type=log_type, source=0, # OperationLog.Source.ADMIN - status=0, # OperationLog.Status.NORMAL + status=status, method=request.endpoint or "", request_method=request.method, url=request.path, @@ -228,7 +237,7 @@ def _write_log(response): os=ua_info.get("os", "未知"), browser=ua_info.get("browser", "未知"), consume_time=consume_time, - error="", + error=error_msg, username=username, ) diff --git a/ui/.env b/ui/.env index 23785f8..fc2dbd5 100644 --- a/ui/.env +++ b/ui/.env @@ -5,7 +5,7 @@ VITE_PORT = 8001 VITE_GLOB_APP_TITLE = Flask+NaiveUI版 # spa shortname -VITE_GLOB_APP_SHORT_NAME = Flask+NaiveUI版 +VITE_GLOB_APP_SHORT_NAME = DjangoAdmin # 生产环境 开启mock VITE_GLOB_PROD_MOCK = true