Laravel游戏论坛API开发完整教程

摘要

关于这部分,我的实际体会是这样的:本文详细介绍Laravel游戏论坛API的完整开发过程,包括1000y项目回顾、89个API端点设计、64张表数据库架构、核心功能实现代码示例。

项目回顾

1000y游戏论坛

  • 域名:https://1000y.chencunli.com
  • 框架:Laravel 11.48.0 + PHP 8.4.18
  • API数量:89个端点(已上线)
  • 数据库:1000y(64张表)
  • 读写分离:HyperDB + MySQL主从复制

路由设计

路由文件结构

routes/
├── api.php          # API路由
├── web.php          # Web路由
└── channels.php     # 广播通道

API路由分组

// routes/api.php

// ========================================
// 公开API
// ========================================
Route::prefix("v1")->group(function () {
    // 游戏相关
    Route::apiResource("games", GameController::class);
    Route::get("games/{slug}/articles", [GameArticleController::class, "byGame"]);
    Route::get("games/{slug}/primary", [GameController::class, "getPrimary"]);
    
    // 文章相关
    Route::apiResource("articles", ArticleController::class);
    Route::get("articles/{id}/comments", [CommentController::class, "index"]);
    
    // 分类和标签
    Route::apiResource("categories", CategoryController::class);
    Route::apiResource("tags", TagController::class);
});

// ========================================
// 需要认证的API
// ========================================
Route::middleware("auth:sanctum")->group(function () {
    // 用户文章管理
    Route::post("articles", [ArticleController::class, "store"]);
    Route::put("articles/{id}", [ArticleController::class, "update"]);
    Route::delete("articles/{id}", [ArticleController::class, "destroy"]);
    
    // 评论管理
    Route::post("articles/{id}/comments", [CommentController::class, "store"]);
    Route::put("comments/{id}", [CommentController::class, "update"]);
    Route::delete("comments/{id}", [CommentController::class, "destroy"]);
});

重要:路由排序规则

具体路由必须在通配符路由之前!

// ✅ 正确顺序
Route::get("games/{slug}/primary", [GameController::class, "getPrimary"]);
Route::get("games/{slug}", [GameController::class, "show"]);

// ❌ 错误顺序
Route::get("games/{slug}", [GameController::class, "show"]);
Route::get("games/{slug}/primary", [GameController::class, "getPrimary"]);
// /games/primary 会被第一条路由捕获,slug值为"primary"

数据库架构

核心表结构

表名 说明 关联
games 游戏表 hasMany(articles)
game_articles 游戏文章 belongsTo(Game)
categories 分类表 belongsToMany(Articles)
tags 标签表 belongsToMany(Articles)
comments 评论表 belongsTo(Article)
users 用户表 hasMany(Articles)

游戏表迁移示例

// database/migrations/2024_01_01_create_games_table.php

Schema::create("games", function (Blueprint $table) {
    $table->id();
    $table->string("name");
    $table->string("slug")->unique();
    $table->text("description")->nullable();
    $table->string("cover_image")->nullable();
    $table->string("theme_color")->default("#00f0ff");
    $table->boolean("is_primary")->default(false);
    $table->integer("sort_order")->default(0);
    $table->timestamps();
    $table->softDeletes();
});

核心功能实现

1. 游戏列表API

// app/Http/Controllers/GameController.php

class GameController extends Controller
{
    /**
     * 获取游戏列表
     * GET /api/v1/games
     */
    public function index(Request $request)
    {
        $games = Game::query()
            ->withCount("articles as articles_count")
            ->orderBy("sort_order")
            ->orderBy("id", "desc")
            ->paginate($request->get("per_page", 12));

        return response()->json([
            "success" => true,
            "data" => $games->items(),
            "pagination" => [
                "total" => $games->total(),
                "per_page" => $games->perPage(),
                "current_page" => $games->currentPage(),
                "last_page" => $games->lastPage(),
            ]
        ]);
    }

    /**
     * 获取单个游戏详情
     * GET /api/v1/games/{slug}
     */
    public function show($slug)
    {
        $game = Game::where("slug", $slug)
            ->with(["articles" => function ($query) {
                $query->limit(10);
            }])
            ->firstOrFail();

        return response()->json([
            "success" => true,
            "data" => $game
        ]);
    }

