---
description: "小程序RPC客户端开发规范 - 基于Taro + Hono RPC的完整实现指南"
---

# 小程序RPC开发规范

## 概述

本文档定义了小程序端使用Taro框架结合Hono RPC客户端的标准开发规范。基于现有的`mini/src/api.ts`、`mini/src/utils/rpc-client.ts`和`mini/src/pages/login/wechat-login.tsx`中的最佳实践。

## 核心架构

### 1. RPC客户端配置

#### 1.1 客户端初始化 (`mini/src/utils/rpc-client.ts`)

```typescript
// 环境配置
const API_BASE_URL = process.env.TARO_APP_API_BASE_URL || 'http://localhost:3000'

// 自定义fetch适配Taro.request
const taroFetch: any = async (input, init) => {
  const url = typeof input === 'string' ? input : input.url
  const method = init.method || 'GET'
  
  const requestHeaders: Record<string, string> = init.headers;

  // 自动设置content-type
  const keyOfContentType = Object.keys(requestHeaders).find(item => item.toLowerCase() === 'content-type')
  if (!keyOfContentType) {
    requestHeaders['content-type'] = 'application/json'
  }

  // 构建Taro请求选项
  const options: Taro.request.Option = {
    url,
    method: method as any,
    data: init.body,
    header: requestHeaders
  }

  // 自动添加token认证
  const token = Taro.getStorageSync('mini_token')
  if (token) {
    options.header = {
      ...options.header,
      'Authorization': `Bearer ${token}`
    }
  }

  try {
    const response = await Taro.request(options)
    
    // 处理响应数据
    const body = response.statusCode === 204
      ? null
      : responseHeaders['content-type']!.includes('application/json')
        ? JSON.stringify(response.data)
        : response.data;

    return new ResponsePolyfill(
      body,
      {
        status: response.statusCode,
        statusText: response.errMsg || 'OK',
        headers: responseHeaders
      }
    )
  } catch (error) {
    console.error('API Error:', error)
    Taro.showToast({
      title: error.message || '网络错误',
      icon: 'none'
    })
    throw error
  }
}

// 创建Hono RPC客户端工厂函数
export const rpcClient = <T extends Hono>() => {
  return hc<T>(`${API_BASE_URL}`, {
    fetch: taroFetch
  })
}
```

#### 1.2 客户端API定义 (`mini/src/api.ts`)

```typescript
import type { AuthRoutes, UserRoutes, RoleRoutes, FileRoutes } from '@/server/api'
import { rpcClient } from './utils/rpc-client'

// 创建各个模块的RPC客户端
export const authClient = rpcClient<AuthRoutes>().api.v1.auth
export const userClient = rpcClient<UserRoutes>().api.v1.users
export const roleClient = rpcClient<RoleRoutes>().api.v1.roles
export const fileClient = rpcClient<FileRoutes>().api.v1.files
```

## 使用规范

### 2.1 调用方式

#### 标准GET请求
```typescript
const response = await userClient.$get({
  query: {
    page: 1,
    pageSize: 10
  }
})
```

#### POST请求（带请求体）
```typescript
const response = await authClient['mini-login'].$post({
  json: {
    code: loginRes.code,
    userInfo: userProfile.userInfo
  }
})
```

#### 带路径参数的请求
```typescript
const response = await userClient[':id'].$get({
  param: {
    id: userId
  }
})
```

### 2.2 响应处理规范

#### 成功响应处理
```typescript
if (response.status === 200) {
  const { token, user, isNewUser } = await response.json()
  
  // 保存token到本地存储
  Taro.setStorageSync('mini_token', token)
  Taro.setStorageSync('userInfo', user)
  
  // 显示成功提示
  Taro.showToast({
    title: isNewUser ? '注册成功' : '登录成功',
    icon: 'success',
    duration: 1500
  })
}
```

#### 错误响应处理
```typescript
try {
  const response = await authClient['mini-login'].$post({
    json: { code, userInfo }
  })
  
  if (response.status !== 200) {
    const errorData = await response.json()
    throw new Error(errorData.message || '操作失败')
  }
} catch (error: any) {
  const errorMessage = error.message || '网络错误'
  
  // 分类处理错误
  if (errorMessage.includes('用户拒绝授权')) {
    Taro.showModal({
      title: '提示',
      content: '需要授权才能使用小程序的全部功能',
      showCancel: false
    })
  } else {
    Taro.showToast({
      title: errorMessage,
      icon: 'none',
      duration: 3000
    })
  }
}
```

