@bangdao-ai/zentao-mcp 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -141,6 +141,7 @@ pip3 install -r requirements.txt
141
141
  "env": {
142
142
  "ZENTAO_PRODUCT_ID": "365",
143
143
  "ZENTAO_OPENED_BY": "69610",
144
+ "KEY": "643f0f490d1ea0d47520dd270989c99a",
144
145
  "ZENTAO_KEYWORDS": "AI",
145
146
  "ZENTAO_TITLE_PREFIX": ""
146
147
  }
@@ -151,7 +152,8 @@ pip3 install -r requirements.txt
151
152
 
152
153
  **配置说明**:
153
154
  - `ZENTAO_PRODUCT_ID`(必需):产品ID
154
- - `ZENTAO_OPENED_BY`(必需):创建人和指派人ID(同一个人)
155
+ - `ZENTAO_OPENED_BY`(必需):创建人和指派人ID(同一个人),作为API认证的code参数
156
+ - `KEY`(必需):API认证密钥,作为API认证的key参数
155
157
  - `ZENTAO_KEYWORDS`(可选):关键词,默认为空
156
158
  - `ZENTAO_TITLE_PREFIX`(可选):标题前缀,默认为空
157
159
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bangdao-ai/zentao-mcp",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "禅道Bug管理系统MCP工具",
5
5
  "main": "index.js",
6
6
  "bin": {
package/requirements.txt CHANGED
@@ -3,4 +3,5 @@ requests>=2.31.0
3
3
  python-dotenv>=1.0.0
4
4
  fastapi>=0.104.0
5
5
  uvicorn[standard]>=0.24.0
6
+ pytest>=7.0.0
6
7
 
package/src/mcp_server.py CHANGED
@@ -203,10 +203,22 @@ class ZenTaoMCPServer:
203
203
  "enum": ["fixed", "bydesign", "reqchange", "duplicate", "external", "notrepro", "postponed", "willnotfix", "tostory", "history", "configchange"]
204
204
  },
205
205
  "resolved_by": {"type": "string", "description": "解决人(姓名或工号)"},
206
- "resolved_build": {"type": "string", "description": "解决版本ID(如果解决方案是fixed时,解决版本必传)"}
206
+ "resolved_build": {"type": "string", "description": "解决版本ID(可选,如果未提供则默认使用trunk)"}
207
207
  },
208
208
  "required": ["product_id", "bug_ids", "resolution", "resolved_by"]
209
209
  }
210
+ ),
211
+ Tool(
212
+ name="close_bug",
213
+ description="关闭BUG",
214
+ inputSchema={
215
+ "type": "object",
216
+ "properties": {
217
+ "bug_id": {"type": "string", "description": "BUG ID"},
218
+ "comment": {"type": "string", "description": "关闭备注(可选,默认AI关闭)"}
219
+ },
220
+ "required": ["bug_id"]
221
+ }
210
222
  )
211
223
  ]
212
224
 
@@ -230,7 +242,8 @@ class ZenTaoMCPServer:
230
242
  "product_id": product_id,
231
243
  "opened_by": opened_by,
232
244
  "keywords": os.getenv("ZENTAO_KEYWORDS", ""),
233
- "title_prefix": os.getenv("ZENTAO_TITLE_PREFIX", "")
245
+ "title_prefix": os.getenv("ZENTAO_TITLE_PREFIX", ""),
246
+ "role_type": os.getenv("ZENTAO_ROLE_TYPE", "")
234
247
  }
235
248
  temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False)
236
249
  json.dump(temp_config, temp_file, ensure_ascii=False)
@@ -435,6 +448,13 @@ class ZenTaoMCPServer:
435
448
  result = self.nexus.resolve_bug(product_id, bug_ids, resolution, resolved_by, resolved_build)
436
449
  return [TextContent(type="text", text=json.dumps(result, ensure_ascii=False, indent=2))]
437
450
 
451
+ elif name == "close_bug":
452
+ bug_id = arguments["bug_id"]
453
+ comment = arguments.get("comment", "AI关闭")
454
+
455
+ result = self.nexus.close_bug(bug_id, comment)
456
+ return [TextContent(type="text", text=json.dumps(result, ensure_ascii=False, indent=2))]
457
+
438
458
  else:
439
459
  raise ValueError(f"未知的工具: {name}")
440
460
 
@@ -104,13 +104,6 @@ class ZenTaoAPIClient:
104
104
  # 写死的配置
105
105
  BASE_URL = "https://zentao.bangdao-tech.com"
