项目完成时间: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; }
|
创建 Token
1 2 3 4 5 6 7
| $token = $user->createToken('api')->plainTextToken;
return response()->json([ 'token' => $token // 格式:1|abcd1234... ]);
|
使用 Token 认证
1 2 3 4 5 6 7
| Route::middleware('auth:sanctum')->group(function () { Route::get('/user', [LoginController::class, 'user']); });
curl -H "Authorization: Bearer 1|abcd1234..." http:
|
2. Model 关系定义
一对多(hasMany / belongsTo)
1 2 3 4 5 6 7 8 9 10 11
| public function articles() { return $this->hasMany(Article::class); }
public function user() { return $this->belongsTo(User::class); }
|
多对多(belongsToMany)
1 2 3 4 5 6 7 8 9
| 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() ✅ |
定义验证规则
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, 'user' => [ 'id' => $this->user->id, 'name' => $this->user->name, ], ]; } }
|
一对多/多对多关系格式化
1 2 3 4 5 6 7
| 'tags' => $this->tags->map(function ($tag) { return ['id' => $tag->id, 'name' => $tag->name]; }),
'tags' => TagResource::collection($this->tags),
|
5. 预加载(解决 N+1 问题)
1 2 3 4 5 6 7 8
| $articles = Article::all(); foreach ($articles as $article) { echo $article->user->name; }
$articles = Article::with('user', 'category', 'tags')->get();
|
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'); $article->increment('views', 5);
|
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', ]; }
|
1 2 3 4 5
| '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