๐ŸŽฐ 100% ๋‹น์ฒจ ๊ฐ€์ฑ  ์ด๋ฒคํŠธ ๊ฐœ๋ฐœ๊ธฐ

Zainยท2025๋…„ 4์›” 26์ผ
post-thumbnail

B2C ์•ฑ์—์„œ 100% ๋‹น์ฒจ ๋ฝ‘๊ธฐ ์ด๋ฒคํŠธ๋ฅผ ๋งก๊ฒŒ ๋˜์—ˆ๋‹ค. ๊ฐœ๋ฐœํ•˜๋Š” ๊ณผ์ •์ด ํฅ๋ฏธ๋กญ๊ณ  ์žฌ๋ฏธ์žˆ์–ด์„œ, ์ด๋ฅผ ๋ธ”๋กœ๊ทธ์— ๊ธฐ๋กํ•ด๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค.

๐ŸŽฏ ์ด๋ฒคํŠธ ์„ค๋ช…

์ด๋ฒˆ ์ด๋ฒคํŠธ๋Š” ํ”„๋ก ํŠธ์—”๋“œ์™€ ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์„ ๋ชจ๋‘ ๋งก๊ฒŒ ๋˜์—ˆ๊ณ , ๊ธฐํš ๋‚ด์šฉ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค:

  • ์ด 10,000๊ฐœ์˜ ๋ณด์ƒ์ด ์ค€๋น„๋˜์–ด ์žˆ์Œ
  • ๋‹น์ฒจ ํ™•๋ฅ ์€ 100% (์ฆ‰, ์‘๋ชจ๋งŒ ํ•˜๋ฉด ๋ฌด์กฐ๊ฑด ๋ณด์ƒ ์ง€๊ธ‰)
  • ๋ณด์ƒ์€ ์ด 6์ข…๋ฅ˜์ด๋ฉฐ, ๊ฐ ๋ณด์ƒ๋งˆ๋‹ค ์ˆ˜๋Ÿ‰์ด ๋‹ค๋ฆ„
  • ๋ชจ๋“  ๋ณด์ƒ์ด ์†Œ์ง„๋˜๋ฉด ์ด๋ฒคํŠธ๋Š” ์ž๋™ ์ข…๋ฃŒ๋จ

๐Ÿ—๏ธ 100% ๋‹น์ฒจ ๋ฝ‘๊ธฐ ์•„ํ‚คํ…์ฒ˜

๐Ÿ”ธ ํ…Œ์ด๋ธ” ๊ตฌ์„ฑ

Table: lucky_draw_rewards

  • ์œ ์‚ฌํ•œ ์ด๋ฒคํŠธ์— ์žฌ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ๊ณตํ†ต ํ…Œ์ด๋ธ”๋กœ ์„ค๊ณ„
  • ์™ธ๋ž˜ํ‚ค(FK)๋กœ ์ด๋ฒคํŠธ ํ…Œ์ด๋ธ”์˜ EVENT_IDX๋ฅผ ์ฐธ์กฐ
  • ์ฃผ์š” ์ปฌ๋Ÿผ ๊ตฌ์„ฑ:
    • REWARD_TYPE (์˜ˆ: ํฌ์ธํŠธ, ๊ฒฝํ—˜์น˜, ์•„์ดํ…œ ๋“ฑ)
    • REWARD_VALUE (๋ณด์ƒ ๊ฐ’)
    • GRANTED_YN (์ง€๊ธ‰ ์—ฌ๋ถ€: Y/N)
    • MEMBER_IDX (๋ณด์ƒ ์ˆ˜๋ นํ•œ ์œ ์ € ์ •๋ณด)

