Rapina 后端API框架配套前端代码编写指南

先明确核心:Rapina 是 Rust 后端API框架,本身不提供前端渲染能力,只输出标准 JSON + 自动生成 OpenAPI 接口文档;前端独立开发(Vue/React/原生JS等),通过 HTTP 请求调用 Rapina 接口。
官网 userapina.com 是 Rapina 项目官网,仅展示后端用法,无内置前端模板,但框架提供两大关键能力简化前端对接:

  1. 自动生成 OpenAPI 规范(可导入Swagger/Postman/前端TS类型生成器)
  2. 统一标准化错误返回、JWT鉴权规则、请求校验结构

一、前置:Rapina 后端接口约定(前端必须遵守)

1. 鉴权规则(核心)

  • 所有路由默认需要JWT鉴权,仅标记 #[public] 接口无需token
  • JWT 传递方式:请求头 Authorization: Bearer {token}
  • 未携带/过期token:统一返回401结构化错误,带 trace_id
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "missing or invalid jwt token",
    "details": []
  },
  "trace_id": "uuid字符串"
}

2. 请求校验格式(422错误)

使用 Validated<Json<T>> 的接口,参数不合法会提前拦截,返回统一结构:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      {"field": "email", "message": "invalid email"},
      {"field": "password", "message": "min 8 chars"}
    ]
  },
  "trace_id": "xxx"
}

3. 成功响应规范

