feat: podpora high-availability a multi-replica nasazení
- Socket.io Redis adapter pro sdílený stav přes repliky - graceful shutdown serveru - WATCH/MULTI v updateData pro race-condition-safe aktualizace - lease mechanismus pro push reminder (zabrání duplicitnímu odesílání) - k8s/ manifesty pro testovací kind cluster - Dockerfile: opraven EXPOSE port na 3001 - .gitignore: ignorovány Claude pracovní soubory
This commit is contained in:
@@ -154,6 +154,21 @@ function App() {
|
||||
}
|
||||
}, [auth?.login, socket]);
|
||||
|
||||
// Po znovupřipojení socketu znovu vstoupit do místnosti a obnovit data
|
||||
useEffect(() => {
|
||||
const onReconnect = () => {
|
||||
if (auth?.login) socket.emit('join', auth.login);
|
||||
getData({ query: { dayIndex: dayIndexRef.current } }).then(response => {
|
||||
if (response.data) {
|
||||
setData(response.data);
|
||||
setFood(response.data.menus);
|
||||
}
|
||||
});
|
||||
};
|
||||
socket.io.on('reconnect', onReconnect);
|
||||
return () => { socket.io.off('reconnect', onReconnect); };
|
||||
}, [socket, auth?.login]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!auth?.login || !data?.choices) {
|
||||
return
|
||||
|
||||
@@ -8,12 +8,25 @@ if (process.env.NODE_ENV === 'development') {
|
||||
socketPath = undefined;
|
||||
} else {
|
||||
socketUrl = `${globalThis.location.host}`;
|
||||
socketPath = `${globalThis.location.pathname}socket.io`;
|
||||
socketPath = '/socket.io';
|
||||
}
|
||||
|
||||
export const socket = socketio.connect(socketUrl, { path: socketPath, transports: ["websocket"] });
|
||||
export const SocketContext = React.createContext();
|
||||
|
||||
// Prohlížeče throttlují setTimeout v neaktivních tabech, což zdržuje automatické
|
||||
// znovupřipojení socket.io. Po návratu do tabu nebo focusu okna se připojíme hned.
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible' && !socket.connected) {
|
||||
socket.connect();
|
||||
}
|
||||
});
|
||||
window.addEventListener('focus', () => {
|
||||
if (!socket.connected) {
|
||||
socket.connect();
|
||||
}
|
||||
});
|
||||
|
||||
// Konstanty websocket eventů, musí odpovídat těm na serveru!
|
||||
export const EVENT_CONNECT = 'connect';
|
||||
export const EVENT_DISCONNECT = 'disconnect';
|
||||
|
||||
@@ -128,6 +128,13 @@ export default function OrderGroupsPage() {
|
||||
return () => { socket.off(EVENT_MESSAGE); socket.off(EVENT_PENDING_QR); };
|
||||
}, [socket]);
|
||||
|
||||
useEffect(() => {
|
||||
// Po znovupřipojení socketu načteme aktuálně zobrazený den (mohli jsme přijít o živé aktualizace)
|
||||
const onReconnect = () => fetchData(selectedDateRef.current);
|
||||
socket.io.on('reconnect', onReconnect);
|
||||
return () => { socket.io.off('reconnect', onReconnect); };
|
||||
}, [socket]);
|
||||
|
||||
// Navigace mezi dny pomocí klávesových šipek (←/→), obdobně jako na hlavní stránce
|
||||
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
||||
// Ignorujeme, pokud uživatel právě píše do formulářového pole
|
||||
@@ -162,7 +169,6 @@ export default function OrderGroupsPage() {
|
||||
setData(result.data);
|
||||
socket.emit?.('message', result.data as ClientData);
|
||||
}
|
||||
await fetchData();
|
||||
// Sada dnů s objednávkou se mohla změnit (vytvoření/smazání skupiny)
|
||||
fetchOrderDates();
|
||||
return true;
|
||||
|
||||
@@ -8,6 +8,7 @@ export default defineConfig({
|
||||
plugins: [react(), viteTsconfigPaths()],
|
||||
server: {
|
||||
open: true,
|
||||
host: '0.0.0.0',
|
||||
port: 3000,
|
||||
proxy: {
|
||||
'/api': 'http://localhost:3001',
|
||||
|
||||
Reference in New Issue
Block a user