    /**
     * 获取主推游戏
     * GET /api/v1/games/primary
     */
    public function getPrimary()
    {
        $game = Game::where("is_primary", true)
            ->firstOrFail();

        return response()->json([
            "success" => true,
            "data" => $game
        ]);
    }
}

2. 文章详情API

// app/Http/Controllers/ArticleController.php

class ArticleController extends Controller
{
    /**
     * 获取文章详情
     * GET /api/v1/articles/{id}
     */
    public function show($id)
    {
        $article = Article::with(["game", "category", "tags", "author"])
            ->findOrFail($id);

        // 增加浏览次数
        $article->increment("views");

        return response()->json([
            "success" => true,
            "data" => $article
        ]);
    }

    /**
     * 按游戏获取文章
     * GET /api/v1/games/{slug}/articles
     */
    public function byGame($slug, Request $request)
    {
        $game = Game::where("slug", $slug)->firstOrFail();

        $articles = $game->articles()
            ->with(["category", "tags"])
            ->orderBy("id", "desc")
            ->paginate($request->get("per_page", 10));

        return response()->json([
            "success" => true,
            "data" => $articles->items(),
            "pagination" => [
                "total" => $articles->total(),
                "per_page" => $articles->perPage(),
            ]
        ]);
    }
}

3. 评论系统API

// app/Http/Controllers/CommentController.php

class CommentController extends Controller
{
    /**
     * 获取文章评论列表
     * GET /api/v1/articles/{id}/comments
     */
    public function index($articleId)
    {
        $comments = Comment::where("article_id", $articleId)
            ->whereNull("parent_id")  // 只获取顶级评论
            ->with(["user", "replies.user"])
            ->orderBy("id", "desc")
            ->paginate(20);

        return response()->json([
            "success" => true,
            "data" => $comments->items()
        ]);
    }

    /**
     * 创建评论
     * POST /api/v1/articles/{id}/comments
     */
    public function store(Request $request, $articleId)
    {
        $validated = $request->validate([
            "content" => "required|string|max:5000",
            "parent_id" => "nullable|exists:comments,id"
        ]);

        $comment = Comment::create([
            "article_id" => $articleId,
            "user_id" => auth()->id(),
            "content" => $validated["content"],
            "parent_id" => $validated["parent_id"] ?? null
        ]);

        return response()->json([
            "success" => true,
            "data" => $comment->load("user")
        ], 201);
    }
}

API测试方法

1. 使用curl测试

# 获取游戏列表
curl https://1000y.chencunli.com/api/v1/games

# 获取游戏详情
curl https://1000y.chencunli.com/api/v1/games/qian-mmorpg

# 获取主推游戏
curl https://1000y.chencunli.com/api/v1/games/primary

2. 使用Postman

  • 导入API集合
  • 配置环境变量
  • 设置认证Token(如需要)

3. 验证端点

访问:https://1000y.chencunli.com/test-db-read-write

读写分离配置

database.php配置

// config/database.php

"mysql" => [
    "read" => [
        "host" => [
            "101.201.48.221",  // 主库2
            "8.130.67.202"     // 主库3
        ]
    ],
    "write" => [
        "host" => [
            "localhost"       // 主库1
        ]
    ],
    "sticky" => true,    // 写后读从主库
    "driver" => "mysql",
    "database" => "1000y",
    "username" => "root",
    "password" => "Zwk)k_p7E0Jh",
    // ... 其他配置
]

常见问题FAQ

Q1: 路由不生效?

回过头看,A: 清除路由缓存:

php artisan route:clear
php artisan route:cache

Q2: API返回404?

A: 检查路由顺序和slug参数:

// 查看所有路由
php artisan route:list --path=games

Q3: Eloquent查询返回null?

A: 使用Eloquent模型而非DB查询:

// ✅ 使用Eloquent
$game = Game::where("slug", $slug)->first();

// ❌ 避免使用DB(特别是在EIP环境下)
$game = DB::table("games")->where("slug", $slug)->first();

最后一些建议

说说我自己的经历和看法:本文介绍了Laravel游戏论坛API的完整开发过程:

  • 路由设计:89个API端点,路由排序规则
  • 数据库架构:64张表,关联关系设计
  • 核心功能:游戏列表、文章详情、评论系统
  • 读写分离:HyperDB + MySQL主从复制

相关资源

发表评论