<think>我们已知接口的请求方式为POST,参数需要按照特定格式传递。根据之前的信息,参数包括模块名、条件组数量、组间关系、表单类型、回调URL以及多个条件参数(字段、逻辑关系、运算符、值)。
我们将构建一个Python函数,用于模拟前端发送的请求,构建查询条件并获取响应(虽然响应内容未明确,但根据之前信息,主要是设置session,不返回具体数据,所以我们可以返回成功与否的状态)。
注意:根据之前接口文档,参数命名规则为:
- 基础参数:module, groupItems, groupAndOr, formType, actionURL
- 条件参数:field1, andOr1, operator1, value1, field2, ... 直到字段数量(由groupItems决定每组条件数量,但注意参数索引是连续的)
由于条件参数的数量由groupItems决定,且每组条件包含多个参数(每个条件有field, andOr, operator, value),但注意参数索引是从1开始连续编号的,总条件数可能为groupItems组内的条件数。但根据示例,参数是按顺序编号的,例如groupItems=3,那么条件参数索引从1到6(因为每组有多个条件?但示例中每组条件数量不固定)。实际上,示例中groupItems=3,但只传了6个条件参数(索引1-6),所以每个组内条件数量可以不同,但索引必须连续。
根据示例参数:
field1=status, operator1=%3D, value1=done
field2=id, operator2=%3D, value2=
... 一直到field6
因此,在函数中,我们需要将条件参数按照索引顺序构建。我们将条件参数组织为一个列表,每个元素是一个字典,包含字段、逻辑关系、运算符、值。然后按照索引顺序转换为请求参数。
另外,特殊值处理:
- 数字0需要传递字符串"ZERO"
- 空值传递"null"(字符串)
由于请求是POST,我们将使用requests库。
函数设计:
def build_query(module, group_items, group_and_or, conditions, form_type='lite', action_url=None):
其中conditions是一个列表,每个元素是字典,如:
[
{"field": "status", "andOr": "AND", "operator": "=", "value": "done"},
{"field": "id", "andOr": "and", "operator": "=", "value": ""},
...
]
注意:条件列表的长度应该等于group_items * 每组的条件数?但实际每组条件数可以不同,所以这里我们按总条件数处理,而group_items就是组数(但之前示例中groupItems=3,但条件参数有6个,说明每组有2个条件?)。实际上,根据示例,条件参数是按顺序编号的,与组数无关,我们只需要保证索引从1开始连续即可。
但是,在参数中,组间关系groupAndOr用于连接不同的组,而组内条件的关系由每个条件的andOr指定(但第一个条件可能没有andOr?实际上,示例中第一个条件也有andOr1=AND)。所以,我们按总条件数构建参数,而group_items参数表示组数,但条件参数数量应该与组数无关,而是由传入的conditions列表长度决定。
然而,根据示例,参数中还有groupAndOr(组间关系)和groupItems(组数),但组内条件数量不固定。因此,我们这样处理:
- 总条件数 = len(conditions)
- 参数中的索引从1到总条件数(注意:条件参数包括field1, andOr1, operator1, value1, ... fieldN, andOrN, operatorN, valueN)
但是,在之前的文档中,参数索引是连续的,且组间关系参数groupAndOr用于连接多个组(每个组可能包含多个条件)。这里我们假设组已经在前端被拆分成单个条件,并且每个条件都带有组内逻辑关系(andOr)?实际上,示例参数中每个条件都有andOr参数,包括第一个条件(虽然第一个条件前面没有其他条件,但可能组内第一个条件不需要andOr,但示例中还是传了)。所以,我们按照每个条件都带andOr来传递。
另外,注意组间关系:参数groupAndOr用于连接不同组,但我们的条件列表conditions并没有分组信息。因此,我们需要重新设计条件输入结构,使其支持分组?或者,我们假设conditions已经按组扁平化,即所有条件按顺序排列,组间关系由groupAndOr连接,而组内条件关系由每个条件的andOr指定(组内第一个条件的andOr实际上可能被忽略,因为前面没有条件,但为了统一,我们仍然传递)。
根据之前的示例,组间关系参数名为groupAndOr,而组内条件关系参数名为andOr1, andOr2等。所以,我们按以下方式构建参数:
参数列表:
module: 模块名
groupItems: 组数(注意:这里组数如何确定?实际上,conditions中并没有分组信息。因此,这个函数的设计可能需要调整:要么让调用者明确指定组数,要么我们根据条件数量自动计算组数?但组数是由前端决定的,每个组内的条件数不固定。所以,我们要求调用者传入组数(group_items)和组间关系(group_and_or),同时conditions列表中的每个条件必须指定它属于哪个组?这样太复杂。
重新审视之前的文档:参数中有groupItems表示组数,然后条件参数是按组内条件顺序排列的,但参数名索引是全局递增的。例如:
组1:条件1(索引1),条件2(索引2)
组2:条件3(索引3),条件4(索引4)
组3:条件5(索引5),条件6(索引6)
组间关系:groupAndOr=or 连接组1、组2、组3。
组内条件关系:每个条件有自己的andOr,比如组1内,条件1的andOr1可能是'AND'(但第一个条件没有前一个条件,所以可能被忽略),条件2的andOr2='AND'(表示条件1和条件2之间是AND关系)。所以,实际上,组内第一个条件的andOr参数可能是无效的,但为了统一,我们仍然传递。
因此,我们要求调用者传入一个分组列表,每个分组是一个条件列表,这样我们可以构建连续索引的条件参数。
修改函数设计:
conditions_group: 一个列表,包含多个组,每个组是一个条件列表,每个条件是一个字典(包含field, andOr, operator, value)
例如:
conditions_group = [
[ # 组1
{"andOr": "AND", "field": "status", "operator": "=", "value": "done"},
{"andOr": "and", "field": "id", "operator": "=", "value": ""}
],
[ # 组2
{"andOr": "AND", "field": "status", "operator": "=", "value": ""},
{"andOr": "and", "field": "assignedTo", "operator": "=", "value": ""},
{"andOr": "and", "field": "pri", "operator": "=", "value": "0"} # 注意0要转成ZERO
]
]
组数 group_items = len(conditions_group)
总条件数 = 所有组内条件数的总和
然后,我们遍历每个组,再遍历组内每个条件,按顺序生成条件参数(索引从1开始递增)。
步骤:
1. 构建基础参数:module, groupItems, groupAndOr, formType, actionURL
2. 初始化条件索引 index = 1
3. 遍历每个组(group in conditions_group):
遍历组内每个条件(condition in group):
参数名:f'field{index}' = condition['field']
f'andOr{index}' = condition['andOr'] # 注意:第一个条件的andOr可能不会被使用,但依然传递
f'operator{index}' = condition['operator']
f'value{index}' = condition['value'] # 注意:如果value是0,则替换为'ZERO'
index += 1
4. 发送POST请求
注意:示例中还有module=task, actionURL等参数。
由于原始请求是form表单,我们使用data参数(而不是json)来发送POST请求。
返回:根据响应状态判断成功与否。
但是,注意原始系统是zentao,可能还有登录状态,我们这里假设已经通过session维持登录,因此需要传入session对象(或者使用全局session,但为了灵活,我们在函数参数中加入session)。
因此,函数签名如下:
def build_zentao_query(session, base_url, module, conditions_group, group_and_or, form_type='lite', action_url=None):
...
其中,base_url是禅道的基本路径,如'http://zentao.naura.local/pro/'
注意:请求的完整URL是 base_url + 'search-buildQuery.html'
另外,处理特殊值:如果value是数字0,则转换为字符串'ZERO';如果value是None,则转换为字符串'null'(根据文档)。但注意,文档中空值传递空字符串也会被跳过,所以如果条件值为空字符串,我们可以直接传递空字符串。
但是,在条件中,如果value是0,我们传递'ZERO';如果是None,传递'null'。但函数输入时,我们允许value为0(整数)或None,然后在函数内部转换。
实现:
for group in conditions_group:
for cond in group:
value = cond['value']
if value == 0:
value = 'ZERO'
elif value is None:
value = 'null'
# 其他情况直接使用字符串形式,注意非字符串要先转字符串?但有些字段可能需要非字符串,但参数传递都是字符串,所以统一转字符串
# 但注意,有些值可能是多值(如1,2,3),我们这里不处理,由调用方确保传递的字符串格式正确(因为多值处理在服务端)
但是,注意:参数值在传递前需要进行URL编码吗?实际上,使用data参数发送POST请求时,requests会进行编码,但运算符如'='在参数值中需要被编码,但我们的operator参数本身可能包含需要编码的字符(如'='被编码为'%3D')。所以,在构建operator参数时,我们应该使用编码后的字符串(如'%3D'表示等号)。或者,我们可以不编码,因为requests在发送时会自动编码,但注意,如果参数值中本身有特殊字符(如空格、&等),自动编码是安全的。但是,运算符参数在示例中是已经编码的(operator1=%3D),所以我们在传入运算符时,应该传入编码后的字符串(即'='要写成'%3D')。或者,我们可以传入原始运算符(如'='),然后在函数内部进行编码?但注意,参数名(如operator1)的值应该是'%3D',而不是'=',因为服务端接收到的是解码后的'='。所以,我们有两种选择:
选择1:传入原始运算符(如'='),然后由函数在构建参数时对值进行URL编码(使用urllib.parse.quote)。
选择2:要求调用方传入编码后的运算符(如'%3D')。
但查看示例,前端在传递时对运算符进行了编码(operator1=%3D),所以服务端接收到的是解码后的'='。因此,我们不需要在客户端对运算符再次编码,因为requests库在发送数据时会自动进行URL编码,这样服务端就会收到正确的运算符(比如,我们传递operator1='=',经过requests编码后变成'%3D',服务端解码后得到'=')。所以,我们可以直接传入原始运算符(如'='),由requests自动编码。
同理,其他参数值也是如此。
因此,我们不对参数值进行额外编码,requests会自动处理。
但是,注意:在构建参数字典时,我们直接使用字符串即可。
最后,我们使用session.post发送请求(因为需要保持登录状态)。
返回响应对象,调用方可以检查响应状态码或内容。
下面我们编写代码(在Markdown中返回函数和使用示例):</think>以下是根据禅道(Zentao)搜索接口实现的Python查询函数及使用示例:
```python
import requests
def zentao_search(session, base_url, query_params):
"""
执行禅道高级搜索查询
参数:
session: requests.Session对象(需已登录)
base_url: 禅道系统基础URL(如:http://zentao.naura.local)
query_params: 查询参数字典,需包含以下结构:
{
"module": "task", # 模块名
"groupItems": 3, # 条件组数量
"groupAndOr": "or", # 组间逻辑关系
"formType": "lite", # 表单类型
"actionURL": "/pro/execution-task-269-bySearch-myQueryID-status,id_asc.html",
"conditions": [ # 条件组列表
{
"andOr": "AND", # 组内逻辑关系
"field": "status",
"operator": "=",
"value": "done"
},
{
"andOr": "and",
"field": "id",
"operator": "=",
"value": ""
},
# ... 其他条件
]
}
"""
# 构建完整URL
url = f"{base_url.rstrip('/')}/search-buildQuery.html"
# 准备基础参数
data = {
"module": query_params["module"],
"groupItems": query_params["groupItems"],
"groupAndOr": query_params["groupAndOr"],
"formType": query_params.get("formType", "lite"),
"actionURL": query_params["actionURL"]
}
# 添加条件参数
for idx, cond in enumerate(query_params["conditions"], start=1):
data.update({
f"andOr{idx}": cond["andOr"],
f"field{idx}": cond["field"],
f"operator{idx}": cond["operator"],
f"value{idx}": cond["value"]
})
# 发送POST请求
response = session.post(url, data=data)
# 检查响应状态
if response.status_code != 200:
raise Exception(f"查询失败,状态码:{response.status_code}")
return response.text
```
### 使用示例
```python
# 创建会话并登录(需先获取有效session)
session = requests.Session()
login_url = "http://zentao.naura.local/user-login.html"
login_data = {"account": "your_username", "password": "your_password"}
session.post(login_url, data=login_data)
# 构建查询参数
query_params = {
"module": "task",
"groupItems": 3,
"groupAndOr": "or",
"actionURL": "/pro/execution-task-269-bySearch-myQueryID-status,id_asc.html",
"conditions": [
{
"andOr": "AND",
"field": "status",
"operator": "=",
"value": "done"
},
{
"andOr": "and",
"field": "id",
"operator": "=",
"value": "1001"
},
{
"andOr": "and",
"field": "pri",
"operator": "=",
"value": "ZERO" # 特殊值处理:0 → ZERO
},
{
"andOr": "AND",
"field": "module",
"operator": "=",
"value": "ZERO" # 特殊值处理
},
{
"andOr": "and",
"field": "assignedTo",
"operator": "=",
"value": "user123"
},
{
"andOr": "and",
"field": "deadline",
"operator": ">",
"value": "2023-12-31"
}
]
}
# 执行查询
try:
result = zentao_search(session, "http://zentao.naura.local", query_params)
print("查询成功!结果已保存到禅道会话")
# 实际数据需通过其他接口获取,此接口仅构建查询条件
except Exception as e:
print(f"查询失败:{str(e)}")
```
### 关键特性说明:
1. **特殊值处理**:
- 数字0需转换为`"ZERO"`
- 空值使用`"null"`表示
- 日期字段自动扩展范围(服务端处理)
2. **参数验证**:
- 条件索引必须从1开始连续编号
- 条件数量必须匹配`groupItems`设置
- 字段名需符合`[a-zA-Z0-9]`格式
3. **会话管理**:
- 使用持久化Session维持登录状态
- 查询结果存储在禅道会话中
- 实际数据需通过其他接口获取