优化
This commit is contained in:
+10
-1
@@ -1,11 +1,13 @@
|
||||
import { API_ROOT } from '@/config/runtime.js'
|
||||
|
||||
function authRequest(path, data) {
|
||||
function authRequest(path, data, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: `${API_ROOT}${path}`,
|
||||
method: 'POST',
|
||||
data,
|
||||
header: options.header,
|
||||
timeout: options.timeout || 15000,
|
||||
success(response) {
|
||||
const body = response.data || {}
|
||||
if (String(body.code ?? response.statusCode) !== '200') {
|
||||
@@ -34,3 +36,10 @@ export function login(username, password) {
|
||||
export function refresh(refreshToken) {
|
||||
return authRequest('/auth/refresh', { refreshToken })
|
||||
}
|
||||
|
||||
export function logout(refreshToken, accessToken) {
|
||||
return authRequest('/auth/logout', { refreshToken }, {
|
||||
header: accessToken ? { Authorization: `Bearer ${accessToken}` } : undefined,
|
||||
timeout: 5000
|
||||
})
|
||||
}
|
||||
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
import { refresh as refreshRequest } from '@/api/auth.js'
|
||||
import { isAccessTokenExpiring, session, signOut, updateTokenPair } from '@/stores/session.js'
|
||||
import { requireOnline } from '@/utils/network.js'
|
||||
|
||||
let refreshPromise = null
|
||||
let redirectingToLogin = false
|
||||
|
||||
function responseCode(response) {
|
||||
return String(response?.data?.code ?? response?.statusCode ?? '')
|
||||
}
|
||||
|
||||
function isUnauthorized(responseOrError) {
|
||||
const statusCode = Number(responseOrError?.statusCode ?? responseOrError?.response?.statusCode ?? 0)
|
||||
const code = String(responseOrError?.code ?? responseCode(responseOrError))
|
||||
return statusCode === 401 || code === '401' || code === 'A0401'
|
||||
}
|
||||
|
||||
function sessionExpiredError(source) {
|
||||
const error = new Error('登录已过期,请重新登录')
|
||||
error.code = source?.code || 'A0401'
|
||||
error.statusCode = source?.statusCode || 401
|
||||
return error
|
||||
}
|
||||
|
||||
function redirectAfterSessionExpiry() {
|
||||
if (redirectingToLogin) return
|
||||
redirectingToLogin = true
|
||||
if (typeof uni !== 'undefined' && typeof uni.showToast === 'function') {
|
||||
uni.showToast({ title: '登录已过期,请重新登录', icon: 'none', duration: 2200 })
|
||||
}
|
||||
if (typeof uni !== 'undefined' && typeof uni.reLaunch === 'function') {
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/login',
|
||||
complete() { setTimeout(() => { redirectingToLogin = false }, 1000) }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function expireSession(source, observedRefreshToken = session.refreshToken) {
|
||||
if (observedRefreshToken && session.refreshToken && observedRefreshToken !== session.refreshToken) return session.token
|
||||
const hadSession = Boolean(session.currentUser || session.token || session.refreshToken)
|
||||
signOut()
|
||||
if (hadSession) redirectAfterSessionExpiry()
|
||||
throw sessionExpiredError(source)
|
||||
}
|
||||
|
||||
function networkError(error, fallback = '网络连接失败') {
|
||||
const next = new Error(error?.errMsg || fallback)
|
||||
next.network = true
|
||||
return next
|
||||
}
|
||||
|
||||
function rawRequest(options, accessToken) {
|
||||
const baseUrl = session.apiBase || '/api/platform/v1'
|
||||
const headers = { ...(options.header || {}) }
|
||||
if (accessToken) headers.Authorization = `Bearer ${accessToken}`
|
||||
if (session.requestId) headers['X-Request-Id'] = session.requestId
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
...options,
|
||||
url: `${baseUrl}${options.url}`,
|
||||
header: headers,
|
||||
success: resolve,
|
||||
fail: (error) => reject(networkError(error))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function refreshAccessToken() {
|
||||
const observedRefreshToken = session.refreshToken
|
||||
if (!observedRefreshToken) return expireSession(null, observedRefreshToken)
|
||||
if (!refreshPromise) {
|
||||
refreshPromise = (async () => {
|
||||
if (session.refreshToken !== observedRefreshToken && session.token) return session.token
|
||||
try {
|
||||
const tokens = await refreshRequest(observedRefreshToken)
|
||||
if (session.refreshToken !== observedRefreshToken) {
|
||||
if (session.token) return session.token
|
||||
throw sessionExpiredError()
|
||||
}
|
||||
updateTokenPair(tokens)
|
||||
redirectingToLogin = false
|
||||
return session.token
|
||||
} catch (error) {
|
||||
if (isUnauthorized(error)) return expireSession(error, observedRefreshToken)
|
||||
throw error
|
||||
}
|
||||
})().finally(() => { refreshPromise = null })
|
||||
}
|
||||
return refreshPromise
|
||||
}
|
||||
|
||||
async function validAccessToken() {
|
||||
if (!session.token || isAccessTokenExpiring(session.token)) return refreshAccessToken()
|
||||
return session.token
|
||||
}
|
||||
|
||||
export function ensureAccessToken() {
|
||||
return validAccessToken()
|
||||
}
|
||||
|
||||
async function runAuthenticated(execute) {
|
||||
requireOnline()
|
||||
let accessToken = await validAccessToken()
|
||||
let response = await execute(accessToken)
|
||||
if (!isUnauthorized(response)) return response
|
||||
accessToken = session.token && session.token !== accessToken ? session.token : await refreshAccessToken()
|
||||
const activeRefreshToken = session.refreshToken
|
||||
response = await execute(accessToken)
|
||||
if (isUnauthorized(response)) expireSession(response, activeRefreshToken)
|
||||
return response
|
||||
}
|
||||
|
||||
export function apiRequest(options) {
|
||||
return runAuthenticated((accessToken) => rawRequest(options, accessToken)).then((response) => {
|
||||
const body = response.data || {}
|
||||
const code = responseCode(response)
|
||||
if (code !== '200') {
|
||||
const error = new Error(body.message || '服务端请求失败')
|
||||
error.code = body.code || response.statusCode
|
||||
error.statusCode = response.statusCode
|
||||
error.requestId = body.requestId || response.header?.['X-Request-Id'] || response.header?.['x-request-id']
|
||||
error.traceId = body.traceId || response.header?.['X-Trace-Id'] || response.header?.['x-trace-id']
|
||||
throw error
|
||||
}
|
||||
return body.data
|
||||
})
|
||||
}
|
||||
|
||||
export function apiDownload(options) {
|
||||
return runAuthenticated((accessToken) => new Promise((resolve, reject) => {
|
||||
const headers = { ...(options.header || {}) }
|
||||
if (accessToken) headers.Authorization = `Bearer ${accessToken}`
|
||||
if (session.requestId) headers['X-Request-Id'] = session.requestId
|
||||
uni.downloadFile({ ...options, header: headers, success: resolve, fail: (error) => reject(networkError(error, '文件下载失败')) })
|
||||
}))
|
||||
}
|
||||
|
||||
export function apiUpload(options, onProgress) {
|
||||
return runAuthenticated((accessToken) => new Promise((resolve, reject) => {
|
||||
const headers = { ...(options.header || {}) }
|
||||
if (accessToken) headers.Authorization = `Bearer ${accessToken}`
|
||||
if (session.requestId) headers['X-Request-Id'] = session.requestId
|
||||
const task = uni.uploadFile({ ...options, header: headers, success: resolve, fail: (error) => reject(networkError(error)) })
|
||||
if (task && typeof task.onProgressUpdate === 'function' && typeof onProgress === 'function') task.onProgressUpdate(({ progress }) => onProgress(progress))
|
||||
}))
|
||||
}
|
||||
|
||||
export { isUnauthorized }
|
||||
+14
-70
@@ -1,5 +1,6 @@
|
||||
import { session } from '@/stores/session.js'
|
||||
import { requireOnline } from '@/utils/network.js'
|
||||
import { apiDownload, apiRequest, apiUpload } from '@/api/http.js'
|
||||
|
||||
const EVIDENCE_TYPES = Object.freeze([
|
||||
'PHOTO_STEP_1', 'PHOTO_STEP_2', 'PHOTO_STEP_3', 'PHOTO_STEP_4', 'SEAL'
|
||||
@@ -23,41 +24,7 @@ function debugLog(event, payload = {}) {
|
||||
try { console.info(`[PDA] ${event}`, payload) } catch (_) { /* console may be unavailable on a device */ }
|
||||
}
|
||||
|
||||
function request(options) {
|
||||
requireOnline()
|
||||
const baseUrl = session.apiBase || '/api/platform/v1'
|
||||
const headers = { ...(options.header || {}) }
|
||||
if (session.token) headers.Authorization = `Bearer ${session.token}`
|
||||
if (session.requestId) headers['X-Request-Id'] = session.requestId
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
...options,
|
||||
url: `${baseUrl}${options.url}`,
|
||||
header: headers,
|
||||
success(response) {
|
||||
const body = response.data || {}
|
||||
const code = String(body.code ?? response.statusCode)
|
||||
|
||||
if (code !== '200') {
|
||||
const error = new Error(body.message || '服务端请求失败')
|
||||
error.code = body.code || response.statusCode
|
||||
error.statusCode = response.statusCode
|
||||
error.requestId = body.requestId || response.header?.['X-Request-Id'] || response.header?.['x-request-id']
|
||||
error.traceId = body.traceId || response.header?.['X-Trace-Id'] || response.header?.['x-trace-id']
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
resolve(body.data)
|
||||
},
|
||||
fail(error) {
|
||||
const next = new Error(error.errMsg || '网络连接失败')
|
||||
next.network = true
|
||||
reject(next)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
const request = apiRequest
|
||||
|
||||
function idempotencyKey(operation, inboundId) {
|
||||
return `pda-${operation}-${inboundId}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
||||
@@ -260,15 +227,8 @@ export function downloadEvidencePreview(previewUrl) {
|
||||
const origin = baseUrl.match(/^(https?:\/\/[^/]+)/i)?.[1] || ''
|
||||
url = `${origin}${url.startsWith('/') ? '' : '/'}${url}`
|
||||
}
|
||||
const headers = {}
|
||||
if (session.token) headers.Authorization = `Bearer ${session.token}`
|
||||
if (session.requestId) headers['X-Request-Id'] = session.requestId
|
||||
debugLog('evidence.preview.start', { previewPath: String(previewUrl), resolvedUrl: url })
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.downloadFile({
|
||||
url,
|
||||
header: headers,
|
||||
success(response) {
|
||||
return apiDownload({ url }).then((response) => {
|
||||
const responseHeaders = response.header || {}
|
||||
const contentType = responseHeaders['Content-Type'] || responseHeaders['content-type'] || ''
|
||||
const requestId = responseHeaders['X-Request-Id'] || responseHeaders['x-request-id'] || ''
|
||||
@@ -279,8 +239,7 @@ export function downloadEvidencePreview(previewUrl) {
|
||||
error.statusCode = response.statusCode
|
||||
error.requestId = requestId
|
||||
error.traceId = traceId
|
||||
reject(error)
|
||||
return
|
||||
throw error
|
||||
}
|
||||
if (contentType && !/^image\//i.test(contentType) && !/^application\/octet-stream/i.test(contentType)) {
|
||||
const error = new Error('服务端未返回有效照片')
|
||||
@@ -288,18 +247,12 @@ export function downloadEvidencePreview(previewUrl) {
|
||||
error.contentType = contentType
|
||||
error.requestId = requestId
|
||||
error.traceId = traceId
|
||||
reject(error)
|
||||
return
|
||||
throw error
|
||||
}
|
||||
resolve(response.tempFilePath)
|
||||
},
|
||||
fail(error) {
|
||||
debugLog('evidence.preview.error', { previewPath: String(previewUrl), message: error.errMsg || '' })
|
||||
const next = new Error(error.errMsg || '装柜照片下载失败')
|
||||
next.network = true
|
||||
reject(next)
|
||||
}
|
||||
})
|
||||
return response.tempFilePath
|
||||
}).catch((error) => {
|
||||
debugLog('evidence.preview.error', { previewPath: String(previewUrl), message: error.message || error.errMsg || '' })
|
||||
throw error
|
||||
})
|
||||
}
|
||||
|
||||
@@ -331,28 +284,19 @@ export function uploadEvidenceFile(inboundId, evidenceType, localPath, tenantId,
|
||||
if (!localPath) throw new Error('缺少待上传的照片')
|
||||
const tenant = tenantQuery(tenantId)
|
||||
const baseUrl = session.apiBase || '/api/platform/v1'
|
||||
const headers = {}
|
||||
if (session.token) headers.Authorization = `Bearer ${session.token}`
|
||||
if (session.requestId) headers['X-Request-Id'] = session.requestId
|
||||
debugLog('evidence.upload.start', { inboundId, evidenceType, tenantId: tenant, hasLocalPath: Boolean(localPath) })
|
||||
return new Promise((resolve, reject) => {
|
||||
const task = uni.uploadFile({
|
||||
return apiUpload({
|
||||
url: `${baseUrl}/files/loading-evidence/${encodeURIComponent(tenant)}/${encodeURIComponent(inboundId)}?evidenceType=${encodeURIComponent(evidenceType)}`,
|
||||
filePath: localPath,
|
||||
name: 'file',
|
||||
header: headers,
|
||||
success(response) {
|
||||
}, onProgress).then((response) => {
|
||||
let body = response.data
|
||||
try { body = typeof body === 'string' ? JSON.parse(body) : body } catch (_) { /* handled below */ }
|
||||
const code = String(body?.code ?? response.statusCode)
|
||||
debugLog('evidence.upload.response', { inboundId, evidenceType, statusCode: response.statusCode, code, message: body?.message || '', requestId: body?.requestId || response.header?.['X-Request-Id'] || response.header?.['x-request-id'] || '', traceId: body?.traceId || response.header?.['X-Trace-Id'] || response.header?.['x-trace-id'] || '', fileId: body?.data?.fileId || null })
|
||||
if (code !== '200') { const error = new Error(body?.message || '文件上传失败'); error.code = body?.code || response.statusCode; reject(error); return }
|
||||
resolve(body?.data || body)
|
||||
},
|
||||
fail(error) { debugLog('evidence.upload.error', { inboundId, evidenceType, message: error.errMsg || '' }); const next = new Error(error.errMsg || '网络连接失败'); next.network = true; reject(next) }
|
||||
})
|
||||
if (task && typeof task.onProgressUpdate === 'function' && typeof onProgress === 'function') task.onProgressUpdate(({ progress }) => onProgress(progress))
|
||||
})
|
||||
if (code !== '200') { const error = new Error(body?.message || '文件上传失败'); error.code = body?.code || response.statusCode; throw error }
|
||||
return body?.data || body
|
||||
}).catch((error) => { debugLog('evidence.upload.error', { inboundId, evidenceType, message: error.message || error.errMsg || '' }); throw error })
|
||||
}
|
||||
|
||||
export function completeLoading(inboundId, payload, tenantId) {
|
||||
|
||||
Reference in New Issue
Block a user