You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

151 lines
4.5 KiB

  1. /**
  2. * 统一 API 请求封装,完全对齐 ai_uniapp_v2/utils/request.js
  3. *
  4. * 请求路径走 /api 前缀 → server/api/[...].js 服务端代理
  5. * /api/user/info → 代理到 https://api.jiefuku.com/user/info
  6. *
  7. * 自动注入(对齐 V2):
  8. * Header: { token, timestr, nonce }
  9. * Body: { token, uid, mac, base_timestamp, client:1, source, client_ios, version, version_code }
  10. *
  11. * 后端响应规范:
  12. * { code: 0, data: {...}, list: [...], msg: '...' } // 成功
  13. * { code: 1000, data: null, msg: '...' } // 业务异常
  14. * { code: 1009, data: null, msg: '...' } // 登录失效
  15. */
  16. import md5 from '~/utils/md5'
  17. export function useApi() {
  18. const tokenCookie = useCookie('token', { path: '/' })
  19. const macCookie = useCookie('mac', { maxAge: 365 * 24 * 3600, path: '/' })
  20. const openIdCookie = useCookie('open_id', { path: '/' })
  21. const sourceCookie = useCookie('source', { path: '/' })
  22. // mac 缺失时自动生成
  23. if (import.meta.client && !macCookie.value) {
  24. macCookie.value = generateUUID()
  25. }
  26. function generateUUID() {
  27. return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
  28. const r = (Math.random() * 16) | 0
  29. const v = c === 'x' ? r : (r & 0x3) | 0x8
  30. return v.toString(16)
  31. })
  32. }
  33. /**
  34. * 生成 nonce + timestr(对齐 V2 getNonce)
  35. */
  36. function getNonce() {
  37. const timeStr = Date.now().toString()
  38. const str = md5(timeStr)
  39. const nonce = str.substring(4, 14)
  40. return { timeStr, nonce }
  41. }
  42. /**
  43. * 发起请求
  44. */
  45. async function request(path, options = {}) {
  46. const { method = 'GET', params, body } = options
  47. // 走 /api 代理,对齐 V2 H5 端的 BASE_URL = '/api'
  48. const url = '/api' + path
  49. const timestamp = Math.floor(Date.now() / 1000)
  50. const { timeStr, nonce } = getNonce()
  51. // Header 签名(对齐 V2 header 注入)
  52. const reqHeaders = {
  53. 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
  54. timestr: timeStr,
  55. nonce,
  56. }
  57. const token = tokenCookie.value
  58. if (token) reqHeaders.token = token
  59. // Body 公共参数(对齐 V2 data 注入)
  60. const commonParams = {
  61. token: token || '',
  62. uid: openIdCookie.value || '',
  63. mac: macCookie.value || '',
  64. base_timestamp: timestamp,
  65. client: 1,
  66. source: sourceCookie.value || '',
  67. client_ios: 0,
  68. version: '1.0.0',
  69. version_code: 1,
  70. }
  71. const fetchOptions = {
  72. method,
  73. headers: reqHeaders,
  74. onResponseError({ response }) {
  75. console.error('[useApi] HTTP Error', response.status, url)
  76. },
  77. }
  78. if (method === 'GET') {
  79. // GET: 参数拼接到 URL
  80. const allParams = { ...commonParams, ...(params || {}) }
  81. // 去除空字符串
  82. Object.keys(allParams).forEach((k) => {
  83. if (allParams[k] === '') delete allParams[k]
  84. })
  85. const queryStr = new URLSearchParams(allParams).toString()
  86. fetchOptions.query = allParams
  87. // GET 请求通过 server proxy 时,参数在 query 里
  88. const finalUrl = url + (queryStr ? '?' + queryStr : '')
  89. const result = await $fetch(finalUrl, fetchOptions)
  90. return handleResult(result)
  91. } else {
  92. // POST: body 为 x-www-form-urlencoded
  93. const postBody = { ...commonParams, ...(body || {}) }
  94. // 去除空字符串
  95. Object.keys(postBody).forEach((k) => {
  96. if (postBody[k] === '') delete postBody[k]
  97. })
  98. fetchOptions.body = new URLSearchParams(postBody).toString()
  99. const result = await $fetch(url, fetchOptions)
  100. return handleResult(result)
  101. }
  102. }
  103. /**
  104. * 统一处理响应
  105. */
  106. function handleResult(result) {
  107. if (result.code === 1009) {
  108. // 登录失效 → 触发登录弹窗
  109. if (import.meta.client) {
  110. const { logout } = useUser()
  111. logout()
  112. const showLogin = useState('showLogin', () => false)
  113. showLogin.value = true
  114. }
  115. const err = new Error(result.msg || '登录已失效,请重新登录')
  116. err.code = 1009
  117. throw err
  118. }
  119. if (result.code !== 0) {
  120. const err = new Error(result.msg || '请求失败')
  121. err.code = result.code || 1000
  122. throw err
  123. }
  124. return result
  125. }
  126. return {
  127. /** GET 请求 */
  128. get: (path, params) => request(path, { method: 'GET', params }),
  129. /** POST 请求 (x-www-form-urlencoded) */
  130. post: (path, body) => request(path, { method: 'POST', body }),
  131. /** 通用请求 */
  132. request,
  133. }
  134. }