๋ฐ๋ ค๊ฒฌ MBTI ๊ฒ์ฌ๋ฅผ ํตํ ์ปค๋ฎค๋ํฐ ์น ์๋น์ค
๐ ์ฌ์ดํธ : https://withdog.me/
๐ ์ ํ๋ธ
1๋ถ - ๋ฉ์ธ & ํ์๊ฐ์ , ๋ก๊ทธ์ธ & MBTI๊ฒ์ฌ
2๋ถ - ๐ ์ฑํ โ
3๋ถ - ๊ฒ์๊ธ ์์ฑ
โจ ํ๋ก์ ํธ ๋ค์ด๊ฐ๊ธฐ ๐ (1) ์์ฒด ํ๋ก์ ํธ with-dog UI
โจ ์ฑํ ๊ธฐ๋ฅ ๊ตฌํ ๐ (2) ์์ฒด ํ๋ก์ ํธ with-dog ์ฑํ ๊ตฌํ
ํ๋ก์ ํธ ์ฑํ ๊ธฐ๋ฅ ๊ตฌํ์ ์์ Websocket๊ณผ Socket.IO ๊ฐ๋ ์๊ณ ์ตํ๊ธฐ. ์ค์ต ํ๋ก์ ํธ !
1. JavaScript + Websocket ๐ WebSocket ์ผ๋ก ์ฑํ ๊ธฐ๋ฅ ๊ตฌํ ํด๋ณด๊ธฐ
2. JavaScript + Socket.IO & React + Socket.IO ๐ Socket.IO๋ก ์ฑํ ๊ธฐ๋ฅ ๊ตฌํ ํด๋ณด๊ธฐ
โ๏ธ ๋จผ์ ํผ์ node.js & react & Socket.IO ๋ฅผ ์ด์ฉํ์ฌ server์ client๊ฐ์ ๊ธฐ๋ณธ์ ์ธ ํต์ ๊ตฌํ ๋ฐ ํ์ธ ํ ๋ฐฑ์๋์ ํ์ ์ ์งํํ๊ธฐ๋ก ํ๋ค.๐
โจ nodemon, express, socket.io-client ์ค์น ๋ฐ ์ธํ
// package.json
"scripts": {
"dev": "nodemon",
...
}
1) node.js ์์ require ์ฐ๊ธฐ
const express = require('express');
const SocketIO = require('socket.io');
const http = require('http');
2) ์ฝ๋๋ฅผ ์๋์ฒ๋ผ ์ง๊ณ ์คํ์ nodemon public/server.js โ ์ด๋ ๊ฒฝ๋ก์ public์ ์๋ตํ๋ฉด ์๋๋ค! (nodemon์ด ์๋ node๋ง ์ธ ๊ฒฝ์ฐ ํซ๋ฆฌ๋ก๋๊ฐ ์๋๋ค!)
"scripts": {
"dev": "nodemon",
"start": "react-scripts start",
...
}
3) connection ์คํ ํ์ธํ๊ธฐ
โจ 1) ์ ์ฅ ์์ผ
const [message, setMessage] = useState('');
const [messages, setMessages]: any = useState([]);
useEffect(() => {
socket.emit('์
์ฅ', { nickname, room });
console.log('1) ์
์ฅ ์์ผ / ํด๋ผ->์๋ฒ');
}, []);
โจ 2) ์๋ฒ์์ ๋ฐ๊ณ ๋ณด๋ด๊ธฐ
socket.on('์
์ฅ', ({ nickname, room }) => {
socket.join(room);
// ๋ฉ์ธ์ง๊ฐ ๋ฐ๋ก ์ฝ์์ ์ฐํ๋ค.
socket.emit('add_message', {
user: 'admin',
text: `${nickname}, ์ด์์ค์๊ฐ! `,
});
// ๋๋ฅผ ์ ์ธํ ์ฌ๋์๊ฒ ์ฐํ๋ฏ๋ก ๋ฐ๋ก ์ฐํ์ง ์๋๋ค. -> ๋ค๋ก๊ฐ๊ธฐ ํ ๋ค์ ์ฑํ
๋ฐฉ์ ๋ค์ด์ฌ ๊ฒฝ์ฐ ๋ณด์ด๊ฒ ๋๋ค.
// ์ด๊ฒ ์์ ๋ก์ง์ด๋ผ๊ณ ์๊ฐํ๊ณ ์ฝ์์ ์ฐํ์ง ์์์ ์ฝ์งํ์..
socket.broadcast.to(room).emit('add_message', {
user: 'admin',
text: `${nickname} ๋์ด ๋ค์ด์ค์
จ์ต๋๋ค!`,
});
console.log('2) ๋ฉ์ธ์ง ์ถ๊ฐ / ์๋ฒ->ํด๋ผ');
});
โจ 3) ๋ฉ์ธ์ง ์ถ๊ฐํ๊ธฐ
socket.on('add_message', message => {
setMessages([...messages, message]);
console.log('3) ๋ฉ์ธ์ง ์ถ๊ฐ / ํด๋ผ');
});
console.log('4) ๋ฉ์ธ์ง ๋ฐฐ์ด ํ์ธ', messages);
โ๏ธ console ํ์ธํด๋ณด๊ธฐ
โจ ์ฑํ ๋ฐฉ ํ์ด์ง UI ๊ตฌํ
isSentByCurrentUser
์ ๋ฐ๋ฅธ ๋์ UI ์ค์ styled.div<{ currentUser: boolean }>
โจ ๋ฉ์ธ์ง๋ฅผ ํ์ ์ปดํฌ๋ํธ๋ก ์ ๋ฌํด์ฃผ๊ธฐ
// index.tsx
return (
<ChatRoomContainer>
<TitleBar />
<Messages messages={messages} /> // ๋ฉ์ธ์ง ๋ด๋ ค ๋ณด๋ด๊ธฐ
<Input
message={message}
setMessage={setMessage}
sendMessage={sendMessage}
/>
</ChatRoomContainer>
);
1. ์ฑํ ๊ธฐ๋ฅ ๊ตฌํ
2. ์ ์ญ ์ํ๊ด๋ฆฌ
3. ์ฑํ ๋ฃธ ๋ ๋๋ง
โจ ๋ฐฐ์ด์ ๋ฐ์์ map ์ ๋๋ฆด ๋ ์ฃผ์ํ๊ธฐ
const Messages = messages => {
console.log('messages', messages);
return (
<MessagesContainer>
{messages.map((message, i) => (
<div key={i}>
<Message message={message}></Message>
</div>
))}
</MessagesContainer>
);
};
์์ ๊ฐ์ด ์์ฑํ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํ๋ค
โย ์ด์ ๊ฐ ๋ฌด์์ผ๊น? ์ด์ ๊น์ง์ ๊ฒฝํ์ ๋ฐํ์ผ๋ก ์ถ์ธกํด๋ณด๋ฉด map์ ๋๋ฆฌ๊ธฐ์ํ messages ํ์์ ๋ฌธ์ ๊ฐ ์๋ ๋ฏ ํ๋ค. ๐ง
โ๏ธ ํด๊ฒฐ์ props์ ์์๋ค.
const Messages = ({ messages }) => { ...
โ ์ด๋ ๊ฒ () ์์ {} ๋ก ์์ฑํด ์ฃผ๋ฉด์ ํด๊ฒฐ!
๐ย ๊ฐ์ฒด์๋ .map ๋ฉ์๋๊ฐ ์๋ค. ์ค๋ก์ง ๋ฐฐ์ด์์๋ง ์ฌ์ฉ ๊ฐ๋ฅํ๋ค๋ ๊ฒ์์ ์ ์!
๋๋ messages๋ฅผ props๋ก ๋๊ฒจ์ค ๋ ์๋ง ์๋์ ๊ฐ์ ํํ๋ก ๋๊ฒจ์คฌ์ ๊ฒ์ด๋ค! ๋ฐ๋ผ์ ๋ฐ์์ฌ ๋ ๊ฐ์ฒด๋ฅผ ํ๋ฒ ํ์ด์ค์ผ ํ๋ค! ๐
โจ ๋ฉ์ธ์ง๋ฅผ ํ๋ฉด์ ์ถ๋ ฅํ๊ธฐ
โ๏ธ 1์ฐจ์๋
useEffect(() => {
socket.on('add_message', ๋ฐ์๋ฉ์ธ์ง => {
setMessages([...messages, ๋ฐ์๋ฉ์ธ์ง]);
console.log('๊ธฐ์กด ๋ฉ์ธ์ง๋ค:', messages);
console.log('์ถ๊ฐ๋ ๋ฉ์ธ์ง๋ฐ์ดํฐ:', message);
});
}, []);
โ๏ธ 2์ฐจ์๋
๊ฒฝํ์ ์์๋ณต์ฌ์ ๊น์๋ณต์ฌ, ์์ ๋น๊ต์ ๊น์ ๋น๊ต์ ๊ด๋ จ์ด ์์ ๊ฒ ๊ฐ์๋ค. ๐ค
์ฌ๋ฌ ์๋, ๊ตฌ๊ธ๋ง ๋์ ์๋์ ๊ฐ์ด ๋ณ๊ฒฝ! โ messages ๋ฐฐ์ด์ ๊ธฐ์กด๋ฉ์ธ์ง์ ์ด์ด์ ์ ์ถ๊ฐ๊ฐ ๋์๊ณ ํ๋ฉด์๋ ์ฌ๋ฌ๊ฐ์ ๋ฉ์ธ์ง๊ฐ ์ ๊ทธ๋ ค์ก๋ค!โจ
useEffect(() => {
socket.on('add_message', message => {
const newMsg = message;
setMessages(messages => [...messages, newMsg]);
});
}, []);
โจ ํ๊ธ ์ ๋ ฅ ์ค๋ณต ์ด์ ํด๊ฒฐํ๊ธฐ
โ ๋ฌธ์ ํ์ ํ๊ธฐ
๋ฉ์ธ์ง๋ฅผ ์
๋ ฅํ ๋๋ง๋ค ๋๋ฒ์ฉ ์ถ๋ ฅ๋๋ ๊ฒฝ์ฐ
โ ํ๊ธ์
๋ ฅ + Enter
๋ฉ์ธ์ง๋ฅผ ์
๋ ฅํ ๋ ํ ๋ฒ์ฉ ์ ์ถ๋ ฅ๋๋ ๊ฒฝ์ฐ
โ ์๋ฌธ์
๋ ฅ / ํน์๋ฌธ์ ์
๋ ฅ / ์ซ์์
๋ ฅ / ํ๊ธ ์
๋ ฅ + ์ง์ ์ ์ก๋ฒํผ ๋๋ฅด๊ธฐ
โ๏ธ ํด๊ฒฐํ๊ธฐ
์ฌ๊ธฐ์ ํ๊ธ์ ๋ ฅ์ ํ ๋๋ง ๋ ๋ฒ์ฉ ์ถ๋ ฅ๋๋ค๋ ๊ฒ, ๊ทธ๋ฆฌ๊ณ ํ๊ธ ์ ๋ ฅ์ ํ๋๋ผ๋ ์ ์ก ๋ฒํผ์ ๋๋ฅด๋ฉด ํ ๋ฒ ์ถ๋ ฅ์ด ๋๋ค๋ ์ ์ ์บ์นํด์ Input ์ปดํฌ๋ํธ์ ์์ฑํ ์ด๋ฒคํธ์ ๊ด๋ จ๋ ๋ถ๋ถ์ ์ญ์ถ์ !!
๊ตฌ๊ธ๋ง์ ํตํด ์์๋ธ ๊ฒ์ onKeyDown ๋์ onKeyPress ๋ฅผ ์์ฑ!
(ํ์ง๋ง ๋ด๊ฐ์๊ธฐ๋ก onKeyPress๋ ๋์ด์ ์ฌ์ฉํ์ง ๋ง๋ผ๊ณ ํ๋๋ฐ.. ๐งย ) ๐ MDN
๐ ๊ด๋ จ(์ฐธ๊ณ ํ) ๊ธ ๋ณด๊ธฐ
๐ onKeyDown
์ผ๋ก ํด๊ฒฐํ๊ธฐ
โ onKeyDown์ ๊ทธ๋๋ก ์ฌ์ฉํ๋ฉด์ ํด๊ฒฐํ ๋ฐฉ๋ฒ์ ์์๊น?
onKeyDown={e =>
e.key === 'Enter' && e.nativeEvent.isComposing === false
? sendMessage(e)
: null
}
โ๏ธ ์ด๋ ๊ฒ e.nativeEvent.isComposing === false
์กฐ๊ฑด์ ๋ฃ์ด์ค์ผ๋ก์จ ํด๊ฒฐํ ์ ์์๋ค.๐โจ ๐ ์ฐธ๊ณ ํ ๊ธ
โจย ์ฑํ ๋ฐฉ ์๋ ์คํฌ๋กค ์ฌ์ฉ์ฑ ๊ฐ์
react-scroll-bottom
์ ํ์ฉํ ์๋ค.๐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ์ง์ ์ง๋ณด๊ธฐ!
โ๏ธ 1์ฐจ ์๋
const scrollToBottom = () => {
if (messagesEndRef.current) {
messagesEndRef.current.scrollTop = messagesEndRef.current.scrollHeight;
}
};
โ๏ธ 2์ฐจ ์๋
const scrollToBottom = () => {
if (messagesEndRef.current) {
messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
}
};
โ๏ธ 3์ฐจ ์๋
const scrollToBottom = () => {
if (messagesEndRef.current) {
messagesEndRef.current.scrollTo({
top: messagesEndRef.current.scrollHeight,
behavior: 'smooth',
});
}
};
โจ ๋ฆฌํฉํ ๋ง ๋ฐ ํ์ ์ด์ ํด๊ฒฐ
const scrollToBottom = () => {
messagesEndRef.current?.scrollTo({ // ?๋ฅผ ์ง์ฐ๋ฉด ์๋ฌ
top: messagesEndRef.current.scrollHeight,
behavior: 'smooth',
});
};
const refTest = useRef<any>(null); // x
const refTest = useRef<HTMLUListElement>(null); // x
const refTest = useRef<HTMLUListElement>(); // x
const messagesEndRef = useRef<HTMLDivElement>(null); // o
โจย ์๊ฐ ์ถ๊ฐํ๊ธฐ
const getDate = () => {
const date = new Date();
const hours = date.getHours();
const minutes = date.getMinutes();
const currentTime = hours > 12 ? `์คํ ${hours - 12}` : `์ค์ ${hours}`;
return `${currentTime}:${minutes}`;
};
โ์๊ฐ์ ์ป์ด๋ผ ์ ์์ง๋ง ๋ชจ๋ UI๊ฐ ํ์ฌ ์๊ฐ์ ๋ฐ์ํ๋ค. โ ์๊ฐ ์์ฑ์ ์ถ๊ฐํด์ ์น์์ผ ํต์ ์ ํด์ผํ ๊ฒ ๊ฐ๋ค.๐ค
// ํด๋ผ์ด์ธํธ
const [currentTime, SetCurrentTime] = useState('');
const sendMessage = e => {
e.preventDefault();
if (message) {
socket.emit('send_message', message, nickname, room, currentTime, () =>
setMessage('')
);
}
};
// ์๋ฒ
socket.on(
'send_message',
(message, nickname, room, currentTime, callback) => {
io.to(room).emit('add_message', {
user: nickname,
text: message,
time: currentTime,
});
callback();
}
);
// ํด๋ผ์ด์ธํธ
useEffect(() => {
socket.on('add_message', message => { // ์๋ฒ๋ก ๋ถํฐ ๋ฐ์ ๋ฉ์ธ์ง ๊ฐ์ฒด ์์๋ time๋ ์กด์ฌํ๊ฒ ๋์๋ค.
const newMwssage = message;
setMessages(messages => [...messages, newMwssage]);
});
}, []);
โ๏ธ ์์ ์ ์ฝ๋
useEffect(() => {
const date = new Date();
const hours = date.getHours();
const minutes = date.getMinutes();
const currentTime = hours > 12 ? `์คํ ${hours - 12}` : `์ค์ ${hours}`;
SetCurrentTime(`${currentTime}:${minutes}`);
}, [messages]);
โ๏ธ ์์ ํ ์ฝ๋
useEffect(() => {
const date = new Date();
const hours = date.getHours();
const minutes = ('0' + date.getMinutes()).slice(-2);
const currentTime = hours > 12 ? `์คํ ${hours - 12}` : `์ค์ ${hours}`;
SetCurrentTime(`${currentTime}:${minutes}`);
}, [messages]);
๐ ์ฐธ๊ณ ๊ธ
โจ ๊ด๋ฆฌ์ ์ ์ฉ UI ๋ง๋ค๊ธฐ
const Message = ({ message: { user, text, time }, nickname }) => {
let isSentByCurrentUser = false;
if (user === nickname) {
isSentByCurrentUser = true;
}
const storeData = useSelector((state: any) => state);
const userImage = storeData.user.userData.thumbnail_url;
if (user === 'ํจ๊ปํ๊ฐ ๊ด๋ฆฌ์') { // 1)
return (
<AdminTextBox>
<Text>{text}</Text>
</AdminTextBox>
);
}
return isSentByCurrentUser ? ( // 2)
<MessageContainer currentUser={isSentByCurrentUser}>
<MessageBox>
<TextContainer>
<TextData>
<Time>{time}</Time>
</TextData>
<TextBox currentUser={isSentByCurrentUser}>
<Text>{text}</Text>
</TextBox>
</TextContainer>
</MessageBox>
<ProfileImg src={userImage} />
</MessageContainer>
) : (
<MessageContainer currentUser={isSentByCurrentUser}>
<ProfileImg src={userImage} />
<MessageBox>
<UserBox>
<Nickname>{user}</Nickname>
<Mbti>INFP</Mbti>
</UserBox>
<TextContainer>
<TextBox currentUser={isSentByCurrentUser}>
<Text>{text}</Text>
</TextBox>
<TextData>
<ReportIcon src={Siren} />
<Time>{time}</Time>
</TextData>
</TextContainer>
</MessageBox>
</MessageContainer>
);
};
...
const MessageContainer = styled.div<{ currentUser: boolean }>`
${props => props.theme.flex.flexBox()}
justify-content: ${({ currentUser }) => (currentUser ? 'end' : 'start')};
padding: 1.3rem 1rem 0 1rem;
`;
1) ํ์ฌ user๊ฐ ํจ๊ปํ๊ฐ ๊ด๋ฆฌ์ ์ด๋ฉด ์๋ ๋ฆฌํด๋ฌธ์ ์คํ
2) ํ์ฌ user๊ฐ โ๋โ์ธ์ง โ๋ค๋ฅธ์ ์ โ ์ธ์ง์ ๋ฐ๋ผ์ ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ ํด์ค๋ค. styled-components ์์๋ ์ ์ ์ ๋ฐ๋ผ์ ๋ค๋ฅธ UI ๋ ์ด์์์ ๋ณด์ฌ์ค๋ค.
โ ์ ์ ๊ฐ ๋์ผํ ๋๋ค์์ผ ๊ฒฝ์ฐ
โ ํ๋ก์ ํธ ์งํ ์ค, ๊ฐ์ ๋๋ค์์ ๊ฐ์ง ์ ์ ๊ฐ ๊ฐ์ ๋ฐฉ์ ์๋ค๋ฉด UI๊ฐ ์ค๋ณต๋๋ ์ด์ ๋ฐ์!
โ๏ธ ์ ์ ์ ๋๋ค์์ด ์๋ ๊ณ ์ id๋ก ๊ตฌ๋ถํด์ฃผ๊ธฐ
let isSentByCurrentUser = false;
if (user_id === id) {
isSentByCurrentUser = true;
}
โจย ๋ ๋ฌ์ ๋ ๋ฉ์ธ์ง ์ ์กํ๊ธฐ
/* ์ ์ ๊ฐ ๋ ๋ฌ์ ๋ */
socket.on('disconnect', ({ nickname, room }) => {
io.to(room).emit('add_message', {
user: 'ํจ๊ปํ๊ฐ ๊ด๋ฆฌ์',
text: `${nickname} ๋์ด ๋ ๋์
จ์ด์.`,
});
console.log('์ ์ ๊ฐ ๋ ๋ฌ์ต๋๋ค.');
});
console.log
๋ ์ ์ฐํ์ง๋ง add_message
๋ก ์ ์ก์ด ๋์ง๋ ์๋๋ค.disconnect
๋ nickname๊ณผ room์ ๋ฐ์ง ์๋๋ค! โ ์ํต ํ ๋ฐฑ์๋์์ ์ฒ๋ฆฌํ๊ธฐ๋ก ํ๋ค. ๐โจย ์ ๊ณ ๊ธฐ๋ฅ ๊ตฌํํ๊ธฐ
...
<ReportIcon
src={Siren}
onClick={() => {
setIsShowModal(true);
ReportChatData(user_id, message_id, text);
}}
/>
+) ์์ค ํํฐ๋ง ๋ฐฑ์๋ ๊ตฌํ ์๋ฃ
โจย ๋ฒค ๋นํ ์ฌ์ฉ์ ์ ๊ทผ ์ ํํ๊ธฐ & join ํ ์ฌ์ฉ์ ์ธ์ฆ
useEffect(() => {
socket.on('connect_error', data => {
if (data.message === 'invalid user') {
socket.close();
navigate('/');
}
});
return () => {
socket.close();
};
}, []);
data
์ ๊ฐ์ด invalid user
(๋ฐฑ์๋์์ ์ง์ ) ์ผ ๊ฒฝ์ฐ socketclose()
๋ฅผ ํตํด ํต์ ์ ์ ํํ๋ค. ์ด ํ navigate๋ฅผ ํตํด์ main ์ผ๋ก ์ด๋์ํจ๋ค.socket.close()
๋ฅผ ํตํด ํต์ ์ ๋์ ์ ์๋ค โ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ธ ๊ฒ ๊ฐ๋ค.๐ง ๋ง์ฝ ์ด ๊ตฌ๋ฌธ์ด ์๋ค๋ฉด, ์ ์ ๊ฐ ๋ค๋ก๊ฐ๊ธฐ๋ฅผ ํตํด ์ฑํ
๋ฐฉ์์ ๋ฒ์ด๋๋ ๋ค๋ฅธ ์ ์ ์ ์ฑํ
๋ฐฉ์์๋ ๋ ๋๊ฐ ์ ์ ์ ๋ฉ์ธ์ง๋ฅผ ๋ฐ์ ์ ์์๋ค. โจย ์ฑํ ์ ๋ ฅ ๊ธ์์ ์ ํ & ์ค๋ฐ๊ฟ์ฒ๋ฆฌ
<InputBox>
<ChatInput
type="text"
maxLength={45}
value={message}
placeholder="๋ฉ์ธ์ง ์
๋ ฅ"
onChange={e => setMessage(e.target.value)}
onKeyDown={e =>
e.key === 'Enter' && e.nativeEvent.isComposing === false
? sendMessage(e)
: null
}
/>
maxLength
๋ ๋ฆฌ์กํธ ๋ฌธ๋ฒ์ โLโ๋๋ฌธ์ ์ฃผ์ ! (JS์์๋ maxlength)<input type="text" name="usrname" maxlength="10">
{}
์์ ์ซ์๋ฅผ ๋ฃ์ด์ฃผ๊ธฐ !! =โโ
ํ์์ผ๋ก๋ ๋์ง ์์๋ค.โจ ์๋ฌธ ์ธํ์ฐฝ์์ ๋ฒ์ด๋จ ํด๊ฒฐํ๊ธฐ
word-wrap: break-word;
๐ Tip
โ๏ธ import styled from 'styled-components/macro';
โ ๊ฐ๋ฐ์๋๊ตฌ UI๋ฅผ ํ์ธํ ๋ ์์์ ํด๋์ค๋ช ์ ์๋๋๋ก ๋ณผ ์ ์๋ ์ฝ๋ !
1) ์ฌ์ฉ์ ์ธ์ฆ โ socket.io auth
2) ๋น๋ก๊ทธ์ธ ์ฑํ ๋ฐฉ ์ ์ฅ ์ ์ดํ๊ธฐ
import os
import socketio
import django
from .mongodb import save_message
from django.conf import settings
from cores.utils import censor_text
# Django ๊ธฐ๋ณธ์์ฑ ํ์ผ์ด ์๋ ๊ฒฝ์ฐ ๋ฐ์์ Django models๋ฅผ ์ํฌํธํ๋ฉด ์๋ฌ ๋ฐ์ํจ
# Django models ์ํฌํธ ํ๊ธฐ ์ ์ django.setup() ํด์ค์ผ ์๋ฌ ์ ๋จ
django.setup()
from cores.models import UserStatus
from users.models import User
# Django ํ๊ฒฝ์ค์ ํ์ผ ๋ก๋
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "togedog_dj.settings")
# ์์ผ.io ์๋ฒ ์ธ์คํด์ค ์์ฑ, ๋น๋๊ธฐ์ฒ๋ฆฌ๋ฅผ ์ํด eventlet ํ์ฉํ๋ฉฐ, cors๋ ํ์ฌ ๋ชจ๋ ํ์ฉ
# logger=True๋ก ์ค์ ํ๋ฉด ์๋ฒ์์ ๋ฐ์ํ๋ ์ด๋ฒคํธ ๋ก๊ทธ๋ฅผ ํฐ๋ฏธ๋์ ์ถ๋ ฅํจ
sio = socketio.Server(async_mode='eventlet', cors_allowed_origins='*', logger=True)
# ์ฑํ
๋ฐฉ ์ ์ ์
์ฅ/ํด์ฅ ๋ฉ์์ง ์ฉ ๋น ๋์
๋๋ฆฌ ์์ฑ
users = {}
# sio.event: connect์ disconnect๋ ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ์ ์ ์ํ๊ฑฐ๋ ์ ์์ ๋์ ๋ ๋ฐ์
# ์ด๋ฒคํธ ๋ช
์ผ๋ก connect, message, disconnect ๋ฑ์ ๋ช
์์ ์ผ๋ก ์ ์๋ ์ด๋ฒคํธ๋ก, python-socketio์์ ์ ํด์ง ๋๋ก๋ง ์ฌ์ฉ ๊ฐ๋ฅ
# @sio.event๋ ๋ฐ์์ ๋ฐ์ฝ๋ ์ดํฐ ๋ฐ์ ํจ์๋ช
(def ํจ์๋ช
)์ ์ด๋ฒคํธ๋ก ๋ฐ์ผ๋ฉฐ @sio.on('์ด๋ฒคํธ๋ช
')๋ ์ธ์ ๊ฐ์ผ๋ก ์ด๋ฒคํธ๋ช
์ ๋ฐ์
# ๋ฐ๋ผ์ @sio.on์ผ๋ก๋ ํจ์๋ช
์ผ๋ก ์ฐ์ง ๋ชปํ๋ ํ๊ธ์ด๋ ๊ณต๋ฐฑ์ด ๋ค์ด๊ฐ ๋ฌธ์์ด์ ์ด๋ฒคํธ๋ช
์ผ๋ก ์ฌ์ฉ ํ ์ ์์
# sio.emit์์ ์ธ์์ธ to์ room์ ์์ ํ ๋๊ฐ์, ์๋ฌด๊ฑฐ๋ ์จ๋ ๋จ, ํน์ room์ผ๋ก๋ง ๋ฉ์์ง๋ฅผ ๋ณด๋ผ ๋ ์ฌ์ฉ
@sio.event
def connect(sid, environ, auth):
'''
socket.io ํด๋ผ์ด์ธํธ๊ฐ ์ฐ๊ฒฐ๋๋ฉด ์๋ ์ฝ๋ ์คํ
auth์ ์ฌ์ฉ์id๊ฐ์ผ๋ก ํด๋น ์ฌ์ฉ์๊ฐ ์กด์ฌํ์ง์๊ฑฐ๋ ์ฐจ๋จ์ ์ ์ด๋ฉด connect_error๋ก emit
emit ๋๋ฉด ํด๋ผ์ด์ธํธ ์ชฝ์์ ๊ฐ์ ๋ก ์ฐ๊ฒฐํด์ ์ฒ๋ฆฌ
'''
print('connected ', sid) # 'connected sid๊ฐ' ํ๋ฆฐํธ
print(auth) # ํด๋ผ์ด์ธํธ๊ฐ ๋ณด๋ธ auth๊ฐ ํ๋ฆฐํธ
user = User.objects.filter(id=auth["userId"])
if not user or user.get().status == UserStatus.BANNED.value:
sio.emit('connect_error', {'message': 'invalid user'})
@sio.on('join')
def handle_join(sid, data):
'''
์ ์ ๊ฐ ์ฑํ
๋ฐฉ์ ์
์ฅํ๋ฉด ์๋ ์ฝ๋ ์คํ
๋ธ๋ผ์ฐ์ ์์ ์ฑํ
๋ฐฉ ์
์ฅ์ connect ์ด๋ฒคํธ๊ฐ ๋จผ์ ๋ฐ์ํ๊ณ ๋ฐ๋ก join ์ด๋ฒคํธ๊ฐ ๋ฐ์๋จ
data๋ฅผ ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ์ฒด๋ก ๋ฐ์ผ๋ฉด ํ์ด์ฌ ๋์
๋๋ฆฌ๋ก ์ฌ์ฉ๊ฐ๋ฅ
data์๋ ๋ฐฉ ๋ฒํธ์ ์ฌ์ฉ์ ๋๋ค์์ด ํฌํจ๋จ
์ฑํ
๋ฐฉ์ ์ ์ํ ์ฌ์ฉ์์ sid๊ฐ์ key๋ก ํ๊ณ , data ๊ฐ์ฒด ์ ์ฒด๊ฐ value๋ก users ๋์
๋๋ฆฌ์ ์ ์ฅ
'''
users[sid] = data
sio.enter_room(sid, room=data['room'])
sio.emit(
'add_message',
{
"user_nickname": 'ํจ๊ปํ๊ฐ ๊ด๋ฆฌ์',
"text": f"{data['nickname']}๋์ด ๋ค์ด์์ด์."
},
to=data['room']
)
@sio.on('send_message')
def handle_send_message(sid, message, nickname, room, currentTime, userMbti, userImage, userId):
'''
๋ฐ์์ emitํ data ๊ฐ์ฒด ์์ฑ
censor_text(): ์์ค ํํฐ๋ง ํจ์, ์์ค ๋ถ๋ถ์ด ์์ผ๋ฉด *์๋ก ์์ ํ ๋ฌธ์์ด์ ๋ฐํํจ
save_message(): MongoDB์ ๋ฉ์์ง ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ๊ณ ์ ObjectId(24์๋ฆฌ ๋ฌธ์์ด)๋ฐํ
ํ์ํ ๋ฐ์ดํฐ ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋๋ฉด add_message๋ก emit
'''
data = {
"user_nickname": nickname,
"user_id" : userId,
"user_mbti" : userMbti,
"user_image" : userImage,
"text" : censor_text(message),
"message_id" : save_message(message, nickname, userId, room),
"time" : currentTime,
}
sio.emit('add_message', data, to=room)
@sio.event
def disconnect(sid):
'''
socket.io ํด๋ผ์ด์ธํธ๊ฐ ์ฐ๊ฒฐ ํด์ ๋๋ฉด ์๋ ์ฝ๋ ์คํ
leave_username: ์ฐ๊ฒฐ ํด์ ๋ sid๊ฐ์ ์ ์ ๋๋ค์
leave_room_number: ์ฐ๊ฒฐ ํด์ ๋ sid๊ฐ์ ๋ฐฉ ๋ฒํธ
ํด์ฅํ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ด์ add_message๋ก emit
users ๋์
๋๋ฆฌ์์ ํด๋น sid๊ฐ์ ๋ฐ์ดํฐ ์ญ์
'''
leave_username = users[sid].get('nickname')
leave_room_number = users[sid].get('room')
sio.emit(
'add_message',
{
"user_nickname": 'ํจ๊ปํ๊ฐ ๊ด๋ฆฌ์',
"text": f"{leave_username}๋์ด ํด์ฅํ์
จ์ด์."
},
to=leave_room_number
)
del users[sid]
๐จ socket id ๋ฌดํ ์์ฑ ์ด์
const ENDPOINT = 'localhost:3000'; // 1)
/* ์
์ฅ์ ๋๋ค์๊ณผ ๋ฐฉ์ ์ ๋ฌ */
useEffect(() => {
socket = io(ENDPOINT); // 2)
socket.emit('์
์ฅ', { nickname, room });
console.log('1) ์
์ฅ์ nickname, room ๋๊ธฐ๊ธฐ');
}, [ENDPOINT]);
โ๏ธ socket = io(ENDPOINT) ์์น ๋ณ๊ฒฝ
1) ์ ์ญ์ ENDPOINT๋ฅผ ์ค์ ํด์ฃผ๊ณ
2) useEffect์ socket = io(ENDPOINT)๋ฅผ ์ง์ ํด์ฃผ์๋ค.
โ ์๋ ์ด ์ฝ๋๊ฐ 1) ๋ฐ๋ก ์๋ ์ ์ญ์ ์์นํด์์๋๋ฐ ์ด ๋๋ฌธ์ ๋งค๋ฒ ์ฌ๋ ๋๋ง์ด ๋๋ฉด์ ์ฝ๋๊ฐ ๊ณ์ํด์ ์คํ, ๊ธ์๋ฅผ ์
๋ ฅํ๋ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋๋ง๋ค ๊ณ์ํด์ socket id๊ฐ ์๋ก ์์ฑ๋๋ ์ด์๊ฐ ์์๋ค.
์์น๋ฅผ useEffect ๋ฌธ์์ ์์ฑ, deps๊ฐ์ผ๋ก ENDPOINT๋ฅผ ์ง์ ํด์ค์ผ๋ก์จ ํด๊ฒฐํ ์ ์์๋ค.
๐จย ํ์ฌ ์๊ฐ ๊ฐ์ ธ์ค๊ธฐ
const [currentTime, SetCurrentTime] = useState('');
/* ํ์ฌ์๊ฐ์ ์ป๋ ํจ์ */
useEffect(() => {
const date = new Date();
const hours = date.getHours();
const minutes = ('0' + date.getMinutes()).slice(-2); // 1)
const currentHour = hours > 12 ? `์คํ ${hours - 12}` : `์ค์ ${hours}`;
SetCurrentTime(`${currentHour}:${minutes}`);
}, [message]); // 2)
1) 1์์๋ฆฌ์ ๊ฐ ๋์ฌ ๊ฒฝ์ฐ ์์ 0์ ๋ถ์ด๋๋ก ์์ฑ
2) ๋ง์ฝ deps ๊ฐ์ผ๋ก messages๋ฅผ ๋ฃ์ผ๋ฉด ์ฑํ
๋ฐฉ์ ์ฑํ
์ ์น์ง์๊ณ ๋จธ๋ฌผ๋ฌ์๋ ์๊ฐ์์ฒด๋ฅผ ๊ธฐ์ตํ๊ณ ์๋ค๊ฐ ์น๋ ์๊ฐ ๊ทธ ์ด์ ์๊ฐ์ด ํ๊ธฐ๋๋ ์ด์๊ฐ ๋ฐ์๋๋ค.๐ค
โ ๊ฐ๋จํ๊ฒ message๋ก ๋ฐ๊ฟ์ฃผ๋ฉด์ ์ด์๊ฐ์ ์ด์ ํด๊ฒฐ!
๐จย ๋ฆฌ๋์ค + ์ ๋ฌํ๋ ๋ฐ์ดํฐ์ ์ ๋ฌ๋ ๋ฐ์ดํฐ ํ์ ์ด์
Object
๋ฐ์ดํฐ๋ฅผ ๊ทธ๋๋ก ๋ ๋๋ง ํ๋ ค๊ณ ํ๊ธฐ ๋๋ฌธ์ ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ค.โย ์๋ฌ์ฝ๋๋ฅผ ๊ฒ์, ์ฐธ๊ณ ๋ธ๋ก๊ทธ์์ ํค์๋ ๋์์ ์ป์๋ค. UI ๋ ๋๋ง์ด ๋ฌธ์ ๋ค? ๋ ๋๋ง์ด ๋ฐ์๋๋ ๋ถ๋ถ์ ์ฒดํฌํด์ผํ ๊น?
// titleBar
...
const roomName: any = useSelector<any>(state => state.chat.title); // <- title ๋ก ํ๋ฒ ๋ ์ง์
...
return (
<TitleBarContainer>
<BackBox onClick={goToChatList}>
<GoBackIcon src={ArrowLeft} />
<GoBackText>์ฑํ
๋ชฉ๋ก</GoBackText>
</BackBox>
<Title>{roomName}</Title>
</TitleBarContainer>
);
โ๏ธ state.chat ๋ถ๋ถ์์ title ์ ํ ๋ฒ๋ ๋ค์ด๊ฐ์ ํด๊ฒฐํ๋ค. ์ด์ ๋๐ง !?
const initialState = { // 1)
id: 0,
title: '',
};
const postsReducer = (prevState = initialState, action) => {
switch (action.type) {
case 'SET_ROOM':
return { ...prevState, title: action.data };
// return action.data;
case 'SET_ROOM_ID':
return { ...prevState, id: action.data };
// return action.data;
default:
return prevState;
}
};
export default postsReducer;
๐จ ์ฐจ๋จ ์ ์ ์ฒ๋ฆฌ
โ ์๋ฒ์ชฝ ์ฝ๋์์ ์๋์ ๊ฐ์ด emit ํ๋ฉด ๋ค๋ฅธ ์ฌ์ฉ์๋ ํ๊ธฐ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
# sio.emit("connect_error", {"message": "invalid user"})
# ํด๋ผ์ด์ธํธ ์ฝ๋:
# useEffect(() => {
# socket.on('connect_error', data => {
# if (data.message === 'invalid user') {
# socket.close();
# navigate('/');
# }
# });
# return () => {
# socket.close();
# };
# }, []);
@sio.event
def connect(sid, environ, auth):
'''
socket.io ํด๋ผ์ด์ธํธ๊ฐ ์ฐ๊ฒฐ๋๋ฉด ์๋ ์ฝ๋ ์คํ
auth์ ์ฌ์ฉ์id๊ฐ์ผ๋ก ํด๋น ์ฌ์ฉ์๊ฐ ์กด์ฌํ์ง์๊ฑฐ๋ ์ฐจ๋จ์ ์ ์ด๋ฉด connect_error๋ก emit
emit ๋๋ฉด ํด๋ผ์ด์ธํธ ์ชฝ์์ ๊ฐ์ ๋ก ์ฐ๊ฒฐํด์ ์ฒ๋ฆฌ
'''
user = User.objects.filter(id=auth["userId"])
if not user.exists() or user.get().status == UserStatus.BANNED.value:
return False
โ๏ธ ์ด๊ธฐ ์ ์ฅ ๋ฉ์ธ์ง ์ ์ก์ io์ socket ์ฐจ์ด
// 1)
socket.on('join', ({ nickname, room }) => {
socket.join(room);
socket.emit('add_message', { // 1)-1
user: 'ํจ๊ปํ๊ฐ ๊ด๋ฆฌ์',
text: `${nickname} ๋์ด ๋ค์ด์์ด์.`,
});
socket.broadcast.to(room).emit('add_message', {
user: 'ํจ๊ปํ๊ฐ ๊ด๋ฆฌ์',
text: `${nickname} ๋์ด ๋ค์ด์ค์
จ์ต๋๋ค!`,
});
// 2)
socket.on('join', ({ nickname, room }) => {
socket.join(room);
io.to(room).emit('add_message', { // 2)-1
user: 'ํจ๊ปํ๊ฐ ๊ด๋ฆฌ์',
text: `${nickname} ๋์ด ๋ค์ด์์ด์.`,
});
});
1), 2) ์ฒ๋ผ ์์ฑํ ๊ฒฝ์ฐ ์๋์ ๊ฐ์ด ์ ๋ฌ๋ค.
ํ์ง๋ง 1)-1
์์ socket๋์ 2)-1
์ฒ๋ผ io.to(room).emit(โฆ) ๋ก ์์ฑํ๋ฉด
๋ฉ์ธ์ง๊ฐ ์ค๋ณต๋์ด์ ์ถ๋ ฅ๋๋ ํ์์ด ๋ฐ์ํ๋ค. โ ํ์ง๋ง ์๋ฒ๋จ์์ room ๊ฐ์ ํ์ํ๊ธฐ๋๋ฌธ์ io.to(room)ํ์์ ์จ์ผํ๋ฏ๋ก 2)์ฒ๋ผ ์์ฑํ๋, broadcast๋ฅผ ์ญ์ ํด์ ์์๊ฐ์ด 2)๋ก ์ฌ์ฉ!
โ๏ธ ์ฑํ ๊ธฐ๋ฅ ๊ตฌํ์ ๋ง์น๋ฉฐ