FastAdmin API 接口开发:前后端分离实践

FastAdmin API 接口开发:前后端分离实践

admin
2026-07-24 / 0 评论 / 1 阅读

前言

随着前后端分离架构的普及,FastAdmin 的 API 模块越来越重要。本文将系统讲解如何使用 FastAdmin 开发 RESTful API 接口,包括 Token 认证、参数验证、数据返回格式规范以及与前端框架的对接实践。

一、API 模块概述

FastAdmin 的 API 模块位于 application/api/,专门用于提供接口服务:

application/api/
├── controller/
│   ├── Common.php          # 公共控制器
│   ├── Index.php           # 首页接口
│   ├── User.php            # 用户接口
│   ├── Api.php             # 基类控制器
│   └── Demo.php            # 示例接口
├── model/
│   └── User.php            # API 用户模型
└── config.php              # API 模块配置

二、API 基类控制器

所有 API 控制器继承自 app\common\controller\Api

<?php
// application/common/controller/Api.php
namespace app\common\controller;

use think\Controller;
use app\common\library\Token;
use app\common\library\Auth;

class Api extends Controller
{
    // 不需要登录的方法
    protected $noNeedLogin = ['*'];

    // 不需要权限验证的方法
    protected $noNeedRight = ['*'];

    // 当前请求的 Token
    protected $token = '';

    // 当前登录用户
    protected $auth = null;

    // 初始化
    public function _initialize()
    {
        parent::_initialize();
        
        // 跨域处理
        $this->checkCors();
        
        // Token 认证
        $this->token = $this->request->server('HTTP_TOKEN', 
            $this->request->request('token', ''));
        
        // 初始化认证
        $this->auth = Auth::instance();
        
        // 检查是否需要登录
        $action = strtolower($this->request->action());
        if (!in_array($action, array_map('strtolower', $this->noNeedLogin))) {
            if (!$this->auth->isLogin()) {
                $this->error(__('Please login first'), null, 401);
            }
        }
    }

    // 跨域设置
    protected function checkCors()
    {
        header('Access-Control-Allow-Origin: *');
        header('Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS');
        header('Access-Control-Allow-Headers: token, Content-Type, Authorization');
        
        if ($this->request->method() == 'OPTIONS') {
            $this->success();
        }
    }

    // 成功返回
    protected function success($msg = '', $data = null, $code = 1)
    {
        $this->result($msg, $data, $code);
    }

    // 失败返回
    protected function error($msg = '', $data = null, $code = 0)
    {
        $this->result($msg, $data, $code);
    }

    // 统一返回格式
    protected function result($msg, $data = null, $code = 1)
    {
        $result = [
            'code'    => $code,
            'msg'     => $msg,
            'time'    => time(),
            'data'    => $data,
        ];
        throw new \think\exception\HttpResponseException(
            json($result)
        );
    }
}

统一返回格式

{
    "code": 1,
    "msg": "获取成功",
    "time": 1721800000,
    "data": {
        "id": 1,
        "username": "admin"
    }
}
字段说明
code1=成功,0=失败
msg提示消息
time服务器时间戳
data业务数据

三、用户认证

3.1 注册接口

<?php
// application/api/controller/User.php
namespace app\api\controller;

use app\common\controller\Api;
use app\common\model\User;
use app\common\library\Token;
use think\Validate;

class User extends Api
{
    protected $noNeedLogin = ['register', 'login'];
    protected $noNeedRight = ['*'];

    /**
     * 用户注册
     * POST /api/user/register
     */
    public function register()
    {
        $username = $this->request->post('username');
        $password = $this->request->post('password');
        $email = $this->request->post('email');
        $mobile = $this->request->post('mobile');

        // 参数验证
        $validate = new Validate([
            'username' => 'require|length:3,30',
            'password' => 'require|length:6,30',
            'email'    => 'require|email',
            'mobile'   => 'require|regex:/^1[3-9]\d{9}$/',
        ], [
            'username.require' => '用户名不能为空',
            'username.length'  => '用户名长度3-30个字符',
            'password.require' => '密码不能为空',
            'password.length'  => '密码长度6-30个字符',
            'email.email'      => '邮箱格式不正确',
            'mobile.regex'     => '手机号格式不正确',
        ]);

        $data = [
            'username' => $username,
            'password' => $password,
            'email'    => $email,
            'mobile'   => $mobile,
        ];

        if (!$validate->check($data)) {
            $this->error($validate->getError());
        }

        // 检查用户名是否已存在
        if (User::where('username', $username)->find()) {
            $this->error('用户名已存在');
        }

        // 创建用户
        $user = new User();
        $user->allowField(true)->save([
            'username'   => $username,
            'password'   => \app\common\library\Hash::make($password),
            'email'      => $email,
            'mobile'     => $mobile,
            'jointime'   => time(),
            'createtime' => time(),
            'updatetime' => time(),
        ]);

        // 生成 Token
        $token = Token::create($user->id, 'user', 86400 * 7);

        $this->success('注册成功', [
            'token' => $token,
            'user'  => [
                'id'       => $user->id,
                'username' => $user->username,
                'email'    => $user->email,
                'mobile'   => $user->mobile,
            ],
        ]);
    }

