zentao_dev.sh 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  1. #!/bin/bash
  2. # ============================================================================
  3. # 禅道 Bug 追踪模块 - DEV 开发环境专用版本
  4. # ============================================================================
  5. # ⚠️ 重要提示:
  6. # 此文件仅供 Jenkinsfile.dev 使用
  7. # Jenkinsfile.release 严禁使用此文件,必须使用 zentao_dev.sh
  8. #
  9. # 功能说明:
  10. # 1. 从 Git 提交历史中提取 Bug 修复信息
  11. # 2. 调用禅道 API 自动更新 Bug 状态为 "resolved"
  12. # 3. 发送飞书(Lark)通知 - 每个 Bug 单独发送绿色卡片
  13. # 4. 发送飞书版本发布汇总通知 - 蓝色卡片汇总所有已解决Bug
  14. # 5. 发送 Telegram 汇总通知
  15. #
  16. # 使用场景:
  17. # - dev 环境: 每次代码推送自动执行
  18. # - 分析范围: 从上次成功构建到当前 HEAD 的所有提交
  19. # - 自动更新: 将 active/wait 状态的 Bug 更新为 resolved
  20. # - 即时通知: 每解决一个 Bug 立即发送飞书通知
  21. #
  22. # 环境隔离:
  23. # ✅ 仅用于开发环境 (Jenkinsfile.dev)
  24. # ❌ 不得用于生产发布 (Jenkinsfile.release)
  25. # ============================================================================
  26. # ----------------------------------------------------------------------------
  27. # 配置部分
  28. # ----------------------------------------------------------------------------
  29. # ⚠️ 重要提示:
  30. # 所有配置项必须从 Jenkinsfile 的 environment 部分传入
  31. # 此脚本不包含任何硬编码配置,确保配置集中管理
  32. #
  33. # 必需的环境变量:
  34. # - ZENTAO_PRODUCT_ID: 禅道产品 ID
  35. # - ZENTAO_API_URL: 禅道 API 地址
  36. # - ZENTAO_BUG_BASE_URL: 禅道 Bug 详情页基础 URL
  37. # - COMMIT_BASE_URL: Git 提交详情页基础 URL
  38. # - LARK_WEBHOOK_URL: Lark Webhook URL
  39. # ----------------------------------------------------------------------------
  40. # 验证必需的环境变量
  41. if [ -z "${ZENTAO_PRODUCT_ID}" ] || [ -z "${ZENTAO_API_URL}" ] ||
  42. [ -z "${ZENTAO_BUG_BASE_URL}" ] || [ -z "${COMMIT_BASE_URL}" ] ||
  43. [ -z "${LARK_WEBHOOK_URL}" ]; then
  44. echo "错误: 缺少必需的环境变量配置" >&2
  45. echo "请确保在 Jenkinsfile 的 environment 部分设置了所有必需变量" >&2
  46. exit 1
  47. fi
  48. # ----------------------------------------------------------------------------
  49. # 函数: 发送单个 Bug 解决通知到飞书
  50. # ----------------------------------------------------------------------------
  51. # 参数:
  52. # $1: bug_id - Bug ID (例如: 2262)
  53. # $2: bug_title - Bug 标题 (例如: "登录页面404错误")
  54. # $3: old_status - 旧状态 (例如: "active")
  55. # $4: commit_info - 提交信息 (例如: "[abc123] 修复登录问题")
  56. # $5: bug_url - Bug 详情页 URL
  57. #
  58. # 功能:
  59. # 发送一个绿色的飞书卡片,显示单个 Bug 的解决信息,包含查看详情按钮
  60. # ----------------------------------------------------------------------------
  61. send_bug_resolved_notification() {
  62. local bug_id="$1"
  63. local bug_title="$2"
  64. local old_status="$3"
  65. local commit_info="$4"
  66. local bug_url="$5"
  67. local code_url="$6"
  68. # 发送 POST 请求到飞书 Webhook
  69. curl -s -X POST "${LARK_WEBHOOK_URL}" \
  70. -H "Content-Type: application/json" \
  71. -d "{
  72. \"msg_type\": \"interactive\",
  73. \"card\": {
  74. \"header\": {
  75. \"title\": {
  76. \"tag\": \"plain_text\",
  77. \"content\": \"🐛 pwtk管端端Bug更新\"
  78. },
  79. \"template\": \"green\"
  80. },
  81. \"elements\": [
  82. {
  83. \"tag\": \"div\",
  84. \"text\": {
  85. \"tag\": \"lark_md\",
  86. \"content\": \"**Bug #${bug_id}**: ${bug_title}\"
  87. }
  88. },
  89. {
  90. \"tag\": \"action\",
  91. \"actions\": [
  92. {
  93. \"tag\": \"button\",
  94. \"text\": {
  95. \"tag\": \"plain_text\",
  96. \"content\": \"查看代码\"
  97. },
  98. \"type\": \"primary\",
  99. \"url\": \"${code_url}\"
  100. }
  101. ]
  102. }
  103. ]
  104. }
  105. }" >/dev/null
  106. }
  107. # ----------------------------------------------------------------------------
  108. # 函数: 发送版本发布汇总通知到飞书
  109. # ----------------------------------------------------------------------------
  110. # 参数:
  111. # $1: previous_tag - 上一个版本标签 (例如: "v1.2.30" 或 "dev")
  112. # $2: current_tag - 当前版本标签 (例如: "v1.2.31" 或 "abc123")
  113. # $3: resolved_count - 已解决的 Bug 数量
  114. # $4: resolved_bugs - 已解决的 Bug 列表 (格式: "bug_info|bug_url" 每行一个)
  115. #
  116. # 功能:
  117. # 发送一个蓝色的飞书卡片,汇总本次发布解决的所有 Bug
  118. # ----------------------------------------------------------------------------
  119. send_release_summary_notification() {
  120. local previous_tag="$1"
  121. local current_tag="$2"
  122. local resolved_count="$3"
  123. local resolved_bugs="$4"
  124. # 构建已解决 Bug 链接列表
  125. local resolved_links=""
  126. if [ -n "$resolved_bugs" ]; then
  127. # 逐行读取 Bug 信息,格式化为 Markdown 链接
  128. while IFS='|' read -r bug_info bug_url; do
  129. if [ -n "$bug_info" ]; then
  130. resolved_links="${resolved_links}• [${bug_info}](${bug_url})\\n"
  131. fi
  132. done <<<"$resolved_bugs"
  133. fi
  134. # 构建完整消息内容
  135. local content="**版本**: ${previous_tag} → ${current_tag}\\n\\n"
  136. if [ ${resolved_count} -gt 0 ]; then
  137. content="${content}**已解决Bug** (${resolved_count}个):\\n${resolved_links}"
  138. else
  139. content="${content}**本次发布未解决Bug**"
  140. fi
  141. # 发送 POST 请求到飞书 Webhook
  142. curl -s -X POST "${LARK_WEBHOOK_URL}" \
  143. -H "Content-Type: application/json" \
  144. -d "{
  145. \"msg_type\": \"interactive\",
  146. \"card\": {
  147. \"header\": {
  148. \"title\": {
  149. \"tag\": \"plain_text\",
  150. \"content\": \"🚀 pwtk管端端版本发布\"
  151. },
  152. \"template\": \"blue\"
  153. },
  154. \"elements\": [
  155. {
  156. \"tag\": \"div\",
  157. \"text\": {
  158. \"tag\": \"lark_md\",
  159. \"content\": \"${content}\"
  160. }
  161. }
  162. ]
  163. }
  164. }" >/dev/null
  165. }
  166. # ----------------------------------------------------------------------------
  167. # 函数: 禅道 Bug 追踪主流程
  168. # ----------------------------------------------------------------------------
  169. # 参数:
  170. # $1: start_ref - 起始 Git 引用 (例如: "HEAD~10" 或 "v1.2.30")
  171. # $2: end_ref - 结束 Git 引用 (例如: "HEAD" 或 "v1.2.31")
  172. # $3: version_info - 版本信息 (例如: "dev (abc123)" 或 "v1.2.30 → v1.2.31")
  173. # $4: update_status - 是否更新禅道状态 ("true"=更新, "false"=只汇总)
  174. #
  175. # 功能:
  176. # 1. 从 Git 提交历史中提取符合格式的 Bug 修复 (fix:2262, fix-2262, fix: 2262 2265)
  177. # 2. 查询禅道获取 Bug 状态和标题
  178. # 3. 如果 update_status=true,调用禅道 API 更新 Bug 状态为 "resolved"
  179. # 4. 发送飞书通知 (每个 Bug 单独发送 + 最后发送汇总)
  180. # 5. 发送 Telegram 汇总通知
  181. #
  182. # 提交格式支持:
  183. # - fix:2262 修复登录问题
  184. # - fix-2262 修复登录问题
  185. # - fix: 2262 修复登录问题
  186. # - fix: 2262 2265 修复多个问题 (一次提交修复多个 Bug)
  187. # ----------------------------------------------------------------------------
  188. process_zentao_bugs() {
  189. local start_ref="$1"
  190. local end_ref="$2"
  191. local version_info="$3"
  192. local update_status="${4:-false}" # 默认为 false,只汇总不更新
  193. # 输出开始信息
  194. if type -t log_message >/dev/null 2>&1; then
  195. log_message "=============== 禅道 Bug 追踪开始 ==============="
  196. log_message "分析范围: ${start_ref}..${end_ref}"
  197. log_message "版本信息: ${version_info}"
  198. log_message "更新禅道状态: ${update_status}"
  199. fi
  200. # 初始化 bug 列表关联数组
  201. # 格式: BUG_MAP[bug_id] = "commit_hash|author|message\ncommit_hash|author|message"
  202. declare -A BUG_MAP
  203. # 获取所有符合格式的提交 (保存到变量以避免管道子shell问题)
  204. # 格式: commit_hash|author|subject
  205. COMMITS=$(git log "${start_ref}..${end_ref}" --pretty=format:"%h|%an|%s")
  206. # 遍历每个提交,提取 Bug ID
  207. while IFS='|' read -r commit_hash author commit_message; do
  208. # 检查提交信息是否包含 fix: 或 fix- 格式
  209. if echo "$commit_message" | grep -qE "fix[:-]\s*[0-9]"; then
  210. # 提取所有 Bug ID (支持多个 Bug ID,用空格分隔)
  211. # 例如: "fix: 2262 2265" 提取出 "2262 2265"
  212. # 步骤1: 提取 fix: 或 fix- 后面的所有数字
  213. # 步骤2: 移除 fix: 或 fix-
  214. # 步骤3: 使用 xargs 自动trim前后空格
  215. bug_ids=$(echo "$commit_message" | grep -oE "fix[:-]\s*([0-9]+(\s+[0-9]+)*)" | sed -E 's/fix[:-]//' | xargs)
  216. if [ -n "$bug_ids" ]; then
  217. if type -t log_message >/dev/null 2>&1; then
  218. log_message "发现 Bug 修复: [$commit_hash] $author - $commit_message"
  219. fi
  220. # 分割多个 Bug ID,逐个处理
  221. for bug_id in $bug_ids; do
  222. if [ -n "$bug_id" ]; then
  223. # 存储到关联数组 (如果已存在则追加,支持一个 Bug 多次提交修复)
  224. if [ -n "${BUG_MAP[$bug_id]}" ]; then
  225. BUG_MAP[$bug_id]="${BUG_MAP[$bug_id]}
  226. ${commit_hash}|${author}|${commit_message}"
  227. else
  228. BUG_MAP[$bug_id]="${commit_hash}|${author}|${commit_message}"
  229. fi
  230. fi
  231. done
  232. fi
  233. fi
  234. done <<<"$COMMITS"
  235. # 统计发现的 Bug 数量
  236. BUG_COUNT=${#BUG_MAP[@]}
  237. if type -t log_message >/dev/null 2>&1; then
  238. log_message "共发现 ${BUG_COUNT} 个 Bug"
  239. fi
  240. # 如果没有发现 Bug,直接返回
  241. if [ $BUG_COUNT -eq 0 ]; then
  242. if type -t log_message >/dev/null 2>&1; then
  243. log_message "本次构建未发现符合格式的 Bug 修复"
  244. log_message "=============== 禅道 Bug 追踪结束 ==============="
  245. fi
  246. return 0
  247. fi
  248. # 获取禅道 API Token
  249. if type -t log_message >/dev/null 2>&1; then
  250. log_message "正在获取禅道 API Token..."
  251. fi
  252. # 调用禅道登录接口获取 Token
  253. ZENTAO_TOKEN_RESPONSE=$(curl -s -X POST "${ZENTAO_API_URL}/tokens" \
  254. -H "Content-Type: application/json" \
  255. -d "{\"account\":\"${ZENTAO_USERNAME}\",\"password\":\"${ZENTAO_PASSWORD}\"}")
  256. # 从响应中提取 Token
  257. ZENTAO_TOKEN=$(echo "$ZENTAO_TOKEN_RESPONSE" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
  258. # 检查 Token 是否获取成功
  259. if [ -z "$ZENTAO_TOKEN" ]; then
  260. if type -t log_message >/dev/null 2>&1; then
  261. log_message "警告: 无法获取禅道 Token,仅生成链接列表"
  262. log_message "响应: ${ZENTAO_TOKEN_RESPONSE}"
  263. fi
  264. # Token 获取失败,只生成链接列表不更新状态
  265. BUG_LINKS=""
  266. for bug_id in $(echo "${!BUG_MAP[@]}" | tr ' ' '\n' | sort -n); do
  267. COMMIT_INFO=""
  268. # 遍历该 Bug 的所有相关提交
  269. while IFS= read -r commit_line; do
  270. commit_hash=$(echo "$commit_line" | cut -d'|' -f1)
  271. author=$(echo "$commit_line" | cut -d'|' -f2)
  272. COMMIT_INFO="${COMMIT_INFO} 修复人: ${author} [${commit_hash}]
  273. "
  274. done <<<"${BUG_MAP[$bug_id]}"
  275. BUG_LINKS="${BUG_LINKS}#${bug_id}
  276. ${COMMIT_INFO} ${ZENTAO_BUG_BASE_URL}${bug_id}.html
  277. "
  278. done
  279. # 发送 Telegram 通知 (包含警告信息)
  280. if type -t send_telegram_message >/dev/null 2>&1; then
  281. send_telegram_message "🐛 Bug 修复汇总
  282. 版本: ${version_info}
  283. ${BUG_LINKS}
  284. 📊 总计: ${BUG_COUNT} 个 Bug 已修复
  285. ⚠️ 禅道状态未更新(Token获取失败)"
  286. fi
  287. return 1
  288. fi
  289. if type -t log_message >/dev/null 2>&1; then
  290. log_message "成功获取禅道 Token"
  291. fi
  292. # 初始化汇总信息
  293. BUG_LINKS="" # Telegram 消息用的链接列表
  294. RESOLVED_COUNT=0 # 已解决的 Bug 数量
  295. ERROR_COUNT=0 # 失败的 Bug 数量
  296. RESOLVED_BUGS_LIST="" # 飞书汇总用的 Bug 列表
  297. # 处理每个 Bug (按 Bug ID 排序)
  298. for bug_id in $(echo "${!BUG_MAP[@]}" | tr ' ' '\n' | sort -n); do
  299. if type -t log_message >/dev/null 2>&1; then
  300. log_message "----------------------------------------"
  301. log_message "处理 Bug #${bug_id}"
  302. fi
  303. # 查询 Bug 状态
  304. BUG_INFO=$(curl -s -X GET "${ZENTAO_API_URL}/bugs/${bug_id}" \
  305. -H "Token: ${ZENTAO_TOKEN}")
  306. # 使用 Python 解析 JSON,正确处理中文
  307. BUG_STATUS=$(echo "$BUG_INFO" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('status', ''))" 2>/dev/null)
  308. BUG_TITLE=$(echo "$BUG_INFO" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('title', ''))" 2>/dev/null)
  309. BUG_OPENED_BY=$(echo "$BUG_INFO" | python3 -c "import sys, json; data=json.load(sys.stdin); openedBy=data.get('openedBy', {}); print(openedBy.get('account', '') if isinstance(openedBy, dict) else '')" 2>/dev/null)
  310. # 检查是否成功获取 Bug 信息
  311. if [ -z "$BUG_STATUS" ]; then
  312. if type -t log_message >/dev/null 2>&1; then
  313. log_message "警告: 无法获取 Bug #${bug_id} 的状态信息"
  314. fi
  315. ERROR_COUNT=$((ERROR_COUNT + 1))
  316. # 添加到失败列表
  317. COMMIT_INFO=""
  318. while IFS= read -r commit_line; do
  319. commit_hash=$(echo "$commit_line" | cut -d'|' -f1)
  320. author=$(echo "$commit_line" | cut -d'|' -f2)
  321. COMMIT_INFO="${COMMIT_INFO} 修复人: ${author} [${commit_hash}]
  322. "
  323. done <<<"${BUG_MAP[$bug_id]}"
  324. BUG_LINKS="${BUG_LINKS}❌ #${bug_id} (查询失败)
  325. ${COMMIT_INFO} ${ZENTAO_BUG_BASE_URL}${bug_id}.html
  326. "
  327. continue
  328. fi
  329. if type -t log_message >/dev/null 2>&1; then
  330. log_message "Bug #${bug_id} 状态: ${BUG_STATUS}"
  331. log_message "Bug #${bug_id} 标题: ${BUG_TITLE}"
  332. log_message "Bug #${bug_id} 创建者: ${BUG_OPENED_BY}"
  333. fi
  334. # 获取提交信息 (所有情况都需要)
  335. COMMIT_INFO=""
  336. while IFS= read -r commit_line; do
  337. commit_hash=$(echo "$commit_line" | cut -d'|' -f1)
  338. author=$(echo "$commit_line" | cut -d'|' -f2)
  339. COMMIT_INFO="${COMMIT_INFO} 修复人: ${author} [${commit_hash}]
  340. "
  341. done <<<"${BUG_MAP[$bug_id]}"
  342. # 检查是否为未解决状态 (active 或 wait 状态才需要处理)
  343. if [ "$BUG_STATUS" = "active" ] || [ "$BUG_STATUS" = "wait" ]; then
  344. # 根据 update_status 参数决定是否更新禅道状态
  345. if [ "$update_status" = "true" ]; then
  346. if type -t log_message >/dev/null 2>&1; then
  347. log_message "Bug #${bug_id} 状态为未解决,准备更新为已解决..."
  348. fi
  349. # 准备评论内容 (包含所有相关 commit 信息)
  350. COMMENT="已修复该问题
  351. 相关提交:"
  352. while IFS= read -r commit_info; do
  353. commit_hash=$(echo "$commit_info" | cut -d'|' -f1)
  354. author=$(echo "$commit_info" | cut -d'|' -f2)
  355. message=$(echo "$commit_info" | cut -d'|' -f3)
  356. COMMENT="${COMMENT}
  357. - [${commit_hash}] ${author}: ${message}"
  358. done <<<"${BUG_MAP[$bug_id]}"
  359. COMMENT="${COMMENT}
  360. 代码提交: <a href=\"${COMMIT_BASE_URL}${commit_hash}\" target=\"_blank\">点击查看</a>"
  361. # 生成解决 Bug 的 JSON 临时文件
  362. RESOLVE_JSON_FILE="/tmp/zentao_resolve_${bug_id}_$$.json"
  363. # 确定指派对象:优先使用创建者,如果获取失败则使用当前用户
  364. ASSIGNEE="${BUG_OPENED_BY}"
  365. if [ -z "$ASSIGNEE" ]; then
  366. if type -t log_message >/dev/null 2>&1; then
  367. log_message "警告: 无法获取 Bug #${bug_id} 创建者,将指派给当前用户 ${ZENTAO_USERNAME}"
  368. fi
  369. ASSIGNEE="${ZENTAO_USERNAME}"
  370. fi
  371. # 使用 Python 生成 JSON (确保正确处理中文和特殊字符)
  372. python3 -c "import json; print(json.dumps({
  373. 'resolution': 'fixed',
  374. 'resolvedBuild': 'trunk',
  375. 'resolvedDate': '$(date "+%Y-%m-%d %H:%M:%S")',
  376. 'assignedTo': '${ASSIGNEE}',
  377. 'comment': '''${COMMENT}'''
  378. }, ensure_ascii=False, indent=2))" >"$RESOLVE_JSON_FILE"
  379. # 调用禅道 API 解决 Bug
  380. RESOLVE_RESPONSE=$(curl -s -X POST "${ZENTAO_API_URL}/bugs/${bug_id}/resolve" \
  381. -H "Token: ${ZENTAO_TOKEN}" \
  382. -H "Content-Type: application/json" \
  383. -d @"$RESOLVE_JSON_FILE")
  384. # 清理临时文件
  385. rm -f "$RESOLVE_JSON_FILE"
  386. # 检查是否成功 (响应包含 "id" 字段表示成功)
  387. if echo "$RESOLVE_RESPONSE" | grep -q '"id"'; then
  388. if type -t log_message >/dev/null 2>&1; then
  389. log_message "✅ 成功解决 Bug #${bug_id}"
  390. fi
  391. RESOLVED_COUNT=$((RESOLVED_COUNT + 1))
  392. BUG_LINKS="${BUG_LINKS}✅ #${bug_id} - ${BUG_TITLE}
  393. ${COMMIT_INFO} ${ZENTAO_BUG_BASE_URL}${bug_id}.html
  394. "
  395. # 添加到已解决 Bug 列表 (用于飞书汇总通知)
  396. RESOLVED_BUGS_LIST="${RESOLVED_BUGS_LIST}#${bug_id} ${BUG_TITLE}|${ZENTAO_BUG_BASE_URL}${bug_id}.html
  397. "
  398. # 发送飞书单个 Bug 通知
  399. BUG_URL="${ZENTAO_BUG_BASE_URL}${bug_id}.html"
  400. CODE_URL="${COMMIT_BASE_URL}${commit_hash}"
  401. COMMIT_SUMMARY=$(echo "${BUG_MAP[$bug_id]}" | head -1 | awk -F'|' '{print "[" $1 "] " $3}')
  402. send_bug_resolved_notification "${bug_id}" "${BUG_TITLE}" "${BUG_STATUS}" "${COMMIT_SUMMARY}" "${BUG_URL}" "${CODE_URL}"
  403. if type -t log_message >/dev/null 2>&1; then
  404. log_message "飞书通知已发送 (Bug #${bug_id})"
  405. fi
  406. else
  407. # 更新失败
  408. if type -t log_message >/dev/null 2>&1; then
  409. log_message "❌ 解决 Bug #${bug_id} 失败: ${RESOLVE_RESPONSE}"
  410. fi
  411. ERROR_COUNT=$((ERROR_COUNT + 1))
  412. BUG_LINKS="${BUG_LINKS}❌ #${bug_id} - ${BUG_TITLE} (更新失败)
  413. ${COMMIT_INFO} ${ZENTAO_BUG_BASE_URL}${bug_id}.html
  414. "
  415. fi
  416. else
  417. # update_status = false: 只汇总,不更新禅道状态
  418. if type -t log_message >/dev/null 2>&1; then
  419. log_message "Bug #${bug_id} 状态为未解决,但只汇总不更新禅道状态"
  420. fi
  421. RESOLVED_COUNT=$((RESOLVED_COUNT + 1))
  422. BUG_LINKS="${BUG_LINKS}📋 #${bug_id} - ${BUG_TITLE}
  423. ${COMMIT_INFO} ${ZENTAO_BUG_BASE_URL}${bug_id}.html
  424. "
  425. # 添加到已解决 Bug 列表
  426. RESOLVED_BUGS_LIST="${RESOLVED_BUGS_LIST}#${bug_id} ${BUG_TITLE}|${ZENTAO_BUG_BASE_URL}${bug_id}.html
  427. "
  428. fi
  429. elif [ "$BUG_STATUS" = "resolved" ] || [ "$BUG_STATUS" = "closed" ]; then
  430. # Bug 已经是 resolved 或 closed 状态,跳过处理
  431. if type -t log_message >/dev/null 2>&1; then
  432. log_message "Bug #${bug_id} 已经是 ${BUG_STATUS} 状态,跳过更新(不显示在通知中)"
  433. fi
  434. # 不添加到 BUG_LINKS,不计数,不发通知
  435. else
  436. # 其他状态 (例如: testing),跳过处理
  437. if type -t log_message >/dev/null 2>&1; then
  438. log_message "Bug #${bug_id} 状态为 ${BUG_STATUS},跳过更新(不显示在通知中)"
  439. fi
  440. # 不添加到 BUG_LINKS,不计数,不发通知
  441. fi
  442. done
  443. # 输出汇总信息
  444. if type -t log_message >/dev/null 2>&1; then
  445. log_message "=============== 禅道 Bug 追踪完成 ==============="
  446. log_message "总计: ${BUG_COUNT} 个 Bug"
  447. log_message "已解决: ${RESOLVED_COUNT} 个"
  448. log_message "失败: ${ERROR_COUNT} 个"
  449. fi
  450. # 发送 Telegram 汇总通知
  451. if type -t send_telegram_message >/dev/null 2>&1; then
  452. send_telegram_message "🐛 Bug 修复汇总
  453. 版本: ${version_info}
  454. ${BUG_LINKS}
  455. 📊 统计:
  456. - 已解决: ${RESOLVED_COUNT} 个
  457. - 失败: ${ERROR_COUNT} 个"
  458. fi
  459. # 发送飞书版本发布汇总通知 (仅在有解决 Bug 时发送)
  460. if [ ${RESOLVED_COUNT} -gt 0 ] || [ ${ERROR_COUNT} -gt 0 ]; then
  461. if type -t log_message >/dev/null 2>&1; then
  462. log_message "发送飞书版本发布汇总通知..."
  463. fi
  464. send_release_summary_notification "${start_ref}" "${end_ref}" "${RESOLVED_COUNT}" "${RESOLVED_BUGS_LIST}"
  465. if type -t log_message >/dev/null 2>&1; then
  466. log_message "飞书版本发布通知已发送"
  467. fi
  468. else
  469. if type -t log_message >/dev/null 2>&1; then
  470. log_message "本次构建无Bug状态变更,跳过飞书版本汇总通知"
  471. fi
  472. fi
  473. if type -t log_message >/dev/null 2>&1; then
  474. log_message "=============== 禅道 Bug 追踪结束 ==============="
  475. fi
  476. }
  477. resolve_zentao_bugs() {
  478. local workspace_dir="${1:-${WORKSPACE:-.}}"
  479. local last_success_file="${workspace_dir}/.last_success_commit"
  480. if type -t log_message >/dev/null 2>&1; then
  481. log_message "=============== 禅道 Bug 自动解决开始 ==============="
  482. fi
  483. # 确定分析范围
  484. LAST_SUCCESS_HASH=$(cat "$last_success_file" 2>/dev/null || echo "")
  485. if [ -z "$LAST_SUCCESS_HASH" ]; then
  486. if type -t log_message >/dev/null 2>&1; then
  487. log_message "首次构建,分析最近 20 个包含 fix: 的提交"
  488. fi
  489. # 获取最近 20 个包含 fix: 的提交
  490. FIX_COMMITS=$(git log -20 --pretty=format:"%h|%an|%s" | grep -E "fix[:-]\s*[0-9]" || true)
  491. else
  492. if type -t log_message >/dev/null 2>&1; then
  493. log_message "上次成功构建: ${LAST_SUCCESS_HASH}"
  494. log_message "分析从 ${LAST_SUCCESS_HASH} 到 HEAD 的所有 fix: 提交"
  495. fi
  496. # 获取从上次成功构建到 HEAD 的所有包含 fix: 的提交
  497. FIX_COMMITS=$(git log ${LAST_SUCCESS_HASH}..HEAD --pretty=format:"%h|%an|%s" | grep -E "fix[:-]\s*[0-9]" || true)
  498. fi
  499. if [ -z "$FIX_COMMITS" ]; then
  500. if type -t log_message >/dev/null 2>&1; then
  501. log_message "未发现包含 Bug 修复标记 (fix:) 的提交"
  502. log_message "=============== 禅道 Bug 自动解决结束 ==============="
  503. fi
  504. return 0
  505. fi
  506. # 统计找到的提交数
  507. COMMIT_COUNT=$(echo "$FIX_COMMITS" | wc -l | tr -d ' ')
  508. if type -t log_message >/dev/null 2>&1; then
  509. log_message "找到 ${COMMIT_COUNT} 个包含 fix: 的提交"
  510. fi
  511. # 初始化 bug 映射数组(bug_id => 最新的提交信息)
  512. # 使用关联数组存储每个 bug 的最新提交(最上面的提交是最新的)
  513. declare -A BUG_MAP
  514. # 解析提交,提取 bug IDs(从新到旧的顺序)
  515. while IFS='|' read -r commit_hash author commit_message; do
  516. # 提取所有 bug IDs
  517. if echo "$commit_message" | grep -qE "fix[:-]\s*[0-9]"; then
  518. bug_ids=$(echo "$commit_message" | grep -oE "fix[:-]\s*([0-9]+(\s+[0-9]+)*)" | sed -E 's/fix[:-]\s*//')
  519. for bug_id in $bug_ids; do
  520. if [ -n "$bug_id" ]; then
  521. # 只在 bug 第一次出现时记录(即最新的提交)
  522. if [ -z "${BUG_MAP[$bug_id]}" ]; then
  523. BUG_MAP[$bug_id]="${commit_hash}|${author}|${commit_message}"
  524. if type -t log_message >/dev/null 2>&1; then
  525. log_message "发现 Bug #${bug_id}: [$commit_hash] $commit_message"
  526. fi
  527. fi
  528. fi
  529. done
  530. fi
  531. done <<<"$FIX_COMMITS"
  532. # 统计发现的 bug 数量
  533. BUG_COUNT=${#BUG_MAP[@]}
  534. if type -t log_message >/dev/null 2>&1; then
  535. log_message "共发现 ${BUG_COUNT} 个唯一的 Bug"
  536. fi
  537. if [ $BUG_COUNT -eq 0 ]; then
  538. if type -t log_message >/dev/null 2>&1; then
  539. log_message "未能提取到有效的 Bug ID"
  540. log_message "=============== 禅道 Bug 自动解决结束 ==============="
  541. fi
  542. return 0
  543. fi
  544. # 获取禅道 Token
  545. if type -t log_message >/dev/null 2>&1; then
  546. log_message "正在获取禅道 API Token..."
  547. fi
  548. ZENTAO_TOKEN_RESPONSE=$(curl -s -X POST "${ZENTAO_API_URL}/tokens" \
  549. -H "Content-Type: application/json" \
  550. -d "{\"account\":\"${ZENTAO_USERNAME}\",\"password\":\"${ZENTAO_PASSWORD}\"}")
  551. ZENTAO_TOKEN=$(echo "$ZENTAO_TOKEN_RESPONSE" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
  552. if [ -z "$ZENTAO_TOKEN" ]; then
  553. if type -t log_message >/dev/null 2>&1; then
  554. log_message "错误: 无法获取禅道 Token"
  555. log_message "响应: ${ZENTAO_TOKEN_RESPONSE}"
  556. log_message "=============== 禅道 Bug 自动解决结束 ==============="
  557. fi
  558. return 1
  559. fi
  560. if type -t log_message >/dev/null 2>&1; then
  561. log_message "成功获取禅道 Token"
  562. fi
  563. # 初始化统计计数器
  564. RESOLVED_COUNT=0
  565. SKIPPED_COUNT=0
  566. ERROR_COUNT=0
  567. # 处理每个 bug(按 bug ID 排序)
  568. for bug_id in $(echo "${!BUG_MAP[@]}" | tr ' ' '\n' | sort -n); do
  569. if type -t log_message >/dev/null 2>&1; then
  570. log_message "----------------------------------------"
  571. log_message "处理 Bug #${bug_id}"
  572. fi
  573. # 获取该 bug 的最新提交信息
  574. commit_info="${BUG_MAP[$bug_id]}"
  575. commit_hash=$(echo "$commit_info" | cut -d'|' -f1)
  576. author=$(echo "$commit_info" | cut -d'|' -f2)
  577. commit_message=$(echo "$commit_info" | cut -d'|' -f3)
  578. # 查询 bug 详情
  579. BUG_INFO=$(curl -s -X GET "${ZENTAO_API_URL}/bugs/${bug_id}" \
  580. -H "Token: ${ZENTAO_TOKEN}")
  581. # 使用 Python 解析 JSON
  582. BUG_STATUS=$(echo "$BUG_INFO" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('status', ''))" 2>/dev/null)
  583. BUG_TITLE=$(echo "$BUG_INFO" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('title', ''))" 2>/dev/null)
  584. BUG_OPENED_BY=$(echo "$BUG_INFO" | python3 -c "import sys, json; data=json.load(sys.stdin); openedBy=data.get('openedBy', {}); print(openedBy.get('account', '') if isinstance(openedBy, dict) else '')" 2>/dev/null)
  585. if [ -z "$BUG_STATUS" ]; then
  586. if type -t log_message >/dev/null 2>&1; then
  587. log_message "警告: 无法获取 Bug #${bug_id} 的状态信息"
  588. fi
  589. ERROR_COUNT=$((ERROR_COUNT + 1))
  590. continue
  591. fi
  592. if type -t log_message >/dev/null 2>&1; then
  593. log_message "Bug #${bug_id} 状态: ${BUG_STATUS}"
  594. log_message "Bug #${bug_id} 标题: ${BUG_TITLE}"
  595. log_message "Bug #${bug_id} 创建者: ${BUG_OPENED_BY}"
  596. log_message "最新修复提交: [${commit_hash}] ${author}"
  597. fi
  598. # 检查是否为未解决状态
  599. if [ "$BUG_STATUS" = "active" ] || [ "$BUG_STATUS" = "wait" ]; then
  600. if type -t log_message >/dev/null 2>&1; then
  601. log_message "Bug #${bug_id} 状态为未解决,准备解决..."
  602. fi
  603. # 准备 comment(只包含最新的提交)
  604. COMMENT="已修复该问题
  605. 相关提交:
  606. - [${commit_hash}] ${author}: ${commit_message}
  607. 代码详情: ${COMMIT_BASE_URL}${commit_hash}"
  608. # 生成解决 bug 的 JSON payload
  609. RESOLVE_JSON_FILE="/tmp/zentao_resolve_${bug_id}_$$.json"
  610. # 确定指派对象:优先使用创建者,如果获取失败则使用当前用户
  611. ASSIGNEE="${BUG_OPENED_BY}"
  612. if [ -z "$ASSIGNEE" ]; then
  613. if type -t log_message >/dev/null 2>&1; then
  614. log_message "警告: 无法获取 Bug #${bug_id} 创建者,将指派给当前用户 ${ZENTAO_USERNAME}"
  615. fi
  616. ASSIGNEE="${ZENTAO_USERNAME}"
  617. fi
  618. python3 -c "import json; print(json.dumps({
  619. 'resolution': 'fixed',
  620. 'resolvedBuild': 'trunk',
  621. 'resolvedDate': '$(date "+%Y-%m-%d %H:%M:%S")',
  622. 'assignedTo': '${ASSIGNEE}',
  623. 'comment': '''${COMMENT}'''
  624. }, ensure_ascii=False, indent=2))" >"$RESOLVE_JSON_FILE"
  625. # 调用 API 解决 bug
  626. RESOLVE_RESPONSE=$(curl -s -X POST "${ZENTAO_API_URL}/bugs/${bug_id}/resolve" \
  627. -H "Token: ${ZENTAO_TOKEN}" \
  628. -H "Content-Type: application/json" \
  629. -d @"$RESOLVE_JSON_FILE")
  630. # 清理临时文件
  631. rm -f "$RESOLVE_JSON_FILE"
  632. # 检查是否成功
  633. if echo "$RESOLVE_RESPONSE" | grep -q '"id"'; then
  634. if type -t log_message >/dev/null 2>&1; then
  635. log_message "✅ 成功解决 Bug #${bug_id}"
  636. fi
  637. RESOLVED_COUNT=$((RESOLVED_COUNT + 1))
  638. # 发送飞书通知(每个 bug 单独通知)
  639. BUG_URL="${ZENTAO_BUG_BASE_URL}${bug_id}.html"
  640. CODE_URL="${COMMIT_BASE_URL}${commit_hash}"
  641. COMMIT_SUMMARY="[${commit_hash}] ${commit_message}"
  642. send_bug_resolved_notification "${bug_id}" "${BUG_TITLE}" "${BUG_STATUS}" "${COMMIT_SUMMARY}" "${BUG_URL}" "${CODE_URL}"
  643. if type -t log_message >/dev/null 2>&1; then
  644. log_message "飞书通知已发送 (Bug #${bug_id})"
  645. fi
  646. else
  647. if type -t log_message >/dev/null 2>&1; then
  648. log_message "❌ 解决 Bug #${bug_id} 失败: ${RESOLVE_RESPONSE}"
  649. fi
  650. ERROR_COUNT=$((ERROR_COUNT + 1))
  651. fi
  652. elif [ "$BUG_STATUS" = "resolved" ] || [ "$BUG_STATUS" = "closed" ]; then
  653. if type -t log_message >/dev/null 2>&1; then
  654. log_message "Bug #${bug_id} 已经是 ${BUG_STATUS} 状态,跳过处理"
  655. fi
  656. SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
  657. else
  658. if type -t log_message >/dev/null 2>&1; then
  659. log_message "Bug #${bug_id} 状态为 ${BUG_STATUS},跳过处理"
  660. fi
  661. SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
  662. fi
  663. done
  664. if type -t log_message >/dev/null 2>&1; then
  665. log_message "=============== 禅道 Bug 自动解决完成 ==============="
  666. log_message "分析提交数: ${COMMIT_COUNT}"
  667. log_message "发现 Bug 数: ${BUG_COUNT}"
  668. log_message "已解决: ${RESOLVED_COUNT} 个"
  669. log_message "已跳过: ${SKIPPED_COUNT} 个"
  670. log_message "失败: ${ERROR_COUNT} 个"
  671. log_message "=============== 禅道 Bug 自动解决结束 ==============="
  672. fi
  673. return 0
  674. }