## 微信小程序特殊场景

### 3.1 微信登录流程

基于`mini/src/pages/login/wechat-login.tsx`的最佳实践：

```typescript
const handleWechatLogin = async () => {
  try {
    Taro.showLoading({
      title: '登录中...',
      mask: true
    })

    // 1. 获取用户信息授权
    const userProfile = await Taro.getUserProfile({
      desc: '用于完善用户资料'
    })

    // 2. 获取登录code
    const loginRes = await Taro.login()
    
    if (!loginRes.code) {
      throw new Error('获取登录凭证失败')
    }

    // 3. 调用RPC接口
    const response = await authClient['mini-login'].$post({
      json: {
        code: loginRes.code,
        userInfo: userProfile.userInfo
      }
    })

    Taro.hideLoading()

    if (response.status === 200) {
      const { token, user, isNewUser } = await response.json()
      
      // 4. 保存登录态
      Taro.setStorageSync('userInfo', user)
      Taro.setStorageSync('mini_token', token)
      
      // 5. 跳转页面
      Taro.switchTab({ url: '/pages/index/index' })
    }
  } catch (error) {
    Taro.hideLoading()
    // 错误处理...
  }
}
```

### 3.2 平台检测

```typescript
import { isWeapp } from '@/utils/platform'

// 检查是否为微信小程序环境
const wechatEnv = isWeapp()
if (!wechatEnv) {
  Taro.showModal({
    title: '提示',
    content: '微信登录功能仅支持在微信小程序中使用',
    showCancel: false
  })
}
```

## 开发规范

### 4.1 文件结构

```
mini/
├── src/
│   ├── api.ts              # RPC客户端定义
│   ├── utils/
│   │   └── rpc-client.ts   # RPC客户端工厂
│   └── pages/
│       └── [功能页面]/
│           └── index.tsx   # 页面逻辑
```

### 4.2 命名规范

- **客户端命名**：`[模块名]Client`（如`authClient`、`userClient`）
- **方法命名**：遵循RESTful规范（如`$get`、`$post`、`$put`、`$delete`）
- **路径命名**：使用小写字母和连字符（如`mini-login`）

### 4.3 类型安全

```typescript
// 使用InferResponseType提取响应类型
import type { InferResponseType } from 'hono/client'
type LoginResponse = InferResponseType<typeof authClient['mini-login']['$post'], 200>

// 使用InferRequestType提取请求类型
import type { InferRequestType } from 'hono/client'
type LoginRequest = InferRequestType<typeof authClient['mini-login']['$post']>['json']
```

### 4.4 环境配置

在`mini/.env`中配置API地址：

```bash
TARO_APP_API_BASE_URL=https://your-api-domain.com
```

## 最佳实践

### 5.1 请求封装

```typescript
// 创建通用请求hook
const useApiRequest = () => {
  const [loading, setLoading] = useState(false)
  
  const request = async <T>(
    apiCall: () => Promise<Response>,
    successCallback?: (data: T) => void,
    errorCallback?: (error: Error) => void
  ) => {
    setLoading(true)
    try {
      const response = await apiCall()
      const data = await response.json()
      
      if (response.status === 200) {
        successCallback?.(data)
      } else {
        throw new Error(data.message || '请求失败')
      }
    } catch (error) {
      errorCallback?.(error)
    } finally {
      setLoading(false)
    }
  }
  
  return { loading, request }
}
```

### 5.2 错误处理

```typescript
const handleApiError = (error: any) => {
  const message = error.message || '网络错误'
  
  // 网络错误
  if (message.includes('Network') || message.includes('网络')) {
    Taro.showModal({
      title: '网络错误',
      content: '请检查网络连接后重试',
      showCancel: false
    })
    return
  }
  
  // 业务错误
  Taro.showToast({
    title: message,
    icon: 'none',
    duration: 3000
  })
}
```

### 5.3 加载状态管理

```typescript
const [loading, setLoading] = useState(false)

const handleRequest = async () => {
  setLoading(true)
  Taro.showLoading({ title: '加载中...' })
  
  try {
    const response = await apiClient.method.$post({ json: data })
    // 处理响应...
  } finally {
    Taro.hideLoading()
    setLoading(false)
  }
}