添加登录和后端校验
完成后端设计(未在本仓库体现),通过安全的手段完成了登录鉴权
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
|
||||
/**
|
||||
* 后端接口请求封装
|
||||
* 后端统一响应:{ code, message, data },code === 0 表示成功
|
||||
* token 通过 Authorization: Bearer <token> 传入
|
||||
*/
|
||||
export const BASE_URL = 'https://wxbackend.tokenleaping.com'
|
||||
|
||||
const TOKEN_KEY = 'smart_access_token'
|
||||
|
||||
/** 安全隐藏 loading:没有活动 loading 时调用 hideLoading 会抛 hideToast:fail,这里吞掉 */
|
||||
export function safeHideLoading(): void {
|
||||
try { Taro.hideLoading() } catch { /* ignore */ }
|
||||
}
|
||||
|
||||
// ---------- token 存取 ----------
|
||||
|
||||
export function getToken(): string {
|
||||
try {
|
||||
return Taro.getStorageSync(TOKEN_KEY) || ''
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export function setToken(token: string): void {
|
||||
Taro.setStorageSync(TOKEN_KEY, token)
|
||||
}
|
||||
|
||||
export function clearToken(): void {
|
||||
try {
|
||||
Taro.removeStorageSync(TOKEN_KEY)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 类型 ----------
|
||||
|
||||
interface ApiResponse<T = unknown> {
|
||||
code: number
|
||||
message: string
|
||||
data: T
|
||||
}
|
||||
|
||||
export interface RequestOptions {
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
|
||||
data?: unknown
|
||||
/** 额外请求头 */
|
||||
headers?: Record<string, string>
|
||||
/** 是否自动携带 token,默认 true */
|
||||
auth?: boolean
|
||||
}
|
||||
|
||||
// ---------- request ----------
|
||||
|
||||
/**
|
||||
* 发请求。成功(code===0)返回 data;业务失败抛 Error(message);HTTP 401 清 token 并抛错。
|
||||
* 调用方可自行 catch 或用 onUnauthorized 钩子统一处理跳登录。
|
||||
*/
|
||||
export async function request<T = unknown>(
|
||||
path: string,
|
||||
options: RequestOptions = {},
|
||||
): Promise<T> {
|
||||
const { method = 'GET', data, headers = {}, auth = true } = options
|
||||
|
||||
const token = getToken()
|
||||
const finalHeaders: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
...(auth && token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
...headers,
|
||||
}
|
||||
|
||||
let res: Taro.request.SuccessCallbackResult
|
||||
try {
|
||||
res = await Taro.request({
|
||||
url: `${BASE_URL}${path}`,
|
||||
method,
|
||||
data,
|
||||
header: finalHeaders,
|
||||
})
|
||||
} catch (e) {
|
||||
// 网络层异常
|
||||
throw new Error('网络异常,请稍后重试')
|
||||
}
|
||||
|
||||
// HTTP 未授权:清 token,交回调处理
|
||||
if (res.statusCode === 401) {
|
||||
clearToken()
|
||||
onUnauthorized?.()
|
||||
throw new Error('登录已过期,请重新登录')
|
||||
}
|
||||
|
||||
const body = res.data as ApiResponse<T>
|
||||
|
||||
// 后端业务码非 0:当作错误抛出
|
||||
if (body && typeof body.code === 'number' && body.code !== 0) {
|
||||
throw new Error(body.message || '请求失败')
|
||||
}
|
||||
|
||||
return (body && typeof body.code === 'number' ? body.data : body) as T
|
||||
}
|
||||
|
||||
/** 401 钩子,由业务方注册(如跳转登录页) */
|
||||
export let onUnauthorized: (() => void) | null = null
|
||||
export function setOnUnauthorized(handler: () => void): void {
|
||||
onUnauthorized = handler
|
||||
}
|
||||
|
||||
// ---------- 便捷方法 ----------
|
||||
|
||||
export const http = {
|
||||
get: <T = unknown>(path: string, options?: RequestOptions) =>
|
||||
request<T>(path, { ...options, method: 'GET' }),
|
||||
post: <T = unknown>(path: string, data?: unknown, options?: RequestOptions) =>
|
||||
request<T>(path, { ...options, method: 'POST', data }),
|
||||
put: <T = unknown>(path: string, data?: unknown, options?: RequestOptions) =>
|
||||
request<T>(path, { ...options, method: 'PUT', data }),
|
||||
patch: <T = unknown>(path: string, data?: unknown, options?: RequestOptions) =>
|
||||
request<T>(path, { ...options, method: 'PATCH', data }),
|
||||
del: <T = unknown>(path: string, options?: RequestOptions) =>
|
||||
request<T>(path, { ...options, method: 'DELETE' }),
|
||||
}
|
||||
|
||||
export default http
|
||||
Reference in New Issue
Block a user