# WTForms Schema规范 ## 概述 项目使用 WTForms 进行请求数据校验。所有请求体参数通过 WTForms 表单类定义,框架在调用 Router 前自动完成解析和验证。 :::tip 核心原则 - 请求体参数必须声明为 WTForms 表单类(Form 类) - 字段约束使用 `validators` 定义 - 自定义校验使用 `validate_` 方法 - 唯一性校验在 Schema 中查询数据库 ::: ## 命名规范 | 类型 | 命名格式 | 示例 | |------|---------|------| | 创建/编辑表单 | `{模块}Form` | `PositionForm` | | 状态更新表单 | `{模块}StatusForm` | `PositionStatusForm` | | 查询参数表单 | `{模块}QueryForm` | `PositionQueryForm` | :::tip 继承 BaseForm 创建/编辑表单继承 `src.common.forms.base_form.BaseForm`,自动获得 `id` 字段(编辑时必填)。 ::: ## PositionForm 示例 以岗位模块为例,`src/modules/system/position/schema.py` 完整内容: ```python from wtforms import StringField, IntegerField from wtforms.validators import DataRequired, Length, NumberRange from src.common.forms.base_form import BaseForm class PositionForm(BaseForm): """岗位创建/编辑表单""" name = StringField('岗位名称', validators=[ DataRequired(message='岗位名称不能为空'), Length(min=1, max=150, message='岗位名称长度必须在1-150个字符之间') ]) status = IntegerField('岗位状态', validators=[ DataRequired(message='岗位状态不能为空'), NumberRange(min=1, max=2, message='岗位状态必须是1或2') ]) sort = IntegerField('岗位排序', validators=[ DataRequired(message='岗位排序不能为空'), NumberRange(min=0, max=99999, message='岗位排序必须在0-99999之间') ]) class PositionStatusForm(BaseForm): """岗位状态更新表单""" id = IntegerField('岗位ID', validators=[ DataRequired(message='岗位ID不能为空'), NumberRange(min=1, message='岗位ID必须大于0') ]) status = IntegerField('岗位状态', validators=[ DataRequired(message='岗位状态不能为空'), NumberRange(min=1, max=2, message='岗位状态必须是1或2') ]) ``` :::tip BaseForm 基类 `BaseForm`(定义在 `src/common/forms/base_form.py`)提供 `id` 字段和数据清洗功能,自动将前端传入的空字符串 `''`/`'null'`/`'undefined'` 转为 `None`,避免 WTForms 解析报错。 ::: ## Validator 约束 | Validator | 类型 | 说明 | 示例 | |------|------|------|------| | `DataRequired` | any | 必填字段 | `DataRequired(message='不能为空')` | | `Length` | str | 字符串长度 | `Length(min=1, max=150)` | | `NumberRange` | int/float | 数值范围 | `NumberRange(min=0, max=99999)` | | `Optional` | any | 可选字段 | `Optional()` | | `Email` | str | 邮箱格式 | `Email(message='邮箱格式不正确')` | | `URL` | str | URL 格式 | `URL(message='URL格式不正确')` | | `Regexp` | str | 正则表达式 | `Regexp(r'^[a-zA-Z]+$')` | :::tip Validator 说明 - `DataRequired`:必填字段,不允许空值 - `Length`:字符串长度限制 - `NumberRange`:数值范围限制 - `Optional`:可选字段 ::: ## 自定义校验器 ### 唯一性校验 ```python from wtforms import ValidationError from src.modules.system.position.repository import PositionRepository class PositionForm(BaseForm): """岗位创建/编辑表单""" name = StringField('岗位名称', validators=[ DataRequired(message='岗位名称不能为空'), Length(min=1, max=150, message='岗位名称长度必须在1-150个字符之间') ]) def validate_name(self, field): """岗位名称唯一性校验""" exclude_id = self.id.data if self.id.data else None if PositionRepository.exists_by([Position.name == field.data], exclude_id): raise ValidationError('岗位名称不能重复') ``` ### 格式校验 ```python from wtforms import ValidationError import re class UserForm(BaseForm): """用户创建/编辑表单""" username = StringField('用户名', validators=[ DataRequired(message='用户名不能为空'), Length(min=3, max=50, message='用户名长度必须在3-50个字符之间') ]) email = StringField('邮箱', validators=[ DataRequired(message='邮箱不能为空'), Length(max=100, message='邮箱长度不能超过100个字符') ]) phone = StringField('手机号', validators=[ Optional(), Length(max=20, message='手机号长度不能超过20个字符') ]) def validate_username(self, field): """用户名只允许字母、数字、下划线""" if not re.match(r'^[a-zA-Z0-9_]+$', field.data): raise ValidationError('用户名只允许字母、数字、下划线') def validate_email(self, field): """邮箱格式校验""" if not re.match(r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$', field.data): raise ValidationError('邮箱格式不正确') def validate_phone(self, field): """手机号格式校验(可选字段,有值时校验)""" if field.data and field.data != '': if not re.match(r'^1[3-9]\d{9}$', field.data): raise ValidationError('手机号格式不正确') ``` :::tip validator 规则 - 方法名必须为 `validate_{field_name}` - 校验失败抛出 `ValidationError(错误消息)` - 可选字段需判断 `None` 值 ::: ## 在 Router 中使用 ```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) ``` :::tip 数据访问 Service 层通过 `data['field']` 直接访问字段。WTForms 在进入 Router 之前已完成校验,Service 无需重复验证。 ::: ## 错误响应 WTForms 校验失败时,由框架自动转换: ```json { "code": 1, "data": null, "msg": "岗位名称不能为空", "ok": false } ``` ## 总结 WTForms Schema 规范要求:表单类命名 `{Module}Form`,继承 `BaseForm`;字段约束用 `validators` 定义长度/范围;自定义校验用 `validate_{field_name}` 方法抛 `ValidationError`;唯一性校验通过 `Repository.exists_by(filters, exclude_id)` 实现。框架自动解析和验证,Service 通过 `form.data['field']` 访问已校验的数据。