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:
parent
458efe3e5d
commit
edc3a2c3d7
5 changed files with 109 additions and 9 deletions
|
|
@ -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);
|
||||
|
|
|
|||
101
backend/src/services/cron.js
Normal file
101
backend/src/services/cron.js
Normal 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분 간격)');
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue