优化客户端请求机制

This commit is contained in:
2026-03-31 15:34:50 +08:00
parent d9f267e257
commit 2a959fa9cf
5 changed files with 35 additions and 60 deletions

View File

@@ -1,31 +1,11 @@
## TODO
- 导航栏
- 账单页面
- 实名认证响应
分离公共 api 接口 env 定义
统一前端基础库类型api
购买页固定套餐
优惠问题
### 禁止直接依赖 form
`\[(.*,)?form(,.*)?\]`
### 次要
业务定制页面每月需求用量,可选项需要确认是否合理
帮助中心文档优化
购买与提取手机端优化,尽量一页展示全部
全部替换封装时间范围组件,检查结束时间字段手机端适配问题(需要尾部对齐)
树组件优化
## 目录结构

View File

@@ -2,15 +2,7 @@
import {cookies} from 'next/headers'
import {ApiResponse, UnauthorizedError} from '@/lib/api'
import {User} from '@/lib/models'
import {callByDevice, callByUser} from '@/actions/base'
type TokenResp = {
access_token: string
refresh_token: string
expires_in: number
token_type: string
scope?: string
}
import {callByDevice, callByUser, TokenResp} from '@/actions/base'
export type LoginMode = 'phone_code' | 'password'

View File

@@ -1,8 +1,16 @@
'use server'
import {API_BASE_URL, ApiResponse, CLIENT_ID, CLIENT_SECRET} from '@/lib/api'
import {add, isBefore} from 'date-fns'
import {cookies, headers} from 'next/headers'
import {cache} from 'react'
import {redirect} from 'next/navigation'
export type TokenResp = {
access_token: string
refresh_token: string
expires_in: number
token_type: string
scope?: string
}
// ======================
// public
@@ -26,6 +34,9 @@ const _callPublic = cache(async <R = undefined>(
// device
// ======================
let token: string | null = null
let token_expire: Date | null = null
async function callByDevice<R = undefined>(
endpoint: string,
data: unknown,
@@ -37,18 +48,20 @@ const _callByDevice = cache(async <R = undefined>(
endpoint: string,
data?: string,
): Promise<ApiResponse<R>> => {
// 获取设备令牌
if (!CLIENT_ID || !CLIENT_SECRET) {
return {
success: false,
status: 401,
message: '未配置 CLIENT_ID 或 CLIENT_SECRET',
if (!token || !token_expire || isBefore(token_expire, new Date())) {
const basic = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64url')
const resp = await call<TokenResp>(`${API_BASE_URL}/api/auth/token`, JSON.stringify({
grant_type: 'client_credentials',
}), `Basic ${basic}`)
if (!resp.success) {
return resp
}
token = resp.data.access_token
token_expire = add(new Date(), {seconds: resp.data.expires_in})
}
const token = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64url')
// 发起请求
return call(`${API_BASE_URL}${endpoint}`, data, `Basic ${token}`)
return call(`${API_BASE_URL}${endpoint}`, data, `Bearer ${token}`)
})
// ======================
@@ -149,24 +162,6 @@ async function call<R = undefined>(url: string, body: RequestInit['body'], auth?
throw new Error(`无法解析响应数据,未处理的 Content-Type: ${type}`)
}
async function postCall<R = undefined>(rawResp: Promise<ApiResponse<R>>) {
const header = await headers()
const pathname = header.get('x-pathname') || '/'
const resp = await rawResp
// 重定向到登录页
const match = [
RegExp(`^/admin.*`),
].some(item => item.test(pathname))
if (match && !resp.success && resp.status === 401) {
console.log('🚗🚗🚗🚗🚗 非正常重定向 🚗🚗🚗🚗🚗')
redirect('/login?force=true')
}
return resp
}
// 导出
export {
callPublic,

View File

@@ -28,7 +28,7 @@ export async function sendSMS(props: {
}
// 请求发送短信
return await callByDevice('/api/auth/verify/sms', {
return await callByDevice('/api/verify/sms', {
phone: props.phone,
purpose: 0,
})

View File

@@ -1,7 +1,15 @@
// 定义后端服务URL和OAuth2配置
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL
const CLIENT_ID = process.env.CLIENT_ID
const CLIENT_SECRET = process.env.CLIENT_SECRET
const _api_base_url = process.env.NEXT_PUBLIC_API_BASE_URL
if (!_api_base_url) throw new Error('NEXT_PUBLIC_API_BASE_URL is not set')
const API_BASE_URL = _api_base_url
const _client_id = process.env.CLIENT_ID
if (!_client_id) throw new Error('CLIENT_ID is not set')
const CLIENT_ID = _client_id
const _client_secret = process.env.CLIENT_SECRET
if (!_client_secret) throw new Error('CLIENT_SECRET is not set')
const CLIENT_SECRET = _client_secret
// 统一的API响应类型
type ApiResponse<T = undefined> = {