签入版本
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
# 请求解析工具
|
||||
|
||||
:::tip 说明
|
||||
`src/common/utils/request.py` 提供请求数据解析相关的工具函数,包括批量 ID 解析、SQL LIKE 安全处理、分页参数解析等。这些工具函数在各业务模块中广泛使用。
|
||||
:::
|
||||
|
||||
## parse_id_list
|
||||
|
||||
从请求体解析批量删除的 ID 数组,校验并转为整数列表。
|
||||
|
||||
```python
|
||||
from flask import request
|
||||
from src.common.utils.request import parse_id_list
|
||||
|
||||
def parse_id_list(request) -> list:
|
||||
"""
|
||||
从请求体解析 ID 数组
|
||||
请求体格式: [1, 2, 3]
|
||||
返回: [1, 2, 3]
|
||||
|
||||
示例:
|
||||
>>> parse_id_list(request)
|
||||
[1, 2, 3]
|
||||
"""
|
||||
ids = request.get_json()
|
||||
if not ids or not isinstance(ids, list) or len(ids) == 0:
|
||||
raise BizError("参数错误:ids必须为非空数组")
|
||||
try:
|
||||
id_list = [int(id_val) for id_val in ids]
|
||||
except (ValueError, TypeError):
|
||||
raise BizError("参数错误:数组元素必须为数字")
|
||||
return id_list
|
||||
```
|
||||
|
||||
使用示例:
|
||||
|
||||
```python
|
||||
@position_blue.route('/batchDelete', methods=['DELETE'])
|
||||
@check_permission('sys:position:batchDelete')
|
||||
def batch_delete():
|
||||
from src.modules.system.position.service import batch_delete_positions
|
||||
id_list = parse_id_list(request)
|
||||
return batch_delete_positions(id_list)
|
||||
```
|
||||
|
||||
## parse_request_body
|
||||
|
||||
解析请求体 JSON 数据,并使用 WTForms 进行校验。
|
||||
|
||||
```python
|
||||
from flask import request
|
||||
from src.common.utils.request import parse_request_body
|
||||
|
||||
def parse_request_body(request, form_class):
|
||||
"""
|
||||
解析请求体并校验
|
||||
|
||||
参数:
|
||||
request: Flask 请求对象
|
||||
form_class: WTForms 表单类
|
||||
|
||||
返回:
|
||||
dict: 校验通过的数据
|
||||
|
||||
异常:
|
||||
BizError: 校验失败时抛出
|
||||
"""
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
raise BizError("请求体不能为空")
|
||||
|
||||
form = form_class(data=data)
|
||||
if not form.validate():
|
||||
# 提取第一个错误信息
|
||||
errors = form.errors
|
||||
first_error = list(errors.values())[0][0] if errors else "参数校验失败"
|
||||
raise BizError(first_error)
|
||||
|
||||
return {field.name: field.data for field in form}
|
||||
```
|
||||
|
||||
使用示例:
|
||||
|
||||
```python
|
||||
@position_blue.route('/add', methods=['POST'])
|
||||
@check_permission('sys:position:add')
|
||||
def add():
|
||||
from src.modules.system.position.service import add_position
|
||||
from src.modules.system.position.schema import PositionForm
|
||||
data = parse_request_body(request, PositionForm)
|
||||
return add_position(data)
|
||||
```
|
||||
|
||||
## parse_pagination
|
||||
|
||||
安全解析分页参数 `pageNo` / `pageSize`。
|
||||
|
||||
```python
|
||||
from flask import request
|
||||
from src.common.utils.request import parse_pagination
|
||||
|
||||
def parse_pagination(default_page_size: int = 10, max_page_size: int = 500) -> tuple:
|
||||
"""
|
||||
安全解析分页参数
|
||||
|
||||
参数:
|
||||
default_page_size: 默认每页数量
|
||||
max_page_size: 最大每页数量
|
||||
|
||||
返回:
|
||||
tuple: (page, page_size),page>=1,1<=page_size<=max_page_size
|
||||
|
||||
示例:
|
||||
>>> parse_pagination()
|
||||
(1, 10)
|
||||
"""
|
||||
page = request.args.get('pageNo', 1, type=int)
|
||||
page_size = request.args.get('pageSize', default_page_size, type=int)
|
||||
|
||||
# 非数字、小于1、超上限等一律回退/夹取
|
||||
page = max(1, page)
|
||||
page_size = max(1, min(page_size, max_page_size))
|
||||
|
||||
return page, page_size
|
||||
```
|
||||
|
||||
使用示例:
|
||||
|
||||
```python
|
||||
def get_position_page():
|
||||
"""获取岗位分页列表"""
|
||||
page, page_size = parse_pagination()
|
||||
# 使用 page 和 page_size 进行分页查询
|
||||
# ...
|
||||
```
|
||||
|
||||
## like_escape
|
||||
|
||||
转义 SQL LIKE 通配符,防止用户输入被当作通配符。
|
||||
|
||||
```python
|
||||
from src.common.utils.request import like_escape
|
||||
|
||||
def like_escape(value, escape_char='\\'):
|
||||
"""
|
||||
转义 SQL LIKE 通配符
|
||||
|
||||
示例:
|
||||
>>> like_escape('100%')
|
||||
'100\\%'
|
||||
>>> like_escape('test_data')
|
||||
'test\\_data'
|
||||
"""
|
||||
escaped = str(value).replace(escape_char, escape_char * 2)
|
||||
escaped = escaped.replace('%', escape_char + '%')
|
||||
escaped = escaped.replace('_', escape_char + '_')
|
||||
return escaped
|
||||
```
|
||||
|
||||
## like_contains
|
||||
|
||||
构建安全的 LIKE 包含查询值(前后加 `%`)。
|
||||
|
||||
```python
|
||||
from src.common.utils.request import like_contains
|
||||
|
||||
def like_contains(value, escape_char='\\'):
|
||||
"""
|
||||
构建安全的 LIKE 包含查询值
|
||||
|
||||
SQLAlchemy 用法:
|
||||
query = query.filter(User.name.like(like_contains(name), escape='\\\\'))
|
||||
"""
|
||||
return f'%{like_escape(value, escape_char)}%'
|
||||
```
|
||||
|
||||
使用示例:
|
||||
|
||||
```python
|
||||
from src.common.utils.request import like_contains
|
||||
|
||||
def search_users(keyword):
|
||||
"""搜索用户"""
|
||||
return UserRepository.find_list(
|
||||
like_filters={'username': keyword} if keyword else None,
|
||||
)
|
||||
```
|
||||
|
||||
## get_client_ip
|
||||
|
||||
获取客户端真实 IP 地址。
|
||||
|
||||
```python
|
||||
from flask import request
|
||||
from src.common.utils.request import get_client_ip
|
||||
|
||||
def get_client_ip() -> str:
|
||||
"""
|
||||
获取客户端真实 IP 地址
|
||||
|
||||
支持反向代理(X-Forwarded-For)和直接连接
|
||||
"""
|
||||
if request.headers.get('X-Forwarded-For'):
|
||||
return request.headers.get('X-Forwarded-For').split(',')[0].strip()
|
||||
return request.remote_addr or '127.0.0.1'
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
请求解析工具函数覆盖了批量操作、模糊查询安全、分页参数等常见场景。`parse_id_list` 和 `parse_pagination` 通过防御性校验避免畸形参数导致 500 错误,`like_escape` 防止 LIKE 通配符注入,`parse_request_body` 使用 WTForms 进行参数校验。
|
||||
Reference in New Issue
Block a user