feat(小程序): 修复运行时与登录链路

This commit is contained in:
stu2not
2026-04-22 17:25:03 +08:00
parent 24b1d1025b
commit d7df6f565c
12 changed files with 297 additions and 426 deletions

View File

@@ -1,7 +1,9 @@
import Taro from '@tarojs/taro'
import { API_BASE_URL } from '@/config/env'
import { getCurrentUserId, getToken, handleUnauthorized } from './session'
const BASE_URL = API_BASE_URL
const REQUEST_TIMEOUT_MS = 10000
class ApiError extends Error {
constructor(public status: number, message: string) {
@@ -9,6 +11,16 @@ class ApiError extends Error {
}
}
function getErrorDetail(data: any): string {
if (!data) return 'Request failed'
if (typeof data.detail === 'string') return data.detail
if (Array.isArray(data.detail)) {
return data.detail.map((item: any) => String(item?.msg || item)).join('; ')
}
if (typeof data.message === 'string') return data.message
return 'Request failed'
}
async function request<T>(
url: string,
options: {
@@ -17,40 +29,42 @@ async function request<T>(
headers?: Record<string, string>
} = {}
): Promise<T> {
const token = Taro.getStorageSync('token')
console.log(`[API] ${options.method || 'GET'} ${url}`, token ? 'with token' : 'no token')
const token = getToken()
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...options.headers,
}
if (token) {
headers['Authorization'] = `Bearer ${token}`
headers.Authorization = `Bearer ${token}`
}
const response = await Taro.request({
url: `${BASE_URL}${url}`,
method: options.method || 'GET',
data: options.data,
header: headers,
})
let response
try {
response = await Taro.request({
url: `${BASE_URL}${url}`,
method: options.method || 'GET',
data: options.data,
header: headers,
timeout: REQUEST_TIMEOUT_MS,
})
} catch (error: any) {
const message = String(error?.errMsg || error?.message || '')
const detail = message.includes('timeout')
? '请求超时,请检查后端服务是否可访问'
: '网络请求失败,请检查后端地址和网络连接'
throw new ApiError(0, detail)
}
if (response.statusCode === 401) {
handleUnauthorized({ redirect: !url.startsWith('/auth/') })
}
console.log(`[API] Response status:`, response.statusCode)
if (response.statusCode >= 400) {
const detail = response.data?.detail || 'Request failed'
throw new ApiError(response.statusCode, detail)
throw new ApiError(response.statusCode, getErrorDetail(response.data))
}
return response.data
return response.data as T
}
export { request, ApiError, BASE_URL }
export function getToken(): string {
return Taro.getStorageSync('token') || ''
}
export function getCurrentUserId(): number {
return Taro.getStorageSync('user_id') || 0
}
export { getCurrentUserId, getToken }

View File

@@ -1,5 +1,9 @@
import Taro from '@tarojs/taro'
import {
clearLoginSession,
getCurrentUserId as getStoredCurrentUserId,
getToken as getStoredToken,
saveLoginSession,
} from './session'
import { request } from './api'
export interface WechatLoginPayload {
@@ -25,40 +29,19 @@ export interface Parent {
status: number
}
// 微信登录
export async function wechatLogin(data: WechatLoginPayload): Promise<LoginSession> {
const session = await request<LoginSession>('/auth/login', {
method: 'POST',
data,
})
Taro.setStorageSync('token', session.access_token)
Taro.setStorageSync('token_type', session.token_type)
Taro.setStorageSync('user_id', session.user_id)
Taro.setStorageSync('expires_in', session.expires_in)
saveLoginSession(session)
return session
}
// 创建家长
export async function createParent(data: {
openid: string
unionid?: string
nickname?: string
avatar_url?: string
}): Promise<Parent> {
return request<Parent>('/parents', {
method: 'POST',
data,
})
}
// 获取家长信息
export async function getParent(userId: number): Promise<Parent> {
return request<Parent>(`/parents/${userId}`)
}
// 更新家长信息
export async function updateParent(
userId: number,
data: { nickname?: string; avatar_url?: string; phone?: string }
@@ -69,20 +52,14 @@ export async function updateParent(
})
}
// 获取 token
export function getToken(): string {
return Taro.getStorageSync('token')
return getStoredToken()
}
// 清除 token
export function clearToken(): void {
Taro.removeStorageSync('token')
Taro.removeStorageSync('token_type')
Taro.removeStorageSync('user_id')
Taro.removeStorageSync('expires_in')
clearLoginSession()
}
// 获取当前用户ID
export function getCurrentUserId(): number {
return Taro.getStorageSync('user_id') || 0
return getStoredCurrentUserId()
}

View File

@@ -0,0 +1,72 @@
import Taro from '@tarojs/taro'
const TOKEN_KEY = 'token'
const TOKEN_TYPE_KEY = 'token_type'
const USER_ID_KEY = 'user_id'
const EXPIRES_IN_KEY = 'expires_in'
const TOKEN_EXPIRES_AT_KEY = 'token_expires_at'
const LOGIN_PAGE_URL = '/pages/login/index'
let redirectingToLogin = false
export interface StoredLoginSession {
access_token: string
token_type: string
expires_in: number
user_id: number
}
export function saveLoginSession(session: StoredLoginSession): void {
const expiresAt = Date.now() + Math.max(session.expires_in, 0) * 1000
Taro.setStorageSync(TOKEN_KEY, session.access_token)
Taro.setStorageSync(TOKEN_TYPE_KEY, session.token_type)
Taro.setStorageSync(USER_ID_KEY, session.user_id)
Taro.setStorageSync(EXPIRES_IN_KEY, session.expires_in)
Taro.setStorageSync(TOKEN_EXPIRES_AT_KEY, expiresAt)
}
export function clearLoginSession(): void {
Taro.removeStorageSync(TOKEN_KEY)
Taro.removeStorageSync(TOKEN_TYPE_KEY)
Taro.removeStorageSync(USER_ID_KEY)
Taro.removeStorageSync(EXPIRES_IN_KEY)
Taro.removeStorageSync(TOKEN_EXPIRES_AT_KEY)
}
function getTokenExpiresAt(): number {
const rawValue = Number(Taro.getStorageSync(TOKEN_EXPIRES_AT_KEY) || 0)
return Number.isFinite(rawValue) ? rawValue : 0
}
export function getToken(): string {
const token = Taro.getStorageSync(TOKEN_KEY) || ''
if (!token) return ''
const expiresAt = getTokenExpiresAt()
if (expiresAt > 0 && Date.now() >= expiresAt) {
clearLoginSession()
return ''
}
return token
}
export function getCurrentUserId(): number {
if (!getToken()) return 0
return Number(Taro.getStorageSync(USER_ID_KEY) || 0)
}
export function handleUnauthorized(options: { redirect?: boolean } = {}): void {
const { redirect = true } = options
clearLoginSession()
if (!redirect || redirectingToLogin) return
redirectingToLogin = true
setTimeout(() => {
Taro.reLaunch({ url: LOGIN_PAGE_URL })
setTimeout(() => {
redirectingToLogin = false
}, 600)
}, 120)
}