
B2C ์ฑ์์ 100% ๋น์ฒจ ๋ฝ๊ธฐ ์ด๋ฒคํธ๋ฅผ ๋งก๊ฒ ๋์๋ค. ๊ฐ๋ฐํ๋ ๊ณผ์ ์ด ํฅ๋ฏธ๋กญ๊ณ ์ฌ๋ฏธ์์ด์, ์ด๋ฅผ ๋ธ๋ก๊ทธ์ ๊ธฐ๋กํด๋ณด๊ธฐ๋ก ํ๋ค.
์ด๋ฒ ์ด๋ฒคํธ๋ ํ๋ก ํธ์๋์ ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋ชจ๋ ๋งก๊ฒ ๋์๊ณ , ๊ธฐํ ๋ด์ฉ์ ๋ค์๊ณผ ๊ฐ๋ค:
Table: lucky_draw_rewards
EVENT_IDX๋ฅผ ์ฐธ์กฐREWARD_TYPE (์: ํฌ์ธํธ, ๊ฒฝํ์น, ์์ดํ
๋ฑ)REWARD_VALUE (๋ณด์ ๊ฐ)GRANTED_YN (์ง๊ธ ์ฌ๋ถ: Y/N)MEMBER_IDX (๋ณด์ ์๋ นํ ์ ์ ์ ๋ณด)GRANTED_YN)์ ์ ์ ์ ๋ณด(MEMBER_IDX)๋ฅผ ํตํด ๋ณด์ ์ฌ์ฉ ์ฌ๋ถ๋ฅผ ํ๋จ// ํด๋ผ์ด์ธํธ ์์ฒญ
['EVENT_IDX' => $EVENT_IDX] = $request->validate([
'EVENT_IDX' => 'required|integer',
]);
EVENT_IDX๋ฅผ ์ ๋ฌํ์ฌ ์ด๋ค ์ด๋ฒคํธ์ธ์ง ๋ช
์ํจ// ๋จ์ ์๋ชจ ๊ฐ๋ฅ ์๋ (group by WR_REWARD)
$remainings = WmiRoulette::calcRemainings($WFE_IDX);
$totalRemaining = $remainings->sum();
// ๋จ์ ์๋์ด ์์ผ๋ฉด ์ข
๋ฃ
if ($totalRemaining === 0) {
throw new ConflictHttpException('์๋ชจ๊ฐ ์ข
๋ฃ๋์์ต๋๋ค.');
}
//
/**
* ๋ณด์๋ณ ๋จ์ ์๋ ๊ณ์ฐ
*/
public static function calcRemainings(int $WFE_IDX): Collection
{
return WmiRoulette::where('WFE_IDX', $WFE_IDX)
->where('WR_GRANTED', 'N')
->groupBy('WR_REWARD')
->select('WR_REWARD', DB::raw('COUNT(*) as remaining'))
->get()
->mapWithKeys(fn($item) => [$item['WR_REWARD'] => $item['remaining']]);
}
WR_GRANTED = 'N'์ธ row๋ค์ WR_REWARD ๊ธฐ์ค์ผ๋ก ๊ทธ๋ฃนํํ์ฌ ๋จ์ ์๋์ ๊ณ์ฐ$reward = null;
// ์๋ชจ์ ์ฑ๊ณตํ ๋๊น์ง 2ํ ์๋
for ($i = 0; $i < 2; $i++) {
// ๋ณด์ ๊ฒฐ์
$random = random_int(1, $totalRemaining);
foreach ($remainings as $reward => $count) {
if ($random <= $count) break;
$random -= $count;
}
$appliedRow = DB::transaction(function () use ($member, $WFE_IDX, $token, $reward) {
// ์๋ชจ ๋ ์ฝ๋ lock ์์ฒญ
$row = WmiRoulette::where('WFE_IDX', $WFE_IDX)
->where('WR_REWARD', $reward)
->where('WR_GRANTED', 'N')
->lockForUpdate()
->first();
// lock ํ๋ ํ ๊ฒ์ฌ
if (!$row || $row->WR_GRANTED === 'Y') return null;
// ์๋ชจ ๋ ์ฝ๋ ์
๋ฐ์ดํธ
$row->update([
'WR_GRANTED' => 'Y',
'MEMBER_IDX' => $member->MEMBER_IDX,
'WR_REQ_TOKEN' => $token,
]);
// ์๋ชจ๊ถ lock ํ๋ ํ ์ฐจ๊ฐ
$this->lockTicketAndConsume("๋ฃฐ๋ ์๋ชจ: WR_IDX={$row->WR_IDX}");
return $row;
});
if ($appliedRow) break;
}
lockForUpdate()๋ก ๋ฝ ๊ฑธ๊ณ WR_GRANTED)WR_GRANTED = 'Y', MEMBER_IDX ์ ์ฅ ๋ฑ)switch($rewatd['type'])
{
case 'item':
// ์์ดํ
์ง๊ธ ์ฒ๋ฆฌ
break
case 'exp'
// ๊ฒฝํ์น ์ง๊ธ ์ฒ๋ฆฌ
break
case 'point'
// ํฌ์ธํธ ์ง๊ธ ์ฒ๋ฆฌ
break
}
reward ํ์
์ ๋ฐ๋ผ ๊ฐ๊ฐ ๋ค๋ฅธ ์ง๊ธ ๋ก์ง์ ์คํ๊ฐ์ค์น ์ถ์ฒจ ๋ฐฉ์(weighted random selection)์ ๊ฐ ํญ๋ชฉ์ ๊ฐ์ค์น๋ฅผ ๋ถ์ฌํ๊ณ ,
๊ทธ ๋น์จ์ ๋ฐ๋ผ ๋ฌด์์๋ก ํญ๋ชฉ์ ์ ํํ๋ ๋ฐฉ์์.
์ด๋ฒ ์ด๋ฒคํธ์์๋ ๊ฐ ๋ณด์๋ง๋ค ๋จ์ ์๋์ด ๋ค๋ฅด๊ธฐ ๋๋ฌธ์
๋จ์ ์๋์ ๊ทธ๋๋ก ๊ฐ์ค์น๋ก ์ฌ์ฉํด์ ์ถ์ฒจํจ.
์ฆ, ์๋์ด ๋ง์ ๋ณด์์ด ๋น์ฒจ ํ๋ฅ ๋ ๋์์ง๋ ๊ตฌ์กฐ.
์๋์ ๊ฐ์ด ๋ณด์์ด ๋จ์ ์๋ค๊ณ ๊ฐ์ ํจ:
| ๋ณด์ ์ข ๋ฅ | ๋จ์ ์๋ |
|---|---|
| ํฌ์ธํธ | 2,000๊ฐ |
| ๊ฒฝํ์น | 3,000๊ฐ |
| ์์ดํ | 5,000๊ฐ |
์ด ๋จ์ ์๋์ 10,000๊ฐ.
๋๋ค ์ซ์๋ฅผ 1๋ถํฐ 10,000 ์ฌ์ด์์ ํ๋ ๋ฝ์์, ํด๋น ๋ฒ์์ ๋ฐ๋ผ ๋ณด์์ ์ ํํจ.
๋๋ค ๊ฐ์ด ์ด๋์ ์ํ๋์ ๋ฐ๋ผ ์ด๋ค ๋ณด์์ด ๋น์ฒจ๋ ์ง ๊ฒฐ์ ๋จ.
๋จ์ ์๋์ด ๋ง์์๋ก ๋ ์์ฃผ ๋ฝํ๋ ๊ตฌ์กฐ์.
์ด ๋ฐฉ์์ ์ฅ์ ์ ์๋์ ๊ฐ์:
์ถ์ฒจ ๋ก์ง์ด ์ฌํํ๋ฉด์๋ ๊ท ํ ์๊ฒ ๋์ํจ.