    /**
     * 用户登录
     * POST /api/user/login
     */
    public function login()
    {
        $account = $this->request->post('account');
        $password = $this->request->post('password');

        if (!$account || !$password) {
            $this->error('账号和密码不能为空');
        }

        // 支持用户名/邮箱/手机号登录
        $user = User::where('username|email|mobile', $account)->find();
        if (!$user) {
            $this->error('账号不存在');
        }

        if (!\app\common\library\Hash::check($password, $user->password)) {
            $this->error('密码错误');
        }

        if ($user->status != 'normal') {
            $this->error('账号已被禁用');
        }

        // 更新登录信息
        $user->save([
            'logintime' => time(),
            'loginip'   => $this->request->ip(),
        ]);

        // 生成 Token
        $token = Token::create($user->id, 'user', 86400 * 7);

        $this->success('登录成功', [
            'token' => $token,
            'user'  => [
                'id'       => $user->id,
                'username' => $user->username,
                'avatar'   => $user->avatar,
                'email'    => $user->email,
            ],
        ]);
    }

    /**
     * 获取当前用户信息
     * GET /api/user/info
     */
    public function info()
    {
        $user = $this->auth->getUser();
        $this->success('获取成功', [
            'id'        => $user->id,
            'username'  => $user->username,
            'avatar'    => $user->avatar,
            'email'     => $user->email,
            'mobile'    => $user->mobile,
            'createtime'=> $user->createtime,
        ]);
    }

    /**
     * 退出登录
     * POST /api/user/logout
     */
    public function logout()
    {
        Token::clear($this->token);
        $this->success('退出成功');
    }
}

3.2 Token 机制

FastAdmin 的 Token 存储在 fa_token 表中:

// 生成 Token(有效期 7 天)
$token = Token::create($userId, 'user', 86400 * 7);

// 验证 Token
$userId = Token::get($token);

// 刷新 Token
$token = Token::refresh($token);

// 清除 Token
Token::clear($token);

前端请求时在 Header 中携带 Token:

// axios 请求拦截器
axios.interceptors.request.use(config => {
    const token = localStorage.getItem('token');
    if (token) {
        config.headers['token'] = token;
    }
    return config;
});

// 响应拦截器处理 401
axios.interceptors.response.use(
    response => response.data,
    error => {
        if (error.response && error.response.status === 401) {
            localStorage.removeItem('token');
            router.push('/login');
        }
        return Promise.reject(error);
    }
);

四、RESTful 接口开发

4.1 文章接口完整实现

<?php
// application/api/controller/Article.php
namespace app\api\controller;

use app\common\controller\Api;
use app\common\model\Article as ArticleModel;
use think\Validate;

class Article extends Api
{
    protected $noNeedLogin = ['index', 'detail'];
    protected $noNeedRight = ['*'];

    /**
     * 文章列表
     * GET /api/article/index?page=1&limit=10&keyword=xxx
     */
    public function index()
    {
        $page = $this->request->get('page/d', 1);
        $limit = $this->request->get('limit/d', 10);
        $keyword = $this->request->get('keyword', '');
        $categoryId = $this->request->get('category_id/d', 0);

        $query = ArticleModel::where('status', 'normal');

        // 关键词搜索
        if ($keyword) {
            $query->where('title', 'like', "%{$keyword}%");
        }

        // 分类筛选
        if ($categoryId) {
            $query->where('category_id', $categoryId);
        }

        // 分页查询
        $total = $query->count();
        $list = $query->order('id', 'desc')
            ->field('id,title,author,views,createtime')
            ->page($page, $limit)
            ->select();

        $this->success('获取成功', [
            'total' => $total,
            'page'  => $page,
            'limit' => $limit,
            'list'  => $list,
        ]);
    }

    /**
     * 文章详情
     * GET /api/article/detail?id=1
     */
    public function detail()
    {
        $id = $this->request->get('id/d', 0);
        if (!$id) {
            $this->error('参数错误');
        }

        $article = ArticleModel::where('id', $id)
            ->where('status', 'normal')
            ->find();

        if (!$article) {
            $this->error('文章不存在');
        }

        // 浏览量 +1
        ArticleModel::where('id', $id)->inc('views')->update();

        $this->success('获取成功', $article);
    }

    /**
     * 创建文章
     * POST /api/article/add
     */
    public function add()
    {
        $data = $this->request->post();
        $data['admin_id'] = $this->auth->id;

        $validate = new Validate([
            'title'   => 'require|length:2,100',
            'content' => 'require|min:10',
        ], [
            'title.require'   => '标题不能为空',
            'content.require' => '内容不能为空',
        ]);

        if (!$validate->check($data)) {
            $this->error($validate->getError());
        }

        $data['createtime'] = time();
        $data['updatetime'] = time();

        $article = new ArticleModel();
        $article->allowField(true)->save($data);

        $this->success('创建成功', ['id' => $article->id]);
    }

