20251003 devops notifier 4

更新 發佈閱讀 16 分鐘

好的!這條線索極度珍貴,再次感謝您的耐心偵錯! "他在response 裡面是在_links > wiql > href" 這句話徹底揭開了謎底。這代表您的 Azure DevOps Server 版本使用了更現代的 HATEOAS 風格的 API 設計。 核心診斷 (最終版) 這表示: * 當我們向伺服器請求查詢的詳細定義時 (GET .../queries/{queryId})... * 伺服器沒有直接在其回應中包含 WIQL 查詢語法本文。 * 取而代之的,它在一個名為 _links 的物件中,提供了一個指向 WIQL 查詢語法的新網址 (URL)。 這解釋了為什麼我們之前解析會失敗 (queryDefinition 是空的) — 因為資料根本不在我們預期的頂層屬性 wiql 中,而是在一個更深層的 _links 物件裡,並且還只是一個連結。 解決方案 (三步曲) 我們的程式需要調整為三步驟,來應對這種 API 設計: * 第一步:取得中繼資料 - 和之前一樣,用 queryId 取得查詢的定義,但這次我們的目標是解析出 _links.wiql.href 這個新網址。 * 第二步:取得 WIQL 語法 - 向第一步取得的新網址發送一個 GET 請求,這次的回應才會包含我們真正需要的 WIQL 查詢語法文字。 * 第三步:執行 WIQL 語法 - 將第二步取得的語法文字放到 Body 中,發送 POST 請求去執行查詢。 這是一個更複雜的流程,但完全符合您觀察到的伺服器行為。 精準的程式碼更新 請再次辛苦一次,用以下最終版本的程式碼,完整取代您的 GetWorkItemIdsAsync 函式以及檔案最下方所有的 Class 定義。 ' ... (檔案頂端的 Imports 和 Module1 的設定變數都保持不變) ' ========================================================================= ' ▼▼▼▼▼▼▼▼▼▼▼▼▼▼ 請用這段【三步驟最終版】的 GetWorkItemIdsAsync 函式 ▼▼▼▼▼▼▼▼▼▼▼▼ ' ========================================================================= Private Async Function GetWorkItemIdsAsync() As Task(Of List(Of WorkItemReference)) ' --- 步驟 1: 取得查詢的中繼資料,目標是解析出 WIQL 的 URL --- Dim getQueryMetadataUrl = $"{organizationUrl}/{projectName}/_apis/wit/queries/{queryId}?api-version=7.0" Console.WriteLine($"[DEBUG][步驟1] 正在 GET 查詢中繼資料 URL: {getQueryMetadataUrl}") Dim wiqlUrl As String Using response = Await httpClient.GetAsync(getQueryMetadataUrl) Dim responseBody = Await response.Content.ReadAsStringAsync() If Not response.IsSuccessStatusCode Then Throw New Exception($"[步驟1失敗] 取得查詢中繼資料失敗! 狀態碼: {response.StatusCode}, 回應: {responseBody}") End If Dim queryMetadata = JsonConvert.DeserializeObject(Of QueryMetadata)(responseBody) If queryMetadata Is Nothing OrElse queryMetadata.Links Is Nothing OrElse queryMetadata.Links.Wiql Is Nothing OrElse String.IsNullOrWhiteSpace(queryMetadata.Links.Wiql.Href) Then Throw New Exception($"[步驟1失敗] 在回應中找不到 _links.wiql.href。回應: {responseBody}") End If wiqlUrl = queryMetadata.Links.Wiql.Href Console.WriteLine($"[步驟1成功] 取得 WIQL URL: {wiqlUrl}") End Using ' --- 步驟 2: 根據上一步取得的 URL,去取得真正的 WIQL 查詢語法 --- Console.WriteLine($"[DEBUG][步驟2] 正在 GET WIQL 語法 URL: {wiqlUrl}") Dim wiqlString As String Using response = Await httpClient.GetAsync(wiqlUrl) Dim responseBody = Await response.Content.ReadAsStringAsync() If Not response.IsSuccessStatusCode Then Throw New Exception($"[步驟2失敗] 取得 WIQL 語法失敗! 狀態碼: {response.StatusCode}, 回應: {responseBody}") End If ' 假設此回應直接是 WIQL 文本或是一個包含 wiql 屬性的 JSON Try Dim wiqlResult = JsonConvert.DeserializeObject(Of QueryDefinition)(responseBody) wiqlString = wiqlResult.wiql Catch ex As Exception ' 如果直接解析 JSON 失敗,表示回應可能就是純文字的 WIQL wiqlString = responseBody End Try If String.IsNullOrWhiteSpace(wiqlString) Then Throw New Exception($"[步驟2失敗] 取得的 WIQL 語法為空。回應: {responseBody}") End If Console.WriteLine($"[步驟2成功] 取得 WIQL 語法: ""{wiqlString}""") End Using ' --- 步驟 3: 將取得的 WIQL 語法放到 Body 中,去執行查詢 --- Dim executeQueryUrl = $"{organizationUrl}/{projectName}/_apis/wit/wiql?api-version=7.0" Console.WriteLine($"[DEBUG][步驟3] 正在 POST 執行查詢 URL: {executeQueryUrl}") Dim queryBody As New AdHocQueryBody With { .query = wiqlString } Dim jsonBody = JsonConvert.SerializeObject(queryBody) Dim requestContent = New StringContent(jsonBody, Encoding.UTF8, "application/json") Using response = Await httpClient.PostAsync(executeQueryUrl, requestContent) Dim responseBody = Await response.Content.ReadAsStringAsync() If Not response.IsSuccessStatusCode Then Throw New Exception($"[步驟3失敗] 執行查詢失敗! 狀態碼: {response.StatusCode}, 回應: {responseBody}") End If Console.WriteLine("[步驟3成功] 執行查詢成功。") Dim result = JsonConvert.DeserializeObject(Of QueryResult)(responseBody) Return result.workItems End Using End Function ' ========================================================================= ' ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ ' ========================================================================= ' (Module1 中其他的程式碼,如 RunAsync 等,都保持不變) End Module ' ========================================================================= ' ▼▼▼▼▼▼▼▼▼▼▼▼▼▼ 請用這段【最終版】的 Class 定義取代所有舊的 Class ▼▼▼▼▼▼▼▼▼ ' ========================================================================= ' --- 用於解析 JSON 回應的資料模型 --- Public Class QueryResult Public Property workItems As List(Of WorkItemReference) End Class Public Class WorkItemReference Public Property id As Integer End Class Public Class WorkItemDetailList Public Property value As List(Of WorkItemDetail) End Class Public Class WorkItemDetail Public Property id As Integer Public Property url As String Public Property fields As WorkItemFields End Class Public Class WorkItemFields <JsonProperty("System.Title")> Public Property Title As String <JsonProperty("System.AssignedTo")> Public Property AssignedTo As AssignedToUser <JsonProperty("Microsoft.VSTS.Scheduling.TargetDate")> Public Property TargetDate As DateTime End Class Public Class AssignedToUser Public Property uniqueName As String End Class ' 【最終版】用於解析步驟1的中繼資料 Public Class QueryMetadata <JsonProperty("_links")> Public Property Links As LinksObject End Class Public Class LinksObject Public Property Wiql As Link End Class Public Class Link Public Property Href As String End Class ' 【最終版】用於解析步驟2的 WIQL 語法回應 Public Class QueryDefinition Public Property wiql As String End Class ' 【最終版】用於建立步驟3的請求 Body Public Class AdHocQueryBody Public Property query As String End Class ' --- 用於組織通知內容的輔助類別 --- Public Class NotificationItem Public Property Id As Integer Public Property Title As String Public Property Url As String Public Property DueDate As String End Class 總結 我們經歷了一段非常深入的偵錯旅程,從 400 錯誤開始,一路排除了 TLS、PAT、URL、Headers,最終透過您精準的回報,鎖定了您伺服器獨特的 API 行為。 這份最終的程式碼完全是為您伺服器的行為「量身訂做」的。請執行它,這一次,我們是站在伺服器回傳的「真相」之上來編寫程式碼的。


