task2-phase2: 链接解析与TAPD API
This commit is contained in:
parent
bf4f346096
commit
397c14faee
@ -1,4 +1,4 @@
|
||||
{
|
||||
"access_token": "1vVdzrLNZahtARUianAypD-WjGnSvHzU9_p0vXE2wmawFj3WigMA-eZZ1W-t0Tki2KWIs_yYrcrTAInDngJcS-_uqupCNlgEYUjL1bMeS2GGnNM1e3spBT9XdUjF8yTvP3XXtwrlY_qS9Se2S09GQEk1VLxnou1okTjmnzdaNQJbTg013-R_uUp3E-CyNFy7t1tQHN87tr9l2GzlzGt6EshjNcJq4COuCgbs5wBA298",
|
||||
"fetch_time": 1767787715.7755494
|
||||
"access_token": "GKRSU-6KIm-q1lKGtlImu8cSa1HpyEJLFlYq9FS-Kqfc-T9cNd25A07qTG7BhaCbZS7eii7nselgxqWoczbj3zg8f-t_jCAILpJ2uCwZSXwQBLasB0c5SedemVyA59n4rK7fwqHMOd9j1LFVtxgjfnAcQeIv6xacYE9ZLVppImHBL-ZOl2yb8NCv43j51CzEOGEryTjLH4AhNAREXKYm1mQ64cIYD3vXuLpcAG9f-cU",
|
||||
"fetch_time": 1767852793.0838747
|
||||
}
|
||||
@ -1,15 +1,583 @@
|
||||
{
|
||||
"records": [],
|
||||
{
|
||||
"api_type": "test",
|
||||
"operation": "task2/setup_test",
|
||||
"timestamp": "2026-01-07 20:14:18",
|
||||
"success": true,
|
||||
"request": {
|
||||
"test": "验证脚本测试"
|
||||
},
|
||||
"response": {
|
||||
"status": "success"
|
||||
}
|
||||
}
|
||||
{
|
||||
"records": [],
|
||||
{
|
||||
"api_type": "test",
|
||||
"operation": "task2/setup_test",
|
||||
"timestamp": "2026-01-07 20:14:18",
|
||||
"success": true,
|
||||
"request": {
|
||||
"test": "验证脚本测试"
|
||||
},
|
||||
"response": {
|
||||
"status": "success"
|
||||
}
|
||||
}
,
|
||||
{
|
||||
"api_type": "tapd",
|
||||
"operation": "stories",
|
||||
"timestamp": "2026-01-07 20:48:56",
|
||||
"success": true,
|
||||
"request": {
|
||||
"url": "https://tapd-api.bilibili.co/tapd/stories",
|
||||
"method": "GET",
|
||||
"params": {
|
||||
"workspace_id": "58335167",
|
||||
"id": "1158335167004800796"
|
||||
},
|
||||
"auth_user": "g41_tapd"
|
||||
},
|
||||
"response": {
|
||||
"status": 1,
|
||||
"data": [
|
||||
{
|
||||
"Story": {
|
||||
"id": "1158335167004800796",
|
||||
"workitem_type_id": "1158335167001002451",
|
||||
"app_id": "1",
|
||||
"name": "12月版本性能优化",
|
||||
"description": "<p><span style=\"color: #3f4a56;\">【设计文档链接】</span><br /></p><p><br /></p><p>【需求文档链接】</p>",
|
||||
"workspace_id": "58335167",
|
||||
"creator": "星渊",
|
||||
"created": "2025-12-19 15:24:53",
|
||||
"modified": "2025-12-19 18:04:08",
|
||||
"status": "status_13",
|
||||
"step": "",
|
||||
"owner": "星渊;",
|
||||
"cc": "",
|
||||
"begin": null,
|
||||
"due": null,
|
||||
"size": null,
|
||||
"priority": "4",
|
||||
"developer": "星渊;",
|
||||
"iteration_id": "0",
|
||||
"test_focus": "",
|
||||
"type": "",
|
||||
"source": "",
|
||||
"module": "",
|
||||
"version": "",
|
||||
"completed": null,
|
||||
"category_id": "1158335167001017101",
|
||||
"path": "1158335167004800796:",
|
||||
"parent_id": "0",
|
||||
"children_id": "||1158335167004800798|1158335167004800799|1158335167004800802|1158335167004800828",
|
||||
"ancestor_id": "1158335167004800796",
|
||||
"level": "0",
|
||||
"business_value": null,
|
||||
"effort": null,
|
||||
"effort_completed": "0",
|
||||
"exceed": "0",
|
||||
"remain": "0",
|
||||
"release_id": "0",
|
||||
"bug_id": "0",
|
||||
"templated_id": "1158335167001006075",
|
||||
"created_from": null,
|
||||
"feature": "",
|
||||
"label": "",
|
||||
"progress": "100",
|
||||
"is_archived": "0",
|
||||
"tech_risk": null,
|
||||
"flows": null,
|
||||
"custom_field_one": "星渊;",
|
||||
"custom_field_two": "",
|
||||
"custom_field_three": "",
|
||||
"custom_field_four": "",
|
||||
"custom_field_five": "",
|
||||
"custom_field_six": "",
|
||||
"custom_field_seven": "",
|
||||
"custom_field_eight": "",
|
||||
"secret_root_id": "0",
|
||||
"progress_manual": "0",
|
||||
"custom_field_9": "",
|
||||
"custom_field_10": "",
|
||||
"custom_field_11": "",
|
||||
"custom_field_12": "",
|
||||
"custom_field_13": "",
|
||||
"custom_field_14": "",
|
||||
"custom_field_15": "",
|
||||
"custom_field_16": "",
|
||||
"custom_field_17": "",
|
||||
"custom_field_18": "",
|
||||
"custom_field_19": "",
|
||||
"custom_field_20": "",
|
||||
"custom_field_21": "",
|
||||
"custom_field_22": "",
|
||||
"custom_field_23": "",
|
||||
"custom_field_24": "",
|
||||
"custom_field_25": "",
|
||||
"custom_field_26": "",
|
||||
"custom_field_27": "",
|
||||
"custom_field_28": "",
|
||||
"custom_field_29": "",
|
||||
"custom_field_30": "",
|
||||
"custom_field_31": "",
|
||||
"custom_field_32": "",
|
||||
"custom_field_33": "",
|
||||
"custom_field_34": "",
|
||||
"custom_field_35": "",
|
||||
"custom_field_36": "",
|
||||
"custom_field_37": "",
|
||||
"custom_field_38": "",
|
||||
"custom_field_39": "",
|
||||
"custom_field_40": "",
|
||||
"custom_field_41": "",
|
||||
"custom_field_42": "",
|
||||
"custom_field_43": "",
|
||||
"custom_field_44": "",
|
||||
"custom_field_45": "",
|
||||
"custom_field_46": "",
|
||||
"custom_field_47": "",
|
||||
"custom_field_48": "",
|
||||
"custom_field_49": "",
|
||||
"custom_field_50": "",
|
||||
"custom_field_51": "",
|
||||
"custom_field_52": "",
|
||||
"custom_field_53": "",
|
||||
"custom_field_54": "",
|
||||
"custom_field_55": "",
|
||||
"custom_field_56": "",
|
||||
"custom_field_57": "",
|
||||
"custom_field_58": "",
|
||||
"custom_field_59": "",
|
||||
"custom_field_60": "",
|
||||
"custom_field_61": "",
|
||||
"custom_field_62": "",
|
||||
"custom_field_63": "",
|
||||
"custom_field_64": "",
|
||||
"custom_field_65": "",
|
||||
"custom_field_66": "",
|
||||
"custom_field_67": "",
|
||||
"custom_field_68": "",
|
||||
"custom_field_69": "",
|
||||
"custom_field_70": "",
|
||||
"custom_field_71": "",
|
||||
"custom_field_72": "",
|
||||
"custom_field_73": "",
|
||||
"custom_field_74": "",
|
||||
"custom_field_75": "",
|
||||
"custom_field_76": "",
|
||||
"custom_field_77": "",
|
||||
"custom_field_78": "",
|
||||
"custom_field_79": "",
|
||||
"custom_field_80": "",
|
||||
"custom_field_81": "",
|
||||
"custom_field_82": "",
|
||||
"custom_field_83": "",
|
||||
"custom_field_84": "",
|
||||
"custom_field_85": "",
|
||||
"custom_field_86": "",
|
||||
"custom_field_87": "",
|
||||
"custom_field_88": "",
|
||||
"custom_field_89": "",
|
||||
"custom_field_90": "",
|
||||
"custom_field_91": "",
|
||||
"custom_field_92": "",
|
||||
"custom_field_93": "",
|
||||
"custom_field_94": "",
|
||||
"custom_field_95": "",
|
||||
"custom_field_96": "",
|
||||
"custom_field_97": "",
|
||||
"custom_field_98": "",
|
||||
"custom_field_99": "",
|
||||
"custom_field_100": "",
|
||||
"custom_field_101": "",
|
||||
"custom_field_102": "",
|
||||
"custom_field_103": "",
|
||||
"custom_field_104": "",
|
||||
"custom_field_105": "",
|
||||
"custom_field_106": "",
|
||||
"custom_field_107": "",
|
||||
"custom_field_108": "",
|
||||
"custom_field_109": "",
|
||||
"custom_field_110": "",
|
||||
"custom_field_111": "",
|
||||
"custom_field_112": "",
|
||||
"custom_field_113": "",
|
||||
"custom_field_114": "",
|
||||
"custom_field_115": "",
|
||||
"custom_field_116": "",
|
||||
"custom_field_117": "",
|
||||
"custom_field_118": "",
|
||||
"custom_field_119": "",
|
||||
"custom_field_120": "",
|
||||
"custom_field_121": "",
|
||||
"custom_field_122": "",
|
||||
"custom_field_123": "",
|
||||
"custom_field_124": "",
|
||||
"custom_field_125": "",
|
||||
"custom_field_126": "",
|
||||
"custom_field_127": "",
|
||||
"custom_field_128": "",
|
||||
"custom_field_129": "",
|
||||
"custom_field_130": "",
|
||||
"custom_field_131": "",
|
||||
"custom_field_132": "",
|
||||
"custom_field_133": "",
|
||||
"custom_field_134": "",
|
||||
"custom_field_135": "",
|
||||
"custom_field_136": "",
|
||||
"custom_field_137": "",
|
||||
"custom_field_138": "",
|
||||
"custom_field_139": "",
|
||||
"custom_field_140": "",
|
||||
"custom_field_141": "",
|
||||
"custom_field_142": "",
|
||||
"custom_field_143": "",
|
||||
"custom_field_144": "",
|
||||
"custom_field_145": "",
|
||||
"custom_field_146": "",
|
||||
"custom_field_147": "",
|
||||
"custom_field_148": "",
|
||||
"custom_field_149": "",
|
||||
"custom_field_150": "",
|
||||
"custom_field_151": "",
|
||||
"custom_field_152": "",
|
||||
"custom_field_153": "",
|
||||
"custom_field_154": "",
|
||||
"custom_field_155": "",
|
||||
"custom_field_156": "",
|
||||
"custom_field_157": "",
|
||||
"custom_field_158": "",
|
||||
"custom_field_159": "",
|
||||
"custom_field_160": "",
|
||||
"custom_field_161": "",
|
||||
"custom_field_162": "",
|
||||
"custom_field_163": "",
|
||||
"custom_field_164": "",
|
||||
"custom_field_165": "",
|
||||
"custom_field_166": "",
|
||||
"custom_field_167": "",
|
||||
"custom_field_168": "",
|
||||
"custom_field_169": "",
|
||||
"custom_field_170": "",
|
||||
"custom_field_171": "",
|
||||
"custom_field_172": "",
|
||||
"custom_field_173": "",
|
||||
"custom_field_174": "",
|
||||
"custom_field_175": "",
|
||||
"custom_field_176": "",
|
||||
"custom_field_177": "",
|
||||
"custom_field_178": "",
|
||||
"custom_field_179": "",
|
||||
"custom_field_180": "",
|
||||
"custom_field_181": "",
|
||||
"custom_field_182": "",
|
||||
"custom_field_183": "",
|
||||
"custom_field_184": "",
|
||||
"custom_field_185": "",
|
||||
"custom_field_186": "",
|
||||
"custom_field_187": "",
|
||||
"custom_field_188": "",
|
||||
"custom_field_189": "",
|
||||
"custom_field_190": "",
|
||||
"custom_field_191": "",
|
||||
"custom_field_192": "",
|
||||
"custom_field_193": "",
|
||||
"custom_field_194": "",
|
||||
"custom_field_195": "",
|
||||
"custom_field_196": "",
|
||||
"custom_field_197": "",
|
||||
"custom_field_198": "",
|
||||
"custom_field_199": "",
|
||||
"custom_field_200": "",
|
||||
"custom_plan_field_1": "1158335167001033687",
|
||||
"custom_plan_field_2": "0",
|
||||
"custom_plan_field_3": "0",
|
||||
"custom_plan_field_4": "0",
|
||||
"custom_plan_field_5": "0",
|
||||
"custom_plan_field_6": "0",
|
||||
"custom_plan_field_7": "0",
|
||||
"custom_plan_field_8": "0",
|
||||
"custom_plan_field_9": "0",
|
||||
"custom_plan_field_10": "0",
|
||||
"priority_label": "High"
|
||||
}
|
||||
}
|
||||
],
|
||||
"info": "success"
|
||||
}
|
||||
}
,
|
||||
{
|
||||
"api_type": "tapd",
|
||||
"operation": "stories",
|
||||
"timestamp": "2026-01-07 20:50:30",
|
||||
"success": true,
|
||||
"request": {
|
||||
"url": "https://tapd-api.bilibili.co/tapd/stories",
|
||||
"method": "GET",
|
||||
"params": {
|
||||
"workspace_id": "58335167",
|
||||
"id": "1158335167004774712"
|
||||
},
|
||||
"auth_user": "g41_tapd"
|
||||
},
|
||||
"response": {
|
||||
"status": 1,
|
||||
"data": [
|
||||
{
|
||||
"Story": {
|
||||
"id": "1158335167004774712",
|
||||
"workitem_type_id": "1158335167001002456",
|
||||
"app_id": "1",
|
||||
"name": "破绽-通用特效",
|
||||
"description": "<p><a href=\"https://doc.weixin.qq.com/sheet/e3_AfIAXgaSAKsCNYIUEmQnvQT6pAabt?scode=ANYAEAdoABEiTVm8UVASEAsgY6AKM&tab=4x5x07\" target=\"_blank\" rel=\"noopener\">https://doc.weixin.qq.com/sheet/e3_AfIAXgaSAKsCNYIUEmQnvQT6pAabt?scode=ANYAEAdoABEiTVm8UVASEAsgY6AKM&tab=4x5x07</a></p>",
|
||||
"workspace_id": "58335167",
|
||||
"creator": "Haoo",
|
||||
"created": "2025-11-24 20:46:53",
|
||||
"modified": "2025-12-08 14:26:37",
|
||||
"status": "status_7",
|
||||
"step": "",
|
||||
"owner": "parkerluo;",
|
||||
"cc": "",
|
||||
"begin": null,
|
||||
"due": "2025-12-31",
|
||||
"size": null,
|
||||
"priority": "2",
|
||||
"developer": "",
|
||||
"iteration_id": "0",
|
||||
"test_focus": "",
|
||||
"type": "",
|
||||
"source": "",
|
||||
"module": "",
|
||||
"version": "",
|
||||
"completed": null,
|
||||
"category_id": "1158335167001017699",
|
||||
"path": "1158335167004782406::1158335167004788137::1158335167004774712:",
|
||||
"parent_id": "1158335167004788137",
|
||||
"children_id": "|",
|
||||
"ancestor_id": "1158335167004782406",
|
||||
"level": "2",
|
||||
"business_value": null,
|
||||
"effort": null,
|
||||
"effort_completed": "0",
|
||||
"exceed": "0",
|
||||
"remain": "0",
|
||||
"release_id": "0",
|
||||
"bug_id": "0",
|
||||
"templated_id": "1158335167001006073",
|
||||
"created_from": null,
|
||||
"feature": "",
|
||||
"label": "",
|
||||
"progress": "0",
|
||||
"is_archived": "0",
|
||||
"tech_risk": null,
|
||||
"flows": null,
|
||||
"custom_field_one": "",
|
||||
"custom_field_two": "",
|
||||
"custom_field_three": "",
|
||||
"custom_field_four": "",
|
||||
"custom_field_five": "",
|
||||
"custom_field_six": "",
|
||||
"custom_field_seven": "",
|
||||
"custom_field_eight": "",
|
||||
"secret_root_id": "0",
|
||||
"progress_manual": "0",
|
||||
"custom_field_9": "",
|
||||
"custom_field_10": "",
|
||||
"custom_field_11": "",
|
||||
"custom_field_12": "",
|
||||
"custom_field_13": "",
|
||||
"custom_field_14": "",
|
||||
"custom_field_15": "",
|
||||
"custom_field_16": "",
|
||||
"custom_field_17": "",
|
||||
"custom_field_18": "",
|
||||
"custom_field_19": "",
|
||||
"custom_field_20": "",
|
||||
"custom_field_21": "",
|
||||
"custom_field_22": "",
|
||||
"custom_field_23": "",
|
||||
"custom_field_24": "",
|
||||
"custom_field_25": "",
|
||||
"custom_field_26": "",
|
||||
"custom_field_27": "",
|
||||
"custom_field_28": "",
|
||||
"custom_field_29": "",
|
||||
"custom_field_30": "",
|
||||
"custom_field_31": "",
|
||||
"custom_field_32": "",
|
||||
"custom_field_33": "",
|
||||
"custom_field_34": "",
|
||||
"custom_field_35": "",
|
||||
"custom_field_36": "",
|
||||
"custom_field_37": "",
|
||||
"custom_field_38": "",
|
||||
"custom_field_39": "",
|
||||
"custom_field_40": "",
|
||||
"custom_field_41": "",
|
||||
"custom_field_42": "",
|
||||
"custom_field_43": "",
|
||||
"custom_field_44": "",
|
||||
"custom_field_45": "",
|
||||
"custom_field_46": "",
|
||||
"custom_field_47": "",
|
||||
"custom_field_48": "",
|
||||
"custom_field_49": "",
|
||||
"custom_field_50": "",
|
||||
"custom_field_51": "",
|
||||
"custom_field_52": "",
|
||||
"custom_field_53": "",
|
||||
"custom_field_54": "",
|
||||
"custom_field_55": "",
|
||||
"custom_field_56": "",
|
||||
"custom_field_57": "",
|
||||
"custom_field_58": "",
|
||||
"custom_field_59": "",
|
||||
"custom_field_60": "",
|
||||
"custom_field_61": "",
|
||||
"custom_field_62": "",
|
||||
"custom_field_63": "",
|
||||
"custom_field_64": "",
|
||||
"custom_field_65": "",
|
||||
"custom_field_66": "",
|
||||
"custom_field_67": "",
|
||||
"custom_field_68": "",
|
||||
"custom_field_69": "",
|
||||
"custom_field_70": "",
|
||||
"custom_field_71": "",
|
||||
"custom_field_72": "",
|
||||
"custom_field_73": "",
|
||||
"custom_field_74": "",
|
||||
"custom_field_75": "",
|
||||
"custom_field_76": "",
|
||||
"custom_field_77": "",
|
||||
"custom_field_78": "",
|
||||
"custom_field_79": "",
|
||||
"custom_field_80": "",
|
||||
"custom_field_81": "",
|
||||
"custom_field_82": "",
|
||||
"custom_field_83": "",
|
||||
"custom_field_84": "",
|
||||
"custom_field_85": "",
|
||||
"custom_field_86": "",
|
||||
"custom_field_87": "",
|
||||
"custom_field_88": "",
|
||||
"custom_field_89": "",
|
||||
"custom_field_90": "",
|
||||
"custom_field_91": "",
|
||||
"custom_field_92": "",
|
||||
"custom_field_93": "",
|
||||
"custom_field_94": "",
|
||||
"custom_field_95": "",
|
||||
"custom_field_96": "",
|
||||
"custom_field_97": "",
|
||||
"custom_field_98": "",
|
||||
"custom_field_99": "",
|
||||
"custom_field_100": "",
|
||||
"custom_field_101": "",
|
||||
"custom_field_102": "",
|
||||
"custom_field_103": "",
|
||||
"custom_field_104": "",
|
||||
"custom_field_105": "",
|
||||
"custom_field_106": "",
|
||||
"custom_field_107": "",
|
||||
"custom_field_108": "",
|
||||
"custom_field_109": "",
|
||||
"custom_field_110": "",
|
||||
"custom_field_111": "",
|
||||
"custom_field_112": "",
|
||||
"custom_field_113": "",
|
||||
"custom_field_114": "",
|
||||
"custom_field_115": "",
|
||||
"custom_field_116": "",
|
||||
"custom_field_117": "",
|
||||
"custom_field_118": "",
|
||||
"custom_field_119": "",
|
||||
"custom_field_120": "",
|
||||
"custom_field_121": "",
|
||||
"custom_field_122": "",
|
||||
"custom_field_123": "",
|
||||
"custom_field_124": "",
|
||||
"custom_field_125": "",
|
||||
"custom_field_126": "",
|
||||
"custom_field_127": "",
|
||||
"custom_field_128": "",
|
||||
"custom_field_129": "",
|
||||
"custom_field_130": "",
|
||||
"custom_field_131": "",
|
||||
"custom_field_132": "",
|
||||
"custom_field_133": "",
|
||||
"custom_field_134": "",
|
||||
"custom_field_135": "",
|
||||
"custom_field_136": "",
|
||||
"custom_field_137": "",
|
||||
"custom_field_138": "",
|
||||
"custom_field_139": "",
|
||||
"custom_field_140": "",
|
||||
"custom_field_141": "",
|
||||
"custom_field_142": "",
|
||||
"custom_field_143": "",
|
||||
"custom_field_144": "",
|
||||
"custom_field_145": "",
|
||||
"custom_field_146": "",
|
||||
"custom_field_147": "",
|
||||
"custom_field_148": "",
|
||||
"custom_field_149": "",
|
||||
"custom_field_150": "",
|
||||
"custom_field_151": "",
|
||||
"custom_field_152": "",
|
||||
"custom_field_153": "",
|
||||
"custom_field_154": "",
|
||||
"custom_field_155": "",
|
||||
"custom_field_156": "",
|
||||
"custom_field_157": "",
|
||||
"custom_field_158": "",
|
||||
"custom_field_159": "",
|
||||
"custom_field_160": "",
|
||||
"custom_field_161": "",
|
||||
"custom_field_162": "",
|
||||
"custom_field_163": "",
|
||||
"custom_field_164": "",
|
||||
"custom_field_165": "",
|
||||
"custom_field_166": "",
|
||||
"custom_field_167": "",
|
||||
"custom_field_168": "",
|
||||
"custom_field_169": "",
|
||||
"custom_field_170": "",
|
||||
"custom_field_171": "",
|
||||
"custom_field_172": "",
|
||||
"custom_field_173": "",
|
||||
"custom_field_174": "",
|
||||
"custom_field_175": "",
|
||||
"custom_field_176": "",
|
||||
"custom_field_177": "",
|
||||
"custom_field_178": "",
|
||||
"custom_field_179": "",
|
||||
"custom_field_180": "",
|
||||
"custom_field_181": "",
|
||||
"custom_field_182": "",
|
||||
"custom_field_183": "",
|
||||
"custom_field_184": "",
|
||||
"custom_field_185": "",
|
||||
"custom_field_186": "",
|
||||
"custom_field_187": "",
|
||||
"custom_field_188": "",
|
||||
"custom_field_189": "",
|
||||
"custom_field_190": "",
|
||||
"custom_field_191": "",
|
||||
"custom_field_192": "",
|
||||
"custom_field_193": "",
|
||||
"custom_field_194": "",
|
||||
"custom_field_195": "",
|
||||
"custom_field_196": "",
|
||||
"custom_field_197": "",
|
||||
"custom_field_198": "",
|
||||
"custom_field_199": "",
|
||||
"custom_field_200": "",
|
||||
"custom_plan_field_1": "1158335167001033687",
|
||||
"custom_plan_field_2": "0",
|
||||
"custom_plan_field_3": "0",
|
||||
"custom_plan_field_4": "0",
|
||||
"custom_plan_field_5": "0",
|
||||
"custom_plan_field_6": "0",
|
||||
"custom_plan_field_7": "0",
|
||||
"custom_plan_field_8": "0",
|
||||
"custom_plan_field_9": "0",
|
||||
"custom_plan_field_10": "0",
|
||||
"priority_label": "Low"
|
||||
}
|
||||
}
|
||||
],
|
||||
"info": "success"
|
||||
}
|
||||
}
|
||||
]}
|
||||
115
src2/link_parser.py
Normal file
115
src2/link_parser.py
Normal file
@ -0,0 +1,115 @@
|
||||
"""
|
||||
TAPD链接解析模块
|
||||
负责从TAPD链接中提取需求单号
|
||||
|
||||
支持的链接格式:
|
||||
1. 列表页弹窗链接: https://www.tapd.cn/tapd_fe/{workspace_id}/story/list?...dialog_preview_id=story_{单号}
|
||||
2. 详情页链接: https://www.tapd.cn/{workspace_id}/prong/stories/view/{单号}
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Tuple, Optional
|
||||
|
||||
|
||||
# 链接类型常量
|
||||
LINK_TYPE_DIALOG = "dialog" # 列表页弹窗链接
|
||||
LINK_TYPE_VIEW = "view" # 详情页链接
|
||||
LINK_TYPE_UNKNOWN = "unknown"
|
||||
|
||||
|
||||
def parse_tapd_link(url: str) -> Tuple[bool, str, str]:
|
||||
"""
|
||||
解析TAPD链接,提取需求单号
|
||||
|
||||
Args:
|
||||
url: TAPD链接字符串
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str, str]: (是否成功, 单号或错误信息, 链接类型)
|
||||
- 成功时: (True, "1234567890", "dialog" 或 "view")
|
||||
- 失败时: (False, "错误信息", "unknown")
|
||||
"""
|
||||
if not url:
|
||||
return (False, "链接为空", LINK_TYPE_UNKNOWN)
|
||||
|
||||
# 确保是字符串类型
|
||||
if not isinstance(url, str):
|
||||
url = str(url)
|
||||
|
||||
url = url.strip()
|
||||
|
||||
if not url:
|
||||
return (False, "链接为空", LINK_TYPE_UNKNOWN)
|
||||
|
||||
# 格式一:列表页弹窗链接
|
||||
# 匹配 dialog_preview_id=story_(\d+)
|
||||
pattern_dialog = r'dialog_preview_id=story_(\d+)'
|
||||
match_dialog = re.search(pattern_dialog, url)
|
||||
if match_dialog:
|
||||
story_id = match_dialog.group(1)
|
||||
return (True, story_id, LINK_TYPE_DIALOG)
|
||||
|
||||
# 格式二:详情页链接
|
||||
# 匹配 /stories/view/(\d+)
|
||||
pattern_view = r'/stories/view/(\d+)'
|
||||
match_view = re.search(pattern_view, url)
|
||||
if match_view:
|
||||
story_id = match_view.group(1)
|
||||
return (True, story_id, LINK_TYPE_VIEW)
|
||||
|
||||
# 未匹配到任何格式
|
||||
return (False, f"无法识别的链接格式: {url[:100]}", LINK_TYPE_UNKNOWN)
|
||||
|
||||
|
||||
def extract_story_id(url: str) -> Optional[str]:
|
||||
"""
|
||||
从TAPD链接中提取需求单号(简化接口)
|
||||
|
||||
Args:
|
||||
url: TAPD链接字符串
|
||||
|
||||
Returns:
|
||||
Optional[str]: 成功返回单号,失败返回None
|
||||
"""
|
||||
success, result, _ = parse_tapd_link(url)
|
||||
return result if success else None
|
||||
|
||||
|
||||
def is_valid_tapd_link(url: str) -> bool:
|
||||
"""
|
||||
检查是否为有效的TAPD链接
|
||||
|
||||
Args:
|
||||
url: TAPD链接字符串
|
||||
|
||||
Returns:
|
||||
bool: 是否为有效链接
|
||||
"""
|
||||
success, _, _ = parse_tapd_link(url)
|
||||
return success
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=== TAPD链接解析器测试 ===\n")
|
||||
|
||||
# 测试用例
|
||||
test_cases = [
|
||||
# 格式一:列表页弹窗链接
|
||||
"https://www.tapd.cn/tapd_fe/58335167/story/list?dialog_preview_id=story_1158335167001044388",
|
||||
# 格式二:详情页链接
|
||||
"https://www.tapd.cn/58335167/prong/stories/view/1158335167001044388",
|
||||
# 无效链接
|
||||
"https://www.tapd.cn/58335167/bugtrace/bugs/view/123456",
|
||||
"https://www.google.com",
|
||||
"",
|
||||
None,
|
||||
]
|
||||
|
||||
for i, url in enumerate(test_cases, 1):
|
||||
print(f"测试 {i}: {url}")
|
||||
success, result, link_type = parse_tapd_link(url)
|
||||
if success:
|
||||
print(f" ✓ 解析成功: 单号={result}, 类型={link_type}")
|
||||
else:
|
||||
print(f" ✗ 解析失败: {result}")
|
||||
print()
|
||||
256
src2/tapd_api.py
Normal file
256
src2/tapd_api.py
Normal file
@ -0,0 +1,256 @@
|
||||
"""
|
||||
TAPD API调用模块(任务二专用)
|
||||
负责与TAPD Open API交互,查询需求(Story)信息
|
||||
|
||||
与任务一的区别:
|
||||
- 任务一:创建和管理Bug单
|
||||
- 任务二:查询需求(Story)状态信息
|
||||
"""
|
||||
|
||||
import os
|
||||
import requests
|
||||
from typing import Dict, Optional, Any
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
# 导入任务二专用的日志模块
|
||||
from src2.logger import get_task2_logger
|
||||
|
||||
|
||||
# TAPD状态值映射表
|
||||
# 将API返回的状态代码转换为中文显示文本
|
||||
STATUS_MAPPING = {
|
||||
"status_5": "进行中",
|
||||
"status_7": "未开始",
|
||||
"status_8": "已完成",
|
||||
"status_9": "待验收",
|
||||
"status_10": "联调",
|
||||
"status_12": "取消",
|
||||
"status_13": "待评审",
|
||||
}
|
||||
|
||||
|
||||
def map_status(status_code: str) -> str:
|
||||
"""
|
||||
将TAPD状态代码转换为中文显示文本
|
||||
|
||||
Args:
|
||||
status_code: TAPD API返回的状态代码(如 "status_5")
|
||||
|
||||
Returns:
|
||||
str: 中文显示文本(如 "进行中"),未知状态返回原始值
|
||||
"""
|
||||
if not status_code:
|
||||
return "未知"
|
||||
return STATUS_MAPPING.get(status_code, status_code)
|
||||
|
||||
|
||||
class TAPDStoryApi:
|
||||
"""TAPD需求API封装类(任务二专用)"""
|
||||
|
||||
# TAPD API基础URL(与任务一相同)
|
||||
BASE_URL = "https://tapd-api.bilibili.co/tapd"
|
||||
|
||||
def __init__(self, workspace_id: str, test_mode: bool = False):
|
||||
"""
|
||||
初始化TAPD Story API
|
||||
|
||||
Args:
|
||||
workspace_id: TAPD项目ID
|
||||
test_mode: 是否启用测试模式(显示API请求和响应)
|
||||
|
||||
Raises:
|
||||
ValueError: 环境变量未设置时抛出
|
||||
"""
|
||||
self.workspace_id = workspace_id
|
||||
self.test_mode = test_mode
|
||||
self.session = requests.Session()
|
||||
|
||||
# 从环境变量读取认证信息(与任务一共用)
|
||||
self.api_user = os.environ.get('TAPD_API_USER')
|
||||
self.api_password = os.environ.get('TAPD_API_PASSWORD')
|
||||
|
||||
if not self.api_user or not self.api_password:
|
||||
raise ValueError(
|
||||
"TAPD认证信息未设置。请设置环境变量:\n"
|
||||
" - TAPD_API_USER\n"
|
||||
" - TAPD_API_PASSWORD"
|
||||
)
|
||||
|
||||
# 设置Basic Auth
|
||||
self.auth = HTTPBasicAuth(self.api_user, self.api_password)
|
||||
|
||||
# 初始化任务二专用的日志记录器
|
||||
self.logger = get_task2_logger()
|
||||
|
||||
print(f" ✓ TAPD Story API初始化完成 (workspace_id: {workspace_id})")
|
||||
if test_mode:
|
||||
print(f" ⚠ 测试模式已启用:将显示所有API调用的详细信息")
|
||||
|
||||
def _make_request(self, endpoint: str, params: Optional[Dict] = None) -> Dict:
|
||||
"""
|
||||
发起TAPD API GET请求的通用方法
|
||||
|
||||
Args:
|
||||
endpoint: API端点(如 "stories")
|
||||
params: URL查询参数
|
||||
|
||||
Returns:
|
||||
Dict: API响应数据
|
||||
|
||||
Raises:
|
||||
RuntimeError: API调用失败时抛出
|
||||
"""
|
||||
url = f"{self.BASE_URL}/{endpoint}"
|
||||
|
||||
# 准备日志记录的请求数据
|
||||
log_request_data = {
|
||||
"url": url,
|
||||
"method": "GET",
|
||||
"params": params,
|
||||
"auth_user": self.api_user
|
||||
}
|
||||
|
||||
# 测试模式:显示请求信息
|
||||
if self.test_mode:
|
||||
print("\n" + "=" * 60)
|
||||
print(f"【测试模式】TAPD API调用: {endpoint}")
|
||||
print("=" * 60)
|
||||
print(f"请求URL: {url}")
|
||||
if params:
|
||||
print(f"URL参数:")
|
||||
for key, value in params.items():
|
||||
print(f" {key}: {value}")
|
||||
|
||||
try:
|
||||
response = self.session.get(
|
||||
url,
|
||||
params=params,
|
||||
auth=self.auth,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
# 测试模式:显示响应信息
|
||||
if self.test_mode:
|
||||
print(f"\n响应状态码: {response.status_code}")
|
||||
try:
|
||||
import json
|
||||
result = response.json()
|
||||
print(f"响应数据:")
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
except:
|
||||
print(f"响应内容: {response.text[:500]}")
|
||||
print("=" * 60)
|
||||
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
# 检查TAPD API返回的状态
|
||||
if result.get('status') != 1:
|
||||
error_msg = result.get('info', '未知错误')
|
||||
self.logger.log_api_call(
|
||||
api_type="tapd",
|
||||
operation=endpoint,
|
||||
request_data=log_request_data,
|
||||
response_data=result,
|
||||
success=False,
|
||||
error_message=error_msg
|
||||
)
|
||||
raise RuntimeError(f"TAPD API调用失败: {error_msg}")
|
||||
|
||||
# 记录成功日志
|
||||
self.logger.log_api_call(
|
||||
api_type="tapd",
|
||||
operation=endpoint,
|
||||
request_data=log_request_data,
|
||||
response_data=result,
|
||||
success=True
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
error_msg = f"TAPD API请求超时: {endpoint}"
|
||||
self.logger.log_api_call(
|
||||
api_type="tapd",
|
||||
operation=endpoint,
|
||||
request_data=log_request_data,
|
||||
response_data={},
|
||||
success=False,
|
||||
error_message=error_msg
|
||||
)
|
||||
raise RuntimeError(error_msg)
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
error_msg = f"TAPD API请求失败: {e}"
|
||||
self.logger.log_api_call(
|
||||
api_type="tapd",
|
||||
operation=endpoint,
|
||||
request_data=log_request_data,
|
||||
response_data={},
|
||||
success=False,
|
||||
error_message=error_msg
|
||||
)
|
||||
raise RuntimeError(error_msg)
|
||||
|
||||
def get_story(self, story_id: str) -> Dict:
|
||||
"""
|
||||
获取需求详情
|
||||
|
||||
Args:
|
||||
story_id: 需求ID
|
||||
|
||||
Returns:
|
||||
Dict: 需求详细信息
|
||||
|
||||
Raises:
|
||||
RuntimeError: 获取失败时抛出
|
||||
"""
|
||||
params = {
|
||||
'workspace_id': self.workspace_id,
|
||||
'id': story_id
|
||||
}
|
||||
|
||||
result = self._make_request("stories", params=params)
|
||||
|
||||
# TAPD API返回格式: {"status": 1, "data": [{"Story": {...}}]}
|
||||
data = result.get('data', [])
|
||||
|
||||
if not isinstance(data, list) or len(data) == 0:
|
||||
raise RuntimeError(f"未找到需求: {story_id}")
|
||||
|
||||
# 取第一个元素
|
||||
first_item = data[0]
|
||||
|
||||
# 提取Story对象
|
||||
if isinstance(first_item, dict) and 'Story' in first_item:
|
||||
story_info = first_item['Story']
|
||||
else:
|
||||
raise RuntimeError(f"API返回数据格式异常: {first_item}")
|
||||
|
||||
if not story_info:
|
||||
raise RuntimeError(f"未找到需求: {story_id}")
|
||||
|
||||
# 转换状态为中文
|
||||
raw_status = story_info.get('status', '')
|
||||
story_info['raw_status'] = raw_status
|
||||
story_info['status'] = map_status(raw_status)
|
||||
|
||||
return story_info
|
||||
|
||||
def get_story_url(self, story_id: str) -> str:
|
||||
"""
|
||||
生成需求的访问URL
|
||||
|
||||
Args:
|
||||
story_id: 需求ID
|
||||
|
||||
Returns:
|
||||
str: 需求的访问URL
|
||||
"""
|
||||
return f"https://www.tapd.cn/{self.workspace_id}/prong/stories/view/{story_id}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=== TAPD Story API 测试 ===\n")
|
||||
print("请使用 test_phase2.py 进行完整测试")
|
||||
|
||||
210
src2/test_phase2.py
Normal file
210
src2/test_phase2.py
Normal file
@ -0,0 +1,210 @@
|
||||
"""
|
||||
任务二第二阶段验证脚本
|
||||
测试链接解析和TAPD API功能
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 将项目根目录添加到 Python 路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from src2.link_parser import parse_tapd_link, extract_story_id, is_valid_tapd_link
|
||||
from src2.tapd_api import TAPDStoryApi, map_status, STATUS_MAPPING
|
||||
|
||||
|
||||
def test_link_parser():
|
||||
"""测试链接解析功能"""
|
||||
print("=" * 60)
|
||||
print("测试1: 链接解析器")
|
||||
print("=" * 60)
|
||||
|
||||
test_cases = [
|
||||
# 格式一:列表页弹窗链接
|
||||
(
|
||||
"https://www.tapd.cn/tapd_fe/58335167/story/list?dialog_preview_id=story_1158335167001044388",
|
||||
True,
|
||||
"1158335167001044388",
|
||||
"dialog"
|
||||
),
|
||||
# 格式二:详情页链接
|
||||
(
|
||||
"https://www.tapd.cn/58335167/prong/stories/view/1158335167001044388",
|
||||
True,
|
||||
"1158335167001044388",
|
||||
"view"
|
||||
),
|
||||
# 无效链接:Bug链接
|
||||
(
|
||||
"https://www.tapd.cn/58335167/bugtrace/bugs/view/123456",
|
||||
False,
|
||||
None,
|
||||
"unknown"
|
||||
),
|
||||
# 无效链接:其他网站
|
||||
(
|
||||
"https://www.google.com",
|
||||
False,
|
||||
None,
|
||||
"unknown"
|
||||
),
|
||||
# 空链接
|
||||
(
|
||||
"",
|
||||
False,
|
||||
None,
|
||||
"unknown"
|
||||
),
|
||||
]
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
|
||||
for i, (url, expected_success, expected_id, expected_type) in enumerate(test_cases, 1):
|
||||
success, result, link_type = parse_tapd_link(url)
|
||||
|
||||
# 检查结果
|
||||
if success == expected_success and link_type == expected_type:
|
||||
if success and result == expected_id:
|
||||
print(f" [{i}] PASS: {url[:50]}...")
|
||||
passed += 1
|
||||
elif not success:
|
||||
print(f" [{i}] PASS: 正确识别无效链接")
|
||||
passed += 1
|
||||
else:
|
||||
print(f" [{i}] FAIL: 单号不匹配 (期望={expected_id}, 实际={result})")
|
||||
failed += 1
|
||||
else:
|
||||
print(f" [{i}] FAIL: {url[:50]}...")
|
||||
print(f" 期望: success={expected_success}, type={expected_type}")
|
||||
print(f" 实际: success={success}, type={link_type}")
|
||||
failed += 1
|
||||
|
||||
print(f"\n链接解析测试结果: {passed} 通过, {failed} 失败")
|
||||
return failed == 0
|
||||
|
||||
|
||||
def test_status_mapping():
|
||||
"""测试状态映射功能"""
|
||||
print("\n" + "=" * 60)
|
||||
print("测试2: 状态映射")
|
||||
print("=" * 60)
|
||||
|
||||
test_cases = [
|
||||
("status_5", "进行中"),
|
||||
("status_7", "未开始"),
|
||||
("status_8", "已完成"),
|
||||
("status_9", "待验收"),
|
||||
("status_10", "联调"),
|
||||
("status_12", "取消"),
|
||||
("status_13", "待评审"),
|
||||
("status_99", "status_99"), # 未知状态返回原值
|
||||
("", "未知"),
|
||||
(None, "未知"),
|
||||
]
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
|
||||
for status_code, expected in test_cases:
|
||||
result = map_status(status_code)
|
||||
if result == expected:
|
||||
print(f" PASS: {status_code} -> {result}")
|
||||
passed += 1
|
||||
else:
|
||||
print(f" FAIL: {status_code} -> {result} (期望: {expected})")
|
||||
failed += 1
|
||||
|
||||
print(f"\n状态映射测试结果: {passed} 通过, {failed} 失败")
|
||||
return failed == 0
|
||||
|
||||
|
||||
def test_tapd_api(story_id: str = None):
|
||||
"""测试TAPD API功能"""
|
||||
print("\n" + "=" * 60)
|
||||
print("测试3: TAPD API")
|
||||
print("=" * 60)
|
||||
|
||||
# 从配置读取workspace_id
|
||||
from src2.config import Task2ConfigManager
|
||||
config = Task2ConfigManager()
|
||||
tapd_config = config.get_tapd_config()
|
||||
workspace_id = tapd_config['workspace_id']
|
||||
|
||||
print(f" workspace_id: {workspace_id}")
|
||||
|
||||
try:
|
||||
# 初始化API
|
||||
api = TAPDStoryApi(workspace_id, test_mode=True)
|
||||
print(" ✓ API初始化成功")
|
||||
except ValueError as e:
|
||||
print(f" ✗ API初始化失败: {e}")
|
||||
return False
|
||||
|
||||
if not story_id:
|
||||
print("\n 跳过需求查询测试(未提供story_id)")
|
||||
print(" 用法: python test_phase2.py <story_id>")
|
||||
return True
|
||||
|
||||
# 测试获取需求详情
|
||||
print(f"\n 测试获取需求: {story_id}")
|
||||
try:
|
||||
story = api.get_story(story_id)
|
||||
print(f" ✓ 获取成功")
|
||||
print(f" - ID: {story.get('id')}")
|
||||
print(f" - 名称: {story.get('name')}")
|
||||
print(f" - 状态: {story.get('status')}")
|
||||
print(f" - 处理人: {story.get('owner')}")
|
||||
print(f" - 预计开始: {story.get('begin')}")
|
||||
print(f" - 预计结束: {story.get('due')}")
|
||||
return True
|
||||
except RuntimeError as e:
|
||||
print(f" ✗ 获取失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 60)
|
||||
print("任务二第二阶段验证")
|
||||
print("=" * 60)
|
||||
|
||||
# 获取命令行参数
|
||||
story_id = None
|
||||
if len(sys.argv) > 1:
|
||||
story_id = sys.argv[1]
|
||||
|
||||
results = []
|
||||
|
||||
# 测试1: 链接解析
|
||||
results.append(("链接解析", test_link_parser()))
|
||||
|
||||
# 测试2: 状态映射
|
||||
results.append(("状态映射", test_status_mapping()))
|
||||
|
||||
# 测试3: TAPD API
|
||||
results.append(("TAPD API", test_tapd_api(story_id)))
|
||||
|
||||
# 汇总结果
|
||||
print("\n" + "=" * 60)
|
||||
print("验收结果汇总")
|
||||
print("=" * 60)
|
||||
|
||||
all_passed = True
|
||||
for name, passed in results:
|
||||
status = "✓ PASS" if passed else "✗ FAIL"
|
||||
print(f" {status}: {name}")
|
||||
if not passed:
|
||||
all_passed = False
|
||||
|
||||
if all_passed:
|
||||
print("\n所有测试通过!第二阶段验收完成。")
|
||||
else:
|
||||
print("\n部分测试失败,请检查。")
|
||||
|
||||
return 0 if all_passed else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
x
Reference in New Issue
Block a user