interceptor-registry.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import appSettings from '@/core/config/app-settings'
  2. import { isAbsoluteHttpUrl, trimTrailingSlash } from '@/core/network/url-detect'
  3. import { buildApiSignature } from '@/core/network/request-sign'
  4. import { applyClientHeaders, readSessionToken } from '@/core/network/client-meta'
  5. import { adaptiveToast } from '@/core/ui/smart-toast'
  6. import { parseApiResponse, snapshotEnvelope, applySuccessPayload } from './response-evaluator'
  7. const LOGIN_ROUTE = '/pages/passport/lobby'
  8. /** 为 true 时 HTTP 4xx/5xx 不弹 toast(用于网络探测等后台请求) */
  9. let suppressHttpErrorToast = false
  10. export function runWithoutHttpErrorToast(fn) {
  11. suppressHttpErrorToast = true
  12. return Promise.resolve()
  13. .then(fn)
  14. .finally(() => {
  15. suppressHttpErrorToast = false
  16. })
  17. }
  18. function ensureHeaderObject(cfg) {
  19. cfg.header = typeof cfg.header === 'object' && cfg.header !== null ? cfg.header : {}
  20. return cfg.header
  21. }
  22. function attachApiAuth(cfg) {
  23. const headers = ensureHeaderObject(cfg)
  24. const body = typeof cfg.data === 'object' && cfg.data !== null ? cfg.data : {}
  25. headers.Sign = buildApiSignature(cfg.url, body)
  26. cfg.url = trimTrailingSlash(appSettings.appurl) + cfg.url
  27. const token = readSessionToken()
  28. if (token) headers.token = token
  29. Object.assign(headers, applyClientHeaders(headers))
  30. }
  31. function decorateRelativeRequest(cfg) {
  32. if (isAbsoluteHttpUrl(cfg.url)) return
  33. attachApiAuth(cfg)
  34. const base = cfg.url
  35. const langQuery = `lang=${encodeURIComponent(uni.getLocale())}`
  36. cfg.url = base.includes('?') ? `${base}&${langQuery}` : `${base}?${langQuery}`
  37. }
  38. function decorateUpload(cfg) {
  39. if (isAbsoluteHttpUrl(cfg.url)) {
  40. const token = readSessionToken()
  41. if (token) ensureHeaderObject(cfg).token = token
  42. return
  43. }
  44. attachApiAuth(cfg)
  45. }
  46. export function registerHttpInterceptors(i18n) {
  47. const t = (key, fallback) => {
  48. try {
  49. return i18n ? i18n.t(key) : fallback
  50. } catch (_) {
  51. return fallback
  52. }
  53. }
  54. uni.addInterceptor('uploadFile', {
  55. invoke(cfg) {
  56. decorateUpload(cfg)
  57. },
  58. success(res) {
  59. if (res.statusCode !== 200) {
  60. let message = '上传失败'
  61. try {
  62. message = JSON.parse(res.data).msg || message
  63. } catch (_) {}
  64. adaptiveToast(message, 'error')
  65. return false
  66. }
  67. uni.showToast({ title: 'Success', position: true })
  68. return true
  69. }
  70. })
  71. uni.addInterceptor('request', {
  72. invoke(cfg) {
  73. cfg.sslVerify = false
  74. decorateRelativeRequest(cfg)
  75. if (appSettings.debug) console.log('[http:invoke]', cfg)
  76. },
  77. success(res) {
  78. snapshotEnvelope(res)
  79. const parsed = parseApiResponse(res)
  80. if (parsed.kind === 'success') {
  81. applySuccessPayload(res, parsed)
  82. if (appSettings.debug) {
  83. try {
  84. const reqUrl = res && res.config && res.config.url
  85. if (reqUrl && /live\/play/.test(reqUrl)) {
  86. console.log('=== [HTTP-DIAG] live/play raw response ===')
  87. console.log('[HTTP-DIAG] url =', reqUrl)
  88. console.log('[HTTP-DIAG] envelope =', res.res)
  89. console.log('[HTTP-DIAG] parsed.data =', parsed.data)
  90. console.log('[HTTP-DIAG] envelope JSON =', JSON.stringify(res.res))
  91. }
  92. } catch (logErr) {
  93. console.warn('[HTTP-DIAG] log failed', logErr)
  94. }
  95. console.log('[http:ok]', res)
  96. }
  97. return true
  98. }
  99. if (parsed.kind === 'passthrough') return
  100. // 业务失败须放行 success,由 http-client finalizeResponse 抛错并结束 Promise;
  101. // 若 return false 会拦截 success,await 永不 settle,页面 loading 无法结束。
  102. if (parsed.kind === 'silent_fail') {
  103. if (appSettings.debug) console.log('[http:silent-fail]', res)
  104. return true
  105. }
  106. if (parsed.kind === 'biz_fail') {
  107. adaptiveToast(parsed.message, 'error')
  108. if (appSettings.debug) console.log('[http:biz-fail]', res)
  109. return true
  110. }
  111. const status = parsed.status
  112. if (suppressHttpErrorToast) {
  113. if (appSettings.debug) {
  114. const reqUrl = (res && res.config && res.config.url) || ''
  115. console.warn('[http:silent-http-error]', status, reqUrl)
  116. }
  117. return true
  118. }
  119. if (status === 401) {
  120. adaptiveToast(parsed.message || '未授权', 'error')
  121. uni.reLaunch({ url: LOGIN_ROUTE })
  122. } else if (status === 404) {
  123. adaptiveToast(t('common.apiNotFound', 'API 接口不存在'), 'error')
  124. } else if (status === 500) {
  125. adaptiveToast(t('common.apiServerBusy', 'API 服務器繁忙'), 'error')
  126. } else {
  127. adaptiveToast(parsed.message || '请求失败', 'error')
  128. }
  129. if (appSettings.debug) console.log('[http:fail]', res)
  130. return true
  131. },
  132. fail(err) {
  133. if (appSettings.debug) console.log('[http:error]', err)
  134. }
  135. })
  136. }