Ver código fonte

♻️ refactor(api): 重构API客户端为Hono RPC客户端架构

- 实现Taro.request到fetch接口的适配,创建taroFetch函数
- 引入hono/client创建类型安全的RPC客户端
- 重构认证、用户、角色和文件模块为独立的RPC客户端
- 添加@/server/*路径映射,支持导入后端类型定义

🔧 chore(config): 添加服务器类型路径映射

- 在tsconfig.json中添加@/server/*路径映射到../src/server/*
- 支持前端直接引用后端API类型定义,实现类型安全的API调用

🐛 fix(auth): 优化401未授权处理逻辑

- 统一错误处理流程,使用Taro.showToast显示错误信息
- 保持清除token并跳转登录页的行为,增强用户体验
yourname 4 meses atrás
pai
commit
7dd0a1aec5
2 arquivos alterados com 74 adições e 122 exclusões
  1. 72 121
      mini/src/utils/api.ts
  2. 2 1
      mini/tsconfig.json

+ 72 - 121
mini/src/utils/api.ts

@@ -1,4 +1,6 @@
 import Taro from '@tarojs/taro'
+import { hc } from 'hono/client'
+import type { AuthRoutes, UserRoutes, RoleRoutes, FileRoutes } from '@/server/api'
 
 // API配置
 const API_BASE_URL = process.env.TARO_APP_API_BASE_URL || 'http://localhost:3000'
@@ -7,8 +9,22 @@ const API_VERSION = process.env.TARO_APP_API_VERSION || 'v1'
 // 完整的API地址
 const BASE_URL = `${API_BASE_URL}/api/${API_VERSION}`
 
-// 请求拦截器
-const requestInterceptor = (options: Taro.request.Option) => {
+// 创建自定义fetch函数,适配Taro.request
+const taroFetch: typeof fetch = async (input, init) => {
+  const url = typeof input === 'string' ? input : input.url
+  const method = init?.method || 'GET'
+  
+  // 构建Taro请求选项
+  const options: Taro.request.Option = {
+    url,
+    method: method as any,
+    data: init?.body,
+    header: {
+      'Content-Type': 'application/json',
+      ...Object.fromEntries(new Headers(init?.headers || {}))
+    }
+  }
+
   // 添加token
   const token = Taro.getStorageSync('token')
   if (token) {
@@ -17,133 +33,68 @@ const requestInterceptor = (options: Taro.request.Option) => {
       'Authorization': `Bearer ${token}`
     }
   }
-  
-  // 设置基础URL
-  options.url = `${BASE_URL}${options.url}`
-  
-  // 设置默认header
-  options.header = {
-    'Content-Type': 'application/json',
-    ...options.header
-  }
-  
-  return options
-}
-
-// 响应拦截器
-const responseInterceptor = (response: Taro.request.SuccessCallbackResult<any>) => {
-  const { statusCode, data } = response
-  
-  if (statusCode === 200 || statusCode === 201) {
-    // 检查数据结构,支持后端返回的格式
-    if (data && data.data) {
-      return data.data
-    }
-    return data
-  } else if (statusCode === 401) {
-    // 未授权,清除token并跳转到登录页
-    Taro.removeStorageSync('token')
-    Taro.removeStorageSync('userInfo')
-    Taro.navigateTo({ url: '/pages/login/index' })
-    throw new Error('请重新登录')
-  } else {
-    throw new Error(data.message || '请求失败')
-  }
-}
 
-// 错误处理
-const errorHandler = (error: any) => {
-  console.error('API Error:', error)
-  Taro.showToast({
-    title: error.message || '网络错误',
-    icon: 'none'
-  })
-  throw error
-}
-
-// 封装请求方法
-class ApiClient {
-  async request(options: Taro.request.Option) {
-    try {
-      const finalOptions = requestInterceptor(options)
-      const response = await Taro.request(finalOptions)
-      return responseInterceptor(response)
-    } catch (error) {
-      errorHandler(error)
+  try {
+    const response = await Taro.request(options)
+    
+    // 处理401未授权
+    if (response.statusCode === 401) {
+      Taro.removeStorageSync('token')
+      Taro.removeStorageSync('userInfo')
+      Taro.navigateTo({ url: '/pages/login/index' })
+      throw new Error('请重新登录')
     }
-  }
 
-  async get(url: string, params?: any) {
-    return this.request({
-      url,
-      method: 'GET',
-      data: params
-    })
-  }
-
-  async post(url: string, data?: any) {
-    return this.request({
-      url,
-      method: 'POST',
-      data
-    })
-  }
-
-  async put(url: string, data?: any) {
-    return this.request({
-      url,
-      method: 'PUT',
-      data
-    })
-  }
-
-  async delete(url: string) {
-    return this.request({
-      url,
-      method: 'DELETE'
+    // 适配fetch响应格式
+    return {
+      ok: response.statusCode >= 200 && response.statusCode < 300,
+      status: response.statusCode,
+      statusText: response.errMsg || 'OK',
+      headers: new Headers(response.header || {}),
+      url: response.data?.url || url,
+      json: async () => response.data,
+      text: async () => JSON.stringify(response.data),
+      blob: async () => new Blob([JSON.stringify(response.data)]),
+      arrayBuffer: async () => new TextEncoder().encode(JSON.stringify(response.data)),
+      clone: () => ({}) as Response,
+      body: null as any,
+      bodyUsed: true
+    } as Response
+  } catch (error) {
+    console.error('API Error:', error)
+    Taro.showToast({
+      title: error.message || '网络错误',
+      icon: 'none'
     })
+    throw error
   }
 }
 
-// 创建单例实例
-export const apiClient = new ApiClient()
-
-// 认证相关API
-export const authApi = {
-  // 密码登录
-  login: (data: { username: string; password: string }) => 
-    apiClient.post('/auth/login', data),
-  
-  // 用户注册
-  register: (data: { username: string; password: string; email?: string }) => 
-    apiClient.post('/auth/register', data),
-  
-  // 获取当前用户信息
-  getCurrentUser: () => 
-    apiClient.get('/auth/me'),
-  
-  // 退出登录
-  logout: () => 
-    apiClient.post('/auth/logout')
+// 创建Hono RPC客户端
+const createRpcClient = <T>(basePath: string) => {
+  return hc<T>(`${BASE_URL}${basePath}`, {
+    fetch: taroFetch
+  })
 }
 
-// 用户相关API
-export const userApi = {
-  // 获取用户列表
-  getUsers: (params?: any) => 
-    apiClient.get('/users', params),
-  
-  // 获取单个用户信息
-  getUser: (id: number) => 
-    apiClient.get(`/users/${id}`),
-  
-  // 更新用户信息
-  updateUser: (id: number, data: any) => 
-    apiClient.put(`/users/${id}`, data),
-  
-  // 删除用户
-  deleteUser: (id: number) => 
-    apiClient.delete(`/users/${id}`)
+// 创建各个模块的RPC客户端
+export const authClient = createRpcClient<AuthRoutes>('/auth')
+export const userClient = createRpcClient<UserRoutes>('/users')
+export const roleClient = createRpcClient<RoleRoutes>('/roles')
+export const fileClient = createRpcClient<FileRoutes>('/files')
+
+// 类型定义
+export type {
+  AuthRoutes,
+  UserRoutes,
+  RoleRoutes,
+  FileRoutes
 }
 
-export default apiClient
+// 默认导出RPC客户端
+export default {
+  auth: authClient,
+  users: userClient,
+  roles: roleClient,
+  files: fileClient,
+}

+ 2 - 1
mini/tsconfig.json

@@ -22,7 +22,8 @@
     ],
     "paths": {
       // TS5090 leading './'
-      "@/*": ["./src/*"]
+      "@/*": ["./src/*"],
+      "@/server/*": ["../src/server/*"]
     }
   },
   "include": ["./src", "./types", "./config"],