쇼핑몰 핵심 기능 완전 정복! 💻
"어떤 기능부터 만들어야 할까?" 개발자 관점에서 우선순위별로 완벽 정리했습니다!
🎯 기능 우선순위 매트릭스
MVP vs 고급 기능 분류
const featurePriority = {
P0_MVP: {
description: "서비스 불가능하면 안 되는 기능",
features: [
"사용자 인증 (로그인/회원가입)",
"상품 목록/상세 조회",
"장바구니 관리",
"주문/결제 처리",
"기본 관리자 기능"
],
developmentTime: "2-3개월"
},
P1_Essential: {
description: "빠른 시일 내 필요한 기능",
features: [
"상품 검색/필터링",
"주문 상태 추적",
"고객 지원 (Q&A)",
"기본 마케팅 (쿠폰/이벤트)",
"모바일 최적화"
],
developmentTime: "1-2개월 추가"
},
P2_Advanced: {
description: "경쟁 우위를 위한 기능",
features: [
"AI 기반 상품 추천",
"실시간 재고 관리",
"고급 분석 대시보드",
"소셜 로그인",
"다국어/다통화 지원"
],
developmentTime: "2-4개월 추가"
},
P3_Innovation: {
description: "차별화를 위한 혁신 기능",
features: [
"AR/VR 체험 기능",
"음성 주문 시스템",
"블록체인 기반 리워드",
"메타버스 쇼핑몰",
"AI 챗봇 상담원"
],
developmentTime: "6개월+ 추가"
}
};
🔐 1. 사용자 인증 시스템
회원가입/로그인 구현
// JWT 기반 인증 시스템
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import { User } from '../models/User.js';
class AuthController {
// 회원가입
async register(req, res) {
try {
const { email, password, name, phone } = req.body;
// 입력 검증
if (!this.validateEmail(email)) {
return res.status(400).json({
error: '유효하지 않은 이메일 형식입니다.'
});
}
if (!this.validatePassword(password)) {
return res.status(400).json({
error: '비밀번호는 8자 이상, 영문+숫자+특수문자 포함해야 합니다.'
});
}
// 중복 체크
const existingUser = await User.findByEmail(email);
if (existingUser) {
return res.status(409).json({
error: '이미 존재하는 이메일입니다.'
});
}
// 비밀번호 해싱
const saltRounds = 12;
const hashedPassword = await bcrypt.hash(password, saltRounds);
// 사용자 생성
const user = await User.create({
email,
password: hashedPassword,
name,
phone,
status: 'active',
emailVerified: false
});
// 이메일 인증 발송
await this.sendVerificationEmail(user.email);
res.status(201).json({
message: '회원가입이 완료되었습니다. 이메일 인증을 진행해주세요.',
userId: user.id
});
} catch (error) {
console.error('Registration error:', error);
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
}
}
// 로그인
async login(req, res) {
try {
const { email, password } = req.body;
// 사용자 조회
const user = await User.findByEmail(email);
if (!user) {
return res.status(401).json({
error: '이메일 또는 비밀번호가 올바르지 않습니다.'
});
}
// 비밀번호 검증
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
return res.status(401).json({
error: '이메일 또는 비밀번호가 올바르지 않습니다.'
});
}
// 계정 상태 확인
if (user.status !== 'active') {
return res.status(403).json({
error: '비활성화된 계정입니다. 고객센터에 문의해주세요.'
});
}
// JWT 토큰 생성
const accessToken = jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role
},
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
const refreshToken = jwt.sign(
{ userId: user.id },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: '7d' }
);
// 리프레시 토큰 저장 (Redis 권장)
await this.saveRefreshToken(user.id, refreshToken);
// 로그인 이력 저장
await this.logUserActivity(user.id, 'login', req.ip);
res.json({
message: '로그인 성공',
user: {
id: user.id,
email: user.email,
name: user.name,
role: user.role
},
accessToken,
refreshToken
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
}
}
// 이메일 형식 검증
validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// 비밀번호 강도 검증
validatePassword(password) {
// 최소 8자, 영문 대소문자, 숫자, 특수문자 포함
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
return passwordRegex.test(password);
}
}
소셜 로그인 구현 (카카오)
// 카카오 소셜 로그인
class SocialAuthController {
async kakaoCallback(req, res) {
try {
const { code } = req.query;
// 카카오 액세스 토큰 획득
const tokenResponse = await fetch('https://kauth.kakao.com/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: process.env.KAKAO_CLIENT_ID,
client_secret: process.env.KAKAO_CLIENT_SECRET,
redirect_uri: process.env.KAKAO_REDIRECT_URI,
code
})
});
const tokenData = await tokenResponse.json();
// 카카오 사용자 정보 조회
const userResponse = await fetch('https://kapi.kakao.com/v2/user/me', {
headers: {
'Authorization': `Bearer ${tokenData.access_token}`,
}
});
const kakaoUser = await userResponse.json();
// 기존 사용자 확인 또는 신규 생성
let user = await User.findBySocialId('kakao', kakaoUser.id);
if (!user) {
user = await User.create({
socialProvider: 'kakao',
socialId: kakaoUser.id,
email: kakaoUser.kakao_account?.email,
name: kakaoUser.properties?.nickname,
profileImage: kakaoUser.properties?.profile_image,
status: 'active',
emailVerified: true
});
}
// JWT 토큰 생성 (기존과 동일)
const accessToken = jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
res.redirect(`${process.env.CLIENT_URL}/auth/success?token=${accessToken}`);
} catch (error) {
console.error('Kakao auth error:', error);
res.redirect(`${process.env.CLIENT_URL}/auth/error`);
}
}
}
🛍️ 2. 상품 관리 시스템
상품 데이터 모델
// 상품 스키마 (MongoDB 기준)
const productSchema = new mongoose.Schema({
// 기본 정보
name: { type: String, required: true, index: true },
description: { type: String, required: true },
shortDescription: { type: String, maxlength: 200 },
// 가격 정보
price: {
type: Number,
required: true,
min: 0,
get: v => Math.round(v * 100) / 100 // 소수점 2자리
},
salePrice: { type: Number, min: 0 },
cost: { type: Number, min: 0 }, // 원가
// 카테고리 및 분류
category: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category',
required: true
},
tags: [{ type: String, index: true }],
brand: { type: String, index: true },
// 재고 관리
inventory: {
sku: { type: String, unique: true, required: true },
stock: { type: Number, default: 0, min: 0 },
reservedStock: { type: Number, default: 0, min: 0 },
lowStockThreshold: { type: Number, default: 10 },
trackInventory: { type: Boolean, default: true }
},
// 상품 옵션 (색상, 사이즈 등)
options: [{
name: String,
values: [{
value: String,
priceAdjustment: { type: Number, default: 0 },
stock: { type: Number, default: 0 }
}]
}],
// 이미지
images: [{
url: { type: String, required: true },
alt: String,
isPrimary: { type: Boolean, default: false },
order: { type: Number, default: 0 }
}],
// SEO
seo: {
metaTitle: String,
metaDescription: String,
slug: { type: String, unique: true, index: true }
},
// 상태 및 표시 옵션
status: {
type: String,
enum: ['draft', 'published', 'archived'],
default: 'draft'
},
featured: { type: Boolean, default: false },
// 배송 정보
shipping: {
weight: { type: Number, min: 0 },
dimensions: {
length: Number,
width: Number,
height: Number
},
freeShippingEligible: { type: Boolean, default: false }
},
// 통계
stats: {
views: { type: Number, default: 0 },
sales: { type: Number, default: 0 },
rating: { type: Number, default: 0, min: 0, max: 5 },
reviewCount: { type: Number, default: 0 }
}
}, {
timestamps: true,
toJSON: { getters: true }
});
// 인덱스 설정
productSchema.index({ name: 'text', description: 'text', tags: 'text' });
productSchema.index({ category: 1, status: 1 });
productSchema.index({ 'stats.sales': -1 });
productSchema.index({ createdAt: -1 });
상품 CRUD API
class ProductController {
// 상품 목록 조회 (필터링, 검색, 페이지네이션)
async getProducts(req, res) {
try {
const {
page = 1,
limit = 20,
category,
brand,
minPrice,
maxPrice,
search,
sortBy = 'createdAt',
sortOrder = 'desc',
inStock = true
} = req.query;
// 쿼리 조건 구성
const query = { status: 'published' };
if (category) query.category = category;
if (brand) query.brand = brand;
if (minPrice || maxPrice) {
query.price = {};
if (minPrice) query.price.$gte = parseFloat(minPrice);
if (maxPrice) query.price.$lte = parseFloat(maxPrice);
}
if (search) {
query.$text = { $search: search };
}
if (inStock === 'true') {
query['inventory.stock'] = { $gt: 0 };
}
// 정렬 조건
const sort = {};
sort[sortBy] = sortOrder === 'desc' ? -1 : 1;
// 페이지네이션
const skip = (parseInt(page) - 1) * parseInt(limit);
// 상품 조회
const products = await Product
.find(query)
.populate('category', 'name slug')
.sort(sort)
.skip(skip)
.limit(parseInt(limit))
.select('-inventory.cost'); // 원가 정보 제외
const total = await Product.countDocuments(query);
res.json({
products,
pagination: {
currentPage: parseInt(page),
totalPages: Math.ceil(total / parseInt(limit)),
totalProducts: total,
hasNext: skip + parseInt(limit) < total,
hasPrev: parseInt(page) > 1
}
});
} catch (error) {
console.error('Get products error:', error);
res.status(500).json({ error: '상품 조회 중 오류가 발생했습니다.' });
}
}
// 상품 상세 조회
async getProduct(req, res) {
try {
const { id } = req.params;
const product = await Product
.findById(id)
.populate('category', 'name slug parentCategory')
.select('-inventory.cost');
if (!product) {
return res.status(404).json({ error: '상품을 찾을 수 없습니다.' });
}
if (product.status !== 'published') {
return res.status(404).json({ error: '상품을 찾을 수 없습니다.' });
}
// 조회수 증가 (비동기로 처리)
Product.updateOne(
{ _id: id },
{ $inc: { 'stats.views': 1 } }
).exec();
// 연관 상품 추천 (같은 카테고리, 비슷한 가격대)
const relatedProducts = await Product
.find({
_id: { $ne: id },
category: product.category,
status: 'published',
price: {
$gte: product.price * 0.7,
$lte: product.price * 1.3
}
})
.limit(4)
.select('name price images slug');
res.json({
product,
relatedProducts
});
} catch (error) {
console.error('Get product error:', error);
res.status(500).json({ error: '상품 조회 중 오류가 발생했습니다.' });
}
}
// 상품 등록 (관리자)
async createProduct(req, res) {
try {
// 권한 확인
if (req.user.role !== 'admin') {
return res.status(403).json({ error: '권한이 없습니다.' });
}
const productData = req.body;
// SKU 중복 확인
const existingProduct = await Product.findOne({
'inventory.sku': productData.inventory.sku
});
if (existingProduct) {
return res.status(409).json({ error: 'SKU가 이미 존재합니다.' });
}
// Slug 자동 생성 (URL 친화적)
if (!productData.seo?.slug) {
productData.seo = {
...productData.seo,
slug: this.generateSlug(productData.name)
};
}
const product = new Product(productData);
await product.save();
res.status(201).json({
message: '상품이 성공적으로 등록되었습니다.',
product
});
} catch (error) {
console.error('Create product error:', error);
res.status(500).json({ error: '상품 등록 중 오류가 발생했습니다.' });
}
}
// Slug 생성 헬퍼
generateSlug(name) {
return name
.toLowerCase()
.replace(/[^a-z0-9가-힣\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.trim('-');
}
}
🛒 3. 장바구니 시스템
장바구니 관리
class CartController {
// 장바구니 조회
async getCart(req, res) {
try {
const userId = req.user?.id;
const sessionId = req.session.id;
// 로그인한 사용자는 DB에서, 비로그인은 세션에서
let cart;
if (userId) {
cart = await Cart.findOne({ userId }).populate({
path: 'items.productId',
select: 'name price images inventory status'
});
} else {
cart = req.session.cart || { items: [] };
}
if (!cart) {
cart = { items: [] };
}
// 상품 정보 업데이트 및 유효성 검사
const validatedCart = await this.validateCartItems(cart);
res.json(validatedCart);
} catch (error) {
console.error('Get cart error:', error);
res.status(500).json({ error: '장바구니 조회 중 오류가 발생했습니다.' });
}
}
// 장바구니에 상품 추가
async addToCart(req, res) {
try {
const { productId, quantity = 1, options = {} } = req.body;
const userId = req.user?.id;
// 상품 존재 및 재고 확인
const product = await Product.findById(productId);
if (!product || product.status !== 'published') {
return res.status(404).json({ error: '상품을 찾을 수 없습니다.' });
}
// 재고 확인
const availableStock = product.inventory.stock - product.inventory.reservedStock;
if (product.inventory.trackInventory && availableStock < quantity) {
return res.status(400).json({
error: `재고가 부족합니다. 현재 재고: ${availableStock}개`
});
}
// 옵션 가격 계산
let optionPriceAdjustment = 0;
if (product.options?.length > 0) {
for (const option of product.options) {
if (options[option.name]) {
const selectedValue = option.values.find(
v => v.value === options[option.name]
);
if (selectedValue) {
optionPriceAdjustment += selectedValue.priceAdjustment;
}
}
}
}
const finalPrice = product.salePrice || product.price;
const totalPrice = (finalPrice + optionPriceAdjustment) * quantity;
// 장바구니 아이템 생성
const cartItem = {
productId,
quantity,
options,
price: finalPrice + optionPriceAdjustment,
totalPrice
};
// 장바구니 업데이트
if (userId) {
await this.updateUserCart(userId, cartItem);
} else {
this.updateSessionCart(req, cartItem);
}
// 임시 재고 예약 (15분간)
if (product.inventory.trackInventory) {
await this.reserveInventory(productId, quantity, 15);
}
res.json({
message: '장바구니에 상품이 추가되었습니다.',
cartItem
});
} catch (error) {
console.error('Add to cart error:', error);
res.status(500).json({ error: '장바구니 추가 중 오류가 발생했습니다.' });
}
}
// 사용자 장바구니 업데이트 (DB)
async updateUserCart(userId, newItem) {
const cart = await Cart.findOne({ userId });
if (!cart) {
// 새 장바구니 생성
await Cart.create({
userId,
items: [newItem]
});
} else {
// 기존 아이템 확인 (같은 상품 + 같은 옵션)
const existingItemIndex = cart.items.findIndex(item =>
item.productId.toString() === newItem.productId &&
JSON.stringify(item.options) === JSON.stringify(newItem.options)
);
if (existingItemIndex >= 0) {
// 기존 아이템 수량 증가
cart.items[existingItemIndex].quantity += newItem.quantity;
cart.items[existingItemIndex].totalPrice += newItem.totalPrice;
} else {
// 새 아이템 추가
cart.items.push(newItem);
}
cart.updatedAt = new Date();
await cart.save();
}
}
// 세션 장바구니 업데이트
updateSessionCart(req, newItem) {
if (!req.session.cart) {
req.session.cart = { items: [] };
}
const existingItemIndex = req.session.cart.items.findIndex(item =>
item.productId === newItem.productId &&
JSON.stringify(item.options) === JSON.stringify(newItem.options)
);
if (existingItemIndex >= 0) {
req.session.cart.items[existingItemIndex].quantity += newItem.quantity;
req.session.cart.items[existingItemIndex].totalPrice += newItem.totalPrice;
} else {
req.session.cart.items.push(newItem);
}
}
// 재고 임시 예약
async reserveInventory(productId, quantity, minutes) {
const expiredAt = new Date(Date.now() + minutes * 60 * 1000);
await InventoryReservation.create({
productId,
quantity,
expiredAt
});
// 예약 재고 업데이트
await Product.updateOne(
{ _id: productId },
{ $inc: { 'inventory.reservedStock': quantity } }
);
}
}
💳 4. 결제 시스템
토스페이먼츠 연동
class PaymentController {
// 결제 준비 (결제 링크 생성)
async preparePayment(req, res) {
try {
const { orderId, amount, paymentMethod } = req.body;
const userId = req.user.id;
// 주문 정보 검증
const order = await Order.findOne({
_id: orderId,
userId,
status: 'pending'
});
if (!order) {
return res.status(404).json({ error: '주문을 찾을 수 없습니다.' });
}
// 금액 일치 확인
if (order.totalAmount !== amount) {
return res.status(400).json({ error: '주문 금액이 일치하지 않습니다.' });
}
// 토스페이먼츠 결제 생성
const paymentData = {
amount,
orderId: order._id.toString(),
orderName: `${order.items[0].productName} 외 ${order.items.length - 1}건`,
customerEmail: req.user.email,
customerName: req.user.name,
returnUrl: `${process.env.CLIENT_URL}/payment/success`,
failUrl: `${process.env.CLIENT_URL}/payment/fail`,
flowMode: 'DIRECT',
easyPay: paymentMethod === 'EASY_PAY' ? '토스페이' : undefined
};
const response = await fetch('https://api.tosspayments.com/v1/payments', {
method: 'POST',
headers: {
'Authorization': `Basic ${Buffer.from(process.env.TOSS_SECRET_KEY + ':').toString('base64')}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(paymentData)
});
const payment = await response.json();
if (!response.ok) {
throw new Error(payment.message || '결제 생성 실패');
}
// 결제 정보 저장
await Payment.create({
orderId,
userId,
paymentKey: payment.paymentKey,
amount,
method: paymentMethod,
status: 'PENDING',
tossPaymentId: payment.id
});
res.json({
success: true,
paymentUrl: payment.checkout.url,
paymentKey: payment.paymentKey
});
} catch (error) {
console.error('Payment preparation error:', error);
res.status(500).json({ error: '결제 준비 중 오류가 발생했습니다.' });
}
}
// 결제 승인
async confirmPayment(req, res) {
try {
const { paymentKey, orderId, amount } = req.body;
// 결제 승인 요청
const response = await fetch(`https://api.tosspayments.com/v1/payments/${paymentKey}`, {
method: 'POST',
headers: {
'Authorization': `Basic ${Buffer.from(process.env.TOSS_SECRET_KEY + ':').toString('base64')}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount,
orderId
})
});
const payment = await response.json();
if (!response.ok) {
throw new Error(payment.message || '결제 승인 실패');
}
// 결제 정보 업데이트
await Payment.updateOne(
{ paymentKey },
{
status: 'COMPLETED',
approvedAt: new Date(),
receipt: payment.receipt,
tossPaymentData: payment
}
);
// 주문 상태 업데이트
await Order.updateOne(
{ _id: orderId },
{
status: 'paid',
paidAt: new Date()
}
);
// 재고 차감
await this.deductInventory(orderId);
// 결제 완료 이메일 발송
await this.sendPaymentConfirmationEmail(orderId);
res.json({
success: true,
message: '결제가 완료되었습니다.',
payment
});
} catch (error) {
console.error('Payment confirmation error:', error);
// 결제 실패 처리
await Payment.updateOne(
{ paymentKey: req.body.paymentKey },
{ status: 'FAILED', failReason: error.message }
);
res.status(400).json({
error: error.message || '결제 승인 중 오류가 발생했습니다.'
});
}
}
// 재고 차감
async deductInventory(orderId) {
const order = await Order.findById(orderId).populate('items.productId');
for (const item of order.items) {
await Product.updateOne(
{ _id: item.productId._id },
{
$inc: {
'inventory.stock': -item.quantity,
'inventory.reservedStock': -item.quantity,
'stats.sales': item.quantity
}
}
);
}
}
}
🤖 5. AI 기반 상품 추천 시스템
추천 엔진 구현
# Python으로 구현한 추천 알고리즘
import numpy as np
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.decomposition import NMF
class RecommendationEngine:
def __init__(self):
self.user_item_matrix = None
self.item_features = None
self.model = None
def collaborative_filtering_recommendations(self, user_id, num_recommendations=5):
"""협업 필터링 기반 추천"""
try:
# 사용자 구매 이력 조회
user_purchases = self.get_user_purchase_history(user_id)
if len(user_purchases) == 0:
return self.popular_items_recommendation(num_recommendations)
# 사용자-상품 매트릭스 생성
user_item_matrix = self.create_user_item_matrix()
# NMF (Non-negative Matrix Factorization) 모델 학습
model = NMF(n_components=50, random_state=42)
W = model.fit_transform(user_item_matrix)
H = model.components_
# 해당 사용자의 잠재 요인 벡터
user_idx = self.get_user_index(user_id)
if user_idx is None:
return self.content_based_recommendations(user_purchases[0], num_recommendations)
user_vector = W[user_idx]
# 모든 상품에 대한 예측 점수 계산
predicted_scores = np.dot(user_vector, H)
# 이미 구매한 상품 제외
purchased_items = set([p['product_id'] for p in user_purchases])
# 추천 상품 선별
recommendations = []
for idx, score in enumerate(predicted_scores):
product_id = self.get_product_id_by_index(idx)
if product_id not in purchased_items:
recommendations.append({
'product_id': product_id,
'score': float(score),
'reason': 'similar_users'
})
# 점수순 정렬
recommendations.sort(key=lambda x: x['score'], reverse=True)
return recommendations[:num_recommendations]
except Exception as e:
print(f"Collaborative filtering error: {e}")
return self.popular_items_recommendation(num_recommendations)
def content_based_recommendations(self, reference_product_id, num_recommendations=5):
"""콘텐츠 기반 필터링 추천"""
try:
# 상품 특성 데이터 조회
products_df = self.get_products_dataframe()
# 텍스트 특성 벡터화
tfidf = TfidfVectorizer(
max_features=1000,
stop_words='english',
ngram_range=(1, 2)
)
# 상품명 + 설명 + 카테고리 결합
text_features = products_df['name'] + ' ' + \
products_df['description'] + ' ' + \
products_df['category']
tfidf_matrix = tfidf.fit_transform(text_features)
# 코사인 유사도 계산
similarity_matrix = cosine_similarity(tfidf_matrix)
# 기준 상품의 인덱스 찾기
ref_idx = products_df[products_df['id'] == reference_product_id].index[0]
# 유사한 상품 찾기
similarity_scores = list(enumerate(similarity_matrix[ref_idx]))
similarity_scores = sorted(similarity_scores, key=lambda x: x[1], reverse=True)
# 기준 상품 제외하고 추천
recommendations = []
for idx, score in similarity_scores[1:num_recommendations+1]:
product = products_df.iloc[idx]
recommendations.append({
'product_id': product['id'],
'score': float(score),
'reason': 'similar_content'
})
return recommendations
except Exception as e:
print(f"Content-based filtering error: {e}")
return self.popular_items_recommendation(num_recommendations)
def hybrid_recommendations(self, user_id, num_recommendations=5):
"""하이브리드 추천 (협업 + 콘텐츠 기반)"""
try:
# 협업 필터링 추천
cf_recs = self.collaborative_filtering_recommendations(
user_id, num_recommendations * 2
)
# 사용자의 최근 구매 상품 기반 콘텐츠 추천
recent_purchases = self.get_recent_purchases(user_id, limit=3)
cb_recs = []
for purchase in recent_purchases:
cb_recs.extend(
self.content_based_recommendations(
purchase['product_id'],
num_recommendations
)
)
# 점수 정규화 및 가중 평균
cf_weight = 0.7
cb_weight = 0.3
# 추천 결과 병합
combined_recs = {}
for rec in cf_recs:
product_id = rec['product_id']
combined_recs[product_id] = {
'product_id': product_id,
'score': rec['score'] * cf_weight,
'reasons': ['similar_users']
}
for rec in cb_recs:
product_id = rec['product_id']
if product_id in combined_recs:
combined_recs[product_id]['score'] += rec['score'] * cb_weight
combined_recs[product_id]['reasons'].append('similar_content')
else:
combined_recs[product_id] = {
'product_id': product_id,
'score': rec['score'] * cb_weight,
'reasons': ['similar_content']
}
# 최종 추천 결과
final_recs = list(combined_recs.values())
final_recs.sort(key=lambda x: x['score'], reverse=True)
return final_recs[:num_recommendations]
except Exception as e:
print(f"Hybrid recommendation error: {e}")
return self.popular_items_recommendation(num_recommendations)
def popular_items_recommendation(self, num_recommendations=5):
"""인기 상품 추천 (기본값)"""
try:
popular_products = self.get_popular_products(num_recommendations)
return [{
'product_id': product['id'],
'score': product['popularity_score'],
'reason': 'popular_item'
} for product in popular_products]
except Exception as e:
print(f"Popular items recommendation error: {e}")
return []
Node.js에서 추천 시스템 연동
class RecommendationService {
// 사용자 맞춤 추천
async getUserRecommendations(userId, limit = 10) {
try {
// Python 추천 엔진 호출
const response = await fetch(`${process.env.ML_SERVICE_URL}/recommendations/user`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.ML_SERVICE_TOKEN}`
},
body: JSON.stringify({
user_id: userId,
num_recommendations: limit
})
});
const recommendations = await response.json();
// 상품 정보 조회
const productIds = recommendations.map(rec => rec.product_id);
const products = await Product
.find({ _id: { $in: productIds }, status: 'published' })
.select('name price images slug stats.rating stats.reviewCount');
// 추천 결과와 상품 정보 결합
const enrichedRecommendations = recommendations
.map(rec => {
const product = products.find(p => p._id.toString() === rec.product_id);
return product ? {
...rec,
product
} : null;
})
.filter(Boolean);
return enrichedRecommendations;
} catch (error) {
console.error('Get user recommendations error:', error);
// 실패 시 인기 상품으로 폴백
return this.getPopularProducts(limit);
}
}
// 연관 상품 추천
async getRelatedProducts(productId, limit = 4) {
try {
const response = await fetch(`${process.env.ML_SERVICE_URL}/recommendations/similar`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.ML_SERVICE_TOKEN}`
},
body: JSON.stringify({
product_id: productId,
num_recommendations: limit
})
});
const recommendations = await response.json();
const productIds = recommendations.map(rec => rec.product_id);
const products = await Product
.find({ _id: { $in: productIds }, status: 'published' })
.select('name price images slug');
return recommendations
.map(rec => {
const product = products.find(p => p._id.toString() === rec.product_id);
return product ? { ...rec, product } : null;
})
.filter(Boolean);
} catch (error) {
console.error('Get related products error:', error);
// 실패 시 같은 카테고리 상품으로 폴백
const originalProduct = await Product.findById(productId);
if (originalProduct) {
return this.getCategoryProducts(originalProduct.category, limit);
}
return [];
}
}
// 실시간 추천 업데이트
async updateUserPreferences(userId, productId, action) {
try {
// 사용자 행동 로그 저장
await UserActivity.create({
userId,
productId,
action, // 'view', 'add_to_cart', 'purchase', 'like'
timestamp: new Date()
});
// ML 서비스에 실시간 업데이트 전송
await fetch(`${process.env.ML_SERVICE_URL}/user-activity`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.ML_SERVICE_TOKEN}`
},
body: JSON.stringify({
user_id: userId,
product_id: productId,
action,
timestamp: new Date().toISOString()
})
});
} catch (error) {
console.error('Update user preferences error:', error);
// 실패해도 메인 프로세스에 영향 주지 않음
}
}
}
🚀 마무리
쇼핑몰의 핵심 기능들을 성공적으로 구현하려면:
개발 우선순위 원칙
- MVP 먼저: 기본 기능으로 빠르게 론칭
- 사용자 피드백: 실제 사용 데이터 기반 개선
- 점진적 고도화: 단계별 기능 확장
- 성능 최적화: 사용자 증가에 맞춘 스케일링
핵심 개발 팁
- 보안 우선: 모든 입력 검증, SQL 인젝션 방지
- 에러 처리: 사용자 친화적 에러 메시지
- 로깅: 디버깅을 위한 상세 로그 기록
- 테스트: 단위/통합 테스트 필수
다음 편에서는 20주 개발 프로세스를 자세히 다룰 예정입니다!
📚 쇼핑몰 구축 완벽 가이드 시리즈
- 2026년 쇼핑몰 시장 동향과 성공 전략
- 쇼핑몰 구축 방법론 완전 비교
- 쇼핑몰 구축 비용 완전 분석
- 예산별 쇼핑몰 구축 전략
- 쇼핑몰 필수 기능 구현 가이드 ← 현재 글
- 쇼핑몰 구축 프로세스 단계별 가이드
- 쇼핑몰 성공/실패 사례 분석
- 2026년 이커머스 트렌드와 미래 전망
- 쇼핑몰 구축 실행 체크리스트
- 쇼핑몰 개발 도구 및 리소스 추천
💡 어떤 기능 구현이 가장 궁금하신가요? 댓글로 알려주시면 더 상세한 코드 예제와 구현 팁을 제공해드릴게요!
Tags: #쇼핑몰개발 #기능구현 #결제시스템 #AI추천 #개발가이드