Laravel Blog API 项目总结

项目完成时间:2026-07-10
技术栈:Laravel 11 + MySQL 8.0 + Laravel Sanctum

项目架构

文件结构

1
2
3
4
5
6
7
8
9
10
blog/
├── app/
│ ├── Http/
│ │ ├── Controllers/ # 控制器
│ │ ├── Middleware/ # 中间件
│ │ ├── Requests/ # 表单验证
│ │ └── Resources/ # API 资源格式化
│ └── Models/ # 数据模型
├── routes/api.php # API 路由
└── database/migrations/ # 数据库迁移文件

数据库设计

表名 说明 关键字段
users 用户表 id, name, email, password, is_admin
articles 文章表 id, user_id, category_id, title, content, status, views
categories 分类表 id, name, slug
tags 标签表 id, name, slug
article_tag 文章标签关联表 article_id, tag_id
comments 评论表 id, article_id, user_id, content, status

关系图

1
2
3
4
5
6
7
User (用户)
↓ hasMany
Article (文章)
↓ belongsTo ↓ belongsToMany
Category (分类) Tag (标签)
↓ hasMany
Comment (评论)

核心技术点

1. Laravel Sanctum 认证

User Model 配置

1
2
3
4
5
6
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
use HasApiTokens; // ✅ 必须添加这个 trait
}

创建 Token

1
2
3
4
5
6
7
// 注册/登录时创建 Token
$token = $user->createToken('api')->plainTextToken;

// 返回给前端
return response()->json([
'token' => $token // 格式:1|abcd1234...
]);

使用 Token 认证

1
2
3
4
5
6
7
// 路由中使用 auth:sanctum 中间件
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', [LoginController::class, 'user']);
});

// 前端请求时带上 Token
curl -H "Authorization: Bearer 1|abcd1234..." http://blog.test/api/user

2. Model 关系定义

一对多(hasMany / belongsTo)

1
2
3
4
5
6
7
8
9
10
11
// User 有多篇文章
public function articles()
{
return $this->hasMany(Article::class);
}

// Article 属于一个 User
public function user()
{
return $this->belongsTo(User::class);
}

多对多(belongsToMany)

1
2
3
4
5
6
7
8
9
// Article 有多个标签
public function tags()
{
return $this->belongsToMany(Tag::class, 'article_tag', 'article_id', 'tag_id');
}

// 关联标签
$article->tags()->attach([1, 2, 3]); // 添加多个标签
$article->tags()->sync([1, 2, 3]); // 同步(替换)标签

关系返回类型(重要!)

关系方法 返回类型 访问方式
belongsTo() 单个对象 $this->relation->field
hasOne() 单个对象 $this->relation->field
hasMany() Collection 遍历或 map()
belongsToMany() Collection 遍历或 map()

3. FormRequest 表单验证

定义验证规则

1
2
3
4
5
6
7
8
9
10
11
12
13
class ArticlesRequest extends FormRequest
{
public function rules(): array
{
return [
'title' => 'required|string|max:255',
'content' => 'required|string',
'category_id' => 'required|integer|exists:categories,id',
'tag_ids' => 'nullable|array|exists:tags,id',
'status' => 'nullable|string|in:draft,published,archived'
];
}
}

Controller 中使用

1
2
3
4
public function store(ArticlesRequest $request)
{
$params = $request->validated(); // ✅ 获取验证后的数据
}

4. API Resource 格式化

一对一关系格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ArticlesResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,

// ✅ belongsTo/hasOne - 单个对象
'user' => [
'id' => $this->user->id,
'name' => $this->user->name,
],
];
}
}

一对多/多对多关系格式化

1
2
3
4
5
6
7
// ✅ hasMany/belongsToMany - Collection,需要遍历
'tags' => $this->tags->map(function ($tag) {
return ['id' => $tag->id, 'name' => $tag->name];
}),

// ✅ 或使用 Resource::collection
'tags' => TagResource::collection($this->tags),

5. 预加载(解决 N+1 问题)

1
2
3
4
5
6
7
8
// ❌ 没有 with,产生 N+1 问题
$articles = Article::all(); // 1 次查询
foreach ($articles as $article) {
echo $article->user->name; // 每次循环都查询
}

// ✅ 使用 with 预加载
$articles = Article::with('user', 'category', 'tags')->get(); // 4 次查询

6. 权限检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function destroy(Request $request, $id)
{
$article = Article::find($id);

if (!$article) {
return response()->json(['code' => 404, 'message' => '文章不存在']);
}

// ✅ 检查权限
if ($article->user_id !== $request->user()->id) {
return response()->json(['code' => 403, 'message' => '无权删除']);
}

$article->delete();
}

7. 数据库原子操作

1
2
3
4
5
6
// ✅ 推荐用法
$article->increment('views'); // 浏览量 +1,并发安全
$article->increment('views', 5); // +5

// ⚠️ 不要和 save 同时使用
// ❌ $article->increment('views'); $article->views++; $article->save();

API 接口文档

认证接口

接口 方法 说明
/api/register POST 用户注册
/api/login POST 用户登录
/api/user GET 获取用户信息
/api/logout POST 用户登出

文章接口

接口 方法 说明
/api/articles GET 获取文章列表
/api/articles POST 创建文章
/api/articles/{id} GET 获取文章详情
/api/articles/{id}/view POST 更新浏览量
/api/my/articles GET 获取我的文章

评论接口

接口 方法 说明
/api/articles/{article_id}/comments GET 获取文章评论
/api/articles/{article_id}/comments POST 创建评论
/api/comments/{id} DELETE 删除评论

分类和标签接口

接口 方法 说明
/api/categories GET 获取分类列表
/api/categories POST 创建分类(管理员)
/api/tags GET 获取标签列表
/api/tags POST 创建标签(管理员)

管理员接口

接口 方法 说明
/api/admin/stats GET 获取统计数据

常见问题解决方案

问题 1:Trait “HasApiTokens” not found

1
2
3
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate

问题 2:Field ‘user_id’ doesn’t have a default value

1
2
3
4
5
6
7
8
9
class Article extends Model
{
protected $fillable = [
'user_id', // ✅ 必须包含
'category_id',
'title',
'content',
];
}

问题 3:Resource 中访问 tags 属性报错

1
2
3
4
5
// ❌ 错误(tags 是 Collection)
'tags' => ['id' => $this->tags->id]

// ✅ 正确写法
'tags' => $this->tags->map(fn($tag) => ['id' => $tag->id, 'name' => $tag->name])

问题 4:浏览量增加不准确

1
2
3
4
5
6
// ✅ 正确(二选一)
$article->increment('views'); // 推荐用法

// 或
$article->views++;
$article->save();

开发经验总结

Model 开发规范

  • ✅ 必须定义 $fillable
  • ✅ 定义关系方法
  • ✅ 文件名和类名保持单数

Controller 开发规范

  • ✅ 使用 FormRequest 验证
  • ✅ 添加权限检查
  • ✅ 使用预加载
  • ✅ 使用原子操作

响应格式统一

1
2
3
4
5
6
7
8
9
10
11
12
// ✅ 成功响应
return response()->json([
'code' => 0,
'message' => '操作成功',
'data' => $data,
]);

// ✅ 错误响应
return response()->json([
'code' => 404,
'message' => '资源不存在',
], 404);

参考资料


项目完成!🎉
最后更新:2026-07-10