Files
PartsInquiry/frontend/common/http.js
2025-09-27 22:57:59 +08:00

157 lines
6.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { API_BASE_URL, API_BASE_URL_CANDIDATES, SHOP_ID, ENABLE_DEFAULT_USER, DEFAULT_USER_ID } from './config.js'
function buildUrl(path) {
if (!path) return API_BASE_URL
if (path.startsWith('http')) return path
return API_BASE_URL + (path.startsWith('/') ? path : '/' + path)
}
function parseJwtClaims(token) {
try {
const parts = String(token || '').split('.')
if (parts.length < 2) return {}
const payload = JSON.parse(decodeURIComponent(escape(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')))))
return payload || {}
} catch (_) { return {} }
}
function buildAuthHeaders(base = {}) {
const headers = { ...base }
try {
const token = typeof uni !== 'undefined' ? (uni.getStorageSync('TOKEN') || '') : ''
if (token) {
headers['Authorization'] = `Bearer ${token}`
const claims = parseJwtClaims(token)
if (claims && claims.shopId) headers['X-Shop-Id'] = claims.shopId
if (claims && claims.userId) headers['X-User-Id'] = claims.userId
} else if (ENABLE_DEFAULT_USER && DEFAULT_USER_ID) {
if (headers['Authorization']) delete headers['Authorization']
headers['X-User-Id'] = headers['X-User-Id'] || DEFAULT_USER_ID
if (SHOP_ID) headers['X-Shop-Id'] = headers['X-Shop-Id'] || SHOP_ID
}
} catch (_) {
if (ENABLE_DEFAULT_USER && DEFAULT_USER_ID) {
headers['X-User-Id'] = headers['X-User-Id'] || DEFAULT_USER_ID
if (SHOP_ID) headers['X-Shop-Id'] = headers['X-Shop-Id'] || SHOP_ID
}
}
return headers
}
function requestWithFallback(options, candidates, idx, resolve, reject) {
const base = candidates[idx] || API_BASE_URL
let url = options.url
if (!/^https?:\/\//.test(url)) {
url = base + (url.startsWith('/') ? '' : '/') + url
} else {
url = options.url.replace(/^https?:\/\/[^/]+/, base)
}
uni.request({ ...options, url, dataType: 'json', success: (res) => {
const { statusCode, data } = res
if (statusCode >= 200 && statusCode < 300) return resolve(data)
const msg = (data && (data.message || data.error || data.msg)) || ('HTTP ' + statusCode)
uni.showToast({ title: msg, icon: 'none' })
// 仅在 5xx 或网络错误时尝试下一个候选地址4xx 不重试。支持通过 __noRetry 禁止重试用于登录接口避免40163
if (!options.__noRetry && statusCode >= 500 && idx + 1 < candidates.length) {
return requestWithFallback(options, candidates, idx + 1, resolve, reject)
}
reject(new Error(msg))
}, fail: (err) => {
if (!options.__noRetry && idx + 1 < candidates.length) return requestWithFallback(options, candidates, idx + 1, resolve, reject)
reject(err)
} })
}
export function get(path, params = {}) {
return new Promise((resolve, reject) => {
const headers = buildAuthHeaders({})
const options = { url: buildUrl(path), method: 'GET', data: params, header: headers }
requestWithFallback(options, API_BASE_URL_CANDIDATES, 0, resolve, reject)
})
}
export function post(path, body = {}) {
return new Promise((resolve, reject) => {
const headers = buildAuthHeaders({ 'Content-Type': 'application/json' })
const options = { url: buildUrl(path), method: 'POST', data: body, header: headers }
const p = String(path || '')
if (
p.includes('/api/auth/wxmp/login') ||
p.includes('/api/auth/sms/login') || p.includes('/api/auth/sms/send') ||
p.includes('/api/auth/email/login') || p.includes('/api/auth/email/send') ||
p.includes('/api/auth/password/login') || p.includes('/api/auth/register')
) options.__noRetry = true
requestWithFallback(options, API_BASE_URL_CANDIDATES, 0, resolve, reject)
})
}
export function put(path, body = {}) {
return new Promise((resolve, reject) => {
const headers = buildAuthHeaders({ 'Content-Type': 'application/json' })
const options = { url: buildUrl(path), method: 'PUT', data: body, header: headers }
requestWithFallback(options, API_BASE_URL_CANDIDATES, 0, resolve, reject)
})
}
export function del(path, body = {}) {
return new Promise((resolve, reject) => {
const headers = buildAuthHeaders({ 'Content-Type': 'application/json' })
const options = { url: buildUrl(path), method: 'DELETE', data: body, header: headers }
requestWithFallback(options, API_BASE_URL_CANDIDATES, 0, resolve, reject)
})
}
function uploadWithFallback(options, candidates, idx, resolve, reject) {
const base = candidates[idx] || API_BASE_URL
const url = options.url.replace(/^https?:\/\/[^/]+/, base)
const uploadOptions = { ...options, url }
uni.uploadFile({
...uploadOptions,
success: (res) => {
const statusCode = res.statusCode || 0
// 2xx: 正常返回
if (statusCode >= 200 && statusCode < 300) {
try {
const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
return resolve(data)
} catch (e) {
return resolve(res.data)
}
}
// 4xx: 不重试,透传后端 JSON如 400 未识别、413 文件过大),给调用方展示 message
if (statusCode >= 400 && statusCode < 500) {
try {
const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
return resolve(data)
} catch (e) {
return resolve({ success: false, message: 'HTTP ' + statusCode })
}
}
// 5xx优先尝试下一个候选地址到达最后一个候选时尽力解析 JSON 供前端展示
if (idx + 1 < candidates.length) return uploadWithFallback(options, candidates, idx + 1, resolve, reject)
try {
const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
return resolve(data)
} catch (e) {
return resolve({ success: false, message: 'HTTP ' + statusCode })
}
},
fail: (err) => {
if (idx + 1 < candidates.length) return uploadWithFallback(options, candidates, idx + 1, resolve, reject)
reject(err)
}
})
}
// 文件上传封装:自动注入租户/用户头并进行多地址回退
export function upload(path, filePath, formData = {}, name = 'file') {
return new Promise((resolve, reject) => {
const header = buildAuthHeaders({})
const options = { url: buildUrl(path), filePath, name, formData, header }
uploadWithFallback(options, API_BASE_URL_CANDIDATES, 0, resolve, reject)
})
}