106
106
  TEST_BASE_URL = "https://test-zendao.bangdao-tech.com"
107
- ACCOUNT = "KUBETEST"
108
- SECRET_KEY = "aa287b7b5a4ef5d051f82fb1825ca1ac"
109
- CASE_SECRET_KEY = "db1d522a190f1135c6e7c324bd337fda"
110
- CASE_ACCOUNT = "AUTOTEST"
111
- # 新API的配置
112
- API_KEY = "1acca81c8a4d131cf7c86e772fa64351"
113
- API_CODE = "test"
114
107
 
115
108
  def __init__(self, silent=True, use_test_env=False):
116
109
  """
@@ -121,34 +114,26 @@ class ZenTaoAPIClient:
121
114
  use_test_env (bool): 是否使用测试环境
122
115
  """
123
116
  self.base_url = self.TEST_BASE_URL if use_test_env else self.BASE_URL
124
- self.account = self.ACCOUNT
125
- self.secret_key = self.SECRET_KEY
126
- self.case_secret_key = self.CASE_SECRET_KEY
127
117
  self.silent = silent
128
118
  self.use_test_env = use_test_env
129
-
130
- def generate_bdtoken(self, timestamp):
131
- """生成BDTOKEN(用于BUG创建)"""
132
- token_string = f"accountfmtamp{timestamp}{self.secret_key}"
133
- md5_hash = hashlib.md5()
134
- md5_hash.update(token_string.encode('utf-8'))
135
- return md5_hash.hexdigest()
136
-
137
- def generate_case_bdtoken(self, timestamp):
138
- """生成用例创建用的BDTOKEN"""
139
- token_string = f"accountfmtamp{timestamp}{self.case_secret_key}"
140
- md5_hash = hashlib.md5()
141
- md5_hash.update(token_string.encode('utf-8'))
142
- return md5_hash.hexdigest()
119
+
120
+ # 从环境变量读取 code 和 key
121
+ self.code = os.getenv("ZENTAO_OPENED_BY") or os.getenv("OPENED_BY")
122
+ self.key = os.getenv("ZENTAO_KEY") or os.getenv("KEY")
123
+
124
+ if not self.code:
125
+ raise ValueError("未设置环境变量 ZENTAO_OPENED_BY 或 OPENED_BY(用于 code)")
126
+ if not self.key:
127
+ raise ValueError("未设置环境变量 ZENTAO_KEY 或 KEY(用于 key)")
143
128
 
144
129
  def generate_api_token(self, timestamp):
145
- """生成新API的token(md5(code + key + time),32位小写)"""
146
- token_string = f"{self.API_CODE}{self.API_KEY}{timestamp}"
130
+ """生成统一API的token(md5(code + key + time),32位小写)"""
131
+ token_string = f"{self.code}{self.key}{timestamp}"
147
132
  md5_hash = hashlib.md5()
148
133
  md5_hash.update(token_string.encode('utf-8'))
149
134
  return md5_hash.hexdigest().lower()
150
135
 
