Jenkinsfile 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. pipeline {
  2. agent any
  3. environment {
  4. // 语言环境设置
  5. LANG = 'en_US.UTF-8'
  6. LC_ALL = 'en_US.UTF-8'
  7. // 项目配置
  8. VITE_NODE_ENV = 'production'
  9. PROJECT_NAME = 'tg-live-game-web'
  10. // 分支配置
  11. BUILD_BRANCH = 'main'
  12. // Node 配置 22.21.1 也是可用的
  13. NODE_VERSION = 'v20.19.3'
  14. NODE_PATH = "/root/.nvm/versions/node/${NODE_VERSION}/bin"
  15. PNPM_PATH = "/root/.nvm/versions/node/${NODE_VERSION}/bin/pnpm"
  16. // Cloudflare 配置
  17. CLOUDFLARE_PROJECT_NAME = 'tg-live-game-web'
  18. DEPLOY_PATH = 'web'
  19. DEPLOY_URL = "https://${CLOUDFLARE_PROJECT_NAME}.pwtk-dev.work"
  20. // 日志配置
  21. BUILD_LOG_FILE = "${WORKSPACE}/build_${BUILD_NUMBER}.log"
  22. // ========================================
  23. // 第三方服务配置
  24. // ========================================
  25. // Lark (飞书) 配置
  26. LARK_WEBHOOK_URL = 'https://open.larksuite.com/open-apis/bot/v2/hook/7142cabc-603b-423f-8081-315cd3c19365'
  27. // Telegram 配置
  28. TELEGRAM_BOT_TOKEN = '8132916684:AAEd1oObTMPxxcLV6LICKIDTPf1vBSn2rGk'
  29. TELEGRAM_CHAT_ID = '7061322031'
  30. // 禅道配置 (可选,设置 ZENTAO_ENABLED=true 启用)
  31. ZENTAO_ENABLED = 'false' // 默认关闭,需要配置好凭据后开启
  32. ZENTAO_PRODUCT_ID = '1'
  33. ZENTAO_API_URL = 'https://zentao.pwtk.cc/zentao/api.php/v1'
  34. ZENTAO_BUG_BASE_URL = 'https://zentao.pwtk.cc/zentao/bug-view-'
  35. // 禅道凭据: 在 Jenkins 中配置 ZENTAO_CREDENTIALS (username/password)
  36. // Git 提交链接配置
  37. COMMIT_BASE_URL = 'https://github.com/pwtk/tg-live-game-web/commit/'
  38. }
  39. options {
  40. // 保留最近的构建记录
  41. buildDiscarder(logRotator(
  42. numToKeepStr: '1',
  43. daysToKeepStr: '1'
  44. ))
  45. // 禁止并发构建
  46. disableConcurrentBuilds()
  47. // 构建超时时间
  48. timeout(time: 30, unit: 'MINUTES')
  49. }
  50. stages {
  51. stage('初始化') {
  52. steps {
  53. script {
  54. sh '''
  55. echo "=============== 初始化 ==============="
  56. echo "PROJECT_NAME=${PROJECT_NAME}"
  57. echo "BUILD_BRANCH=${BUILD_BRANCH}"
  58. echo "NODE_VERSION=${NODE_VERSION}"
  59. source ${WORKSPACE}/jenkins/lib/logger.sh
  60. source ${WORKSPACE}/jenkins/lib/time-utils.sh
  61. source ${WORKSPACE}/jenkins/lib/telegram.sh
  62. source ${WORKSPACE}/jenkins/lib/lark_dev.sh
  63. source ${WORKSPACE}/jenkins/lib/cloudflare_sync.sh
  64. # 导出函数供后续使用
  65. export -f log_message
  66. export -f send_telegram_message
  67. export -f send_telegram_file
  68. export -f send_lark_message
  69. export -f send_lark_fix_notification
  70. export -f update_progress
  71. export -f get_network_time
  72. export -f deploy_to_cloudflare
  73. # 清理旧日志
  74. cleanup_old_logs "/tmp" "build_"
  75. # 初始化日志文件
  76. init_log_file
  77. log_message "=============== 构建开始 ==============="
  78. log_message "构建编号: ${BUILD_NUMBER}"
  79. log_message "工作目录: ${WORKSPACE}"
  80. # 记录构建开始时间
  81. BUILD_START_TIME=$(get_network_time)
  82. BUILD_START_TIMESTAMP=$(get_timestamp "$BUILD_START_TIME")
  83. echo "BUILD_START_TIME='${BUILD_START_TIME}'" > ${WORKSPACE}/build_vars.env
  84. echo "BUILD_START_TIMESTAMP=${BUILD_START_TIMESTAMP}" >> ${WORKSPACE}/build_vars.env
  85. log_message "构建开始时间: ${BUILD_START_TIME}"
  86. # 发送初始进度
  87. update_progress 0 "初始化"
  88. '''
  89. }
  90. }
  91. }
  92. stage('环境检查') {
  93. steps {
  94. script {
  95. sh '''
  96. source ${WORKSPACE}/jenkins/lib/logger.sh
  97. source ${WORKSPACE}/jenkins/lib/telegram.sh
  98. source ${WORKSPACE}/build_vars.env
  99. log_message "=============== 环境检查 ==============="
  100. log_message "检查 Node 版本: ${NODE_VERSION}"
  101. # 检查 Node 版本目录是否存在
  102. NODE_DIR="/root/.nvm/versions/node/${NODE_VERSION}"
  103. if [ ! -d "${NODE_DIR}" ]; then
  104. log_message "❌ 错误: Node ${NODE_VERSION} 未安装"
  105. log_message "Node 目录不存在: ${NODE_DIR}"
  106. log_message "请先在服务器上安装 Node ${NODE_VERSION}"
  107. log_message "安装命令: nvm install ${NODE_VERSION}"
  108. # 列出已安装的 Node 版本
  109. log_message "当前已安装的 Node 版本:"
  110. ls -la /root/.nvm/versions/node/ 2>/dev/null || log_message "无法读取 nvm 版本目录"
  111. send_telegram_message "❌ 构建中止 - ${PROJECT_NAME}
  112. Node 版本 ${NODE_VERSION} 未安装
  113. 请在服务器上执行: nvm install ${NODE_VERSION}"
  114. exit 1
  115. fi
  116. # 检查 node 可执行文件是否存在
  117. if [ ! -x "${NODE_PATH}/node" ]; then
  118. log_message "❌ 错误: Node 可执行文件不存在"
  119. log_message "路径: ${NODE_PATH}/node"
  120. send_telegram_message "❌ 构建中止 - ${PROJECT_NAME}
  121. Node 可执行文件不存在: ${NODE_PATH}/node"
  122. exit 1
  123. fi
  124. # 检查 pnpm 是否存在
  125. if [ ! -x "${PNPM_PATH}" ]; then
  126. log_message "⚠️ 警告: pnpm 未安装,尝试安装..."
  127. export PATH="${NODE_PATH}:$PATH"
  128. npm install -g pnpm
  129. if [ ! -x "${PNPM_PATH}" ]; then
  130. log_message "❌ 错误: pnpm 安装失败"
  131. send_telegram_message "❌ 构建中止 - ${PROJECT_NAME}
  132. pnpm 安装失败,请手动安装: npm install -g pnpm"
  133. exit 1
  134. fi
  135. fi
  136. # 验证 Node 版本
  137. export PATH="${NODE_PATH}:$PATH"
  138. ACTUAL_VERSION=$(node -v)
  139. log_message "✅ Node 版本验证通过: ${ACTUAL_VERSION}"
  140. log_message "✅ pnpm 版本: $(${PNPM_PATH} -v)"
  141. update_progress 10 "环境检查完成"
  142. '''
  143. }
  144. }
  145. }
  146. stage('代码拉取') {
  147. steps {
  148. script {
  149. sh '''
  150. source ${WORKSPACE}/jenkins/lib/logger.sh
  151. source ${WORKSPACE}/jenkins/lib/telegram.sh
  152. source ${WORKSPACE}/build_vars.env
  153. log_message "清理 Git 工作区(保留配置文件和 jenkins 目录)"
  154. git checkout -- . || true
  155. git clean -fd -e .env* -e jenkins/ -e node_modules/ || true
  156. log_message "检查当前 Git 状态"
  157. log_message "当前分支: $(git rev-parse --abbrev-ref HEAD)"
  158. log_message "最新提交: $(git log -1 --oneline)"
  159. # 获取最新提交信息
  160. LAST_COMMIT_DATE=$(git log -1 --format='%ci' | sed 's/ [+-][0-9]\\{4\\}$//')
  161. LAST_COMMIT_MESSAGE=$(git log -1 --pretty=format:"%s")
  162. LAST_COMMIT_HASH=$(git log -1 --pretty=format:"%H" | cut -c1-8)
  163. echo "LAST_COMMIT_DATE='${LAST_COMMIT_DATE}'" >> ${WORKSPACE}/build_vars.env
  164. echo "LAST_COMMIT_MESSAGE='${LAST_COMMIT_MESSAGE}'" >> ${WORKSPACE}/build_vars.env
  165. echo "LAST_COMMIT_HASH='${LAST_COMMIT_HASH}'" >> ${WORKSPACE}/build_vars.env
  166. log_message "提交日期: ${LAST_COMMIT_DATE}"
  167. log_message "提交信息: ${LAST_COMMIT_MESSAGE}"
  168. log_message "提交哈希: ${LAST_COMMIT_HASH}"
  169. # 提取修复信息
  170. log_message "提取本次构建的修复信息"
  171. LAST_SUCCESS_HASH=$(cat ${WORKSPACE}/.last_success_commit 2>/dev/null || echo "")
  172. if [ -z "${LAST_SUCCESS_HASH}" ]; then
  173. log_message "首次构建,提取最近 20 个 fix 提交"
  174. FIX_COMMITS=$(git log -20 --oneline | grep -E "^[a-f0-9]+ fix[:(]" || true)
  175. else
  176. log_message "上次成功构建: ${LAST_SUCCESS_HASH}"
  177. FIX_COMMITS=$(git log ${LAST_SUCCESS_HASH}..HEAD --oneline | grep -E "^[a-f0-9]+ fix[:(]" || true)
  178. fi
  179. if [ -z "${FIX_COMMITS}" ]; then
  180. log_message "没有找到包含 fix: 的提交"
  181. echo "FIX_LIST='无修复记录'" >> ${WORKSPACE}/build_vars.env
  182. echo "FIX_COUNT=0" >> ${WORKSPACE}/build_vars.env
  183. else
  184. FIX_COUNT=$(echo "$FIX_COMMITS" | wc -l)
  185. log_message "找到 ${FIX_COUNT} 个修复"
  186. echo "FIX_COUNT=${FIX_COUNT}" >> ${WORKSPACE}/build_vars.env
  187. fi
  188. # 发送构建开始通知
  189. send_build_start_notification \
  190. "${PROJECT_NAME}" \
  191. "${BUILD_START_TIME}" \
  192. "${LAST_COMMIT_DATE}" \
  193. "${LAST_COMMIT_MESSAGE}" \
  194. "${BUILD_BRANCH}" \
  195. "${VITE_NODE_ENV}"
  196. update_progress 20 "代码拉取完成"
  197. '''
  198. }
  199. }
  200. }
  201. stage('依赖安装') {
  202. steps {
  203. script {
  204. sh '''
  205. source ${WORKSPACE}/jenkins/lib/logger.sh
  206. source ${WORKSPACE}/jenkins/lib/telegram.sh
  207. source ${WORKSPACE}/jenkins/lib/time-utils.sh
  208. source ${WORKSPACE}/build_vars.env
  209. export PATH="${NODE_PATH}:$PATH"
  210. log_message "Node 版本: $(node -v)"
  211. log_message "pnpm 版本: $(${PNPM_PATH} -v)"
  212. log_message "清理旧的构建产物和 node_modules"
  213. rm -rf dist
  214. rm -rf node_modules
  215. log_message "开始安装依赖"
  216. INSTALL_START=$(date +%s)
  217. if [ -f pnpm-lock.yaml ]; then
  218. log_message "使用 pnpm install --frozen-lockfile"
  219. if ! ${PNPM_PATH} install --frozen-lockfile 2>&1 | tee -a "$BUILD_LOG_FILE"; then
  220. log_message "frozen-lockfile 失败,尝试普通安装"
  221. ${PNPM_PATH} install 2>&1 | tee -a "$BUILD_LOG_FILE"
  222. fi
  223. else
  224. ${PNPM_PATH} install 2>&1 | tee -a "$BUILD_LOG_FILE"
  225. fi
  226. INSTALL_END=$(date +%s)
  227. INSTALL_DURATION=$((INSTALL_END - INSTALL_START))
  228. log_message "依赖安装完成,耗时: ${INSTALL_DURATION}秒"
  229. update_progress 35 "依赖安装完成"
  230. '''
  231. }
  232. }
  233. }
  234. stage('生成配置文件') {
  235. steps {
  236. script {
  237. sh '''
  238. source ${WORKSPACE}/jenkins/lib/logger.sh
  239. source ${WORKSPACE}/jenkins/lib/telegram.sh
  240. log_message "检查环境配置文件"
  241. if [ -f ".env.production" ]; then
  242. log_message "✅ 找到 .env.production"
  243. log_message "配置内容:"
  244. cat .env.production | grep -v "^#" | grep -v "^$" | tee -a "$BUILD_LOG_FILE"
  245. else
  246. log_message "⚠️ 未找到 .env.production,使用默认配置"
  247. fi
  248. if [ -f ".env" ]; then
  249. log_message "✅ 找到 .env (基础配置)"
  250. fi
  251. log_message "配置文件检查完成"
  252. update_progress 50 "配置文件检查完成"
  253. '''
  254. }
  255. }
  256. }
  257. stage('项目构建') {
  258. steps {
  259. script {
  260. sh '''
  261. source ${WORKSPACE}/jenkins/lib/logger.sh
  262. source ${WORKSPACE}/jenkins/lib/telegram.sh
  263. source ${WORKSPACE}/jenkins/lib/time-utils.sh
  264. source ${WORKSPACE}/build_vars.env
  265. export PATH="${NODE_PATH}:$PATH"
  266. log_message "开始执行构建"
  267. if ! ${PNPM_PATH} run build 2>&1 | tee -a "$BUILD_LOG_FILE"; then
  268. log_message "错误: build 失败"
  269. FAIL_TIME=$(get_network_time)
  270. FAIL_TIMESTAMP=$(get_timestamp "$FAIL_TIME")
  271. eval $(calculate_duration ${BUILD_START_TIMESTAMP} ${FAIL_TIMESTAMP})
  272. send_build_failure_notification \
  273. "${PROJECT_NAME}" \
  274. "${FAIL_TIME}" \
  275. "${LAST_COMMIT_DATE}" \
  276. "${LAST_COMMIT_MESSAGE}" \
  277. "${LAST_COMMIT_HASH}" \
  278. "${BUILD_BRANCH}" \
  279. "${VITE_NODE_ENV}" \
  280. "build" \
  281. "构建命令执行失败" \
  282. "${MINUTES}" \
  283. "${SECONDS}"
  284. send_telegram_file "$BUILD_LOG_FILE" "❌ 构建失败 - 完整日志"
  285. exit 1
  286. fi
  287. update_progress 70 "构建完成"
  288. '''
  289. }
  290. }
  291. }
  292. stage('Cloudflare 部署') {
  293. steps {
  294. script {
  295. withCredentials([
  296. string(credentialsId: 'XD_CLOUDFLARE_API_TOKEN', variable: 'CLOUDFLARE_API_TOKEN'),
  297. string(credentialsId: 'XD_CLOUDFLARE_ACCOUNT_ID', variable: 'CLOUDFLARE_ACCOUNT_ID')
  298. ]) {
  299. sh '''
  300. source ${WORKSPACE}/jenkins/lib/lark_dev.sh
  301. source ${WORKSPACE}/jenkins/lib/logger.sh
  302. source ${WORKSPACE}/jenkins/lib/telegram.sh
  303. source ${WORKSPACE}/jenkins/lib/time-utils.sh
  304. source ${WORKSPACE}/jenkins/lib/cloudflare_sync.sh
  305. source ${WORKSPACE}/build_vars.env
  306. export PATH="${NODE_PATH}:$PATH"
  307. export CLOUDFLARE_API_TOKEN="${CLOUDFLARE_API_TOKEN}"
  308. if ! deploy_to_cloudflare "${CLOUDFLARE_PROJECT_NAME}" "${BUILD_BRANCH}" "dist"; then
  309. log_message "❌ Cloudflare Pages 部署失败"
  310. FAIL_TIME=$(get_network_time)
  311. FAIL_TIMESTAMP=$(get_timestamp "$FAIL_TIME")
  312. eval $(calculate_duration ${BUILD_START_TIMESTAMP} ${FAIL_TIMESTAMP})
  313. send_build_failure_notification \
  314. "${PROJECT_NAME}" \
  315. "${FAIL_TIME}" \
  316. "${LAST_COMMIT_DATE}" \
  317. "${LAST_COMMIT_MESSAGE}" \
  318. "${LAST_COMMIT_HASH}" \
  319. "${BUILD_BRANCH}" \
  320. "${VITE_NODE_ENV}" \
  321. "Cloudflare 部署" \
  322. "Cloudflare Pages 部署失败" \
  323. "${MINUTES}" \
  324. "${SECONDS}"
  325. send_telegram_file "$BUILD_LOG_FILE" "❌ 构建失败 - 完整日志"
  326. exit 1
  327. fi
  328. log_message "发送部署完成通知"
  329. PACKAGE_VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "未知")
  330. log_message "项目版本: ${PACKAGE_VERSION}"
  331. send_lark_message \
  332. "🌐 Cloudflare 部署完成 - ${PROJECT_NAME}" \
  333. "**访问地址**: [${DEPLOY_URL}](${DEPLOY_URL})
  334. **版本**: ${PACKAGE_VERSION}
  335. **分支**: ${BUILD_BRANCH}
  336. **环境**: ${VITE_NODE_ENV}
  337. **提交**: ${LAST_COMMIT_HASH}
  338. **部署时间**: $(get_network_time)"
  339. log_message "✅ 部署完成: ${DEPLOY_URL}"
  340. update_progress 100 "部署完成"
  341. '''
  342. }
  343. }
  344. }
  345. }
  346. }
  347. post {
  348. success {
  349. script {
  350. sh '''
  351. source ${WORKSPACE}/jenkins/lib/logger.sh
  352. source ${WORKSPACE}/jenkins/lib/telegram.sh
  353. source ${WORKSPACE}/jenkins/lib/lark_dev.sh
  354. source ${WORKSPACE}/jenkins/lib/time-utils.sh
  355. source ${WORKSPACE}/build_vars.env
  356. eval $(get_log_stats)
  357. log_message "Warning数量: ${WARNING_COUNT}"
  358. log_message "Error数量: ${ERROR_COUNT}"
  359. BUILD_END_TIME=$(get_network_time)
  360. BUILD_END_TIMESTAMP=$(get_timestamp "$BUILD_END_TIME")
  361. eval $(calculate_duration ${BUILD_START_TIMESTAMP} ${BUILD_END_TIMESTAMP})
  362. log_message "=============== 构建完成 ==============="
  363. log_message "总耗时: ${MINUTES}分${SECONDS}秒"
  364. send_build_success_notification \
  365. "${PROJECT_NAME}" \
  366. "${BUILD_END_TIME}" \
  367. "${LAST_COMMIT_DATE}" \
  368. "${LAST_COMMIT_MESSAGE}" \
  369. "${LAST_COMMIT_HASH}" \
  370. "${BUILD_BRANCH}" \
  371. "${VITE_NODE_ENV}" \
  372. "${MINUTES}" \
  373. "${SECONDS}"
  374. send_telegram_file "$BUILD_LOG_FILE" "✅ 构建成功 - 完整日志"
  375. if [ "${FIX_COUNT:-0}" -gt 0 ]; then
  376. log_message "发送修复通知到 Lark"
  377. FORMATTED_FIXES=$(echo -e "${FIX_LIST}")
  378. send_lark_fix_notification \
  379. "${PROJECT_NAME}" \
  380. "${BUILD_END_TIME}" \
  381. "${LAST_COMMIT_HASH}" \
  382. "${FIX_COUNT}" \
  383. "${FORMATTED_FIXES}" \
  384. "${BUILD_BRANCH}" \
  385. "${VITE_NODE_ENV}"
  386. fi
  387. echo "${LAST_COMMIT_HASH}" > ${WORKSPACE}/.last_success_commit
  388. log_message "保存成功构建标记: ${LAST_COMMIT_HASH}"
  389. '''
  390. // 禅道 Bug 自动解决 (可选功能)
  391. script {
  392. if (env.ZENTAO_ENABLED == 'true') {
  393. withCredentials([
  394. usernamePassword(credentialsId: 'ZENTAO_CREDENTIALS',
  395. usernameVariable: 'ZENTAO_USERNAME',
  396. passwordVariable: 'ZENTAO_PASSWORD')
  397. ]) {
  398. sh '''
  399. source ${WORKSPACE}/jenkins/lib/logger.sh
  400. source ${WORKSPACE}/jenkins/lib/telegram.sh
  401. source ${WORKSPACE}/jenkins/lib/lark_dev.sh
  402. source ${WORKSPACE}/jenkins/lib/zentao_dev.sh
  403. log_message "=============== 禅道 Bug 追踪 ==============="
  404. # 调用禅道 Bug 自动解决
  405. resolve_zentao_bugs "${WORKSPACE}" || true
  406. log_message "=============== 禅道 Bug 追踪完成 ==============="
  407. '''
  408. }
  409. } else {
  410. echo "禅道集成未启用 (设置 ZENTAO_ENABLED=true 启用)"
  411. }
  412. }
  413. }
  414. }
  415. failure {
  416. script {
  417. sh '''
  418. source ${WORKSPACE}/jenkins/lib/logger.sh
  419. source ${WORKSPACE}/jenkins/lib/telegram.sh
  420. log_message "构建失败,请查看日志"
  421. if [ -f "${WORKSPACE}/build_vars.env" ]; then
  422. source ${WORKSPACE}/build_vars.env
  423. send_telegram_message "❌ 构建失败 - ${PROJECT_NAME} - 请查看 Jenkins 日志"
  424. fi
  425. '''
  426. }
  427. }
  428. always {
  429. script {
  430. archiveArtifacts artifacts: 'build_*.log', allowEmptyArchive: true
  431. }
  432. }
  433. }
  434. }