2์ฐจ Project ํ๊ณ ๋ก
๐ 2์ฐจ ํ๋ก์ ํธ ์๊ฐ
์ ํ๋ธ ์์ ๐
๋ณด๋ฌ๊ฐ๊ธฐ โฌ๏ธ
Github [BackEnd]
๊ด์ฐฐ ์ฌ์ดํธ : NIKE
ํ ๋ช : Just Do It
ํ๋ก์ ํธ ๋ชฉํ : Wecode์์ ๋ฐฐ์ด๋ด์ฉ์ ํ์ฉํ์ฌ ๊ด์ฐฐํ ์ฌ์ดํธ์ ๊ธฐ๋ฅ๊ฐ๋ฐ ๋ฐ ๋ ์ด์์ ๊ทธ๋ฆฌ๊ธฐ
์งํ๊ธฐ๊ฐ : 2021๋ 1์ 17์ผ ~ 2022๋ 1์ 29์ผ ( total : 12days )
ํ๋ก์ ํธ ์ฐธ์ฌ์ : ์ด์คํ, ๊น์์ฑ, ์ด์ง์ , ํฉํฌ์ค
- ๊ธฐ์ ์คํ -
BackEnd ๊ธฐ์ ์คํ
- Page & API -
๊น์์ฑ
์ด์ง์
ํฉํฌ์ค
๐ง ํ๊ณ
1์ฐจ ํ๋ก์ ํธ๋ e-commerce์ฌ์ดํธ๋ฅผ ํด๋ก ํ์๊ธฐ์ 2์ฐจ ํ๋ก์ ํธ๋ e-commerce์ฌ์ดํธ๊ฐ ์๋ ๋ค๋ฅธ ํ๋ซํผ์ ํ๊ณ ์ถ์์ง๋ง
์์ฝ๊ฒ๋ ๋์ดํค๋ฅผ ๋ชจํฐ๋ธ๋กํ๋ ํ๋ก์ ํธ๋ฅผ ๋งก๊ฒ๋์๋ค.
๊ทธ๋๋ ๋์ดํค๋ง์ ๊ฐ์ฑ์ด ์์ผ๋ ์๊ด์ ์๋ค.
ํ๊ณ ๋ ๊ธฐ์ต์ ๋จ๋๊ฒ ๋ช๊ฐ์ง๋ง ์ฐ๋๋ก ํ๊ฒ ๋ค.
๐คฎ [ํ๊ณ -1]
DB ๋ฐ์ดํฐ์์
์ฐ์ ๋์ดํค์ ํน์ง์ ๋ง์ ์ ํ๋ค, ๊ทธ๋ฆฌ๊ณ ๊ทธ ์ ํ๋ค์ ์ธ๋ถํ์์ผ์ฃผ๋ ํํฐ๊ธฐ๋ฅ, ์ถ์ฒจ๊ธฐ๋ฅ ์ด๋ ๊ฒ 3๊ฐ์ง์ด๋ค.
๋๋ ์์ฑ๋์ DB ๋ชจ๋ธ๋ง์ด ๋๋์๋ง์ ๋์ดํค์ ํน์ง์ ์ด๋ฆฌ๊ธฐ ์ํด DB์ ๋ค์ด๊ฐ ์ ํ๋ค, ์ ํ์ฌ์ง๋ค์
์์งํ๊ณ ์ง์ด๋ฃ์๋๋ฐ.. ์ด 166๊ฐ์ ์ ํ๊ณผ x4 ๋งํผ์ ์ฌ์ง์ DB์ ๋ฃ์๋ค.
ํนํ ๊ฐ ์ ํ๋ณ ์ฌ์ด์ฆ์ ์๋์ ์ง์ด๋ฃ๋ ๊ณผ์ ์ด ๊ดด๋ํ๋๋ฐ
const style_code = ['ACA', 'ACB', 'ACC', 'ACD', 'ACE'];
for (let j = 0; j < style_code.length; j++) {
for (let k = 1; k < 4; k++) {
for (let i = 1; i < 10; i++) {
console.log(`("${style_code[j]}-000${k}",${i},80),`);
}
}
} // ์ ํํ ์๊ฐ์ ์๋์ง๋ง ๋์ถฉ ์ด๋ฌํ ํ์์ด์๋ ๊ฒ ๊ฐ๋ค.
๋์ ํ ์์์ ์ผ๋ก ํ ์๊ฐ ์๋ค๊ณ ํ๋จํ์ฌ ์๋ฆฌ๊ณ ์ฆ์ ํตํด DB์ ๋ฃ์๋ค.
๋ช๋ช ๋ค๋ฅธ์์ ๋ค๋ ์์์ ์ด ํ๋ค๋ค ์ถ์๋ ์๊ณ ๋ฆฌ์ฆ์ ํตํด ๊ฐํธํ๊ฒ ์์ฑํ์ฌ DB์ ๋ฃ์๋ค.
์ ํ ์๊ฐ์น๋ ๋ชปํ ๋ถ๋ถ์์ ์๊ฐ์ ๋ง์ด ์ก์๋จน์ด์ ์กฐ๊ธ ๋๋ฌ๋๋ฐ,
์์ผ๋ก DB ๋ฐ์ดํฐ์์ ์ด ๋ง์ ํ๋ก์ ํธ๋ผ๋ฉด ์ํ๋ก ๋ช๊ฐ๋ง ๋ฃ๊ณ
๋ชจ๋ API๊ฐ ์์ฑ๋์ด์ก์๋ ๋ฐ์ดํฐ๋ฅผ ๋ฃ๋๊ฒ์ด ํ๋ช ํ ํ๋จ์ผ ๊ฒ ๊ฐ๋ค.
๐ [ํ๊ณ -2]
์ํต
๋ฐ์ดํฐ์์ , prisma๋ก ์คํค๋ง ๋ง๋ค๊ธฐ ๋ฑ ๋ค์ํ ๊ฒ๋ค์
vsCode์ Rive Share์ Zoom, Slack์ ์ด์ฉํ์ฌ ์์ฑ๋๊ณผ ํจ๊ปํ๋ ํฌ๋ก์ค์ฒดํน์ ํตํด ๋์น๋ ๊ฒ์ด ์์๋ค.
(๋ฌผ๋ก ๋๋ค ๋ชฐ๋๋๊ฒ์ ์ ์ธํ๊ณค)
์ด๋ฌํ ๊ณผ์ ๋๋ฌธ์ด์์๊น? ์์ฑ๋๊ณผ ์ผ๋ฏธ๊ฐ ์๋ง์๊ณ ์ํต์ ๋ํ ๋ถํธํจ?์ ๋๋ผ์ง ๋ชปํ๋ค.
๋ํ ์์ฑ๋์ด ์ฑ ์๊ฐ๋ ๊ฐ๊ณ ํ๋ก์ ํธ์ ์ํ๋๊ฑธ ์๊ฒ๋์ด ๋ฏฟ๊ณ ๋งก๊ธธ ์ ์์๊ณ
์์งํ ํ๋ก์ ํธ ๊ธฐ๊ฐ๋ด๋ด ๋ฐฑ์๋ ์์ ์ ์ฌ๋ฐ์๋ค.
ํ์ง๋ง front์์ ์ํต์ด ๋ฌธ์ ์๋ค.
๋๋ช ์ด์ UI์์ ์ ๋ชจ๋ ํ๋ ค๊ณ ํ๋ค๋ณด๋ ๋๋ฌด ๋ฐ์๊ฒ ๊ฐ์๊ณ
๊ทธ ๋๋ฌธ์ ์๊ฐ๋ณด๋ค ์ฐ๋ฆฌ์์ ๊ต๋ฅ๊ฐ ์์๋ค.
โAPI๋ฅผ ~~~ํ ํํ๋ก ๋ณด๋ด์คฌ์ผ๋ฉด ์ข๊ฒ ๋ค.โ ๋ผ๋ ๊ฒ ๋ํ ์์๊ธฐ์
1์ฐจ ํ๋ก์ ํธ๋ front๋ฅผ ํ๋ ๊ฒฝํ์ ์ด๋ ค front๊ฐ ์ฌ์ฉํ๊ธฐ ํธํ ํํ๋ก API๋ฅผ ๋ง๋ค์๋ค.
๊ทธ๋ฆฌ๊ณ front๋ถ๋ค์ด โ์ด๋ป๊ฒ ์์ฒญ์ ๋ ๋ ค์ผํ๋์ง๋ฅผโ ์๊ธฐ์ํด back์ code๋ฅผ ๋ณด๋ฉฐ
์๊ฐ์ ํ๋นํ๋๊ฒ์ ๋ง๊ธฐ์ํด ๊ฐ API๋ง๋ค ๋ช ์ธ์๋ฅผ ํ์ผ๋ก ๋ง๋ค์ด์ Slack๋ฐฉ์๋ค๊ฐ ๊ณต์ ํ์๋ค.
์์๊ฐ์ด API ๋ช ์ธ์๋ฅผ ๋ง๋ค์ด front์๊ฒ ์ ๋ฌํ๋
๋์ ์๋๋ฅผ ์์ธํ ๋งํ ์ ์์๊ณ ์๋๋ฐฉ ๋ํ ๋น ๋ฅด๊ฒ ์ดํดํ๋ค.
ํ๋ก ํธ์ ์ฅ์์ APIํํ๋ฅผ ์์ฒญ์ ๋ ๋ฆฌ์ง ์์๋ ์ ์ ์์๊ณ
back์ ์ ๊ฒฝ์ฐ์ง์๊ณ ์์ ์ ๋ชฐ๋ํ ์ ์๊ฒ๋์ด ์ข์๋ค๊ณ ํ๋ค.
๐ค [ํ๊ณ -3]
SQL์ ์ข ๋ ๊ณต๋ถํด๋ณด์
์ด๋ฒ ํ๋ก์ ํธ๋ฅผ ํ๋ฉด์ SQL๋ก ํ ์ ์๋๊ฒ์ด ์๊ฐ๋ณด๋ค ๋ง๋ค๋ ๊ฒ์ ์๊ฒ๋์๋ค.
์ง๊ธ๊น์ง Dao๋จ์์๋ ๊ธฐ์ด์ ์ธ๊ฒ๋ค๋ก ๊ตฌํํ๊ณ ๋ชจ๋ ๊ฐ๊ณต์ Service๋จ์์ ์ฒ๋ฆฌํ์๋๋ฐ
๊ทธ๋ฌ๋ค๋ณด๋ ์ธ๋ฐ์๋ DB์ ์ ์์ด ๋์๊ณ ์ฝ๋ ๋ํ ๊ฐ๋ ์ฑ์ด ์ข์ง ์์๋ค.
์ด๋ฌํ ๋ถ๋ถ์ ์ฝ๋๋ฆฌ๋ทฐ๋ก ์ง์ ๋ฐ์ ๊ตฌ๊ธ๋ง์ ํ๋ค๋ณด๋ โ์ฟผ๋ฆฌ๋ฌธ์ผ๋ก ์ด๋ฐ๊ฒ๋ ํ ์ ์์ด?โ ๋ผ๋ ์๊ฐ๊ณผ ํจ๊ป ๋์ ๋ฌด์งํจ์ ์๊ฒ๋์๋ค.
์ด๊ธ์์ค์ธ ๋์๊ฒ ๊ทธ ๊น์ด๋ ์๋ํ๊ธฐ์ ํ๋์ ์ ๋ณด์ ๋ช์ ๋น ์ ธ ์ฝ๊ฐ์ ์ข์ ๊ฐ์ ๋น ์ ธ์์๋ค.
ํ์ง๋ง ์ด์ ์์ํ๋ ๋จ๊ณ์์ ์ธ์ ํ์ ๋ง์์ด ์กฐ๊ธ ํธํด์ก๊ณ ๋งค์ผ ์๋กญ๊ฒ ์๊ฒ๋๋ ์ฟผ๋ฆฌ๋ฌธ์ ์ ๋ฆฌํ๊ธฐ๋ก ๋ง์๋จน์๋ค.
๐งโ๐ป Backend (Just Do It)
- ์ฌ์ DB ๋ชจ๋ธ๋ง -
๊ธฐ๋ณธ์ ์ผ๋ก e-commerce์ฌ์ดํธ ํน์ง์ธ products๊ฐ ์ค์ฌ์ด ๋์ด ๋๋ถ๋ถ์ table์ด ์ฐ๊ฒฐ๋์๊ณ
๋์ดํค๋ ํํฐ๊ธฐ๋ฅ์ด ์๋นํ ๋ง๊ธฐ์ ๊ทธ์ ๋ง๊ฒ table์ด ๋๋ ์ ธ ์๋๊ฒ์ ๋ณผ ์ ์๋ค.
๋์ดํค๋ ํ๊ฐ์ง ํน์ง์ด ์๋๋ฐ ๊ทธ๊ฒ์ ๋ฐ๋ก ์ถ์ฒจ(Draw)๊ธฐ๋ฅ์ด๋ค.
์ถ์ฒจ์ ํ๊ธฐ ์ํ ์ํ์ ์ฅ๋ฐ๊ตฌ๋์๋ ๋ค์ด๊ฐ์ง ์์ผ๋ฉฐ ๋๊ธ๋ํ ๋ฌ ์ ์๊ธฐ์
products๊ฐ ์๋ snkrs table์ ๋ฐ๋ก ๋ด์ ํน๋ณ์ทจ๊ธํ์๋ค.
๋ํ ์ถ์ฒจ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ์ํด ์ถ์ฒจ์ ํ ์ฌ๋๋ค์ ๊ด๋ฆฌํ๋ table์ธ snkrs_data
๋น์ฒจ๋ ์ฌ๋๋ค์ ๊ด๋ฆฌํ๋ table์ธ snkrs_winners๋ฅผ ๋ง๋ค์๋ค.
Detail API
models/productDao.js
const getProductData = async style_code => {
return await prisma.$queryRaw`
SELECT
products.style_code,
products.name,
categories.name as category,
product_colors.name as color,
product_colors.color_hex as hex,
product_genders.name as gender,
normal_price,
sale_rate,
sale_price,
is_member,
sub_icon.name as shoes_type,
sub_brand.name as brand,
sub_clothes.name as clothes_type,
sub_accessories.name as acc_type,
(
SELECT JSON_ARRAYAGG(JSON_OBJECT('url', product_img_urls.name, 'is_main', is_main))
FROM product_img_urls
JOIN products ON products.style_code = product_img_urls.style_code
WHERE product_img_urls.style_code = ${style_code}
) AS img,
(
SELECT JSON_ARRAYAGG(JSON_OBJECT('size', product_sizes.name, 'quantity', product_with_sizes.quantity))
FROM product_with_sizes
JOIN products ON products.style_code = product_with_sizes.style_code
JOIN product_sizes ON product_size_id = product_sizes.id
WHERE product_with_sizes.style_code = ${style_code}
) AS info
FROM
products
JOIN
categories ON category_id = categories.id
LEFT JOIN
product_colors ON color_id = product_colors.id
JOIN
product_genders ON gender_id = product_genders.id
LEFT JOIN
sub_icon ON sub_icon_id = sub_icon.id
LEFT JOIN
sub_brand ON sub_brand_id = sub_brand.id
LEFT JOIN
sub_clothes ON sub_clothes_id = sub_clothes.id
LEFT JOIN
sub_accessories ON sub_accessories_id = sub_accessories.id
WHERE
products.style_code = ${style_code};
`;
};
์ํ์ ๋ํ ์ผ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ์ฟผ๋ฆฌ๋ฌธ์ด๋ค.
ํ๋์ ์ ํ์ 4๊ฐ์ฉ ์๋ ์ด๋ฏธ์ง์ 10๊ฐ์ฉ ์๋ ์ฌ์ด์ฆ๋ฅผ โ์ด๋ป๊ฒ ๊ฐ์ฒดํ ์์ผ์ ๊ฐ์ ธ์ฌ๊น?โ์ ๋ํด ๋ง์ด ๊ณ ๋ฏผํ์๋ค.
์ฒ์์๋ GROUP_CONCAT๊ณผ CONCAT์ผ๋ก [,]์ผ๋ก ๋ฐฐ์ด์ ๋ชจ์์ ๋ง๋ค๊ณ ๊ทธ๊ฒ์ {,}๋ก ๊ฐ์ฒด๋ฅผ ๋ง๋ค๋ ค๊ณ ํ์๋ค.
์ด์ฐ์ ์ฐ ๋ง๋ค๊ธด ํ์์ผ๋ ์ฌํ์ฉํ๊ธฐ์๋ ๋๋ฌด๋ ์ฝ๋๊ฐ ๊ฐ๋ ์ฑ์ด ๋จ์ด์ง๊ณ ๋ณต์กํด๋ณด์๋ค.
๊ทธ๋ฌ๋์ค JSON์ ์ฌ์ฉํ์ฌ ์์ฝ๊ฒ ๋ฐฐ์ด๊ณผ ๊ฐ์ฒด๋ฅผ ๋ง๋ค ์ ์๋ค๋ ๊ฒ์ ๊ตฌ๊ธ๋ง์ ํตํด ์์๋๋ค.!
JSON_OBJECT์ผ๋ก ๊ฐ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ฒดํ ์ํค๊ณ JSON_ARRAYAGG์ ํตํด ๋ฐฐ์ด๋ก ๋ง๋ค์ด์ค๋ค.
์๊ฐ๋ณด๋ค ์ฌ์ฉ๋ฒ๋ ๊ฐ๋จํ๊ณ ๊ฒฐ๊ณผ๋ฌผ๋ ์ข์์ ์์ฃผ ์ฌ์ฉํ ๊ฒ ๊ฐ๋ค.
{
"message": "์ฑ๊ณต",
"data": {
"style_code": "AAA-0001",
"name": "๋์ดํค ์์ดํฌ์ค 1 GORE-TEX",
"category": "shoes",
"color": "์ฃผํฉ์",
"hex": "#EB621D",
"gender": "๋จ์ฑ",
"normal_price": 189000,
"sale_rate": null,
"sale_price": null,
"is_member": 0,
"shoes_type": "์์ด ํฌ์ค 1",
"brand": "๋์ดํค ์คํฌ์ธ ์จ์ด",
"clothes_type": null,
"acc_type": null,
"img": [
{
"url": "/Images/Men/Shoes/Airforce/1/1-1.png",
"is_main": 1
},
{
"url": "/Images/Men/Shoes/Airforce/1/1-2.png",
"is_main": 0
},
{
"url": "/Images/Men/Shoes/Airforce/1/1-3.png",
"is_main": 0
},
{
"url": "/Images/Men/Shoes/Airforce/1/1-4.png",
"is_main": 0
}
],
"info": [
{
"size": "220",
"quantity": 80
},
{
"size": "230",
"quantity": 80
},
{
"size": "240",
"quantity": 80
},
...
]
}
}
Cart API
๋ชจ๋๋ค ์ค๋ช ํ ์ ์์ผ๋ Cart API์ ํ ๋ถ๋ถ์ธ Create ๋ถ๋ถ๋ง ๋ณด๋๋ก ํ๋ค.
middleWare/authorization.js
const authentication = async (req, res, next) => {
try {
const token = req.body.user_id;
const validToken = verifyToken(token);
if (validToken) {
const [check] = await userDao.isExistUser(validToken.id[0].id);
const isExsitUser = new IsExistItem(check, resultType, 404);
isExsitUser.notExistErr('์กด์ฌํ์ง ์๋ UserId ์
๋๋ค.');
req.body.user_id = validToken.id[0].id;
} else {
const err = new Error('ํ ํฐ์ด ์ ํจํ์ง ์์ต๋๋ค.');
err.status = 401;
throw err;
}
next();
} catch (err) {
res.status(err.status || 500).send({ message: '์คํจ', err: err.message });
}
};
ํด๋ผ์ด์ธํธ๊ฐ user_id๋ฅผ ๊ฐ๋ ํ ํฐ์ ๋ด์ ์์ฒญ์ ๋ณด๋ด๋ฉด
ํ ํฐ์ ํ์ธํ์ฌ ์ ํจํ์ง ํ์ธํ๊ณ , ์ ํจํ์ง ์๋ค๋ฉด ์ธ์ฆ์๋ฌ์ธ 401์ ๋์ด๋ค.
๋ํ ํ ํฐ์ ์ ํจํ์ง๋ง DB์ ์๋ user๋ผ๋ฉด ์์์ด ์กด์ฌํ์ง์๋๋ค๋ ๋ป์ด๋ฏ๋ก 404์๋ฌ๋ฅผ ๋์ด๋ค.
utils/err.js
class RequiredKeys {
constructor(REQUIRED_KEYS) {
this.REQUIRED_KEYS = REQUIRED_KEYS;
}
verify() {
for (let key in this.REQUIRED_KEYS) {
if (!this.REQUIRED_KEYS[key] && this.REQUIRED_KEYS[key] !== 0) {
const err = new Error(`${key} ์ ๋ณด๊ฐ ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค.`);
err.status = 400;
throw err;
}
}
}
}
class AffectedRow {...} // ์๋ต
class IsExistItem {...} // ์๋ต
export { RequiredKeys, AffectedRow, IsExistItem };
์์ฃผ ์ฌ์ฉ๋๋ ์๋ฌํจ์๋ค์ ์บก์ํ ํ์ฌ ๊ด๋ฆฌํ์๋ค.
ํ๋๋ก ํฉ์น ๊น ์๊ฐํ์์ง๋ง ์ผ๋จ์ ๊ฐ๊ฐ ๋๋ ๋์๋ค.
(๋๋ฌด ๊ธธ์ด์ง๋ฏ๋ก ๋๋จธ์ง๋ ์๋ต)
controllers/cartControllers.js
const createCart = async (req, res) => {
try {
const { style_code, user_id, size, quantity } = req.body;
const REQUIRED_KEYS = { style_code, user_id, size, quantity };
const keys = new RequiredKeys(REQUIRED_KEYS);
keys.verify();
const result = await cartServices.createCart(
style_code,
user_id,
size,
quantity
);
res.status(201).send({ message: '์ฑ๊ณต', result });
} catch (err) {
res.status(err.status || 500).send({ message: '์คํจ', err: err.message });
}
};
์ปจํธ๋กค๋ฌ๋จ์์๋ ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญ์ ํ์ํ ๊ฐ๋ค์ ์ ๋๋ก ๋ฃ์๋์ง ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ๊ณ
์๋๋ผ๋ฉด 400์๋ฌ๋ฅผ ๋์ฐ๋๋ก ํ๋ค.
์ฑ๊ณต์์๋ 201์ฝ๋๋ก ๋ฐํํด์ฃผ์๋ค.
services/cartServices.js
import { cartDao, productDao } from '../models';
import { resultType } from '../type';
import { IsExistItem } from '../utils/err';
const createCart = async (style_code, user_id, size, quantity) => {
const [check] = await cartDao.checkCart(style_code, user_id, size);
const [checkSize] = await productDao.isExistSizes(style_code, size);
const [checkStyleCode] = await productDao.isExistStyleCode(style_code);
const isExistStyleCode = new IsExistItem(checkStyleCode, resultType, 404);
isExistStyleCode.notExistErr('์ ํจํ์ง ์๋ styleCode ์
๋๋ค.');
const isExistSize = new IsExistItem(checkSize, resultType, 404);
isExistSize.notExistErr('์ ํจํ์ง ์๋ size ์
๋๋ค.');
const isExistItem = new IsExistItem(check, resultType, 409);
isExistItem.existErr('์ด๋ฏธ ํ๋ชฉ์ ์์ต๋๋ค.');
await cartDao.createCart(style_code, user_id, size, quantity);
const data = await cartDao.getCartList(user_id);
return data;
};
์ด๋ฏธ Create๋ ์ ํ์ด๋ผ๋ฉด 409์๋ฌ๋ฅผ ๋์ฐ๋๋ก ํ์๋ค.
์๋น์ค๋จ์์๋ ํ๋ฒ๋ ์์ฒญ์ ๋ํ ๊ฐ์ ์ ํจ์ฑ ๊ฒ์ฌํ์ฌ 404์๋ฌ๋ฅผ ๋์ฐ๋๋ก ํ๋ค.
์ฌ์ค 404์๋ฌ๋ค์ ํ๊ณณ์ ๋ชจ์ middleWare๋ก ๋ง๋๋๊ฒ ๋ ์ข์๊ฒ ๊ฐ์์ง๋ง
ํ์๋ค๊ณผ ์ฌ์ ํฉ์๊ฐ ๋์ง์์ ์ํฉ์์ ๋ ๋จ์ ์ผ๋ก ํ ์๊ฐ์์ด ์ด๋๋ก ๋์๋ค.
๊ทธ๋ฆฌ๊ณ ์นดํธ์ Create๊ฐ ์ฑ๊ณตํ์์
ํ๋ก ํธ์์ ์นดํธ๋ฆฌ์คํธ๋ฅผ ๋ณด์ฌ์ฃผ๋ API๋ฅผ ๋ ์์ฒญํ๋๊ฒ์ด ๊ท์ฐฎ์์๋(๋๋ ์ด๋ ค์์ด) ์์๊ฒ ๊ฐ์
getCartList๋ฅผ return ํด์ฃผ์๋ค.
models/cartDao.js
import { PrismaClient } from '@prisma/client';
import { createType, updateType, deleteType } from '../type';
import { AffectedRow } from '../utils/err';
const prisma = new PrismaClient();
const createCart = async (style_code, user_id, size, quantity) => {
await prisma.$queryRaw`
INSERT INTO
carts(
style_code,
user_id,
size,
quantity
)
VALUES
(
${style_code},
${user_id},
${size},
${quantity}
);
`;
const [row] = await prisma.$queryRaw`
SELECT ROW_COUNT() as result;
`;
const newRow = new AffectedRow(row, createType, 409);
newRow.result();
return;
};
์ฌ์ค ์ด๋ถ๋ถ์ ๋ฃ์์ง ๋ง์ง ๊ณ ๋ฏผํ๋ค.
ํ์ง๋ง ๋ณด๋ค ์์ฑ๋ ๋์ API๋ฅผ ๋ง๋ค๊ธฐ ์ํด
Create๋ ํ Affected Row๊ฐ 1์ด๋ ๊ฐ์ ์ ๋๋ก ๋ฑ์ด๋ด๋์ง ํ์ธ์ ํ๋ค.
๋ง์ฝ ์ ๋๋ก row๊ฐ ์์ฑ๋์ง ์์๋ค๋ฉด 409์๋ฌ๋ฅผ ๋์ฐ๋๋ก ํ๋ค.
์์ง ์ด๋ถ๋ถ์ 50%๋ฐ์ ์์ฑ์ด ๋์ง์์๋ค.
์ด๋ค ์ด์ ์์๋ ์ง ๋ง์ฝ row๊ฐ 2๊ฐ ์๊ฒผ์ ์ ์๋ฌ๋ฅผ ๋์ฐ๊ฒ ์ง๋ง
์ด๋ฏธ ๊ทธ ๊ฐ์ table์ ๋ค์ด๊ฐ์ ๊ฒ์ด๋ค.
๊ทธ๋ฌ๋ฏ๋ก ์ถํ TRANSACTION์ ํตํด ์๋ฌ๊ฐ ๋ฐ์๋๋ฉด ROLLBACK ์ํค๋ ๋ก์ง์ ์ถ๊ฐํ ๊ฒ์ด๋ค.
Draw API
๋์ดํค์ ๊ฝ ์ถ์ฒจ๊ธฐ๋ฅ์ด๋ค.
server.js
const lottoSchedule = async () => {
const list = await snkrsDao.getSnkrsList();
let isOpen = false;
for (let i = 0; i < list.length; i++) {
cron.schedule(`00 09 * * *`, async () => {
isOpen = true;
await snkrsDao.updataOpenClose(isOpen, list[i].style_code);
console.log('์์');
});
cron.schedule(`${(i + 1) * 5} 30 09 * * *`, async () => {
isOpen = false;
await snkrsDao.updataOpenClose(isOpen, list[i].style_code);
await snkrsServices.selectWinner(list[i].style_code);
console.log('๋ง๊ฐ');
});
}
};
snkrs ์ ํ๋ค์ ๋งค์ผ 09:00 ~ 09:30์ ์ถ์ฒจ์ ํ ์ ์๋ค.
์ฒ์์๋ mysql table์ ์นผ๋ผ์ ๊ฐ์ ์ด๋ป๊ฒ ์๊ฐ์ผ๋ก ์ ์ด ํ ์ ์์์ง ๋ง๋งํ์๋ค.
ํ์ง๋ง ๋คํํ๋ ๊ตฌ๊ธ๋งํด๋ณด๋ ์์ค๋ ๋ง์๋ค.
Event Scheduler, Cron ๋ฑ ๋ค์ํ๊ฒ์ด ์์์ง๋ง
node์์ ์ง์ํด์ฃผ๋ node-cron์ด๋๊ฒ์ด ์๋ค๋ ๊ฒ์ ์๊ฒ๋์ด ์ด๊ฒ์ ํ์ฉํ๊ธฐ๋ก ํ๋ค.
์์ ํจ์๋ ์ ํด์ง ์๊ฐ์ snkrs ์ ํ๋ค์ ์ถ์ฒจ์ฌ๋ถ๊ฐ ๋ณ๊ฒฝ๋๊ณ ๋น์ฒจ์๋ฅผ ๋ฝ๊ฒํ๋ ๊ธฐ๋ฅ์ด๋ค.
services/snkrsServices.js
const createUsersToLottoBox = async (user_id, style_code, size) => {
const [snkrs] = await snkrsDao.getSnkrsData(style_code);
const [checkStyleCode] = await snkrsDao.isExistStyleCode(style_code);
const [checkSize] = await snkrsDao.isExistSizes(style_code, size);
const isExistStyleCode = new IsExistItem(checkStyleCode, resultType, 404);
isExistStyleCode.notExistErr('์ ํจํ์ง ์๋ styleCode ์
๋๋ค.');
const isExistSize = new IsExistItem(checkSize, resultType, 404);
isExistSize.notExistErr('์ ํจํ์ง ์๋ size ์
๋๋ค.');
if (snkrs.is_open === statusType.OPEN) {
const [check] = await snkrsDao.checkUserLottoBox(user_id, style_code);
const isExistItem = new IsExistItem(check, resultType, 409);
isExistItem.existErr('์ด๋ฏธ ์ถ์ฒจ์ ํ์
จ์ต๋๋ค.');
await snkrsDao.addLottoBox(user_id, style_code, size);
await snkrsDao.addWinnerBox(user_id, style_code, size);
return;
} else if (snkrs.is_open === statusType.CLOSE) {
const err = new Error('์ถ์ฒจ๊ธฐ๊ฐ์ด ์๋๋๋ค');
err.status = 409;
throw err;
}
};
๋ฐ๋ก ์๋น์ค๋จ์์ ์ค๋ช ์ ํ๊ฒ ๋ค.
์ฐ์ ํ๋ก ํธ์์ โ์๋ชจํ๊ธฐโ๋ฒํผ์ ์ ํ์ is_open์ด๋ผ๋ ๊ฐ์ ์ํด active๊ฐ ๊ฒฐ์ ๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋ฐฑ์์๋ is_open๊ฐ์ ๋ฐ๋ผ ์ถ์ฒจ๊ฐ๋ฅ์ฌ๋ถ๋ฅผ ํ๋จํ๋ค.
์ถ์ฒจ์๊ฐ์ด ๋์ด ์ฌ์ฉ์๊ฐ โ์๋ชจํ๊ธฐโ๋ฒํผ์ ๋๋ฌ ์์ฒญ์ ๋ณด๋ด๋ฉด ์์ฒญ์ ๋ํ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ๋ค.
๊ฒ์ฌ์ ์ด์์ด ์๋ค๋ฉด ,
addLottoBox, addWinnerBoxํจ์๋ฅผ ํตํด DB์ snkrs_data, snkrs_winner ํ ์ด๋ธ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ค.
(Dao๋จ์์๋ Affected Row๋ฅผ ํตํด ์ ๋๋ก ์์ฑ์ด ๋์๋์ง๋ฅผ ํ์ธํ๊ณ ์๋๋ผ๋ฉด ์๋ฌ๋ฅผ ๋์ฐ๋ ์ฝ๋๊ฐ ์กด์ฌํ๋ค.)
services/snkrsServices.js
const selectWinner = async style_code => {
const [check] = await snkrsDao.checkUserWinnerBox(style_code);
const [count] = await snkrsDao.getCount(style_code);
const participants = await snkrsDao.getNumOfParticipants(style_code);
if (check.result === resultType.EXIST) {
const [winnerInfo] = await snkrsDao.selectWinner(style_code);
await snkrsDao.updateCount(
winnerInfo.style_code,
count['MAX(count)'] + 1,
participants.length
);
await snkrsDao.updateWinner(winnerInfo.style_code, winnerInfo.user_id);
await snkrsDao.deleteLottoBox(style_code);
return;
} else {
return;
}
};
๋น์ฒจ์๋ฅผ ๋ฝ๋ ์ฝ๋์ด๋ค.
checkUserWinnerBoxํจ์๋ฅผ ํตํด snkrs_winner์ ๋ฐ์ดํฐ ์ ๋ฌด๋ฅผ ํ์ธํ๊ณ ๋ง์ฝ ์๋ฌด๊ฐ๋ ์๋ค๋ฉด
์๋ฌด๋ ์๋ชจํ์ง ์์ ๊ฒ์ด๋ฏ๋ก ๊ทธ๋ฅ return ๋๋ค.
ํ๋ช ์ด์ ์๋ชจ๋ฅผ ํ์์ selectWinnerํจ์๋ฅผ ํตํด ๋๋ค์ผ๋ก 1๋ช ์ ๋ฝ๋๋ค.
๋น์ฒจ์๊ฐ ์ ํด์ง๋ฉด updateCount๋ฅผ ํตํด ํด๋นํ์ฐจ์ ์๋ชจํ ์ฌ๋๋งํผ๋ง n+1ํ์ฐจ๋ก ์ ๋ฐ์ดํธ ์์ผ์ค๋ค.
๋ง์ง๋ง์ผ๋ก ๋น์ฒจ์๊ฐ ๋ฝํ์ผ๋ ์์๋ก ์๋ชจํ ์ฌ๋๋ค์ ์ ์ฅํ๋ snkrs_data ํ ์ด๋ธ์ ์ญ์ ์์ผ์ค๋ค.
(Dao๋จ์์๋ Affected Row๋ฅผ ํตํด ํด๋น ์ํ์ ์ฐธ๊ฐํ ์ธ์๋งํผ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ์์ผฐ๋์ง ํ์ธํ๋ ์ฝ๋๊ฐ ์กด์ฌํ๋ค.)
๋ณด๋ฌ๊ฐ๊ธฐ โฌ๏ธ
1์ฐจ ํ๋ก์ ํธ ํ๊ณ ๋ก