request.js 3.5 KB

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