אנו מפתחים אפליקציות מכירות פומביות שבהן כל אלפית שנייה של השהיה משמעותה אובדן פריט. דמיינו: נותרו 30 שניות על פריט נדיר, ופתאום השרת נתקע למשך 2 שניות — המשתמש מפסיד את ההזדמנות שלו. אנו מתמודדים עם תרחישים כאלה באמצעות ארכיטקטורת WebSocket ונעילת אופטימיות. להלן, אנו מכסים החלטות טכניות מרכזיות: WebSocket עם חיבור מחדש אוטומטי, טיפול במצבי מרוץ, הצעות מחיר אוטומטיות (הצעות פרוקסי), והגנה מפני הצעות ברגע האחרון.
אפליקציית מכירות פומביות היא יותר מקטלוג. הלב שלה הוא זמן אמת, שבו עקביות הנתונים היא קריטית. ללא גרסאות וכללי עסקים מתאימים, משתמשים רואים מחירים מיושנים ומפסידים הצעות. הנה ארכיטקטורה מוכחת שמתמודדת עם אלפי חיבורים בו-זמנית.
כיצד לבנות אפליקציית מכירות פומביות למובייל מאפס
אנו מתחילים בבחירת פרוטוקול התעבורה והסנכרון. אנו משתמשים ב-WebSocket (RFC 6455) ובנעילת אופטימיות ברמת מסד הנתונים. אנו מגדירים אירועים: BID_PLACED, AUCTION_EXTENDED, AUCTION_ENDED, YOU_WON, YOU_WERE_OUTBID. כל אירוע מכיל את גרסת הפריט לבדיקות עקביות.
למה WebSocket, לא Polling?
Polling אינו מקובל למכירות פומביות: עיכובי HTTP של 5-10 שניות גורמים להפסד הצעות. WebSocket עם חיבור מחדש הוא הסטנדרט, ומספק השהיה של <100 אלפיות שנייה — מהיר פי 50-100 מ-Polling, קריטי למכירות פומביות עם פעילות גבוהה.
| שיטה | השהיה | אמינות |
|---|---|---|
| Polling (HTTP) | 5-10 שניות | בינונית — בקשות תכופות מעמיסות על השרת |
| WebSocket | <100 אלפיות שנייה | גבוהה — חיבור מחדש אוטומטי |
יישום ב-Swift וב-Kotlin:
Swift WebSocket (URLSessionWebSocketTask)
// iOS: WebSocket через URLSessionWebSocketTask
class AuctionWebSocket {
private var webSocketTask: URLSessionWebSocketTask?
private var reconnectTimer: Timer?
func connect(auctionId: String) {
let url = URL(string: "wss://api.yourauction.com/auctions/\(auctionId)/live")!
webSocketTask = URLSession.shared.webSocketTask(with: url)
webSocketTask?.resume()
receive()
}
private func receive() {
webSocketTask?.receive { [weak self] result in
switch result {
case .success(let message):
if case .string(let text) = message {
self?.handleMessage(text)
}
self?.receive() // продолжаем слушать
case .failure:
self?.scheduleReconnect()
}
}
}
private func scheduleReconnect() {
reconnectTimer?.invalidate()
reconnectTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { [weak self] _ in
self?.connect(auctionId: self?.currentAuctionId ?? "")
}
}
} Kotlin WebSocket (OkHttp)
// Android: OkHttp WebSocket class AuctionWebSocketManager(
private val client: OkHttpClient,
private val scope: CoroutineScope
) {
private val _events = MutableSharedFlow<AuctionEvent>()
val events: SharedFlow<AuctionEvent> = _events.asSharedFlow()
fun connect(auctionId: String) {
val request = Request.Builder()
.url("wss://api.yourauction.com/auctions/$auctionId/live")
.build()
client.newWebSocket(request, object : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, text: String) {
scope.launch {
val event = json.decodeFromString<AuctionEvent>(text)
_events.emit(event)
}
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
scope.launch {
delay(3000)
connect(auctionId)
}
}
})
}
} כיצד להימנע ממצבי מרוץ עם הצעות בו-זמנית?
הלוגיקה העסקית הקשה ביותר: שני משתמשים מציעים הצעה באותו זמן. דוגמה:
- הצעה נוכחית: $1000
- משתמש א' רואה $1000, מציע $1100
- משתמש ב' רואה $1000, מציע $1050
- שתי ההצעות מגיעות לשרת בו-זמנית
הפתרון הנכון הוא נעילת אופטימיות עם גרסאות פריט. הוא עדיף על נעילת פסימיות כי הוא לא נועל את השורה בקריאות, ומאפשר תפוקה גבוהה. השרת בודק את גרסת הפריט לפני העדכון:
def place_bid(user_id: str, lot_id: str, amount: Decimal, expected_version: int) -> BidResult:
with db.transaction():
lot = db.select_for_update(f"SELECT * FROM lots WHERE id = %s", lot_id)
if lot.version != expected_version:
return BidResult(
success=False,
reason="LOT_UPDATED",
current_bid=lot.current_bid,
new_version=lot.version
)
if amount <= lot.current_bid:
return BidResult(
success=False,
reason="BID_TOO_LOW",
current_bid=lot.current_bid,
new_version=lot.version
)
db.execute(
"UPDATE lots SET current_bid=%s, current_bidder=%s, version=version+1 WHERE id=%s",
(amount, user_id, lot_id)
)
db.execute(
"INSERT INTO bids (lot_id, user_id, amount) VALUES (%s, %s, %s)",
(lot_id, user_id, amount)
)
broadcast_to_websockets(lot_id, {
"type": "BID_PLACED",
"amount": str(amount),
"bidder": mask_username(user_id),
"version": lot.version + 1
})
return BidResult(success=True, new_version=lot.version + 1)ב-// iOS: WebSocket через URLSessionWebSocketTask class AuctionWebSocket { private var webSocketTask: URLSessionWebSocketTask? private var reconnectTimer: Timer? func connect(auctionId: String) { let url = URL(string: "wss://api.yourauction.com/auctions/\(auctionId)/live")! webSocketTask = URLSession.shared.webSocketTask(with: url) webSocketTask?.resume() receive() } private func receive() { webSocketTask?.receive { [weak self] result in switch result { case .success(let message): if case .string(let text) = message { self?.handleMessage(text) } self?.receive() // продолжаем слушать case .failure: self?.scheduleReconnect() } } } private func scheduleReconnect() { reconnectTimer?.invalidate() reconnectTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { [weak self] _ in self?.connect(auctionId: self?.currentAuctionId ?? "") } } } , הלקוח מקבל את ההצעה הנוכחית ויכול להציע למשתמש להציע הצעה חדשה עם המחיר המעודכן. זה מונע חיובים כפולים ומאזנים שגויים.
כיצד פועלת הצעת מחיר אוטומטית?
משתמשים מגדירים סכום הצעה מקסימלי. המערכת מעלה את הצעתם אוטומטית בתגובה להצעות מתחרים — עד לסכום שנקבע.
def process_autobid(lot_id: str, new_bid_amount: Decimal, new_bidder_id: str):
"""Проверяем автоставки после каждой новой ставки"""
autobids = db.get_active_autobids(lot_id, exclude_user=new_bidder_id)
for autobid in sorted(autobids, key=lambda x: x.max_amount, reverse=True):
counter_amount = new_bid_amount + lot.bid_step
if counter_amount <= autobid.max_amount:
# Автоматически ставим от имени держателя автоставки
place_bid(autobid.user_id, lot_id, counter_amount, lot.version)
break
הצעות אוטומטיות עלולות לגרום לעימותים עם משתמשים, ולכן היסטוריה מפורטת היא חיונית: "ההצעה שלך בסך $1,200 הועלתה אוטומטית בתגובה להצעה של $1,100." המשתמש תמיד רואה מה קרה ויכול להתאים את הסכום המקסימלי שלו.
מהי הגנה מפני הצעות ברגע האחרון?
הצעת סנייפינג היא הצעה ברגע האחרון. כדי למנוע זאת, המכירה הפומבית מתארכת כאשר מוגשת הצעה סמוך לסיום:
ANTI_SNIPING_THRESHOLD = timedelta(minutes=2)
ANTI_SNIPING_EXTENSION = timedelta(minutes=2)
def after_bid_placed(lot_id: str):
lot = db.get_lot(lot_id)
time_remaining = lot.ends_at - datetime.utcnow()
if time_remaining < ANTI_SNIPING_THRESHOLD:
new_end_time = lot.ends_at + ANTI_SNIPING_EXTENSION
db.update_lot_end_time(lot_id, new_end_time)
broadcast_to_websockets(lot_id, {
"type": "AUCTION_EXTENDED",
"new_end_time": new_end_time.isoformat()
})שעון הלקוח מסונכרן עם שעון השרת בעת קבלת // Android: OkHttp WebSocket class AuctionWebSocketManager( private val client: OkHttpClient, private val scope: CoroutineScope ) { private val _events = MutableSharedFlow<AuctionEvent>() val events: SharedFlow<AuctionEvent> = _events.asSharedFlow() fun connect(auctionId: String) { val request = Request.Builder() .url("wss://api.yourauction.com/auctions/$auctionId/live") .build() client.newWebSocket(request, object : WebSocketListener() { override fun onMessage(webSocket: WebSocket, text: String) { scope.launch { val event = json.decodeFromString<AuctionEvent>(text) _events.emit(event) } } override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { scope.launch { delay(3000) connect(auctionId) } } }) } } . זה נותן לכל המשתתפים סיכוי שווה להציע הצעה נגדית.
כיצד לארגן תשלומים?
הזוכה מקבל הודעת Push ויש לו זמן מוגבל (24-48 שעות) לשלם. אם לא שולם, הפריט עובר למציע הבא בתור או מפורסם מחדש.
עבור פריטים בעלי ערך גבוה — הפקדה לפני הצעה: נחסמת בעת ההרשמה למכירה הפומבית, ומוחזרת למפסידים.
עמלה (מהקונה או מהמוכר) מנוכה בעת התשלום. תשלומים למוכרים דרך Stripe Connect או דומה. אם אתה צריך מערכת תשלומים מותאמת אישית, צור קשר — נמצא פתרון.
לוחות זמנים משוערים
| היקף | לוח זמנים |
|---|---|
| גלישת פריטים, WebSocket, הצעות, הודעות Push | 6-8 שבועות |
| הצעות אוטומטיות, הגנה מפני הצעות ברגע האחרון, היסטוריית הצעות | +2 שבועות |
| לוח בקרה למוכר, פרסום פריטים, תשלומים | +3-4 שבועות |
| הפקדות ונאמנות | +1-2 שבועות |
העלות נקבעת באופן אישי לאחר ניתוח דרישות.
מה כלול בעבודה?
- מפרט טכני ואב-טיפוס UI/UX
- פיתוח iOS (Swift, SwiftUI) ו-Android (Kotlin, Jetpack Compose)
- צד שרת ב-Python/FastAPI או Node.js
- שילוב WebSocket עם חיבור מחדש
- יישום הצעות אוטומטיות והגנה מפני הצעות ברגע האחרון
- מערכת תשלומים (הפקדות, נאמנות, תשלומים)
- בדיקות (יחידה, UI, עומס)
- פרסום ב-App Store וב-Google Play
- תיעוד והדרכת מנהלים
- תמיכה באחריות למשך 3 חודשים
קבל ייעוץ לפרויקט שלך. נעריך לוחות זמנים ונציע פתרון אופטימלי. הניסיון שלנו מבטיח פעילות יציבה של המכירה הפומבית גם בעומס גבוה. צור קשר כדי לדון בארכיטקטורה ובפרטי היישום.







