前言
随着前后端分离架构的普及,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"
}
}| 字段 | 说明 |
|---|---|
code | 1=成功,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)