You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

指定工单可加评论,JQL拉取工单调用Jira REST API v3报404

问题:Jira API批量添加评论时404错误

手动指定工单编号调用Jira REST API v3添加评论正常,但通过pull_ticket_info函数(基于JQL查询拉取工单信息)返回的工单编号调用add_comment时,抛出HTTP 404错误,提示:

问题不存在或您没有查看权限。

已确认信息:

  • 两次调用使用相同认证凭据,且权限验证通过
  • 打印工单编号显示为正确的Issue Key格式
  • 尝试过去除多余空格、改用Issue ID,问题未解决

相关代码如下:

pull_ticket_info函数

def pull_ticket_info(jql_query, jira_url, api_token, jira_username):
    """
    Retrieve ticket information from Jira using a JQL query.

    Args:
        jql_query (str): The JQL query to search for tickets in Jira.
        jira_url (str): The URL of the Jira instance.
        api_token (str): The API token for authentication with Jira.
        jira_username (str): The username for authentication with Jira.

    Returns:
        str: The ticket number of research tickets. Returns None if no ticket is found.
    """

    ticket_info_dict = {}  # Dictionary to store ticket number and research type

    # Set up the API endpoint for JQL search
    api_endpoint = f"{jira_url}/rest/api/3/search"

    # Encode credentials for authentication
    credentials = f"{jira_username}:{api_token}"
    base64_credentials = base64.b64encode(credentials.encode()).decode()

    # Set up the authentication headers with base64-encoded credentials
    headers = {
        "Authorization": f"Basic {base64_credentials}"
    }

    # Set up the JQL query parameters and specify the fields you want to retrieve
    params = {
        "jql": jql_query,
        # "fields": "key, summary, customfield_13810, customfield_13427"
    }

    try:
        # Make the GET request to perform the JQL search
        jql_response = requests.get(api_endpoint, headers=headers, params=params)
        api_response = jql_response.json()
        # print(api_response)

        if jql_response.status_code == 200:
            for issue in api_response["issues"]:
                ticket_id = issue["id"]
                ticket_number = issue["key"]
                research_type = issue["fields"].get("customfield_13427", {})
                research_type_value = research_type.get("value", "N/A")

                # Add ticket number and research type to the dictionary
                # ticket_info_dict[ticket_number] = research_type_value
                ticket_info_dict[ticket_number] = research_type_value


            # Return the dictionary with ticket numbers and research types as key-value pairs
            return ticket_info_dict

        else:
            # Handle errors if the JQL search request is unsuccessful
            print(f"JQL Error: {jql_response.status_code}, {api_response.get('errorMessages', 'Unknown error')}")
            return {}

    except requests.exceptions.RequestException as e:
        # Handle exceptions related to the HTTP request
        print(f"Request Error: {e}")
        return {}

add_comment函数

def add_comment(ticket_info_dict, jira_url, api_token, jira_username):
    """
    Add a comment to a Jira ticket with documentation links based on the research type.

    Args:
        ticket_info_dict (dict): A dictionary containing ticket numbers as keys and research types as values.
        jira_url (str): The URL of the Jira instance.
        api_token (str): The API token for authentication with Jira.
        jira_username (str): The username for authentication with Jira.

    Returns:
        bool: True if all comments were added successfully, False otherwise.
    """
    # Iterate through the ticket_info_dict to process each ticket and its research type
    for ticket_number, research_type in ticket_info_dict.items():
        # Set up the API endpoint for adding comments to the ticket
        print(ticket_number)
        add_comment_endpoint = f"{jira_url}/rest/api/3/issue/{ticket_number}/comment"

        # Encode credentials for authentication
        credentials = f"{jira_username}:{api_token}"
        base64_credentials = base64.b64encode(credentials.encode()).decode()

        headers = {
            "Authorization": f"Basic {base64_credentials}",
            "Content-Type": "application/json",
        }

        # Check if the research type exists in the documentation dictionary
        if research_type in documentation_dict:
            # If the research type is found, include the corresponding documentation links in the comment
            documentation_links = documentation_dict[research_type]

            # Create the comment body including the comment_text and documentation links
            comment_body = {
                "body": {
                    "content": [
                        {
                            "type": "paragraph",
                            "content": [
                                {"text": "This documentation relates to this research type:\n", "type": "text"},
                            ],
                        },
                        {
                            "type": "paragraph",
                            "content": [{"text": "Documentation Links:\n", "type": "text"}],
                        },
                        {
                            "type": "paragraph",
                            "content": [
                                {
                                    "text": f"-{doc_text}\n",
                                    "type": "text",
                                    "marks": [{"type": "link", "attrs": {"href": doc_link}}],
                                }
                                for doc_text, doc_link in documentation_links.items()
                            ],
                        },
                    ],
                    "type": "doc",
                    "version": 1
                },
                "properties": [
                    {
                        "key": "sd.public.comment",
                        "value": {
                            "internal": "true"
                        }
                    }
                ],
                "visibility": {
                        "type": "role",
                        "value": "Service Desk Team"
                    }
            }

            try:
                # Make the POST request to add the comment
                comment_response = requests.post(add_comment_endpoint, headers=headers, json=comment_body)

                # Check if the request was successful (HTTP status code 201)
                if comment_response.status_code == 201:
                    print(f"Comment added successfully to ticket {ticket_number}.")
                else:
                    print(f"Failed to add comment to ticket {ticket_number}. Status code: {comment_response.status_code}, Response: {comment_response.text}")

            except requests.exceptions.RequestException as e:
                # Handle exceptions related to the HTTP request
                print(f"Error: {e}")
                return False

        else:
            # If the research type is not found in the documentation dictionary, add a generic comment
            generic_comment = f"This ticket has a research type of '{research_type}' but no documentation links are available for this type."
            print(generic_comment)

    # Return True if all comments were added successfully
    return True

