144 lines
3.9 KiB
JavaScript
144 lines
3.9 KiB
JavaScript
const app = getApp()
|
||
|
||
function apiUrl(path) {
|
||
const base = (app && app.globalData && app.globalData.apiBase) || 'http://localhost:8000'
|
||
return base.replace(/\/$/, '') + path
|
||
}
|
||
|
||
function authHeader() {
|
||
const token = (app && app.globalData && app.globalData.token) || wx.getStorageSync('token')
|
||
return token ? { Authorization: 'Bearer ' + token } : {}
|
||
}
|
||
|
||
function request(path, options) {
|
||
options = options || {}
|
||
return new Promise((resolve, reject) => {
|
||
wx.request({
|
||
url: apiUrl(path),
|
||
method: options.method || 'GET',
|
||
data: options.data,
|
||
header: Object.assign(
|
||
{ 'Content-Type': 'application/json' },
|
||
authHeader(),
|
||
options.header || {}
|
||
),
|
||
success(res) {
|
||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||
resolve(res.data)
|
||
} else {
|
||
const detail = (res.data && (res.data.detail || res.data.message)) || res.errMsg || '请求失败'
|
||
reject(new Error(typeof detail === 'string' ? detail : JSON.stringify(detail)))
|
||
}
|
||
},
|
||
fail(err) {
|
||
reject(new Error(err.errMsg || '网络异常'))
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
function uploadFile(path, filePath, name) {
|
||
return new Promise((resolve, reject) => {
|
||
wx.uploadFile({
|
||
url: apiUrl(path),
|
||
filePath: filePath,
|
||
name: name || 'file',
|
||
header: authHeader(),
|
||
success(res) {
|
||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||
try {
|
||
resolve(JSON.parse(res.data))
|
||
} catch (e) {
|
||
resolve(res.data)
|
||
}
|
||
} else {
|
||
reject(new Error(res.data || '上传失败'))
|
||
}
|
||
},
|
||
fail(err) {
|
||
reject(new Error(err.errMsg || '上传失败'))
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 以分块方式请求 SSE 流。onEvent({event, data}) 回调每个事件。
|
||
* 返回一个 requestTask,可用于中断。
|
||
*/
|
||
function streamRequest(path, body, onEvent, onDone, onError) {
|
||
const task = wx.request({
|
||
url: apiUrl(path),
|
||
method: 'POST',
|
||
data: body,
|
||
responseType: 'text',
|
||
enableChunked: true,
|
||
header: Object.assign(
|
||
{ 'Content-Type': 'application/json', Accept: 'text/event-stream' },
|
||
authHeader()
|
||
),
|
||
success(res) {
|
||
if (res.statusCode < 200 || res.statusCode >= 300) {
|
||
onError && onError(new Error('HTTP ' + res.statusCode + ' ' + (res.data || '')))
|
||
} else {
|
||
onDone && onDone()
|
||
}
|
||
},
|
||
fail(err) {
|
||
onError && onError(new Error(err.errMsg || '流式请求失败'))
|
||
}
|
||
})
|
||
|
||
let buf = ''
|
||
const parseChunk = (chunk) => {
|
||
buf += chunk
|
||
// SSE 事件以空行分隔(\n\n)
|
||
let idx
|
||
while ((idx = buf.indexOf('\n\n')) !== -1) {
|
||
const raw = buf.slice(0, idx)
|
||
buf = buf.slice(idx + 2)
|
||
let event = 'message'
|
||
let dataStr = ''
|
||
raw.split('\n').forEach((line) => {
|
||
if (line.startsWith('event:')) event = line.slice(6).trim()
|
||
else if (line.startsWith('data:')) dataStr += line.slice(5).trim()
|
||
})
|
||
let data
|
||
try { data = JSON.parse(dataStr) } catch (e) { data = dataStr }
|
||
onEvent && onEvent({ event: event, data: data })
|
||
}
|
||
}
|
||
|
||
if (task && task.onChunkReceived) {
|
||
task.onChunkReceived((response) => {
|
||
const buffer = response.data
|
||
let text = ''
|
||
if (typeof buffer === 'string') {
|
||
text = buffer
|
||
} else {
|
||
// ArrayBuffer -> UTF-8
|
||
const bytes = new Uint8Array(buffer)
|
||
// 简易 UTF-8 解码
|
||
try {
|
||
if (typeof TextDecoder !== 'undefined') {
|
||
text = new TextDecoder('utf-8').decode(bytes)
|
||
} else {
|
||
text = decodeURIComponent(escape(String.fromCharCode.apply(null, bytes)))
|
||
}
|
||
} catch (e) {
|
||
text = String.fromCharCode.apply(null, bytes)
|
||
}
|
||
}
|
||
parseChunk(text)
|
||
})
|
||
}
|
||
return task
|
||
}
|
||
|
||
module.exports = {
|
||
apiUrl: apiUrl,
|
||
request: request,
|
||
uploadFile: uploadFile,
|
||
streamRequest: streamRequest
|
||
}
|