はじめに
前回は環境構築をしました。
今回はサーバーサイドの実装を進めます。
こちらのようなLaravel入門記事が参考になります。
仕様
以下の画像のようなランキング機能を実装します。
ランキングにはユーザー名とスコアを表示します。
ゲーム開始前にユーザー登録はなく、ランキング登録時のみユーザー登録できるようになっています。
ランキング登録時に、新規ユーザーの場合はスコア登録と同時にユーザー登録も行い、既存ユーザーの場合はハイスコアなら更新します。
DB設計
ユーザー情報テーブル
user_profiles
Field | Type | Length | Allow Null | Kety | Default | Extra |
---|---|---|---|---|---|---|
id | INT | 10 | × | PRI | auto_increment | |
uuid | VARCHAR | 255 | × | UNI | None | |
user_name | VARCHAR | 255 | × | None | ||
created_at | TIMESTAMP | ○ | NULL | None | ||
updated_at | TIMESTAMP | ○ | NULL | None |
ユーザーランキングテーブル
user_rankings
Field | Type | Length | Allow Null | Kety | Default | Extra |
---|---|---|---|---|---|---|
id | INT | 10 | × | PRI | auto_increment | |
user_profile_id | BIGINT | 20 | × | None | ||
score | INT | 11 | × | None | ||
created_at | TIMESTAMP | ○ | NULL | None | ||
updated_at | TIMESTAMP | ○ | NULL | None |
API設計
ランキング登録API
add_user_ranking
- Params
name | Type | memo |
---|---|---|
uuid | string | ユーザーを識別するID |
user_name | string | ユーザー名 |
score | int | 取得したスコア |
- Responses(新規ユーザーの場合)
name | Type | memo |
---|---|---|
uuid | string | ユーザーを識別するID |
- Responses(既存ユーザーの場合)
なし
ランキング情報取得API
get_user_ranking
-
Params
なし -
Responses
name | Type | memo |
---|---|---|
responseParams.user_name | string | ユーザー名 |
responseParams.score | int | ハイスコア |
ユーザー情報取得API
get_user_info
- Params
name | Type | memo |
---|---|---|
uuid | string | ユーザーを識別するID |
- Responses
name | Type | memo |
---|---|---|
responseParams.user_name | string | ユーザー名 |
responseParams.high_score | int | ハイスコア |
Laravelコマンド
Laravelではartisanコマンドで様々なファイルの作成を行います。
以降のコマンドの実行はLaravelをインストールしたディレクトリに移動し行ってください。
私はSample_Api/laravel
に入れたので、以下のコマンドでこのディレクトリに移動しておきます。
cd Sample_Api/laravel
マイグレーション
テーブルの作成を行います。
まず以下のコマンドでuser_profilesテーブルのマイグレーションファイルを作成します。
php artisan make:migration create_user_profiles_table
laravel/database/migrations
ディレクトリにマイグレーションファイルが作成されました。
DB設計に合わせて以下のように記述します。
public function up()
{
Schema::create('user_profiles', function (Blueprint $table) {
$table->increments('id');
$table->string('uuid')->unique();
$table->string('user_name');
$table->timestamps();
});
}
次は同じようにuser_rankingsテーブルのマイグレーションファイルを作成します。
以下のコマンドを実行します。
php artisan make:migration create_user_rankings_table
以下のように記述します。
public function up()
{
Schema::create('user_rankings', function (Blueprint $table) {
$table->increments('id');
$table->bigInteger('user_profile_id')->references('id')->on('user_profiles');
$table->integer('score');
$table->timestamps();
});
}
準備が完了しました。
マイグレーションを実行し、テーブルを作成します。
マイグレーション実行のコマンドは以下です。
php artisan migrate
Sequel proなどでDBを確認しましょう。
user_profilesテーブルとuser_rankingsテーブルが生成されていると思います。
モデル
テーブルに対応するようモデルを作成します。
モデルはテーブルと自動でマッピングされるようになっています。
マッピングさせるため、必ずテーブル名の単数形を名付けるようにしてください。
まずはuser_profilesテーブルとマッピングするモデルを作成します。
以下のコマンドでモデルファイルを作成できます。
php artisan make:model UserProfile
次にuser_rankingsテーブルとマッピングするモデルを作成します。
以下のコマンドを実行します。
php artisan make:model UserRanking
モデルが作成できました。
コントローラー
コントローラーにAPIの実装を書いていきます。
ユーザーランキングのAPIを作成するので、UserRankingControllerと名付けることにします。
コントローラーは以下のコマンドで作成できます。
php artisan make:controller UserRankingController
今回は3つのAPIを作成します。
①ランキング登録API
②ランキング情報取得API
③ユーザー情報取得API
①はadd_user_rankingメソッドに、②はget_user_rankingメソッドに、③はget_user_infoメソッドに記述することにします。
詳しい実装は後で書きます。
ルーティング
リクエストを渡すメソッドを設定します。
APIのルーティングはlaravel/routes/app.php
に記述します。
Laravel 8.xでは以下のように書きます。
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UserRankingController;
Route::group(['middleware' => ['api']], function(){
Route::post('/user/ranking/add', [UserRankingController::class, 'add_user_ranking']);
Route::get('/user/ranking/get', [UserRankingController::class, 'get_user_ranking']);
Route::post('/user/info/get', [UserRankingController::class, 'get_user_info']);
});
1つ目が①ランキング登録API、2つ目が②ランキング情報取得API、3つ目が③ユーザー情報取得APIです。
コントローラーのパスをuseするのを忘れないようにしましょう。
また、/user/ranking/add
のように頭に/
を必ずつける必要があります。
POSTMANなどで接続の確認をしましょう。
例えばhttp://localhost:8002/laravel/public/api/user/ranking/add
のリンクで1つ目のAPIに接続できます。
ランキング情報取得APIの実装
ランキングを表示するときに、ランキング情報を取得するAPIを実装します。
リクエストはなし、レスポンスにはユーザー名とスコアを返します。
SQLで書くと以下のようになります。
SELECT up.user_name, ur.score
FROM user_profiles up
INNER JOIN user_rankings ur ON up.id = ur.user_profile_id
ORDER BY ur.score DESC, ur.updated_at ASC
順位付けの優先度は①スコアが高い②登録日が早いにしました。
これをLaravelのクエリビルダ記法で書くと以下のようになります。
$user_scores = DB::table('user_profiles')
->join('user_rankings', 'user_profiles.id', '=', 'user_rankings.user_profile_id')
->select('user_profiles.user_name', 'user_rankings.score')
->orderBy('user_rankings.score', 'desc')
->orderBy('user_rankings.updated_at', 'asc')
->get();
クエリビルダにはDBクラスを使います。
useするのを忘れないようにしましょう。
use Illuminate\Support\Facades\DB;
取得したデータをレスポンスで返します。
return ["responseParams" => $user_scores];
これで完成です。
use Illuminate\Support\Facades\DB;
class UserRankingController extends Controller
{
public function get_user_ranking()
{
$user_scores = DB::table('user_profiles')
->join('user_rankings', 'user_profiles.id', '=', 'user_rankings.user_profile_id')
->select('user_profiles.user_name', 'user_rankings.score')
->orderBy('user_rankings.score', 'desc')
->orderBy('user_rankings.updated_at', 'asc')
->get();
return ["responseParams" => $user_scores];
}
}
ランキング登録APIの実装
次はランキング登録するAPIを実装します。
新規ユーザーの場合は同時にユーザー登録も行い、既存ユーザーの場合はハイスコアのときのみスコアを更新します。
リクエストはuuid、ユーザー名、スコアで、新規ユーザーの場合は作成したuuidをレスポンスで返します。また、リクエストに応じてDBに変更を加えます。
まずはリクエストを受け取ります。
$uuid = (string)$request->input('uuid');
$user_name = (string)$request->input('user_name');
$score = (int)$request->input('score');
キャストはしておいた方が良いです。
キャストしなければintがstringになってしまいます。
次に、新規ユーザーか既存ユーザー判定し、それぞれ処理を書いています。
uuidがないなら新規、あるなら既存ユーザーですので、以下のように場合分けできます。
if($uuid == null) // 新規ユーザー
{
...
}
else // 既存ユーザー
{
...
}
まずは新規ユーザーの処理を書いていきます。
新規ユーザーなので、ユーザー登録をする必要があります。
uuidを生成してあげましょう。
こちらを参考にしました。
Laravelがメソッドを用意してくれているので簡単です。
$new_uuid = (string)Str::uuid();
これだけです。
Strクラスをuseする必要があります。
use Illuminate\Support\Str;
これだけでも良いのですが、万が一生成されるuuidが被ったときのために、被ったら生成し直す処理を書きます。
// uuidの被り判定
$same_flg = true;
// 被りのないものができるまでuuid生成
while($same_flg == true)
{
$new_uuid = (string)Str::uuid();
$same_uuid = UserProfile::where('uuid', '=', $new_uuid)
->first();
if(empty($same_uuid))
{
$same_flg = false;
}
}
これでuuidが生成できました。
生成したuuidをリクエストで得たuser_nameと一緒にユーザー登録します。
UserProfile::create([
'uuid' => $new_uuid,
'user_name' => $user_name
]);
LaravelのEloquentを使っています。
これはすべてのモデルを継承しています。
使用するモデルをuseするようにしましょう。
use App\Models\UserProfile;
作成したuuidを最後にレスポンスで返します。
return $new_uuid;
次に作成したユーザーでランキング登録をします。
ユーザー情報テーブルとユーザーランキングテーブルはユーザー情報テーブルのidで紐づいています。
なので、生成したuuidからユーザー情報テーブルのidを取得し、そのidとリクエストで受け取ったスコアをユーザーランキングテーブルに登録するという手順になります。
まずは生成したuuidからユーザー情報テーブルのidを取得します。
$array_user_profile_id = UserProfile::where('uuid', '=', $new_uuid)
->first(['id']);
$user_profile_id = $array_user_profile_id['id'];
idが配列となって抽出されるので、その後、値だけを抜き出すようにしています。
次にランキング登録をします。
UserRanking::create([
'user_profile_id' => $user_profile_id,
'score' => $score
]);
使用するモデルのuseは忘れないようにしましょう。
use App\Models\UserRanking;
これで新規ユーザーの処理はできました。
次に既存ユーザーの処理です。
リクエストで得たuuidに紐づくユーザーランキングテーブルのuser_profile_idとscoreを取得し、そのscoreをリクエストで得たscoreが超えていれば、そのuser_profile_idのscoreを更新するという手順になります。
まずはリクエストで得たuuidに紐づくユーザーランキングテーブルのuser_profile_idとscoreを取得します。
クエリビルダで書きました。
$array_user_high_score = DB::table('user_rankings')
->join('user_profiles', 'user_rankings.user_profile_id', '=', 'user_profiles.id')
->select('user_rankings.user_profile_id', 'user_rankings.score')
->where('user_profiles.uuid', '=', $uuid)
->first();
$user_profile_id = $array_user_high_score->user_profile_id;
$high_score = $array_user_high_score->score;
これも先ほどと同じように配列で抽出されるため、その後、値だけを抜き出すようにしています。
値を抜き出す記法が先ほどと違いますが、クエリビルダの場合はこう書くようです。
参考記事はこちらです。
次に、取得したscoreがリクエストで得たscoreを超えていたとき、そのユーザーののscoreを更新する処理を書きます。
if($score > $high_score)
{
UserRanking::where('user_profile_id', '=', $user_profile_id)
->update([
'score' => $score
]);
}
以上で既存ユーザーの処理もできました。
これで完成です。
use Illuminate\Http\Request;
use App\Models\UserRanking;
use App\Models\UserProfile;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;
class UserRankingController extends Controller
{
// ランキング登録
public function add_user_ranking(Request $request)
{
// リクエストパラメータを受け取る
$uuid = (int)$request->input('uuid');
$user_name = (string)$request->input('user_name');
$score = (int)$request->input('score');
if($uuid == null) // 新規ユーザー
{
// uuidを生成する
$new_uuid = self::_create_uuid();
// ユーザー登録
UserProfile::create([
'uuid' => $new_uuid,
'user_name' => $user_name
]);
// ランキング登録
$array_user_profile_id = UserProfile::where('uuid', '=', $new_uuid)
->first(['id']);
$user_profile_id = $array_user_profile_id['id'];
UserRanking::create([
'user_profile_id' => $user_profile_id,
'score' => $score
]);
return $new_uuid;
}
else // 既存ユーザー
{
// uuidに紐づくユーザーのスコアを取得する
$array_user_high_score = DB::table('user_rankings')
->join('user_profiles', 'user_rankings.user_profile_id', '=', 'user_profiles.id')
->select('user_rankings.user_profile_id', 'user_rankings.score')
->where('user_profiles.uuid', '=', $uuid)
->first();
$user_profile_id = $array_user_high_score->user_profile_id;
$high_score = $array_user_high_score->score;
// ハイスコアならランキングテーブルを更新する
if($score > $high_score)
{
UserRanking::where('user_profile_id', '=', $user_profile_id)
->update([
'score' => $score
]);
}
}
}
// uuidを生成する
private function _create_uuid()
{
// uuidの被り判定
$same_flg = true;
// 被りのないものができるまでuuid生成
while($same_flg == true)
{
$new_uuid = (string)Str::uuid();
$same_uuid = UserProfile::where('uuid', '=', $new_uuid)
->first();
if(empty($same_uuid))
{
$same_flg = false;
}
}
return $new_uuid;
}
}
uuidの生成は別のメソッドに分けました。
処理ごとにメソッドを分けておくと、使いまわせて便利です。
ユーザー情報取得APIの実装
最後にユーザー情報を取得するAPIを実装します。
リクエストはuuid、で、ユーザー名とハイスコアをレスポンスで返します。
まずはリクエストを受け取ります。
$uuid = (string)$request->input('uuid');
次にこのuuidからユーザー情報テーブルのユーザー名と、ユーザーランキングテーブルのスコアを取得します。先ほどとほぼ同じです。
// uuidに紐づくユーザー情報を取得する
$array_user_data = DB::table('user_rankings')
->join('user_profiles', 'user_rankings.user_profile_id', '=', 'user_profiles.id')
->select('user_profiles.user_name', 'user_rankings.score')
->where('user_profiles.uuid', '=', $uuid)
->first();
$user_name = $array_user_data->user_name;
$high_score = $array_user_data->score;
最後にレスポンスを返します。
return ["responseParams" => [[
'user_name' => $user_name,
'high_score' => $high_score
]]];
これで完成です。
// ユーザー情報取得
public function get_user_info(Request $request)
{
// リクエストパラメータを受け取る
$uuid = (string)$request->input('uuid');
// uuidに紐づくユーザー情報を取得する
$array_user_data = DB::table('user_rankings')
->join('user_profiles', 'user_rankings.user_profile_id', '=', 'user_profiles.id')
->select('user_profiles.user_name', 'user_rankings.score')
->where('user_profiles.uuid', '=', $uuid)
->first();
$user_name = $array_user_data->user_name;
$high_score = $array_user_data->score;
return ["responseParams" => [[
'user_name' => $user_name,
'high_score' => $high_score
]]];
}
さいごに
今回はサーバーサイドをやりました。
次回はクライアントサイドをやります。