# VIP系統規格書 [TOC] ## 需求說明 ### 階級方案 #### 靜態階級 數據庫初始時,靜態生成 <span style="color:#808080">**10**</span> 個階級. - 禮包識別碼規則 > 組別階級編號 = `id`(自增鍵) % 10 以 `vip_gifts_{組別階級編號}` 為禮包唯一識別碼. - 修改週期數據 複製當前週期對應的系列階級,修改並於插入數據庫作為下一週期的數據. - 新增週期數據 各週期的組別階級數據為常量,不允許新增. #### 動態階級 - 禮包識別碼規則 > 門檻權重 = 儲值門檻 + 有效投注 > 階級 = 升冪排序索引(門檻權重&id) 以 `vip_gifts_{階級}` 為禮包唯一識別碼. - 修改週期數據 複製當前週期對應的系列階級,修改並於插入數據庫作為下一週期的數據. - 新增週期數據 複製當前週期對應的系列階級,新增並於插入數據庫作為下一週期的數據. <details> <summary><b>週期迭代情境說明</b></summary> > 第一個週期 > |id|*階級*|儲值|投注|*權重*| > |-|-|-|-|-| > |4|1|100|300|400| > |2|2|150|350|500| > |3|3|200|400|600| > |1|4|100|500|600| > |5|5|250|450|700| 假設玩家在第一個週期達到`階級二`,結算時將派發`階級一`與`階級二`對應的禮包內容. > 第二個週期(相較第一個週期,在原本`階級一`與`階級二`間插入一個新階級) > |id|*階級*|儲值|投注|*權重*| > |-|-|-|-|-| > |4|1|100|300|400| > |6|2|200|250|450| > |2|3|150|350|500| > |3|4|200|400|600| > |1|5|100|500|600| > |5|6|250|450|700| 第二個週期結算時,由於`階級一`與`階級二`對應的禮包內容已在第一週期派發,故無法再次派發,此時若玩家達成`階級三`的門檻時,將會派發`階級三`對應的禮包內容. </details> ## API規格 ### 檢索階級門檻 > 各階級有效投注門檻、各階級儲值門檻、各階級等級. ### 檢索階級狀態 > 當前階級、週期內有效投注、週期內儲值. ### VIP前端API [連結](https://hackmd.io/wt94MYY9RJmta2fN_qnXIQ?both) ## 行為流程圖 ### 等級提升 ### 降級處置 ### 週期設置 ### 禮包派發 ## 數據庫表 ### 狀態表(vip_stats) #### 數據結構 | user_id | deposit | valid_bet | tier_id | tier_id_achieved | created_at | updated_at | |---------|---------|-----------|-------|----------------|--------------------|--------------------| | 1001 | 650 | 720 | 1 | 2 | 2024-02-26 08:00:00| 2024-02-26 08:00:00| | 1002 | 420 | 890 | 2 | 2 | 2024-02-25 11:30:00| 2024-02-25 11:30:00| | 1003 | 880 | 250 | 2 | 3 | 2024-02-24 09:45:00| 2024-02-24 09:45:00| | 1004 | 210 | 510 | 3 | 3 | 2024-02-23 06:20:00| 2024-02-23 06:20:00| | 1005 | 790 | 320 | 3 | 4 | 2024-02-22 13:10:00| 2024-02-22 13:10:00| > 透過 `level_achived`(達成等級) 與 `level`(目前等級) 判斷玩家是否降級過. - Laravel Migration ```PHP Schema::create('vip_stats', function (Blueprint $table) { $table->string('user_id', 32); $table->decimal('deposit', 16, 4) ->default(0.0000) ->comment('歷史累積儲值'); $table->decimal('valid_bet', 16, 4) ->default(0.0000) ->comment('歷史累積效投'); $table->unsignedtinyInteger('tier_id')->comment('目前階級id'); $table->unsignedtinyInteger('tier_id_achieved')->comment('達成階級id'); $table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP')); $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')); $table->primary('user_id'); $table->index('user_id'); $table->index('updated_at'); }); ``` ### 週期表(vip_cycles) 會員週期與版主週期統計數據. #### 數據結構 | id | user_id | deposit | valid_bet | cycle_begin_date | cycle_until_date | created_at | updated_at | |----|---------|---------|-----------|---------------------|---------------------|----------------------|----------------------| | 1 | 1001 | 500 | 700 | 2024-03-01 | 2024-03-30| 2024-02-28| 2024-02-28| | 2 | 1002 | 1000 | 1500 | 2024-03-01| 2024-03-30 | 2024-02-29| 2024-02-29| | 3 | 1003 | 1500 | 2000 | 2024-03-01| 2024-03-30| 2024-03-01| 2024-03-01| | 4 | 1004 | 2000 | 2500 | 2024-03-01 | 2024-03-30| 2024-03-01| 2024-03-02| | 5 | 1005 | 2500 | 3000 | 2024-03-01 | 2024-03-30 | 2024-03-01 | 2024-03-03 | | 6 | 1004 | 2500 | 3000 | 2024-04-01 | 2024-04-30 | 2024-04-03 | 2024-03-03 | | 7 | 1005 | 2500 | 3000 | 2024-04-01 | 2024-04-30 | 2024-04-03 | 2024-04-03 | - Laravel Migration ```PHP Schema::create('vip_cycles', function (Blueprint $table) { $table->increments('id'); $table->string('user_id', 32); $table->decimal('deposit', 16, 4) ->default(0.0000) ->comment('當週累積儲值'); $table->decimal('valid_bet', 16, 4) ->default(0.0000) ->comment('當週累積效投'); $table->date('cycle_begin_date')->comment('週期起始日'); $table->date('cycle_until_date')->comment('週期結束日'); $table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP')); $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')); $table->index('user_id'); $table->index(['cycle_begin_at', 'cycle_until_at']); $table->index('updated_at'); }); ``` ### 階級表(vip_tiers) 定義階級所需要的儲值門檻、投注門檻. #### 數據結構 | id | name | upgrade_deposit | upgrade_valid_bet | keep_deposit | keep_valid_bet | enable | created_at| updated_at| |----|--------------------|---------|-----------|---------|-----------|-----------|--------------------|--------------------| | 1 | 階級1| 300 | 400 | 300 | 400 | ture | 2024-02-20 08:00:00| 2024-02-20 08:00:00| | 2 | 階級2| 350 | 500 | 350 | 500 | false | 2024-02-20 08:00:00| 2024-02-20 08:00:00| | 3 | 階級3| 400 | 600 | 400 | 600 | ture | 2024-02-20 08:00:00| 2024-02-20 08:00:00| | 4 | 階級4| 500 | 600 | 500 | 600 | ture | 2024-02-20 08:00:00| 2024-02-20 08:00:00| | 5 | 階級5| 450 | 700 | 450 | 700 | ture | 2024-02-20 08:00:00| 2024-02-20 08:00:00| | 6 | 階級6| 300 | 400 | 300 | 400 | ture | 2024-02-22 13:10:00| 2024-02-22 13:10:00| | 7 | 階級7| 350 | 500 | 350 | 500 | ture | 2024-02-22 13:10:00| 2024-02-22 13:10:00| | 8 | 階級8| 400 | 600 | 400 | 600 | false | 2024-02-22 13:10:00| 2024-02-22 13:10:00| | 9 | 階級9| 500 | 600 | 500 | 600 | ture | 2024-02-22 13:10:00| 2024-02-22 13:10:00| | 10 | 階級10| 450 | 700 | 450 | 700 | false | 2024-02-22 13:10:00| 2024-02-22 13:10:00| - Laravel Migration ```PHP Schema::create('vip_tiers', function (Blueprint $table) { $table->string('id', 32)->comment('階級id'); $table->string('name', 32)->comment('名稱'); $table->decimal('upgrade_deposit', 16, 4) ->default(0.0000) ->comment('升級儲值門檻'); $table->decimal('upgrade_valid_bet', 16, 4) ->default(0.0000) ->comment('升級效投門檻'); $table->decimal('keep_deposit', 16, 4) ->default(0.0000) ->comment('保級儲值門檻'); $table->decimal('keep_valid_bet', 16, 4) ->default(0.0000) ->comment('保級效投門檻'); $table->boolean('enable')->default(false)->comment('開關'); $table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP')); $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')); $table->primary('id'); $table->unique('upgrade_deposit'); $table->unique('upgrade_valid_bet'); $table->index(['id', 'enable']); $table->index('updated_at'); }); ``` ### 日誌表(vip_logs) 1. 異動VIP等級時需要紀錄 2. 派發3種禮包時紀錄 3. VIP門檻升降級 4. 手動調整VIP等級 #### 數據結構 | id | user_id | type | current_tier_id | achieved_tier_ids | log_date | handler_id | remark | notice | created_at| updated_at| |----|----|----|--------|---------|-------|-------|-------|-------|--------------------|--------------------| |01hrrxnaeehy6nd58bzbf0r54n|01hcyg20p3v4npn7pc356xvyjk|register|1|[]|2024-03-19|01hcyg20p3v4npn7pc356xvyjl||no|2024-03-19 08:00:00| 2024-03-19 08:00:00| |01hrrxnaeehy6nd58bzbf0r54k|01hcyg20p3v4npn7pc356xvyjk|system-upgrade|2|[2]|2024-03-19|01hcyg20p3v4npn7pc356xvyjl||no|2024-03-19 12:00:00| 2024-03-19 12:00:00| |01hrrxnaeehy6nd58bzbf0r54l|01hcyg20p3v4npn7pc356xvyjk|manual-upgrade|4|[3,4]|2024-03-19|01hcyg20p3v4npn7pc356xvyjl||no|2024-03-19 14:00:00| 2024-03-19 14:00:00| |01hrrxnaeehy6nd58bzbf0r54a|01hcyg20p3v4npn7pc356xvyjk|downgrade|1|[]|2024-03-30|01hcyg20p3v4npn7pc356xvyjl||no|2024-03-30 12:00:00| 2024-03-30 12:00:00| - Laravel Migration ```PHP Schema::create('vip_logs', function (Blueprint $table) { // [PK] 資料識別碼 $table->string('id', 32)->comment('資料識別碼'); // 會員ID $table->string('user_id', 32)->comment('會員ID, mapping users.id'); $table->string('type', 32)->comment('紀錄類型'); $table->unsignedtinyInteger('current_tier_id')->comment('目前階級id'); $table->longtext('achieved_tier_ids') ->default('[]') ->comment('升降級的tier id or 禮包id'); $table->string('log_date')->comment('紀錄時間'); $table->string('handler_id', 32)->comment('操作人員'); // 備註 $table->text('remark')->default('')->comment('備註'); // 會員推波通知開關 (yes已通知, no未通知) $table->enum('notice', ['yes', 'no'])->default('no')->comment('會員推波通知開關 (yes已通知, no未通知)'); $table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP')); $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')); // === 索引 === // 指定主鍵索引 $table->primary(['id']); $table->index('user_id'); $table->index('type'); $table->index('updated_at'); }); ``` #### 站台/會員週期相關設置 config 1.起始日期 2.週期天數 #### 業務邏輯 - VIP階級推算 - SQL 語法 ```SQL SELECT ROW_NUMBER() OVER( ORDER BY (deposit + valid_bet) ASC, id ASC ) AS level, id, deposit, valid_bet, (deposit + valid_bet) AS weight FROM your_table_name ORDER BY weight ASC, id ASC; ``` - Laravel 語法 ```PHP= $results = YourTableName::select([ DB::raw("ROW_NUMBER() OVER( ORDER BY (deposit + valid_bet) ASC, id ASC ) AS level"), 'valid_bet', 'deposit', DB::raw('(deposit + valid_bet) AS weight') ]) ->orderBy('weight', 'asc') ->orderBy('id', 'asc') ->get(); ``` - 預期結果 |level|deposit|valid_bet|weight| |-|-|-|-| |1|100|300|400| |2|150|350|500| |3|200|400|600| |4|100|500|600| |5|250|450|700| ### 活動對應表(vip_activity_relation) VIP與活動關聯的表 #### 數據結構 |id|vip_tiers_id|activity_id|type|point|rebate_table|rebate_mode|threshold_gift_multiple|revenge_limit|revenge_frequency_days|gift_limit|created_at|updated_at| |-------|----|---------|-----|------|----|------|---------|---|----|----|----------|---------| |01e4nb76nq2440vpmjf6b4dhva|01e4nb76nq2440vpmjf6b4dhvb|01e4nb76nq2440vpmjf6b4dhvd|upgrade|[]|null|50|1|0|0|0|2024-02-20 08:00:00| 2024-02-20 08:00:00| |01e66hv9j1e8b8rjde9xtak73a|01e66hv9j1e8b8rjde9xtak73b|01e66hv9j1e8b8rjde9xtak731|revenge|[]|null|60|1|100|20|0|2024-02-20 08:00:00| 2024-02-20 08:00:00| |01e66mp01m3w9crbbb3aaff0ka|01e66mp01m3w9crbbb3aaff0kb|01e66mp01m3w9crbbb3aaff0kd|rebate|{"sa_gaming":{"bac":{"general":"9"}}}|station|0|1|0|0|1000|2024-02-20 08:00:00| 2024-02-20 08:00:00| - Laravel Migration ```PHP Schema::create('vip_activity_relation', function (Blueprint $table) { $table->string('id', 32); $table->string('vip_tier_id', 32)->comment('VIP階級ID'); $table->string('activity_id', 32)->comment('活動ID'); $table->string('type', 16)->comment('類型 (upgrade=升級, revenge=復仇金, rebate=返水)'); $table->decimal('point', 16, 4) ->default(0.0000) ->comment('獎金'); $table->longtext('rebate_table') ->default('[]') ->comment('遊戲返水表'); $table->string('rebate_mode', 16) ->nullable() ->default(null) ->comment('返水模式'); $table->decimal('revenge_limit', 16, 4) ->default(0.0000) ->comment('復仇金最低額'); $table->unsignedtinyInteger('revenge_frequency_days')->default(0)->comment('復仇金發送頻率'); $table->decimal('threshold_gift_multiple', 16, 4) ->default(0.0000) ->comment('洗碼倍數(禮金)'); $table->decimal('gift_limit', 16, 4) ->default(0.0000) ->comment('禮金上限'); $table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP')); $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')); $table->primary('id'); $table->index(['activity_id', 'vip_tier_id']); $table->index('type'); $table->index('updated_at'); }); ``` ### VIP活動條件表(vip_gift_condition) 由vip_activity_relation衍生出來的活動條件. #### 數據結構 |id|is_daily_login|total_valid_bet|station_valid_bet|revenge_limit|revenge_frequency_days|created_at|updated_at| |---|---|---|---|---|---|---|---| |01e4nb76nq2440vpmjf6b4dhva|true|null|null|null|null|2024-02-20 08:00:00| 2024-02-20 08:00:00| |01e4nb76nq2440vpmjf6b4dhvb|null|100|null|null|null|2024-02-20 08:00:00| 2024-02-20 08:00:00| |01e4nb76nq2440vpmjf6b4dhvc|null|null|1000|null|null|2024-02-20 08:00:00| 2024-02-20 08:00:00| - Laravel Migration ```php Schema::create('vip_gift_condition', function (Blueprint $table) { $table->string('id', 32); $table->boolean('is_daily_login')->nullable()->comment('每日登入'); $table->decimal('total_valid_bet', 16, 4)->nullable()->comment('總有效投注'); $table->longtext('station_valid_bet')->nullable()->comment('特定遊戲館有效投注'); $table->decimal('revenge_limit', 16, 4)->nullable()->comment('復仇金最低額'); $table->unsignedtinyInteger('revenge_frequency_days')->nullable()->comment('復仇金發送頻率'); $table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP')); $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')); $table->primary('id'); $table->index('updated_at'); }); ``` ### 活動彩卷表(activity_lottery) 活動彩卷包含所屬活動、週期、票卷類型、關聯獲得獎項. #### 數據結構 |id|user_id|activity_id|activity_sheet_id|gift_id|gift_model|ticket_type|prize_id|expired_at|used_at|handle_by|created_at|updated_at| |---|---|---|---|---|---|---|---|---|---|---|---|---| |01e66mp01m3w9crbbb3aaff0kb|01e66mp01m3w9crbbb3aafsjro|01e66mp01m3w9crbbb3aaff8po|01e66mp01m3w9crbbb3aaffjey|null|null|null|null|2999-01-01 00:00:00|null|01e66mp01m3w9crbbb3aajue8i|2024-02-20 08:00:00| 2024-02-20 08:00:00| - Laravel Migration ```php Schema::create('activity_lottery', function (Blueprint $table) { $table->string('id', 32)->comment('獎券唯一值'); $table->string('user_id', 32)->comment('會員id'); $table->string('activity_id', 32)->nullable()->comment('對應活動id'); $table->string('activity_sheet_id', 32)->nullable()->comment('對應活動期數id'); $table->string('gift_id', 32)->nullable()->comment('對應禮包資訊id'); $table->string('gift_model', 16)->nullable()->comment('對應禮包的model'); $table->string('ticket_type', 8)->nullable()->comment('票券類型'); $table->string('prize_id', 32)->nullable()->comment('對應獎項id'); $table->datetime('expired_at')->default('2999-01-01 00:00:00')->comment('獎券到期時間'); $table->datetime('used_at')->nullable()->comment('獎券抽獎時間'); $table->string('handle_by', 32)->nullable()->comment('派發人員'); $table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP')); $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')); $table->primary('id'); $table->index('user_id'); $table->index(['activity_id', 'activity_sheet_id']); $table->index('created_at'); $table->index('updated_at'); }); ``` ## 開發資訊 ### setting 設定參數 > 取得方式 ```php= config('platform.vip_cycles') ``` > 參數內容 ```php= 'vip_cycles' => [ 'days' => -1, 'begin_date' => '', 'type' => '', ] ``` ### Gitlab分支 > vip_dev ### 開發測試站 > aa 站 ### 前端頁面 > [here](https://upspeedt.com/) ### 版主端頁面 > [here](https://ctup.upspeedt.com/zh-Hant) > 帳密同ab ==關閉自動部署,需進機器自行reset== ## 系統Q&A 1. 異常補發VIP禮包時是否需要以前一個週期的規則? 2. 版主套用會員週期時,由於各用戶週期不同,要如何統計上期人數? -- 設定成會員週期不用顯示上期人數 3. 每日結算時間是否可以訂於中午12:00? -- 可設定於中午12:00 4. 週期內異動VIP階級表時,是否會影響當下週期的邏輯判斷? ## 測試要點 ## 小記錄 1. 開一張附表對應 activities 與 vip 2. sheet 表為排程自動產生 * `activity:manual-checkout->checkoutActivityPeriod` * `activity:rebate:sheet->createRebateActivitySheetByDateTime` 3. 一個VIP會產生三個禮包:返水、升級、復仇