접속 IP 를 제한하는 결제사 API 를 로컬·스테이징에서 연동하려면 서버가 내보내는 요청의 출발지 IP 를 바꿔야 한다. 브라우저 프록시로는 바뀌지 않는 축이라 코어 환경설정으로 도입한다. 게이트는 디버그 모드이며 판정은 OutboundProxy 한 곳이 소유한다. 화면의 조건부 렌더링은 편의일 뿐이라 저장 API 직접 호출을 막지 못하므로, 실질 게이트를 판정 지점에 둔다. 주입·적용 지점은 결과만 소비한다. 적용은 Http::globalOptions 전역 옵션이라 확장의 Http:: 호출까지 함께 경유한다. 외부 연동 규약상 curl 핸들을 직접 다뤄야 하는 확장은 OutboundProxy::curlOptions 로 같은 프록시를 탄다 — KG이니시스 본인인증 승인 요청과 CBT 연결 점검을 이 통로로 편입했다. CBT 의 TCP 443 확인은 원시 소켓으로는 프록시를 태울 수 없어 curl CONNECT_ONLY 로 교체했다. 저장 전 연결 테스트는 저장값이 아니라 제출값을 검사하고, 그 프록시를 거쳤을 때 외부에 보이는 IP 를 함께 보고한다. 운영자가 결제사에 등록할 값이라 저장하고 나서 되짚지 않도록 했다. 적용과 같은 조립을 거치므로 확인한 구성과 저장 후 적용되는 구성이 어긋나지 않는다.
214 lines
6.7 KiB
PHP
214 lines
6.7 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Console\Commands\Traits\HasUnifiedConfirm;
|
|
use App\Models\SystemConfig;
|
|
use App\Repositories\JsonConfigRepository;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
/**
|
|
* 기존 DB 설정을 JSON 파일로 마이그레이션하는 커맨드
|
|
*/
|
|
class MigrateSettingsToJsonCommand extends Command
|
|
{
|
|
use HasUnifiedConfirm;
|
|
|
|
/**
|
|
* 커맨드 시그니처
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $signature = 'settings:migrate-to-json
|
|
{--force : 기존 JSON 파일이 있어도 덮어쓰기}';
|
|
|
|
/**
|
|
* 커맨드 설명
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $description = 'DB의 system_configs 테이블 데이터를 JSON 파일로 마이그레이션합니다.';
|
|
|
|
/**
|
|
* 설정 키와 카테고리 매핑
|
|
*
|
|
* @var array<string, string>
|
|
*/
|
|
private array $categoryMap = [
|
|
// general
|
|
'site_name' => 'general',
|
|
'site_url' => 'general',
|
|
'site_description' => 'general',
|
|
'admin_email' => 'general',
|
|
'timezone' => 'general',
|
|
'language' => 'general',
|
|
'currency' => 'general',
|
|
'maintenance_mode' => 'general',
|
|
'description' => 'general',
|
|
'keywords' => 'general',
|
|
|
|
// security
|
|
'force_https' => 'security',
|
|
'login_attempt_enabled' => 'security',
|
|
'auth_token_lifetime' => 'security',
|
|
'max_login_attempts' => 'security',
|
|
'login_lockout_time' => 'security',
|
|
'two_factor_auth' => 'security',
|
|
'password_min_length' => 'security',
|
|
'require_password_special_char' => 'security',
|
|
|
|
// mail
|
|
'mailer' => 'mail',
|
|
'mail_host' => 'mail',
|
|
'mail_port' => 'mail',
|
|
'mail_username' => 'mail',
|
|
'mail_password' => 'mail',
|
|
'mail_encryption' => 'mail',
|
|
'mail_from_address' => 'mail',
|
|
'mail_from_name' => 'mail',
|
|
|
|
// upload
|
|
'max_file_size' => 'upload',
|
|
'allowed_extensions' => 'upload',
|
|
'image_max_width' => 'upload',
|
|
'image_max_height' => 'upload',
|
|
'image_quality' => 'upload',
|
|
|
|
// seo
|
|
'meta_title_suffix' => 'seo',
|
|
'meta_description' => 'seo',
|
|
'meta_keywords' => 'seo',
|
|
'google_analytics_id' => 'seo',
|
|
'google_site_verification' => 'seo',
|
|
'naver_site_verification' => 'seo',
|
|
|
|
// cache
|
|
'cache_enabled' => 'cache',
|
|
'layout_cache_enabled' => 'cache',
|
|
'layout_cache_ttl' => 'cache',
|
|
'stats_cache_enabled' => 'cache',
|
|
'stats_cache_ttl' => 'cache',
|
|
'seo_cache_enabled' => 'cache',
|
|
'seo_cache_ttl' => 'cache',
|
|
|
|
// debug
|
|
'debug_mode' => 'debug',
|
|
'sql_query_log' => 'debug',
|
|
'log_level' => 'debug',
|
|
'outbound_proxy' => 'debug',
|
|
'outbound_proxy_bypass' => 'debug',
|
|
];
|
|
|
|
/**
|
|
* 키 이름 변환 매핑 (DB 키 -> JSON 키)
|
|
*
|
|
* @var array<string, string>
|
|
*/
|
|
private array $keyMapping = [
|
|
'mail_host' => 'host',
|
|
'mail_port' => 'port',
|
|
'mail_username' => 'username',
|
|
'mail_password' => 'password',
|
|
'mail_encryption' => 'encryption',
|
|
'mail_from_address' => 'from_address',
|
|
'mail_from_name' => 'from_name',
|
|
'cache_enabled' => 'enabled',
|
|
'layout_cache_enabled' => 'layout_enabled',
|
|
'layout_cache_ttl' => 'layout_ttl',
|
|
'stats_cache_enabled' => 'stats_enabled',
|
|
'stats_cache_ttl' => 'stats_ttl',
|
|
'seo_cache_enabled' => 'seo_enabled',
|
|
'seo_cache_ttl' => 'seo_ttl',
|
|
'debug_mode' => 'mode',
|
|
];
|
|
|
|
/**
|
|
* 커맨드를 실행합니다.
|
|
*
|
|
* DI 컨테이너 바인딩 시점 문제로 직접 인스턴스화합니다.
|
|
*/
|
|
public function handle(): int
|
|
{
|
|
$this->info('설정 마이그레이션을 시작합니다...');
|
|
|
|
// system_configs 테이블 존재 확인
|
|
if (! Schema::hasTable('system_configs')) {
|
|
$this->warn('system_configs 테이블이 존재하지 않습니다. 기본값으로 초기화합니다.');
|
|
|
|
$configRepository = new JsonConfigRepository;
|
|
$configRepository->initialize();
|
|
|
|
$this->info('기본 설정 파일이 생성되었습니다.');
|
|
|
|
return Command::SUCCESS;
|
|
}
|
|
|
|
// JsonConfigRepository 직접 인스턴스화 (DI 컨테이너 바인딩 전 실행 가능)
|
|
$configRepository = new JsonConfigRepository;
|
|
|
|
// 기존 JSON 파일 존재 확인
|
|
if (file_exists(storage_path('app/settings/general.json')) && ! $this->option('force')) {
|
|
if (! $this->unifiedConfirm('기존 JSON 설정 파일이 존재합니다. 덮어쓰시겠습니까?', false)) {
|
|
$this->info('마이그레이션이 취소되었습니다.');
|
|
|
|
return Command::SUCCESS;
|
|
}
|
|
}
|
|
|
|
// 기존 설정 조회
|
|
$configs = SystemConfig::all();
|
|
|
|
if ($configs->isEmpty()) {
|
|
$this->warn('마이그레이션할 설정이 없습니다. 기본값으로 초기화합니다.');
|
|
$configRepository->initialize();
|
|
$this->info('기본 설정 파일이 생성되었습니다.');
|
|
|
|
return Command::SUCCESS;
|
|
}
|
|
|
|
$this->info("총 {$configs->count()}개의 설정을 발견했습니다.");
|
|
|
|
// 카테고리별로 그룹화
|
|
$grouped = [];
|
|
foreach ($configs as $config) {
|
|
$category = $this->categoryMap[$config->key] ?? 'general';
|
|
$jsonKey = $this->keyMapping[$config->key] ?? $config->key;
|
|
$grouped[$category][$jsonKey] = $this->parseValue($config->value, $config->type);
|
|
}
|
|
|
|
// JSON 파일로 저장
|
|
$defaults = $configRepository->getDefaults();
|
|
|
|
foreach ($defaults as $category => $defaultSettings) {
|
|
$categorySettings = array_merge(
|
|
$defaultSettings,
|
|
$grouped[$category] ?? []
|
|
);
|
|
|
|
$configRepository->saveCategory($category, $categorySettings);
|
|
$this->info(" ✓ {$category} 카테고리 저장 완료");
|
|
}
|
|
|
|
$this->newLine();
|
|
$this->info('마이그레이션이 완료되었습니다!');
|
|
$this->info('설정 파일 위치: storage/app/settings/');
|
|
|
|
return Command::SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* 문자열 값을 원래 타입으로 파싱합니다.
|
|
*/
|
|
private function parseValue(string $value, string $type): mixed
|
|
{
|
|
return match ($type) {
|
|
'boolean' => filter_var($value, FILTER_VALIDATE_BOOLEAN),
|
|
'integer' => (int) $value,
|
|
'float' => (float) $value,
|
|
'json', 'array' => json_decode($value, true) ?: [],
|
|
default => $value,
|
|
};
|
|
}
|
|
}
|