151
- def _call_new_api(self, function_name, params=None, form_data=None):
136
+ def _call_new_api(self, function_name, params=None, form_data=None, files=None, m_param='allneedlist'):
152
137
  """
153
138
  调用新的API接口(使用code、time、token认证)
154
139
 
@@ -156,6 +141,8 @@ class ZenTaoAPIClient:
156
141
  function_name: API函数名(f参数)
157
142
  params: URL参数(字典)
158
143
  form_data: POST form-data数据(字典或列表,列表格式用于数组参数)
144
+ files: 文件上传(字典,用于文件上传接口)
145
+ m_param: m参数值(默认allneedlist,某些接口可能需要其他值如bug)
159
146
 
160
147
  Returns:
161
148
  dict: API响应结果
@@ -165,10 +152,10 @@ class ZenTaoAPIClient:
165
152
 
166
153
  api_url = f"{self.base_url}/zentaopms/www/api.php"
167
154
  url_params = {
168
- 'code': self.API_CODE,
155
+ 'code': self.code,
169
156
  'time': current_timestamp,
170
157
  'token': token,
171
- 'm': 'allneedlist',
158
+ 'm': m_param,
172
159
  'f': function_name
173
160
  }
174
161
 
@@ -180,8 +167,74 @@ class ZenTaoAPIClient:
180
167
  'User-Agent': 'Python-ZenTao-Client/1.0'
181
168
  }
182
169
 
170
+ # 打印请求信息(仅在非静默模式下)
171
+ if not self.silent:
172
+ print("\n" + "=" * 80)
173
+ print("【禅道API请求信息】")
174
+ print("=" * 80)
175
+ print(f"API URL: {api_url}")
176
+ print(f"\n【Headers】")
177
+ for key, value in headers.items():
178
+ print(f" {key}: {value}")
179
+
180
+ print(f"\n【Token生成信息】")
181
+ print(f" code: {self.code}")
182
+ print(f" key: {self.key[:10]}...{self.key[-10:] if len(self.key) > 20 else self.key}") # 只显示部分key,保护隐私
183
+ print(f" time: {current_timestamp}")
184
+ print(f" token生成公式: md5(code + key + time)")
185
+ print(f" token生成字符串: {self.code}{self.key}{current_timestamp}")
186
+ print(f" token: {token}")
187
+
188
+ print(f"\n【URL参数】")
189
+ for key, value in url_params.items():
190
+ print(f" {key}: {value}")
191
+
192
+ # 处理form_data用于显示
193
+ display_form_data = form_data
194
+ if isinstance(form_data, dict):
195
+ # 检查是否有数组类型的值
196
+ processed_data = []
197
+ for key, value in form_data.items():
198
+ if isinstance(value, (list, tuple)):
199
+ for item in value:
200
+ processed_data.append((key, str(item)))
201
+ else:
202
+ processed_data.append((key, str(value)))
203
+ display_form_data = processed_data
204
+
205
+ if display_form_data:
206
+ print(f"\n【Body (form-data)】")
207
+ if isinstance(display_form_data, list):
208
+ for item in display_form_data:
209
+ if isinstance(item, tuple):
210
+ key, value = item
211
+ # 如果是文件,只显示文件名
212
+ if hasattr(value, 'read'):
213
+ print(f" {key}: <文件对象>")
214
+ else:
215
+ print(f" {key}: {value}")
216
+ else:
217
+ print(f" {item}")
218
+ elif isinstance(display_form_data, dict):
219
+ for key, value in display_form_data.items():
220
+ print(f" {key}: {value}")
221
+
222
+ if files:
223
+ print(f"\n【Files】")
224
+ for key, file_info in files.items():
225
+ if isinstance(file_info, tuple):
226
+ filename = file_info[0] if len(file_info) > 0 else "unknown"
227
+ print(f" {key}: {filename}")
228
+ else:
229
+ print(f" {key}: {file_info}")
230
+
231
+ print("=" * 80)
232
+
183
233
  try:
184
- if form_data:
234
+ if files:
235
+ # 文件上传
236
+ response = requests.post(api_url, params=url_params, headers=headers, data=form_data, files=files, timeout=30)
237
+ elif form_data:
185
238
  # 如果form_data是列表,直接使用(用于数组参数)
186
239
  # 如果是字典,需要检查是否有数组类型的值
187
240
  if isinstance(form_data, dict):
@@ -200,9 +253,33 @@ class ZenTaoAPIClient:
200
253
  else:
201
254
  response = requests.post(api_url, params=url_params, headers=headers, timeout=30)
202
255
 
256
+ # 处理响应
203
257
  try:
204
- return response.json()
258
+ response_json = response.json()
259
+ # 打印响应信息(仅在非静默模式下)
260
+ if not self.silent:
261
+ print("\n【禅道API响应信息】")
262
+ print("=" * 80)
263
+ print(f"状态码: {response.status_code}")
264
+ print(f"响应Headers:")
265
+ for key, value in response.headers.items():
266
+ print(f" {key}: {value}")
267
+ print(f"\n响应Body (JSON):")
268
+ print(json.dumps(response_json, ensure_ascii=False, indent=2))
269
+ print("=" * 80 + "\n")
270
+ return response_json
205
271
  except json.JSONDecodeError:
272
+ # 打印响应信息(仅在非静默模式下)
273
+ if not self.silent:
274
+ print("\n【禅道API响应信息】")
275
+ print("=" * 80)
276
+ print(f"状态码: {response.status_code}")
277
+ print(f"响应Headers:")
278
+ for key, value in response.headers.items():
279
+ print(f" {key}: {value}")
280
+ print(f"\n响应Body (非JSON):")
281
+ print(response.text[:1000]) # 只显示前1000个字符
282
+ print("=" * 80 + "\n")
206
283
  return {
207
284
  "status": 0,
208
285
  "message": "fail",
@@ -211,6 +288,11 @@ class ZenTaoAPIClient:
211
288
  "raw_response": response.text
212
289
  }
213
290
  except requests.exceptions.RequestException as e:
291
+ # 打印异常信息(仅在非静默模式下)
292
+ if not self.silent:
293
+ print(f"\n【请求异常】")
294
+ print(f"错误信息: {str(e)}")
295
+ print("=" * 80 + "\n")
214
296
  return {
215
297
  "status": 0,
216
298
  "message": "fail",
@@ -299,14 +381,14 @@ class ZenTaoAPIClient:
299
381
 
300
382
  return self._call_new_api('getBugList', params=params, form_data=form_data if form_data else None)
301
383
 
302
- def resolve_bug(self, product_id, bug_ids, resolution, resolved_by, resolved_build=None):
384
+ def resolve_bug(self, product_id, bug_ids, resolution="fixed", resolved_by=None, resolved_build=None):
303
385
  """
