diff --git a/README.md b/README.md index 6f89b30..8cb2314 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,11 @@ ## TODO -- 导航栏 -- 账单页面 -- 实名认证响应 - 分离公共 api 接口 env 定义 统一前端基础库(类型,api) 购买页固定套餐 -优惠问题 - -### 禁止直接依赖 form - -`\[(.*,)?form(,.*)?\]` - -### 次要 - -业务定制页面每月需求用量,可选项需要确认是否合理 - -帮助中心文档优化 - -购买与提取手机端优化,尽量一页展示全部 - -全部替换封装时间范围组件,检查结束时间字段手机端适配问题(需要尾部对齐) - 树组件优化 ## 目录结构 diff --git a/src/actions/auth.ts b/src/actions/auth.ts index c675810..bae061b 100644 --- a/src/actions/auth.ts +++ b/src/actions/auth.ts @@ -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' diff --git a/src/actions/base.ts b/src/actions/base.ts index 5552e14..c01ca6e 100644 --- a/src/actions/base.ts +++ b/src/actions/base.ts @@ -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 ( // device // ====================== +let token: string | null = null +let token_expire: Date | null = null + async function callByDevice( endpoint: string, data: unknown, @@ -37,18 +48,20 @@ const _callByDevice = cache(async ( endpoint: string, data?: string, ): Promise> => { - // 获取设备令牌 - 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(`${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(url: string, body: RequestInit['body'], auth? throw new Error(`无法解析响应数据,未处理的 Content-Type: ${type}`) } -async function postCall(rawResp: Promise>) { - 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, diff --git a/src/actions/verify.ts b/src/actions/verify.ts index 3a24712..0ec2dbf 100644 --- a/src/actions/verify.ts +++ b/src/actions/verify.ts @@ -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, }) diff --git a/src/lib/api.ts b/src/lib/api.ts index b15ef2c..a673f50 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -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 = {