Реалізація Real-Time аукціону на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація Real-Time аукціону на сайті
Середня
~5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація Real-Time аукціону на сайті

Онлайн-аукціон вимагає точної синхронізації ставок у реальному часі: відображення актуальної ціни у всіх учасників, таймер, сигнали про нові ставки, визначення переможця при закінченні.

Серверна логіка

class AuctionService {
  async placeBid(auctionId: string, userId: string, amount: number): Promise<BidResult> {
    // Пессимістична блокування через Redis SETNX
    const lockKey = `lock:auction:${auctionId}`;
    const lockAcquired = await redis.set(lockKey, userId, 'NX', 'PX', 5000);

    if (!lockAcquired) {
      throw new Error('Auction is processing another bid, try again');
    }

    try {
      const auction = await auctionRepo.findById(auctionId);

      // Валідація
      if (auction.status !== 'active') throw new BidError('Auction is not active');
      if (new Date() > auction.endsAt) throw new BidError('Auction has ended');
      if (amount <= auction.currentPrice) {
        throw new BidError(`Bid must be higher than ${auction.currentPrice}`);
      }
      if (amount < auction.currentPrice + auction.minIncrement) {
        throw new BidError(`Minimum increment is ${auction.minIncrement}`);
      }
      if (auction.currentLeaderId === userId) {
        throw new BidError('You are already the highest bidder');
      }

      // Зберігаємо ставку
      const bid = await bidRepo.create({ auctionId, userId, amount });
      await auctionRepo.updateCurrentPrice(auctionId, amount, userId);

      // Anti-sniping: якщо ставка в останні 2 хвилини — продлюємо на 2 хвилини
      const timeLeft = auction.endsAt.getTime() - Date.now();
      if (timeLeft < 2 * 60 * 1000) {
        const newEndTime = new Date(Date.now() + 2 * 60 * 1000);
        await auctionRepo.extendTime(auctionId, newEndTime);
      }

      // Повідомляємо всіх учасників
      await this.broadcastBid(auctionId, bid, auction);

      return { success: true, bid };

    } finally {
      await redis.del(lockKey);
    }
  }

  async broadcastBid(auctionId: string, bid: Bid, auction: Auction) {
    io.to(`auction:${auctionId}`).emit('bid:new', {
      bidId: bid.id,
      amount: bid.amount,
      bidderId: bid.userId,
      bidderName: anonymizeBidder(bid.userId),
      timestamp: bid.createdAt,
      totalBids: auction.bidCount + 1,
      newEndTime: auction.endsAt
    });
  }
}

Таймер на сервері

class AuctionTimer {
  async startTimer(auctionId: string, endTime: Date): Promise<void> {
    const msUntilEnd = endTime.getTime() - Date.now();

    // Відкладена задача — завершити аукціон точно в терміни
    setTimeout(async () => {
      await this.finalizeAuction(auctionId);
    }, msUntilEnd);

    // Broadcast кожну секунду останні 60 секунд
    const broadcastInterval = setInterval(async () => {
      const remaining = endTime.getTime() - Date.now();

      if (remaining <= 0) {
        clearInterval(broadcastInterval);
        return;
      }

      if (remaining <= 60000) {
        io.to(`auction:${auctionId}`).emit('timer:tick', {
          remaining: Math.ceil(remaining / 1000)
        });
      }
    }, 1000);
  }

  async finalizeAuction(auctionId: string): Promise<void> {
    const auction = await auctionRepo.findById(auctionId);
    if (auction.status !== 'active') return;  // уже завершений

    await auctionRepo.finalize(auctionId);

    io.to(`auction:${auctionId}`).emit('auction:ended', {
      winnerId: auction.currentLeaderId,
      winnerName: await getUserName(auction.currentLeaderId),
      finalPrice: auction.currentPrice
    });

    // Повідомляємо переможця та попередніх учасників
    await this.notifyParticipants(auction);
  }
}

Клієнтський компонент

function AuctionRoom({ auctionId }) {
  const [auction, setAuction] = useState<AuctionState>();
  const [bids, setBids] = useState<Bid[]>([]);
  const [timeLeft, setTimeLeft] = useState<number>(0);
  const socket = useSocket();

  useEffect(() => {
    if (!socket) return;

    socket.emit('auction:join', { auctionId });

    socket.on('auction:state', (state) => setAuction(state));

    socket.on('bid:new', (bid) => {
      setBids(prev => [bid, ...prev].slice(0, 50));
      setAuction(prev => prev ? { ...prev, currentPrice: bid.amount } : prev);
    });

    socket.on('timer:tick', ({ remaining }) => setTimeLeft(remaining));

    socket.on('auction:ended', ({ winnerId, finalPrice }) => {
      setAuction(prev => prev ? { ...prev, status: 'ended' } : prev);
      if (winnerId === currentUserId) {
        showCongratulations(finalPrice);
      }
    });

    return () => socket.emit('auction:leave', { auctionId });
  }, [socket, auctionId]);

  const placeBid = async (amount: number) => {
    try {
      await fetch(`/api/auctions/${auctionId}/bids`, {
        method: 'POST',
        body: JSON.stringify({ amount })
      });
    } catch (e) {
      showError(e.message);
    }
  };

  return (
    <div className="auction-room">
      <CurrentPrice price={auction?.currentPrice} />
      <AuctionTimer seconds={timeLeft} critical={timeLeft < 30} />
      <BidForm
        minBid={(auction?.currentPrice ?? 0) + (auction?.minIncrement ?? 100)}
        onBid={placeBid}
        disabled={auction?.status !== 'active'}
      />
      <BidHistory bids={bids} currentUserId={currentUserId} />
    </div>
  );
}

Часові рамки реалізації

  • Базовий аукціон зі ставками та таймером: 2–3 тижні
  • Anti-sniping, повідомлення, історія ставок: ще 1 тиждень
  • Аукціон з кількома лотами та оплатою: 4–6 тижнів