304
386
  批量解决BUG
305
387
 
306
388
  Args:
307
389
  product_id: 产品ID(必传)
308
390
  bug_ids: BUG ID列表(必传),可以是单个ID或ID列表
309
- resolution: 解决方案(必传),可选值:
391
+ resolution: 解决方案(可选,默认fixed),可选值:
310
392
  fixed: 修复解决
311
393
  bydesign: 设计如此
312
394
  reqchange: 修改需求
@@ -319,18 +401,14 @@ class ZenTaoAPIClient:
319
401
  history: 历史遗留
320
402
  configchange: 调整配置
321
403
  resolved_by: 解决人(必传,姓名或工号)
322
- resolved_build: 解决版本ID(如果解决方案是fixed时,解决版本必传)
404
+ resolved_build: 解决版本ID(可选,如果未提供则默认使用"trunk")
323
405
 
324
406
  Returns:
325
407
  dict: 解决结果
326
408
  """
327
- if resolution == 'fixed' and not resolved_build:
328
- return {
329
- "status": 0,
330
- "message": "fail",
331
- "info": "解决方案为fixed时,解决版本(resolvedBuild)必传",
332
- "data": {}
333
- }
409
+ # 如果没有提供 resolved_build,使用默认值 "trunk"
410
+ if not resolved_build:
411
+ resolved_build = "trunk"
334
412
 
335
413
  params = {'productID': product_id}
336
414
  form_data = {
@@ -349,83 +427,38 @@ class ZenTaoAPIClient:
349
427
 
350
428
  return self._call_new_api('resolveBug', params=params, form_data=form_data)
351
429
 
352
- def create_bug_by_api(self, bug_data, use_form_data=True):
353
- """通过API创建bug"""
354
- current_timestamp = int(time.time())
355
- bdtoken = self.generate_bdtoken(current_timestamp)
356
-
357
- url = f"{self.base_url}/zentaopms/www/index.php"
358
- params = {
359
- 'm': 'allneedlist',
360
- 'f': 'createbugbyapi',
361
- 'account': self.account,
362
- 'tamp': current_timestamp
363
- }
430
+ def close_bug(self, bug_id, comment="AI关闭"):
431
+ """
432
+ 关闭BUG
364
433
 
365
- headers = {
366
- 'BDTOKEN': bdtoken,
367
- 'User-Agent': 'Python-ZenTao-Client/1.0'
368
- }
434
+ Args:
435
+ bug_id: BUG ID(必传)
436
+ comment: 关闭备注(可选,默认"AI关闭")
369
437
 
370
- try:
371
- if use_form_data:
372
- response = requests.post(url, params=params, headers=headers, data=bug_data, timeout=30)
373
- else:
374
- headers['Content-Type'] = 'application/json'
375
- response = requests.post(url, params=params, headers=headers, json=bug_data, timeout=30)
376
-
377
- try:
378
- return response.json()
379
- except json.JSONDecodeError:
380
- return {
381
- "status": -1,
382
- "message": "响应不是有效的JSON格式",
383
- "raw_response": response.text
384
- }
385
- except requests.exceptions.RequestException as e:
438
+ Returns:
439
+ dict: 关闭结果
440
+ """
441
+ params = {'bugID': bug_id}
442
+ form_data = {'comment': comment}
443
+ result = self._call_new_api('close', params=params, form_data=form_data, m_param='bug')
444
+
445
+ # 如果返回的是"Array"字符串,表示操作成功(禅道某些接口的特殊返回格式)
446
+ if isinstance(result, dict) and result.get("raw_response") == "Array":
386
447
  return {
387
- "status": -1,
388
- "message": f"请求失败: {str(e)}"
448
+ "status": 1,
449
+ "message": "success",
450
+ "info": "success"
389
451
  }
452
+
453
+ return result
454
+
455
+ def create_bug_by_api(self, bug_data, use_form_data=True):
456
+ """通过API创建bug"""
457
+ return self._call_new_api('createbugbyapi', form_data=bug_data)
390
458
 
391
459
  def create_case_by_api(self, case_data, use_form_data=True):
392
460
  """通过API创建测试用例"""
393
- current_timestamp = int(time.time())
394
- bdtoken = self.generate_case_bdtoken(current_timestamp)
395
-
396
- url = f"{self.base_url}/zentaopms/www/index.php"
397
- params = {
398
- 'm': 'allneedlist',
399
- 'f': 'createcasebyapi',
400
- 'account': self.CASE_ACCOUNT,
401
- 'tamp': current_timestamp
402
- }
403
-
404
- headers = {
405
- 'BDTOKEN': bdtoken,
406
- 'User-Agent': 'Python-ZenTao-Client/1.0'
407
- }
408
-
409
- try:
410
- if use_form_data:
411
- response = requests.post(url, params=params, headers=headers, data=case_data, timeout=30)
412
- else:
413
- headers['Content-Type'] = 'application/json'
414
- response = requests.post(url, params=params, headers=headers, json=case_data, timeout=30)
415
-
416
- try:
417
- return response.json()
418
- except json.JSONDecodeError:
419
- return {
420
- "status": -1,
421
- "message": "响应不是有效的JSON格式",
422
- "raw_response": response.text
423
- }
424
- except requests.exceptions.RequestException as e:
425
- return {
426
- "status": -1,
427
- "message": f"请求失败: {str(e)}"
428
- }
461
+ return self._call_new_api('createcasebyapi', form_data=case_data)
429
462
 
430
463
  def upload_image_to_bug(self, bug_id, image_path, custom_filename=None):
431
464
  """上传图片到BUG附件"""
@@ -435,25 +468,6 @@ class ZenTaoAPIClient:
435
468
  "message": f"图片文件不存在: {image_path}"
436
469
  }
437
470
 
438
- current_timestamp = int(datetime.now().timestamp())
439
- url = f"{self.base_url}/zentaopms/www/index.php"
440
- params = {
441
- 'm': 'allneedlist',
442
- 'f': 'uploadbyapi',
443
- 'account': self.CASE_ACCOUNT,
444
- 'tamp': current_timestamp
445
- }
446
-
447
- token_string = f"accountfmtamp{current_timestamp}{self.case_secret_key}"
448
- md5_hash = hashlib.md5()
449
- md5_hash.update(token_string.encode('utf-8'))
450
- bdtoken = md5_hash.hexdigest()
451
-
452
- headers = {
453
- 'BDTOKEN': bdtoken,
454
- 'User-Agent': 'Python-ZenTao-Client/1.0'
455
- }
456
-
457
471
  filename = custom_filename or os.path.basename(image_path)
458
472
 
459
473
  try:
@@ -461,22 +475,8 @@ class ZenTaoAPIClient:
461
475
  files = {
462
476
  'files[]': (filename, f, 'application/octet-stream')
463
477
  }
464
- data = {'id': bug_id}
465
- response = requests.post(url, params=params, headers=headers, files=files, data=data, timeout=30)
466
-
467
- try:
468
- return response.json()
469
- except json.JSONDecodeError:
470
- return {
471
- "status": -1,
472
- "message": "响应不是有效的JSON格式",
473
- "raw_response": response.text
474
- }
475
- except requests.exceptions.RequestException as e:
476
- return {
477
- "status": -1,
478
- "message": f"请求失败: {str(e)}"
479
- }
478
+ form_data = {'id': bug_id}
479
+ return self._call_new_api('uploadbyapi', form_data=form_data, files=files)
480
480
  except Exception as e:
481
481
  return {
482
482
  "status": -1,
@@ -681,6 +681,15 @@ class ZenTaoNexus:
681
681
  LOWER_BUG = "0"
682
682
  REGRESSION = "0"
683
683
 
684
+ # 职位类型到任务类型的映射
685
+ ROLE_TO_TASK_TYPE = {
686
+ "测试": "4", # 测试
687
+ "前端": "8", # 前端开发
688
+ "后端": "3", # 后端开发
689
+ "其它": "20", # 其他
690
+ "其他": "20" # 其他(兼容两种写法)
691
+ }
692
+
684
693
  def __init__(self, config_path: str = None):
685
694
  """
