Files
sam-kd/opendart/api/sync_corpcode.php
hskwon aca1767eb9 초기 커밋: 5130 레거시 시스템
- URL 하드코딩 → .env APP_URL 기반 동적 URL로 변경
- DB 연결 하드코딩 → .env 기반으로 변경
- MySQL strict mode DATE 오류 수정
2025-12-10 20:14:31 +09:00

291 lines
10 KiB
PHP

<?php
/**
* Open DART 기업코드 동기화 API (프록시 방식)
*
* 멀티테넌시 환경 지원:
* - 클라이언트의 IP가 아닌 서버의 IP로 Open DART API 호출
* - Open DART에 등록된 서버 IP만 사용하므로 여러 클라이언트 지원 가능
*
* 동작 흐름:
* 클라이언트 → 회사 서버 (이 파일) → Open DART API (ZIP 다운로드) → 회사 서버 (압축 해제 및 JSON 변환) → 클라이언트
*/
header('Content-Type: application/json; charset=utf-8');
// 프록시 헬퍼 함수 로드
require_once __DIR__ . '/proxy_helper.php';
// API Key Load
$apiKeyPath = $_SERVER['DOCUMENT_ROOT'] . '/apikey/opendart.txt';
if (!file_exists($apiKeyPath)) {
echo json_encode(['status' => 'error', 'message' => 'API Key file not found.']);
exit;
}
$apiKey = trim(file_get_contents($apiKeyPath));
if (empty($apiKey)) {
echo json_encode(['status' => 'error', 'message' => 'API Key is empty.']);
exit;
}
// Data Directory
$dataDir = $_SERVER['DOCUMENT_ROOT'] . '/opendart/data';
if (!is_dir($dataDir)) {
if (!mkdir($dataDir, 0777, true)) {
echo json_encode(['status' => 'error', 'message' => 'Failed to create data directory.']);
exit;
}
}
// Check if ZipArchive extension is available
if (!class_exists('ZipArchive')) {
echo json_encode(['status' => 'error', 'message' => 'ZipArchive extension is not available. Please enable php_zip extension.']);
exit;
}
// Open DART URL
$url = "https://opendart.fss.or.kr/api/corpCode.xml?crtfc_key=" . $apiKey;
// Download Zip
$zipFile = $dataDir . '/corpCode.zip';
// Remove old zip file if exists
if (file_exists($zipFile)) {
@unlink($zipFile);
}
$fp = @fopen($zipFile, 'w+');
if (!$fp) {
echo json_encode(['status' => 'error', 'message' => 'Failed to create zip file. Check directory permissions.']);
exit;
}
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 300); // 5 minutes timeout
curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
$effectiveUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
curl_close($ch);
fclose($fp);
if ($httpCode !== 200) {
$errorContent = '';
if (file_exists($zipFile)) {
$errorContent = file_get_contents($zipFile);
@unlink($zipFile);
}
// Check if it's an IP access error from Open DART
if ($errorContent && strpos($errorContent, '접근할 수 없는 IP') !== false || strpos($errorContent, 'Inaccessible IP') !== false) {
// Parse XML error response
$xml = @simplexml_load_string($errorContent);
$errorMessage = '';
$reportedIP = '';
if ($xml && isset($xml->message)) {
$errorMessage = (string)$xml->message;
// Extract IP from message if present
if (preg_match('/\(([0-9\.]+)\)/', $errorMessage, $matches)) {
$reportedIP = $matches[1];
}
} else {
$errorMessage = 'IP 접근이 거부되었습니다.';
}
// Get actual server IP
$actualIP = getServerPublicIP();
$ipErrorMsg = "Open DART IP 접근 오류: $errorMessage";
if ($reportedIP) {
$ipErrorMsg .= "\n\nOpen DART에서 감지한 IP: $reportedIP";
}
$ipErrorMsg .= "\n현재 서버의 공인 IP: $actualIP";
$ipErrorMsg .= "\n\n해결 방법:";
$ipErrorMsg .= "\n1. Open DART 홈페이지(https://opendart.fss.or.kr)에 로그인";
$ipErrorMsg .= "\n2. '회원정보 > API 인증키 관리' 메뉴로 이동";
$ipErrorMsg .= "\n3. '사용IP' 항목에 위의 '현재 서버의 공인 IP'를 등록하거나 수정";
$ipErrorMsg .= "\n4. 변경 사항 저장 후 몇 분 대기 후 다시 시도";
echo json_encode([
'status' => 'error',
'message' => $ipErrorMsg,
'error_type' => 'ip_access_denied',
'reported_ip' => $reportedIP,
'actual_ip' => $actualIP
]);
exit;
}
$debugMsg = "HTTP Code: $httpCode";
if ($curlError) {
$debugMsg .= ", cURL Error: $curlError";
}
if ($effectiveUrl) {
$debugMsg .= ", Effective URL: $effectiveUrl";
}
if ($errorContent) {
$debugMsg .= ", Content Preview: " . substr($errorContent, 0, 500);
}
echo json_encode(['status' => 'error', 'message' => 'Failed to download data. ' . $debugMsg]);
exit;
}
// Verify downloaded file
if (!file_exists($zipFile)) {
echo json_encode(['status' => 'error', 'message' => 'Downloaded file does not exist.']);
exit;
}
$fileSize = filesize($zipFile);
if ($fileSize === false || $fileSize < 100) {
// File too small, likely an error message
$errorContent = file_get_contents($zipFile);
@unlink($zipFile);
echo json_encode(['status' => 'error', 'message' => 'Downloaded file is too small or invalid. Content: ' . substr($errorContent, 0, 500)]);
exit;
}
// Check if file is actually a ZIP file (check magic bytes)
$handle = fopen($zipFile, 'rb');
$magicBytes = fread($handle, 4);
fclose($handle);
// ZIP file magic bytes: PK\x03\x04 or PK\x05\x06 (empty zip) or PK\x07\x08
if (substr($magicBytes, 0, 2) !== 'PK') {
$errorContent = file_get_contents($zipFile);
@unlink($zipFile);
// Check if it's an XML error response from Open DART (IP access error)
if (strpos($errorContent, '<?xml') !== false && (strpos($errorContent, '접근할 수 없는 IP') !== false || strpos($errorContent, 'Inaccessible IP') !== false)) {
$xml = @simplexml_load_string($errorContent);
$errorMessage = '';
$reportedIP = '';
if ($xml && isset($xml->message)) {
$errorMessage = (string)$xml->message;
if (preg_match('/\(([0-9\.]+)\)/', $errorMessage, $matches)) {
$reportedIP = $matches[1];
}
} else {
$errorMessage = 'IP 접근이 거부되었습니다.';
}
// Get actual server IP
$actualIP = getServerPublicIP();
$ipErrorMsg = "Open DART IP 접근 오류: $errorMessage";
if ($reportedIP) {
$ipErrorMsg .= "\n\nOpen DART에서 감지한 IP: $reportedIP";
}
$ipErrorMsg .= "\n현재 서버의 공인 IP: $actualIP";
$ipErrorMsg .= "\n\n해결 방법:";
$ipErrorMsg .= "\n1. Open DART 홈페이지(https://opendart.fss.or.kr)에 로그인";
$ipErrorMsg .= "\n2. '회원정보 > API 인증키 관리' 메뉴로 이동";
$ipErrorMsg .= "\n3. '사용IP' 항목에 위의 '현재 서버의 공인 IP'를 등록하거나 수정";
$ipErrorMsg .= "\n4. 변경 사항 저장 후 몇 분 대기 후 다시 시도";
echo json_encode([
'status' => 'error',
'message' => $ipErrorMsg,
'error_type' => 'ip_access_denied',
'reported_ip' => $reportedIP,
'actual_ip' => $actualIP
]);
exit;
}
echo json_encode(['status' => 'error', 'message' => 'Downloaded file is not a valid ZIP file. Content: ' . substr($errorContent, 0, 500)]);
exit;
}
// Unzip
$zip = new ZipArchive;
$zipResult = $zip->open($zipFile);
if ($zipResult !== TRUE) {
$errorMessages = [
ZipArchive::ER_OK => 'No error',
ZipArchive::ER_MULTIDISK => 'Multi-disk zip archives not supported',
ZipArchive::ER_RENAME => 'Renaming temporary file failed',
ZipArchive::ER_CLOSE => 'Closing zip archive failed',
ZipArchive::ER_SEEK => 'Seek error',
ZipArchive::ER_READ => 'Read error',
ZipArchive::ER_WRITE => 'Write error',
ZipArchive::ER_CRC => 'CRC error',
ZipArchive::ER_ZIPCLOSED => 'Containing zip archive was closed',
ZipArchive::ER_NOENT => 'No such file',
ZipArchive::ER_EXISTS => 'File already exists',
ZipArchive::ER_OPEN => 'Can\'t open file',
ZipArchive::ER_TMPOPEN => 'Failure to create temporary file',
ZipArchive::ER_ZLIB => 'Zlib error',
ZipArchive::ER_MEMORY => 'Memory allocation failure',
ZipArchive::ER_CHANGED => 'Entry has been changed',
ZipArchive::ER_COMPNOTSUPP => 'Compression method not supported',
ZipArchive::ER_EOF => 'Premature EOF',
ZipArchive::ER_INVAL => 'Invalid argument',
ZipArchive::ER_NOZIP => 'Not a zip archive',
ZipArchive::ER_INTERNAL => 'Internal error',
ZipArchive::ER_INCONS => 'Zip archive inconsistent',
ZipArchive::ER_REMOVE => 'Can\'t remove file',
ZipArchive::ER_DELETED => 'Entry has been deleted',
];
$errorMsg = isset($errorMessages[$zipResult]) ? $errorMessages[$zipResult] : "Unknown error (Code: $zipResult)";
echo json_encode(['status' => 'error', 'message' => 'Failed to unzip file. ' . $errorMsg . ' (Error Code: ' . $zipResult . ')']);
exit;
}
// Extract to data directory
if (!$zip->extractTo($dataDir)) {
$zip->close();
echo json_encode(['status' => 'error', 'message' => 'Failed to extract files from zip. Check directory permissions.']);
exit;
}
$zip->close();
// Parse XML
$xmlFile = $dataDir . '/CORPCODE.xml';
if (!file_exists($xmlFile)) {
echo json_encode(['status' => 'error', 'message' => 'XML file not found in zip.']);
exit;
}
$xml = simplexml_load_file($xmlFile);
$jsonArray = [];
foreach ($xml->list as $item) {
$corp_code = (string)$item->corp_code;
$corp_name = (string)$item->corp_name;
$stock_code = (string)$item->stock_code; // optional
$modify_date = (string)$item->modify_date;
$jsonArray[] = [
'corp_code' => $corp_code,
'corp_name' => $corp_name,
'stock_code' => $stock_code,
'modify_date' => $modify_date
];
}
// Save to JSON
$jsonFile = $dataDir . '/corp_codes.json';
if (file_put_contents($jsonFile, json_encode($jsonArray, JSON_UNESCAPED_UNICODE))) {
echo json_encode([
'status' => 'success',
'message' => 'Data synced successfully.',
'count' => count($jsonArray),
'timestamp' => date('Y-m-d H:i:s')
]);
} else {
echo json_encode(['status' => 'error', 'message' => 'Failed to save JSON data.']);
}
?>