    /**
     * 编辑文章
     * PUT /api/article/edit?id=1
     */
    public function edit()
    {
        $id = $this->request->get('id/d', 0);
        $data = $this->request->post();

        $article = ArticleModel::find($id);
        if (!$article) {
            $this->error('文章不存在');
        }

        // 权限检查:只能编辑自己的文章
        if ($article->admin_id != $this->auth->id) {
            $this->error('无权操作', null, 403);
        }

        $data['updatetime'] = time();
        $article->allowField(true)->save($data);

        $this->success('修改成功');
    }

    /**
     * 删除文章
     * DELETE /api/article/del?id=1
     */
    public function del()
    {
        $id = $this->request->get('id/d', 0);

        $article = ArticleModel::find($id);
        if (!$article) {
            $this->error('文章不存在');
        }

        if ($article->admin_id != $this->auth->id) {
            $this->error('无权操作', null, 403);
        }

        $article->delete();
        $this->success('删除成功');
    }
}

4.2 路由配置

application/api/route.php 中定义路由规则:

<?php
use think\Route;

// 文章相关
Route::get('articles', 'api/article/index');
Route::get('article/:id', 'api/article/detail');
Route::post('article', 'api/article/add');
Route::put('article/:id', 'api/article/edit');
Route::delete('article/:id', 'api/article/del');

// 用户相关
Route::post('register', 'api/user/register');
Route::post('login', 'api/user/login');
Route::get('user/info', 'api/user/info');
Route::post('logout', 'api/user/logout');

五、接口文档自动生成

FastAdmin 内置 API 文档生成功能:

# 生成 API 文档
php think api --force=true

文档输出到 public/api.html,包含所有接口的参数说明、返回示例和在线测试功能。

注释规范

/**
 * @ApiTitle    (文章列表)
 * @ApiSummary  (获取文章列表,支持分页和搜索)
 * @ApiMethod   (GET)
 * @ApiRoute    (/api/article/index)
 * @ApiParams   (name="page", type="integer", required=false, description="页码,默认1")
 * @ApiParams   (name="limit", type="integer", required=false, description="每页条数,默认10")
 * @ApiParams   (name="keyword", type="string", required=false, description="搜索关键词")
 * @ApiReturn   ({"code":1,"msg":"获取成功","data":{"total":100,"list":[...]}})
 */
public function index()
{
    // ...
}

六、与前端对接

6.1 Vue3 对接示例

// api/article.ts
import axios from 'axios'

const API_BASE = '/api'

export interface Article {
  id: number
  title: string
  author: string
  views: number
  createtime: number
}

export interface ArticleListResponse {
  total: number
  page: number
  limit: number
  list: Article[]
}

export const getArticles = (params: {
  page?: number
  limit?: number
  keyword?: string
}) => {
  return axios.get<{ code: number; data: ArticleListResponse }>(
    `${API_BASE}/article/index`,
    { params }
  )
}

export const getArticleDetail = (id: number) => {
  return axios.get<{ code: number; data: Article }>(
    `${API_BASE}/article/detail`,
    { params: { id } }
  )
}

6.2 小程序对接示例

// utils/request.js
const BASE_URL = 'https://api.example.com'

export function request(options) {
  const token = wx.getStorageSync('token')
  return new Promise((resolve, reject) => {
    wx.request({
      url: BASE_URL + options.url,
      method: options.method || 'GET',
      data: options.data,
      header: {
        'token': token,
        'Content-Type': 'application/json'
      },
      success(res) {
        if (res.data.code === 1) {
          resolve(res.data.data)
        } else if (res.data.code === 0 && res.statusCode === 401) {
          wx.removeStorageSync('token')
          wx.redirectTo({ url: '/pages/login/login' })
        } else {
          wx.showToast({ title: res.data.msg, icon: 'none' })
          reject(res.data)
        }
      },
      fail(err) {
        reject(err)
      }
    })
  })
}

// 调用
request({ url: '/api/article/index', data: { page: 1, limit: 10 } })
  .then(data => {
    console.log(data.list)
  })

七、安全防护

7.1 接口限流

// 使用缓存实现简单限流
public function _initialize()
{
    parent::_initialize();
    
    $ip = $this->request->ip();
    $key = 'api_rate_' . $ip;
    $count = \think\Cache::inc($key);
    if ($count == 1) {
        \think\Cache::expire($key, 60);  // 60秒内
    }
    if ($count > 60) {
        $this->error('请求过于频繁', null, 429);
    }
}

7.2 签名验证

// 客户端生成签名
// sign = md5(参数按key排序拼接 + secret_key)

public function verifySign($params, $sign)
{
    unset($params['sign']);
    ksort($params);
    $str = http_build_query($params) . '&key=' . config('api_secret');
    return md5($str) === $sign;
}

总结

FastAdmin API 模块提供了完整的前后端分离方案:

  • 统一返回格式和错误处理
  • Token 认证机制,支持多端登录
  • RESTful 路由设计,接口语义清晰
  • 内置 API 文档自动生成
  • 配合 Vue3/小程序等前端框架,快速搭建完整应用
0

评论 (0)

取消
0:00