v0.0.1
This commit is contained in:
@@ -83,15 +83,17 @@ def get_session():
|
||||
|
||||
|
||||
def find_service_request_object_id(session, ticket_id):
|
||||
"""通过人类可读的 ticket ID 查找 OData ObjectID"""
|
||||
"""通过人类可读的 ticket ID 查找 OData ObjectID 和 SerialID"""
|
||||
url = f"{ODATA_C4C}/ServiceRequestCollection"
|
||||
params = {"$format": "json", "$filter": f"ID eq '{ticket_id}'", "$select": "ObjectID,ID"}
|
||||
params = {"$format": "json", "$filter": f"ID eq '{ticket_id}'"}
|
||||
resp = session.get(url, params=params, timeout=60)
|
||||
resp.raise_for_status()
|
||||
results = resp.json().get("d", {}).get("results", [])
|
||||
if not results:
|
||||
raise ValueError(f"未找到 ID={ticket_id} 的 ServiceRequest")
|
||||
return results[0]["ObjectID"]
|
||||
sr = results[0]
|
||||
serial_id = sr.get("SerialID", "")
|
||||
return sr["ObjectID"], serial_id
|
||||
|
||||
|
||||
def _parse_attachments(results):
|
||||
@@ -137,6 +139,15 @@ def list_issue_items(session, ticket_id):
|
||||
return resp.json().get("d", {}).get("results", [])
|
||||
|
||||
|
||||
def get_issue_item_detail(session, object_id):
|
||||
"""通过 ObjectID 获取 XIssueItem 详细信息,包括真实的 IssueID_SDK"""
|
||||
url = f"{ODATA_CUST}/ServiceRequest_XIssueItem_SDKCollection('{object_id}')"
|
||||
params = {"$format": "json"}
|
||||
resp = session.get(url, params=params, timeout=60)
|
||||
resp.raise_for_status()
|
||||
return resp.json().get("d", {}).get("results", {})
|
||||
|
||||
|
||||
def list_issue_item_attachments(session, issue_item_uuid):
|
||||
"""
|
||||
获取 XIssueItem 级别的附件。
|
||||
@@ -325,8 +336,8 @@ def dsm_upload_file(sid, local_path, remote_path):
|
||||
return data
|
||||
|
||||
|
||||
def dsm_upload_downloaded_files(downloaded_files, json_mode=False):
|
||||
"""将所有已下载文件上传到群晖 DSM"""
|
||||
def dsm_upload_downloaded_files(downloaded_files, ticket_id, serial_id, json_mode=False):
|
||||
"""将所有已下载文件上传到群晖 DSM,按 ticket 和 issue 组织目录结构"""
|
||||
if not DSM_URL or not DSM_USER or not DSM_PASSWORD or not DSM_PATH:
|
||||
return []
|
||||
|
||||
@@ -334,10 +345,13 @@ def dsm_upload_downloaded_files(downloaded_files, json_mode=False):
|
||||
if not files_to_upload:
|
||||
return []
|
||||
|
||||
# 目录名: ticketID_serialID
|
||||
folder_name = f"{ticket_id}_{serial_id}" if serial_id else ticket_id
|
||||
|
||||
if not json_mode:
|
||||
print(f"\n{'='*60}")
|
||||
print(f"上传到群晖 DSM: {DSM_URL}")
|
||||
print(f"目标路径: {DSM_PATH}")
|
||||
print(f"目标路径: {DSM_PATH}/{folder_name}")
|
||||
print('='*60)
|
||||
|
||||
upload_results = []
|
||||
@@ -353,12 +367,29 @@ def dsm_upload_downloaded_files(downloaded_files, json_mode=False):
|
||||
for f in files_to_upload:
|
||||
local_path = f["savedPath"]
|
||||
filename = os.path.basename(local_path)
|
||||
entry = {"file": filename, "remotePath": f"{DSM_PATH}/{filename}"}
|
||||
issue_id = f.get("issueId", "")
|
||||
|
||||
# 根据 issueId 判断目录结构
|
||||
# SR 附件: {DSM_PATH}/{ticketID_serialID}/{filename}
|
||||
# IssueItem 附件: {DSM_PATH}/{ticketID_serialID}/{issueID}/{filename}
|
||||
if issue_id:
|
||||
remote_path = f"{DSM_PATH}/{folder_name}/{issue_id}"
|
||||
else:
|
||||
remote_path = f"{DSM_PATH}/{folder_name}"
|
||||
full_remote_path = f"{remote_path}/{filename}"
|
||||
|
||||
entry = {
|
||||
"file": filename,
|
||||
"ticketId": ticket_id,
|
||||
"serialId": serial_id,
|
||||
"issueId": issue_id,
|
||||
"remotePath": full_remote_path,
|
||||
}
|
||||
try:
|
||||
dsm_upload_file(sid, local_path, DSM_PATH)
|
||||
dsm_upload_file(sid, local_path, remote_path)
|
||||
entry["success"] = True
|
||||
if not json_mode:
|
||||
print(f" 上传成功: {filename} -> {DSM_PATH}/{filename}")
|
||||
print(f" 上传成功: {filename} -> {full_remote_path}")
|
||||
except Exception as e:
|
||||
entry["success"] = False
|
||||
entry["error"] = str(e)
|
||||
@@ -441,11 +472,12 @@ def run(ticket_id, output_dir, list_only=False, json_mode=False):
|
||||
}
|
||||
|
||||
try:
|
||||
# 1) 通过 ticket ID 找到 ObjectID
|
||||
sr_object_id = find_service_request_object_id(session, ticket_id)
|
||||
# 1) 通过 ticket ID 找到 ObjectID 和 SerialID
|
||||
sr_object_id, serial_id = find_service_request_object_id(session, ticket_id)
|
||||
result["srObjectId"] = sr_object_id
|
||||
result["serialId"] = serial_id
|
||||
if not json_mode:
|
||||
print(f"ServiceRequest ID={ticket_id}, ObjectID={sr_object_id}")
|
||||
print(f"ServiceRequest ID={ticket_id}, ObjectID={sr_object_id}, SerialID={serial_id}")
|
||||
|
||||
# 2) ServiceRequest 级别附件
|
||||
if not json_mode:
|
||||
@@ -458,7 +490,7 @@ def run(ticket_id, output_dir, list_only=False, json_mode=False):
|
||||
print(f"找到 {len(sr_attachments)} 个附件")
|
||||
|
||||
if not list_only:
|
||||
_do_download(session, sr_attachments, "SR", None, result, json_mode)
|
||||
_do_download(session, sr_attachments, "SR", "", None, result, json_mode)
|
||||
|
||||
# 3) XIssueItem 级别附件
|
||||
if not json_mode:
|
||||
@@ -474,8 +506,17 @@ def run(ticket_id, output_dir, list_only=False, json_mode=False):
|
||||
issue_uuid = item.get("XIssueItemUUIDcontent_SDK", "")
|
||||
issue_desc = (item.get("IssuesDescriptionX_SDK") or "")[:80]
|
||||
|
||||
# 通过 ObjectID 查询详细信息,获取真实的 IssueID_SDK
|
||||
issue_id = ""
|
||||
try:
|
||||
item_detail = get_issue_item_detail(session, item_oid)
|
||||
issue_id = item_detail.get("IssueID_SDK", "")
|
||||
except Exception as e:
|
||||
print(f" ⚠ 获取 IssueID 失败: {e}", file=sys.stderr)
|
||||
|
||||
issue_entry = {
|
||||
"objectId": item_oid,
|
||||
"issueId": issue_id,
|
||||
"uuid": issue_uuid,
|
||||
"description": issue_desc,
|
||||
"attachments": [],
|
||||
@@ -483,6 +524,8 @@ def run(ticket_id, output_dir, list_only=False, json_mode=False):
|
||||
|
||||
if not json_mode:
|
||||
print(f"\n XIssueItem: {item_oid}")
|
||||
if issue_id:
|
||||
print(f" IssueID: {issue_id}")
|
||||
print(f" UUID: {issue_uuid}")
|
||||
print(f" 描述: {issue_desc}")
|
||||
|
||||
@@ -498,8 +541,9 @@ def run(ticket_id, output_dir, list_only=False, json_mode=False):
|
||||
print(f" 找到 {len(atts)} 个附件")
|
||||
|
||||
if not list_only:
|
||||
label = f"IssueItem-{issue_id}" if issue_id else f"IssueItem-{item_oid[:12]}"
|
||||
_do_download(
|
||||
session, atts, f"IssueItem-{item_oid[:12]}",
|
||||
session, atts, label, issue_id,
|
||||
f"{ODATA_CUST}/BO_XSRIssueItemAttachmentFolderCollection",
|
||||
result, json_mode,
|
||||
)
|
||||
@@ -510,7 +554,8 @@ def run(ticket_id, output_dir, list_only=False, json_mode=False):
|
||||
if not json_mode:
|
||||
all_attachments = [("SR", sr_attachments)]
|
||||
for ie in result["issueItems"]:
|
||||
all_attachments.append((f"IssueItem-{ie['objectId'][:12]}", ie["attachments"]))
|
||||
ie_label = f"IssueItem-{ie['issueId']}" if ie.get("issueId") else f"IssueItem-{ie['objectId'][:12]}"
|
||||
all_attachments.append((ie_label, ie["attachments"]))
|
||||
print_attachment_summary(all_attachments)
|
||||
|
||||
except Exception as e:
|
||||
@@ -522,9 +567,9 @@ def run(ticket_id, output_dir, list_only=False, json_mode=False):
|
||||
return result
|
||||
|
||||
|
||||
def _download_single_file(session, att, label, odata_url, json_mode):
|
||||
def _download_single_file(session, att, label, issue_id, odata_url, json_mode):
|
||||
"""下载单个文件附件(用于多线程)"""
|
||||
entry = {"source": label, "c4cName": att["FileName"], "type": "file", "mime": att.get("MimeType")}
|
||||
entry = {"source": label, "issueId": issue_id, "c4cName": att["FileName"], "type": "file", "mime": att.get("MimeType")}
|
||||
try:
|
||||
content = download_file_via_odata(session, att, odata_url)
|
||||
file_path = os.path.join(OUTPUT_DIR, att["FileName"])
|
||||
@@ -543,10 +588,10 @@ def _download_single_file(session, att, label, odata_url, json_mode):
|
||||
return entry
|
||||
|
||||
|
||||
def _download_single_link(link_att, label, json_mode):
|
||||
def _download_single_link(link_att, label, issue_id, json_mode):
|
||||
"""下载单个链接附件(用于多线程)"""
|
||||
link_url = link_att.get("LinkWebURI")
|
||||
entry = {"source": label, "c4cName": link_att["FileName"], "type": "link", "linkUrl": link_url}
|
||||
entry = {"source": label, "issueId": issue_id, "c4cName": link_att["FileName"], "type": "link", "linkUrl": link_url}
|
||||
|
||||
if not link_url:
|
||||
entry["error"] = "无链接地址"
|
||||
@@ -571,7 +616,7 @@ def _download_single_link(link_att, label, json_mode):
|
||||
return entry
|
||||
|
||||
|
||||
def _do_download(session, attachments, label, odata_url, result, json_mode):
|
||||
def _do_download(session, attachments, label, issue_id, odata_url, result, json_mode):
|
||||
"""执行下载并将结果追加到 result['downloadedFiles'](多线程版本)"""
|
||||
file_atts = [a for a in attachments if a["CategoryCode"] == "2"]
|
||||
link_atts = [a for a in attachments if a["CategoryCode"] == "3"]
|
||||
@@ -584,12 +629,12 @@ def _do_download(session, attachments, label, odata_url, result, json_mode):
|
||||
|
||||
# 提交文件附件下载任务
|
||||
for att in file_atts:
|
||||
future = executor.submit(_download_single_file, session, att, label, odata_url, json_mode)
|
||||
future = executor.submit(_download_single_file, session, att, label, issue_id, odata_url, json_mode)
|
||||
futures.append(future)
|
||||
|
||||
# 提交链接附件下载任务
|
||||
for att in link_atts:
|
||||
future = executor.submit(_download_single_link, att, label, json_mode)
|
||||
future = executor.submit(_download_single_link, att, label, issue_id, json_mode)
|
||||
futures.append(future)
|
||||
|
||||
# 收集结果
|
||||
@@ -659,9 +704,22 @@ def main():
|
||||
|
||||
# 下载完成后上传到群晖 DSM
|
||||
if DSM_URL and not args.list_only and result["success"]:
|
||||
upload_results = dsm_upload_downloaded_files(result["downloadedFiles"], args.json_mode)
|
||||
serial_id = result.get("serialId", "")
|
||||
upload_results = dsm_upload_downloaded_files(result["downloadedFiles"], args.ticket, serial_id, args.json_mode)
|
||||
result["dsmUpload"] = upload_results
|
||||
|
||||
# 上传完成后清理本地下载文件
|
||||
for f in result["downloadedFiles"]:
|
||||
local_path = f.get("savedPath")
|
||||
if local_path and os.path.exists(local_path):
|
||||
try:
|
||||
os.remove(local_path)
|
||||
if not args.json_mode:
|
||||
print(f" 已删除本地文件: {local_path}")
|
||||
except OSError as e:
|
||||
if not args.json_mode:
|
||||
print(f" 删除失败: {local_path}: {e}")
|
||||
|
||||
if args.json_mode:
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
sys.exit(0 if result["success"] else 1)
|
||||
|
||||
Reference in New Issue
Block a user