Swagger 都改到ApiResponse 輸出
Swagger Room加入TcpSocketClient Swagger room 加入 設備註冊,設備開關 Swagger room 有異動需要寫記錄 20250519
This commit is contained in:
parent
3e9d02451e
commit
ae5ed4aa1f
@ -4,11 +4,20 @@ namespace App\Enums;
|
||||
|
||||
use App\Enums\Traits\HasLabels;
|
||||
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="RoomStatus",
|
||||
* type="string",
|
||||
* enum={"active", "closed", "fire", "error", "maintenance"},
|
||||
* example="error"
|
||||
* )
|
||||
*/
|
||||
enum RoomStatus: string {
|
||||
use HasLabels;
|
||||
|
||||
case Active = 'active';
|
||||
case Closed = 'closed';
|
||||
case Fire ='fire';
|
||||
case Error = 'error';
|
||||
case Maintenance = 'maintenance';
|
||||
|
||||
@ -18,6 +27,7 @@ enum RoomStatus: string {
|
||||
return match($this) {
|
||||
self::Active => __('enums.room.status.Active'),
|
||||
self::Closed => __('enums.room.status.Closed'),
|
||||
self::Fire => __('enums.room.status.Fire'),
|
||||
self::Error => __('enums.room.status.Error'),
|
||||
self::Maintenance => __('enums.room.status.Maintenance'),
|
||||
};
|
||||
|
@ -11,53 +11,53 @@ use OpenApi\Annotations as OA;
|
||||
/**
|
||||
* @OA\Tag(
|
||||
* name="Auth",
|
||||
* description="用戶註冊、登入、登出與個人資料"
|
||||
* description="用戶個人資料"
|
||||
* )
|
||||
*/
|
||||
class AuthController extends Controller
|
||||
{
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/profile",
|
||||
* summary="Get current user profile",
|
||||
* tags={"Auth"},
|
||||
* security={{"Authorization":{}}},
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="User profile",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="data", ref="#/components/schemas/User")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=401,
|
||||
* description="Unauthorized",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="code", type="string", example="UNAUTHORIZED"),
|
||||
* @OA\Property(property="message", type="string", example="Unauthorized"),
|
||||
* @OA\Property(property="data", type="null")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="Accept",
|
||||
* in="header",
|
||||
* required=true,
|
||||
* @OA\Schema(type="string", default="application/json")
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
* @OA\Get(
|
||||
* path="/api/profile",
|
||||
* summary="Get current user profile",
|
||||
* tags={"Auth"},
|
||||
* security={{"Authorization":{}}},
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="User profile",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="data", ref="#/components/schemas/User")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=401,
|
||||
* description="Unauthorized",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="code", type="string", example="UNAUTHORIZED"),
|
||||
* @OA\Property(property="message", type="string", example="Unauthorized"),
|
||||
* @OA\Property(property="data", type="null")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="Accept",
|
||||
* in="header",
|
||||
* required=true,
|
||||
* @OA\Schema(type="string", default="application/json")
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function profile(Request $request)
|
||||
{
|
||||
return response()->json($request->user());
|
||||
return \App\Http\Responses\ApiResponse::success($request->user());
|
||||
}
|
||||
}
|
||||
|
@ -29,18 +29,4 @@ use Illuminate\Routing\Controller as BaseController;
|
||||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
||||
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="UnauthorizedResponse",
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="code", type="string", example="UNAUTHORIZED"),
|
||||
* @OA\Property(property="message", type="string", example="Unauthorized"),
|
||||
* @OA\Property(property="data", type="null", nullable=true)
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
}
|
157
app/Http/Controllers/RoomControlController.php
Normal file
157
app/Http/Controllers/RoomControlController.php
Normal file
@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\SendRoomSwitchCommandRequest;
|
||||
use App\Http\Requests\ReceiveRoomRegisterRequest;
|
||||
use App\Services\TcpSocketClient;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
use App\Models\Room;
|
||||
use App\Enums\RoomStatus;
|
||||
|
||||
/**
|
||||
* @OA\Tag(
|
||||
* name="Auth",
|
||||
* description="包廂控制"
|
||||
* )
|
||||
*/
|
||||
class RoomControlController extends Controller
|
||||
{
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/room/receiveRegister",
|
||||
* summary="包廂註冊控制指令",
|
||||
* description="依據傳入的 branch_id 與 room_name,知道過來的設備來之於那個IP設備。",
|
||||
* operationId="registerRoomCommand",
|
||||
* tags={"Room Control"},
|
||||
* security={{"Authorization":{}}},
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/ReceiveRoomRegisterRequest")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="成功傳送指令並回傳 TCP 回應",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="data", ref="#/components/schemas/Room")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=401,
|
||||
* description="Unauthorized",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="code", type="string", example="UNAUTHORIZED"),
|
||||
* @OA\Property(property="message", type="string", example="Unauthorized"),
|
||||
* @OA\Property(property="data", type="null")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="Accept",
|
||||
* in="header",
|
||||
* required=true,
|
||||
* @OA\Schema(type="string", default="application/json")
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function receiveRegister(ReceiveRoomRegisterRequest $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validated();
|
||||
|
||||
$room= Room::where([['branch_id',$validated['branch_id']],['name',$validated['room_name']]])->first();
|
||||
if (!$room) {
|
||||
return \App\Http\Responses\ApiResponse::error("'找不到對應包廂'");
|
||||
}
|
||||
$room->internal_ip = $validated['room_ip'];
|
||||
$room->port =1000;
|
||||
$room->touch();
|
||||
$room->status=RoomStatus::Closed;
|
||||
$room->save();
|
||||
return \App\Http\Responses\ApiResponse::success($room);
|
||||
|
||||
}
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/room/sendSwitch",
|
||||
* summary="送出包廂控制指令",
|
||||
* description="依據傳入的 room_id 與 command,透過 TCP 傳送對應指令給包廂電腦。",
|
||||
* operationId="sendRoomSwitchCommand",
|
||||
* tags={"Room Control"},
|
||||
* security={{"Authorization":{}}},
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/SendRoomSwitchCommandRequest")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="成功傳送指令並回傳 TCP 回應",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="data", ref="#/components/schemas/Room")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=401,
|
||||
* description="Unauthorized",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="code", type="string", example="UNAUTHORIZED"),
|
||||
* @OA\Property(property="message", type="string", example="Unauthorized"),
|
||||
* @OA\Property(property="data", type="null")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="Accept",
|
||||
* in="header",
|
||||
* required=true,
|
||||
* @OA\Schema(type="string", default="application/json")
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function sendSwitch(SendRoomSwitchCommandRequest $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validated();
|
||||
$room= Room::where([['branch_id',$validated['branch_id']],['name',$validated['room_name']]])->first();
|
||||
|
||||
$ip = $room->internal_ip;
|
||||
$port = $room->port;
|
||||
$data=(substr($room->name, -3) ?? $room->name).",".($validated['command']=='active' ? 'O':'X');
|
||||
|
||||
//dd($data);
|
||||
$client = new TcpSocketClient($ip, $port);
|
||||
try {
|
||||
$response = $client->send($data);
|
||||
|
||||
$room->status=$validated['command'];
|
||||
$room->started_at=$validated['started_at'];
|
||||
$room->ended_at=$validated['ended_at'];
|
||||
$room->save();
|
||||
|
||||
return \App\Http\Responses\ApiResponse::success($room);
|
||||
} catch (\Throwable $e) {
|
||||
$room->status=RoomStatus::Error;
|
||||
$room->started_at=null;
|
||||
$room->ended_at=null;
|
||||
$room->save();
|
||||
return \App\Http\Responses\ApiResponse::error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
31
app/Http/Requests/ApiRequest.php
Normal file
31
app/Http/Requests/ApiRequest.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use App\Http\Requests\Traits\FailedValidationJsonResponse;
|
||||
|
||||
class ApiRequest extends FormRequest
|
||||
{
|
||||
|
||||
use FailedValidationJsonResponse;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array{
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
31
app/Http/Requests/ReceiveRoomRegisterRequest.php
Normal file
31
app/Http/Requests/ReceiveRoomRegisterRequest.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="ReceiveRoomRegisterRequest",
|
||||
* required={"branch_id", "room_id", "ip"},
|
||||
* @OA\Property(property="branch_id", type="integer", example="5"),
|
||||
* @OA\Property(property="room_name", type="string", example="pc102"),
|
||||
* @OA\Property(property="room_ip", type="string", example="192.168.x.x"),
|
||||
* )
|
||||
*/
|
||||
class ReceiveRoomRegisterRequest extends ApiRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'branch_id' => 'required|integer|exists:branches,id',
|
||||
'room_name' => 'required|string',
|
||||
'room_ip' => 'nullable|ip',
|
||||
];
|
||||
}
|
||||
}
|
35
app/Http/Requests/SendRoomSwitchCommandRequest.php
Normal file
35
app/Http/Requests/SendRoomSwitchCommandRequest.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="SendRoomSwitchCommandRequest",
|
||||
* required={"room_id", "command"},
|
||||
* @OA\Property(property="branch_id", type="integer", example="5"),
|
||||
* @OA\Property(property="room_name", type="string", example="pc102"),
|
||||
* @OA\Property(property="command", type="string", enum={"active", "closed", "fire", "maintenance"}, example="active"),
|
||||
* @OA\Property(property="started_at", type="string", nullable=true, example="2025-05-19 09:31:00"),
|
||||
* @OA\Property(property="ended_at", type="string", nullable=true, example="2025-05-19 09:31:00")
|
||||
* )
|
||||
*/
|
||||
class SendRoomSwitchCommandRequest extends ApiRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'branch_id' => 'required|integer',
|
||||
'room_name' => 'required|string',
|
||||
'command' => 'required|string',
|
||||
'started_at' => 'nullable|date_format:Y-m-d H:i:s',
|
||||
'ended_at' => 'nullable|date_format:Y-m-d H:i:s',
|
||||
];
|
||||
}
|
||||
}
|
20
app/Http/Requests/Traits/FailedValidationJsonResponse.php
Normal file
20
app/Http/Requests/Traits/FailedValidationJsonResponse.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Traits;
|
||||
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
trait FailedValidationJsonResponse
|
||||
{
|
||||
protected function failedValidation(Validator $validator)
|
||||
{
|
||||
throw new HttpResponseException(response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => $validator->errors(),
|
||||
'code' => 'ERROR',
|
||||
'token' => ''
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY));
|
||||
}
|
||||
}
|
@ -6,6 +6,19 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Traits\LogsModelActivity;
|
||||
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="Room",
|
||||
* type="object",
|
||||
* @OA\Property(property="id", type="integer", example=16),
|
||||
* @OA\Property(property="name", type="string", example="pc102"),
|
||||
* @OA\Property(property="internal_ip", type="string", example="192.168.11.7"),
|
||||
* @OA\Property(property="port", type="int", example="9000"),
|
||||
* @OA\Property(property="status", ref="#/components/schemas/RoomStatus"),
|
||||
* @OA\Property(property="started_at", type="string", format="date-time", example="2025-05-11T16:00:00.000000Z"),
|
||||
* @OA\Property(property="ended_at", type="string", format="date-time", example=null),
|
||||
* )
|
||||
*/
|
||||
class Room extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\ArtistFactory> */
|
||||
@ -20,6 +33,11 @@ class Room extends Model
|
||||
'ended_at',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'internal_ip',
|
||||
'port',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'name' => 'string',
|
||||
'internal_ip' =>'string',
|
||||
|
@ -10,7 +10,7 @@ class RoomStatusLog extends Model
|
||||
/** @use HasFactory<\Database\Factories\ArtistFactory> */
|
||||
use HasFactory;
|
||||
|
||||
public $timestamps = false;
|
||||
public $timestamps = true;
|
||||
|
||||
protected $fillable =
|
||||
[
|
||||
@ -18,11 +18,10 @@ class RoomStatusLog extends Model
|
||||
'user_id',
|
||||
'status',
|
||||
'message',
|
||||
'logged_at'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'logged_at' => 'datetime',
|
||||
'status' => \App\Enums\RoomStatus::class,
|
||||
];
|
||||
|
||||
public function room() {
|
||||
|
@ -2,7 +2,10 @@
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
use App\Models\Room;
|
||||
use App\Models\RoomStatusLog;
|
||||
|
||||
class RoomObserver
|
||||
{
|
||||
@ -25,8 +28,7 @@ class RoomObserver
|
||||
'room_id' => $room->id,
|
||||
'user_id' => Auth::id(), // 若是 console 或系統自動操作可能為 null
|
||||
'status' => $room->status,
|
||||
'message' => '狀態自動變更紀錄',
|
||||
'logged_at' => now(),
|
||||
'message' => 'started_at:'.$room->started_at.',ended_at:'.$room->ended_at,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,9 @@ namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
use App\Models\Room;
|
||||
use App\Observers\RoomObserver;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
@ -19,6 +22,6 @@ class AppServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
Room::observe(RoomObserver::class);
|
||||
}
|
||||
}
|
||||
|
43
app/Services/TcpSocketClient.php
Normal file
43
app/Services/TcpSocketClient.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
namespace App\Services;
|
||||
|
||||
class TcpSocketClient
|
||||
{
|
||||
protected $ip;
|
||||
protected $port;
|
||||
protected $timeout;
|
||||
|
||||
public function __construct(string $ip, int $port, int $timeout = 5)
|
||||
{
|
||||
$this->ip = $ip;
|
||||
$this->port = $port;
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
|
||||
public function send(string $data): string
|
||||
{
|
||||
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||
if ($socket === false) {
|
||||
throw new \Exception("Socket create failed: " . socket_strerror(socket_last_error()));
|
||||
}
|
||||
|
||||
$result = socket_connect($socket, $this->ip, $this->port);
|
||||
if ($result === false) {
|
||||
throw new \Exception("Socket connect failed: " . socket_strerror(socket_last_error($socket)));
|
||||
}
|
||||
|
||||
socket_write($socket, $data, strlen($data));
|
||||
|
||||
$response = '';
|
||||
while ($out = socket_read($socket, 2048)) {
|
||||
$response .= $out;
|
||||
// 根據協議判斷是否結束接收,可以自行調整
|
||||
if (strpos($response, "\n") !== false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
socket_close($socket);
|
||||
return $response;
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ return new class extends Migration
|
||||
$table->string('name')->comment('包廂名稱');
|
||||
$table->string('internal_ip')->nullable()->comment('內部 IP');
|
||||
$table->unsignedSmallInteger('port')->nullable()->comment('通訊 Port');
|
||||
$table->enum('status', ['active', 'closed', 'error', 'maintenance'])->default('error')->comment('狀態'); // :啟用中 / 已結束
|
||||
$table->enum('status', ['active', 'closed','fire', 'error', 'maintenance'])->default('error')->comment('狀態'); // :啟用中 / 已結束
|
||||
$table->dateTime('started_at')->nullable()->comment('開始時間'); //
|
||||
$table->dateTime('ended_at')->nullable()->comment('結束時間'); //
|
||||
$table->timestamps();
|
||||
|
@ -15,7 +15,7 @@ return new class extends Migration
|
||||
$table->id();
|
||||
$table->foreignId('room_id')->constrained()->onDelete('cascade');
|
||||
$table->foreignId('user_id')->nullable()->constrained()->onDelete('set null'); // 操作者,可為 null(系統)
|
||||
$table->enum('status', ['active', 'closed', 'error', 'maintenance']);
|
||||
$table->enum('status', ['active', 'closed','fire', 'error', 'maintenance']);
|
||||
$table->text('message')->nullable(); // 可填異常原因或操作說明
|
||||
$table->timestamps();
|
||||
});
|
||||
|
@ -26,6 +26,7 @@ return [
|
||||
|
||||
'room.status.Active' => '已占用',
|
||||
'room.status.Closed' => '可用',
|
||||
'room.status.Fire' => '火災',
|
||||
'room.status.Error' => '異常',
|
||||
'room.status.Maintenance' => '維修',
|
||||
];
|
@ -3,10 +3,13 @@
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\ArtistController;
|
||||
use App\Http\Controllers\AuthController;
|
||||
use App\Http\Controllers\RoomControlController;
|
||||
|
||||
Route::get('/artists/search', [App\Http\Controllers\ArtistController::class, 'search'])->name('api.artists.search');
|
||||
|
||||
|
||||
Route::middleware('auth:sanctum')->group(function () {
|
||||
Route::get('/profile', [AuthController::class, 'profile']);
|
||||
Route::post('/room/receiveRegister', [RoomControlController::class, 'receiveRegister']);
|
||||
Route::post('/room/sendSwitch', [RoomControlController::class, 'sendSwitch']);
|
||||
});
|
@ -127,10 +127,201 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/room/receiveRegister": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Room Control"
|
||||
],
|
||||
"summary": "包廂註冊控制指令",
|
||||
"description": "依據傳入的 branch_id 與 room_name,知道過來的設備來之於那個IP設備。",
|
||||
"operationId": "registerRoomCommand",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Accept",
|
||||
"in": "header",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"default": "application/json"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ReceiveRoomRegisterRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功傳送指令並回傳 TCP 回應",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ApiResponse"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/Room"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ApiResponse"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string",
|
||||
"example": "UNAUTHORIZED"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Unauthorized"
|
||||
},
|
||||
"data": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Authorization": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/room/sendSwitch": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Room Control"
|
||||
],
|
||||
"summary": "送出包廂控制指令",
|
||||
"description": "依據傳入的 room_id 與 command,透過 TCP 傳送對應指令給包廂電腦。",
|
||||
"operationId": "sendRoomSwitchCommand",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Accept",
|
||||
"in": "header",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"default": "application/json"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SendRoomSwitchCommandRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功傳送指令並回傳 TCP 回應",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ApiResponse"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/Room"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ApiResponse"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string",
|
||||
"example": "UNAUTHORIZED"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Unauthorized"
|
||||
},
|
||||
"data": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Authorization": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"RoomStatus": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"active",
|
||||
"closed",
|
||||
"fire",
|
||||
"error",
|
||||
"maintenance"
|
||||
],
|
||||
"example": "error"
|
||||
},
|
||||
"UserGender": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@ -151,6 +342,64 @@
|
||||
],
|
||||
"example": "0"
|
||||
},
|
||||
"ReceiveRoomRegisterRequest": {
|
||||
"required": [
|
||||
"branch_id",
|
||||
"room_id",
|
||||
"ip"
|
||||
],
|
||||
"properties": {
|
||||
"branch_id": {
|
||||
"type": "integer",
|
||||
"example": "5"
|
||||
},
|
||||
"room_name": {
|
||||
"type": "string",
|
||||
"example": "pc102"
|
||||
},
|
||||
"ip": {
|
||||
"type": "string",
|
||||
"example": "192.168.x.x"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"SendRoomSwitchCommandRequest": {
|
||||
"required": [
|
||||
"room_id",
|
||||
"command"
|
||||
],
|
||||
"properties": {
|
||||
"branch_id": {
|
||||
"type": "integer",
|
||||
"example": "5"
|
||||
},
|
||||
"room_name": {
|
||||
"type": "string",
|
||||
"example": "pc102"
|
||||
},
|
||||
"command": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"active",
|
||||
"closed",
|
||||
"maintenance"
|
||||
],
|
||||
"example": "active"
|
||||
},
|
||||
"started_at": {
|
||||
"type": "string",
|
||||
"example": "2025-05-19 09:31:00",
|
||||
"nullable": true
|
||||
},
|
||||
"ended_at": {
|
||||
"type": "string",
|
||||
"example": "2025-05-19 09:31:00",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ApiResponse": {
|
||||
"properties": {
|
||||
"code": {
|
||||
@ -168,6 +417,40 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Room": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 16
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "pc102"
|
||||
},
|
||||
"internal_ip": {
|
||||
"type": "string",
|
||||
"example": "192.168.11.7"
|
||||
},
|
||||
"port": {
|
||||
"type": "integer",
|
||||
"example": "9000"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/RoomStatus"
|
||||
},
|
||||
"started_at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"example": "2025-05-11T16:00:00.000000Z"
|
||||
},
|
||||
"ended_at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"example": null
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"User": {
|
||||
"properties": {
|
||||
"id": {
|
||||
@ -216,7 +499,11 @@
|
||||
},
|
||||
{
|
||||
"name": "Auth",
|
||||
"description": "用戶註冊、登入、登出與個人資料"
|
||||
"description": "包廂控制"
|
||||
},
|
||||
{
|
||||
"name": "Room Control",
|
||||
"description": "Room Control"
|
||||
}
|
||||
]
|
||||
}
|
43
tests/Feature/TcpSocketClientTest.php
Normal file
43
tests/Feature/TcpSocketClientTest.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
// tests/Feature/TcpSocketClientTest.php
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Tests\TestCase;
|
||||
use App\Services\TcpSocketClient;
|
||||
|
||||
class TcpSocketClientTest extends TestCase
|
||||
{
|
||||
public function testCanSendAndReceiveTcpMessage()
|
||||
{
|
||||
$host = '127.0.0.1';
|
||||
$port = 12345;
|
||||
|
||||
// 建立一個假的 TCP server (background)
|
||||
$pid = pcntl_fork();
|
||||
if ($pid == -1) {
|
||||
$this->fail("Unable to fork process for mock TCP server.");
|
||||
} elseif ($pid == 0) {
|
||||
// 子處理序:啟動簡單 TCP server
|
||||
$socket = stream_socket_server("tcp://$host:$port", $errno, $errstr);
|
||||
if (!$socket) {
|
||||
exit(1);
|
||||
}
|
||||
$conn = stream_socket_accept($socket);
|
||||
$data = fread($conn, 1024); // 收資料
|
||||
fwrite($conn, "Echo: " . $data); // 回傳資料
|
||||
fclose($conn);
|
||||
fclose($socket);
|
||||
exit(0);
|
||||
} else {
|
||||
// 父處理序:給 server 一點時間啟動
|
||||
usleep(100_000); // 0.1 秒
|
||||
|
||||
$client = new TcpSocketClient($host, $port);
|
||||
$response = $client->send("hello test\n");
|
||||
|
||||
$this->assertEquals("Echo: hello test\n", $response);
|
||||
|
||||
pcntl_wait($status); // 等子程序結束
|
||||
}
|
||||
}
|
||||
}
|
5
開發手冊.ini
5
開發手冊.ini
@ -126,4 +126,7 @@ php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
|
||||
php artisan migrate
|
||||
|
||||
composer require "darkaonline/l5-swagger"
|
||||
php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"
|
||||
php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"
|
||||
php -d memory_limit=512M artisan l5-swagger:generate
|
||||
|
||||
4|qdXjrZTvWzOE5kNhbmOXFO6d7DrFzGh1RqhVATLZf8475a6b
|
Loading…
x
Reference in New Issue
Block a user