Deploy / deploy (push) Canceled after 0s
- access token 是 15 分钟的 JWT,走 Authorization: Bearer;refresh token 7 天,每次换都轮换 - 退出登录、改密码、禁用账号仍然立刻生效:每个请求核对 Redis 会话和库里新加的 user.token_version - 在服务器后台改密码 / 禁用,机房那边登着的也会一起下线(原来两边 Redis 分开,管不到) - WebSocket 握手借子协议带 token;JWT 密钥存在本站 Redis,部署不用加配置 - 去掉 COOKIE_SECURE;上线后所有人要重新登录一次 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
62 lines
2.0 KiB
TypeScript
62 lines
2.0 KiB
TypeScript
import axios from "axios"
|
|
import { createDiscreteApi } from "naive-ui"
|
|
import { getAccessToken } from "./auth"
|
|
|
|
// 指向新后端的 /api/admin。响应是 zip 二进制,不走 { error, data } 信封,
|
|
// 所以不能复用 utils/api 的拦截器(它会把 response.data.data 取出来)。
|
|
const http = axios.create({
|
|
baseURL: "/api/admin",
|
|
responseType: "blob",
|
|
})
|
|
|
|
http.interceptors.request.use(async (config) => {
|
|
const token = await getAccessToken()
|
|
if (token) config.headers.set("Authorization", `Bearer ${token}`)
|
|
return config
|
|
})
|
|
|
|
// 脱离 n-message-provider 也能弹,理由同 utils/api.ts
|
|
const { message: toast } = createDiscreteApi(["message"])
|
|
|
|
/**
|
|
* 失败时把错误信封读出来。`responseType: "blob"` 是给成功路径(zip)设的,
|
|
* 出错时 axios 照样把 JSON 信封包成 Blob 交回来,直接 `.error.message` 取不到。
|
|
*/
|
|
async function failureMessage(error: unknown) {
|
|
const data = (error as { response?: { data?: unknown } }).response?.data
|
|
if (data instanceof Blob) {
|
|
try {
|
|
const payload = JSON.parse(await data.text())
|
|
const message = payload?.error?.message
|
|
if (typeof message === "string" && message) return message
|
|
} catch {
|
|
// 不是 JSON(网关的 HTML 错误页之类),走下面的兜底文案
|
|
}
|
|
}
|
|
return "下载失败"
|
|
}
|
|
|
|
async function download(url: string) {
|
|
let res
|
|
try {
|
|
res = await http.get(url)
|
|
} catch (error) {
|
|
// 以前这里什么都不接:题目还没传测试点时后端回 404,页面上一点反应都没有
|
|
toast.error(await failureMessage(error))
|
|
return
|
|
}
|
|
const headers = res.headers
|
|
const link = document.createElement("a")
|
|
link.href = window.URL.createObjectURL(
|
|
new window.Blob([res.data], {
|
|
type: String(headers["content-type"] ?? ""),
|
|
}),
|
|
)
|
|
link.download = (headers["content-disposition"] || "").split("filename=")[1]
|
|
document.body.appendChild(link)
|
|
link.click()
|
|
link.remove()
|
|
}
|
|
|
|
export default download
|