실시간 경매 플랫폼 - 입찰 전쟁 & 낙찰 알림
live-auction-bidding-platform
문제 설명
🔨 실시간 경매 플랫폼
온라인 경매의 긴장감과 흥미를 그대로 구현한 풀스택 경매 시스템입니다. Laravel 13.x + Blade + Reverb(WebSocket)을 활용하여 실시간 입찰, 자동 연장, 낙찰 알림까지 완벽하게 구현합니다.
핵심 기능
- 실시간 입찰 시스템: Laravel Reverb로 WebSocket 구현, 모든 참가자에게 즉시 입찰가 업데이트
- 경매 상품 관리: 판매자가 상품 등록, 시작가/즉시구매가 설정, 이미지 업로드
- 자동 마감 연장: 마감 5분 전 입찰 시 자동으로 5분 연장 (스나이핑 방지)
- 입찰 히스토리: 각 상품별 입찰 기록 타임라인, 입찰자별 히스토리 조회
- 낙찰 알림: 경매 종료 시 낙찰자에게 이메일 & 인앱 알림 자동 발송
- 입찰 제한: 최소 입찰 단위 설정, 본인 최고가 재입찰 방지, 잔액 검증
- 실시간 참여자 수: 현재 상품 페이지를 보고 있는 사용자 수 표시
- 경매 상태 관리: 예정/진행중/종료 상태별 필터링 및 자동 상태 전환
기술 스택
- 백엔드: Laravel 13.x + SQLite/MySQL + Queue + Reverb(WebSocket)
- 프론트엔드: Blade 템플릿 + Tailwind CSS + Alpine.js
- 실시간: Laravel Reverb(WebSocket) + Laravel Echo
- 인증: Laravel Breeze (Blade)
- 알림: Laravel Notification + Mail + Database
- 스케줄링: Laravel Scheduler로 경매 자동 종료 처리
보너스 기능
- 입찰 전 보증금 예치 시스템
- 자동 입찰 에이전트 (최대 금액 설정 시 자동으로 입찰)
- 경매 카테고리별 필터링 및 검색
- 판매자 신뢰도 평가 시스템
- 낙찰 후 결제 연동 (Stripe/Toss Payments)
- 입찰 통계 대시보드 (판매자용)
- 관심 상품 알림 (경매 시작 10분 전 푸시)
- Redis 캐싱으로 실시간 조회수 최적화
제약 조건
- • Laravel 13.x 필수 사용
- • Blade 템플릿 엔진 사용 (React/Vue 금지)
- • Laravel Reverb로 WebSocket 구현 (Pusher 대체 가능하나 Reverb 권장)
- • Laravel Breeze (Blade) 인증 시스템 사용
- • Tailwind CSS 3.x + Alpine.js 3.x 사용
- • SQLite 또는 MySQL 데이터베이스
- • Laravel Queue로 비동기 작업 처리 (이메일, 알림)
- • Laravel Scheduler로 경매 자동 종료 cron 작업
- • 이미지 업로드는 Laravel Storage (public disk)
- • 실시간 입찰 시 트랜잭션과 Lock으로 동시성 제어
- • 경매 종료 시 낙�찰자에게 자동 이메일 발송
- • 입찰 최소 단위 검증 (예: 1,000원 단위)
- • 본인이 현재 최고가인 경우 재입찰 불가
- • 마감 5분 전 입찰 시 자동 5분 연장 로직
- • WebSocket 연결 시 인증된 사용자만 입찰 가능
프롬프트 레시피
아래 프롬프트를 순서대로 AI에게 보내면 됩니다. 복사 버튼을 눌러 바로 사용하세요.
mkdir live-auction-bidding-platform && cd live-auction-bidding-platform 이 폴더에서 Laravel 13.x 프로젝트를 생성하고 기본 설정을 완료하세요. ## 요구사항 1. Laravel 13.x 설치 (`composer create-project laravel/laravel .`) 2. `.env` 파일 설정: - DB_CONNECTION=sqlite (또는 mysql) - QUEUE_CONNECTION=database - BROADCAST_DRIVER=reverb (Reverb 사용) 3. Laravel Breeze (Blade) 설치: ```bash composer require laravel/breeze --dev php artisan breeze:install blade npm install && npm run build ``` 4. Laravel Reverb 설치 및 설정: ```bash php artisan install:broadcasting ``` - `config/broadcasting.php`에서 reverb 연결 확인 - `.env`에 Reverb 설정 추가 (REVERB_APP_ID, REVERB_APP_KEY 등) 5. 데이터베이스 마이그레이션 실행: ```bash php artisan migrate ``` 6. Tailwind CSS 3.x 및 Alpine.js 3.x 설정 확인 (`resources/css/app.css`, `resources/js/app.js`) 7. Queue 테이블 생성: ```bash php artisan queue:table php artisan migrate ``` 프로젝트 생성 후 `php artisan serve`로 서버가 정상 작동하는지 확인하세요.
기대 결과: Laravel 13.x + Breeze + Reverb + Tailwind + Alpine.js 기본 프로젝트 생성 완료. 인증 시스템(로그인/회원가입) 동작 확인. Reverb WebSocket 서버 설정 완료.
경매 시스템의 핵심 데이터베이스 스키마를 설계하고 마이그레이션을 생성하세요.
## 요구사항
### 1. auctions 테이블 (경매 상품)
```php
Schema::create('auctions', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade'); // 판매자
$table->string('title');
$table->text('description');
$table->string('image')->nullable(); // 대표 이미지 경로
$table->decimal('starting_price', 12, 2); // 시작가
$table->decimal('current_price', 12, 2)->default(0); // 현재가
$table->decimal('buy_now_price', 12, 2)->nullable(); // 즉시구매가
$table->decimal('bid_increment', 10, 2)->default(1000); // 최소 입찰 단위
$table->integer('bid_count')->default(0); // 총 입찰 횟수
$table->timestamp('starts_at'); // 경매 시작 시간
$table->timestamp('ends_at'); // 경매 종료 시간
$table->enum('status', ['pending', 'active', 'ended'])->default('pending');
$table->foreignId('winner_id')->nullable()->constrained('users')->onDelete('set null'); // 낙찰자
$table->timestamps();
$table->index(['status', 'ends_at']);
$table->index('user_id');
});
```
### 2. bids 테이블 (입찰 기록)
```php
Schema::create('bids', function (Blueprint $table) {
$table->id();
$table->foreignId('auction_id')->constrained()->onDelete('cascade');
$table->foreignId('user_id')->constrained()->onDelete('cascade'); // 입찰자
$table->decimal('amount', 12, 2); // 입찰가
$table->boolean('is_auto_bid')->default(false); // 자동 입찰 여부
$table->timestamps();
$table->index(['auction_id', 'created_at']);
$table->index('user_id');
});
```
### 3. notifications 테이블 (인앱 알림, Laravel 기본 테이블)
```bash
php artisan notifications:table
php artisan migrate
```
### 4. Model 생성
- `php artisan make:model Auction`
- `php artisan make:model Bid`
각 모델에 관계 설정:
- Auction: belongsTo(User), hasMany(Bid), belongsTo(User as winner)
- Bid: belongsTo(Auction), belongsTo(User)
- User: hasMany(Auction), hasMany(Bid)
### 5. Eloquent 관계 및 Accessor 추가
- Auction 모델에 `isActive()`, `isEnded()`, `timeRemaining()` 헬퍼 메서드
- Auction 모델에 `$casts`로 starts_at, ends_at을 Carbon 인스턴스로 변환
마이그레이션 실행 후 `php artisan tinker`로 데이터 구조 확인하세요.
기대 결과: auctions, bids, notifications 테이블 마이그레이션 완료. Auction, Bid 모델 생성 및 관계 설정 완료. 데이터베이스 스키마가 경매 시스템 요구사항을 충족함.
경매 상품 등록 및 목록 페이지를 구현하세요. 판매자가 경매 상품을 등록하고, 사용자가 진행 중인 경매를 탐색할 수 있어야 합니다.
## 요구사항
### 1. AuctionController 생성
```bash
php artisan make:controller AuctionController
```
### 2. 라우트 설정 (`routes/web.php`)
```php
Route::middleware('auth')->group(function () {
Route::get('/auctions', [AuctionController::class, 'index'])->name('auctions.index');
Route::get('/auctions/create', [AuctionController::class, 'create'])->name('auctions.create');
Route::post('/auctions', [AuctionController::class, 'store'])->name('auctions.store');
Route::get('/auctions/{auction}', [AuctionController::class, 'show'])->name('auctions.show');
});
```
### 3. AuctionController 메서드 구현
#### index() - 경매 목록
- 상태별 필터링 (active, pending, ended) - 쿼리 파라미터 `?status=active`
- 페이지네이션 (15개씩)
- 종료 임박 순 정렬 (진행 중인 경매 우선)
- Blade 뷰: `resources/views/auctions/index.blade.php`
#### create() - 경매 등록 폼
- Blade 뷰: `resources/views/auctions/create.blade.php`
- 폼 필드: 제목, 설명, 이미지, 시작가, 즉시구매가, 입찰 단위, 시작 시간, 종료 시간
#### store() - 경매 등록 처리
- FormRequest 생성: `php artisan make:request StoreAuctionRequest`
- 검증 규칙:
- title: required, max:255
- description: required
- image: required, image, max:2048
- starting_price: required, numeric, min:1000
- buy_now_price: nullable, numeric, gt:starting_price
- bid_increment: required, numeric, min:100
- starts_at: required, date, after:now
- ends_at: required, date, after:starts_at
- 이미지 업로드: `Storage::disk('public')->put('auctions', $request->file('image'))`
- 경매 생성 후 `current_price`는 `starting_price`로 초기화
- 성공 시 `auctions.show`로 리다이렉트
### 4. Blade 뷰 구현
#### index.blade.php
- Tailwind CSS 그리드 레이아웃 (3열)
- 각 경매 카드:
- 이미지, 제목, 현재가, 입찰 횟수
- 남은 시간 표시 (Alpine.js로 실시간 카운트다운)
- 상태 뱃지 (진행중/종료/예정)
- "입찰하기" 버튼 (show 페이지 링크)
- 상태 필터 탭 (전체/진행중/종료)
#### create.blade.php
- Tailwind CSS 폼 스타일링
- 이미지 미리보기 (Alpine.js)
- 날짜/시간 입력 (datetime-local input)
- 즉시구매가 선택적 입력 (체크박스로 활성화)
### 5. 헬퍼 메서드 추가 (Auction 모델)
```php
public function isActive(): bool {
return $this->status === 'active' && $this->ends_at->isFuture();
}
public function timeRemaining(): string {
if ($this->ends_at->isPast()) return '종료';
return $this->ends_at->diffForHumans();
}
```
### 6. Storage 링크 생성
```bash
php artisan storage:link
```
웹 브라우저에서 경매 등록 및 목록 조회가 정상 작동하는지 확인하세요.
기대 결과: 경매 상품 등록 폼 완성. 이미지 업로드 및 유효성 검증 동작. 경매 목록 페이지에서 상태별 필터링, 남은 시간 표시, 카드 UI 구현 완료.
경매 상세 페이지를 구현하고, 실시간 입찰 기능을 Laravel Reverb(WebSocket)로 구축하세요.
## 요구사항
### 1. AuctionController::show() 메서드
- 경매 상세 정보 로드
- 최근 입찰 기록 10개 로드 (with('user')로 eager loading)
- 현재 사용자가 최고 입찰자인지 확인
- Blade 뷰: `resources/views/auctions/show.blade.php`
### 2. BidController 생성
```bash
php artisan make:controller BidController
```
라우트 추가:
```php
Route::post('/auctions/{auction}/bid', [BidController::class, 'store'])->name('bids.store');
```
### 3. BidController::store() - 입찰 처리
```php
public function store(Request $request, Auction $auction) {
// 검증
$request->validate([
'amount' => 'required|numeric|min:' . ($auction->current_price + $auction->bid_increment)
]);
// 경매 상태 확인
if (!$auction->isActive()) {
return back()->withErrors(['경매가 종료되었거나 시작되지 않았습니다.']);
}
// 본인 최고가 재입찰 방지
$lastBid = $auction->bids()->latest()->first();
if ($lastBid && $lastBid->user_id === auth()->id()) {
return back()->withErrors(['이미 최고가 입찰자입니다.']);
}
// 트랜잭션 + Lock으로 동시성 제어
DB::transaction(function () use ($request, $auction) {
$auction->lockForUpdate(); // 비관적 잠금
$bid = $auction->bids()->create([
'user_id' => auth()->id(),
'amount' => $request->amount,
]);
$auction->update([
'current_price' => $request->amount,
'bid_count' => $auction->bid_count + 1,
]);
// 마감 5분 전 입찰 시 자동 연장
if ($auction->ends_at->diffInMinutes(now()) < 5) {
$auction->update(['ends_at' => $auction->ends_at->addMinutes(5)]);
}
// WebSocket 브로드캐스트
broadcast(new BidPlaced($auction, $bid))->toOthers();
});
return back()->with('success', '입찰이 완료되었습니다!');
}
```
### 4. BidPlaced 이벤트 생성
```bash
php artisan make:event BidPlaced
```
`app/Events/BidPlaced.php`:
```php
class BidPlaced implements ShouldBroadcast {
use Dispatchable, InteractsWithSockets, SerializesModels;
public $auction;
public $bid;
public function __construct(Auction $auction, Bid $bid) {
$this->auction = $auction->load('bids.user');
$this->bid = $bid->load('user');
}
public function broadcastOn() {
return new Channel('auction.' . $this->auction->id);
}
public function broadcastWith() {
return [
'current_price' => $this->auction->current_price,
'bid_count' => $this->auction->bid_count,
'latest_bid' => [
'user_name' => $this->bid->user->name,
'amount' => $this->bid->amount,
'time' => $this->bid->created_at->diffForHumans(),
],
];
}
}
```
### 5. Blade 뷰 (show.blade.php) 구현
#### HTML 구조
- 왼쪽: 경매 이미지 및 설명
- 오른쪽: 입찰 정보 카드
- 현재가 (큰 텍스트)
- 입찰 횟수
- 남은 시간 (실시간 카운트다운)
- 입찰 폼 (금액 입력 + 버튼)
- 빠른 입찰 버튼 (+1만원, +5만원, +10만원)
- 즉시구매 버튼 (있을 경우)
- 하단: 최근 입찰 기록 타임라인
#### Alpine.js + Laravel Echo 실시간 연동
```html
<div x-data="auctionPage()" x-init="init()">
<div class="text-4xl font-bold" x-text="formatPrice(currentPrice)"></div>
<div x-text="bidCount + '회 입찰'"></div>
<div x-text="timeRemaining"></div>
<form @submit.prevent="submitBid">
<input type="number" x-model="bidAmount" :min="currentPrice + bidIncrement">
<button type="submit">입찰하기</button>
</form>
<div id="bid-history">
<template x-for="bid in recentBids" :key="bid.id">
<div x-text="bid.user_name + ' - ' + formatPrice(bid.amount)"></div>
</template>
</div>
</div>
<script>
function auctionPage() {
return {
currentPrice: {{ $auction->current_price }},
bidCount: {{ $auction->bid_count }},
bidIncrement: {{ $auction->bid_increment }},
bidAmount: {{ $auction->current_price + $auction->bid_increment }},
recentBids: @json($auction->bids->take(10)),
timeRemaining: '',
init() {
// 실시간 카운트다운
this.updateTime();
setInterval(() => this.updateTime(), 1000);
// Laravel Echo 리스너
window.Echo.channel('auction.{{ $auction->id }}')
.listen('BidPlaced', (e) => {
this.currentPrice = e.current_price;
this.bidCount = e.bid_count;
this.bidAmount = this.currentPrice + this.bidIncrement;
this.recentBids.unshift(e.latest_bid);
if (this.recentBids.length > 10) this.recentBids.pop();
});
},
updateTime() {
const end = new Date('{{ $auction->ends_at }}');
const now = new Date();
const diff = end - now;
if (diff <= 0) {
this.timeRemaining = '종료';
return;
}
const hours = Math.floor(diff / 3600000);
const minutes = Math.floor((diff % 3600000) / 60000);
const seconds = Math.floor((diff % 60000) / 1000);
this.timeRemaining = `${hours}시간 ${minutes}분 ${seconds}초 남음`;
},
formatPrice(price) {
return new Intl.NumberFormat('ko-KR', { style: 'currency', currency: 'KRW' }).format(price);
},
submitBid() {
// Livewire 또는 fetch로 입찰 요청
}
}
}
</script>
```
### 6. Laravel Echo 클라이언트 설정
`resources/js/bootstrap.js`에 Echo 설정 추가:
```javascript
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});
```
### 7. Reverb 서버 실행
```bash
php artisan reverb:start
```
웹 브라우저 2개를 열어 같은 경매 페이지에서 입찰 시 실시간으로 업데이트되는지 확인하세요.
기대 결과: 경매 상세 페이지 완성. 입찰 폼 동작 및 유효성 검증 완료. Laravel Reverb로 실시간 입찰가 업데이트 구현. Alpine.js로 실시간 카운트다운 및 UI 업데이트 동작 확인.
경매 자동 종료 및 낙찰 알림 시스템을 구현하세요. Laravel Scheduler와 Queue를 활용하여 경매가 종료되면 자동으로 낙찰자를 결정하고 이메일 알림을 발송합니다.
## 요구사항
### 1. CloseExpiredAuctions 명령어 생성
```bash
php artisan make:command CloseExpiredAuctions
```
`app/Console/Commands/CloseExpiredAuctions.php`:
```php
class CloseExpiredAuctions extends Command {
protected $signature = 'auctions:close-expired';
protected $description = '종료 시간이 지난 경매를 자동으로 종료하고 낙찰자를 결정합니다.';
public function handle() {
$expiredAuctions = Auction::where('status', 'active')
->where('ends_at', '<=', now())
->get();
foreach ($expiredAuctions as $auction) {
$highestBid = $auction->bids()->orderBy('amount', 'desc')->first();
if ($highestBid) {
$auction->update([
'status' => 'ended',
'winner_id' => $highestBid->user_id,
]);
// 낙찰 알림 발송 (Queue)
$highestBid->user->notify(new AuctionWonNotification($auction));
// 판매자에게도 알림
$auction->user->notify(new AuctionSoldNotification($auction));
$this->info("경매 #{$auction->id} 종료. 낙찰자: {$highestBid->user->name}");
} else {
// 입찰자 없이 종료
$auction->update(['status' => 'ended']);
$this->info("경매 #{$auction->id} 종료. 입찰자 없음.");
}
}
$this->info("총 {$expiredAuctions->count()}개 경매 처리 완료.");
}
}
```
### 2. Notification 생성
```bash
php artisan make:notification AuctionWonNotification
php artisan make:notification AuctionSoldNotification
```
#### AuctionWonNotification (낙찰자용)
```php
class AuctionWonNotification extends Notification implements ShouldQueue {
use Queueable;
public $auction;
public function __construct(Auction $auction) {
$this->auction = $auction;
}
public function via($notifiable) {
return ['mail', 'database'];
}
public function toMail($notifiable) {
return (new MailMessage)
->subject('축하합니다! 경매에서 낙찰되었습니다.')
->line("'{$this->auction->title}' 경매에서 낙찰되셨습니다!")
->line('낙찰가: ' . number_format($this->auction->current_price) . '원')
->action('경매 보기', route('auctions.show', $this->auction))
->line('판매자가 곧 연락드릴 예정입니다.');
}
public function toArray($notifiable) {
return [
'auction_id' => $this->auction->id,
'title' => $this->auction->title,
'final_price' => $this->auction->current_price,
'message' => "'{$this->auction->title}' 경매에서 낙찰되었습니다!",
];
}
}
```
#### AuctionSoldNotification (판매자용)
- 낙찰자 정보 포함
- 낙찰가 및 수수료 안내
### 3. Laravel Scheduler 등록
`app/Console/Kernel.php`:
```php
protected function schedule(Schedule $schedule) {
$schedule->command('auctions:close-expired')
->everyMinute() // 1분마다 실행
->withoutOverlapping() // 중복 실행 방지
->runInBackground(); // 백그라운드 실행
}
```
### 4. Cron 작업 설정 (서버)
로컬 개발 환경에서는 아래 명령어로 스케줄러 실행:
```bash
php artisan schedule:work
```
프로덕션 서버에서는 crontab에 등록:
```
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
```
### 5. Queue Worker 실행
```bash
php artisan queue:work --tries=3
```
### 6. 인앱 알림 표시 (Blade 레이아웃)
`resources/views/layouts/app.blade.php`에 알림 벨 아이콘 추가:
```html
<div x-data="{ open: false }" class="relative">
<button @click="open = !open" class="relative">
<svg><!-- 벨 아이콘 --></svg>
@if(auth()->user()->unreadNotifications->count())
<span class="absolute top-0 right-0 bg-red-500 text-white rounded-full px-1 text-xs">
{{ auth()->user()->unreadNotifications->count() }}
</span>
@endif
</button>
<div x-show="open" @click.away="open = false" class="absolute right-0 mt-2 w-80 bg-white shadow-lg">
@forelse(auth()->user()->unreadNotifications->take(5) as $notification)
<a href="{{ route('auctions.show', $notification->data['auction_id']) }}"
class="block p-4 hover:bg-gray-50">
<p class="font-semibold">{{ $notification->data['message'] }}</p>
<p class="text-sm text-gray-500">{{ $notification->created_at->diffForHumans() }}</p>
</a>
@empty
<p class="p-4 text-gray-500">새 알림이 없습니다.</p>
@endforelse
</div>
</div>
```
### 7. 알림 읽음 처리
알림 클릭 시 읽음 표시:
```php
Route::post('/notifications/{notification}/read', function ($id) {
auth()->user()->notifications()->findOrFail($id)->markAsRead();
return back();
})->name('notifications.read');
```
### 8. 테스트
- 경매 종료 시간을 현재 시간 +2분으로 설정
- `php artisan schedule:work` 실행
- 2분 후 경매가 자동으로 종료되고 이메일/알림 발송 확인
- Queue 대시보드 확인: `php artisan queue:monitor`
이메일 발송 확인은 `.env`에서 `MAIL_MAILER=log`로 설정 후 `storage/logs/laravel.log` 확인.
기대 결과: Laravel Scheduler로 1분마다 종료된 경매 자동 처리. 낙찰자에게 이메일 및 인앱 알림 발송. Queue로 비동기 처리 완료. 알림 벨 아이콘 UI 구현 및 읽음 처리 동작 확인.
경매 시스템의 고급 기능을 추가하세요: 자동 입찰, 즉시구매, 입찰 히스토리 및 통계 대시보드를 구현합니다.
## 요구사항
### 1. 자동 입찰 시스템
#### auto_bids 테이블 생성
```bash
php artisan make:migration create_auto_bids_table
```
```php
Schema::create('auto_bids', function (Blueprint $table) {
$table->id();
$table->foreignId('auction_id')->constrained()->onDelete('cascade');
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->decimal('max_amount', 12, 2); // 최대 입찰 금액
$table->boolean('is_active')->default(true);
$table->timestamps();
$table->unique(['auction_id', 'user_id']); // 한 경매당 1개만
});
```
#### AutoBid 모델 및 로직
```bash
php artisan make:model AutoBid
```
입찰 발생 시 자동 입찰 트리거:
- BidController::store()에서 입찰 후 `AutoBidService::trigger($auction)` 호출
- AutoBidService:
- 현재가보다 높은 max_amount를 가진 자동 입찰 찾기
- 가장 높은 자동 입찰자를 찾아 자동으로 입찰 생성
- 입찰 금액은 현재가 + bid_increment (또는 경쟁자보다 increment만큼만 높게)
#### 자동 입찰 설정 폼
경매 상세 페이지에 "자동 입찰 설정" 버튼 추가:
- 모달로 최대 금액 입력
- POST `/auctions/{auction}/auto-bid`로 저장
### 2. 즉시구매 기능
#### BuyNowController 생성
```bash
php artisan make:controller BuyNowController
```
```php
public function store(Auction $auction) {
if (!$auction->buy_now_price) {
return back()->withErrors(['즉시구매가 설정되지 않았습니다.']);
}
if (!$auction->isActive()) {
return back()->withErrors(['경매가 이미 종료되었습니다.']);
}
DB::transaction(function () use ($auction) {
$auction->update([
'status' => 'ended',
'winner_id' => auth()->id(),
'current_price' => $auction->buy_now_price,
]);
// 즉시구매 알림
auth()->user()->notify(new AuctionWonNotification($auction));
$auction->user->notify(new AuctionSoldNotification($auction));
});
return redirect()->route('auctions.show', $auction)
->with('success', '즉시구매가 완료되었습니다!');
}
```
라우트 추가:
```php
Route::post('/auctions/{auction}/buy-now', [BuyNowController::class, 'store'])->name('auctions.buy-now');
```
경매 상세 페이지에 "즉시구매" 버튼 추가 (buy_now_price가 있을 경우만 표시).
### 3. 입찰 히스토리 페이지
#### UserBidController 생성
```bash
php artisan make:controller UserBidController
```
```php
public function index() {
$myBids = auth()->user()->bids()
->with('auction')
->latest()
->paginate(20);
return view('bids.index', compact('myBids'));
}
```
라우트:
```php
Route::get('/my-bids', [UserBidController::class, 'index'])->name('bids.index');
```
Blade 뷰 (`resources/views/bids/index.blade.php`):
- 입찰한 경매 목록
- 각 경매별 내 최고 입찰가, 현재 상태 (최고가/낙찰/패찰)
- 경매 상태별 필터 (진행중/종료)
### 4. 판매자 대시보드
#### SellerDashboardController 생성
```bash
php artisan make:controller SellerDashboardController
```
```php
public function index() {
$myAuctions = auth()->user()->auctions()
->withCount('bids')
->latest()
->paginate(15);
$stats = [
'total_auctions' => auth()->user()->auctions()->count(),
'active_auctions' => auth()->user()->auctions()->where('status', 'active')->count(),
'total_revenue' => auth()->user()->auctions()->where('status', 'ended')->whereNotNull('winner_id')->sum('current_price'),
'total_bids' => auth()->user()->auctions()->withCount('bids')->get()->sum('bids_count'),
];
return view('seller.dashboard', compact('myAuctions', 'stats'));
}
```
Blade 뷰:
- 통계 카드 (총 경매 수, 진행 중, 총 매출, 총 입찰 수)
- 내 경매 목록 (상태별 필터)
- 각 경매별 입찰 횟수, 현재가, 낙찰자 정보
### 5. 실시간 참여자 수 표시
#### AuctionViewers 테이블 (Redis 캐시 대신 간단히 DB로 구현)
```bash
php artisan make:migration create_auction_viewers_table
```
```php
Schema::create('auction_viewers', function (Blueprint $table) {
$table->id();
$table->foreignId('auction_id')->constrained()->onDelete('cascade');
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->timestamp('last_seen_at');
$table->unique(['auction_id', 'user_id']);
});
```
경매 상세 페이지 로드 시:
```php
AuctionViewer::updateOrCreate(
['auction_id' => $auction->id, 'user_id' => auth()->id()],
['last_seen_at' => now()]
);
```
실시간 참여자 수:
```php
$viewerCount = $auction->viewers()->where('last_seen_at', '>=', now()->subMinutes(5))->count();
```
Alpine.js로 30초마다 갱신:
```javascript
setInterval(() => {
fetch(`/auctions/${auctionId}/viewers`)
.then(res => res.json())
.then(data => this.viewerCount = data.count);
}, 30000);
```
### 6. UI 개선
- 입찰 성공 시 Confetti 애니메이션 (canvas-confetti 라이브러리)
- 입찰 실패 시 Toast 알림 (Alpine.js)
- 낙찰 시 축하 모달
### 7. 테스트
- 자동 입찰 설정 후 다른 사용자가 입찰 시 자동으로 입찰되는지 확인
- 즉시구매 클릭 시 경매 즉시 종료 및 알림 발송 확인
- 입찰 히스토리 페이지에서 내 입찰 내역 조회
- 판매자 대시보드에서 통계 및 경매 목록 확인
모든 기능이 정상 작동하는지 다양한 시나리오로 테스트하세요.
기대 결과: 자동 입찰 시스템 완성. 즉시구매 기능 동작 확인. 입찰 히스토리 및 판매자 대시보드 구현. 실시간 참여자 수 표시. UI 애니메이션 및 알림 추가 완료.
경매 플랫폼의 보안, 성능 최적화 및 프로덕션 배포 준비를 완료하세요.
## 요구사항
### 1. 보안 강화
#### CSRF 보호 확인
- 모든 POST 요청에 `@csrf` 토큰 포함 확인
#### Rate Limiting (입찰 스팸 방지)
`app/Http/Kernel.php`:
```php
protected $middlewareGroups = [
'web' => [
// ...
\Illuminate\Routing\Middleware\ThrottleRequests::class.':web',
],
];
```
입찰 라우트에 추가 제한:
```php
Route::post('/auctions/{auction}/bid', [BidController::class, 'store'])
->middleware('throttle:10,1') // 1분에 10회 제한
->name('bids.store');
```
#### XSS 방지
- Blade 템플릿에서 `{{ }}` 사용 (자동 이스케이핑)
- 사용자 입력 출력 시 `{!! !!}` 사용 금지
#### SQL Injection 방지
- Eloquent ORM 사용으로 자동 방어
- Raw 쿼리 사용 시 반드시 파라미터 바인딩
#### 입찰 금액 검증 강화
```php
$request->validate([
'amount' => [
'required',
'numeric',
'min:' . ($auction->current_price + $auction->bid_increment),
'max:999999999', // 최대 금액 제한
function ($attribute, $value, $fail) use ($auction) {
if ($value % $auction->bid_increment !== 0) {
$fail('입찰 금액은 ' . number_format($auction->bid_increment) . '원 단위여야 합니다.');
}
},
],
]);
```
### 2. 성능 최적화
#### 데이터베이스 인덱스 추가
이미 마이그레이션에 포함되었지만 확인:
```php
$table->index(['status', 'ends_at']); // 경매 목록 조회 최적화
$table->index(['auction_id', 'created_at']); // 입찰 기록 조회 최적화
```
#### Eager Loading으로 N+1 쿼리 방지
```php
// Before
$auctions = Auction::paginate(15);
foreach ($auctions as $auction) {
echo $auction->user->name; // N+1 발생
}
// After
$auctions = Auction::with('user', 'bids')->paginate(15);
```
#### Redis 캐싱 (선택사항)
`.env`:
```
CACHE_DRIVER=redis
SESSION_DRIVER=redis
```
경매 목록 캐싱:
```php
$auctions = Cache::remember('auctions.active', 60, function () {
return Auction::where('status', 'active')->with('user')->get();
});
```
입찰 발생 시 캐시 무효화:
```php
Cache::forget('auctions.active');
```
#### 이미지 최적화
- 업로드 시 자동 리사이징 (Intervention Image 라이브러리)
```bash
composer require intervention/image
```
```php
use Intervention\Image\Facades\Image;
$image = $request->file('image');
$filename = time() . '.' . $image->getClientOriginalExtension();
$path = storage_path('app/public/auctions/' . $filename);
Image::make($image)
->resize(800, 600, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})
->save($path, 80); // 80% 품질
$auction->image = 'auctions/' . $filename;
```
### 3. 프로덕션 배포 준비
#### 환경 변수 설정
`.env.production`:
```
APP_ENV=production
APP_DEBUG=false
APP_URL=https://yourdomain.com
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=auction_platform
DB_USERNAME=root
DB_PASSWORD=secure_password
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=your_username
MAIL_PASSWORD=your_password
BROADCAST_DRIVER=reverb
QUEUE_CONNECTION=redis
CACHE_DRIVER=redis
SESSION_DRIVER=redis
```
#### 최적화 명령어 실행
```bash
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan optimize
npm run build
```
#### Supervisor 설정 (Queue Worker 데몬)
`/etc/supervisor/conf.d/auction-worker.conf`:
```ini
[program:auction-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /path/to/project/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/path/to/project/storage/logs/worker.log
stopwaitsecs=3600
```
```bash
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start auction-worker:*
```
#### Nginx 설정 예시
```nginx
server {
listen 80;
server_name yourdomain.com;
root /path/to/project/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
```
### 4. 모니터링 및 로깅
#### Laravel Telescope (개발 환경)
```bash
composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrate
```
`.env`:
```
TELESCOPE_ENABLED=true
```
`/telescope` 경로에서 요청, 쿼리, 이벤트 모니터링.
#### 에러 로깅
`.env`:
```
LOG_CHANNEL=daily
LOG_LEVEL=error
```
#### Sentry 연동 (선택사항)
```bash
composer require sentry/sentry-laravel
php artisan sentry:publish --dsn=your_sentry_dsn
```
### 5. 최종 테스트
#### 기능 테스트 체크리스트
- [ ] 회원가입/로그인 동작
- [ ] 경매 등록 및 이미지 업로드
- [ ] 경매 목록 페이지 필터링
- [ ] 실시간 입찰 및 WebSocket 업데이트
- [ ] 자동 입찰 시스템
- [ ] 즉시구매 기능
- [ ] 경매 자동 종료 (스케줄러)
- [ ] 낙찰 이메일 알림
- [ ] 인앱 알림 표시
- [ ] 입찰 히스토리 조회
- [ ] 판매자 대시보드
- [ ] Rate Limiting 동작
- [ ] 반응형 UI (모바일)
#### 성능 테스트
- Apache Bench로 부하 테스트:
```bash
ab -n 1000 -c 10 http://localhost:8000/auctions
```
- 쿼리 최적화 확인 (Laravel Debugbar):
```bash
composer require barryvdh/laravel-debugbar --dev
```
### 6. 문서화
#### README.md 작성
```markdown
# 실시간 경매 플랫폼
## 기능
- 실시간 입찰 시스템 (Laravel Reverb)
- 자동 입찰 에이전트
- 즉시구매
- 경매 자동 종료 및 낙찰 알림
- 판매자 대시보드
## 설치
1. `composer install`
2. `npm install && npm run build`
3. `.env` 설정
4. `php artisan migrate`
5. `php artisan storage:link`
## 실행
- Web: `php artisan serve`
- WebSocket: `php artisan reverb:start`
- Queue: `php artisan queue:work`
- Scheduler: `php artisan schedule:work`
## 기술 스택
- Laravel 13.x
- Blade + Tailwind CSS + Alpine.js
- Laravel Reverb (WebSocket)
- SQLite/MySQL
```
배포 후 실제 사용자 테스트를 진행하고 피드백을 수집하세요!
기대 결과: 보안 강화 완료 (CSRF, Rate Limiting, XSS 방지). 성능 최적화 (인덱스, Eager Loading, 캐싱, 이미지 리사이징) 적용. 프로덕션 배포 준비 완료 (환경 변수, 최적화, Supervisor, Nginx). 모니터링 및 로깅 설정. 최종 기능/성능 테스트 완료. README 문서 작성 완료.
제출된 작품 (2)
로그인 후 제출그누보드7 자산 원장 모듈, glitter-economy
포인트/예치금/보증금/정산 등 다양한 잔액과 거래를 관리하는 범용 지갑·원장 시스템