Compare commits
20 Commits
2ca6683a96
...
675af9f33c
| Author | SHA1 | Date | |
|---|---|---|---|
| 675af9f33c | |||
| cba5e3b86e | |||
| ef4ae0b5f2 | |||
| 465a82093e | |||
| 5b83c30327 | |||
| 7deb85c2b4 | |||
| 362346f0d6 | |||
| eadc19ccc7 | |||
| 178c2a79a7 | |||
| d70b756308 | |||
| 12a10a1210 | |||
| d631384e6b | |||
| 843c763afe | |||
| b5c12928eb | |||
| c129af9a9b | |||
| 388ba4dddb | |||
| 8948631454 | |||
| 28d7592b20 | |||
| 233f502d15 | |||
| 097af4e624 |
37
.gitignore
vendored
37
.gitignore
vendored
@@ -51,7 +51,7 @@ Dockerfile
|
||||
#-------------------------
|
||||
# CI ignore
|
||||
#-------------------------
|
||||
app/Config/App.php
|
||||
#app/Config/App.php
|
||||
|
||||
#-------------------------
|
||||
# Temporary Files
|
||||
@@ -71,6 +71,13 @@ writable/uploads/*
|
||||
writable/debugbar/*
|
||||
!writable/debugbar/index.html
|
||||
|
||||
# Ignore writable but keep directory structure
|
||||
!/writable/logs/.gitkeep
|
||||
!/writable/cache/.gitkeep
|
||||
!/writable/session/.gitkeep
|
||||
!/writable/debugbar/.gitkeep
|
||||
!/writable/uploads/.gitkeep
|
||||
|
||||
php_errors.log
|
||||
|
||||
#-------------------------
|
||||
@@ -136,3 +143,31 @@ _modules/*
|
||||
.history/
|
||||
|
||||
.README
|
||||
|
||||
# 1. 민감한 환경 설정 파일 (필수)
|
||||
# 실제 환경 변수 값이 담긴 파일은 Git에 절대 포함하지 않습니다.
|
||||
#/.env
|
||||
|
||||
# 2. Composer 종속성 (필수)
|
||||
# CI/CD에서 'composer install'로 재설치합니다.
|
||||
/vendor/
|
||||
|
||||
# 3. CI4가 생성하는 런타임 파일 (필수)
|
||||
# 캐시, 로그, 세션 등은 서버에서 생성 및 관리되어야 합니다.
|
||||
/writable/cache/*
|
||||
/writable/logs/*
|
||||
/writable/session/*
|
||||
|
||||
# 4. IDE 및 OS 생성 파일
|
||||
# 개발 환경에서만 필요한 파일 (Windows/MacOS/Linux 등)
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
.DS_Store
|
||||
|
||||
# 5. 빌드 및 테스트 부산물 (선택적)
|
||||
# 특정 IDE나 빌드 도구가 생성하는 파일은 추가합니다.
|
||||
/build/
|
||||
/dist/
|
||||
/node_modules/
|
||||
.env
|
||||
|
||||
107
app/Commands/NaverWorker.php
Normal file
107
app/Commands/NaverWorker.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace App\Commands;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
|
||||
class NaverWorker extends BaseCommand
|
||||
{
|
||||
protected $group = 'Workers';
|
||||
protected $name = 'naver:worker';
|
||||
protected $description = 'Process Naver verification requests from Redis queue with retry logic.';
|
||||
|
||||
// 최대 재시도 횟수 정의
|
||||
private const MAX_RETRIES = 3;
|
||||
private const RETRY_DELAY = 60; // 초 단위 (60초 지연 후 재시도)
|
||||
|
||||
public function run(array $params)
|
||||
{
|
||||
$redis = new \Redis();
|
||||
// 환경 변수를 사용하도록 변경 (php_worker service의 env 설정 활용)
|
||||
$redisHost = getenv('REDIS_HOST') ?: 'redis';
|
||||
$redisPort = getenv('REDIS_PORT') ?: 6379;
|
||||
|
||||
$redis->connect($redisHost, $redisPort);
|
||||
|
||||
CLI::write('Worker started. Listening on naver:queue...');
|
||||
|
||||
while (true) {
|
||||
// 메인 큐 및 재시도 큐에서 데이터 꺼내기 (Blocking Pop, 5초 타임아웃)
|
||||
// LIFO (lPush/brPop) 사용 시: ['naver:queue:retry', 'naver:queue']
|
||||
$item = $redis->brPop(['naver:worker_queue'], 5);
|
||||
|
||||
if ($item) {
|
||||
$payload = json_decode($item[1], true);
|
||||
$this->process($redis, $payload); // Redis 객체를 process에 전달
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function process(\Redis $redis, $payload)
|
||||
{
|
||||
$articleNum = $payload['articleNumbr'];
|
||||
$requestType = $payload['reqeustType'];
|
||||
$retryCount = $payload['retry_count'] ?? 0;
|
||||
|
||||
CLI::write("Processing {$requestType} for {$articleNum} (Attempt: " . ($retryCount + 1) . ")");
|
||||
|
||||
if (!in_array($requestType, ['REG', 'MOD'])) {
|
||||
CLI::write("Skipping non-verification request {$requestType} for {$articleNum}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 네이버 CP API 호출
|
||||
$client = \Config\Services::curlrequest([
|
||||
'timeout' => 10,
|
||||
// ... (인증 헤더 설정 등 이전 답변의 보안 관련 항목 추가)
|
||||
]);
|
||||
$url = "https://네이버CP/kiso/center/verification-article/{$articleNum}";
|
||||
|
||||
try {
|
||||
$response = $client->get($url);
|
||||
$statusCode = $response->getStatusCode();
|
||||
|
||||
if ($statusCode >= 200 && $statusCode < 300) {
|
||||
// 2. 성공 처리
|
||||
CLI::write("✅ SUCCESS: {$requestType} for {$articleNum} (Status: {$statusCode})");
|
||||
// 성공 시 DB에 결과 업데이트 등 후속 작업 진행
|
||||
} else {
|
||||
// 3. API 실패 (4xx, 5xx 에러)
|
||||
$this->handleFailure($redis, $payload, $retryCount, "API returned Status: {$statusCode}");
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 4. 네트워크/타임아웃 오류
|
||||
$this->handleFailure($redis, $payload, $retryCount, "Network Error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function handleFailure(\Redis $redis, $payload, $retryCount, $reason)
|
||||
{
|
||||
$articleNum = $payload['articleNumbr'];
|
||||
|
||||
if ($retryCount < self::MAX_RETRIES) {
|
||||
// 5. 재시도 로직: 재시도 횟수 증가 및 큐에 다시 푸시
|
||||
$payload['retry_count'] = $retryCount + 1;
|
||||
|
||||
// 지연된 재시도를 위해 Sorted Set (ZADD) 또는 별도의 큐 사용을 고려할 수 있으나,
|
||||
// 여기서는 단순성을 위해 즉시 재시도 큐에 넣고 sleep을 사용하는 방식을 가정합니다.
|
||||
// **주의: 실제 프로덕션 환경에서는 `Redis ZSET`을 사용하여 지연된 작업을 처리하는 것이 더 일반적입니다.**
|
||||
|
||||
// 단순 재시도 큐 (ZSET을 사용하지 않는 경우)
|
||||
$redis->lPush('naver:queue:retry', json_encode($payload));
|
||||
|
||||
// CLI 환경에서 재시도 간 지연 시간을 강제 (Blocking pop을 쓰므로 실제 지연은 다음 worker 실행 시 발생)
|
||||
CLI::error("Attempt {$retryCount} FAILED for {$articleNum}. Reason: {$reason}. Retrying later.");
|
||||
|
||||
} else {
|
||||
// 6. 최종 실패 처리: DLQ로 이동
|
||||
$payload['fail_reason'] = $reason;
|
||||
$payload['fail_time'] = date('Y-m-d H:i:s');
|
||||
|
||||
$redis->lPush('naver:queue:dlq', json_encode($payload));
|
||||
|
||||
CLI::error("❌ CRITICAL FAIL: {$articleNum} moved to DLQ after " . self::MAX_RETRIES . " attempts.");
|
||||
}
|
||||
}
|
||||
}
|
||||
202
app/Config/App.php
Normal file
202
app/Config/App.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
namespace Config;
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
|
||||
class App extends BaseConfig
|
||||
{
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Base Site URL
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* URL to your CodeIgniter root. Typically, this will be your base URL,
|
||||
* WITH a trailing slash:
|
||||
*
|
||||
* E.g., http://example.com/
|
||||
*/
|
||||
public string $baseURL = 'http://test2-admin.confirms.co.kr';
|
||||
|
||||
/**
|
||||
* Allowed Hostnames in the Site URL other than the hostname in the baseURL.
|
||||
* If you want to accept multiple Hostnames, set this.
|
||||
*
|
||||
* E.g.,
|
||||
* When your site URL ($baseURL) is 'http://example.com/', and your site
|
||||
* also accepts 'http://media.example.com/' and 'http://accounts.example.com/':
|
||||
* ['media.example.com', 'accounts.example.com']
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
public array $allowedHostnames = [];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Index File
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Typically, this will be your `index.php` file, unless you've renamed it to
|
||||
* something else. If you have configured your web server to remove this file
|
||||
* from your site URIs, set this variable to an empty string.
|
||||
*/
|
||||
public string $indexPage = '';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* URI PROTOCOL
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* This item determines which server global should be used to retrieve the
|
||||
* URI string. The default setting of 'REQUEST_URI' works for most servers.
|
||||
* If your links do not seem to work, try one of the other delicious flavors:
|
||||
*
|
||||
* 'REQUEST_URI': Uses $_SERVER['REQUEST_URI']
|
||||
* 'QUERY_STRING': Uses $_SERVER['QUERY_STRING']
|
||||
* 'PATH_INFO': Uses $_SERVER['PATH_INFO']
|
||||
*
|
||||
* WARNING: If you set this to 'PATH_INFO', URIs will always be URL-decoded!
|
||||
*/
|
||||
public string $uriProtocol = 'REQUEST_URI';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Allowed URL Characters
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This lets you specify which characters are permitted within your URLs.
|
||||
| When someone tries to submit a URL with disallowed characters they will
|
||||
| get a warning message.
|
||||
|
|
||||
| As a security measure you are STRONGLY encouraged to restrict URLs to
|
||||
| as few characters as possible.
|
||||
|
|
||||
| By default, only these are allowed: `a-z 0-9~%.:_-`
|
||||
|
|
||||
| Set an empty string to allow all characters -- but only if you are insane.
|
||||
|
|
||||
| The configured value is actually a regular expression character group
|
||||
| and it will be used as: '/\A[<permittedURIChars>]+\z/iu'
|
||||
|
|
||||
| DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!!
|
||||
|
|
||||
*/
|
||||
public string $permittedURIChars = 'a-z 0-9~%.:_\-';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Default Locale
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* The Locale roughly represents the language and location that your visitor
|
||||
* is viewing the site from. It affects the language strings and other
|
||||
* strings (like currency markers, numbers, etc), that your program
|
||||
* should run under for this request.
|
||||
*/
|
||||
public string $defaultLocale = 'en';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Negotiate Locale
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* If true, the current Request object will automatically determine the
|
||||
* language to use based on the value of the Accept-Language header.
|
||||
*
|
||||
* If false, no automatic detection will be performed.
|
||||
*/
|
||||
public bool $negotiateLocale = false;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Supported Locales
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* If $negotiateLocale is true, this array lists the locales supported
|
||||
* by the application in descending order of priority. If no match is
|
||||
* found, the first locale will be used.
|
||||
*
|
||||
* IncomingRequest::setLocale() also uses this list.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
public array $supportedLocales = ['en'];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Application Timezone
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* The default timezone that will be used in your application to display
|
||||
* dates with the date helper, and can be retrieved through app_timezone()
|
||||
*
|
||||
* @see https://www.php.net/manual/en/timezones.php for list of timezones
|
||||
* supported by PHP.
|
||||
*/
|
||||
public string $appTimezone = 'UTC';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Default Character Set
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* This determines which character set is used by default in various methods
|
||||
* that require a character set to be provided.
|
||||
*
|
||||
* @see http://php.net/htmlspecialchars for a list of supported charsets.
|
||||
*/
|
||||
public string $charset = 'UTF-8';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Force Global Secure Requests
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* If true, this will force every request made to this application to be
|
||||
* made via a secure connection (HTTPS). If the incoming request is not
|
||||
* secure, the user will be redirected to a secure version of the page
|
||||
* and the HTTP Strict Transport Security (HSTS) header will be set.
|
||||
*/
|
||||
public bool $forceGlobalSecureRequests = false;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Reverse Proxy IPs
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* If your server is behind a reverse proxy, you must whitelist the proxy
|
||||
* IP addresses from which CodeIgniter should trust headers such as
|
||||
* X-Forwarded-For or Client-IP in order to properly identify
|
||||
* the visitor's IP address.
|
||||
*
|
||||
* You need to set a proxy IP address or IP address with subnets and
|
||||
* the HTTP header for the client IP address.
|
||||
*
|
||||
* Here are some examples:
|
||||
* [
|
||||
* '10.0.1.200' => 'X-Forwarded-For',
|
||||
* '192.168.5.0/24' => 'X-Real-IP',
|
||||
* ]
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public array $proxyIPs = [];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Content Security Policy
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Enables the Response's Content Secure Policy to restrict the sources that
|
||||
* can be used for images, scripts, CSS files, audio, video, etc. If enabled,
|
||||
* the Response object will populate default values for the policy from the
|
||||
* `ContentSecurityPolicy.php` file. Controllers can always add to those
|
||||
* restrictions at run time.
|
||||
*
|
||||
* For a better understanding of CSP, see these documents:
|
||||
*
|
||||
* @see http://www.html5rocks.com/en/tutorials/security/content-security-policy/
|
||||
* @see http://www.w3.org/TR/CSP/
|
||||
*/
|
||||
public bool $CSPEnabled = false;
|
||||
}
|
||||
@@ -21,7 +21,7 @@ class Cache extends BaseConfig
|
||||
* The name of the preferred handler that should be used. If for some reason
|
||||
* it is not available, the $backupHandler will be used in its place.
|
||||
*/
|
||||
public string $handler = 'file';
|
||||
public string $handler = 'redis';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
@@ -114,13 +114,7 @@ class Cache extends BaseConfig
|
||||
*
|
||||
* @var array{host?: string, password?: string|null, port?: int, timeout?: int, database?: int}
|
||||
*/
|
||||
public array $redis = [
|
||||
'host' => '127.0.0.1',
|
||||
'password' => null,
|
||||
'port' => 6379,
|
||||
'timeout' => 0,
|
||||
'database' => 0,
|
||||
];
|
||||
public array $redis = [];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
@@ -159,4 +153,22 @@ class Cache extends BaseConfig
|
||||
* @var bool|list<string>
|
||||
*/
|
||||
public $cacheQueryString = false;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// Redis 설정에 .env 값을 할당 (이전 논의된 Docker 호스트 이름 'redis' 사용)
|
||||
$this->redis = [
|
||||
'host' => env('redis.default.host', '127.0.0.1'),
|
||||
'password' => env('redis.default.password', null),
|
||||
'port' => (int)env('redis.default.port', 6379),
|
||||
'timeout' => (int)env('redis.default.timeout', 0),
|
||||
'database' => (int)env('redis.default.database', 0)
|
||||
];
|
||||
|
||||
// 필요하다면, 이 생성자에서 $handler나 $backupHandler 같은 다른 설정도
|
||||
// 환경 변수에 따라 동적으로 설정할 수 있습니다.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ class Filters extends BaseFilters
|
||||
'pagecache' => PageCache::class,
|
||||
'performance' => PerformanceMetrics::class,
|
||||
'auth' => \App\Filters\AuthCheck::class,
|
||||
'jsInjector' => \App\Filters\JavascriptInjector::class,
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -84,13 +85,15 @@ class Filters extends BaseFilters
|
||||
'index.php/login/*', // /index.php/login/*
|
||||
'register', // 회원가입 등
|
||||
'register/*',
|
||||
'api/*', // 필요하면 API는 예외
|
||||
'kiso/*', // 필요하면 API는 예외
|
||||
],
|
||||
],
|
||||
],
|
||||
'after' => [
|
||||
// 'honeypot',
|
||||
// 'secureheaders',
|
||||
'jsInjector', // 모든 페이지 응답 후에 실행
|
||||
'toolbar',
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
@@ -209,3 +209,16 @@ $routes->group('manage', ['namespace' => 'App\Controllers\Manage'], function ($r
|
||||
* 로그인 API
|
||||
*/
|
||||
$routes->post('/login/chkLogin', 'Login::chkLogin');
|
||||
|
||||
|
||||
/*
|
||||
* --------------------------------------------------------------------
|
||||
* Additional Routing
|
||||
* --------------------------------------------------------------------
|
||||
*
|
||||
* 이 영역에서 다른 라우트 파일을 로드할 수 있습니다.
|
||||
*/
|
||||
|
||||
if (is_file($filepath = APPPATH . 'Config/Routes/Api.php')) {
|
||||
require $filepath;
|
||||
}
|
||||
|
||||
10
app/Config/Routes/Api.php
Normal file
10
app/Config/Routes/Api.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
// Routes 변수는 반드시 use 해야 합니다.
|
||||
use CodeIgniter\Router\RouteCollection;
|
||||
|
||||
/** @var RouteCollection $routes */
|
||||
|
||||
$routes->group('kiso', function(RouteCollection $routes) {
|
||||
$routes->match(['get', 'post'], 'api/vrfcReq', 'KisoController::vrfcReq');
|
||||
});
|
||||
80
app/Controllers/KisoController.php
Normal file
80
app/Controllers/KisoController.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\Response;
|
||||
|
||||
class KisoController extends BaseController
|
||||
{
|
||||
/** @var RequestInterface */
|
||||
protected $request;
|
||||
|
||||
/** @var ResponseInterface */
|
||||
protected $response;
|
||||
|
||||
public function vrfcReq()
|
||||
{
|
||||
// 1. 요청 방식에 따라 데이터 파싱
|
||||
if ( $this->request->is('post') ) {
|
||||
// POST 방식: JSON Body에서 데이터 가져오기
|
||||
$data = $this->request->getJSON(true);
|
||||
} else if ( $this->request->is('get') ) {
|
||||
// GET 방식: Query Parameter에서 데이터 가져오기
|
||||
$data = $this->request->getGet();
|
||||
} else {
|
||||
// 지원하지 않는 메소드 처리 (예: PUT, DELETE 등)
|
||||
return $this->response->setStatusCode(Response::HTTP_METHOD_NOT_ALLOWED)
|
||||
->setJSON(['resultCode' => 'E005', 'resultMessage' => 'Method not allowed. Use GET or POST.']);
|
||||
}
|
||||
|
||||
// 2. 필수 항목 검증
|
||||
$requiredKeys = ['articleNumber', 'requestType', 'requestDatetime'];
|
||||
|
||||
foreach ($requiredKeys as $key) {
|
||||
// 파싱된 데이터($data) 내에 키가 없거나 값이 비어있는지 확인
|
||||
if (empty($data[$key])) {
|
||||
return $this->response->setStatusCode(Response::HTTP_BAD_REQUEST)
|
||||
->setJSON([
|
||||
'resultCode' => 'E001',
|
||||
'resultMessage' => "Missing required parameter: {$key}"
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Redis 연결 및 예외 처리
|
||||
// 3. Redis 연결 및 직접 푸시
|
||||
try {
|
||||
$redis = new \Redis();
|
||||
// Docker 환경이므로 host를 'redis'로 설정
|
||||
$success = $redis->connect('redis', 6379);
|
||||
|
||||
if (!$success) {
|
||||
throw new \Exception('Redis connection failed');
|
||||
}
|
||||
|
||||
$redis->select(10); // 10번 DB 선택
|
||||
|
||||
// 데이터 준비
|
||||
$data['retry_count'] = 0;
|
||||
|
||||
// 리스트에 데이터 삽입 (이 명령어가 실행되어야 monitor에 LPUSH가 뜹니다)
|
||||
$redis->lPush('naver:queue', json_encode($data));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Redis Push Error: ' . $e->getMessage());
|
||||
return $this->response->setStatusCode(500)->setJSON([
|
||||
'resultCode' => 'E999',
|
||||
'resultMessage' => 'Redis Connection Error'
|
||||
]);
|
||||
}
|
||||
|
||||
// 4. 성공 응답
|
||||
return $this->response->setStatusCode(Response::HTTP_ACCEPTED) // 202 Accepted
|
||||
->setJSON([
|
||||
'resultCode' => 'S000',
|
||||
'resultMessage' => 'Request successfully queued for processing',
|
||||
'articleNumber' => $data['articleNumber']
|
||||
]);
|
||||
}
|
||||
}
|
||||
39
app/Filters/JavascriptInjector.php
Normal file
39
app/Filters/JavascriptInjector.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use CodeIgniter\Filters\FilterInterface;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
class JavascriptInjector implements FilterInterface
|
||||
{
|
||||
public function before(RequestInterface $request, $arguments = null)
|
||||
{
|
||||
// 실행 전 로직 불필요
|
||||
}
|
||||
|
||||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
|
||||
{
|
||||
// HTML 응답일 때만 실행
|
||||
if (strpos($response->getHeaderLine('Content-Type'), 'text/html') === false) return;
|
||||
|
||||
// .env에서 서버 이름 가져오기 (없으면 'Unknown' 또는 컨테이너ID)
|
||||
$serverAlias = env('APP_SERVER_NAME') ?? gethostname();
|
||||
$envMode = ENVIRONMENT;
|
||||
|
||||
$scriptTag = "
|
||||
<script>
|
||||
var SERVER_INFO = {
|
||||
alias: '{$serverAlias}',
|
||||
env: '{$envMode}'
|
||||
};
|
||||
</script>
|
||||
<script src='/common/js/de.js'></script>";
|
||||
|
||||
$body = $response->getBody();
|
||||
if (strpos($body, '</body>') !== false) {
|
||||
$response->setBody(str_replace('</body>', $scriptTag . '</body>', $body));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,3 +35,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// CI4의 환경 변수(env)를 체크하여 스타일 결정
|
||||
$is_local = (ENVIRONMENT === 'development');
|
||||
$status_color = $is_local ? '#ffc107' : '#007bff'; // 로컬은 노란색, 서버는 파란색
|
||||
?>
|
||||
|
||||
<div class="app-wrapper-footer" style="border-top: 5px solid <?= $status_color ?>;">
|
||||
<div class="app-footer">
|
||||
<div class="app-footer__inner">
|
||||
<div class="app-footer-left">
|
||||
<strong>Current Mode:</strong> <?= strtoupper(ENVIRONMENT) ?>
|
||||
</div>
|
||||
|
||||
<div class="app-footer-right">
|
||||
<small class="text-muted">IP: <?= $_SERVER['SERVER_ADDR'] ?></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
43
public/common/js/de.js
Normal file
43
public/common/js/de.js
Normal file
@@ -0,0 +1,43 @@
|
||||
(function() {
|
||||
// 1. 환경 데이터 가져오기 (없으면 기본값 production)
|
||||
const info = window.SERVER_INFO || { alias: 'UNKNOWN', env: 'production' };
|
||||
|
||||
// 2. 운영 환경이면 실행 중단 (표시 안 함)
|
||||
if (info.env === 'production') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 테스트/로컬 환경일 때 스타일 설정 (강렬한 빨간색)
|
||||
const bgColor = '#dc3545'; // 경고 의미의 빨간색
|
||||
const textColor = '#ffffff';
|
||||
const borderColor = '#a71d2a'; // 더 어두운 빨간색 테두리
|
||||
|
||||
// 4. 상태바 생성 및 스타일 적용
|
||||
const statusDiv = document.createElement('div');
|
||||
Object.assign(statusDiv.style, {
|
||||
position: 'fixed',
|
||||
bottom: '0',
|
||||
left: '0',
|
||||
width: '100%',
|
||||
height: '32px',
|
||||
backgroundColor: bgColor,
|
||||
color: textColor,
|
||||
textAlign: 'center',
|
||||
fontSize: '14px',
|
||||
lineHeight: '32px',
|
||||
fontWeight: '900',
|
||||
zIndex: '2147483647', // 최상단 레이어 보장
|
||||
opacity: '1',
|
||||
pointerEvents: 'none', // 클릭 방해 금지
|
||||
boxShadow: '0 -4px 15px rgba(0,0,0,0.4)',
|
||||
borderTop: `3px solid ${borderColor}`,
|
||||
letterSpacing: '0.5px',
|
||||
fontFamily: 'system-ui, -apple-system, sans-serif'
|
||||
});
|
||||
|
||||
// 5. 출력 문구 (서버 이름 강조)
|
||||
statusDiv.innerHTML = `⚠️ [TEST SERVER] NAME: ${info.alias} | HOST: ${window.location.hostname} ⚠️`;
|
||||
|
||||
// 6. 문서에 추가
|
||||
document.body.appendChild(statusDiv);
|
||||
})();
|
||||
Reference in New Issue
Block a user