feat: 자동갱신 cron + react-router 제거

- node-cron 30분 간격 배송 자동 조회 (DELIVERED 제외)
- 상태 변경 시 로그 기록
- react-router-dom 제거 (다이얼로그 전환으로 불필요)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
caadiq 2026-03-24 19:48:36 +09:00
parent 458efe3e5d
commit edc3a2c3d7
5 changed files with 109 additions and 9 deletions

View file

@ -1,5 +1,6 @@
import { buildApp } from './app.js';
import config from './config/index.js';
import { startCronJobs } from './services/cron.js';
async function start() {
const app = await buildApp();
@ -10,6 +11,8 @@ async function start() {
host: config.server.host,
});
startCronJobs(app);
app.log.info(`서버 시작: http://${config.server.host}:${config.server.port}`);
} catch (err) {
app.log.error(err);

View file

@ -0,0 +1,101 @@
import cron from 'node-cron';
function toMysqlDatetime(isoString) {
if (!isoString) return null;
const d = new Date(isoString);
if (isNaN(d.getTime())) return null;
return d.toISOString().slice(0, 19).replace('T', ' ');
}
export function startCronJobs(fastify) {
// 30분 간격으로 배송중인 택배 자동 조회
cron.schedule('*/30 * * * *', async () => {
try {
const [parcels] = await fastify.db.execute(
"SELECT * FROM parcels WHERE status != 'DELIVERED'"
);
if (parcels.length === 0) return;
fastify.log.info(`[cron] 배송중 택배 ${parcels.length}건 자동 조회 시작`);
for (const parcel of parcels) {
try {
const result = await fastify.tracker.track(
parcel.carrier_id,
parcel.tracking_number
);
if (!result) {
await fastify.db.execute(
'UPDATE parcels SET last_checked_at = NOW() WHERE id = ?',
[parcel.id]
);
continue;
}
const status = result.lastEvent?.status || 'UNKNOWN';
const lastDetail = result.lastEvent?.description || '';
const deliveredAt =
status === 'DELIVERED'
? toMysqlDatetime(result.lastEvent?.time)
: null;
const prevStatus = parcel.status;
await fastify.db.execute(
`UPDATE parcels SET status = ?, last_detail = ?, last_checked_at = NOW(),
delivered_at = COALESCE(?, delivered_at),
sender_name = COALESCE(?, sender_name),
recipient_name = COALESCE(?, recipient_name)
WHERE id = ?`,
[
status,
lastDetail,
deliveredAt,
result.senderName,
result.recipientName,
parcel.id,
]
);
// 이벤트 갱신
await fastify.db.execute(
'DELETE FROM tracking_events WHERE parcel_id = ?',
[parcel.id]
);
for (const event of result.events) {
await fastify.db.execute(
`INSERT INTO tracking_events (parcel_id, status, status_name, description, location, event_time)
VALUES (?, ?, ?, ?, ?, ?)`,
[
parcel.id,
event.status,
event.statusName,
event.description,
event.location,
toMysqlDatetime(event.time),
]
);
}
if (prevStatus !== status) {
fastify.log.info(
`[cron] ${parcel.carrier_name} ${parcel.tracking_number}: ${prevStatus}${status}`
);
}
} catch (err) {
fastify.log.warn(
`[cron] 조회 실패 (${parcel.tracking_number}): ${err.message}`
);
}
}
fastify.log.info(`[cron] 자동 조회 완료`);
} catch (err) {
fastify.log.error(`[cron] 오류: ${err.message}`);
}
});
fastify.log.info('[cron] 자동 조회 스케줄러 시작 (30분 간격)');
}

View file

@ -171,10 +171,9 @@ CREATE TABLE tracking_events (
- React Query로 데이터 페칭 (택배 목록, 상세, 택배사 목록)
- 등록/수정/삭제/새로고침 mutation + 캐시 무효화
- 택배사 로고를 RustFS URL에서 로드
- [ ] 자동갱신 cron 서비스 (node-cron)
- 배송 중인 택배: 30분 간격 자동 조회
- 배송 완료된 택배: 조회 중단
- 상태 변경 감지 시 알림 트리거
- [x] 자동갱신 cron 서비스 (node-cron, 30분 간격)
- 배송 완료되지 않은 택배만 자동 조회 (DELIVERED 제외)
- 상태 변경 시 로그 기록
- [ ] 알림 서비스
- Discord Webhook 또는 Telegram Bot API
- 설정 가능한 Webhook URL (.env)

View file

@ -16,7 +16,7 @@
"lucide-react": "^0.344.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.3",
"zustand": "^5.0.9"
},
"devDependencies": {

View file

@ -1,6 +1,5 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import App from "./App";
import "./index.css";
@ -18,9 +17,7 @@ const queryClient = new QueryClient({
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<App />
</BrowserRouter>
<App />
</QueryClientProvider>
</React.StrictMode>
);