refactor: P5 DI 注入 + 中间件迁入 src/core
【依赖注入(解决 core→modules 倒置)】 - src/core/permission.py:PermissionProvider 协议 + check_permission (不再 import menu 服务,改从 current_app.extensions['permission_provider'] 获取) - src/core/middleware.py:整合 check_login/check_demo/transaction/op_log/error_handler (op_log 写入器 DI:current_app.extensions['operation_log_writer']) - application/factory.py:组合根注入 MenuPermissionProvider + record_operation_log - src/core/scheduler.py 懒加载指向 src.modules.job.scheduler_service 【验收】src/core 内零 application 引用;DI 注入验证通过; 未登录 401、白名单放行;12 passed;core+中间件 flake8 零告警 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
35f89395a2
commit
0b0fa0c047
@@ -1,241 +1,8 @@
|
||||
"""
|
||||
AOP 事务拦截器(企业级实现)
|
||||
兼容 shim(绞杀者迁移期间保留)
|
||||
=============================================================
|
||||
|
||||
设计理念:
|
||||
1. 零代码侵入:业务代码无需任何事务管理代码
|
||||
2. 自动读写分离:GET 不提交,POST/PUT/DELETE 自动提交
|
||||
3. 统一异常处理:所有异常自动回滚
|
||||
4. 性能监控:记录事务执行时间
|
||||
|
||||
使用方式:
|
||||
在 Flask 应用初始化时调用 register_transaction(app) 即可。
|
||||
业务代码无需任何改动。
|
||||
|
||||
Example:
|
||||
# app/__init__.py
|
||||
from application.middleware.transaction import register_transaction
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
register_transaction(app) # 一行代码搞定所有事务
|
||||
return app
|
||||
AOP 事务拦截器已迁移至 src/core/middleware.py。
|
||||
本文件仅作兼容转发,最终在 P7(切尾)阶段删除。
|
||||
"""
|
||||
|
||||
from flask import request, g
|
||||
from extensions import db
|
||||
import logging
|
||||
import time
|
||||
from functools import wraps
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 核心:AOP 事务拦截器
|
||||
# ============================================================
|
||||
|
||||
def register_transaction(app):
|
||||
"""
|
||||
注册 AOP 事务拦截器到 Flask 应用
|
||||
|
||||
在请求生命周期中自动管理事务:
|
||||
- before_request: 开启事务标记
|
||||
- after_request: 自动提交(POST/PUT/DELETE)
|
||||
- teardown_request: 异常回滚 + 资源清理
|
||||
|
||||
Args:
|
||||
app: Flask 应用实例
|
||||
|
||||
Example:
|
||||
register_transaction(app)
|
||||
"""
|
||||
|
||||
@app.before_request
|
||||
def begin_transaction():
|
||||
"""请求开始:记录开始时间,开启事务标记"""
|
||||
g.request_start_time = time.time()
|
||||
g.transaction_active = True
|
||||
|
||||
logger.debug(
|
||||
f"请求开始, method: {request.method}, path: {request.path}, "
|
||||
f"ip: {request.remote_addr}"
|
||||
)
|
||||
|
||||
@app.after_request
|
||||
def commit_transaction(response):
|
||||
"""
|
||||
请求结束:自动提交事务
|
||||
|
||||
提交规则:
|
||||
1. 只对写操作(POST/PUT/DELETE/PATCH)提交
|
||||
2. 只在响应成功(2xx)且业务成功(code=0)时提交
|
||||
3. GET/OPTIONS/HEAD 不提交(只读)
|
||||
4. 业务失败(R.failed)不提交,自动回滚
|
||||
"""
|
||||
# 只对写操作处理事务
|
||||
if request.method in ('POST', 'PUT', 'DELETE', 'PATCH'):
|
||||
should_commit = 200 <= response.status_code < 300
|
||||
|
||||
# 检查业务状态码:R.failed() 返回 HTTP 200 但 code=1/ok=False
|
||||
if should_commit:
|
||||
try:
|
||||
data = response.get_json(silent=True)
|
||||
if data and data.get('code', 0) != 0:
|
||||
should_commit = False
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if should_commit:
|
||||
try:
|
||||
if db.session.is_active:
|
||||
db.session.commit()
|
||||
logger.debug(
|
||||
f"事务提交成功, method: {request.method}, "
|
||||
f"path: {request.path}, status: {response.status_code}"
|
||||
)
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
logger.error(
|
||||
f"事务提交失败, method: {request.method}, "
|
||||
f"path: {request.path}, error: {str(e)}",
|
||||
exc_info=True
|
||||
)
|
||||
from utils import R
|
||||
return R.failed(msg="数据处理失败"), 500
|
||||
else:
|
||||
# 业务失败或非2xx,回滚未提交的变更
|
||||
try:
|
||||
if db.session.is_active:
|
||||
db.session.rollback()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 记录响应日志
|
||||
elapsed = time.time() - g.get('request_start_time', time.time())
|
||||
logger.debug(
|
||||
f"请求结束, method: {request.method}, path: {request.path}, "
|
||||
f"status: {response.status_code}, elapsed: {elapsed:.3f}s"
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
@app.teardown_request
|
||||
def cleanup_transaction(exception=None):
|
||||
"""
|
||||
请求结束:清理资源
|
||||
|
||||
发生异常时自动回滚事务,确保数据一致性。
|
||||
"""
|
||||
if exception is not None:
|
||||
try:
|
||||
db.session.rollback()
|
||||
logger.error(
|
||||
f"请求异常,事务已回滚, method: {request.method}, "
|
||||
f"path: {request.path}, error: {str(exception)}",
|
||||
exc_info=True
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"事务回滚失败: {str(e)}", exc_info=True)
|
||||
|
||||
# 清理请求标记
|
||||
g.pop('transaction_active', None)
|
||||
g.pop('request_start_time', None)
|
||||
|
||||
logger.info("AOP 事务拦截器注册成功")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 备用:精细控制装饰器(特殊场景使用)
|
||||
# ============================================================
|
||||
|
||||
def transactional(func):
|
||||
"""
|
||||
事务装饰器(备用)
|
||||
|
||||
适用于需要精细控制事务边界的特殊场景。
|
||||
大多数情况下,使用 AOP 拦截器即可。
|
||||
|
||||
Example:
|
||||
@transactional
|
||||
def complex_business():
|
||||
# 精细控制的事务逻辑
|
||||
pass
|
||||
"""
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
if g.get('disable_auto_commit', False):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
try:
|
||||
result = func(*args, **kwargs)
|
||||
if not g.get('transaction_active', False):
|
||||
db.session.commit()
|
||||
return result
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
logger.error(f"装饰器事务执行失败: {str(e)}", exc_info=True)
|
||||
raise
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def transactional_response(func):
|
||||
"""
|
||||
事务装饰器(带返回 R 对象)
|
||||
|
||||
用于需要直接返回 R 对象的接口。
|
||||
|
||||
Example:
|
||||
@transactional_response
|
||||
def add_position():
|
||||
position.save()
|
||||
return R.ok(msg="添加成功")
|
||||
"""
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
from utils import R
|
||||
try:
|
||||
result = func(*args, **kwargs)
|
||||
if not g.get('transaction_active', False):
|
||||
db.session.commit()
|
||||
return result
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
logger.error(f"装饰器事务执行失败: {str(e)}", exc_info=True)
|
||||
return R.failed(msg=f"操作失败: {str(e)}")
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 备用:事务上下文管理器(特殊场景使用)
|
||||
# ============================================================
|
||||
|
||||
class TransactionContext:
|
||||
"""
|
||||
事务上下文管理器
|
||||
|
||||
用于需要精细控制事务的特殊场景。
|
||||
|
||||
Example:
|
||||
with TransactionContext():
|
||||
position.save()
|
||||
user.save()
|
||||
# 自动提交或回滚
|
||||
"""
|
||||
|
||||
def __enter__(self):
|
||||
g.disable_auto_commit = True
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
g.disable_auto_commit = False
|
||||
if exc_type is None:
|
||||
if not g.get('transaction_active', False):
|
||||
db.session.commit()
|
||||
else:
|
||||
db.session.rollback()
|
||||
logger.error(f"事务上下文回滚: {str(exc_val)}", exc_info=True)
|
||||
return False
|
||||
from src.core.middleware import * # noqa: F401,F403
|
||||
|
||||
Reference in New Issue
Block a user