쇼핑몰 필수 기능 구현 가이드 - 결제부터 AI 추천까지 개발자 관점의 실무 가이드

쇼핑몰개발기능구현결제시스템AI추천개발가이드

쇼핑몰 핵심 기능 완전 정복! 💻

"어떤 기능부터 만들어야 할까?" 개발자 관점에서 우선순위별로 완벽 정리했습니다!

🎯 기능 우선순위 매트릭스

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);
      // 실패해도 메인 프로세스에 영향 주지 않음
    }
  }
}

🚀 마무리

쇼핑몰의 핵심 기능들을 성공적으로 구현하려면:

개발 우선순위 원칙

  1. MVP 먼저: 기본 기능으로 빠르게 론칭
  2. 사용자 피드백: 실제 사용 데이터 기반 개선
  3. 점진적 고도화: 단계별 기능 확장
  4. 성능 최적화: 사용자 증가에 맞춘 스케일링

핵심 개발 팁

  • 보안 우선: 모든 입력 검증, SQL 인젝션 방지
  • 에러 처리: 사용자 친화적 에러 메시지
  • 로깅: 디버깅을 위한 상세 로그 기록
  • 테스트: 단위/통합 테스트 필수

다음 편에서는 20주 개발 프로세스를 자세히 다룰 예정입니다!


📚 쇼핑몰 구축 완벽 가이드 시리즈

  1. 2026년 쇼핑몰 시장 동향과 성공 전략
  2. 쇼핑몰 구축 방법론 완전 비교
  3. 쇼핑몰 구축 비용 완전 분석
  4. 예산별 쇼핑몰 구축 전략
  5. 쇼핑몰 필수 기능 구현 가이드 ← 현재 글
  6. 쇼핑몰 구축 프로세스 단계별 가이드
  7. 쇼핑몰 성공/실패 사례 분석
  8. 2026년 이커머스 트렌드와 미래 전망
  9. 쇼핑몰 구축 실행 체크리스트
  10. 쇼핑몰 개발 도구 및 리소스 추천

💡 어떤 기능 구현이 가장 궁금하신가요? 댓글로 알려주시면 더 상세한 코드 예제와 구현 팁을 제공해드릴게요!

Tags: #쇼핑몰개발 #기능구현 #결제시스템 #AI추천 #개발가이드

궁금한 점이 있으신가요?

문의사항이 있으시면 언제든지 연락주세요.