BIDCAST๋ ๋๊ตฌ๋ ๊ฒฝ๋งค๋ฅผ ๊ฐ์คํ๊ณ ์
์ฐฐ์ ์ฐธ์ฌํ ์ ์๋ ์ค์๊ฐ ์์ ๊ธฐ๋ฐ ๊ฒฝ๋งค ์์คํ
์
๋๋ค.
์ค์๊ฐ ์ฑํ
, ์์ ๊ณต์ , ์
์ฐฐ ๊ธฐ๋ฅ์ ํตํด ์๋๊ฐ ์๋ ๊ฒฝ๋งค ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค.
๐ BIDCAST ์๋ฒ GitHub ๐ BIDCAST ํด๋ผ์ด์ธํธ GitHub ๐ AWS์ธํ ํ๊ธฐ ๐ BIDCAST ํํ์ด์ง
โ ๏ธ ์ฃผ์:
application-custom.properties
ํ์ผ์ Git์ ํฌํจ๋์ง ์์ต๋๋ค. ์ง์ src/main/resources
๊ฒฝ๋ก์ ์๋ ๋ด์ฉ์ ํฌํจํ ํ์ผ์ ์ถ๊ฐํด์ฃผ์ธ์:
application-custom.properties
์์ ๋ณด๊ธฐproperties
spring.datasource.url=jdbc:postgresql://<DB์ฃผ์>:5432/bidcast
spring.datasource.username=<DB์ ์ >
spring.datasource.password=<DB๋น๋ฐ๋ฒํธ>
jwt.secret=<JWT ๋น๋ฐ ํค>
jwt.expiration=3600000
aws.s3.access-key=<AccessKey>
aws.s3.secret-key=<SecretKey>
aws.s3.region=ap-northeast-2
aws.s3.bucket=<๋ฒํท๋ช
>
aws.s3.folder=uploads
1. Server
1-1. App Server (์ ์ ์๋น์ค)
- Build Tools:
Vite
,Gradle
- Front-End:
React
,HTML
,CSS
,Thymeleaf
- Back-End:
Spring Boot
,WebRTC
,WebSocket
- Persistence Layer:
MyBatis
,JPA
- Authorization:
JWT
,Spring Security
1-2. SFU Server (์ค๊ณ ์๋ฒ)
Node.js
,WebRTC
,mediasoup
,WebSocket
1-3. Database
Amazon RDS
,PostgreSQL
1-4. Cloud Storage
Amazon S3
2. Infrastructure
Nginx
,certbot
,Let's Encrypt
3. Dev Tools
IntelliJ IDEA
,Figma
4. Collaboration
Git
,GitHub
5. CI/CD
Jenkins
socket
ํด๋ผ์ด์ธํธ ํ๋ํ๋์ ์ฐ๊ฒฐ์ ๋ํ๋ด๋ ๊ฐ์ฒด
ํด๋น ํด๋ผ์ด์ธํธ์ 1:1 ํต์ ํ๊ฑฐ๋ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ ๋ ์ฌ์ฉ
io
์ ์ฒด ์์ผ ์๋ฒ ๊ฐ์ฒด๋ก, ๋ชจ๋ ํด๋ผ์ด์ธํธ์ ๋ฉ์์ง๋ฅผ ๋ณด๋ด๊ฑฐ๋ ํน์ ๋ฐฉ(room)์ ๋ฉ์์ง๋ฅผ ์ ๋ฌํ ๋ ์ฌ์ฉ
์: io.to(roomId).emit('event',data)
broadcast
์์ ์ ์ ์ธํ ๊ฐ์ ๋ฐฉ ๋๋ ์ ์ฒด ํด๋ผ์ด์ธํธ์๊ฒ ๋ฉ์์ง ์ ์ก
์: socket.broadcast.emit()
โ ์๊ธฐ ์์ ์ ์ ์ธํ ๋ชจ๋์๊ฒ ์ ์ก
on
ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ์ด๋ฒคํธ๊ฐ ์์ ๋ ์คํํ ์ฝ๋ฐฑ์ ๋ฑ๋ก
์: socket.on('produce', callback)
emit
์ด๋ฒคํธ์ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ด๋ ๋ฐฉ์
์๋ฒ โ ํด๋ผ์ด์ธํธ ์๋ฐฉํฅ์ผ๋ก ์ฌ์ฉ
์: socket.emit('new-producer', data)
์ฉ์ด | ์ค๋ช |
---|---|
ICE | ์ฐ๊ฒฐ ๊ฐ๋ฅํ ํ๋ณด ์ฃผ์๋ค์ ์์งํด ์ต์ ๊ฒฝ๋ก๋ฅผ ์ ํํ๋ ํ๋ ์์ํฌ |
STUN | ๊ณต์ธ IP ๋ฐ ํฌํธ ์ ๋ณด๋ฅผ ์์๋ด๊ธฐ ์ํ ์๋ฒ |
TURN | P2P ์ฐ๊ฒฐ์ด ๋ถ๊ฐํ ๊ฒฝ์ฐ ๋ฏธ๋์ด๋ฅผ ์ค๊ณํด์ฃผ๋ ์๋ฒ (๋์ญํญ ์๋ชจ โ) |
์ฉ์ด | ์ค๋ช |
---|---|
Worker | ๋ฏธ๋์ด ์ฒ๋ฆฌ๋ฅผ ๋ด๋นํ๋ ๋ฐฑ๊ทธ๋ผ์ด๋ ํ๋ก์ธ์ค |
Router | ํ ๋ฐฉ(room)์ ํ๋์ฉ ์กด์ฌ, ๋ฏธ๋์ด ๊ฒฝ๋ก ์ ์ด ์ญํ |
Transport | ํด๋ผ์ด์ธํธ์ ์๋ฒ ๊ฐ ๋ฏธ๋์ด ์ฐ๊ฒฐ ํต๋ก (DTLS/ICE ๋ฑ ํฌํจ) |
Producer | ํด๋ผ์ด์ธํธ๊ฐ ๋ฏธ๋์ด๋ฅผ ์ก์ถํ ๋ ์์ฑ๋๋ ๊ฐ์ฒด |
Consumer | ํด๋ผ์ด์ธํธ๊ฐ ๋ฏธ๋์ด๋ฅผ ์์ ํ ๋ ์์ฑ๋๋ ๊ฐ์ฒด |
{
"codecs": [
{ "kind": "audio", "mimeType": "audio/opus", ... },
{ "kind": "video", "mimeType": "video/VP8", ... }
]
}
socket.on('create-router', (_, callback) => {
callback({ rtpCapabilities: router.rtpCapabilities });
});
socket.on('create-transport', async ({ direction }, callback) => {
const transport = await router.createWebRtcTransport({
listenIps: [{ ip: '0.0.0.0', announcedIp: 'bidcastserver.kro.kr' }],
enableUdp: true,
enableTcp: true,
preferUdp: true,
portRange: { min: 40000, max: 40010 },
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'turn:bidcastserver.kro.kr:3478', username: 'webrtc', credential: '1234' }
],
});
transports.set(transport.id, { transport, socketId: socket.id, direction });
callback({
id: transport.id,
iceParameters: transport.iceParameters,
iceCandidates: transport.iceCandidates,
dtlsParameters: transport.dtlsParameters,
});
});
socket.on('connect-transport', async ({ dtlsParameters, transportId }, callback) => {
const data = transports.get(transportId);
if (!data) throw new Error('Transport not found');
await data.transport.connect({ dtlsParameters });
callback();
});
socket.on('produce', async ({ kind, rtpParameters, transportId, roomId }, callback) => {
const data = transports.get(transportId);
if (!data) throw new Error('Transport not found');
const producer = await data.transport.produce({ kind, rtpParameters });
if (!producers.has(roomId)) producers.set(roomId, new Map());
producers.get(roomId).set(producer.id, { producer, socketId: socket.id, kind });
socket.broadcast.to(roomId).emit('new-producer', {
producerId: producer.id,
socketId: socket.id,
kind,
});
callback({ id: producer.id });
});
socket.on('consume', async ({ producerId, rtpCapabilities, transportId, roomId }, callback) => {
const data = transports.get(transportId);
if (!data) throw new Error('Transport not found');
const roomProducers = producers.get(roomId);
if (!roomProducers || !roomProducers.has(producerId)) throw new Error('Producer not found');
if (!router.canConsume({ producerId, rtpCapabilities })) throw new Error('Cannot consume');
const consumer = await data.transport.consume({
producerId,
rtpCapabilities,
paused: false,
});
consumers.set(consumer.id, { consumer, socketId: socket.id });
callback({
id: consumer.id,
producerId,
kind: consumer.kind,
rtpParameters: consumer.rtpParameters,
});
});
socket.on('consumer-resume', async ({ consumerId }) => {
const data = consumers.get(consumerId);
if (!data) return;
await data.consumer.resume();
});
socket.on('disconnect', () => {
for (const [transportId, data] of transports) {
if (data.socketId === socket.id) {
data.transport.close();
transports.delete(transportId);
}
}
for (const [roomId, roomProducers] of producers) {
for (const [producerId, data] of roomProducers) {
if (data.socketId === socket.id) {
data.producer.close();
roomProducers.delete(producerId);
io.emit('user-disconnected', {
socketId: socket.id,
producerId,
});
}
}
if (roomProducers.size === 0) producers.delete(roomId);
}
for (const [consumerId, data] of consumers) {
if (data.socketId === socket.id) {
data.consumer.close();
consumers.delete(consumerId);
}
}
});
transports
(Map)
transportId๋ฅผ key๋ก, { transport, socketId, direction }
๊ฐ์ฒด๋ฅผ value๋ก ์ ์ฅ
producers
(Map)
roomId๋ฅผ key๋ก, value๋ ๋ ๋ค๋ฅธ Map
๋ด๋ถ Map: producerId๋ฅผ key๋ก { producer, socketId, kind }
consumers
(Map)
consumerId๋ฅผ key๋ก, { consumer, socketId }
์ ์ฅ
socketRoomMap
(Map)
socketId๋ฅผ key๋ก, ํ์ฌ ์ฌ์ฉ์๊ฐ ์
์ฅํ roomId ์ ์ฅ
socketIdMap
(Map)
loginId๋ฅผ key๋ก, ์์ผ ID ์ ์ฅ
auctionHostMap
(Map)
auctionId๋ฅผ key๋ก, ํด๋น ๊ฒฝ๋งค์ ํธ์คํธ ์์ผ ID ์ ์ฅ
auctionStates
(๊ฐ์ฒด)
auctionId๋ฅผ key๋ก, ๊ฒฝ๋งค ์งํ ์ํ(์ ํ๋ ์ํ ๋ฑ) ์ ์ฅ
auctionUserStatus
(๊ฐ์ฒด)
socketId๋ฅผ key๋ก, ์
์ฐฐ์ ๋๋ค์, ๋ง์ง๋ง ์
์ฐฐ๊ฐ ๋ฑ ์ํ ์ ์ฅ
function normalizeProduct(raw) {
return {
prodKey: raw.prod_key,
aucKey: raw.auc_key,
prodName: raw.prod_name,
prodDetail: raw.prod_detail,
unitValue: raw.unit_value,
initPrice: raw.init_price,
currentPrice: raw.current_price,
finalPrice: raw.final_price,
winnerId: raw.winner_id,
prodStatus: raw.prod_status,
fileUrl: raw.file_url
};
}
join-room(roomId, userInfo)
socketRoomMap[socket.id] = roomId
socketIdMap[userInfo.loginId] = socket.id
auctionUserStatus[socket.id] = { nickname, lastBid: 0 }
user-status-update
๋ธ๋ก๋์บ์คํธtransports
, producers
, consumers
์์ socket ์์ ์ ๊ฑฐsocketRoomMap
, socketIdMap
, auctionUserStatus
์์ ์ ๊ฑฐuser-disconnected
produce(kind, rtpParameters, transportId, roomId)
transports[transportId]
ํ์ธproducers[roomId][producerId] = { producer, socketId, kind }
producerId
new-producer
get-existing-producers(roomId)
producers
Map(roomId โ Map(producerId โ { producer, socketId, kind }))auctionHostMap
(roomId โ hostSocketId){ existingProducers, hostSocketId }
๋ฐํclose-producer(roomId)
producers
Map์์ socket.id์ ํด๋นํ๋ Producer ๊ฐ์ฒด ์ ๊ฑฐ ๋ฐ close ํธ์ถuser-disconnected
์ด๋ฒคํธ (ํด๋น socketId, producerId) ์ ์กproducers
Map์์ ํด๋น roomId ์ญ์ consume(producerId, rtpCapabilities, transportId, roomId)
transports[transportId]
ํ์ธconsumers[consumerId] = { consumer, socketId }
{ consumerId, producerId, kind, rtpParameters }
consumer-resume(consumerId)
consumers[consumerId].consumer.resume()
chat-message(roomId, userId, message)
socketRoomMap[socket.id]
โ roomId ์ถ์ถchat-message
bid-attempt(roomId, productId, bidAmount,userLoginId)
auctionStates[auctionId]
โ ์ ํ๋ ์ํ ์ํ ๊ฐฑ์ auctionUserStatus[auctionId][socket.id]
โ ์
์ฐฐ์๋ณ ์
์ฐฐ ๊ธฐ๋ก ์ ์ฅpool.query()
โ ์ฌ์ฉ์ ์ ๋ณด ์กฐํ, ์ํ ๊ฐ๊ฒฉ ๊ฐฑ์ , ์
์ฐฐ ๊ธฐ๋ก ์ ์ฅuser-status-update
โ ์
์ฐฐ์ ์ํ ๊ฐฑ์ ์ ์ฒด ์ ์กbid-update
โ ์
์ฐฐ ๊ฒฐ๊ณผ(์ํ, ์
์ฐฐ์ ์ ๋ณด ํฌํจ) ์ ์กbid-rejected
โ ์
์ฐฐ ์คํจ ์ ๊ฐ๋ณ ์๋ตhost-selected-product(auctionId, product)
auctionStates[auctionId].selectedProduct = product
product.prod_status = 'P'
์
๋ฐ์ดํธhost-selected-product
(์ ํ๋ ์ํ ์ ๋ณด) ์ ์กbid-status(auctionId, prodKey, winner_id, status)
product.prod_status
์
๋ฐ์ดํธ ('C'
or 'F'
)auctionUserStatus[auctionId]
์์ ํด๋น ์ํ ์
์ฐฐ ๊ธฐ๋ก ์ญ์ bid-status
(์ํ ์ํ, ๋์ฐฐ์ ์ ๋ณด) ์ ์ก (ํธ์คํธ ์ ์ธ)user-status-update
(์ ์ฒด ์ ์ ์ํ ๊ฐฑ์ )revert-bidder(auctionId, prodKey, winnerId, finalPrice)
product.final_price
, winner_id
์
๋ฐ์ดํธauctionStates[auctionId].selectedProduct
์
๋ฐ์ดํธbid-update
(์
๋ฐ์ดํธ๋ ์ํยท๋์ฐฐ์ ์ ๋ณด) ์ ์กchange-bid-unit(roomId, newUnit)
auctionStates[roomId].selectedProduct.unitValue = newUnit
bid-unit-changed
auction-end(auctionId)
auction
ํ
์ด๋ธ status='์ข
๋ฃ', end_time=NOW()๋ก ์
๋ฐ์ดํธauction-ended
get-guest-counts(auctionIds[])
io.sockets.adapter.rooms
(๊ฐ ๊ฒฝ๋งค๋ฐฉ(room) ์ธ์ ์ ์กฐํ){ auctionId: ์ธ์์ }
๊ฐ์ฒด ๋ฐํvite.config.js
์ rollupOptions.input
์ ๊ฐ ์ง์
JSX ํ์ผ๋ค์ด ์ ์๋์ด ์์ผ๋ฉฐ, ๋น๋์ ๊ฒฐ๊ณผ๋ฌผ์ resources/static/bundle
๊ฒฝ๋ก์ JS/CSS ํ์ผ๋ก ์ถ๋ ฅ๋จ.${pageName}
๋ณ์๋ฅผ ํตํด ํด๋น JS/CSS๋ฅผ ๋์ ์ผ๋ก ๋ก๋ํ๊ณ , <div id="root">
์ React ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ๋จ. โ SPA๊ฐ ์๋ ๋ฉํฐ ํ์ด์ง ๊ธฐ๋ฐ ๊ตฌ์กฐ์.vite.config.js
์ server.https
์ค์ ์ **๋ก์ปฌ ๊ฐ๋ฐ์ฉ(ํ
์คํธ์ฉ HTTPS)**์ด๋ฉฐ, ๋ฐฐํฌ ํ๊ฒฝ์์๋ ์ฌ์ฉ๋์ง ์์.import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import fs from 'fs';
import path from 'path';
export default defineConfig({
plugins: [react()],
root: 'src/main/react',
build: {
outDir: '../resources/static/bundle',
emptyOutDir: true,
cssCodeSplit: true,
rollupOptions: {
input: {
home: path.resolve(__dirname, 'src/home/home.jsx'),
login: path.resolve(__dirname, 'src/auth/login/login.jsx'),
bidGuest: path.resolve(__dirname, 'src/bidGuest/bidGuest.jsx'),
// ... ์๋ต ๊ฐ๋ฅ
},
output: {
entryFileNames: 'js/[name].bundle.js',
chunkFileNames: 'chunk/[name].chunk.js',
assetFileNames: `css/[name].css`,
},
},
},
server: {
https: {
key: fs.readFileSync('certs/key.pem'),
cert: fs.readFileSync('certs/cert.pem'),
},
host: '0.0.0.0',
port: 3200,
},
});
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>[[${pageName}]]</title>
<link rel="stylesheet" th:href="@{'/bundle/css/' + ${pageName} + '.css'}" onerror="this.remove()" />
</head>
<body>
<div id="root"></div>
<script type="module" th:src="@{'/bundle/js/' + ${pageName} + '.bundle.js'}"></script>
</body>
</html>
package com.project.bidcast.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import javax.servlet.http.HttpSession;
@Controller
public class MainController {
@GetMapping("/")
public String redirectToHome() {
return "redirect:/home.do";
}
@GetMapping("/{pageName}.do") // ๋ชจ๋ ํ์ด์ง ์์ฒญ์ ์ฒ๋ฆฌ
public String page(HttpSession session , @PathVariable String pageName, Model model) {
model.addAttribute("pageName", pageName); // Thymeleaf์ pageName ์ ๋ฌ
System.out.println("๋ทฐ์ด๋ฆ:" + pageName);
if(session.getAttribute("id") != null)
System.out.println(session.getAttribute("id"));
return "view"; // ํญ์ ๋์ผํ view.html ํ
ํ๋ฆฟ์ผ๋ก ์ด๋
}
}
server: {
https: {
key: fs.readFileSync('certs/key.pem'),
cert: fs.readFileSync('certs/cert.pem'),
},
host: '0.0.0.0',
port: 3200,
}
๋ณ์๋ช | ์ค๋ช |
---|---|
peers |
์ ์ฒด ์ฐธ๊ฐ์์ ์์ผ ID๋ฅผ ํค๋ก ํ๋ ๊ฐ์ฒด ์: { socketId: { stream } } |
hostSocketId |
ํธ์คํธ(๋ฐฉ์ก ์ก์ถ์)์ ์์ผ ID |
mySocketId |
ํ์ฌ ํด๋ผ์ด์ธํธ(ํธ์คํธ)์ ์์ผ ID |
userInfoMap |
์ฐธ๊ฐ์๋ณ ๋๋ค์, ์ ์ฐฐ๊ฐ ๋ฑ์ด ์ ์ฅ๋ ๊ฐ์ฒด |
selectedProductIdx |
ํ์ฌ ๊ฒฝ๋งค ์ค์ธ ์ํ์ ์ธ๋ฑ์ค |
products |
๊ฒฝ๋งค ์ํ ๋ฆฌ์คํธ ๋ฐฐ์ด |
prevHighestBidder |
์ด์ ์ต๊ณ ์ ์ฐฐ์ ์ ๋ณด (์ฌ์ ์ฐฐ ๋ฐ์ ์ ์ฌ์ฉ) |
์ด๋ฒคํธ๋ช | ๋ชฉ์ ๋ฐ ์ค๋ช | ๋ฐ์ ์ | ์์ ์ |
---|---|---|---|
host-selected-product |
ํธ์คํธ๊ฐ ํน์ ๊ฒฝ๋งค ์ํ์ ์ ํํ์์ ์๋ฒ์ ์๋ฆผ | ํธ์คํธ | ์๋ฒ, ํด๋ผ์ด์ธํธ |
bid-status |
๋์ฐฐ(์๋ฃ), ์ ์ฐฐ(์คํจ) ์ํ๋ฅผ ์๋ฒ์ ์ ๋ฌ | ํธ์คํธ | ์๋ฒ, ํด๋ผ์ด์ธํธ |
revert-bidder |
์ต๊ณ ์ ์ฐฐ์๋ฅผ ์ด์ ์ ์ฐฐ์๋ก ๋๋๋ฆด ๋ ์๋ฒ์ ์์ฒญ | ํธ์คํธ | ์๋ฒ, ํด๋ผ์ด์ธํธ |
peers[hostSocketId].stream
ํํ๋ก ๊ด๋ฆฌํ๋ฉฐ, ์ด๋ฅผ ๋ฉ์ธ ๋น๋์ค ์ปดํฌ๋ํธ(MainVideo.jsx
)์ ์ ๋ฌํด ์ก์ถํจpeers
๊ฐ์ฒด๋ฅผ ํตํด ์์ , VideoGrid.jsx
๋ฅผ ํตํด ํ๋ฉด์ ํ์๋ณ์๋ช | ์ค๋ช |
---|---|
peers |
์ ์ฒด ์ฐธ๊ฐ์์ ์์ผ ID๋ฅผ ํค๋ก ํ๋ ๊ฐ์ฒด ์: { socketId: { stream } } |
hostSocketId |
ํธ์คํธ ์์ผ ID |
mySocketId |
ํ์ฌ ํด๋ผ์ด์ธํธ(๊ฒ์คํธ)์ ์์ผ ID |
userInfoMap |
์ฐธ๊ฐ์๋ณ ๋๋ค์, ์ ์ฐฐ๊ฐ ๋ฑ์ด ์ ์ฅ๋ ๊ฐ์ฒด |
product |
ํ์ฌ ๊ฒฝ๋งค ์ค์ธ ์ํ ์ ๋ณด |
์ด๋ฒคํธ๋ช | ๋ชฉ์ ๋ฐ ์ค๋ช | ๋ฐ์ ์ | ์์ ์ |
---|---|---|---|
bid-attempt |
์ ์ฐฐ ์๋ ์์ฒญ | ๊ฒ์คํธ | ์๋ฒ, ํด๋ผ์ด์ธํธ |
bid-update |
์ ์ฐฐ๊ฐ ๋ณ๊ฒฝ ์๋ฆผ | ์๋ฒ | ๋ชจ๋ ํด๋ผ์ด์ธํธ |
host-selected-product |
ํธ์คํธ๊ฐ ์ํ์ ์ ํํ์์ ์๋ฆผ | ์๋ฒ | ํด๋ผ์ด์ธํธ ์ ์ |
bid-status |
๋์ฐฐ, ์ ์ฐฐ ์ํ ์ ๋ฐ์ดํธ | ์๋ฒ | ํด๋ผ์ด์ธํธ ์ ์ |
peers[mySocketId].stream
์ผ๋ก ๊ด๋ฆฌpeers
๊ฐ์ฒด๋ฅผ ํตํด ๋ฐ์ VideoGrid
์ปดํฌ๋ํธ์ ํ์๊ธฐ๋ฅ | ์์ ์ฃผ์ฒด | ์ด๋ฒคํธ ์ด๋ฆ | ์๋ฒ ์ฒ๋ฆฌ ๋ด์ฉ | ํด๋ผ์ด์ธํธ ์ฒ๋ฆฌ ๋ด์ฉ |
---|---|---|---|---|
์ ์ฐฐ ์๋ | ๊ฒ์คํธ | bid-attempt |
์
์ฐฐ๊ฐ ์ ํจ์ฑ ๊ฒ์ฌ ๋ฐ ์ต๊ณ ์
์ฐฐ์ ๊ฐฑ์ ํ bid-update ๋ฐฉ์ก |
bid-update ์์ ํ UI ๋ฐ ์ํ ๊ฐฑ์ |
๊ฒฝ๋งค ์ํ ์ ํ | ํธ์คํธ | host-selected-product |
์ํ ๋ณ๊ฒฝ ์ ์ฅ ๋ฐ ๋ชจ๋ ํด๋ผ์ด์ธํธ์ ๋ฐฉ์ก | ์ํ ์ ๋ณด ๊ฐฑ์ ๋ฐ UI ๋ณ๊ฒฝ |
๋์ฐฐ/์ ์ฐฐ ์ํ ๋ณ๊ฒฝ | ํธ์คํธ | bid-status |
๊ฒฝ๋งค ์ํ ์ ์ฅ ๋ฐ ๋ฐฉ์ก | ์ํ ๋ฉ์์ง ๋ฐ UI ๊ฐฑ์ |
์ต๊ณ ์ ์ฐฐ์ ๋๋๋ฆฌ๊ธฐ | ํธ์คํธ | revert-bidder |
์ต๊ณ ์ ์ฐฐ์ ์ ๋ณด ์ฌ์ค์ ๋ฐ ๋ฐฉ์ก | ์ ์ฐฐ์ ์ ๋ณด ์ฌ๊ฐฑ์ |
WebRTC ๋ฏธ๋์ด ์ก์ถ | ํธ์คํธ/๊ฒ์คํธ | WebRTC ์๊ทธ๋๋ง | SFU ์ญํ ๋ก ๋ฏธ๋์ด ์คํธ๋ฆผ ์ค๊ณ | ์คํธ๋ฆผ ์์ฑ ๋ฐ ์์ , ๋น๋์ค ์ปดํฌ๋ํธ์ ์ถ๋ ฅ |
์ค์๊ฐ ์ฑํ | ํธ์คํธ/๊ฒ์คํธ | chat-message |
๋ฉ์์ง ์ค๊ณ ๋ฐ ๋ธ๋ก๋์บ์คํธ | ๋ฉ์์ง ์์ ๋ฐ UI ์ ๋ฐ์ดํธ |
BCryptPasswordEncoder
๋ฅผ ์ฌ์ฉํด ๋น๋ฐ๋ฒํธ๋ฅผ ์์ ํ๊ฒ ์ํธํ/css/**
, /js/**
, /img/**
, /home.do
, /login.do
๋ฑ)๋ ์ธ์ฆ ์์ด ์ ๊ทผ ํ์ฉSessionCreationPolicy.IF_REQUIRED
)/login.do
ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ/login.do
/login
/home.do
๋ก ์ด๋/logout
/home.do
๋ก ์ด๋JSESSIONID
์ฟ ํค ์ญ์ ์ค์ ํญ๋ชฉ | ๋ด์ฉ ๋ฐ ์ญํ |
---|---|
passwordEncoder() |
๋น๋ฐ๋ฒํธ๋ฅผ BCrypt ๋ฐฉ์์ผ๋ก ์ํธํ |
csrf().disable() |
CSRF ๊ณต๊ฒฉ ๋ฐฉ์ด ๊ธฐ๋ฅ ๋นํ์ฑํ (API ํ๊ฒฝ์ ์ ํฉ) |
authorizeHttpRequests() |
์ธ์ฆ ์์ด ์ ๊ทผ ๊ฐ๋ฅํ URL ๊ฒฝ๋ก ์ง์ |
.anyRequest().authenticated() |
๋๋จธ์ง ๋ชจ๋ ์์ฒญ์ ์ธ์ฆ ํ์ |
sessionManagement() |
์ธ์
์์ฑ ์ ์ฑ
์ค์ (IF_REQUIRED : ํ์์๋ง ์์ฑ) |
exceptionHandling() |
์ธ์ฆ ์คํจ ์ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ ์ฒ๋ฆฌ |
formLogin() |
๋ก๊ทธ์ธ ํ์ด์ง, ์ฒ๋ฆฌ URL, ์ฑ๊ณต ๋ฐ ์คํจ ํธ๋ค๋ฌ ์ค์ |
logout() |
๋ก๊ทธ์์ URL, ์ฑ๊ณต ํ ์ด๋ ํ์ด์ง, ์ธ์ ๋ฌดํจํ, ์ฟ ํค ์ญ์ |