238 lines
6.7 KiB
Markdown
238 lines
6.7 KiB
Markdown
# 数据库会话
|
||
|
||
:::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()` 提供独立事务能力用于审计数据持久化。这种设计确保了事务边界清晰,业务代码无需手动管理事务。
|