request.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import { API_BASE_URL, REQUEST_TIMEOUT_MS } from './config.js'
  2. import { forceRelogin } from './auth-session.js'
  3. /** 这些接口的业务 code=401 表示凭据错误等,不是会话过期 */
  4. const AUTH_EXEMPT_PATHS = [
  5. '/web/auth/login',
  6. '/web/auth/login-rsa-public-key',
  7. '/web/auth/refresh-token',
  8. '/mini/auth/wechat-login',
  9. '/mini/auth/web-qr-scanned',
  10. '/mini/auth/web-qr-confirm'
  11. ]
  12. function isAuthExemptPath(path) {
  13. const p = String(path || '')
  14. for (let i = 0; i < AUTH_EXEMPT_PATHS.length; i++) {
  15. if (p.indexOf(AUTH_EXEMPT_PATHS[i]) !== -1) return true
  16. }
  17. return false
  18. }
  19. function hasBearerToken(header) {
  20. const auth = (header && header.Authorization) || ''
  21. return /^Bearer\s+\S+/i.test(String(auth))
  22. }
  23. /**
  24. * HTTP 401,或已带 token 的业务 code=401 → 强制重新登录。
  25. * 登录接口豁免,避免「账号密码错误」被当成会话失效。
  26. */
  27. function maybeForceRelogin(options, httpStatus, body) {
  28. if (isAuthExemptPath(options && options.url)) return
  29. const bizCode = body && body.code
  30. const msg =
  31. (body && typeof body.message === 'string' && body.message.trim()) ||
  32. (body && typeof body.detail === 'string' && body.detail.trim()) ||
  33. ''
  34. if (httpStatus === 401 || (bizCode === 401 && hasBearerToken(options && options.header))) {
  35. forceRelogin(msg || '登录已失效,请重新登录')
  36. }
  37. }
  38. /**
  39. * 解析后端统一响应。成功返回 data;失败抛 Error(message 为业务文案)。
  40. * @param {{ code?: number, message?: string, data?: any }} body
  41. * @param {number} [httpStatus]
  42. */
  43. export function unwrapResponse(body, httpStatus) {
  44. if (body == null || typeof body !== 'object') {
  45. const err = new Error('网络异常,请稍后重试')
  46. err.code = 'BAD_RESPONSE'
  47. throw err
  48. }
  49. const code = body.code
  50. if (code === 200) {
  51. return body.data
  52. }
  53. const msg =
  54. (typeof body.message === 'string' && body.message.trim()) ||
  55. '请求失败'
  56. const err = new Error(msg)
  57. err.code = code
  58. err.httpStatus = httpStatus
  59. throw err
  60. }
  61. function joinUrl(path) {
  62. const base = (API_BASE_URL || '').replace(/\/$/, '')
  63. const p = path.startsWith('/') ? path : `/${path}`
  64. return `${base}${p}`
  65. }
  66. /**
  67. * @param {{ url: string, method?: string, data?: any, header?: Record<string, string> }} options
  68. * @returns {Promise<any>} data 字段
  69. */
  70. export function request(options) {
  71. const method = (options.method || 'GET').toUpperCase()
  72. const url = joinUrl(options.url)
  73. const header = Object.assign(
  74. { 'Content-Type': 'application/json' },
  75. options.header || {}
  76. )
  77. const reqOpts = { url: options.url, header }
  78. return new Promise((resolve, reject) => {
  79. uni.request({
  80. url,
  81. method,
  82. data: options.data,
  83. header,
  84. timeout: REQUEST_TIMEOUT_MS,
  85. success(res) {
  86. try {
  87. const body = res.data
  88. maybeForceRelogin(reqOpts, res.statusCode, body)
  89. if (res.statusCode >= 200 && res.statusCode < 300) {
  90. resolve(unwrapResponse(body, res.statusCode))
  91. return
  92. }
  93. // HTTP 非 2xx:若 body 仍是业务包,优先展示 message
  94. try {
  95. resolve(unwrapResponse(body, res.statusCode))
  96. } catch (bizErr) {
  97. reject(bizErr)
  98. }
  99. } catch (e) {
  100. reject(e)
  101. }
  102. },
  103. fail() {
  104. const err = new Error('网络异常,请稍后重试')
  105. err.code = 'NETWORK'
  106. reject(err)
  107. }
  108. })
  109. })
  110. }
  111. export function get(url, header) {
  112. return request({ url, method: 'GET', header })
  113. }
  114. export function post(url, data, header) {
  115. return request({ url, method: 'POST', data, header })
  116. }