686
695
  初始化ZenTao Nexus工具
@@ -719,6 +728,10 @@ class ZenTaoNexus:
719
728
  env_title_prefix = os.getenv("ZENTAO_TITLE_PREFIX") or os.getenv("TITLE_PREFIX")
720
729
  self.title_prefix = env_title_prefix or self.config_loader.get("title_prefix", "")
721
730
 
731
+ # 职位类型(用于创建任务时自动设置任务类型)
732
+ env_role_type = os.getenv("ZENTAO_ROLE_TYPE") or os.getenv("ROLE_TYPE")
733
+ self.role_type = env_role_type or self.config_loader.get("role_type", "")
734
+
722
735
  # 创建API客户端
723
736
  self.client = ZenTaoAPIClient(silent=True)
724
737
 
@@ -835,7 +848,8 @@ class ZenTaoNexus:
835
848
  "product_id": self.product_id,
836
849
  "opened_by": self.opened_by,
837
850
  "keywords": self.keywords,
838
- "title_prefix": self.title_prefix
851
+ "title_prefix": self.title_prefix,
852
+ "role_type": self.role_type
839
853
  }
840
854
 
841
855
  def upload_image_to_bug(self, bug_id, image_path, custom_filename=None):
@@ -855,7 +869,43 @@ class ZenTaoNexus:
855
869
  return self.client.get_story_detail(story_id)
856
870
 
857
871
  def create_task_by_api(self, task_data):
858
- """创建任务"""
872
+ """
873
+ 创建任务
874
+
875
+ Args:
876
+ task_data: 任务数据(字典,form-data格式)
877
+ 如果 task_data 中没有 'mold' 字段,且配置了职位类型(role_type),
878
+ 则根据职位类型自动设置任务类型
879
+ 自动设置字段:
880
+ - estStarted: 当前日期(如果未指定)
881
+ - deadline: 当前日期+3天(如果未指定)
882
+ - pri: 2(如果未指定)
883
+ - openedBy: 配置的创建人(如果未指定)
884
+
885
+ Returns:
886
+ dict: 创建结果,包含taskID
887
+ """
888
+ # 如果 task_data 中没有指定 mold,且配置了职位类型,则自动设置
889
+ if 'mold' not in task_data or (task_data.get('mold') == '' or task_data.get('mold') is None):
890
+ if self.role_type and self.role_type in self.ROLE_TO_TASK_TYPE:
891
+ task_data['mold'] = self.ROLE_TO_TASK_TYPE[self.role_type]
892
+
893
+ # 自动设置预计开始时间(当前日期)
894
+ if 'estStarted' not in task_data or (task_data.get('estStarted') == '' or task_data.get('estStarted') is None):
895
+ task_data['estStarted'] = datetime.now().strftime("%Y-%m-%d")
896
+
897
+ # 自动设置预计结束时间(当前日期+3天)
898
+ if 'deadline' not in task_data or (task_data.get('deadline') == '' or task_data.get('deadline') is None):
899
+ task_data['deadline'] = (datetime.now() + timedelta(days=3)).strftime("%Y-%m-%d")
900
+
901
+ # 自动设置优先级为2
902
+ if 'pri' not in task_data or (task_data.get('pri') == '' or task_data.get('pri') is None):
903
+ task_data['pri'] = '2'
904
+
905
+ # 自动设置创建人
906
+ if 'openedBy' not in task_data or (task_data.get('openedBy') == '' or task_data.get('openedBy') is None):
907
+ task_data['openedBy'] = self.opened_by
908
+
859
909
  return self.client.create_task_by_api(task_data)
860
910
 
861
911
  def assign_task(self, task_id, assigned_to, comment=None):
@@ -868,9 +918,13 @@ class ZenTaoNexus:
868
918
  return self.client.get_bug_list(product_id, assigned_to, opened_by, resolved_by,
869
919
  confirmed, status, start_date, end_date)
870
920
 
871
- def resolve_bug(self, product_id, bug_ids, resolution, resolved_by, resolved_build=None):
921
+ def resolve_bug(self, product_id, bug_ids, resolution="fixed", resolved_by=None, resolved_build=None):
872
922
  """批量解决BUG"""
873
923
  return self.client.resolve_bug(product_id, bug_ids, resolution, resolved_by, resolved_build)
924
+
925
+ def close_bug(self, bug_id, comment="AI关闭"):
926
+ """关闭BUG"""
927
+ return self.client.close_bug(bug_id, comment)
874
928
 
875
929
 
876
930
  # 便捷函数