112 lines
4.1 KiB
Python
112 lines
4.1 KiB
Python
"""通用高级筛选器出入参(需求 2.7.1)。
|
||
|
||
请求体形如:
|
||
|
||
{
|
||
"model": "student",
|
||
"rules": [
|
||
{"field": "age", "operator": ">", "value": 25},
|
||
{"field": "gender", "operator": "=", "value": "男"},
|
||
{"logic": "OR", "sub_rules": [
|
||
{"field": "salary", "operator": ">=", "value": 15000},
|
||
{"field": "class_name", "operator": "like", "value": "Java"}
|
||
]}
|
||
],
|
||
"order_by": "age",
|
||
"order": "desc",
|
||
"page": 1,
|
||
"page_size": 10
|
||
}
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any, Literal
|
||
|
||
from pydantic import BaseModel, Field, field_validator, model_validator
|
||
|
||
from app.schema.common import SortOrder
|
||
|
||
|
||
class FilterRule(BaseModel):
|
||
"""一条规则 = 一个条件 或 一组子规则。"""
|
||
|
||
field: str | None = Field(None, description="字段名,与 sub_rules 二选一")
|
||
operator: str | None = Field(
|
||
None,
|
||
description="操作符 > < = != >= <= like in not_like not_in between is_null not_null",
|
||
)
|
||
value: Any = Field(None, description="比较值;in/not_in 传数组,between 传 [a, b]")
|
||
|
||
logic: Literal["AND", "OR", "and", "or"] | None = Field(None, description="子规则之间的逻辑,AND / OR")
|
||
sub_rules: list["FilterRule"] | None = Field(None, description="嵌套子规则,可多层")
|
||
|
||
@model_validator(mode="after")
|
||
def _check(self):
|
||
is_group = bool(self.sub_rules)
|
||
if is_group:
|
||
if self.logic is None:
|
||
self.logic = "AND"
|
||
return self
|
||
if not self.field or not self.operator:
|
||
raise ValueError("叶子规则必须同时提供 field 和 operator")
|
||
return self
|
||
|
||
@field_validator("operator")
|
||
@classmethod
|
||
def _lower(cls, v): # noqa: ANN001, ANN201
|
||
return v.strip().lower() if isinstance(v, str) else v
|
||
|
||
|
||
FilterRule.model_rebuild()
|
||
|
||
|
||
class QueryRequest(BaseModel):
|
||
model: str = Field("student", description="目标模型:student / class / teacher / score / employment / advisor")
|
||
rules: list[FilterRule] = Field(default_factory=list, description="筛选规则树")
|
||
order_by: str | None = Field(None, description="排序字段(同样受白名单限制)")
|
||
order: SortOrder = Field(SortOrder.ASC, description="排序方向 asc / desc")
|
||
page: int = Field(1, ge=1)
|
||
page_size: int = Field(10, ge=1, le=200)
|
||
fields: list[str] | None = Field(None, description="指定返回字段,不传返回全部可读字段")
|
||
with_explain: bool = Field(True, description="是否返回规则解析后的说明,方便调试规则")
|
||
|
||
|
||
class MetricSpec(BaseModel):
|
||
func: Literal["count", "count_distinct", "avg", "sum", "max", "min"] = Field(..., description="聚合函数")
|
||
field: str | None = Field(None, description="聚合字段;count 时可省略")
|
||
alias: str | None = Field(None, description="结果列名")
|
||
|
||
|
||
class AggregationParams(BaseModel):
|
||
model: str = Field(..., description="目标模型")
|
||
group_by: list[str] = Field(default_factory=list, description="分组字段,可为空=整体聚合")
|
||
metrics: list[MetricSpec] = Field(..., min_length=1, description="聚合指标")
|
||
rules: list[FilterRule] = Field(default_factory=list, description="筛选规则树")
|
||
having: list[FilterRule] = Field(default_factory=list, description="对聚合结果的过滤(HAVING)")
|
||
order_by: str | None = Field(None, description="按结果列名排序")
|
||
order: SortOrder = SortOrder.DESC
|
||
limit: int = Field(50, ge=1, le=500)
|
||
|
||
|
||
class AggregationResult(BaseModel):
|
||
columns: list[str] = Field(default_factory=list)
|
||
rows: list[dict[str, Any]] = Field(default_factory=list)
|
||
total: int = 0
|
||
sql: str | None = Field(None, description="实际执行的 SQL(已参数化,便于讲解与排查)")
|
||
|
||
|
||
class FieldMeta(BaseModel):
|
||
field: str
|
||
type: str
|
||
desc: str
|
||
sortable: bool = True
|
||
filterable: bool = True
|
||
|
||
|
||
class ModelMeta(BaseModel):
|
||
model: str
|
||
label: str
|
||
fields: list[FieldMeta] = Field(default_factory=list)
|
||
relations: list[str] = Field(default_factory=list, description="可通过 JOIN 使用的关联字段")
|