1.将部门修改为渠道,并且所有dice_表关联渠道表

2.将所有配置表,记录表设置关联渠道
3.优化后台页面设置
This commit is contained in:
2026-05-19 09:49:02 +08:00
parent 085454fb78
commit dd264b1e97
143 changed files with 4741 additions and 1254 deletions

View File

@@ -10,6 +10,7 @@ use support\think\Db;
use app\api\logic\GameLogic;
use app\api\logic\PlayStartLogic;
use app\api\util\ReturnCode;
use app\dice\helper\AdminScopeHelper;
use app\dice\model\config\DiceConfig;
use app\dice\model\ante_config\DiceAnteConfig;
use app\dice\model\play_record\DicePlayRecord;
@@ -34,7 +35,12 @@ class GameController extends BaseController
*/
public function config(Request $request): Response
{
$rows = DiceConfig::select('name', 'group', 'title', 'title_en', 'value', 'value_en', 'create_time', 'update_time')->get();
$configDeptId = $this->resolvePlayerConfigDeptIdFromRequest($request);
$rows = (new DiceConfig())
->field('name,group,title,title_en,value,value_en,create_time,update_time')
->where('dept_id', $configDeptId)
->select()
->toArray();
$lang = $request->header('lang', 'zh');
if (!is_string($lang) || $lang === '') {
$lang = 'zh';
@@ -43,15 +49,15 @@ class GameController extends BaseController
$isEn = $langLower === 'en' || str_starts_with($langLower, 'en-');
$data = [];
foreach ($rows as $row) {
$group = $row->group ?? '';
$group = $row['group'] ?? '';
if (!isset($data[$group])) {
$data[$group] = [];
}
$title = $row->title;
$value = $row->value;
$title = $row['title'] ?? '';
$value = $row['value'] ?? '';
if ($isEn) {
$titleEn = $row->title_en ?? '';
$valueEn = $row->value_en ?? '';
$titleEn = $row['title_en'] ?? '';
$valueEn = $row['value_en'] ?? '';
if ($titleEn !== '') {
$title = $titleEn;
}
@@ -60,11 +66,11 @@ class GameController extends BaseController
}
}
$data[$group][] = [
'name' => $row->name,
'name' => $row['name'] ?? '',
'title' => $title,
'value' => $value,
'create_time' => $row->create_time,
'update_time' => $row->update_time,
'create_time' => $row['create_time'] ?? '',
'update_time' => $row['update_time'] ?? '',
];
}
return $this->success($data);
@@ -107,7 +113,8 @@ class GameController extends BaseController
*/
public function lotteryPool(Request $request): Response
{
$list = DiceRewardConfig::getCachedList();
$configDeptId = $this->resolvePlayerConfigDeptIdFromRequest($request);
$list = DiceRewardConfig::getCachedList($configDeptId);
$list = array_values(array_filter($list, function ($row) {
return (string) ($row['tier'] ?? '') !== 'BIGWIN';
}));
@@ -145,9 +152,9 @@ class GameController extends BaseController
*/
public function anteConfig(Request $request): Response
{
// 用于后续抽奖校验:在接口中实例化 model后续逻辑可复用相同的数据读取方式。
$configDeptId = $this->resolvePlayerConfigDeptIdFromRequest($request);
$anteConfigModel = new DiceAnteConfig();
$rows = $anteConfigModel->order('id', 'asc')->select()->toArray();
$rows = $anteConfigModel->where('dept_id', $configDeptId)->order('id', 'asc')->select()->toArray();
return $this->success($rows);
}
@@ -200,7 +207,8 @@ class GameController extends BaseController
$rewardTier = array_key_exists('reward_tier', $data) ? (string) ($data['reward_tier'] ?? '') : '';
$targetIndex = array_key_exists('target_index', $data) ? (int) ($data['target_index'] ?? 0) : 0;
if ($rewardTier !== 'BIGWIN' && $targetIndex > 0) {
$configRow = DiceRewardConfig::getCachedById($targetIndex);
$configDeptId = AdminScopeHelper::resolvePlayerConfigDeptId($player);
$configRow = DiceRewardConfig::getCachedById($targetIndex, $configDeptId);
if ($configRow !== null) {
$uiText = '';
$uiTextEn = '';
@@ -273,4 +281,20 @@ class GameController extends BaseController
Db::execute('SELECT RELEASE_LOCK(?)', [$lockName]);
}
}
/**
* 从 token 注入的 player_id 解析所属渠道配置 ID
*/
private function resolvePlayerConfigDeptIdFromRequest(Request $request): int
{
$userId = (int) ($request->player_id ?? 0);
if ($userId <= 0) {
return AdminScopeHelper::DEFAULT_TEMPLATE_DEPT;
}
$player = DicePlayer::find($userId);
if (!$player) {
return AdminScopeHelper::DEFAULT_TEMPLATE_DEPT;
}
return AdminScopeHelper::resolvePlayerConfigDeptId($player);
}
}

View File

@@ -36,4 +36,26 @@ return [
'BATCH_DELETE_FORBIDDEN' => 'Batch delete is not allowed',
'SUPER_ADMIN_CANNOT_DELETE' => 'Super admin cannot be deleted',
'OLD_PASSWORD_WRONG' => 'Old password is incorrect',
'ADD_SUCCESS' => 'Added successfully',
'UPDATE_SUCCESS' => 'Updated successfully',
'DELETE_SUCCESS' => 'Deleted successfully',
'ADD_FAILED' => 'Add failed',
'UPDATE_FAILED' => 'Update failed',
'DELETE_FAILED' => 'Delete failed',
'NOT_FOUND' => 'Data not found',
'ANTE_MUST_POSITIVE' => 'Ante must be greater than 0',
'ANTE_NOT_ALLOWED' => 'Ante %s is not available for current channel, please select from ante config',
'ANTE_CONFIG_NOT_FOUND' => 'Ante config not found',
'ANTE_CONFIG_NOT_IN_CHANNEL' => 'Ante config does not belong to current channel',
'POOL_CONFIG_NOT_IN_CHANNEL' => 'Pool config does not belong to current channel',
'CHANNEL_DEPT_ID_REQUIRED' => 'Please select a channel, or assign a valid administrator/player for this record',
'INVALID_CHANNEL_DEPT_ID' => 'Invalid channel. Please reselect channel or administrator',
'PLAYER_USERNAME_DEPT_UNIQUE' => 'Username already exists in this channel',
'NO_PERMISSION_UPDATE' => 'No permission to update this record',
'NO_PERMISSION_VIEW' => 'No permission to view this record',
'NO_PERMISSION_OPERATE_PLAYER' => 'No permission to operate this player',
'PLEASE_SELECT_DATA' => 'Please select data to delete',
'OPERATION_SUCCESS' => 'Operation successful',
'TEST_DATA_CLEARED' => 'Test data cleared',
'CLEAR_FAILED' => 'Clear failed: %s',
];

View File

@@ -9,6 +9,28 @@ declare(strict_types=1);
return [
'success' => 'Success',
'fail' => 'Fail',
'add success' => 'Added successfully',
'update success' => 'Updated successfully',
'save success' => 'Saved successfully',
'delete success' => 'Deleted successfully',
'add failed' => 'Add failed',
'update failed' => 'Update failed',
'delete failed' => 'Delete failed',
'not found' => 'Data not found',
'operation success' => 'Operation successful',
'test data cleared' => 'Test data cleared',
'ante must be greater than 0' => 'Ante must be greater than 0',
'ante not allowed: %s' => 'Ante %s is not available for current channel, please select from ante config',
'pool config does not belong to current channel' => 'Pool config does not belong to current channel',
'no permission to update this record' => 'No permission to update this record',
'no permission to view this record' => 'No permission to view this record',
'no permission to operate this player' => 'No permission to operate this player',
'please select data to delete' => 'Please select data to delete',
'please select player' => 'Please select player',
'please login first' => 'Please login first',
'missing player_id' => 'Missing player_id',
'Player not found' => 'Player not found',
'record not found' => 'Record not found',
'username、password 不能为空' => 'username and password are required',
'请携带 token' => 'Please provide token',
'token 无效' => 'Invalid or expired token',

View File

@@ -36,5 +36,27 @@ return [
'BATCH_DELETE_FORBIDDEN' => '禁止批量删除操作',
'SUPER_ADMIN_CANNOT_DELETE' => '超级管理员禁止删除',
'OLD_PASSWORD_WRONG' => '原密码错误',
'ADD_SUCCESS' => '添加成功',
'UPDATE_SUCCESS' => '修改成功',
'DELETE_SUCCESS' => '删除成功',
'ADD_FAILED' => '添加失败',
'UPDATE_FAILED' => '修改失败',
'DELETE_FAILED' => '删除失败',
'NOT_FOUND' => '数据不存在',
'ANTE_MUST_POSITIVE' => '底注必须大于 0',
'ANTE_NOT_ALLOWED' => '底注 %s 在当前渠道不可用,请从底注配置中选择',
'ANTE_CONFIG_NOT_FOUND' => '底注配置不存在',
'ANTE_CONFIG_NOT_IN_CHANNEL' => '底注配置不属于当前渠道',
'POOL_CONFIG_NOT_IN_CHANNEL' => '奖池配置不属于当前渠道',
'CHANNEL_DEPT_ID_REQUIRED' => '请选择所属渠道,或为记录指定有效的所属管理员/玩家',
'INVALID_CHANNEL_DEPT_ID' => '渠道无效,请重新选择所属渠道或管理员',
'PLAYER_USERNAME_DEPT_UNIQUE' => '该渠道下用户名已存在',
'NO_PERMISSION_UPDATE' => '无权限修改该记录',
'NO_PERMISSION_VIEW' => '无权限查看该记录',
'NO_PERMISSION_OPERATE_PLAYER' => '无权限操作该玩家',
'PLEASE_SELECT_DATA' => '请选择要删除的数据',
'OPERATION_SUCCESS' => '操作成功',
'TEST_DATA_CLEARED' => '测试数据已清空',
'CLEAR_FAILED' => '清空失败:%s',
];

View File

@@ -6,6 +6,7 @@ namespace app\api\logic;
use app\api\cache\UserCache;
use app\api\util\ApiLang;
use app\api\service\LotteryService;
use app\dice\helper\AdminScopeHelper;
use app\dice\model\lottery_pool_config\DiceLotteryPoolConfig;
use app\dice\model\play_record\DicePlayRecord;
use app\dice\model\ante_config\DiceAnteConfig;
@@ -64,6 +65,8 @@ class PlayStartLogic
throw new ApiException('User not found');
}
$configDeptId = AdminScopeHelper::resolvePlayerConfigDeptId($player);
$coin = (float) $player->coin;
if ($ante <= 0) {
throw new ApiException('ante must be a positive integer');
@@ -71,7 +74,7 @@ class PlayStartLogic
// 注数合规校验ante 必须存在于 dice_ante_config.mult
$anteConfigModel = new DiceAnteConfig();
$exists = $anteConfigModel->where('mult', $ante)->count();
$exists = $anteConfigModel->where('mult', $ante)->where('dept_id', $configDeptId)->count();
if ($exists <= 0) {
throw new ApiException('当前注数不合规,请选择正确的注数');
}
@@ -109,8 +112,8 @@ class PlayStartLogic
}
}
$configType0 = DiceLotteryPoolConfig::where('name', 'default')->find();
$configType1 = DiceLotteryPoolConfig::where('name', 'killScore')->find();
$configType0 = DiceLotteryPoolConfig::where('name', 'default')->where('dept_id', $configDeptId)->find();
$configType1 = DiceLotteryPoolConfig::where('name', 'killScore')->where('dept_id', $configDeptId)->find();
if (!$configType0) {
throw new ApiException('Lottery pool config not found (name=default required)');
}
@@ -120,7 +123,7 @@ class PlayStartLogic
// 游玩前余额校验(按 T4 惩罚最大值兜底):
// 门槛 = paidAmount(压注*1) + abs(T4最小real_ev)*ante
$t4List = DiceRewardConfig::getCachedByTier('T4');
$t4List = DiceRewardConfig::getCachedByTier('T4', $configDeptId);
$t4MinRealEv = null;
foreach ($t4List as $row) {
$ev = $row['real_ev'] ?? null;
@@ -155,7 +158,7 @@ class PlayStartLogic
: $configType0;
// 按档位 T1-T5 抽取后,从 DiceReward 表按当前方向取该档位数据,再按 weight 抽取一条得到 grid_number
$rewardInstance = DiceReward::getCachedInstance();
$rewardInstance = DiceReward::getCachedInstance($configDeptId);
$byTierDirection = $rewardInstance['by_tier_direction'] ?? [];
$maxTierRetry = 10;
$chosen = null;
@@ -216,7 +219,7 @@ class PlayStartLogic
$superWinCoin = 0.0;
$rollArray = $this->generateNonSuperWinRollArrayWithSum($rollNumber);
} else {
$bigWinConfig = DiceRewardConfig::getCachedByTierAndGridNumber('BIGWIN', $rollNumber);
$bigWinConfig = DiceRewardConfig::getCachedByTierAndGridNumber('BIGWIN', $rollNumber, $configDeptId);
$alwaysSuperWin = in_array($rollNumber, self::SUPER_WIN_ALWAYS_GRID_NUMBERS, true);
$doSuperWin = $alwaysSuperWin;
if (!$doSuperWin) {
@@ -639,9 +642,9 @@ class PlayStartLogic
* @param array|null $customTierWeights 自定义档位权重 ['T1'=>x, 'T2'=>x, ...],非空时忽略 config 的档位权重
* @return array 可直接用于 DicePlayRecordTest::create 的字段 + tier用于统计档位概率
*/
public function simulateOnePlay($config, int $direction, int $lotteryType = 0, int $ante = 1, ?array $customTierWeights = null): array
public function simulateOnePlay($config, int $direction, int $lotteryType = 0, int $ante = 1, ?array $customTierWeights = null, ?int $configDeptId = null): array
{
$rewardInstance = DiceReward::getCachedInstance();
$rewardInstance = DiceReward::getCachedInstance($configDeptId);
$byTierDirection = $rewardInstance['by_tier_direction'] ?? [];
$maxTierRetry = 10;
$chosen = null;
@@ -698,7 +701,7 @@ class PlayStartLogic
$superWinCoin = 0.0;
$rollArray = $this->generateNonSuperWinRollArrayWithSum($rollNumber);
} else {
$bigWinConfig = DiceRewardConfig::getCachedByTierAndGridNumber('BIGWIN', $rollNumber);
$bigWinConfig = DiceRewardConfig::getCachedByTierAndGridNumber('BIGWIN', $rollNumber, $configDeptId);
$alwaysSuperWin = in_array($rollNumber, self::SUPER_WIN_ALWAYS_GRID_NUMBERS, true);
$doSuperWin = $alwaysSuperWin;
if (!$doSuperWin) {

View File

@@ -5,7 +5,6 @@ namespace app\api\logic;
use app\dice\model\player\DicePlayer;
use app\api\cache\UserCache;
use plugin\saiadmin\app\model\system\SystemDept;
use plugin\saiadmin\app\model\system\SystemUser;
use plugin\saiadmin\exception\ApiException;
use Tinywan\Jwt\JwtToken;
@@ -44,46 +43,8 @@ class UserLogic
}
/**
* 根据 parent_id 向上遍历找到顶级部门parent_id=0
*/
private static function getTopDeptIdByParentId(int $deptId): ?int
{
$currentId = $deptId;
$visited = [];
while ($currentId > 0 && !isset($visited[$currentId])) {
$visited[$currentId] = true;
$dept = SystemDept::find($currentId);
if (!$dept) {
return null;
}
$parentId = (int) ($dept->parent_id ?? 0);
if ($parentId === 0) {
return $currentId;
}
$currentId = $parentId;
}
return $currentId > 0 ? $currentId : null;
}
/**
* 根据顶级部门 id递归获取其下所有部门 id含自身仅用 id 和 parent_id
*/
private static function getAllDeptIdsUnderTop(int $topId): array
{
$deptIds = [$topId];
$prevCount = 0;
while (count($deptIds) > $prevCount) {
$prevCount = count($deptIds);
$children = SystemDept::whereIn('parent_id', $deptIds)->column('id');
$deptIds = array_unique(array_merge($deptIds, array_map('intval', $children)));
}
return array_values($deptIds);
}
/**
* 根据 agent_id 获取当前管理员所在顶级部门下的所有管理员 ID 列表
* 使用 SystemDept 的 id 和 parent_id 字段遍历:先向上找顶级部门(parent_id=0),再向下收集所有子部门
* 用于 getGameUrl 接口判断 DicePlayer 是否属于该部门,同顶级部门下不重复创建玩家
* 根据 agent_id 获取同渠道下的所有管理员 ID 列表
* 用于 getGameUrl 接口判断 DicePlayer 是否属于该渠道,同渠道下不重复创建玩家
*
* @param string $agentId 代理标识sa_system_user.agent_id
* @return int[] 管理员 ID 列表,空数组表示未找到或无法解析
@@ -103,16 +64,8 @@ class UserLogic
return [(int) $admin->id];
}
$deptId = (int) $deptId;
$topId = self::getTopDeptIdByParentId($deptId);
if ($topId === null) {
return [(int) $admin->id];
}
$deptIds = self::getAllDeptIdsUnderTop($topId);
if (empty($deptIds)) {
$deptIds = [$deptId];
}
$adminIds = SystemUser::whereIn('dept_id', $deptIds)->column('id');
return array_map('intval', $adminIds);
$adminIds = SystemUser::where('dept_id', $deptId)->column('id');
return array_map('intval', $adminIds ?: [(int) $admin->id]);
}
/**
@@ -155,6 +108,10 @@ class UserLogic
$player->coin = $coin;
if ($adminId !== null && $adminId > 0) {
$player->admin_id = $adminId;
$adminUser = SystemUser::find($adminId);
if ($adminUser && !empty($adminUser->dept_id)) {
$player->dept_id = $adminUser->dept_id;
}
}
$player->save();
}