๐ŸŽ ๋ณด์ƒ ๋ฐ์ดํ„ฐ ์„ธํŒ…

  • ์ด๋ฒคํŠธ ์‹œ์ž‘ ์ „, 10,000๊ฐœ์˜ ๋ณด์ƒ row๋ฅผ ์‚ฌ์ „ ์ฃผ์ž…
  • ์ง€๊ธ‰ ์—ฌ๋ถ€(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 ๊ธฐ์ค€์œผ๋กœ ๊ทธ๋ฃนํ•‘ํ•˜์—ฌ ๋‚จ์€ ์ˆ˜๋Ÿ‰์„ ๊ณ„์‚ฐ
  • ๋‚จ์€ ์ˆ˜๋Ÿ‰์ด 0์ด๋ฉด ์ด๋ฒคํŠธ๋Š” ์ข…๋ฃŒ ์ฒ˜๋ฆฌ

โ‘ก ๋ณด์ƒ ๊ฒฐ์ • (์ˆ˜๋Ÿ‰ ๊ธฐ๋ฐ˜ ๊ฐ€์ค‘์น˜ ์ถ”์ฒจ)

$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;
}
๋ณด์ƒ ๊ฒฐ์ • ๋กœ์ง ์š”์•ฝ:
  • ๋‚จ์€ ์ˆ˜๋Ÿ‰ ๊ธฐ๋ฐ˜ ๊ฐ€์ค‘์น˜ ๋ฐฉ์‹์œผ๋กœ ๋ณด์ƒ์„ ๋žœ๋ค ์„ ํƒ
  • ์ „์ฒด ๋‚จ์€ ์ˆ˜๋Ÿ‰ ๋ฒ”์œ„ ๋‚ด์—์„œ ์ˆซ์ž๋ฅผ ํ•˜๋‚˜ ๋ฝ‘๊ณ , ํ•ด๋‹น ๊ฐ’์ด ์–ด๋А ๋ณด์ƒ์— ์†ํ•˜๋Š”์ง€ ๊ณ„์‚ฐ
  • ์ถ”์ฒจ ๊ฒฐ๊ณผ์— ํ•ด๋‹นํ•˜๋Š” ๋ณด์ƒ row๋ฅผ DB์—์„œ lockForUpdate()๋กœ ๋ฝ ๊ฑธ๊ณ 
    • ์ด๋ฏธ ์ง€๊ธ‰๋๋Š”์ง€ ๊ฒ€์ฆ (WR_GRANTED)
    • ์œ ํšจํ•˜๋‹ค๋ฉด ์ง€๊ธ‰ ์ฒ˜๋ฆฌ (WR_GRANTED = 'Y', MEMBER_IDX ์ €์žฅ ๋“ฑ)
  • 2ํšŒ๊นŒ์ง€ ์‹œ๋„, ์‹คํŒจ ์‹œ ๋‹ค๋ฅธ ๋ณด์ƒ์œผ๋กœ ์žฌ์‹œ๋„

๐Ÿ” ๋ณด์ƒ ์ง€๊ธ‰ ์ฒ˜๋ฆฌ

  • ์ถ”์ฒจ๋œ ๋ณด์ƒ ์ •๋ณด๋ฅผ ํ† ๋Œ€๋กœ, ์‹ค์ œ ๋ณด์ƒ์„ ์ง€๊ธ‰
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 ์‚ฌ์ด์—์„œ ํ•˜๋‚˜ ๋ฝ‘์•„์„œ, ํ•ด๋‹น ๋ฒ”์œ„์— ๋”ฐ๋ผ ๋ณด์ƒ์„ ์„ ํƒํ•จ.

  • 1 ~ 2,000 โ†’ ํฌ์ธํŠธ
  • 2,001 ~ 5,000 โ†’ ๊ฒฝํ—˜์น˜
  • 5,001 ~ 10,000 โ†’ ์•„์ดํ…œ

๋žœ๋ค ๊ฐ’์ด ์–ด๋””์— ์†ํ•˜๋ƒ์— ๋”ฐ๋ผ ์–ด๋–ค ๋ณด์ƒ์ด ๋‹น์ฒจ๋ ์ง€ ๊ฒฐ์ •๋จ.
๋‚จ์€ ์ˆ˜๋Ÿ‰์ด ๋งŽ์„์ˆ˜๋ก ๋” ์ž์ฃผ ๋ฝ‘ํžˆ๋Š” ๊ตฌ์กฐ์ž„.


์ด ๋ฐฉ์‹์˜ ์žฅ์ ์€ ์•„๋ž˜์™€ ๊ฐ™์Œ:

  • ๋ณ„๋„๋กœ ํ™•๋ฅ  ๊ณ„์‚ฐ ์•ˆ ํ•ด๋„ ๋จ (์ˆ˜๋Ÿ‰ = ํ™•๋ฅ )
  • ๋ณด์ƒ์ด ์ ์  ์†Œ์ง„๋ ์ˆ˜๋ก ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ™•๋ฅ ์ด ์žฌ๋ถ„๋ฐฐ๋จ
  • ํŠน์ • ๋ณด์ƒ๋งŒ ๋ชฐ๋ฆฌ๋Š” ํ˜„์ƒ ๋ฐฉ์ง€ ๊ฐ€๋Šฅ
  • ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ๊ฐ€ ์šฉ์ดํ•จ (๋ฝ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฒ˜๋ฆฌํ•  ๊ฒฝ์šฐ)
  • ์ด๋ฒคํŠธ ์ข…๋ฃŒ ์‹œ์ ์ด ๋‹ค๊ฐ€์˜ฌ์ˆ˜๋ก ๋‚จ์€ ๋ณด์ƒ ์ค‘ ํ•˜๋‚˜๊ฐ€ ํ™•์ •์ ์œผ๋กœ ๋ฝ‘ํžˆ๊ฒŒ ๋จ

์ถ”์ฒจ ๋กœ์ง์ด ์‹ฌํ”Œํ•˜๋ฉด์„œ๋„ ๊ท ํ˜• ์žˆ๊ฒŒ ๋™์ž‘ํ•จ.

profile
Vue, Laravel | TS, JS, PHP, MySQL

0๊ฐœ์˜ ๋Œ“๊ธ€