后端返回 Json<T> 直接输出对象,无多余包装(健康接口直接返回纯文本 ok
示例登录返回:

{"token": "jwt字符串", "expires_at": 1789000000}

4. 全局 trace_id

所有请求响应携带 trace_id,前端日志必须打印,方便后端排查线上问题。

二、方案1:原生JS/HTML 极简前端示例

对接 Rapina 登录、获取当前用户、健康检查接口

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Rapina API Demo</title>
</head>
<body>
  <div id="app">
    <h3>登录</h3>
    <input type="email" id="email" placeholder="邮箱">
    <input type="password" id="pwd" placeholder="密码">
    <button onclick="login()">登录</button>

    <h3>我的信息</h3>
    <button onclick="getMe()">获取用户</button>
    <div id="result"></div>
  </div>

  <script>
    const BASE_URL = "http://localhost:3000";
    // 全局统一请求封装,自动携带JWT、统一错误处理
    async function request(path, options = {}) {
      const token = localStorage.getItem("rapina_token");
      const headers = {
        "Content-Type": "application/json",
        ...options.headers
      };
      // 非公开接口自动附加鉴权头
      if (token) headers.Authorization = `Bearer ${token}`;

      const res = await fetch(`${BASE_URL}${path}`, {
        ...options,
        headers
      });
      const data = await res.json();
      // 统一错误拦截
      if (!res.ok) {
        console.error("请求异常 trace_id:", data.trace_id, data.error);
        // 401 自动清除token跳登录
        if (res.status === 401) {
          localStorage.removeItem("rapina_token");
          alert("登录失效,请重新登录");
        }
        throw new Error(data.error.message);
      }
      return data;
    }

    // 1. 公开接口:健康检查 #[public]
    async function healthCheck() {
      const res = await fetch(`${BASE_URL}/health`);
      console.log("服务状态:", await res.text());
    }

    // 2. 登录接口 #[public] /login
    async function login() {
      const email = document.getElementById("email").value;
      const password = document.getElementById("pwd").value;
      try {
        const tokenData = await request("/login", {
          method: "POST",
          body: JSON.stringify({ email, password })
        });
        // 存储JWT
        localStorage.setItem("rapina_token", tokenData.token);
        alert("登录成功");
      } catch (err) {
        document.getElementById("result").innerText = err.message;
      }
    }

    // 3. 需鉴权接口 /me
    async function getMe() {
      try {
        const user = await request("/me");
        document.getElementById("result").innerText = JSON.stringify(user, null, 2);
      } catch (err) {
        document.getElementById("result").innerText = err.message;
      }
    }

    healthCheck();
  </script>
</body>
</html>

三、方案2:Vue3 + Axios 工程化前端(主流)

1. 请求封装 src/utils/rapinaRequest.js

import axios from "axios";
const service = axios.create({
  baseURL: "http://localhost:3000",
  timeout: 10000
});

// 请求拦截器:自动注入JWT
service.interceptors.request.use(config => {
  const token = localStorage.getItem("rapina_token");
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

// 响应拦截器:统一处理Rapina标准错误
service.interceptors.response.use(
  res => res.data,
  err => {
    const resData = err.response?.data;
    if (resData?.trace_id) {
      console.error("Rapina请求报错 trace_id:", resData.trace_id, resData.error);
    }
    // 401 清除登录态
    if (err.response.status === 401) {
      localStorage.removeItem("rapina_token");
      router.push("/login");
    }
    // 校验错误弹窗展示字段信息
    if (resData?.error?.code === "VALIDATION_ERROR") {
      const msg = resData.error.details.map(i => `${i.field}:${i.message}`).join(";");
      ElMessage.error(msg);
    } else {
      ElMessage.error(resData?.error?.message || "接口请求失败");
    }
    return Promise.reject(err);
  }
);
export default service;

2. API 接口分层 src/api/user.js

import request from "@/utils/rapinaRequest";

// 登录 公开接口
export function loginApi(data) {
  return request({
    url: "/login",
    method: "post",
    data
  });
}

// 创建用户
export function createUserApi(data) {
  return request({
    url: "/users",
    method: "post",
    data
  });
}

// 获取当前登录用户
export function getMeApi() {
  return request({ url: "/me", method: "get" });
}

// 根据ID查询用户
export function getUserByIdApi(id) {
  return request({ url: `/users/${id}`, method: "get" });
}

// 健康检查
export function healthApi() {
  return request({ url: "/health", method: "get" });
}

3. 页面组件调用示例

<script setup>
import { loginApi } from "@/api/user";
const loginForm = ref({ email: "", password: "" });
const submitLogin = async () => {
  const tokenInfo = await loginApi(loginForm.value);
  localStorage.setItem("rapina_token", tokenInfo.token);
};
</script>

四、方案3:React + Typescript(推荐,配合OpenAPI生成类型)

1. 利用 Rapina 自动 OpenAPI 生成TS类型

Rapina 内置 OpenAPI 文档,访问 http://localhost:3000/openapi.json 下载规范文件
使用工具 openapi-typescript 一键生成前端接口类型:

# 安装工具
npm i -D openapi-typescript
# 生成类型文件
npx openapi-typescript http://localhost:3000/openapi.json -o src/types/rapinaApi.ts

生成后直接拥有 LoginRequestUserCreateUser 等后端结构体对应的TS类型,前后端类型完全统一,杜绝参数拼写错误。

2. Axios + TS 请求封装核心片段

import axios from "axios";
import type { paths } from "@/types/rapinaApi";

const api = axios.create({ baseURL: "http://localhost:3000" });
// 登录接口类型约束
export const login = async (body: paths["/login"]["post"]["requestBody"]["content"]["application/json"]) => {
  const res = await api.post("/login", body);
  return res.data as paths["/login"]["post"]["responses"]["200"]["content"]["application/json"];
};

五、关键开发优化技巧

1. 区分公开/鉴权接口

  • #[public](/health、/login):无需token,前端直接请求
  • #[public](/me、/users/:id):请求头必须携带 Bearer token,封装层自动处理,无需手动写

2. 统一处理校验错误

Rapina 会提前校验入参,返回固定 details 数组,前端循环遍历展示表单错误,不用自己写校验逻辑:

// 提取表单错误
const errMap = {};
resData.error.details.forEach(item => {
  errMap[item.field] = item.message;
});

3. 部署与跨域配置

Rapina 内置 CORS 支持,后端可配置允许前端域名,避免跨域报错;单二进制部署,前端打包静态资源可直接通过 Rapina 托管静态文件。

4. 调试工具

  1. rapina routes 命令查看所有接口路径、是否公开
  2. 访问后端 /docs 打开自动生成的 Swagger UI,在线调试接口,复制请求示例
  3. trace_id 全链路追踪,前端埋点打印,后端日志快速定位报错请求

六、常见前端踩坑点

  1. 忘记携带Token:所有非 #[public] 接口401报错,统一在请求拦截器自动附加token
  2. 请求体格式错误:Rapina 使用 Json 序列化,前端必须传 Content-Type: application/json
  3. 路径参数格式/users/:id 后端强类型校验,传数字字符串/非法ID会直接返回校验错误
  4. 密码长度、邮箱格式:后端使用 Validate 宏拦截,前端建议同步做基础预校验提升体验
  5. token过期:后端返回401,前端清空本地存储并跳转登录页

七、极简前端技术栈推荐

  1. 快速原型:原生HTML+Fetch(无需构建工具)
  2. 后台管理:Vue3 + Element Plus + Axios
  3. 大型项目:React + TS + OpenAPI自动类型生成 + React Query
  4. 移动端:UniApp / Taro,请求封装逻辑和Web端完全通用
posted @ 2026-06-26 20:24  卓能文  阅读(6)  评论(0)    收藏  举报