DB数据库文件

This commit is contained in:
2026-05-26 09:43:42 +08:00
parent e0b303c5d4
commit a4c8f623be
30 changed files with 1416 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
<?php
/**
* 审计各渠道游戏配置是否已从默认模板实例化
* 用法php server/db/audit_channel_config.php [--fix]
*/
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../support/bootstrap.php';
use app\dice\helper\AdminScopeHelper;
use app\dice\service\DiceChannelConfigService;
use plugin\saiadmin\app\model\system\SystemDept;
use support\think\Db;
$fix = in_array('--fix', $argv ?? [], true);
$templateId = AdminScopeHelper::DEFAULT_TEMPLATE_DEPT;
$tables = [
'dice_config',
'dice_ante_config',
'dice_lottery_pool_config',
'dice_reward_config',
'dice_game',
'dice_reward',
];
$templateCounts = [];
foreach ($tables as $table) {
$templateCounts[$table] = (int) Db::table($table)
->where(function ($q) use ($templateId) {
$q->where('dept_id', $templateId)->whereOr('dept_id', null);
})
->count();
}
$depts = SystemDept::where('id', '>', 0)->column('id');
echo "========== 渠道配置实例化审计 ==========\n";
echo "默认模板 dept_id={$templateId} 行数:\n";
foreach ($templateCounts as $table => $cnt) {
echo " {$table}: {$cnt}\n";
}
echo "\n";
$missing = [];
foreach ($depts as $deptId) {
$deptId = (int) $deptId;
if ($deptId <= 0) {
continue;
}
$issues = [];
foreach ($tables as $table) {
$expected = $templateCounts[$table];
if ($expected <= 0) {
continue;
}
$actual = (int) Db::table($table)->where('dept_id', $deptId)->count();
if ($actual < $expected) {
$issues[] = "{$table}: {$actual}/{$expected}";
}
}
if ($issues !== []) {
$missing[$deptId] = $issues;
echo "渠道 {$deptId} 不完整 → " . implode(', ', $issues) . "\n";
} else {
echo "渠道 {$deptId} OK\n";
}
}
if ($missing === []) {
echo "\n全部渠道配置已实例化。\n";
exit(0);
}
if (!$fix) {
echo "\n存在缺失。执行 php server/db/audit_channel_config.php --fix 可自动补齐。\n";
exit(1);
}
echo "\n开始补齐...\n";
$service = new DiceChannelConfigService();
$summary = $service->syncAllChannelsFromDefault();
foreach ($summary as $deptId => $info) {
$copied = implode(',', $info['copied_tables'] ?? []);
echo "渠道 {$deptId}: 新增表 [{$copied}] 补齐行 " . ($info['merged_rows'] ?? 0) . "\n";
}
echo "补齐完成,请重新运行审计确认。\n";

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
define('BASE_PATH', dirname(__DIR__));
require BASE_PATH . '/vendor/autoload.php';
\Dotenv\Dotenv::createUnsafeMutable(BASE_PATH)->load();
\Webman\Config::load(BASE_PATH . '/config', ['route', 'plugin']);
\Webman\ThinkOrm\ThinkOrm::start(null);
use support\think\Db;
$depts = Db::table('sa_system_dept')->column('id');
array_unshift($depts, 0);
foreach ($depts as $d) {
$c = Db::table('dice_reward_config')->where('dept_id', $d)->count();
$r = Db::table('dice_reward')->where('dept_id', $d)->count();
$nonBig = Db::table('dice_reward_config')->where('dept_id', $d)->where('tier', '<>', 'BIGWIN')->count();
echo "dept {$d}: reward_config={$c}, non_bigwin={$nonBig}, reward={$r}\n";
}

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
define('BASE_PATH', dirname(__DIR__));
require BASE_PATH . '/vendor/autoload.php';
\Dotenv\Dotenv::createUnsafeMutable(BASE_PATH)->load();
\Webman\ThinkOrm\ThinkOrm::start(null);
use support\think\Db;
foreach (['dice_config', 'dice_ante_config', 'dice_game', 'dice_lottery_pool_config'] as $table) {
echo "{$table} dept1123: " . Db::table($table)->where('dept_id', 1123)->count() . "\n";
}

View File

@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
define('BASE_PATH', dirname(__DIR__));
require BASE_PATH . '/vendor/autoload.php';
\Dotenv\Dotenv::createUnsafeMutable(BASE_PATH)->load();
\Webman\Config::load(BASE_PATH . '/config', ['route', 'plugin']);
\Webman\ThinkOrm\ThinkOrm::start(null);
use app\dice\helper\AdminScopeHelper;
use app\dice\logic\reward_config\DiceRewardConfigLogic;
use plugin\saiadmin\app\cache\UserInfoCache;
use support\think\Db;
$adminInfo = UserInfoCache::getUserInfo(123);
$logic = new DiceRewardConfigLogic();
$query = $logic->search([]);
AdminScopeHelper::applyConfigScope($query, $adminInfo, 0);
$_GET['limit'] = 200;
$_REQUEST['limit'] = 200;
$result = $logic->getList($query);
echo "limit=200 data count: " . count($result['data'] ?? []) . " total=" . ($result['total'] ?? 0) . "\n";
$bw = 0;
foreach ($result['data'] ?? [] as $row) {
if (($row['tier'] ?? '') === 'BIGWIN') {
$bw++;
echo "BIGWIN id={$row['id']} grid={$row['grid_number']}\n";
}
}
$query3 = $logic->search([]);
AdminScopeHelper::applyConfigScope($query3, $adminInfo, 0);
unset($_GET['limit'], $_REQUEST['limit']);
$result3 = $logic->getList($query3);
echo "default limit data count: " . count($result3['data'] ?? []) . "\n";
$bw3 = 0;
foreach ($result3['data'] ?? [] as $row) {
if (($row['tier'] ?? '') === 'BIGWIN') {
$bw3++;
}
}
echo "default limit BIGWIN: {$bw3}\n";
$_GET['saiType'] = 'all';
$_REQUEST['saiType'] = 'all';
$resultAll = $logic->getList($query);
echo "saiType=all count: " . (is_array($resultAll) ? count($resultAll) : 0) . "\n";
if (is_array($resultAll)) {
$bwAll = 0;
foreach ($resultAll as $row) {
if (($row['tier'] ?? '') === 'BIGWIN') {
$bwAll++;
}
}
echo "saiType=all BIGWIN: {$bwAll}\n";
}
echo "\nAll rows by id for dept 1123:\n";
$allRows = \support\think\Db::table('dice_reward_config')->where('dept_id', 1123)->order('id', 'asc')->select();
foreach ($allRows as $r) {
echo "id={$r['id']} tier={$r['tier']} grid={$r['grid_number']}\n";
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
define('BASE_PATH', dirname(__DIR__));
require BASE_PATH . '/vendor/autoload.php';
\Dotenv\Dotenv::createUnsafeMutable(BASE_PATH)->load();
\Webman\Config::load(BASE_PATH . '/config', ['route', 'plugin']);
\Webman\ThinkOrm\ThinkOrm::start(null);
use app\dice\helper\AdminScopeHelper;
use app\dice\logic\reward_config\DiceRewardConfigLogic;
use plugin\saiadmin\app\cache\UserInfoCache;
$adminInfo = UserInfoCache::getUserInfo(123);
$logic = new DiceRewardConfigLogic();
// paginated default
$query = $logic->search([]);
AdminScopeHelper::applyConfigScope($query, $adminInfo, 0);
$page = $logic->getList($query);
echo "default paginate count=" . count($page['data'] ?? []) . " total=" . ($page['total'] ?? 0) . "\n";
// saiType all
$_GET['saiType'] = 'all';
$_REQUEST['saiType'] = 'all';
$query2 = $logic->search([]);
AdminScopeHelper::applyConfigScope($query2, $adminInfo, 0);
$all = $logic->getList($query2);
echo "saiType=all is_array=" . (is_array($all) ? 'yes' : 'no') . " count=" . (is_array($all) ? count($all) : 0) . "\n";
$nonBigwin = 0;
if (is_array($all)) {
foreach ($all as $row) {
if (($row['tier'] ?? '') !== 'BIGWIN') {
$nonBigwin++;
}
}
}
echo "non-BIGWIN rows for index tab: {$nonBigwin}\n";
// simulate dept_id=1123 explicit
unset($_GET['saiType'], $_REQUEST['saiType']);
$_GET['saiType'] = 'all';
$query3 = $logic->search([]);
AdminScopeHelper::applyConfigScope($query3, $adminInfo, 1123);
$all3 = $logic->getList($query3);
echo "dept_id=1123 saiType=all count=" . (is_array($all3) ? count($all3) : 0) . "\n";

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
define('BASE_PATH', dirname(__DIR__));
require BASE_PATH . '/vendor/autoload.php';
\Dotenv\Dotenv::createUnsafeMutable(BASE_PATH)->load();
\Webman\Config::load(BASE_PATH . '/config', ['route', 'plugin']);
\Webman\ThinkOrm\ThinkOrm::start(null);
use app\dice\helper\AdminScopeHelper;
use app\dice\logic\reward_config\DiceRewardConfigLogic;
use app\dice\model\reward_config\DiceRewardConfig;
use plugin\saiadmin\app\cache\UserInfoCache;
use support\think\Db;
$adminInfo = UserInfoCache::getUserInfo(123);
echo "Raw DB count dept 1123: " . Db::table('dice_reward_config')->where('dept_id', 1123)->whereNull('delete_time')->count() . "\n";
$logic = new DiceRewardConfigLogic();
$query = $logic->search([]);
AdminScopeHelper::applyConfigScope($query, $adminInfo, 1123);
$sql = $query->fetchSql(true)->select();
echo "SQL: {$sql}\n";
$rows = $query->fetchSql(false)->select()->toArray();
echo "Model select count: " . count($rows) . "\n";
$model = new DiceRewardConfig();
$q2 = $model->where('dept_id', 1123)->order('id', 'asc');
$rows2 = $q2->select()->toArray();
echo "Direct model dept 1123: " . count($rows2) . "\n";
$tierCounts = Db::table('dice_reward_config')->where('dept_id', 1123)->group('tier')->column('count(*)', 'tier');
echo "Tier counts: " . json_encode($tierCounts, JSON_UNESCAPED_UNICODE) . "\n";
$idCounts = Db::query('SELECT id, COUNT(*) as c FROM dice_reward_config WHERE dept_id=1123 GROUP BY id HAVING c>1');
echo "Duplicate business ids in dept 1123: " . count($idCounts) . "\n";
if ($idCounts) {
print_r($idCounts);
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
define('BASE_PATH', dirname(__DIR__));
require BASE_PATH . '/vendor/autoload.php';
\Dotenv\Dotenv::createUnsafeMutable(BASE_PATH)->load();
\Webman\Config::load(BASE_PATH . '/config', ['route', 'plugin']);
\Webman\ThinkOrm\ThinkOrm::start(null);
use app\dice\helper\AdminScopeHelper;
use plugin\saiadmin\app\cache\UserInfoCache;
use plugin\saiadmin\app\logic\system\SystemUserLogic;
use support\think\Db;
$uid = 123;
$user = Db::table('sa_system_user')->where('id', $uid)->find();
echo "DB user: " . json_encode($user, JSON_UNESCAPED_UNICODE) . "\n";
$logic = new SystemUserLogic();
$info = $logic->getUser($uid);
echo "getUser deptList: " . json_encode($info['deptList'] ?? null, JSON_UNESCAPED_UNICODE) . "\n";
echo "getUser dept_id: " . ($info['dept_id'] ?? 'null') . "\n";
$cached = UserInfoCache::getUserInfo($uid);
echo "cache deptList: " . json_encode($cached['deptList'] ?? null, JSON_UNESCAPED_UNICODE) . "\n";
echo "getDeptId: " . var_export(AdminScopeHelper::getDeptId($cached), true) . "\n";
echo "resolveConfigDeptId(null): " . AdminScopeHelper::resolveConfigDeptId($cached, null) . "\n";
echo "resolveConfigDeptId(0): " . AdminScopeHelper::resolveConfigDeptId($cached, 0) . "\n";
$deptId = 1123;
$all = Db::table('dice_reward_config')->where('dept_id', $deptId)->count();
$bigwin = Db::table('dice_reward_config')->where('dept_id', $deptId)->where('tier', 'BIGWIN')->count();
$reward = Db::table('dice_reward')->where('dept_id', $deptId)->count();
echo "dept {$deptId}: reward_config={$all}, BIGWIN={$bigwin}, dice_reward={$reward}\n";
$sample = Db::table('dice_reward_config')->where('dept_id', $deptId)->limit(3)->select();
echo "sample reward_config: " . json_encode($sample, JSON_UNESCAPED_UNICODE) . "\n";

View File

@@ -0,0 +1,26 @@
-- 渠道扁平化:将子渠道用户归并到顶级渠道,删除子渠道,更新表注释
-- 执行前请备份数据库
-- 1. 表及字段注释改为「渠道」
ALTER TABLE `sa_system_dept` COMMENT = '渠道表';
ALTER TABLE `sa_system_dept`
MODIFY COLUMN `parent_id` bigint(20) UNSIGNED NULL DEFAULT 0 COMMENT '父级ID扁平渠道固定为0',
MODIFY COLUMN `name` varchar(64) NOT NULL COMMENT '渠道名称',
MODIFY COLUMN `code` varchar(64) NULL DEFAULT NULL COMMENT '渠道编码',
MODIFY COLUMN `leader_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '渠道负责人ID';
-- 2. 菜单名称(按实际 id 调整id=5 为渠道管理菜单)
UPDATE `sa_system_menu` SET `name` = '渠道管理' WHERE `id` = 5 OR `name` LIKE '%渠道(部门)%' OR `name` = '部门管理';
-- 3. 将子渠道下的用户 dept_id 提升到顶级渠道(需配合 run_dept_flatten_channels.php 处理多级)
-- 以下为单级子渠道快速迁移parent_id != 0 的直接挂到父级)
UPDATE `sa_system_user` u
INNER JOIN `sa_system_dept` d ON u.dept_id = d.id AND d.parent_id > 0
INNER JOIN `sa_system_dept` p ON d.parent_id = p.id
SET u.dept_id = p.id;
-- 4. 删除所有子渠道parent_id > 0
DELETE FROM `sa_system_dept` WHERE `parent_id` > 0;
-- 5. 剩余渠道统一为顶级
UPDATE `sa_system_dept` SET `parent_id` = 0, `level` = '0';

View File

@@ -0,0 +1,10 @@
-- dice_player同一渠道内用户名唯一dept_id + username— 数据库根本约束
-- 执行前请备份;若存在重复数据需先清理后再执行
-- 推荐php db/run_dice_player_dept_username_unique.php
-- 移除仅按 username 的普通索引(若不存在可忽略报错)
-- ALTER TABLE `dice_player` DROP INDEX `idx_dice_player_username`;
-- 同一渠道下用户名唯一UNIQUE 为数据库层最终约束)
ALTER TABLE `dice_player`
ADD UNIQUE INDEX `uk_dice_player_dept_username` (`dept_id`, `username`);

View File

@@ -0,0 +1,2 @@
-- 已废弃:请使用 dice_player_dept_username_unique.sql同一渠道 dept_id + username 唯一)
-- 历史脚本保留说明,勿再单独执行本文件

View File

@@ -0,0 +1,92 @@
-- 大富翁游戏相关表增加 dept_id关联 sa_system_dept渠道表
ALTER TABLE `dice_ante_config`
ADD COLUMN `dept_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '所属渠道ID' AFTER `id`,
ADD INDEX `idx_dept_id` (`dept_id`);
ALTER TABLE `dice_config`
ADD COLUMN `dept_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '所属渠道ID' AFTER `id`,
ADD INDEX `idx_dept_id` (`dept_id`);
ALTER TABLE `dice_game`
ADD COLUMN `dept_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '所属渠道ID' AFTER `id`,
ADD INDEX `idx_dept_id` (`dept_id`);
ALTER TABLE `dice_lottery_config`
ADD COLUMN `dept_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '所属渠道ID' AFTER `id`,
ADD INDEX `idx_dept_id` (`dept_id`);
ALTER TABLE `dice_lottery_poll_record`
ADD COLUMN `dept_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '所属渠道ID' AFTER `id`,
ADD INDEX `idx_dept_id` (`dept_id`);
ALTER TABLE `dice_lottery_pool_config`
ADD COLUMN `dept_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '所属渠道ID' AFTER `id`,
ADD INDEX `idx_dept_id` (`dept_id`);
ALTER TABLE `dice_play_record`
ADD COLUMN `dept_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '所属渠道ID' AFTER `id`,
ADD INDEX `idx_dept_id` (`dept_id`);
ALTER TABLE `dice_play_record_test`
ADD COLUMN `dept_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '所属渠道ID' AFTER `id`,
ADD INDEX `idx_dept_id` (`dept_id`);
ALTER TABLE `dice_player`
ADD COLUMN `dept_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '所属渠道ID' AFTER `id`,
ADD INDEX `idx_dept_id` (`dept_id`);
-- 同一渠道内用户名唯一(根本约束,新库初始化时执行;已有库请用 dice_player_dept_username_unique.sql
ALTER TABLE `dice_player`
ADD UNIQUE INDEX `uk_dice_player_dept_username` (`dept_id`, `username`);
ALTER TABLE `dice_player_ticket_record`
ADD COLUMN `dept_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '所属渠道ID' AFTER `id`,
ADD INDEX `idx_dept_id` (`dept_id`);
ALTER TABLE `dice_player_wallet_record`
ADD COLUMN `dept_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '所属渠道ID' AFTER `id`,
ADD INDEX `idx_dept_id` (`dept_id`);
ALTER TABLE `dice_reward`
ADD COLUMN `dept_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '所属渠道ID' AFTER `id`,
ADD INDEX `idx_dept_id` (`dept_id`);
ALTER TABLE `dice_reward_config`
ADD COLUMN `dept_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '所属渠道ID' AFTER `id`,
ADD INDEX `idx_dept_id` (`dept_id`);
ALTER TABLE `dice_reward_config_record`
ADD COLUMN `dept_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '所属渠道ID' AFTER `id`,
ADD INDEX `idx_dept_id` (`dept_id`);
-- 从管理员归属回填玩家 dept_id
UPDATE `dice_player` p
INNER JOIN `sa_system_user` u ON p.admin_id = u.id
SET p.dept_id = u.dept_id
WHERE p.dept_id IS NULL AND u.dept_id IS NOT NULL AND u.dept_id > 0;
UPDATE `dice_play_record` r
INNER JOIN `dice_player` p ON r.player_id = p.id
SET r.dept_id = p.dept_id
WHERE r.dept_id IS NULL AND p.dept_id IS NOT NULL;
UPDATE `dice_play_record_test` r
INNER JOIN `dice_player` p ON r.player_id = p.id
SET r.dept_id = p.dept_id
WHERE r.dept_id IS NULL AND p.dept_id IS NOT NULL;
UPDATE `dice_player_ticket_record` r
INNER JOIN `dice_player` p ON r.player_id = p.id
SET r.dept_id = p.dept_id
WHERE r.dept_id IS NULL AND p.dept_id IS NOT NULL;
UPDATE `dice_player_wallet_record` r
INNER JOIN `dice_player` p ON r.player_id = p.id
SET r.dept_id = p.dept_id
WHERE r.dept_id IS NULL AND p.dept_id IS NOT NULL;
UPDATE `dice_reward_config_record` r
INNER JOIN `sa_system_user` u ON r.admin_id = u.id
SET r.dept_id = u.dept_id
WHERE r.dept_id IS NULL AND u.dept_id IS NOT NULL AND u.dept_id > 0;

View File

@@ -0,0 +1,93 @@
<?php
/**
* 排查 dice_player 删除报错:
* - 表结构
* - 外键引用情况
* - 实际 destroy 流程(不真的删,仅 dry-run 抓异常)
*
* 用法php server/db/inspect_player_destroy.php [<id1>,<id2>,...]
*/
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../support/bootstrap.php';
use support\think\Db;
$config = config('database');
$default = $config['default'];
$conn = $config['connections'][$default];
$dbName = $conn['database'] ?? '';
echo "[DB] {$default} -> {$dbName}\n\n";
echo "--- dice_player columns ---\n";
$cols = Db::query("SHOW FULL COLUMNS FROM `dice_player`");
foreach ($cols as $c) {
echo str_pad((string)$c['Field'], 26) . ' | ' . str_pad((string)$c['Type'], 24) . ' | NULL=' . $c['Null'] . ' | Key=' . $c['Key'] . "\n";
}
echo "\n";
echo "--- referenced by foreign keys (other tables -> dice_player) ---\n";
$fks = Db::query("SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = ? AND REFERENCED_TABLE_NAME = 'dice_player'", [$dbName]);
if (empty($fks)) {
echo " (none)\n";
} else {
foreach ($fks as $f) {
echo " {$f['TABLE_NAME']}.{$f['COLUMN_NAME']} -> {$f['REFERENCED_TABLE_NAME']}.{$f['REFERENCED_COLUMN_NAME']} [{$f['CONSTRAINT_NAME']}]\n";
}
}
echo "\n";
echo "--- top 3 dice_player rows ---\n";
$rows = Db::table('dice_player')->limit(3)->select()->toArray();
foreach ($rows as $r) {
echo "id={$r['id']} dept_id={$r['dept_id']} username={$r['username']} delete_time=" . ($r['delete_time'] ?? 'null') . "\n";
}
echo "\n";
$idsArg = $argv[1] ?? '';
if ($idsArg === '') {
echo "(no ids passed, skip dry-run delete)\n";
return;
}
$ids = array_filter(array_map('intval', explode(',', $idsArg)));
if (empty($ids)) {
echo "(invalid ids)\n";
return;
}
echo "--- dry-run destroy ids: " . implode(',', $ids) . " ---\n";
$beforeAll = Db::query('SELECT id, delete_time FROM dice_player WHERE id IN (' . implode(',', array_fill(0, count($ids), '?')) . ')', $ids);
echo "before (raw): " . count($beforeAll) . " rows\n";
$placeholders = implode(',', array_fill(0, count($ids), '?'));
// Case A: static destroy
try {
Db::startTrans();
$result = \app\dice\model\player\DicePlayer::destroy($ids, true);
$afterAny = Db::query("SELECT COUNT(*) AS c FROM dice_player WHERE id IN ({$placeholders})", $ids);
echo "[static destroy] returned: " . var_export($result, true) . ", remaining raw rows: " . ($afterAny[0]['c'] ?? 'n/a') . "\n";
Db::rollback();
} catch (\Throwable $e) {
Db::rollback();
echo "EXCEPTION (static destroy): " . get_class($e) . ": " . $e->getMessage() . "\n";
}
// Case B: instance ->delete()
try {
Db::startTrans();
$instance = \app\dice\model\player\DicePlayer::find($ids[0]);
if ($instance) {
$r = $instance->delete();
$afterAny = Db::query("SELECT COUNT(*) AS c FROM dice_player WHERE id = ?", [$ids[0]]);
echo "[instance delete] returned: " . var_export($r, true) . ", remaining raw rows for id={$ids[0]}: " . ($afterAny[0]['c'] ?? 'n/a') . "\n";
} else {
echo "[instance delete] no instance found for id={$ids[0]}\n";
}
Db::rollback();
} catch (\Throwable $e) {
Db::rollback();
echo "EXCEPTION (instance delete): " . get_class($e) . ": " . $e->getMessage() . "\n";
}
echo "(both rolled back, no actual delete)\n";

View File

@@ -0,0 +1,35 @@
-- 从所有角色中移除以下运维菜单及其按钮权限
-- /safeguard/dict
-- /safeguard/attachment
-- /safeguard/database
-- /safeguard/server
-- /safeguard/cache
-- /safeguard/email-log
--
-- 推荐执行php db/run_remove_safeguard_ops_role_menus.php
-- 该脚本会按 route 动态匹配菜单及其子权限,并清理菜单缓存
-- 主菜单
DELETE rm FROM `sa_system_role_menu` rm
INNER JOIN `sa_system_menu` m ON rm.menu_id = m.id
WHERE m.component IN (
'/safeguard/dict',
'/safeguard/attachment',
'/safeguard/database',
'/safeguard/server',
'/safeguard/cache',
'/safeguard/email-log'
);
-- 子按钮权限
DELETE rm FROM `sa_system_role_menu` rm
INNER JOIN `sa_system_menu` child ON rm.menu_id = child.id
INNER JOIN `sa_system_menu` parent ON child.parent_id = parent.id
WHERE parent.component IN (
'/safeguard/dict',
'/safeguard/attachment',
'/safeguard/database',
'/safeguard/server',
'/safeguard/cache',
'/safeguard/email-log'
);

View File

@@ -0,0 +1,14 @@
-- 移除岗位Post相关数据表与菜单
-- 执行前请备份数据库
-- 删除岗位子菜单权限id: 41-47
DELETE FROM `sa_system_role_menu` WHERE `menu_id` IN (41, 42, 43, 44, 45, 46, 47);
DELETE FROM `sa_system_menu` WHERE `id` IN (41, 42, 43, 44, 45, 46, 47);
-- 删除岗位管理主菜单id: 7
DELETE FROM `sa_system_role_menu` WHERE `menu_id` = 7;
DELETE FROM `sa_system_menu` WHERE `id` = 7;
-- 删除用户岗位关联表与岗位信息表
DROP TABLE IF EXISTS `sa_system_user_post`;
DROP TABLE IF EXISTS `sa_system_post`;

191
server/db/run_all_init.php Normal file
View File

@@ -0,0 +1,191 @@
<?php
/**
* 一键执行渠道与游戏配置初始化(需已备份数据库)
* 用法:在 server 目录执行 php db/run_all_init.php
*/
declare(strict_types=1);
define('BASE_PATH', dirname(__DIR__));
require_once BASE_PATH . '/vendor/autoload.php';
if (class_exists(\Dotenv\Dotenv::class) && is_file(BASE_PATH . '/.env')) {
if (method_exists(\Dotenv\Dotenv::class, 'createUnsafeMutable')) {
\Dotenv\Dotenv::createUnsafeMutable(BASE_PATH)->load();
} else {
\Dotenv\Dotenv::createMutable(BASE_PATH)->load();
}
}
// 加载配置(排除 route.php避免 CLI 报错)
\Webman\Config::load(BASE_PATH . '/config', ['route', 'plugin']);
\Webman\ThinkOrm\ThinkOrm::start(null);
use app\dice\service\DiceChannelConfigService;
use plugin\saiadmin\app\model\system\SystemDept;
use plugin\saiadmin\app\model\system\SystemUser;
use support\think\Db;
function cliPdo(): PDO
{
$host = getenv('DB_HOST') ?: '127.0.0.1';
$port = getenv('DB_PORT') ?: '3306';
$db = getenv('DB_NAME') ?: '';
$user = getenv('DB_USER') ?: '';
$pass = getenv('DB_PASSWORD') ?: '';
$dsn = "mysql:host={$host};port={$port};dbname={$db};charset=utf8mb4";
return new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
}
function runSqlFile(PDO $pdo, string $path, string $label, bool $alterOnly = false): void
{
echo "\n=== {$label} ===\n";
if (!is_file($path)) {
echo "跳过:文件不存在\n";
return;
}
$sql = file_get_contents($path);
$sql = preg_replace('/--.*$/m', '', $sql);
$parts = array_filter(array_map('trim', explode(';', $sql)));
$ok = 0;
$skip = 0;
foreach ($parts as $statement) {
if ($statement === '') {
continue;
}
if ($alterOnly && stripos($statement, 'ALTER TABLE') !== 0) {
continue;
}
try {
$pdo->exec($statement);
$ok++;
} catch (PDOException $e) {
$msg = $e->getMessage();
$isAlter = stripos($statement, 'ALTER TABLE') === 0;
if ($isAlter && (stripos($msg, 'Duplicate column') !== false
|| stripos($msg, 'Duplicate key name') !== false)) {
$skip++;
} elseif (!$isAlter) {
echo " [警告] " . substr($msg, 0, 120) . "\n";
$skip++;
} else {
echo " [错误] {$msg}\n";
throw $e;
}
}
}
echo "完成:成功 {$ok}" . ($skip > 0 ? ",跳过 {$skip} 条(字段已存在)" : '') . "\n";
}
function getRootDeptId(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;
}
echo "========== 大富翁渠道/配置初始化 ==========\n";
echo '数据库: ' . (getenv('DB_NAME') ?: '') . '@' . (getenv('DB_HOST') ?: '') . "\n";
$pdo = cliPdo();
runSqlFile($pdo, __DIR__ . '/dice_tables_add_dept_id.sql', '1. dice 表增加 dept_id', true);
runSqlFile($pdo, __DIR__ . '/dept_flatten_channels.sql', '2. 渠道扁平化 SQL');
echo "\n=== 3. 渠道扁平化 PHP多级用户归并 + 删除子渠道) ===\n";
Db::transaction(function () {
$users = SystemUser::where('dept_id', '>', 0)->select();
$moved = 0;
foreach ($users as $user) {
$deptId = (int) $user->dept_id;
$rootId = getRootDeptId($deptId);
if ($rootId !== null && $rootId !== $deptId) {
SystemUser::where('id', $user->id)->update(['dept_id' => $rootId]);
echo " 用户 {$user->id}: dept {$deptId} -> {$rootId}\n";
$moved++;
}
}
$childIds = SystemDept::where('parent_id', '>', 0)->column('id');
if (!empty($childIds)) {
SystemDept::destroy($childIds);
echo ' 已删除子渠道: ' . implode(',', $childIds) . "\n";
} else {
echo " 无子渠道需删除\n";
}
SystemDept::where('id', '>', 0)->update(['parent_id' => 0, 'level' => '0']);
echo " 用户归并 {$moved} 人,渠道扁平化完成\n";
});
$service = new DiceChannelConfigService();
echo "\n=== 3.5 配置表复合键与默认模板 dept_id=0 ===\n";
$service->ensureConfigCompositeKeys();
echo " dice_config / dice_reward_config: ok\n";
echo "\n=== 3.6 彩金池配置按渠道唯一dept_id + name ===\n";
try {
$indexes = Db::query("SHOW INDEX FROM `dice_lottery_pool_config` WHERE Key_name = 'dice_lottery_poll_config_unique'");
if (!empty($indexes)) {
Db::execute('ALTER TABLE `dice_lottery_pool_config` DROP INDEX `dice_lottery_poll_config_unique`');
echo " 已移除 name 全局唯一索引\n";
}
$uk = Db::query("SHOW INDEX FROM `dice_lottery_pool_config` WHERE Key_name = 'uk_dept_name'");
if (empty($uk)) {
Db::execute('ALTER TABLE `dice_lottery_pool_config` ADD UNIQUE KEY `uk_dept_name` (`dept_id`, `name`)');
echo " 已添加 uk_dept_name(dept_id, name)\n";
} else {
echo " uk_dept_name 已存在\n";
}
} catch (\Throwable $e) {
echo " 跳过: {$e->getMessage()}\n";
}
echo "\n=== 4. 将现有配置设为默认模板dept_id=0 ===\n";
$tables = [
'dice_config',
'dice_ante_config',
'dice_lottery_pool_config',
'dice_reward_config',
'dice_reward',
'dice_game',
];
foreach ($tables as $table) {
try {
Db::table($table)->update(['dept_id' => 0]);
echo " {$table}: ok\n";
} catch (\Throwable $e) {
echo " {$table}: 跳过 ({$e->getMessage()})\n";
}
}
echo "\n=== 5. 为所有渠道从默认模板复制配置 ===\n";
$summary = $service->syncAllChannelsFromDefault();
foreach ($summary as $deptId => $info) {
$copied = implode(',', $info['copied'] ?? []) ?: '无';
$skipped = implode(',', $info['skipped'] ?? []) ?: '无';
echo " 渠道 {$deptId}: 复制 [{$copied}],跳过 [{$skipped}]\n";
}
echo "\n=== 6. 修复无效渠道并回填 dept_id ===\n";
echo " 无效渠道归并: ";
print_r($service->repairOrphanDeptReferences());
echo " 回填: ";
print_r($service->backfillDataDeptId());
$deptCount = SystemDept::count();
echo "\n当前顶级渠道数: {$deptCount}\n";
echo "========== 全部初始化完成 ==========\n";

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
define('BASE_PATH', dirname(__DIR__));
require_once BASE_PATH . '/vendor/autoload.php';
\Dotenv\Dotenv::createUnsafeMutable(BASE_PATH)->load();
\Webman\Config::load(BASE_PATH . '/config', ['route', 'plugin']);
\Webman\ThinkOrm\ThinkOrm::start(null);
use app\dice\service\DiceChannelConfigService;
$service = new DiceChannelConfigService();
echo "修复无效渠道引用...\n";
print_r($service->repairOrphanDeptReferences());
echo "\n回填 dept_id...\n";
print_r($service->backfillDataDeptId());
echo "完成。\n";

View File

@@ -0,0 +1,46 @@
<?php
/**
* 渠道配置初始化:默认模板 + 为已有渠道补齐配置 + 业务数据 dept_id 回填
* 用法:在 server 目录执行 php db/run_channel_config_init.php
*/
require_once __DIR__ . '/../vendor/autoload.php';
$bootstrap = __DIR__ . '/../support/bootstrap.php';
if (is_file($bootstrap)) {
require_once $bootstrap;
}
use app\dice\service\DiceChannelConfigService;
use support\think\Db;
$service = new DiceChannelConfigService();
echo "1. 将 dept_id=0 的配置归为默认模板dept_id 置空)...\n";
echo " 若需将全部现有配置作为模板,请确认后手动执行 UPDATE ... SET dept_id=NULL\n";
foreach (
[
'dice_config',
'dice_ante_config',
'dice_lottery_pool_config',
'dice_reward_config',
'dice_reward',
'dice_game',
] as $table
) {
try {
Db::table($table)->where('dept_id', 0)->update(['dept_id' => null]);
echo " {$table}: ok\n";
} catch (\Throwable $e) {
echo " {$table}: 跳过 ({$e->getMessage()})\n";
}
}
echo "2. 为所有渠道补齐默认配置...\n";
$summary = $service->syncAllChannelsFromDefault();
print_r($summary);
echo "3. 回填玩家及记录 dept_id...\n";
$stats = $service->backfillDataDeptId();
print_r($stats);
echo "完成。\n";

View File

@@ -0,0 +1,55 @@
<?php
/**
* 渠道扁平化迁移脚本(多级子渠道用户归并到顶级渠道后删除子渠道)
* 用法:在 server 目录下执行 php db/run_dept_flatten_channels.php
*/
require_once __DIR__ . '/../vendor/autoload.php';
use plugin\saiadmin\app\model\system\SystemDept;
use plugin\saiadmin\app\model\system\SystemUser;
use support\think\Db;
$bootstrap = __DIR__ . '/../support/bootstrap.php';
if (is_file($bootstrap)) {
require_once $bootstrap;
}
function getRootDeptId(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;
}
Db::transaction(function () {
$users = SystemUser::where('dept_id', '>', 0)->select();
foreach ($users as $user) {
$deptId = (int) $user->dept_id;
$rootId = getRootDeptId($deptId);
if ($rootId !== null && $rootId !== $deptId) {
SystemUser::where('id', $user->id)->update(['dept_id' => $rootId]);
echo "用户 {$user->id} dept_id {$deptId} -> {$rootId}\n";
}
}
$childIds = SystemDept::where('parent_id', '>', 0)->column('id');
if (!empty($childIds)) {
SystemDept::destroy($childIds);
echo '已删除子渠道: ' . implode(',', $childIds) . "\n";
}
SystemDept::where('id', '>', 0)->update(['parent_id' => 0, 'level' => '0']);
echo "渠道扁平化完成\n";
});

View File

@@ -0,0 +1,53 @@
<?php
/**
* 为 dice_player 增加 (dept_id, username) 唯一索引
* 用法:在 server 目录执行 php db/run_dice_player_dept_username_unique.php
*/
declare(strict_types=1);
define('BASE_PATH', dirname(__DIR__));
require_once BASE_PATH . '/vendor/autoload.php';
if (class_exists(\Dotenv\Dotenv::class) && is_file(BASE_PATH . '/.env')) {
if (method_exists(\Dotenv\Dotenv::class, 'createUnsafeMutable')) {
\Dotenv\Dotenv::createUnsafeMutable(BASE_PATH)->load();
} else {
\Dotenv\Dotenv::createMutable(BASE_PATH)->load();
}
}
\Webman\Config::load(BASE_PATH . '/config', ['route', 'plugin']);
\Webman\ThinkOrm\ThinkOrm::start(null);
use support\think\Db;
echo "检查 (dept_id, username) 重复...\n";
$dupes = Db::query(
'SELECT dept_id, username, COUNT(*) AS c FROM dice_player
WHERE username IS NOT NULL AND username <> \'\'
GROUP BY dept_id, username HAVING c > 1 LIMIT 20'
);
if (!empty($dupes)) {
echo "存在重复记录,请先处理后再执行:\n";
print_r($dupes);
exit(1);
}
$indexes = Db::query("SHOW INDEX FROM `dice_player` WHERE Key_name = 'idx_dice_player_username'");
if (!empty($indexes)) {
Db::execute('ALTER TABLE `dice_player` DROP INDEX `idx_dice_player_username`');
echo "已删除 idx_dice_player_username\n";
}
$uk = Db::query("SHOW INDEX FROM `dice_player` WHERE Key_name = 'uk_dice_player_dept_username'");
if (empty($uk)) {
Db::execute(
'ALTER TABLE `dice_player` ADD UNIQUE INDEX `uk_dice_player_dept_username` (`dept_id`, `username`)'
);
echo "已创建 uk_dice_player_dept_username\n";
} else {
echo "uk_dice_player_dept_username 已存在,跳过\n";
}
echo "完成\n";

View File

@@ -0,0 +1,33 @@
<?php
/**
* 为已有渠道补齐缺失的默认配置(不删除已有数据)
* 用法php db/run_fill_missing_channel_config.php
*/
declare(strict_types=1);
define('BASE_PATH', dirname(__DIR__));
require_once BASE_PATH . '/vendor/autoload.php';
if (class_exists(\Dotenv\Dotenv::class) && is_file(BASE_PATH . '/.env')) {
\Dotenv\Dotenv::createUnsafeMutable(BASE_PATH)->load();
}
\Webman\Config::load(BASE_PATH . '/config', ['route', 'plugin']);
\Webman\ThinkOrm\ThinkOrm::start(null);
use app\dice\service\DiceChannelConfigService;
use plugin\saiadmin\app\model\system\SystemDept;
echo "========== 补齐渠道缺失配置 ==========\n";
$service = new DiceChannelConfigService();
$service->ensureConfigCompositeKeys();
$summary = $service->syncAllChannelsFromDefault();
foreach ($summary as $deptId => $info) {
$copied = implode(',', $info['copied'] ?? []) ?: '无';
$merged = empty($info['merged']) ? '无' : json_encode($info['merged'], JSON_UNESCAPED_UNICODE);
$skipped = implode(',', $info['skipped'] ?? []) ?: '无';
echo "渠道 {$deptId}: 新增表 [{$copied}] 补齐行 {$merged} 跳过 [{$skipped}]\n";
}
echo "\n渠道数: " . SystemDept::count() . "\n完成。\n";

View File

@@ -0,0 +1,100 @@
<?php
/**
* 从所有角色中移除以下运维菜单及其按钮权限:
* /safeguard/dict
* /safeguard/attachment
* /safeguard/database
* /safeguard/server
* /safeguard/cache
* /safeguard/email-log
*
* 用法(在 server 目录): php db/run_remove_safeguard_ops_role_menus.php
*/
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../support/bootstrap.php';
use plugin\saiadmin\app\cache\UserMenuCache;
use support\think\Db;
$targetRoutes = [
'/safeguard/dict',
'/safeguard/attachment',
'/safeguard/database',
'/safeguard/server',
'/safeguard/cache',
'/safeguard/email-log',
];
/**
* @param int[] $parentIds
* @return int[]
*/
function collectChildMenuIds(array $parentIds): array
{
if ($parentIds === []) {
return [];
}
$childIds = Db::name('sa_system_menu')
->whereIn('parent_id', $parentIds)
->column('id');
if ($childIds === []) {
return [];
}
$childIds = array_map('intval', $childIds);
$deeper = collectChildMenuIds($childIds);
return array_values(array_unique(array_merge($childIds, $deeper)));
}
echo "=== remove safeguard ops menus from all roles ===\n";
$rootMenuIds = Db::name('sa_system_menu')
->whereIn('component', $targetRoutes)
->column('id');
$rootMenuIds = array_map('intval', $rootMenuIds ?: []);
$childMenuIds = collectChildMenuIds($rootMenuIds);
$menuIds = array_values(array_unique(array_merge($rootMenuIds, $childMenuIds)));
if ($menuIds === []) {
echo "WARN: no menu matched target routes\n";
exit(0);
}
$menuRows = Db::name('sa_system_menu')
->whereIn('id', $menuIds)
->field('id, parent_id, name, component, slug, type')
->order('id', 'asc')
->select()
->toArray();
echo "Matched menus (" . count($menuRows) . "):\n";
foreach ($menuRows as $row) {
echo sprintf(
" - id=%d parent=%d type=%s component=%s slug=%s name=%s\n",
(int) ($row['id'] ?? 0),
(int) ($row['parent_id'] ?? 0),
(string) ($row['type'] ?? ''),
(string) ($row['component'] ?? ''),
(string) ($row['slug'] ?? ''),
(string) ($row['name'] ?? '')
);
}
$deleted = Db::name('sa_system_role_menu')->whereIn('menu_id', $menuIds)->delete();
echo "Deleted role-menu rows: {$deleted}\n";
$roleCount = Db::name('sa_system_role_menu')
->whereIn('menu_id', $menuIds)
->count();
echo "Remaining role-menu rows for these menus: {$roleCount}\n";
UserMenuCache::clearMenuCache();
echo "Cleared menu cache\n";
echo "Done.\n";

View File

@@ -0,0 +1,74 @@
<?php
/**
* 执行 sa_system_role 渠道隔离迁移,并为已有渠道复制默认角色、映射用户角色
*
* 用法(在 server 目录): php db/run_sa_system_role_dept_id.php
*/
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../support/bootstrap.php';
use plugin\saiadmin\app\service\SystemRoleChannelService;
use support\think\Db;
function tableHasColumn(string $table, string $column): bool
{
try {
$fields = Db::getFields($table);
return isset($fields[$column]);
} catch (\Throwable $e) {
return false;
}
}
function indexExists(string $table, string $indexName): bool
{
$rows = Db::query("SHOW INDEX FROM `{$table}` WHERE Key_name = ?", [$indexName]);
return !empty($rows);
}
echo "=== sa_system_role dept_id migration ===\n";
if (!tableHasColumn('sa_system_role', 'dept_id')) {
Db::execute(
"ALTER TABLE `sa_system_role`
ADD COLUMN `dept_id` bigint(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT '所属渠道ID0=默认模板' AFTER `id`"
);
echo "OK: ADD COLUMN dept_id\n";
} else {
echo "SKIP: dept_id column exists\n";
}
if (!indexExists('sa_system_role', 'idx_dept_id')) {
Db::execute('ALTER TABLE `sa_system_role` ADD INDEX `idx_dept_id` (`dept_id`)');
echo "OK: ADD INDEX idx_dept_id\n";
} else {
echo "SKIP: idx_dept_id exists\n";
}
Db::execute('UPDATE `sa_system_role` SET `dept_id` = 0 WHERE `id` > 1');
echo "OK: UPDATE template dept_id\n";
if (indexExists('sa_system_role', 'uk_slug')) {
Db::execute('ALTER TABLE `sa_system_role` DROP INDEX `uk_slug`');
echo "OK: DROP INDEX uk_slug\n";
} else {
echo "SKIP: uk_slug not found\n";
}
if (!indexExists('sa_system_role', 'uk_dept_code')) {
Db::execute('ALTER TABLE `sa_system_role` ADD UNIQUE KEY `uk_dept_code` (`dept_id`, `code`)');
echo "OK: ADD UNIQUE uk_dept_code\n";
} else {
echo "SKIP: uk_dept_code exists\n";
}
$service = new SystemRoleChannelService();
$sync = $service->syncAllChannelsFromDefault();
echo "Synced roles for channels: " . json_encode($sync, JSON_UNESCAPED_UNICODE) . "\n";
$mapped = $service->remapUserRolesToChannelRoles();
echo "Remapped user roles: {$mapped}\n";
echo "Done.\n";

View File

@@ -0,0 +1,58 @@
<?php
/**
* 仅执行渠道配置同步(步骤 4-6适用于已完成表结构迁移后
* 用法php db/run_sync_channel_config.php
*/
declare(strict_types=1);
define('BASE_PATH', dirname(__DIR__));
require_once BASE_PATH . '/vendor/autoload.php';
if (class_exists(\Dotenv\Dotenv::class) && is_file(BASE_PATH . '/.env')) {
\Dotenv\Dotenv::createUnsafeMutable(BASE_PATH)->load();
}
\Webman\Config::load(BASE_PATH . '/config', ['route', 'plugin']);
\Webman\ThinkOrm\ThinkOrm::start(null);
use app\dice\service\DiceChannelConfigService;
use plugin\saiadmin\app\model\system\SystemDept;
use support\think\Db;
echo "========== 渠道配置同步 ==========\n";
$service = new DiceChannelConfigService();
echo "调整配置表主键与默认模板 dept_id=0...\n";
$service->ensureConfigCompositeKeys();
$tables = ['dice_config', 'dice_ante_config', 'dice_lottery_pool_config', 'dice_reward_config', 'dice_reward', 'dice_game'];
echo "\n清理各渠道不完整配置后重新复制...\n";
$deptIds = SystemDept::column('id');
foreach ($deptIds as $deptId) {
$deptId = (int) $deptId;
if ($deptId <= 0) {
continue;
}
foreach ($tables as $table) {
try {
Db::table($table)->where('dept_id', $deptId)->delete();
} catch (\Throwable $e) {
}
}
}
echo "设为默认模板 (dept_id=0)...\n";
foreach ($tables as $table) {
Db::table($table)->whereNull('dept_id')->update(['dept_id' => 0]);
}
echo "从默认模板复制到各渠道...\n";
$summary = $service->syncAllChannelsFromDefault();
foreach ($summary as $deptId => $info) {
echo "渠道 {$deptId}: 复制 [" . (implode(',', $info['copied'] ?? []) ?: '无') . "] 跳过 [" . (implode(',', $info['skipped'] ?? []) ?: '无') . "]\n";
}
echo "\n回填 dept_id...\n";
print_r($service->backfillDataDeptId());
echo "\n渠道数: " . SystemDept::count() . "\n完成。\n";

View File

@@ -0,0 +1,85 @@
<?php
/**
* 为登录日志、操作日志补充 dept_id 字段并回填历史数据。
*
* 用法(在 server 目录): php db/run_system_log_dept_id.php
*/
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../support/bootstrap.php';
use support\think\Db;
function systemLogTableHasColumn(string $table, string $column): bool
{
try {
$fields = Db::getFields($table);
return isset($fields[$column]);
} catch (\Throwable $e) {
return false;
}
}
function systemLogIndexExists(string $table, string $indexName): bool
{
$rows = Db::query("SHOW INDEX FROM `{$table}` WHERE Key_name = ?", [$indexName]);
return !empty($rows);
}
function addLogDeptColumn(string $table, string $afterColumn): void
{
if (!systemLogTableHasColumn($table, 'dept_id')) {
Db::execute(
"ALTER TABLE `{$table}`
ADD COLUMN `dept_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '所属渠道ID' AFTER `{$afterColumn}`"
);
echo "OK: {$table} ADD COLUMN dept_id\n";
} else {
echo "SKIP: {$table} dept_id column exists\n";
}
if (!systemLogIndexExists($table, 'idx_dept_id')) {
Db::execute("ALTER TABLE `{$table}` ADD INDEX `idx_dept_id` (`dept_id`)");
echo "OK: {$table} ADD INDEX idx_dept_id\n";
} else {
echo "SKIP: {$table} idx_dept_id exists\n";
}
}
echo "=== system log dept_id migration ===\n";
addLogDeptColumn('sa_system_login_log', 'login_time');
addLogDeptColumn('sa_system_oper_log', 'request_data');
$loginByCreator = Db::execute(
"UPDATE `sa_system_login_log` l
INNER JOIN `sa_system_user` u ON u.id = l.created_by
SET l.dept_id = u.dept_id
WHERE (l.dept_id IS NULL OR l.dept_id = 0)
AND u.dept_id IS NOT NULL
AND u.dept_id > 0"
);
echo "OK: login log backfilled by created_by: {$loginByCreator}\n";
$loginByUsername = Db::execute(
"UPDATE `sa_system_login_log` l
INNER JOIN `sa_system_user` u ON u.username = l.username
SET l.dept_id = u.dept_id
WHERE (l.dept_id IS NULL OR l.dept_id = 0)
AND u.dept_id IS NOT NULL
AND u.dept_id > 0"
);
echo "OK: login log backfilled by username: {$loginByUsername}\n";
$operByUsername = Db::execute(
"UPDATE `sa_system_oper_log` o
INNER JOIN `sa_system_user` u ON u.username = o.username
SET o.dept_id = u.dept_id
WHERE (o.dept_id IS NULL OR o.dept_id = 0)
AND u.dept_id IS NOT NULL
AND u.dept_id > 0"
);
echo "OK: oper log backfilled by username: {$operByUsername}\n";
echo "Done.\n";

View File

@@ -0,0 +1,10 @@
-- 角色表按渠道隔离dept_id=0 为默认模板角色,各渠道拥有独立角色副本
ALTER TABLE `sa_system_role`
ADD COLUMN `dept_id` bigint(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT '所属渠道ID0=默认模板' AFTER `id`,
ADD INDEX `idx_dept_id` (`dept_id`);
UPDATE `sa_system_role` SET `dept_id` = 0 WHERE `dept_id` IS NULL OR `id` > 1;
ALTER TABLE `sa_system_role` DROP INDEX `uk_slug`;
ALTER TABLE `sa_system_role` ADD UNIQUE KEY `uk_dept_code` (`dept_id`, `code`);

View File

@@ -0,0 +1,21 @@
<?php
/**
* 为各渠道补齐三个默认代理角色,并清理多余角色(无用户绑定的)
* 用法: php db/sync_channel_default_roles.php
*/
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../support/bootstrap.php';
use plugin\saiadmin\app\service\SystemRoleChannelService;
$service = new SystemRoleChannelService();
echo 'Default role codes: ' . implode(', ', $service->getDefaultChannelRoleCodes()) . "\n";
$sync = $service->syncAllChannelsFromDefault();
echo json_encode($sync, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
$mapped = $service->remapUserRolesToChannelRoles();
echo "Remapped user roles: {$mapped}\n";
echo "Done.\n";

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
define('BASE_PATH', dirname(__DIR__));
require BASE_PATH . '/vendor/autoload.php';
\Dotenv\Dotenv::createUnsafeMutable(BASE_PATH)->load();
\Webman\Config::load(BASE_PATH . '/config', ['route', 'plugin']);
\Webman\ThinkOrm\ThinkOrm::start(null);
use app\dice\helper\AdminScopeHelper;
use app\dice\model\reward_config\DiceRewardConfig;
use app\dice\logic\reward_config\DiceRewardConfigLogic;
$deptId = 1123;
$query = (new DiceRewardConfig())->order('id', 'asc');
AdminScopeHelper::applyConfigScope($query, null, $deptId);
$logic = new DiceRewardConfigLogic();
$result = $logic->getList($query);
echo 'keys: ' . implode(',', array_keys($result)) . "\n";
$data = $result['data'] ?? $result['records'] ?? [];
echo 'count: ' . (is_array($data) ? count($data) : 0) . "\n";

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
define('BASE_PATH', dirname(__DIR__));
require BASE_PATH . '/vendor/autoload.php';
\Dotenv\Dotenv::createUnsafeMutable(BASE_PATH)->load();
\Webman\Config::load(BASE_PATH . '/config', ['route', 'plugin']);
\Webman\ThinkOrm\ThinkOrm::start(null);
use app\dice\helper\AdminScopeHelper;
use app\dice\model\reward_config\DiceRewardConfig;
foreach ([0, 1123] as $deptId) {
$q1 = DiceRewardConfig::where('dept_id', $deptId);
echo "direct dept {$deptId}: " . $q1->count() . "\n";
$q2 = (new DiceRewardConfig())->order('id', 'asc');
AdminScopeHelper::applyConfigScope($q2, ['id' => 1], $deptId);
echo "super admin scoped dept {$deptId}: " . $q2->count() . "\n";
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
define('BASE_PATH', dirname(__DIR__));
require_once BASE_PATH . '/vendor/autoload.php';
\Dotenv\Dotenv::createUnsafeMutable(BASE_PATH)->load();
\Webman\Config::load(BASE_PATH . '/config', ['route', 'plugin']);
\Webman\ThinkOrm\ThinkOrm::start(null);
use plugin\saiadmin\app\model\system\SystemDept;
use support\think\Db;
$deptIds = SystemDept::column('id');
echo "渠道数: " . count($deptIds) . " [" . implode(',', $deptIds) . "]\n\n";
$tables = [
'dice_config',
'dice_ante_config',
'dice_lottery_pool_config',
'dice_reward_config',
'dice_reward',
'dice_game',
];
echo "=== 配置按 dept_id 统计 ===\n";
foreach ($tables as $table) {
$rows = Db::query("SELECT dept_id, COUNT(*) AS cnt FROM `{$table}` GROUP BY dept_id ORDER BY dept_id");
echo "{$table}:\n";
foreach ($rows as $r) {
$dept = $r['dept_id'] === null ? 'NULL' : (string) $r['dept_id'];
echo " dept_id={$dept}: {$r['cnt']}\n";
}
}
echo "\n=== 业务数据未回填 dept_id ===\n";
$biz = ['dice_player', 'dice_play_record', 'dice_play_record_test'];
foreach ($biz as $table) {
if (!Db::getFields($table)) {
continue;
}
$nullCnt = Db::table($table)->where(function ($q) {
$q->whereNull('dept_id')->whereOr('dept_id', 0);
})->count();
$total = Db::table($table)->count();
echo "{$table}: 未回填 {$nullCnt} / 总计 {$total}\n";
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
define('BASE_PATH', dirname(__DIR__));
require BASE_PATH . '/vendor/autoload.php';
\Dotenv\Dotenv::createUnsafeMutable(BASE_PATH)->load();
\Webman\Config::load(BASE_PATH . '/config', ['route', 'plugin']);
\Webman\ThinkOrm\ThinkOrm::start(null);
use app\dice\helper\AdminScopeHelper;
use app\dice\logic\reward_config\DiceRewardConfigLogic;
use plugin\saiadmin\app\cache\UserInfoCache;
$adminInfo = UserInfoCache::getUserInfo(123);
$logic = new DiceRewardConfigLogic();
$query = $logic->search([]);
AdminScopeHelper::applyConfigScope($query, $adminInfo, 1123);
$data = $query->order('id', 'asc')->select()->toArray();
echo 'controller-style all rows: ' . count($data) . PHP_EOL;
$nonBig = 0;
foreach ($data as $row) {
if (($row['tier'] ?? '') !== 'BIGWIN') {
$nonBig++;
}
}
echo 'index tab rows (non-BIGWIN): ' . $nonBig . PHP_EOL;
echo 'bigwin tab rows: ' . (count($data) - $nonBig) . PHP_EOL;