排查与修复方案

1. 验证Jira URL一致性

检查两个函数传入的jira_url是否完全一致,重点关注末尾是否带/:

  • 若jira_url为https://xxx.atlassian.net,拼接后接口为https://xxx.atlassian.net/rest/api/3/issue/GCPS-9679/comment
  • 若末尾多了/,会变成https://xxx.atlassian.net//rest/api/3/issue/GCPS-9679/comment,直接导致404

可以在两个函数中打印最终API端点对比:

# 在pull_ticket_info中添加
print(f"Search endpoint: {api_endpoint}")
# 在add_comment中添加
print(f"Comment endpoint: {add_comment_endpoint}")

2. 清理工单编号的不可见字符

即使打印的工单编号看起来正常,也可能存在全角空格、换行符等不可见字符。在add_comment中添加清理逻辑:

# 清理工单编号
cleaned_ticket_number = ticket_number.strip().replace('\n', '').replace('\r', '')
# 打印字符编码对比差异
print(f"Original chars: {[ord(c) for c in ticket_number]}")
print(f"Cleaned chars: {[ord(c) for c in cleaned_ticket_number]}")
# 使用清理后的编号拼接接口
add_comment_endpoint = f"{jira_url}/rest/api/3/issue/{cleaned_ticket_number}/comment"

3. 检查评论可见性配置合法性

add_comment中设置的可见性角色Service Desk Team可能不存在,或当前用户无权限使用该角色限制评论可见性。临时注释以下代码测试:

# "properties": [
#     {
#         "key": "sd.public.comment",
#         "value": {
#             "internal": "true"
#         }
#     }
# ],
# "visibility": {
#         "type": "role",
#         "value": "Service Desk Team"
#     }

4. 改用Jira官方SDK

原生requests易忽略API细节,改用jira库可简化会话管理和URL处理:

# 安装依赖
pip install jira

# 重构示例代码
from jira import JIRA

def pull_ticket_info(jql_query, jira_url, api_token, jira_username):
    jira = JIRA(server=jira_url, basic_auth=(jira_username, api_token))
    issues = jira.search_issues(jql_query)
    ticket_info_dict = {}
    for issue in issues:
        research_type_field = issue.fields.customfield_13427
        research_type_value = research_type_field.value if research_type_field else "N/A"
        ticket_info_dict[issue.key] = research_type_value
    return ticket_info_dict

def add_comment(ticket_info_dict, jira_url, api_token, jira_username):
    jira = JIRA(server=jira_url, basic_auth=(jira_username, api_token))
    for ticket_number, research_type in ticket_info_dict.items():
        try:
            issue = jira.issue(ticket_number)
            if research_type in documentation_dict:
                comment_text = "This documentation relates to this research type:\n\nDocumentation Links:\n"
                for doc_text, doc_link in documentation_dict[research_type].items():
                    comment_text += f"- [{doc_text}]({doc_link})\n"
                jira.add_comment(issue, comment_text, is_internal=True, visibility={"type": "role", "value": "Service Desk Team"})
                print(f"Comment added successfully to ticket {ticket_number}.")
            else:
                print(f"This ticket has a research type of '{research_type}' but no documentation links are available for this type.")
        except Exception as e:
            print(f"Failed to add comment to ticket {ticket_number}: {str(e)}")
    return True

内容的提问来源于stack exchange,提问作者Stephen Byrd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 01:09:57