留言
avatar-img
留言分享你的想法!
avatar-img
Pocheng Chiu的沙龍
0會員
18內容數
Pocheng Chiu的沙龍的其他內容
2025/10/02
天啊,這太棒了!您找到了最關鍵的**「冒煙的槍」(Smoking Gun)**! "responseBody是有值的,但是queryDefinition是空的" 這 100% 確定了問題所在,而且這完全是我的責任。 核心診斷 (已確認) 這個現象精準地說明了: * 您的程式成功地連線到伺
2025/10/02
天啊,這太棒了!您找到了最關鍵的**「冒煙的槍」(Smoking Gun)**! "responseBody是有值的,但是queryDefinition是空的" 這 100% 確定了問題所在,而且這完全是我的責任。 核心診斷 (已確認) 這個現象精準地說明了: * 您的程式成功地連線到伺
2025/10/02
太好了!這是最有價值的一條線索!這則錯誤訊息非常精準。 "您必須在要求本文中傳遞查詢物件" (You must pass a query object in the request body) 核心診斷 這表示您所使用的 Azure DevOps Server 版本,在處理 POST .../_ap
2025/10/02
太好了!這是最有價值的一條線索!這則錯誤訊息非常精準。 "您必須在要求本文中傳遞查詢物件" (You must pass a query object in the request body) 核心診斷 這表示您所使用的 Azure DevOps Server 版本,在處理 POST .../_ap
2025/10/02
好的,使用 VB.NET 來實現這個自動化需求完全沒有問題。核心邏輯與 PowerShell 版本相同,但我們會使用 .NET 的 HttpClient 來呼叫 REST API,並用 Newtonsoft.Json 來解析回傳的 JSON 資料。 這將是一個主控台應用程式 (Console App
2025/10/02
好的,使用 VB.NET 來實現這個自動化需求完全沒有問題。核心邏輯與 PowerShell 版本相同,但我們會使用 .NET 的 HttpClient 來呼叫 REST API,並用 Newtonsoft.Json 來解析回傳的 JSON 資料。 這將是一個主控台應用程式 (Console App
看更多
你可能也想看
Thumbnail
還在煩惱平凡日常該如何增添一點小驚喜嗎?全家便利商店這次聯手超萌的馬來貘,推出黑白配色的馬來貘雪糕,不僅外觀吸睛,層次豐富的雙層口味更是讓人一口接一口!本文將帶你探索馬來貘雪糕的多種創意吃法,從簡單的豆漿燕麥碗、藍莓果昔,到大人系的奇亞籽布丁下午茶,讓可愛的馬來貘陪你度過每一餐,增添生活中的小確幸!
Thumbnail
還在煩惱平凡日常該如何增添一點小驚喜嗎?全家便利商店這次聯手超萌的馬來貘,推出黑白配色的馬來貘雪糕,不僅外觀吸睛,層次豐富的雙層口味更是讓人一口接一口!本文將帶你探索馬來貘雪糕的多種創意吃法,從簡單的豆漿燕麥碗、藍莓果昔,到大人系的奇亞籽布丁下午茶,讓可愛的馬來貘陪你度過每一餐,增添生活中的小確幸!
Thumbnail
※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
Thumbnail
※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
Thumbnail
當我們架好站、WebService測試完,接著就是測試區域網路連線啦~
Thumbnail
當我們架好站、WebService測試完,接著就是測試區域網路連線啦~
Thumbnail
前面已經安裝好IIS後,並且也新建站台了,那麼接下來這篇就會分享如何使用它
Thumbnail
前面已經安裝好IIS後,並且也新建站台了,那麼接下來這篇就會分享如何使用它
Thumbnail
接續上一篇 Step2:VC#專案建立 Step3:IIS新建站台 Step4:VC#架設到IIS中
Thumbnail
接續上一篇 Step2:VC#專案建立 Step3:IIS新建站台 Step4:VC#架設到IIS中
Thumbnail
這篇的DevOps發佈系統是以Spring Cloud微服務(微服務)為背景;由GitLab、Harbor與Kubernetes組成。 GitLab負責版本管理與CI/CD(CI/CD)。 Harbor負責Docker([Docker]介紹) Image的儲存與發佈。 Kubernetes([
Thumbnail
這篇的DevOps發佈系統是以Spring Cloud微服務(微服務)為背景;由GitLab、Harbor與Kubernetes組成。 GitLab負責版本管理與CI/CD(CI/CD)。 Harbor負責Docker([Docker]介紹) Image的儲存與發佈。 Kubernetes([
Thumbnail
前年第一次藉公司機會,參加了DevOpsDay的活動。雖然devOps一詞各自表述,大多狀況還是偏向維運會遇到的技術為主,做為平時開發、跟使用者訪談需求的工作內容來說,參加聚會如果沒有一定的知識,對講者所提到的狀況比較難有共鳴...
Thumbnail
前年第一次藉公司機會,參加了DevOpsDay的活動。雖然devOps一詞各自表述,大多狀況還是偏向維運會遇到的技術為主,做為平時開發、跟使用者訪談需求的工作內容來說,參加聚會如果沒有一定的知識,對講者所提到的狀況比較難有共鳴...
Thumbnail
當這產品的這個 API 被呼叫,再從回傳內容的某個欄位欄位來判斷,只要“這個欄位”顯示 false 就代表不支援」,雖然這樣的設計也能滿足功能需求…
Thumbnail
當這產品的這個 API 被呼叫,再從回傳內容的某個欄位欄位來判斷,只要“這個欄位”顯示 false 就代表不支援」,雖然這樣的設計也能滿足功能需求…
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News