자잘한 오류 수정과 스크롤, 헤더 변경완료
This commit is contained in:
@@ -17,19 +17,54 @@ export class OpenApiProxyController {
|
||||
|
||||
console.log(`🌤️ 날씨 조회 요청: ${city}`);
|
||||
|
||||
// 기상청 API Hub 키 확인
|
||||
// 1순위: OpenWeatherMap API (실시간에 가까움, 10분마다 업데이트)
|
||||
const openWeatherKey = process.env.OPENWEATHER_API_KEY;
|
||||
if (openWeatherKey) {
|
||||
try {
|
||||
console.log(`🌍 OpenWeatherMap API 호출: ${city}`);
|
||||
const response = await axios.get('https://api.openweathermap.org/data/2.5/weather', {
|
||||
params: {
|
||||
q: `${city},KR`,
|
||||
appid: openWeatherKey,
|
||||
units: 'metric',
|
||||
lang: 'kr',
|
||||
},
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
const data = response.data;
|
||||
const weatherData = {
|
||||
city: data.name,
|
||||
country: data.sys.country,
|
||||
temperature: Math.round(data.main.temp),
|
||||
feelsLike: Math.round(data.main.feels_like),
|
||||
humidity: data.main.humidity,
|
||||
pressure: data.main.pressure,
|
||||
weatherMain: data.weather[0].main,
|
||||
weatherDescription: data.weather[0].description,
|
||||
weatherIcon: data.weather[0].icon,
|
||||
windSpeed: Math.round(data.wind.speed * 10) / 10,
|
||||
clouds: data.clouds.all,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
console.log(`✅ OpenWeatherMap 날씨 조회 성공: ${weatherData.city} ${weatherData.temperature}°C`);
|
||||
res.json({ success: true, data: weatherData });
|
||||
return;
|
||||
} catch (error) {
|
||||
console.warn('⚠️ OpenWeatherMap API 실패, 기상청 API로 폴백:', error instanceof Error ? error.message : error);
|
||||
}
|
||||
}
|
||||
|
||||
// 2순위: 기상청 API Hub (매시간 정시 데이터)
|
||||
const apiKey = process.env.KMA_API_KEY;
|
||||
|
||||
// API 키가 없으면 테스트 모드로 실시간 날씨 제공
|
||||
// API 키가 없으면 오류 반환
|
||||
if (!apiKey) {
|
||||
console.log('⚠️ 기상청 API 키가 없습니다. 테스트 데이터를 반환합니다.');
|
||||
|
||||
const regionCode = getKMARegionCode(city as string);
|
||||
const weatherData = generateRealisticWeatherData(regionCode?.name || (city as string));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: weatherData,
|
||||
console.log('⚠️ 기상청 API 키가 설정되지 않았습니다.');
|
||||
res.status(503).json({
|
||||
success: false,
|
||||
message: '기상청 API 키가 설정되지 않았습니다. 관리자에게 문의하세요.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -48,32 +83,39 @@ export class OpenApiProxyController {
|
||||
// 기상청 API Hub 사용 (apihub.kma.go.kr)
|
||||
const now = new Date();
|
||||
|
||||
// 기상청 데이터는 매시간 정시(XX:00)에 발표되고 약 10분 후 조회 가능
|
||||
// 현재 시각이 XX:10 이전이면 이전 시간 데이터 조회
|
||||
const minute = now.getMinutes();
|
||||
let targetTime = new Date(now);
|
||||
// 한국 시간(KST = UTC+9)으로 변환
|
||||
const kstOffset = 9 * 60 * 60 * 1000; // 9시간을 밀리초로
|
||||
const kstNow = new Date(now.getTime() + kstOffset);
|
||||
|
||||
if (minute < 10) {
|
||||
// 아직 이번 시간 데이터가 업데이트되지 않음 → 이전 시간으로
|
||||
targetTime = new Date(now.getTime() - 60 * 60 * 1000);
|
||||
}
|
||||
// 기상청 지상관측 데이터는 매시간 정시(XX:00)에 발표
|
||||
// 가장 최근의 정시 데이터를 가져오기 위해 현재 시간의 정시로 설정
|
||||
const targetTime = new Date(kstNow);
|
||||
|
||||
// tm 파라미터: YYYYMMDDHH00 형식 (정시만 조회)
|
||||
const year = targetTime.getFullYear();
|
||||
const month = String(targetTime.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(targetTime.getDate()).padStart(2, '0');
|
||||
const hour = String(targetTime.getHours()).padStart(2, '0');
|
||||
const year = targetTime.getUTCFullYear();
|
||||
const month = String(targetTime.getUTCMonth() + 1).padStart(2, '0');
|
||||
const day = String(targetTime.getUTCDate()).padStart(2, '0');
|
||||
const hour = String(targetTime.getUTCHours()).padStart(2, '0');
|
||||
const tm = `${year}${month}${day}${hour}00`;
|
||||
|
||||
console.log(`🕐 현재 시각(KST): ${kstNow.toISOString().slice(0, 16).replace('T', ' ')}, 조회 시각: ${tm}`);
|
||||
|
||||
// 기상청 API Hub - 지상관측시간자료
|
||||
const url = 'https://apihub.kma.go.kr/api/typ01/url/kma_sfctm2.php';
|
||||
// 기상청 API Hub - 지상관측시간자료 (시간 범위 조회로 최신 데이터 확보)
|
||||
// sfctm3: 시간 범위 조회 가능 (tm1~tm2)
|
||||
const url = 'https://apihub.kma.go.kr/api/typ01/url/kma_sfctm3.php';
|
||||
|
||||
// 최근 1시간 범위 조회 (현재 시간 - 1시간 ~ 현재 시간) - KST 기준
|
||||
const tm1Time = new Date(kstNow.getTime() - 60 * 60 * 1000); // 1시간 전
|
||||
const tm1 = `${tm1Time.getUTCFullYear()}${String(tm1Time.getUTCMonth() + 1).padStart(2, '0')}${String(tm1Time.getUTCDate()).padStart(2, '0')}${String(tm1Time.getUTCHours()).padStart(2, '0')}00`;
|
||||
const tm2 = tm; // 현재 시간
|
||||
|
||||
console.log(`📡 기상청 API Hub 호출: ${regionCode.name} (관측소: ${regionCode.stnId}, 시간: ${tm})`);
|
||||
console.log(`📡 기상청 API Hub 호출: ${regionCode.name} (관측소: ${regionCode.stnId}, 기간: ${tm1}~${tm2})`);
|
||||
|
||||
const response = await axios.get(url, {
|
||||
params: {
|
||||
tm: tm,
|
||||
stn: 0, // 0 = 전체 관측소 데이터 조회
|
||||
tm1: tm1,
|
||||
tm2: tm2,
|
||||
stn: regionCode.stnId, // 특정 관측소만 조회
|
||||
authKey: apiKey,
|
||||
help: 0,
|
||||
disp: 1,
|
||||
@@ -95,30 +137,36 @@ export class OpenApiProxyController {
|
||||
} catch (error: unknown) {
|
||||
console.error('❌ 날씨 조회 실패:', error);
|
||||
|
||||
// API 호출 실패 시 자동으로 테스트 모드로 전환
|
||||
// API 호출 실패 시 명확한 오류 메시지 반환
|
||||
if (axios.isAxiosError(error)) {
|
||||
const status = error.response?.status;
|
||||
|
||||
// 모든 오류 → 테스트 데이터 반환
|
||||
console.log('⚠️ API 오류 발생. 테스트 데이터를 반환합니다.');
|
||||
const { city = '서울' } = req.query;
|
||||
const regionCode = getKMARegionCode(city as string);
|
||||
const weatherData = generateRealisticWeatherData(regionCode?.name || (city as string));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: weatherData,
|
||||
});
|
||||
if (status === 401 || status === 403) {
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
message: '기상청 API 인증에 실패했습니다. API 키를 확인하세요.',
|
||||
});
|
||||
} else if (status === 404) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: '기상청 API에서 데이터를 찾을 수 없습니다.',
|
||||
});
|
||||
} else if (error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT') {
|
||||
res.status(504).json({
|
||||
success: false,
|
||||
message: '기상청 API 연결 시간이 초과되었습니다. 잠시 후 다시 시도하세요.',
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '기상청 API 호출 중 오류가 발생했습니다.',
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 예상치 못한 오류 → 테스트 데이터 반환
|
||||
console.log('⚠️ 예상치 못한 오류. 테스트 데이터를 반환합니다.');
|
||||
const { city = '서울' } = req.query;
|
||||
const regionCode = getKMARegionCode(city as string);
|
||||
const weatherData = generateRealisticWeatherData(regionCode?.name || (city as string));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: weatherData,
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '날씨 정보를 가져오는 중 예상치 못한 오류가 발생했습니다.',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -169,15 +217,19 @@ export class OpenApiProxyController {
|
||||
} catch (error: unknown) {
|
||||
console.error('❌ 환율 조회 실패:', error);
|
||||
|
||||
// API 호출 실패 시 실제 근사값 반환
|
||||
console.log('⚠️ API 오류 발생. 근사값을 반환합니다.');
|
||||
const { base = 'KRW', target = 'USD' } = req.query;
|
||||
const approximateRate = generateRealisticExchangeRate(base as string, target as string);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: approximateRate,
|
||||
});
|
||||
// API 호출 실패 시 명확한 오류 메시지 반환
|
||||
if (axios.isAxiosError(error)) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '환율 정보를 가져오는 중 오류가 발생했습니다.',
|
||||
error: error.message,
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '환율 정보를 가져오는 중 예상치 못한 오류가 발생했습니다.',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,19 +657,26 @@ function parseKMAHubWeatherData(data: any, regionCode: { name: string; stnId: st
|
||||
throw new Error('날씨 데이터를 파싱할 수 없습니다.');
|
||||
}
|
||||
|
||||
// 요청한 관측소(stnId)의 데이터 찾기
|
||||
const targetLine = lines.find((line: string) => {
|
||||
// 요청한 관측소(stnId)의 모든 데이터 찾기 (시간 범위 조회 시 여러 줄 반환됨)
|
||||
const targetLines = lines.filter((line: string) => {
|
||||
const cols = line.trim().split(/\s+/);
|
||||
return cols[1] === regionCode.stnId; // STN 컬럼 (인덱스 1)
|
||||
});
|
||||
|
||||
if (!targetLine) {
|
||||
if (targetLines.length === 0) {
|
||||
throw new Error(`${regionCode.name} 관측소 데이터를 찾을 수 없습니다.`);
|
||||
}
|
||||
|
||||
// 가장 최근 데이터 선택 (마지막 줄)
|
||||
const targetLine = targetLines[targetLines.length - 1];
|
||||
|
||||
// 데이터 라인 파싱 (공백으로 구분)
|
||||
const values = targetLine.trim().split(/\s+/);
|
||||
|
||||
// 관측 시각 로깅
|
||||
const obsTime = values[0]; // YYMMDDHHMI
|
||||
console.log(`🕐 관측 시각: ${obsTime} (${regionCode.name})`);
|
||||
|
||||
// 기상청 API Hub 데이터 형식 (실제 응답 기준):
|
||||
// [0]YYMMDDHHMI [1]STN [2]WD [3]WS [4]GST_WD [5]GST_WS [6]GST_TM [7]PA [8]PS [9]PT [10]PR [11]TA [12]TD [13]HM [14]PV [15]RN ...
|
||||
const temperature = parseFloat(values[11]) || 0; // TA: 기온 (인덱스 11)
|
||||
|
||||
Reference in New Issue
Block a user