refactor: 使用 ApiMessage 统一错误响应格式

- 在多个控制器中引入 ApiMessage,替换原有的 ApiResponse 错误处理逻辑,确保错误信息的一致性与可读性。
- 更新错误返回信息,使用更具语义的键值,提升 API 的可维护性与用户体验。
- 适配相关控制器的请求参数,确保在处理错误时能够正确返回相应的错误信息。
This commit is contained in:
2026-06-01 14:23:48 +08:00
parent e547e2b4a6
commit e6cf94af46
59 changed files with 518 additions and 230 deletions

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Config;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Models\OddsVersion; use App\Models\OddsVersion;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
@@ -27,7 +28,7 @@ final class OddsItemsReplaceController extends Controller
if ($version->status !== ConfigVersionStatus::Draft->value) { if ($version->status !== ConfigVersionStatus::Draft->value) {
return ApiResponse::error( return ApiResponse::error(
'version is not draft', ApiMessage::get($request, 'config_version_not_draft'),
ErrorCode::ConfigVersionNotDraft->value, ErrorCode::ConfigVersionNotDraft->value,
null, null,
400, 400,

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Config;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Models\OddsVersion; use App\Models\OddsVersion;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@@ -25,7 +26,7 @@ final class OddsVersionDestroyController extends Controller
if ($version->status === ConfigVersionStatus::Active->value) { if ($version->status === ConfigVersionStatus::Active->value) {
return ApiResponse::error( return ApiResponse::error(
'cannot delete active config version', ApiMessage::get($request, 'config_version_cannot_delete_active'),
ErrorCode::ConfigVersionCannotDeleteActive->value, ErrorCode::ConfigVersionCannotDeleteActive->value,
null, null,
400, 400,

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Config;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Models\OddsVersion; use App\Models\OddsVersion;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@@ -26,7 +27,7 @@ final class OddsVersionPublishController extends Controller
if ($version->status !== ConfigVersionStatus::Draft->value) { if ($version->status !== ConfigVersionStatus::Draft->value) {
return ApiResponse::error( return ApiResponse::error(
'version is not draft', ApiMessage::get($request, 'config_version_not_draft'),
ErrorCode::ConfigVersionNotDraft->value, ErrorCode::ConfigVersionNotDraft->value,
null, null,
400, 400,

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Config;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
@@ -27,7 +28,7 @@ final class PlayConfigItemsReplaceController extends Controller
if ($version->status !== ConfigVersionStatus::Draft->value) { if ($version->status !== ConfigVersionStatus::Draft->value) {
return ApiResponse::error( return ApiResponse::error(
'version is not draft', ApiMessage::get($request, 'config_version_not_draft'),
ErrorCode::ConfigVersionNotDraft->value, ErrorCode::ConfigVersionNotDraft->value,
null, null,
400, 400,

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Config;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\PlayConfigVersion; use App\Models\PlayConfigVersion;
@@ -25,7 +26,7 @@ final class PlayConfigVersionDestroyController extends Controller
if ($version->status === ConfigVersionStatus::Active->value) { if ($version->status === ConfigVersionStatus::Active->value) {
return ApiResponse::error( return ApiResponse::error(
'cannot delete active config version', ApiMessage::get($request, 'config_version_cannot_delete_active'),
ErrorCode::ConfigVersionCannotDeleteActive->value, ErrorCode::ConfigVersionCannotDeleteActive->value,
null, null,
400, 400,

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Config;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\PlayConfigVersion; use App\Models\PlayConfigVersion;
@@ -25,12 +26,7 @@ final class PlayConfigVersionPublishController extends Controller
$version = PlayConfigVersion::query()->whereKey($id)->firstOrFail(); $version = PlayConfigVersion::query()->whereKey($id)->firstOrFail();
if ($version->status !== ConfigVersionStatus::Draft->value) { if ($version->status !== ConfigVersionStatus::Draft->value) {
return ApiResponse::error( return ApiMessage::errorResponse($request, 'config_version_not_draft', ErrorCode::ConfigVersionNotDraft->value, null, 400);
'version is not draft',
ErrorCode::ConfigVersionNotDraft->value,
null,
400,
);
} }
$service->publish($version, $admin, $request); $service->publish($version, $admin, $request);

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Config;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\RiskCapVersion; use App\Models\RiskCapVersion;
@@ -26,7 +27,7 @@ final class RiskCapItemsReplaceController extends Controller
if ($version->status !== ConfigVersionStatus::Draft->value) { if ($version->status !== ConfigVersionStatus::Draft->value) {
return ApiResponse::error( return ApiResponse::error(
'version is not draft', ApiMessage::get($request, 'config_version_not_draft'),
ErrorCode::ConfigVersionNotDraft->value, ErrorCode::ConfigVersionNotDraft->value,
null, null,
400, 400,

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Config;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Models\RiskCapVersion; use App\Models\RiskCapVersion;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@@ -25,7 +26,7 @@ final class RiskCapVersionDestroyController extends Controller
if ($version->status === ConfigVersionStatus::Active->value) { if ($version->status === ConfigVersionStatus::Active->value) {
return ApiResponse::error( return ApiResponse::error(
'cannot delete active config version', ApiMessage::get($request, 'config_version_cannot_delete_active'),
ErrorCode::ConfigVersionCannotDeleteActive->value, ErrorCode::ConfigVersionCannotDeleteActive->value,
null, null,
400, 400,

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Config;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\RiskCapVersion; use App\Models\RiskCapVersion;
@@ -26,7 +27,7 @@ final class RiskCapVersionPublishController extends Controller
if ($version->status !== ConfigVersionStatus::Draft->value) { if ($version->status !== ConfigVersionStatus::Draft->value) {
return ApiResponse::error( return ApiResponse::error(
'version is not draft', ApiMessage::get($request, 'config_version_not_draft'),
ErrorCode::ConfigVersionNotDraft->value, ErrorCode::ConfigVersionNotDraft->value,
null, null,
400, 400,

View File

@@ -4,7 +4,9 @@ namespace App\Http\Controllers\Api\V1\Admin\Currency;
use App\Models\Currency; use App\Models\Currency;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use App\Services\LotterySettings; use App\Services\LotterySettings;
@@ -12,26 +14,23 @@ use App\Http\Controllers\Controller;
final class AdminCurrencyDestroyController extends Controller final class AdminCurrencyDestroyController extends Controller
{ {
public function __invoke(Currency $currency): JsonResponse public function __invoke(Request $request, Currency $currency): JsonResponse
{ {
$code = strtoupper((string) $currency->code); $code = strtoupper((string) $currency->code);
if ($code === LotterySettings::defaultCurrency()) { if ($code === LotterySettings::defaultCurrency()) {
return ApiResponse::error( return ApiMessage::errorResponse($request, 'admin.currency_default_cannot_delete', ErrorCode::ValidationFailed->value, null, 422);
'默认币种不可删除',
ErrorCode::ValidationFailed->value,
null,
422,
);
} }
$references = $this->referenceSummary($code); $references = $this->referenceSummary($code);
if ($references !== []) { if ($references !== []) {
return ApiResponse::error( return ApiMessage::errorResponse(
'该币种已被业务数据引用,暂不可删除:'.implode('、', $references), $request,
'admin.currency_referenced_cannot_delete',
ErrorCode::ValidationFailed->value, ErrorCode::ValidationFailed->value,
['references' => $references], ['references' => $references],
422, 422,
['refs' => implode('、', $references)],
); );
} }

View File

@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Api\V1\Admin\Draw; namespace App\Http\Controllers\Api\V1\Admin\Draw;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -20,7 +21,7 @@ final class AdminDrawBatchDestroyController extends Controller
$drawIds = $request->input('draw_ids', []); $drawIds = $request->input('draw_ids', []);
if (!is_array($drawIds) || empty($drawIds)) { if (!is_array($drawIds) || empty($drawIds)) {
return ApiResponse::error(trans('api.invalid_params'), ErrorCode::ClientHttpError->value, [], 400); return ApiMessage::errorResponse($request, 'invalid_params', ErrorCode::ClientHttpError->value, [], 400);
} }
$results = [ $results = [
@@ -36,17 +37,14 @@ final class AdminDrawBatchDestroyController extends Controller
} catch (\RuntimeException $e) { } catch (\RuntimeException $e) {
$results['failed'][] = [ $results['failed'][] = [
'id' => $drawId, 'id' => $drawId,
'reason' => match ($e->getMessage()) { 'reason' => ApiMessage::reason($request, $e->getMessage()),
'draw_not_deletable' => trans('api.draw_not_deletable'), 'reason_key' => $e->getMessage(),
'draw_has_bets' => trans('api.draw_has_bets'),
'draw_result_exists' => trans('api.draw_result_exists'),
default => trans('api.client_error'),
},
]; ];
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) { } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
$results['failed'][] = [ $results['failed'][] = [
'id' => $drawId, 'id' => $drawId,
'reason' => trans('api.draw_not_found'), 'reason' => ApiMessage::get($request, 'draw_not_found'),
'reason_key' => 'draw_not_found',
]; ];
} }
} }

View File

@@ -5,6 +5,8 @@ namespace App\Http\Controllers\Api\V1\Admin\Draw;
use App\Models\Draw; use App\Models\Draw;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use App\Support\ApiMessage;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Services\Draw\DrawDestroyService; use App\Services\Draw\DrawDestroyService;
@@ -15,19 +17,12 @@ final class AdminDrawDestroyController extends Controller
private readonly DrawDestroyService $service, private readonly DrawDestroyService $service,
) {} ) {}
public function __invoke(Draw $draw): JsonResponse public function __invoke(Request $request, Draw $draw): JsonResponse
{ {
try { try {
$this->service->destroy($draw); $this->service->destroy($draw);
} catch (\RuntimeException $e) { } catch (\RuntimeException $e) {
$message = match ($e->getMessage()) { return ApiMessage::runtimeErrorResponse($request, $e);
'draw_not_deletable' => trans('api.draw_not_deletable'),
'draw_has_bets' => trans('api.draw_has_bets'),
'draw_result_exists' => trans('api.draw_result_exists'),
default => trans('api.client_error'),
};
return ApiResponse::error($message, ErrorCode::ClientHttpError->value, ['reason' => $e->getMessage()], 409);
} }
return ApiResponse::success(['deleted' => true]); return ApiResponse::success(['deleted' => true]);

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Draw;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use App\Support\ApiMessage;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Services\Draw\DrawManualCreateService; use App\Services\Draw\DrawManualCreateService;
@@ -20,13 +21,7 @@ final class AdminDrawStoreController extends Controller
try { try {
$draw = $this->service->create($request->validated()); $draw = $this->service->create($request->validated());
} catch (\RuntimeException $e) { } catch (\RuntimeException $e) {
$message = match ($e->getMessage()) { return ApiMessage::runtimeErrorResponse($request, $e);
'draw_no_exists' => trans('api.draw_no_exists'),
'draw_timeline_invalid' => trans('api.draw_timeline_invalid'),
default => trans('api.client_error'),
};
return ApiResponse::error($message, ErrorCode::ClientHttpError->value, ['reason' => $e->getMessage()], 409);
} }
return ApiResponse::success([ return ApiResponse::success([

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Draw;
use App\Models\Draw; use App\Models\Draw;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use App\Support\ApiMessage;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\DrawStoreRequest; use App\Http\Requests\Admin\DrawStoreRequest;
@@ -21,16 +22,7 @@ final class AdminDrawUpdateController extends Controller
try { try {
$updated = $this->service->update($draw, $request->validated()); $updated = $this->service->update($draw, $request->validated());
} catch (\RuntimeException $e) { } catch (\RuntimeException $e) {
$message = match ($e->getMessage()) { return ApiMessage::runtimeErrorResponse($request, $e);
'draw_no_exists' => trans('api.draw_no_exists'),
'draw_timeline_invalid' => trans('api.draw_timeline_invalid'),
'draw_not_editable' => trans('api.draw_not_editable'),
'draw_has_bets' => trans('api.draw_has_bets'),
'draw_result_exists' => trans('api.draw_result_exists'),
default => trans('api.client_error'),
};
return ApiResponse::error($message, ErrorCode::ClientHttpError->value, ['reason' => $e->getMessage()], 409);
} }
return ApiResponse::success([ return ApiResponse::success([

View File

@@ -4,7 +4,9 @@ namespace App\Http\Controllers\Api\V1\Admin\Draw;
use App\Models\Draw; use App\Models\Draw;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Services\Draw\DrawAdminActionService; use App\Services\Draw\DrawAdminActionService;
@@ -15,12 +17,12 @@ final class DrawCancelController extends Controller
private readonly DrawAdminActionService $service, private readonly DrawAdminActionService $service,
) {} ) {}
public function __invoke(Draw $draw): JsonResponse public function __invoke(Request $request, Draw $draw): JsonResponse
{ {
try { try {
$cancelled = $this->service->cancelBeforeResult($draw); $cancelled = $this->service->cancelBeforeResult($draw);
} catch (\RuntimeException) { } catch (\RuntimeException $e) {
return ApiResponse::error(trans('api.client_error'), ErrorCode::ClientHttpError->value, null, 409); return ApiMessage::runtimeErrorResponse($request, $e);
} }
return ApiResponse::success([ return ApiResponse::success([

View File

@@ -4,7 +4,9 @@ namespace App\Http\Controllers\Api\V1\Admin\Draw;
use App\Models\Draw; use App\Models\Draw;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Services\Draw\DrawAdminActionService; use App\Services\Draw\DrawAdminActionService;
@@ -15,12 +17,12 @@ final class DrawManualCloseController extends Controller
private readonly DrawAdminActionService $service, private readonly DrawAdminActionService $service,
) {} ) {}
public function __invoke(Draw $draw): JsonResponse public function __invoke(Request $request, Draw $draw): JsonResponse
{ {
try { try {
$closed = $this->service->manualClose($draw); $closed = $this->service->manualClose($draw);
} catch (\RuntimeException) { } catch (\RuntimeException $e) {
return ApiResponse::error(trans('api.client_error'), ErrorCode::ClientHttpError->value, null, 409); return ApiMessage::runtimeErrorResponse($request, $e);
} }
return ApiResponse::success([ return ApiResponse::success([

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Draw;
use App\Models\Draw; use App\Models\Draw;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
@@ -31,13 +32,8 @@ final class DrawManualResultBatchStoreController extends Controller
try { try {
$batch = $this->service->createPendingBatch($draw, $admin, $request->validated('items')); $batch = $this->service->createPendingBatch($draw, $admin, $request->validated('items'));
} catch (\RuntimeException) { } catch (\RuntimeException $e) {
return ApiResponse::error( return ApiMessage::runtimeErrorResponse($request, $e);
trans('api.client_error', [], $request->lotteryLocale()),
ErrorCode::ClientHttpError->value,
null,
409,
);
} }
$draw->refresh(); $draw->refresh();

View File

@@ -6,6 +6,7 @@ use App\Models\Draw;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use App\Support\ApiMessage;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Services\Draw\DrawReopenService; use App\Services\Draw\DrawReopenService;
@@ -39,13 +40,8 @@ final class DrawReopenController extends Controller
try { try {
$reopened = $this->service->reopenCooldownDraw($draw, $admin, $request->validated('reason') ?? null); $reopened = $this->service->reopenCooldownDraw($draw, $admin, $request->validated('reason') ?? null);
} catch (\RuntimeException) { } catch (\RuntimeException $e) {
return ApiResponse::error( return ApiMessage::runtimeErrorResponse($request, $e);
trans('api.client_error', [], $request->lotteryLocale()),
ErrorCode::ClientHttpError->value,
null,
409,
);
} }
return ApiResponse::success([ return ApiResponse::success([

View File

@@ -6,6 +6,7 @@ use App\Models\Draw;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use App\Support\ApiMessage;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\DrawResultBatch; use App\Models\DrawResultBatch;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@@ -44,13 +45,8 @@ final class DrawResultBatchPublishController extends Controller
try { try {
$this->publishService->publishManualBatch($batch, $admin); $this->publishService->publishManualBatch($batch, $admin);
} catch (\RuntimeException) { } catch (\RuntimeException $e) {
return ApiResponse::error( return ApiMessage::runtimeErrorResponse($request, $e);
trans('api.client_error', [], $request->lotteryLocale()),
ErrorCode::ClientHttpError->value,
null,
409,
);
} }
$draw->refresh(); $draw->refresh();

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Draw;
use App\Models\Draw; use App\Models\Draw;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use App\Support\ApiMessage;
use App\Lottery\DrawStatus; use App\Lottery\DrawStatus;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
@@ -32,8 +33,8 @@ final class DrawRngRunController extends Controller
return $this->rng->executeLocked($locked); return $this->rng->executeLocked($locked);
}); });
} catch (\RuntimeException) { } catch (\RuntimeException $e) {
return ApiResponse::error(trans('api.client_error'), ErrorCode::ClientHttpError->value, null, 409); return ApiMessage::runtimeErrorResponse(request(), $e);
} }
$draw->refresh(); $draw->refresh();

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Integration;
use App\Models\AdminSite; use App\Models\AdminSite;
use App\Models\Player; use App\Models\Player;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -24,7 +25,7 @@ final class AdminIntegrationSiteConnectivityTestController extends Controller
abort_if($admin === null, 401); abort_if($admin === null, 401);
if (! AdminIntegrationSiteAccess::canAccess($admin, $admin_site)) { if (! AdminIntegrationSiteAccess::canAccess($admin, $admin_site)) {
return ApiResponse::error('无权访问该站点', ErrorCode::AdminForbidden->value, null, 403); return ApiMessage::errorResponse($request, 'admin.site_access_denied', ErrorCode::AdminForbidden->value, null, 403);
} }
$sitePlayerId = trim((string) $request->validated('site_player_id')); $sitePlayerId = trim((string) $request->validated('site_player_id'));

View File

@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Api\V1\Admin\Integration; namespace App\Http\Controllers\Api\V1\Admin\Integration;
use App\Models\AdminSite; use App\Models\AdminSite;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -20,7 +21,7 @@ final class AdminIntegrationSiteExportController extends Controller
abort_if($admin === null, 401); abort_if($admin === null, 401);
if (! AdminIntegrationSiteAccess::canAccess($admin, $admin_site)) { if (! AdminIntegrationSiteAccess::canAccess($admin, $admin_site)) {
return ApiResponse::error('无权访问该站点', ErrorCode::AdminForbidden->value, null, 403); return ApiMessage::errorResponse($request, 'admin.site_access_denied', ErrorCode::AdminForbidden->value, null, 403);
} }
$sheet = AdminIntegrationSitePresenter::parameterSheet($admin_site); $sheet = AdminIntegrationSitePresenter::parameterSheet($admin_site);

View File

@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Api\V1\Admin\Integration; namespace App\Http\Controllers\Api\V1\Admin\Integration;
use App\Models\AdminSite; use App\Models\AdminSite;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -25,7 +26,7 @@ final class AdminIntegrationSiteRotateSecretsController extends Controller
abort_if($admin === null, 401); abort_if($admin === null, 401);
if (! AdminIntegrationSiteAccess::canAccess($admin, $admin_site)) { if (! AdminIntegrationSiteAccess::canAccess($admin, $admin_site)) {
return ApiResponse::error('无权操作该站点', ErrorCode::AdminForbidden->value, null, 403); return ApiMessage::errorResponse($request, 'admin.site_rotate_denied', ErrorCode::AdminForbidden->value, null, 403);
} }
$result = $service->rotateSecrets($admin_site); $result = $service->rotateSecrets($admin_site);

View File

@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Api\V1\Admin\Integration; namespace App\Http\Controllers\Api\V1\Admin\Integration;
use App\Models\AdminSite; use App\Models\AdminSite;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -19,7 +20,7 @@ final class AdminIntegrationSiteShowController extends Controller
abort_if($admin === null, 401); abort_if($admin === null, 401);
if (! AdminIntegrationSiteAccess::canAccess($admin, $admin_site)) { if (! AdminIntegrationSiteAccess::canAccess($admin, $admin_site)) {
return ApiResponse::error('无权访问该站点', ErrorCode::AdminForbidden->value, null, 403); return ApiMessage::errorResponse($request, 'admin.site_access_denied', ErrorCode::AdminForbidden->value, null, 403);
} }
return ApiResponse::success(AdminIntegrationSitePresenter::detail($admin_site)); return ApiResponse::success(AdminIntegrationSitePresenter::detail($admin_site));

View File

@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Api\V1\Admin\Integration; namespace App\Http\Controllers\Api\V1\Admin\Integration;
use App\Models\AdminSite; use App\Models\AdminSite;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -26,7 +27,7 @@ final class AdminIntegrationSiteUpdateController extends Controller
abort_if($admin === null, 401); abort_if($admin === null, 401);
if (! AdminIntegrationSiteAccess::canAccess($admin, $admin_site)) { if (! AdminIntegrationSiteAccess::canAccess($admin, $admin_site)) {
return ApiResponse::error('无权修改该站点', ErrorCode::AdminForbidden->value, null, 403); return ApiMessage::errorResponse($request, 'admin.site_update_denied', ErrorCode::AdminForbidden->value, null, 403);
} }
$before = AdminIntegrationSitePresenter::detail($admin_site); $before = AdminIntegrationSitePresenter::detail($admin_site);

View File

@@ -6,6 +6,7 @@ use App\Models\JackpotPool;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use App\Support\ApiMessage;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Services\Jackpot\JackpotPoolAdjustmentService; use App\Services\Jackpot\JackpotPoolAdjustmentService;
@@ -44,14 +45,7 @@ final class AdminJackpotPoolAdjustController extends Controller
$request, $request,
); );
} catch (\RuntimeException $e) { } catch (\RuntimeException $e) {
$msg = match ($e->getMessage()) { return ApiMessage::runtimeErrorResponse($request, $e, ErrorCode::ClientHttpError->value, 422);
'adjustment_delta_zero' => trans('jackpot.adjustment_delta_zero', [], $request->lotteryLocale()),
'adjustment_reason_required' => trans('jackpot.adjustment_reason_required', [], $request->lotteryLocale()),
'adjustment_would_make_balance_negative' => trans('jackpot.adjustment_negative_balance', [], $request->lotteryLocale()),
default => trans('api.client_error', [], $request->lotteryLocale()),
};
return ApiResponse::error($msg, ErrorCode::ClientHttpError->value, ['reason' => $e->getMessage()], 422);
} }
$pool->refresh(); $pool->refresh();

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Jackpot;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Models\JackpotPool; use App\Models\JackpotPool;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use App\Support\ApiMessage;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@@ -46,7 +47,9 @@ final class AdminJackpotPoolManualBurstController extends Controller
$payload = $this->service->execute($pool, (int) $data['draw_id']); $payload = $this->service->execute($pool, (int) $data['draw_id']);
} catch (\RuntimeException $e) { } catch (\RuntimeException $e) {
return ApiResponse::error( return ApiResponse::error(
trans('api.jackpot_manual_burst_failed', ['reason' => $e->getMessage()], $request->lotteryLocale()), ApiMessage::get($request, 'jackpot_manual_burst_failed', [
'reason' => ApiMessage::reason($request, $e->getMessage()),
]),
ErrorCode::ClientHttpError->value, ErrorCode::ClientHttpError->value,
['reason' => $e->getMessage()], ['reason' => $e->getMessage()],
409, 409,

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Player;
use App\Models\Player; use App\Models\Player;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@@ -29,12 +30,7 @@ final class AdminPlayerDestroyController extends Controller
->exists(); ->exists();
if ($hasWallets) { if ($hasWallets) {
return ApiResponse::error( return ApiMessage::errorResponse($request, 'admin.player_wallet_balance_blocks_delete', ErrorCode::ValidationFailed->value, null, 422);
'该玩家钱包仍有余额,请先清空后再删除',
ErrorCode::ValidationFailed->value,
null,
422,
);
} }
$hasTickets = Player::query() $hasTickets = Player::query()
@@ -43,12 +39,7 @@ final class AdminPlayerDestroyController extends Controller
->exists(); ->exists();
if ($hasTickets) { if ($hasTickets) {
return ApiResponse::error( return ApiMessage::errorResponse($request, 'admin.player_has_tickets_blocks_delete', ErrorCode::ValidationFailed->value, null, 422);
'该玩家存在注单记录,无法删除',
ErrorCode::ValidationFailed->value,
null,
422,
);
} }
$player->wallets()->delete(); $player->wallets()->delete();

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Player;
use App\Models\Player; use App\Models\Player;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use App\Support\AdminSiteScope; use App\Support\AdminSiteScope;
@@ -21,12 +22,7 @@ final class AdminPlayerStoreController extends Controller
$siteCode = (string) $request->validated('site_code'); $siteCode = (string) $request->validated('site_code');
if (! AdminSiteScope::siteCodeAllowed($admin, $siteCode)) { if (! AdminSiteScope::siteCodeAllowed($admin, $siteCode)) {
return ApiResponse::error( return ApiMessage::errorResponse($request, 'admin.player_create_site_forbidden', ErrorCode::AdminForbidden->value, null, 403);
'无权在该站点下创建玩家',
ErrorCode::AdminForbidden->value,
null,
403,
);
} }
$exists = Player::query() $exists = Player::query()
@@ -35,12 +31,7 @@ final class AdminPlayerStoreController extends Controller
->exists(); ->exists();
if ($exists) { if ($exists) {
return ApiResponse::error( return ApiMessage::errorResponse($request, 'admin.player_already_registered', ErrorCode::ValidationFailed->value, null, 422);
'该主站玩家已在彩票平台注册',
ErrorCode::ValidationFailed->value,
null,
422,
);
} }
$player = Player::query()->create([ $player = Player::query()->create([

View File

@@ -5,7 +5,9 @@ namespace App\Http\Controllers\Api\V1\Admin\Risk;
use App\Models\Draw; use App\Models\Draw;
use App\Models\RiskPool; use App\Models\RiskPool;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request;
use App\Models\RiskPoolLockLog; use App\Models\RiskPoolLockLog;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@@ -17,26 +19,26 @@ final class AdminRiskPoolManualStatusController extends Controller
public function __construct( public function __construct(
private readonly RiskPoolService $riskPoolService, private readonly RiskPoolService $riskPoolService,
) {} ) {}
public function close(Draw $draw, string $number_4d): JsonResponse public function close(Request $request, Draw $draw, string $number_4d): JsonResponse
{ {
$pool = $this->updateStatus($draw, $number_4d, true, 'close', 'admin_manual_close'); $pool = $this->updateStatus($draw, $number_4d, true, 'close', 'admin_manual_close');
if ($pool === null) { if ($pool === null) {
return ApiResponse::error(trans('api.not_found'), ErrorCode::ClientHttpError->value, null, 404); return ApiMessage::errorResponse($request, 'not_found', ErrorCode::ClientHttpError->value, null, 404);
} }
return ApiResponse::success($this->row($pool)); return ApiResponse::success($this->row($pool));
} }
public function recover(Draw $draw, string $number_4d): JsonResponse public function recover(Request $request, Draw $draw, string $number_4d): JsonResponse
{ {
$pool = $this->updateStatus($draw, $number_4d, false, 'recover', 'admin_manual_recover'); $pool = $this->updateStatus($draw, $number_4d, false, 'recover', 'admin_manual_recover');
if ($pool === null) { if ($pool === null) {
return ApiResponse::error(trans('api.not_found'), ErrorCode::ClientHttpError->value, null, 404); return ApiMessage::errorResponse($request, 'not_found', ErrorCode::ClientHttpError->value, null, 404);
} }
if ((int) $pool->remaining_amount <= 0) { if ((int) $pool->remaining_amount <= 0) {
return ApiResponse::error(trans('api.client_error'), ErrorCode::ClientHttpError->value, [ return ApiMessage::errorResponse($request, 'risk_pool_no_remaining_amount', ErrorCode::ClientHttpError->value, [
'reason' => 'risk_pool_no_remaining_amount', 'reason' => 'risk_pool_no_remaining_amount',
], 409); ], 409);
} }

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Settlement;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use App\Models\SettlementBatch; use App\Models\SettlementBatch;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@@ -24,8 +25,8 @@ final class AdminSettlementBatchApproveController extends Controller
try { try {
$updated = $this->service->approve($batch, $admin, $request->validated('remark') ?? null); $updated = $this->service->approve($batch, $admin, $request->validated('remark') ?? null);
} catch (\RuntimeException) { } catch (\RuntimeException $e) {
return ApiResponse::error(trans('api.client_error'), ErrorCode::ClientHttpError->value, null, 409); return ApiMessage::runtimeErrorResponse($request, $e);
} }
return ApiResponse::success([ return ApiResponse::success([

View File

@@ -3,7 +3,9 @@
namespace App\Http\Controllers\Api\V1\Admin\Settlement; namespace App\Http\Controllers\Api\V1\Admin\Settlement;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request;
use App\Models\SettlementBatch; use App\Models\SettlementBatch;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
@@ -13,18 +15,12 @@ final class AdminSettlementBatchPayoutController extends Controller
{ {
public function __construct(private readonly SettlementBatchWorkflowService $service) {} public function __construct(private readonly SettlementBatchWorkflowService $service) {}
public function __invoke(SettlementBatch $batch): JsonResponse public function __invoke(Request $request, SettlementBatch $batch): JsonResponse
{ {
try { try {
$updated = $this->service->payout($batch); $updated = $this->service->payout($batch);
} catch (\RuntimeException $e) { } catch (\RuntimeException $e) {
$reason = $e->getMessage(); return ApiMessage::runtimeErrorResponse($request, $e);
$msg = match ($reason) {
'settlement_not_approved' => trans('api.settlement_not_approved'),
default => trans('api.client_error'),
};
return ApiResponse::error($msg, ErrorCode::ClientHttpError->value, ['reason' => $reason], 409);
} }
return ApiResponse::success([ return ApiResponse::success([

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Settlement;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use App\Models\SettlementBatch; use App\Models\SettlementBatch;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@@ -24,8 +25,8 @@ final class AdminSettlementBatchRejectController extends Controller
try { try {
$updated = $this->service->reject($batch, $admin, $request->validated('remark') ?? null); $updated = $this->service->reject($batch, $admin, $request->validated('remark') ?? null);
} catch (\RuntimeException) { } catch (\RuntimeException $e) {
return ApiResponse::error(trans('api.client_error'), ErrorCode::ClientHttpError->value, null, 409); return ApiMessage::runtimeErrorResponse($request, $e);
} }
return ApiResponse::success([ return ApiResponse::success([

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1\Admin\User;
use App\Models\AdminRole; use App\Models\AdminRole;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Services\AuditLogger; use App\Services\AuditLogger;
@@ -16,13 +17,13 @@ final class AdminRoleDestroyController extends Controller
public function __invoke(Request $request, AdminRole $admin_role): JsonResponse public function __invoke(Request $request, AdminRole $admin_role): JsonResponse
{ {
if ($admin_role->slug === AdminRole::ROLE_SUPER_ADMIN) { if ($admin_role->slug === AdminRole::ROLE_SUPER_ADMIN) {
return ApiResponse::error('不能删除超级管理员角色', ErrorCode::ValidationFailed->value, null, 422); return ApiMessage::errorResponse($request, 'admin.role_cannot_delete_super_admin', ErrorCode::ValidationFailed->value, null, 422);
} }
if ((bool) $admin_role->is_system) { if ((bool) $admin_role->is_system) {
return ApiResponse::error('系统内置角色不允许删除', ErrorCode::ValidationFailed->value, null, 422); return ApiMessage::errorResponse($request, 'admin.role_builtin_cannot_delete', ErrorCode::ValidationFailed->value, null, 422);
} }
if ($admin_role->assignedUserCount() > 0) { if ($admin_role->assignedUserCount() > 0) {
return ApiResponse::error('该角色下仍有关联管理员,不能删除', ErrorCode::ValidationFailed->value, null, 422); return ApiMessage::errorResponse($request, 'admin.role_has_users_cannot_delete', ErrorCode::ValidationFailed->value, null, 422);
} }
$before = AdminRoleApiPresenter::item($admin_role); $before = AdminRoleApiPresenter::item($admin_role);

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1\Admin\User;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Services\AuditLogger; use App\Services\AuditLogger;
@@ -20,12 +21,7 @@ final class AdminUserDestroyController extends Controller
$actor = $request->lotteryAdmin(); $actor = $request->lotteryAdmin();
if ((int) $actor->getKey() === (int) $admin_user->getKey()) { if ((int) $actor->getKey() === (int) $admin_user->getKey()) {
return ApiResponse::error( return ApiMessage::errorResponse($request, 'admin.user_cannot_delete_self', ErrorCode::ValidationFailed->value, null, 422);
'不能删除当前登录账号',
ErrorCode::ValidationFailed->value,
null,
422,
);
} }
$admin_user->load('roles'); $admin_user->load('roles');
@@ -35,12 +31,7 @@ final class AdminUserDestroyController extends Controller
->whereHas('roles', static fn ($q) => $q->where('admin_roles.slug', AdminUser::ROLE_SUPER_ADMIN)) ->whereHas('roles', static fn ($q) => $q->where('admin_roles.slug', AdminUser::ROLE_SUPER_ADMIN))
->exists(); ->exists();
if (! $hasOther) { if (! $hasOther) {
return ApiResponse::error( return ApiMessage::errorResponse($request, 'admin.user_cannot_delete_last_super_admin', ErrorCode::ValidationFailed->value, null, 422);
'不能删除最后一个超级管理员',
ErrorCode::ValidationFailed->value,
null,
422,
);
} }
} }

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1\Admin\User\Concerns;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@@ -15,12 +16,7 @@ trait EnsuresSuperAdminActor
/** @var AdminUser $actor */ /** @var AdminUser $actor */
$actor = $request->lotteryAdmin(); $actor = $request->lotteryAdmin();
if (! $actor->isSuperAdmin()) { if (! $actor->isSuperAdmin()) {
return ApiResponse::error( return ApiMessage::errorResponse($request, 'admin.super_admin_only_for_roles', ErrorCode::AdminForbidden->value, null, 403);
'仅超级管理员可管理角色',
ErrorCode::AdminForbidden->value,
null,
403,
);
} }
return null; return null;

View File

@@ -2,7 +2,9 @@
namespace App\Http\Controllers\Api\V1\Admin\Wallet; namespace App\Http\Controllers\Api\V1\Admin\Wallet;
use App\Lottery\ErrorCode;
use App\Models\TransferOrder; use App\Models\TransferOrder;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use App\Support\LotteryMessage; use App\Support\LotteryMessage;
use App\Exceptions\WalletOperationException; use App\Exceptions\WalletOperationException;
@@ -27,7 +29,7 @@ final class TransferOrderReconcileController extends Controller
{ {
$order = TransferOrder::query()->where('transfer_no', $transferNo)->first(); $order = TransferOrder::query()->where('transfer_no', $transferNo)->first();
if ($order === null) { if ($order === null) {
return ApiResponse::error(__('wallet.order_not_found'), 404); return ApiMessage::errorResponse($request, 'wallet.order_not_found', ErrorCode::ClientHttpError->value, null, 404);
} }
try { try {
@@ -52,7 +54,7 @@ final class TransferOrderReconcileController extends Controller
{ {
$order = TransferOrder::query()->where('transfer_no', $transferNo)->first(); $order = TransferOrder::query()->where('transfer_no', $transferNo)->first();
if ($order === null) { if ($order === null) {
return ApiResponse::error(__('wallet.order_not_found'), 404); return ApiMessage::errorResponse($request, 'wallet.order_not_found', ErrorCode::ClientHttpError->value, null, 404);
} }
try { try {
@@ -77,7 +79,7 @@ final class TransferOrderReconcileController extends Controller
{ {
$order = TransferOrder::query()->where('transfer_no', $transferNo)->first(); $order = TransferOrder::query()->where('transfer_no', $transferNo)->first();
if ($order === null) { if ($order === null) {
return ApiResponse::error(__('wallet.order_not_found'), 404); return ApiMessage::errorResponse($request, 'wallet.order_not_found', ErrorCode::ClientHttpError->value, null, 404);
} }
try { try {

View File

@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Api\V1\Play; namespace App\Http\Controllers\Api\V1\Play;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@@ -23,20 +24,10 @@ final class PlayEffectiveCatalogController extends Controller
try { try {
return ApiResponse::success($catalog->build($c)); return ApiResponse::success($catalog->build($c));
} catch (ModelNotFoundException) { } catch (ModelNotFoundException) {
return ApiResponse::error( return ApiMessage::errorResponse($request, 'effective_config_not_initialized', ErrorCode::NotFound->value, null, 404);
'effective config not initialized',
ErrorCode::NotFound->value,
null,
404,
);
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
if ($e->getMessage() === 'currency') { if ($e->getMessage() === 'currency') {
return ApiResponse::error( return ApiMessage::errorResponse($request, 'invalid_or_disabled_currency', ErrorCode::ConfigCurrencyInvalid->value, null, 400);
'invalid or disabled currency',
ErrorCode::ConfigCurrencyInvalid->value,
null,
400,
);
} }
throw $e; throw $e;

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api\V1\Setting;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\LotterySetting; use App\Models\LotterySetting;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -25,7 +26,8 @@ final class SettingIndexController extends Controller
$group = $request->query('group'); $group = $request->query('group');
if (! is_string($group) || $group === '' || ! in_array($group, self::PUBLIC_GROUPS, true)) { if (! is_string($group) || $group === '' || ! in_array($group, self::PUBLIC_GROUPS, true)) {
return ApiResponse::error( return ApiMessage::errorResponse(
$request,
'invalid_settings_group', 'invalid_settings_group',
ErrorCode::ClientHttpError->value, ErrorCode::ClientHttpError->value,
['allowed_groups' => self::PUBLIC_GROUPS], ['allowed_groups' => self::PUBLIC_GROUPS],

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api\V1\Wallet;
use App\Models\Player; use App\Models\Player;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Models\PlayerWallet; use App\Models\PlayerWallet;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Support\CurrencyResolver; use App\Support\CurrencyResolver;
@@ -88,7 +89,7 @@ final class WalletBalanceController extends Controller
if (! CurrencyResolver::isEnabled($code)) { if (! CurrencyResolver::isEnabled($code)) {
return ApiResponse::error( return ApiResponse::error(
__('wallet.invalid_currency'), ApiMessage::get($request, 'wallet.invalid_currency'),
ErrorCode::WalletInvalidCurrency->value, ErrorCode::WalletInvalidCurrency->value,
null, null,
400, 400,

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1\Wallet;
use App\Models\Player; use App\Models\Player;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use App\Support\LotteryMessage; use App\Support\LotteryMessage;
use App\Support\CurrencyResolver; use App\Support\CurrencyResolver;
@@ -59,7 +60,7 @@ final class WalletTransferInController extends Controller
if (! CurrencyResolver::isEnabled($code)) { if (! CurrencyResolver::isEnabled($code)) {
return ApiResponse::error( return ApiResponse::error(
__('wallet.invalid_currency'), ApiMessage::get($request, 'wallet.invalid_currency'),
ErrorCode::WalletInvalidCurrency->value, ErrorCode::WalletInvalidCurrency->value,
null, null,
400, 400,

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1\Wallet;
use App\Models\Player; use App\Models\Player;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use App\Support\LotteryMessage; use App\Support\LotteryMessage;
use App\Support\CurrencyResolver; use App\Support\CurrencyResolver;
@@ -59,7 +60,7 @@ final class WalletTransferOutController extends Controller
if (! CurrencyResolver::isEnabled($code)) { if (! CurrencyResolver::isEnabled($code)) {
return ApiResponse::error( return ApiResponse::error(
__('wallet.invalid_currency'), ApiMessage::get($request, 'wallet.invalid_currency'),
ErrorCode::WalletInvalidCurrency->value, ErrorCode::WalletInvalidCurrency->value,
null, null,
400, 400,

View File

@@ -5,6 +5,7 @@ namespace App\Http\Middleware;
use Closure; use Closure;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse; use App\Support\ApiResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@@ -27,7 +28,7 @@ final class EnsureAdminApiResourcePermission
$route = $request->route(); $route = $request->route();
$routeName = is_object($route) ? $route->getName() : null; $routeName = is_object($route) ? $route->getName() : null;
if (! is_string($routeName) || $routeName === '') { if (! is_string($routeName) || $routeName === '') {
return ApiResponse::error('后台路由缺少 route name,无法执行资源鉴权。', ErrorCode::InternalError->value, null, 500); return ApiMessage::errorResponse($request, 'admin.route_name_missing_for_permission', ErrorCode::InternalError->value, null, 500);
} }
$normalizedRouteName = $this->normalizeAdminRouteName($routeName); $normalizedRouteName = $this->normalizeAdminRouteName($routeName);
@@ -37,11 +38,13 @@ final class EnsureAdminApiResourcePermission
->first(['id', 'code', 'auth_mode']); ->first(['id', 'code', 'auth_mode']);
if ($resource === null) { if ($resource === null) {
return ApiResponse::error( return ApiMessage::errorResponse(
sprintf('后台 API 资源未配置:%s', $normalizedRouteName), $request,
'admin.api_resource_not_configured',
ErrorCode::InternalError->value, ErrorCode::InternalError->value,
['route_name' => $normalizedRouteName], ['route_name' => $normalizedRouteName],
500, 500,
['route' => $normalizedRouteName],
); );
} }
@@ -59,11 +62,13 @@ final class EnsureAdminApiResourcePermission
->all(); ->all();
if ($permissionCodes === []) { if ($permissionCodes === []) {
return ApiResponse::error( return ApiMessage::errorResponse(
sprintf('后台 API 资源未绑定权限动作:%s', (string) $resource->code), $request,
'admin.api_resource_no_permission_binding',
ErrorCode::InternalError->value, ErrorCode::InternalError->value,
['resource_code' => $resource->code], ['resource_code' => $resource->code],
500, 500,
['code' => (string) $resource->code],
); );
} }

View File

@@ -20,19 +20,9 @@ final class AdminMessage
* *
* @param string $key 语言包键名,如 'unauthenticated', 'permission_denied' * @param string $key 语言包键名,如 'unauthenticated', 'permission_denied'
*/ */
public static function get(Request $request, string $key): string public static function get(Request $request, string $key, array $replace = []): string
{ {
$fallback = (string) config('lottery.locales.fallback', 'en'); return ApiMessage::get($request, 'admin.'.$key, $replace);
$locale = (string) ($request->attributes->get('lottery_locale') ?? LotteryLocale::resolve($request));
$fullKey = 'admin.'.$key;
$msg = trans($fullKey, [], $locale);
if ($msg !== $fullKey) {
return $msg;
}
return trans($fullKey, [], $fallback);
} }
/** /**

View File

@@ -6,6 +6,8 @@ use App\Models\AdminSite;
use App\Models\AdminUser; use App\Models\AdminUser;
use App\Models\Player; use App\Models\Player;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@@ -147,7 +149,13 @@ final class AdminSiteScope
public static function denyUnlessPlayerAccessible(AdminUser $admin, Player $player): ?JsonResponse public static function denyUnlessPlayerAccessible(AdminUser $admin, Player $player): ?JsonResponse
{ {
if (! self::playerAccessible($admin, $player)) { if (! self::playerAccessible($admin, $player)) {
return ApiResponse::error('无权访问该站点下的玩家', ErrorCode::AdminForbidden->value, null, 403); return ApiMessage::errorResponse(
request(),
'admin.site_player_access_denied',
ErrorCode::AdminForbidden->value,
null,
403,
);
} }
return null; return null;

140
app/Support/ApiMessage.php Normal file
View File

@@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
namespace App\Support;
use App\Lottery\ErrorCode;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Lang;
/**
* API 响应文案zh / en / ne msg 字段与 RuntimeException reason 翻译。
*/
final class ApiMessage
{
/** @var list<string> */
private const LOOKUP_PREFIXES = ['api.reasons.', 'api.', 'admin.', 'jackpot.', 'wallet.'];
public static function locale(?Request $request = null): string
{
$request ??= request();
if ($request instanceof Request && $request->attributes->has('lottery_locale')) {
return (string) $request->attributes->get('lottery_locale');
}
return LotteryLocale::resolve($request instanceof Request ? $request : null);
}
/**
* @param array<string, string|int|float> $replace
*/
public static function get(?Request $request, string $key, array $replace = []): string
{
$locale = self::locale($request);
$fallback = (string) config('lottery.locales.fallback', 'en');
foreach ([$locale, $fallback] as $tryLocale) {
foreach (self::candidateKeys($key) as $fullKey) {
$msg = trans($fullKey, $replace, $tryLocale);
if ($msg !== $fullKey && $msg !== '') {
return $msg;
}
}
}
return $key;
}
/**
* RuntimeException / 业务 reason 机器码 用户可见文案。
*
* @param array<string, string|int|float> $replace
*/
public static function reason(?Request $request, string $reasonKey, array $replace = []): string
{
$reasonKey = trim($reasonKey);
if ($reasonKey === '') {
return self::get($request, 'client_error', $replace);
}
$translated = self::get($request, $reasonKey, $replace);
if ($translated !== $reasonKey) {
return $translated;
}
return self::get($request, 'client_error', $replace);
}
public static function successMessage(?Request $request = null): string
{
return self::get($request, 'success.ok');
}
/**
* @param array<string, string|int|float> $replace
*/
public static function errorResponse(
?Request $request,
string $messageKey,
int $code,
mixed $data = null,
int $httpStatus = 400,
array $replace = [],
): JsonResponse {
return ApiResponse::error(
self::get($request, $messageKey, $replace),
$code,
$data,
$httpStatus,
);
}
public static function runtimeErrorResponse(
?Request $request,
\RuntimeException $exception,
int $code = 0,
int $httpStatus = 409,
): JsonResponse {
$reason = trim($exception->getMessage());
$resolvedCode = $code !== 0 ? $code : ErrorCode::ClientHttpError->value;
return ApiResponse::error(
self::reason($request, $reason),
$resolvedCode,
['reason' => $reason],
$httpStatus,
);
}
/**
* @return list<string>
*/
private static function candidateKeys(string $key): array
{
$key = trim($key);
if ($key === '') {
return ['api.client_error'];
}
if (str_contains($key, '.')) {
return array_values(array_unique([
$key,
'api.'.$key,
'admin.'.$key,
'jackpot.'.$key,
'wallet.'.$key,
'api.reasons.'.$key,
]));
}
$keys = [];
foreach (self::LOOKUP_PREFIXES as $prefix) {
$keys[] = $prefix.$key;
}
return $keys;
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Support;
use App\Lottery\ErrorCode; use App\Lottery\ErrorCode;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/** /**
* 对外 API 统一 JSON 结构:{ code, msg, data } * 对外 API 统一 JSON 结构:{ code, msg, data }
@@ -13,11 +14,11 @@ use Illuminate\Http\JsonResponse;
*/ */
final class ApiResponse final class ApiResponse
{ {
public static function success(mixed $data = null, string $msg = 'ok', int $code = 0): JsonResponse public static function success(mixed $data = null, ?string $msg = null, int $code = 0, ?Request $request = null): JsonResponse
{ {
return response()->json([ return response()->json([
'code' => $code, 'code' => $code,
'msg' => $msg, 'msg' => $msg ?? ApiMessage::successMessage($request),
'data' => $data, 'data' => $data,
]); ]);
} }

View File

@@ -22,16 +22,7 @@ final class LotteryMessage
*/ */
public static function wallet(Request $request, int $code): string public static function wallet(Request $request, int $code): string
{ {
$fallback = (string) config('lottery.locales.fallback', 'en'); return ApiMessage::get($request, 'wallet.'.$code);
$locale = (string) ($request->attributes->get('lottery_locale') ?? LotteryLocale::resolve($request));
$key = 'wallet.'.$code;
$msg = trans($key, [], $locale);
if ($msg !== $key) {
return $msg;
}
return trans($key, [], $fallback);
} }
/** /**
@@ -41,16 +32,6 @@ final class LotteryMessage
*/ */
public static function sso(Request $request, int $code): string public static function sso(Request $request, int $code): string
{ {
$fallback = (string) config('lottery.locales.fallback', 'en'); return ApiMessage::get($request, 'sso.'.$code);
$locale = (string) ($request->attributes->get('lottery_locale') ?? LotteryLocale::resolve($request));
$key = 'sso.'.$code; // 对应 lang/{locale}/sso.php 内键名,如 '8001'
$msg = trans($key, [], $locale);
// Laravel 无键时返回整条 key 字符串,此时改用 fallback 语言再试一次
if ($msg !== $key) {
return $msg;
}
return trans($key, [], $fallback);
} }
} }

View File

@@ -6,5 +6,25 @@ return [
'invalid_credentials' => 'Invalid account or password.', 'invalid_credentials' => 'Invalid account or password.',
'account_disabled' => 'This account has been disabled.', 'account_disabled' => 'This account has been disabled.',
'permission_denied' => 'You do not have permission to perform this action.', 'permission_denied' => 'You do not have permission to perform this action.',
'forbidden' => 'You do not have permission to perform this action.',
'settlement_run_skipped' => 'Settlement was not run for this draw (check draw status and published result batch).', 'settlement_run_skipped' => 'Settlement was not run for this draw (check draw status and published result batch).',
'site_access_denied' => 'You do not have access to this site.',
'site_rotate_denied' => 'You cannot rotate secrets for this site.',
'site_update_denied' => 'You cannot modify this site.',
'site_player_access_denied' => 'You do not have access to players under this site.',
'player_create_site_forbidden' => 'You cannot create players under this site.',
'player_already_registered' => 'This main-site player is already registered.',
'player_wallet_balance_blocks_delete' => 'Player wallet still has balance. Clear it before deletion.',
'player_has_tickets_blocks_delete' => 'Player has ticket records and cannot be deleted.',
'role_cannot_delete_super_admin' => 'Cannot delete the super admin role.',
'role_builtin_cannot_delete' => 'Built-in roles cannot be deleted.',
'role_has_users_cannot_delete' => 'This role still has assigned admins and cannot be deleted.',
'user_cannot_delete_self' => 'Cannot delete your own account.',
'user_cannot_delete_last_super_admin' => 'Cannot delete the last super admin.',
'super_admin_only_for_roles' => 'Only super admins can manage roles.',
'route_name_missing_for_permission' => 'Admin route is missing a route name for permission checks.',
'api_resource_not_configured' => 'Admin API resource is not configured: :route',
'api_resource_no_permission_binding' => 'Admin API resource has no permission binding: :code',
'currency_default_cannot_delete' => 'Default currency cannot be deleted.',
'currency_referenced_cannot_delete' => 'Currency is referenced by business data and cannot be deleted: :refs',
]; ];

View File

@@ -1,9 +1,16 @@
<?php <?php
return [ return [
'success' => [
'ok' => 'OK',
],
'validation_failed' => 'The given data was invalid.', 'validation_failed' => 'The given data was invalid.',
'client_error' => 'This request could not be completed.', 'client_error' => 'This request could not be completed.',
'invalid_params' => 'Invalid request parameters.',
'invalid_settings_group' => 'This settings group is not allowed.',
'draw_no_exists' => 'Draw number already exists. Use another draw number or sequence.', 'draw_no_exists' => 'Draw number already exists. Use another draw number or sequence.',
'draw_not_found' => 'Draw not found.',
'draw_timeline_invalid' => 'Start time must be before close time, and close time must be before draw time.', 'draw_timeline_invalid' => 'Start time must be before close time, and close time must be before draw time.',
'draw_not_editable' => 'Only pending draws, or open draws with no bets, can be edited.', 'draw_not_editable' => 'Only pending draws, or open draws with no bets, can be edited.',
'draw_not_deletable' => 'Only pending draws with no bets can be deleted.', 'draw_not_deletable' => 'Only pending draws with no bets can be deleted.',
@@ -14,4 +21,37 @@ return [
'too_many_requests' => 'Too many requests. Please try again later.', 'too_many_requests' => 'Too many requests. Please try again later.',
'server_error' => 'Something went wrong. Please try again later.', 'server_error' => 'Something went wrong. Please try again later.',
'jackpot_manual_burst_failed' => 'Manual jackpot burst failed: :reason', 'jackpot_manual_burst_failed' => 'Manual jackpot burst failed: :reason',
'config_version_not_draft' => 'This config version is not a draft.',
'config_version_cannot_delete_active' => 'Cannot delete the active config version.',
'effective_config_not_initialized' => 'Play configuration has not been initialized.',
'invalid_or_disabled_currency' => 'Invalid or disabled currency.',
'risk_pool_no_remaining_amount' => 'Insufficient remaining amount in the risk pool.',
'reasons' => [
'jackpot_disabled' => 'Jackpot pool is disabled.',
'jackpot_pool_empty' => 'Jackpot pool balance is zero.',
'jackpot_already_burst_for_draw' => 'Jackpot was already burst for this draw.',
'jackpot_manual_no_first_prize_winners' => 'No first-prize winners on this draw.',
'jackpot_already_allocated_for_draw' => 'Jackpot was already allocated for this draw.',
'draw_not_ready_for_jackpot_burst' => 'Draw is not in settling or settled status.',
'draw_result_not_published' => 'Draw results have not been published.',
'settlement_batch_not_found' => 'Settlement batch not found for this draw.',
'settlement_not_pending_review' => 'Settlement batch is not pending review.',
'settlement_not_approved' => 'Settlement batch is not approved.',
'draw_has_unsettled_tickets' => 'This draw still has unsettled tickets.',
'batch_not_pending_review' => 'Result batch is not pending review.',
'draw_not_ready_to_publish' => 'Draw is not ready to publish results.',
'batch_result_version_stale' => 'Result batch version is stale. Refresh and try again.',
'draw_settlement_in_progress' => 'Settlement is in progress for this draw.',
'draw_already_settled' => 'This draw is already settled.',
'draw_pending_result_batch_exists' => 'A pending result batch already exists.',
'draw_not_closeable' => 'This draw cannot be closed manually.',
'draw_not_cancelable' => 'This draw cannot be cancelled.',
'draw_has_settled_tickets' => 'This draw has settled tickets.',
'draw_not_runnable' => 'RNG cannot be run for this draw.',
'draw_not_in_cooldown' => 'Draw is not in cooldown.',
'adjustment_delta_zero' => 'Adjustment amount cannot be zero.',
'adjustment_reason_required' => 'Adjustment reason is required.',
'adjustment_would_make_balance_negative' => 'Adjustment would make pool balance negative.',
],
]; ];

View File

@@ -3,6 +3,7 @@
/** PRD 钱包段多语言NegotiateLotteryLocale 后由 __() 选用 */ /** PRD 钱包段多语言NegotiateLotteryLocale 后由 __() 选用 */
return [ return [
'invalid_currency' => 'Invalid currency code', 'invalid_currency' => 'Invalid currency code',
'order_not_found' => 'Transfer order not found',
'1001' => 'Insufficient lottery wallet balance', '1001' => 'Insufficient lottery wallet balance',
'1002' => 'A previous transfer is still processing; please retry shortly', '1002' => 'A previous transfer is still processing; please retry shortly',

View File

@@ -1,10 +1,30 @@
<?php <?php
return [ return [
'unauthenticated' => 'प्रमाणीकरण छैन वा सेसन समाप्त भएको छ।', 'unauthenticated' => 'लगइन छैन वा सत्र समाप्त भयो।',
'invalid_captcha' => 'क्याप्चा गलत वा समय सकियो।', 'invalid_captcha' => 'क्याप्चा गलत वा म्याद सकिएको छ।',
'invalid_credentials' => 'खाता वा पासवर्ड गलत।', 'invalid_credentials' => 'खाता वा पासवर्ड गलत।',
'account_disabled' => 'यो खाता निष्क्रिय गरिएको छ।', 'account_disabled' => 'यो खाता निष्क्रिय गरिएको छ।',
'permission_denied' => 'यो कार्य गर्न अनुमति छैन।', 'permission_denied' => 'यो कार्य गर्न अनुमति छैन।',
'settlement_run_skipped' => 'यस ड्रका लागि बन्दोबस्त चलाइएन (ड्र स्थिति र प्रकाशित नतिजा जाँच गर्नुहोस्)।', 'forbidden' => 'यो कार्य गर्ने अनुमति छैन।',
'settlement_run_skipped' => 'यो ड्रको सेटलमेन्ट चलाइएन (ड्र स्थिति र प्रकाशित नतिजा जाँच गर्नुहोस्)।',
'site_access_denied' => 'यो साइटमा पहुँच छैन।',
'site_rotate_denied' => 'यो साइटको गोप्यियता परिवर्तन गर्न मिल्दैन।',
'site_update_denied' => 'यो साइट सम्पादन गर्न मिल्दैन।',
'site_player_access_denied' => 'यो साइटका खेलाडीहरूमा पहुँच छैन।',
'player_create_site_forbidden' => 'यो साइटमा खेलाडी सिर्जना गर्न मिल्दैन।',
'player_already_registered' => 'यो मुख्य साइट खेलाडी पहिले नै दर्ता भइसकेको छ।',
'player_wallet_balance_blocks_delete' => 'खेलाडी वालेटमा ब्यालेन्स छ, मेटाउनु अघि खाली गर्नुहोस्।',
'player_has_tickets_blocks_delete' => 'खेलाडीसँग टिकट रेकर्ड छ, मेटाउन मिल्दैन।',
'role_cannot_delete_super_admin' => 'सुपर एडमिन भूमिका मेटाउन मिल्दैन।',
'role_builtin_cannot_delete' => 'बिल्ट-इन भूमिका मेटाउन मिल्दैन।',
'role_has_users_cannot_delete' => 'यो भूमिकामा अझै एडमिन छ, मेटाउन मिल्दैन।',
'user_cannot_delete_self' => 'आफ्नै खाता मेटाउन मिल्दैन।',
'user_cannot_delete_last_super_admin' => 'अन्तिम सुपर एडमिन मेटाउन मिल्दैन।',
'super_admin_only_for_roles' => 'भूमिका व्यवस्थापन केवल सुपर एडमिनले गर्न सक्छ।',
'route_name_missing_for_permission' => 'एडमिन रुटमा route name छैन, अनुमति जाँच गर्न सकिँदैन।',
'api_resource_not_configured' => 'एडमिन API स्रोत कन्फिग गरिएको छैन: :route',
'api_resource_no_permission_binding' => 'एडमिन API स्रोतमा अनुमति बाइन्डिङ छैन: :code',
'currency_default_cannot_delete' => 'पूर्वनिर्धारित मुद्रा मेटाउन मिल्दैन।',
'currency_referenced_cannot_delete' => 'मुद्रा व्यवसाय डाटामा प्रयोग भएको छ, मेटाउन मिल्दैन: :refs',
]; ];

View File

@@ -1,9 +1,16 @@
<?php <?php
return [ return [
'success' => [
'ok' => 'सफल',
],
'validation_failed' => 'दिइएको डाटा अमान्य छ।', 'validation_failed' => 'दिइएको डाटा अमान्य छ।',
'client_error' => 'यो अनुरोध पूरा गर्न सकिएन।', 'client_error' => 'यो अनुरोध पूरा गर्न सकिएन।',
'invalid_params' => 'अनुरोध प्यारामिटर अमान्य छ।',
'invalid_settings_group' => 'यो सेटिङ समूह पढ्न अनुमति छैन।',
'draw_no_exists' => 'यो ड्र नम्बर पहिले नै छ। अर्को ड्र नम्बर वा क्रम प्रयोग गर्नुहोस्।', 'draw_no_exists' => 'यो ड्र नम्बर पहिले नै छ। अर्को ड्र नम्बर वा क्रम प्रयोग गर्नुहोस्।',
'draw_not_found' => 'ड्र फेला परेन।',
'draw_timeline_invalid' => 'सुरु समय बन्द समय भन्दा अघि, बन्द समय ड्र समय भन्दा अघि हुनुपर्छ।', 'draw_timeline_invalid' => 'सुरु समय बन्द समय भन्दा अघि, बन्द समय ड्र समय भन्दा अघि हुनुपर्छ।',
'draw_not_editable' => 'केवल pending वा बेट नभएको open ड्र सम्पादन गर्न सकिन्छ।', 'draw_not_editable' => 'केवल pending वा बेट नभएको open ड्र सम्पादन गर्न सकिन्छ।',
'draw_not_deletable' => 'केवल pending र बेट नभएको ड्र मेटाउन सकिन्छ।', 'draw_not_deletable' => 'केवल pending र बेट नभएको ड्र मेटाउन सकिन्छ।',
@@ -14,4 +21,37 @@ return [
'too_many_requests' => 'धेरै अनुरोधहरू। कृपया पछि प्रयास गर्नुहोस्।', 'too_many_requests' => 'धेरै अनुरोधहरू। कृपया पछि प्रयास गर्नुहोस्।',
'server_error' => 'केही गडबड भयो। कृपया पछि प्रयास गर्नुहोस्।', 'server_error' => 'केही गडबड भयो। कृपया पछि प्रयास गर्नुहोस्।',
'jackpot_manual_burst_failed' => 'म्यानुअल ज्याकपोट बर्स्ट असफल: :reason', 'jackpot_manual_burst_failed' => 'म्यानुअल ज्याकपोट बर्स्ट असफल: :reason',
'config_version_not_draft' => 'यो कन्फिग संस्करण ड्राफ्ट होइन।',
'config_version_cannot_delete_active' => 'सक्रिय कन्फिग संस्करण मेटाउन मिल्दैन।',
'effective_config_not_initialized' => 'प्ले कन्फिग अझै सुरु भएको छैन।',
'invalid_or_disabled_currency' => 'मुद्रा अमान्य वा निष्क्रिय छ।',
'risk_pool_no_remaining_amount' => 'जोखिम पोलमा बाँकी रकम अपर्याप्त छ।',
'reasons' => [
'jackpot_disabled' => 'ज्याकपोट पोल निष्क्रिय छ।',
'jackpot_pool_empty' => 'ज्याकपोट पोल ब्यालेन्स शून्य छ।',
'jackpot_already_burst_for_draw' => 'यो ड्रमा पहिले नै बर्स्ट भइसकेको छ।',
'jackpot_manual_no_first_prize_winners' => 'यो ड्रमा पहिलो पुरस्कार विजेता छैन।',
'jackpot_already_allocated_for_draw' => 'यो ड्रमा ज्याकपोट पहिले नै बाँडिएको छ।',
'draw_not_ready_for_jackpot_burst' => 'ड्र settling वा settled अवस्थामा छैन।',
'draw_result_not_published' => 'ड्र नतिजा प्रकाशित भएको छैन।',
'settlement_batch_not_found' => 'यो ड्रको सेटलमेन्ट ब्याच फेला परेन।',
'settlement_not_pending_review' => 'सेटलमेन्ट ब्याच समीक्षामा छैन।',
'settlement_not_approved' => 'सेटलमेन्ट ब्याच स्वीकृत छैन।',
'draw_has_unsettled_tickets' => 'यो ड्रमा अझै नसेटल टिकट छ।',
'batch_not_pending_review' => 'नतिजा ब्याच समीक्षामा छैन।',
'draw_not_ready_to_publish' => 'ड्र नतिजा प्रकाशित गर्न तयार छैन।',
'batch_result_version_stale' => 'नतिजा संस्करण पुरानो भयो। रिफ्रेस गरेर पुन: प्रयास गर्नुहोस्।',
'draw_settlement_in_progress' => 'यो ड्रको सेटलमेन्ट चलिरहेको छ।',
'draw_already_settled' => 'यो ड्र पहिले नै सेटल भइसकेको छ।',
'draw_pending_result_batch_exists' => 'पेन्डिङ नतिजा ब्याच पहिले नै छ।',
'draw_not_closeable' => 'यो ड्र म्यानुअल बन्द गर्न मिल्दैन।',
'draw_not_cancelable' => 'यो ड्र रद्द गर्न मिल्दैन।',
'draw_has_settled_tickets' => 'यो ड्रमा सेटल भएका टिकट छ।',
'draw_not_runnable' => 'यो ड्रमा RNG चलाउन मिल्दैन।',
'draw_not_in_cooldown' => 'ड्र कूलडाउन अवस्थामा छैन।',
'adjustment_delta_zero' => 'समायोजन रकम हुन सक्दैन।',
'adjustment_reason_required' => 'समायोजन कारण आवश्यक छ।',
'adjustment_would_make_balance_negative' => 'समायोजन पछि पोल ब्यालेन्स ऋणात्मक हुन्छ।',
],
]; ];

View File

@@ -2,6 +2,7 @@
return [ return [
'invalid_currency' => 'मुद्रा कोड अमान्य', 'invalid_currency' => 'मुद्रा कोड अमान्य',
'order_not_found' => 'स्थानान्तरण अर्डर फेला परेन',
'1001' => 'लटरी वालेट ब्यालेन्स अपर्याप्त', '1001' => 'लटरी वालेट ब्यालेन्स अपर्याप्त',
'1002' => 'अघिल्लो स्थानान्तरण अझै प्रक्रियामा छ, पछि प्रयास गर्नुहोस्', '1002' => 'अघिल्लो स्थानान्तरण अझै प्रक्रियामा छ, पछि प्रयास गर्नुहोस्',

View File

@@ -6,5 +6,25 @@ return [
'invalid_credentials' => '账号或密码错误。', 'invalid_credentials' => '账号或密码错误。',
'account_disabled' => '该账号已被禁用。', 'account_disabled' => '该账号已被禁用。',
'permission_denied' => '当前账号无此操作权限。', 'permission_denied' => '当前账号无此操作权限。',
'forbidden' => '当前账号无此操作权限。',
'settlement_run_skipped' => '本期未执行结算(请检查期号状态与已发布开奖批次)。', 'settlement_run_skipped' => '本期未执行结算(请检查期号状态与已发布开奖批次)。',
'site_access_denied' => '无权访问该站点。',
'site_rotate_denied' => '无权操作该站点。',
'site_update_denied' => '无权修改该站点。',
'site_player_access_denied' => '无权访问该站点下的玩家。',
'player_create_site_forbidden' => '无权在该站点下创建玩家。',
'player_already_registered' => '该主站玩家已在彩票平台注册。',
'player_wallet_balance_blocks_delete' => '该玩家钱包仍有余额,请先清空后再删除。',
'player_has_tickets_blocks_delete' => '该玩家存在注单记录,无法删除。',
'role_cannot_delete_super_admin' => '不能删除超级管理员角色。',
'role_builtin_cannot_delete' => '系统内置角色不允许删除。',
'role_has_users_cannot_delete' => '该角色下仍有关联管理员,不能删除。',
'user_cannot_delete_self' => '不能删除当前登录账号。',
'user_cannot_delete_last_super_admin' => '不能删除最后一个超级管理员。',
'super_admin_only_for_roles' => '仅超级管理员可管理角色。',
'route_name_missing_for_permission' => '后台路由缺少 route name无法执行资源鉴权。',
'api_resource_not_configured' => '后台 API 资源未配置::route',
'api_resource_no_permission_binding' => '后台 API 资源未绑定权限动作::code',
'currency_default_cannot_delete' => '默认币种不可删除。',
'currency_referenced_cannot_delete' => '该币种已被业务数据引用,暂不可删除::refs',
]; ];

View File

@@ -1,9 +1,16 @@
<?php <?php
return [ return [
'success' => [
'ok' => '操作成功',
],
'validation_failed' => '请求参数校验未通过。', 'validation_failed' => '请求参数校验未通过。',
'client_error' => '请求无法完成。', 'client_error' => '请求无法完成。',
'invalid_params' => '请求参数无效。',
'invalid_settings_group' => '不允许读取该配置分组。',
'draw_no_exists' => '期号已存在,请更换期号或流水号。', 'draw_no_exists' => '期号已存在,请更换期号或流水号。',
'draw_not_found' => '期号不存在。',
'draw_timeline_invalid' => '开始时间须早于封盘时间,封盘时间须早于开奖时间。', 'draw_timeline_invalid' => '开始时间须早于封盘时间,封盘时间须早于开奖时间。',
'draw_not_editable' => '仅「未开始」或「可下注且无注单」的期号可编辑时间。', 'draw_not_editable' => '仅「未开始」或「可下注且无注单」的期号可编辑时间。',
'draw_not_deletable' => '仅「未开始」且无注单的期号可删除。', 'draw_not_deletable' => '仅「未开始」且无注单的期号可删除。',
@@ -14,4 +21,37 @@ return [
'too_many_requests' => '请求过于频繁,请稍后再试。', 'too_many_requests' => '请求过于频繁,请稍后再试。',
'server_error' => '服务暂时不可用,请稍后再试。', 'server_error' => '服务暂时不可用,请稍后再试。',
'jackpot_manual_burst_failed' => '手动爆池失败::reason', 'jackpot_manual_burst_failed' => '手动爆池失败::reason',
'config_version_not_draft' => '配置版本不是草稿状态,无法执行该操作。',
'config_version_cannot_delete_active' => '不能删除当前生效中的配置版本。',
'effective_config_not_initialized' => '玩法配置尚未初始化。',
'invalid_or_disabled_currency' => '币种无效或未启用。',
'risk_pool_no_remaining_amount' => '风险池剩余额度不足。',
'reasons' => [
'jackpot_disabled' => '奖池已停用。',
'jackpot_pool_empty' => '奖池余额为 0无法爆池。',
'jackpot_already_burst_for_draw' => '该期已执行过爆池。',
'jackpot_manual_no_first_prize_winners' => '该期没有头奖中奖注单,无法手动爆池。',
'jackpot_already_allocated_for_draw' => '该期已分配过奖池派彩。',
'draw_not_ready_for_jackpot_burst' => '期号尚未进入结算中或已结算,无法手动爆池。',
'draw_result_not_published' => '该期开奖结果尚未发布。',
'settlement_batch_not_found' => '未找到该期的结算批次。',
'settlement_not_pending_review' => '结算批次不在待审核状态。',
'settlement_not_approved' => '结算批次尚未审核通过。',
'draw_has_unsettled_tickets' => '该期仍有未结算注单。',
'batch_not_pending_review' => '开奖结果批次不在待审核状态。',
'draw_not_ready_to_publish' => '期号状态不允许发布开奖结果。',
'batch_result_version_stale' => '开奖结果版本已过期,请刷新后重试。',
'draw_settlement_in_progress' => '该期正在结算中,无法发布结果。',
'draw_already_settled' => '该期已结算完成。',
'draw_pending_result_batch_exists' => '该期已有待审核的开奖批次。',
'draw_not_closeable' => '当前期号状态不允许手动封盘。',
'draw_not_cancelable' => '当前期号状态不允许取消。',
'draw_has_settled_tickets' => '该期已有已结算注单,无法执行此操作。',
'draw_not_runnable' => '当前期号不可执行 RNG 开奖。',
'draw_not_in_cooldown' => '期号不在冷静期,无法重开。',
'adjustment_delta_zero' => '调整金额不能为 0。',
'adjustment_reason_required' => '请填写调整原因。',
'adjustment_would_make_balance_negative' => '调整后奖池余额不能为负数。',
],
]; ];

View File

@@ -2,6 +2,7 @@
return [ return [
'invalid_currency' => '币种参数不合法', 'invalid_currency' => '币种参数不合法',
'order_not_found' => '转账订单不存在',
'1001' => '彩票钱包余额不足', '1001' => '彩票钱包余额不足',
'1002' => '上一笔转账仍在处理中,请稍后重试', '1002' => '上一笔转账仍在处理中,请稍后重试',

View File

@@ -329,10 +329,11 @@ test('admin cannot delete self', function (): void {
$me = AdminUser::query()->where('username', 'self_guard')->firstOrFail(); $me = AdminUser::query()->where('username', 'self_guard')->firstOrFail();
$this->withHeader('Authorization', 'Bearer '.$token) $this->withHeader('Authorization', 'Bearer '.$token)
->withHeader('X-Locale', 'zh')
->deleteJson('/api/v1/admin/admin-users/'.$me->id) ->deleteJson('/api/v1/admin/admin-users/'.$me->id)
->assertStatus(422) ->assertStatus(422)
->assertJsonPath('code', ErrorCode::ValidationFailed->value) ->assertJsonPath('code', ErrorCode::ValidationFailed->value)
->assertJsonPath('msg', '不能删除当前登录账号'); ->assertJsonPath('msg', '不能删除当前登录账号');
}); });
test('admin cannot delete the last super admin', function (): void { test('admin cannot delete the last super admin', function (): void {
@@ -348,9 +349,10 @@ test('admin cannot delete the last super admin', function (): void {
grantSuperAdminRole($s1); grantSuperAdminRole($s1);
$this->withHeader('Authorization', 'Bearer '.$token) $this->withHeader('Authorization', 'Bearer '.$token)
->withHeader('X-Locale', 'zh')
->deleteJson('/api/v1/admin/admin-users/'.$s1->id) ->deleteJson('/api/v1/admin/admin-users/'.$s1->id)
->assertStatus(422) ->assertStatus(422)
->assertJsonPath('msg', '不能删除最后一个超级管理员'); ->assertJsonPath('msg', '不能删除最后一个超级管理员');
$s2 = AdminUser::query()->create([ $s2 = AdminUser::query()->create([
'username' => 'super_two', 'username' => 'super_two',
@@ -362,11 +364,13 @@ test('admin cannot delete the last super admin', function (): void {
grantSuperAdminRole($s2); grantSuperAdminRole($s2);
$this->withHeader('Authorization', 'Bearer '.$token) $this->withHeader('Authorization', 'Bearer '.$token)
->withHeader('X-Locale', 'zh')
->deleteJson('/api/v1/admin/admin-users/'.$s1->id) ->deleteJson('/api/v1/admin/admin-users/'.$s1->id)
->assertOk(); ->assertOk();
$this->withHeader('Authorization', 'Bearer '.$token) $this->withHeader('Authorization', 'Bearer '.$token)
->withHeader('X-Locale', 'zh')
->deleteJson('/api/v1/admin/admin-users/'.$s2->id) ->deleteJson('/api/v1/admin/admin-users/'.$s2->id)
->assertStatus(422) ->assertStatus(422)
->assertJsonPath('msg', '不能删除最后一个超级管理员'); ->assertJsonPath('msg', '不能删除最后一个超级管理员');
}); });

View File

@@ -0,0 +1,30 @@
<?php
use App\Support\ApiMessage;
use Illuminate\Http\Request;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
test('api message resolves runtime reason in zh locale', function (): void {
$request = Request::create('/api/v1/test', 'GET');
$request->attributes->set('lottery_locale', 'zh');
expect(ApiMessage::reason($request, 'draw_not_ready_for_jackpot_burst'))
->toBe('期号尚未进入结算中或已结算,无法手动爆池。');
});
test('api message resolves success ok in en locale', function (): void {
$request = Request::create('/api/v1/test', 'GET');
$request->attributes->set('lottery_locale', 'en');
expect(ApiMessage::successMessage($request))->toBe('OK');
});
test('api message resolves admin key', function (): void {
$request = Request::create('/api/v1/test', 'GET');
$request->attributes->set('lottery_locale', 'zh');
expect(ApiMessage::get($request, 'admin.site_access_denied'))
->toBe('无权访问该站点。');
});