Files
labelmain-demo/api/fetch.ts
2026-04-30 14:58:28 +08:00

364 lines
13 KiB
TypeScript
Raw Permalink 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.

/*
Fetch 接口
fetch() 包含了 fetch() 方法,用于获取资源。
Headers 表示响应/请求的标头信息,允许你查询它们,或者针对不同的结果做不同的操作。
Request 相当于一个资源请求。
Response 相当于请求的响应
fetch 参数:
method: 请求使用的方法,如 GET、POST。
headers: 请求的头信息,形式为 Headers 的对象或包含 ByteString 值的对象字面量
body: 请求的 body 信息:可能是一个 Blob、BufferSource(x禁止)、FormData、URLSearchParams 或者 USVString 对象。注意 GET 或 HEAD 方法的请求不能包含 body 信息。
mode: 请求的模式,如 cors、no-cors 或者 same-origin。
credentials: 请求的 credentials如 omit、same-origin 或者 include。为了在当前域名内自动发送 cookie必须提供这个选项从 Chrome 50 开始,这个属性也可以接受 FederatedCredential (en-US) 实例或是一个 PasswordCredential (en-US) 实例。
*/
"use client"
import { useUserStore } from "@/app/store/user"
import { usePermissionStore } from "@/components/label/store/auth"
import { deleteCookie, refreshKey } from "@/components/login/api"
import { isNumber } from "@/libs/util"
interface OptionsProps {
url: string
method: string
body?: BodyInit
data?: any
responseType?: "blob" | "arraybuffer" | "stream"
needCredentials?: boolean
isUpload?: boolean
isDownload?: boolean
isFormData?: boolean
isLogin?: boolean
isRefresh?: boolean
isDeleteCookie?: boolean
notJson?: boolean
headerAttr?: {
[key: string]: string
}
}
async function httpfetch<T>(options: OptionsProps): Promise<T> {
const { removeUserInfo, refreshToken } = useUserStore.getState()
const { logout } = usePermissionStore.getState()
const access_token = useUserStore.getState().access_token
const refresh_token = useUserStore.getState().refresh_token
let Message = {
error: (params: { content: string }) => {
console.log(params.content)
},
}
const getResponseType = (requestOptions: OptionsProps) => {
return (
requestOptions.responseType ||
(requestOptions.headerAttr?.responseType as
| "blob"
| "arraybuffer"
| "stream"
| undefined)
)
}
const createSsrRequest: (
options: OptionsProps,
token?: string,
refreshToken?: string
) => Request = (options) => {
const responseType = getResponseType(options)
// 设置请求头
let myHeaders = new Headers()
if (!options.isUpload && !options.isFormData && !options.notJson) {
myHeaders.append("Content-Type", "application/json")
}
if (options.isDownload) {
myHeaders.append("Response-Type", "blob")
}
if (options.isFormData) {
options.body &&
(options.body as FormData)?.append &&
(options.body as FormData).append("options", JSON.stringify(options))
}
// 配置常用设置 method 请求方式 headers 请求头
let myInit: RequestInit = {
method: "POST",
headers: myHeaders,
mode: "cors",
cache: "default",
body: options.isFormData
? options.body
: (JSON.stringify({
data: options.body,
...options,
options: {
responseType,
},
}) as BodyInit),
}
const pathList = options.url.split("/")
const request = new Request(
`${process.env.NEXT_PUBLIC_BASE_PATH}/api/${options.isFormData ? "transferFormData" : "transfer"}/${pathList[pathList.length - 1]}`,
myInit
)
return request
}
const createCsrRequest: (
options: OptionsProps,
token?: string,
refreshToken?: string
) => Request = (options, token = "", refreshToken = "") => {
let request = new Request(options.url)
// 设置请求头
let myHeaders = new Headers()
myHeaders.append("Authorization", `bearer ${token}`)
myHeaders.append("Token", token)
myHeaders.append("Refresh-Token", refreshToken)
if (!options.isUpload && !options.isFormData && !options.notJson) {
myHeaders.append("Content-Type", "application/json")
}
if (options.isDownload) {
myHeaders.append("Response-Type", "blob")
}
if (options.headerAttr) {
Object.entries(options.headerAttr).map(([key, value]) => {
if (key === "responseType") return
myHeaders.append(key, value)
})
}
// myHeaders.append("X-Machine-Code", "machine_code0")
// 配置常用设置 method 请求方式 headers 请求头
let myInit: RequestInit = {
method: options.method,
headers: myHeaders,
mode: "cors",
cache: "default",
credentials: options.needCredentials ? "include" : undefined,
body: options.body as BodyInit,
}
let publicUrl = options.url.includes("http")
? ""
: process.env.NEXT_PUBLIC_URL
if (options.method === "GET") {
let searchParams = new URLSearchParams()
Object.entries(options.data || {})?.map((m) => {
searchParams.append(m[0], m[1] as string)
})
let concatenatedURL = searchParams.toString()
? options.url + "?" + searchParams.toString()
: options.url
request = new Request(publicUrl + concatenatedURL, myInit)
} else {
request = new Request(publicUrl + options.url, myInit)
}
return request
}
const createNewRequest =
process.env.NEXT_PUBLIC_OUTPUT_TYPE === "standalone"
? createSsrRequest
: createCsrRequest
const request = createNewRequest(options, access_token, refresh_token)
return new Promise((resolved, rejected) => {
fetch(request)
.then(async (response) => {
const responseType = getResponseType(options)
if (response.status === 200) {
if (options.isDownload) {
return response.blob()
} else if (responseType === "stream") {
return response
} else {
return response.headers
.get("Content-Type")
?.includes("application/json")
? response.json()
: response.text()
}
} else {
const { status } = response
if (status === 400) {
if (
response.headers.get("Content-Type")?.includes("application/json")
) {
const data = await response.json()
const { message: errInfo } = data
Message.error({ content: errInfo || "" })
throw new Error(errInfo)
} else {
const errInfo = await response.text()
Message.error({ content: errInfo || "" })
throw new Error(errInfo)
}
} else if (status === 401) {
try {
const refreshResponse = await refreshKey()
let access_token = refreshResponse?.access || ""
let refresh_token = refreshResponse?.refresh || ""
refreshToken({
refresh_token,
access_token,
})
const retry_request = createNewRequest(options, access_token)
const retry_response = await fetch(retry_request)
const responseType = getResponseType(options)
if (responseType === "stream") return retry_response
return options.isDownload
? retry_response.blob()
: retry_response.json()
} catch (error) {
rejected(error)
try {
await deleteCookie()
} catch (error) {
rejected(error)
} finally {
// 清空身份信息并跳转至登录页面
removeUserInfo()
logout()
window.location.href = "/login"
}
}
} else if (status === 403) {
if (
response.headers.get("Content-Type")?.includes("application/json")
) {
const data = await response.json()
const { message: errInfo } = data
Message.error({ content: errInfo || "" })
location.href = `${process.env.NEXT_PUBLIC_HOME_URL}`
throw new Error(errInfo)
} else {
const errInfo = await response.text()
Message.error({ content: errInfo || "" })
location.href = `${process.env.NEXT_PUBLIC_HOME_URL}`
throw new Error(errInfo)
}
} else if (status === 500) {
if (
response.headers.get("Content-Type")?.includes("application/json")
) {
const data = await response.json()
const { message: errInfo } = data
Message.error({ content: errInfo || "" })
throw new Error(errInfo)
} else {
const errInfo = await response.text()
Message.error({ content: errInfo || "" })
throw new Error(errInfo)
}
} else if (status === 406) {
const data = await response.json()
const { message: errInfo } = data
Message.error({ content: errInfo || "" })
// throw new Error(errInfo)
rejected(errInfo)
try {
await deleteCookie()
} catch (error) {
rejected(error)
} finally {
// 清空身份信息并跳转至登录页面
removeUserInfo()
logout()
window.location.href = "/login"
}
} else if (status >= 510) {
Message.error({ content: "申请资源不足!" })
throw new Error("申请资源不足!")
} else {
Message.error({ content: "网络服务有误!" })
throw new Error("Something went wrong on API server!")
}
}
})
.then(async (response) => {
if (
getResponseType(options) === "stream" &&
response instanceof Response
) {
resolved(response as T)
return
}
const code = response?.code
const errInfo = response?.message || ""
if (response) {
if (isNumber(code)) {
let status = code
// 日后做相关处理
if (status === 401) {
try {
const refreshResponse = await refreshKey()
let access_token = refreshResponse?.access || ""
let refresh_token = refreshResponse?.refresh || ""
refreshToken({
refresh_token,
access_token,
})
const retry_request = createNewRequest(options, access_token)
const retry_response = await fetch(retry_request)
return options.isDownload
? retry_response.blob()
: retry_response.json()
} catch (error) {
rejected(error)
try {
await deleteCookie()
} catch (error) {
rejected(error)
} finally {
// 清空身份信息并跳转至登录页面
removeUserInfo()
logout()
window.location.href = "/login"
}
}
} else if (status === 400) {
const { message: errInfo } = response
Message.error({ content: errInfo || "" })
throw new Error(errInfo)
} else if (status === 403) {
const { message: errInfo } = response
Message.error({ content: errInfo || "" })
// location.href = `${process.env.NEXT_PUBLIC_HOME_URL}`
throw new Error(errInfo)
} else if (status === 500) {
const { message: errInfo } = response
Message.error({ content: errInfo || "" })
throw new Error(errInfo)
} else if (status === 406) {
const { message: errInfo } = response
Message.error({ content: errInfo || "" })
throw new Error(errInfo)
} else if (status >= 510) {
Message.error({ content: "申请资源不足!" })
throw new Error("申请资源不足!")
} else if (status === 200) {
resolved(response)
} else {
Message.error({ content: "网络服务有误!" })
throw new Error("Something went wrong on API server!")
}
}
resolved(response)
} else {
throw new Error(errInfo)
}
})
.catch((error) => {
rejected(error)
})
})
}
export default httpfetch