签入版本
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
# 数据库会话
|
||||
|
||||
:::tip 说明
|
||||
项目通过 Flask 的 `g` 对象和 `before_request` / `after_request` 钩子实现请求级数据库会话管理,写操作按业务成功与否自动 commit/rollback。核心代码位于 `src/core/database.py` 和 `src/middleware/transaction.py`。
|
||||
:::
|
||||
|
||||
## 核心架构
|
||||
|
||||
数据库会话管理位于 `src/core/database.py` 和 `src/middleware/transaction.py`。
|
||||
|
||||
### SQLAlchemy + Flask-SQLAlchemy
|
||||
|
||||
项目使用 Flask-SQLAlchemy 扩展,通过 `db` 对象管理数据库会话:
|
||||
|
||||
```python
|
||||
# src/core/database.py
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
# 创建 Flask-SQLAlchemy 实例
|
||||
db = SQLAlchemy()
|
||||
```
|
||||
|
||||
在 `src/app.py` 的 `create_app()` 中初始化:
|
||||
|
||||
```python
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
# 配置数据库 URI
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
|
||||
# 初始化 SQLAlchemy
|
||||
db.init_app(app)
|
||||
return app
|
||||
```
|
||||
|
||||
业务代码通过 `db.session` 获取请求级会话:
|
||||
|
||||
```python
|
||||
from src.core.database import db
|
||||
|
||||
# 添加记录
|
||||
db.session.add(obj)
|
||||
db.session.flush() # 发送 SQL,获取自增 ID,不提交
|
||||
|
||||
# 查询
|
||||
result = db.session.query(Model).filter(...).all()
|
||||
```
|
||||
|
||||
### Flask g 对象
|
||||
|
||||
Flask 的 `g` 对象是请求级别的全局变量,每个请求都有独立的 `g` 对象:
|
||||
|
||||
```python
|
||||
from flask import g
|
||||
|
||||
# 在请求开始时设置
|
||||
g.user_id = 1
|
||||
|
||||
# 在请求处理过程中获取
|
||||
user_id = g.user_id
|
||||
```
|
||||
|
||||
## 会话中间件
|
||||
|
||||
`src/middleware/transaction.py` 中的中间件负责会话的创建、提交和关闭:
|
||||
|
||||
```python
|
||||
from flask import request, g
|
||||
from src.core.database import db
|
||||
|
||||
def register_transaction(app):
|
||||
"""注册 AOP 事务管理中间件"""
|
||||
|
||||
@app.before_request
|
||||
def _transaction_begin():
|
||||
"""请求开始前:标记事务活跃"""
|
||||
g.transaction_active = True
|
||||
|
||||
@app.after_request
|
||||
def _transaction_commit(response):
|
||||
"""请求结束后:根据响应状态决定提交或回滚"""
|
||||
if request.method in ('POST', 'PUT', 'DELETE', 'PATCH'):
|
||||
if response.status_code == 200:
|
||||
try:
|
||||
body = response.get_json()
|
||||
if body and body.get('code') == 0:
|
||||
# 业务成功:提交事务
|
||||
db.session.commit()
|
||||
else:
|
||||
# 业务失败:回滚事务
|
||||
db.session.rollback()
|
||||
except Exception:
|
||||
db.session.rollback()
|
||||
else:
|
||||
# HTTP 非 200:回滚事务
|
||||
db.session.rollback()
|
||||
return response
|
||||
|
||||
@app.teardown_request
|
||||
def _transaction_rollback(exception):
|
||||
"""请求异常时:回滚事务"""
|
||||
if exception:
|
||||
db.session.rollback()
|
||||
# 移除会话
|
||||
db.session.remove()
|
||||
```
|
||||
|
||||
### 业务成功判断
|
||||
|
||||
中间件通过响应体 JSON 的 `code` 字段判断业务是否成功:
|
||||
|
||||
```python
|
||||
def _is_business_success(body: dict) -> bool:
|
||||
"""仅当响应体为 JSON 且业务码 code == 0 视为成功"""
|
||||
if not body:
|
||||
return False
|
||||
if not isinstance(body, dict):
|
||||
return False
|
||||
return body.get('code') == 0
|
||||
```
|
||||
|
||||
## 独立提交
|
||||
|
||||
`commit_independently()` 函数用于审计日志等需要无论业务成功与否都持久化的场景:
|
||||
|
||||
```python
|
||||
from src.core.database import db
|
||||
|
||||
def commit_independently(obj):
|
||||
"""
|
||||
使用独立session提交对象,不受请求事务影响
|
||||
|
||||
日志等审计数据无论业务成功或失败都应持久化,
|
||||
因此需要绕过 AOP 事务中间件的 commit/rollback 机制。
|
||||
"""
|
||||
independent_session = db.session_factory()
|
||||
try:
|
||||
independent_session.add(obj)
|
||||
independent_session.commit()
|
||||
except Exception:
|
||||
independent_session.rollback()
|
||||
raise
|
||||
finally:
|
||||
independent_session.close()
|
||||
```
|
||||
|
||||
:::warning 使用场景
|
||||
`commit_independently()` 创建独立会话,不受请求级事务影响。适用于操作日志、登录日志等审计数据的持久化。普通业务数据应使用 `db.session` 获取请求级会话,由中间件统一管理事务。
|
||||
:::
|
||||
|
||||
## 会话生命周期
|
||||
|
||||
```
|
||||
请求进入
|
||||
→ before_request 标记事务活跃
|
||||
→ 业务代码通过 db.session 获取会话
|
||||
→ after_request 根据响应码 commit/rollback
|
||||
→ teardown_request 异常时 rollback 并移除会话
|
||||
```
|
||||
|
||||
## 中间件注册顺序
|
||||
|
||||
在 `src/app.py` 的 `create_app()` 中,按以下顺序初始化:
|
||||
|
||||
```python
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
|
||||
# 1. 加载配置(数据库、Redis 等)
|
||||
app.config.from_object(DatabaseConfig)
|
||||
|
||||
# 2. 配置 CORS 跨域
|
||||
CORS(app, ...)
|
||||
|
||||
# 3. 可信代理配置(ProxyFix)
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app, ...)
|
||||
|
||||
# 4. 注册操作日志中间件(须在事务拦截器之前)
|
||||
register_operation_log(app)
|
||||
|
||||
# 5. 注册 AOP 事务拦截器
|
||||
register_transaction(app)
|
||||
|
||||
# 6. 注册 API v1 路由
|
||||
register_v1(app)
|
||||
|
||||
# 7. 初始化扩展(数据库、Redis、调度器等)
|
||||
register_extends(app)
|
||||
|
||||
# 8. 注册中间件(登录校验、演示模式、异常处理)
|
||||
register_middleware(app)
|
||||
|
||||
# 9. 注入依赖(权限提供方、操作日志写入器)
|
||||
_inject_dependencies(app)
|
||||
|
||||
return app
|
||||
```
|
||||
|
||||
`register_operation_log` 在 `register_transaction` 之前,确保操作日志在事务提交前记录。
|
||||
|
||||
## BaseDB.save() 方法
|
||||
|
||||
`BaseDB` 基类提供的 `save()` 方法只执行 `flush`,不执行 `commit`:
|
||||
|
||||
```python
|
||||
from src.core.database import db
|
||||
from src.common.base_model import BaseModel
|
||||
|
||||
class BaseDB:
|
||||
def save(self):
|
||||
"""保存模型到数据库(仅 flush,不 commit)"""
|
||||
db.session.add(self)
|
||||
db.session.flush()
|
||||
return self
|
||||
|
||||
def delete(self):
|
||||
"""软删除模型(仅 flush,不 commit)"""
|
||||
self.is_delete = 1
|
||||
db.session.flush()
|
||||
```
|
||||
|
||||
`flush` 的作用:
|
||||
1. 将 SQL 语句发送到数据库执行
|
||||
2. 获取自增 ID 等数据库生成的值
|
||||
3. 但不提交事务,事务由中间件统一管理
|
||||
|
||||
## 注意事项
|
||||
|
||||
:::warning 重要提示
|
||||
1. **不要手动调用 `db.session.commit()`**:事务由中间件统一管理,手动 commit 可能导致事务提前提交
|
||||
2. **不要手动调用 `db.session.rollback()`**:除非在 `try-except` 块中需要手动回滚
|
||||
3. **只读操作使用 GET 方法**:GET 请求不会触发事务处理
|
||||
4. **写操作必须返回 R 对象**:中间件通过 `code` 字段判断是否需要提交
|
||||
:::
|
||||
|
||||
## 总结
|
||||
|
||||
数据库会话管理通过 Flask 的 `g` 对象和 `before_request` / `after_request` 钩子实现请求级隔离,中间件自动根据业务响应码 commit/rollback,`commit_independently()` 提供独立事务能力用于审计数据持久化。这种设计确保了事务边界清晰,业务代码无需手动管理事务。
|
||||
Reference in New Issue
Block a user