diff --git a/api/shop.php b/api/shop.php
index 992538a..49b916b 100644
--- a/api/shop.php
+++ b/api/shop.php
@@ -205,8 +205,9 @@ function shopBuy($token) {
// 更新库存(同时写admin_stock确保Java同步)
if ($item['stock'] > 0) {
$newStock = $item['stock'] - $amount;
- $stmt = $db->prepare("UPDATE shop_items SET stock = :ns, admin_stock = :ns, hourly_sales = hourly_sales + :amount, total_sales = total_sales + :amount WHERE id = :id");
+ $stmt = $db->prepare("UPDATE shop_items SET stock = :ns, admin_stock = :ns, last_modified = :t, hourly_sales = hourly_sales + :amount, total_sales = total_sales + :amount WHERE id = :id");
$stmt->bindValue(':ns', $newStock, SQLITE3_INTEGER);
+ $stmt->bindValue(':t', time(), SQLITE3_INTEGER);
$stmt->bindValue(':amount', $amount, SQLITE3_INTEGER);
$stmt->bindValue(':id', $itemId, SQLITE3_TEXT);
$stmt->execute();
@@ -543,8 +544,9 @@ function shopBuyCart($token) {
$item = $p['item'];
if ($item['stock'] > 0) {
$newStock = $item['stock'] - $p['amount'];
- $stmt = $db->prepare("UPDATE shop_items SET stock = :ns, admin_stock = :ns, hourly_sales = hourly_sales + :amount, total_sales = total_sales + :amount WHERE id = :id");
+ $stmt = $db->prepare("UPDATE shop_items SET stock = :ns, admin_stock = :ns, last_modified = :t, hourly_sales = hourly_sales + :amount, total_sales = total_sales + :amount WHERE id = :id");
$stmt->bindValue(':ns', $newStock, SQLITE3_INTEGER);
+ $stmt->bindValue(':t', time(), SQLITE3_INTEGER);
$stmt->bindValue(':amount', $p['amount'], SQLITE3_INTEGER);
$stmt->bindValue(':id', $p['item_id'], SQLITE3_TEXT);
$stmt->execute();
diff --git a/api/sync.php b/api/sync.php
index 38b44aa..d70362e 100644
--- a/api/sync.php
+++ b/api/sync.php
@@ -101,6 +101,24 @@ function checkPlayerRegistered() {
$action = getParam('action', '');
+// ★ 数据安全护栏(2026-10-10):任一侧处于块状态 → 拒绝一切数据库操作与握手(含对账),
+// 给 Java 明确失败回执;等管理员修好 sql.md 数据库配置后自动恢复放行。
+if (function_exists('dfGuardBlockedSides')) {
+ $dfBlocked = dfGuardBlockedSides();
+ if ($dfBlocked) {
+ while (ob_get_level() > 0) ob_end_clean();
+ header('Content-Type: application/json; charset=utf-8');
+ http_response_code(503);
+ echo json_encode(array(
+ 'success' => false,
+ 'guard' => 'blocked',
+ 'message' => '数据库安全护栏:' . implode(';', $dfBlocked)
+ . ' 已暂停对该数据库的全部操作并拒绝握手,请管理员检查 sql.md 数据库配置段。',
+ ), JSON_UNESCAPED_UNICODE);
+ exit;
+ }
+}
+
try {
switch ($action) {
case 'sync_shop':
@@ -207,6 +225,9 @@ switch ($action) {
case 'pull_shop_stock':
pullShopStock();
break;
+ case 'push_shop_stock':
+ pushShopStock();
+ break;
case 'clear_admin_stock':
clearAdminStock();
break;
@@ -3829,6 +3850,42 @@ function pullShopStock() {
success(['items' => $items, 'count' => count($items)]);
}
+// ===== 插件推送本地库存改动(双向同步:数据分叉以 Java 为主)=====
+function pushShopStock() {
+ $secret = getParam('secret');
+ if (!$secret || $secret !== SECRET_KEY) error('认证失败');
+
+ $db = getDB();
+ $db->exec("CREATE TABLE IF NOT EXISTS shop_items (
+ id TEXT PRIMARY KEY, category TEXT NOT NULL, display_name TEXT NOT NULL,
+ material TEXT NOT NULL, buy_price INTEGER DEFAULT 0, sell_price INTEGER DEFAULT -1,
+ stock INTEGER DEFAULT -1, hourly_sales INTEGER DEFAULT 0, total_sales INTEGER DEFAULT 0,
+ last_sync INTEGER DEFAULT 0, admin_stock INTEGER DEFAULT NULL,
+ admin_buy_price INTEGER DEFAULT NULL, admin_sell_price INTEGER DEFAULT NULL
+ )");
+
+ $items = getParam('items');
+ if (!$items || !is_array($items)) error('缺少库存数据');
+
+ $time = time();
+ $applied = 0;
+ $kept = 0;
+ // Java 主:直接写 stock 并清 admin_stock(Java 值即最新权威)。
+ // 竞态护栏:PHP 侧该行若有与本次推送同秒更新的未拉取改动(web 购买/后台改库存),
+ // 保留不覆盖,等 Java 下一轮 pull 时按「哪边有改动对齐哪边」处理。
+ $stmt = $db->prepare("UPDATE shop_items SET stock = :s, admin_stock = NULL, last_modified = :t
+ WHERE id = :id AND (admin_stock IS NULL OR last_modified <= :t)");
+ foreach ($items as $it) {
+ if (!is_array($it) || !isset($it['id'])) continue;
+ $stmt->bindValue(':s', (int)$it['stock'], SQLITE3_INTEGER);
+ $stmt->bindValue(':t', $time, SQLITE3_INTEGER);
+ $stmt->bindValue(':id', (string)$it['id'], SQLITE3_TEXT);
+ $stmt->execute();
+ if ($db->changes() > 0) { $applied++; } else { $kept++; }
+ }
+ success(['count' => count($items), 'applied' => $applied, 'kept' => $kept], '库存已接收');
+}
+
// ===== 插件确认已应用管理员库存改动 =====
function clearAdminStock() {
$secret = getParam('secret');
diff --git a/core.php b/core.php
index a9b17f4..6468553 100644
--- a/core.php
+++ b/core.php
@@ -3,6 +3,8 @@
require_once __DIR__ . '/config.php';
// ★ 紧急安全加固(2026-09-29):TOTP 二次验证 + IP 白名单 + 会话纪元(见 security.php)
require_once __DIR__ . '/security.php';
+// ★ 数据安全护栏 / 灾后自愈(2026-10-10):dbOpen 连接后体检,块模式拒绝操作
+require_once __DIR__ . '/db_guard.php';
// ★ 确保任何前置输出(包括 BOM、空格、Warning)都被丢弃
if (ob_get_level() > 0) {
@@ -88,6 +90,16 @@ if (!function_exists('dbExtractTables')) {
$sql = (string)$sql;
$out = array();
+ // ⓪ upsert 尾段预抹(2026-10-10 灾备演练暴露):
+ // INSERT ... ON CONFLICT(...) DO UPDATE SET 列=...(SQLite 方言)
+ // INSERT ... ON DUPLICATE KEY UPDATE 列=...(MySQL 方言)
+ // 尾段里只有列名,会被 ③ 的 UPDATE 正则误提成表名 → dbPickSide 因混进
+ // 非 Java 表而整句判 web → java 组表被送去 caoyuanphp → #1146。
+ // router 判侧发生在方言翻译之前、拿的是 SQLite 原句,故两种形态都要抹;
+ // 只抹到分号/语句尾,绝不开跨界(单次 exec/prepare 本就是单语句)。
+ $sql = preg_replace('/\bON\s+CONFLICT\b[^;]*/is', ' ', $sql);
+ $sql = preg_replace('/\bON\s+DUPLICATE\s+KEY\s+UPDATE\b[^;]*/is', ' ', $sql);
+
// ① CREATE [UNIQUE] INDEX [IF NOT EXISTS] <索引名> ON <表名>(...):只取 ON 之后的表名
$patIdx = '/\bCREATE\s+(?:UNIQUE\s+)?INDEX(?:\s+IF\s+NOT\s+EXISTS)?\s+[`"\']?[\w$]+[`"\']?\s+ON\s+[`"\']?([\w$]+)/i';
if (preg_match_all($patIdx, $sql, $mi)) {
@@ -1452,21 +1464,42 @@ function dbOpen($side) {
$mcfg = sqlMdDbConfig();
if (isset($mcfg['mode']) && $mcfg['mode'] === 'mysql'
&& isset($mcfg[$side]) && is_array($mcfg[$side])) {
+ // ★ 数据安全护栏(2026-10-10):先连上、再过体检,最后才干活
+ $dfVerdict = 'skip'; // skip = 连接都没建成(走 SQLite 兜底,与改造前一致)
+ $mdb = null;
try {
$mdb = new SdfMySqlDb($mcfg[$side], $side);
$mdb->enableExceptions(true);
$mdb->busyTimeout(8000);
try { $mdb->exec('ROLLBACK'); } catch (\Throwable $_) {} // 清上一请求残留事务
+ // 体检放在 initTables 之前:指错库时绝不往里建表/迁移
+ try { $dfVerdict = dfGuardCheck($mdb, $mcfg[$side], $side); } catch (\Throwable $e) {
+ debugLog('dbOpen[' . $side . ']: 数据护栏体检异常(按 ok 放行): ' . $e->getMessage());
+ $dfVerdict = 'ok';
+ }
+ } catch (\Throwable $e) {
+ debugLog('dbOpen[' . $side . ']: MySQL 连接失败,回退 SQLite: ' . $e->getMessage());
+ }
+ if ($mdb !== null && $dfVerdict !== 'skip') {
+ if ($dfVerdict === 'block') {
+ // 数据安全优先:sql.md 指错库/指错机 → 硬失败,绝不软回退 SQLite
+ // (软回退等于把数据写进另一个库,比停机更危险)
+ throw new dfGuardBlockException(dfGuardBlockReason($side));
+ }
try { initTables($mdb, $side); } catch (\Throwable $e) {
debugLog('dbOpen[' . $side . ']: initTables失败: ' . $e->getMessage());
}
try { migrateDatabase($mdb, $side); } catch (\Throwable $e) {
debugLog('dbOpen[' . $side . ']: migrateDatabase失败: ' . $e->getMessage());
}
+ if ($dfVerdict === 'recover') {
+ // 被清空的自愈:补表结构 + 空表从最近备份补推 SQL 数据,然后放行 Java 握手
+ try { dfGuardRecover($mdb, $side); } catch (\Throwable $e) {
+ debugLog('dbOpen[' . $side . ']: 自愈执行失败: ' . $e->getMessage());
+ }
+ }
$cache[$side] = $mdb;
return $mdb;
- } catch (\Throwable $e) {
- debugLog('dbOpen[' . $side . ']: MySQL 连接失败,回退 SQLite: ' . $e->getMessage());
}
}
diff --git a/db_guard.php b/db_guard.php
new file mode 100644
index 0000000..c473313
--- /dev/null
+++ b/db_guard.php
@@ -0,0 +1,398 @@
+ $s) {
+ if (is_array($s) && isset($s['mode']) && $s['mode'] === 'block' && !empty($s['alert'])) {
+ $out[] = $side . '库:' . $s['alert'];
+ }
+ }
+ return $out;
+}
+
+/** 某侧的块原因(dbOpen 抛异常用) */
+function dfGuardBlockReason($side)
+{
+ $st = dfGuardReadState();
+ if (isset($st[$side]['alert']) && $st[$side]['alert']) return (string)$st[$side]['alert'];
+ return '数据库状态异常(身份与上次运行不一致),已停止操作并拒绝握手,请检查 sql.md 数据库配置段。';
+}
+
+/**
+ * 纯判定函数(可单测):给定上次体检快照与本次观测,返回 mode。
+ * @param array|null $prev 上次快照:db/host/port/tables/rows/core/ts
+ * @param array $now 本次观测:db/host/port/tables(array)/rows(int)/core(array 表=>行数)
+ * @return array array('mode' => 'ok'|'recover'|'block', 'why' => string)
+ */
+function dfGuardDecide($prev, $now)
+{
+ $db = isset($now['db']) ? (string)$now['db'] : '';
+ $host = isset($now['host']) ? (string)$now['host'] : '';
+ $port = isset($now['port']) ? (int)$now['port'] : 0;
+
+ // 没上次快照 → 首次见,先记账不报警(部署/状态文件被删后的正常冷启动)
+ if (!is_array($prev) || empty($prev)) {
+ return array('mode' => 'ok', 'why' => 'bootstrap');
+ }
+ $pdb = isset($prev['db']) ? (string)$prev['db'] : '';
+ $phost = isset($prev['host']) ? (string)$prev['host'] : '';
+ $pport = isset($prev['port']) ? (int)$prev['port'] : 0;
+
+ // 身份变化(库名/主机/端口)→ 指错了地方:块,绝不往里写
+ if ($pdb !== $db || $phost !== $host || $pport !== $port) {
+ return array('mode' => 'block', 'why' => 'identity_mismatch');
+ }
+
+ $core = dfGuardCoreTables($pdb === 'caoyuanphp' || strpos($pdb, 'php') !== false ? 'web' : 'java');
+ // 用 prev 记忆的侧别更稳;这里退化用库名猜,调用方会以 $side 覆盖 core 集合
+ $tables = isset($now['tables']) ? $now['tables'] : array();
+ $coreNow = isset($now['core']) && is_array($now['core']) ? $now['core'] : array();
+ $rows = isset($now['rows']) ? (int)$now['rows'] : -1;
+ $prevRows = isset($prev['rows']) ? (int)$prev['rows'] : 0;
+
+ // 核心表一张不剩 → 库被连根清空/删库:走 recover(重建+补推+等 Java)
+ $haveCore = 0;
+ foreach ($core as $t) { if (in_array($t, $tables, true)) $haveCore++; }
+ if ($haveCore === 0 && !empty($prev['tables'])) {
+ return array('mode' => 'recover', 'why' => 'tables_wiped');
+ }
+
+ // 表都在但整体行数塌了(<=2% 或绝对空)→ 被清空:recover
+ if ($rows >= 0 && $prevRows > 0) {
+ $floor = max(50, (int)($prevRows * 0.02));
+ if ($rows <= $floor) {
+ return array('mode' => 'recover', 'why' => 'rows_wiped');
+ }
+ // 核心表精确计数佐证(TABLE_ROWS 是估计值,避免误判)
+ $zeroCore = 0;
+ $checkedCore = 0;
+ foreach ($coreNow as $t => $c) {
+ $checkedCore++;
+ if ((int)$c <= 0) $zeroCore++;
+ }
+ if ($checkedCore > 0 && $zeroCore === $checkedCore) {
+ return array('mode' => 'recover', 'why' => 'core_zeroed');
+ }
+ }
+
+ return array('mode' => 'ok', 'why' => 'healthy');
+}
+
+/**
+ * 连接后体检(在 initTables 之前调用,块模式直接抛,避免往可疑库里建表)。
+ * @return string 'ok'|'recover'|'block'
+ */
+function dfGuardCheck($mdb, $conn, $side)
+{
+ $st = dfGuardReadState();
+ $prev = isset($st[$side]) && is_array($st[$side]) ? $st[$side] : null;
+ $now = time();
+
+ // 节流:刚体检过 → 直接沿用缓存判定(块/恢复态也一样缓存,行为一致)
+ if (is_array($prev) && isset($prev['ts']) && ($now - (int)$prev['ts']) < DF_GUARD_TTL
+ && in_array(isset($prev['mode']) ? $prev['mode'] : '', array('ok', 'recover', 'block'), true)) {
+ return $prev['mode'];
+ }
+
+ $dbName = isset($conn['name']) ? (string)$conn['name'] : '';
+ $host = isset($conn['host']) ? (string)$conn['host'] : '';
+ $port = isset($conn['port']) ? (int)$conn['port'] : 3306;
+
+ // ---- 完整体检 ----
+ $tables = array();
+ $rs = $mdb->query("SELECT table_name FROM information_schema.tables WHERE table_schema='"
+ . $mdb->escapeString($dbName) . "'");
+ if ($rs) {
+ while ($row = $rs->fetch_assoc()) $tables[] = (string)$row['table_name'];
+ }
+ $estRows = 0;
+ $rs = $mdb->query("SELECT SUM(TABLE_ROWS) s FROM information_schema.tables WHERE table_schema='"
+ . $mdb->escapeString($dbName) . "'");
+ if ($rs && ($row = $rs->fetch_assoc())) $estRows = (int)$row['s'];
+
+ $core = dfGuardCoreTables($side);
+ $coreNow = array();
+ foreach ($core as $t) {
+ if (!in_array($t, $tables, true)) continue;
+ $v = dfGuardScalar($mdb, "SELECT COUNT(*) FROM `" . $t . "`");
+ if ($v !== null) $coreNow[$t] = (int)$v;
+ }
+
+ $nowInfo = array(
+ 'db' => $dbName, 'host' => $host, 'port' => $port,
+ 'tables' => $tables, 'rows' => $estRows, 'core' => $coreNow,
+ );
+ $decide = dfGuardDecide($prev, $nowInfo, $side);
+ $mode = $decide['mode'];
+
+ $entry = array(
+ 'db' => $dbName, 'host' => $host, 'port' => $port,
+ 'tables' => count($tables), 'rows' => $estRows,
+ 'core' => $coreNow, 'ts' => $now, 'mode' => $mode,
+ );
+ if ($mode === 'block') {
+ $entry['alert'] = 'sql.md 里这一侧指向的数据库与上次运行不一致(上次 '
+ . (is_array($prev) ? ($prev['host'] . ':' . $prev['port'] . '/' . $prev['db']) : '?')
+ . ' → 现在 ' . $host . ':' . $port . '/' . $dbName . ')。'
+ . '为避免写错库破坏数据,已停止对该库的一切操作并拒绝与 Java 握手。'
+ . '请登录服务器核对 sql.md 的数据库配置段(地址/端口/库名/账号),改对后本护栏会自动恢复。';
+ } elseif ($mode === 'recover') {
+ $entry['alert'] = '检测到该库被清空(' . $decide['why'] . '),已执行自愈:补表结构并尝试从最近备份补推 SQL 数据,'
+ . '随后放行 Java 握手补推其余数据。PHP 侧自有表(工单/订单/流水等)只能从备份恢复,请检查 debug.log 的补推明细。';
+ } else {
+ $entry['alert'] = null;
+ }
+
+ $st[$side] = $entry;
+ dfGuardWriteState($st);
+
+ if ($mode === 'block') {
+ dfGuardLog('dbGuard[' . $side . '] ★ 块模式:' . $entry['alert']);
+ } elseif ($mode === 'recover') {
+ dfGuardLog('dbGuard[' . $side . '] ★ 恢复模式:' . $decide['why'] . ',行数 '
+ . $estRows . '(上次 ' . (is_array($prev) ? (int)$prev['rows'] : 0) . ')');
+ }
+ return $mode;
+}
+
+function dfGuardScalar($mdb, $sql)
+{
+ try {
+ $rs = $mdb->query($sql);
+ if ($rs && ($row = $rs->fetch_assoc())) {
+ foreach ($row as $v) return $v;
+ }
+ } catch (\Throwable $e) { return null; }
+ return null;
+}
+
+function dfGuardLog($msg)
+{
+ if (function_exists('debugLog')) { debugLog($msg); return; }
+ @error_log($msg);
+}
+
+/** 恢复模式动作:补表结构(db_schema_mysql.sql)+ 把仍为空的表从最近可读备份补推数据 */
+function dfGuardRecover($mdb, $side)
+{
+ $done = array('schema' => 0, 'restored' => array(), 'skipped' => array(), 'err' => array());
+
+ // 1) 缺的表用内置 schema 补(initTables 通常已经补过,这里是双保险)
+ $schemaFile = __DIR__ . '/db_schema_mysql.sql';
+ if (is_file($schemaFile) && is_readable($schemaFile)) {
+ $done['schema'] = dfGuardApplySchema($mdb, $side, $schemaFile);
+ }
+
+ // 2) 仍为空的表 → 从最近一次可读备份补推(只补 0 行表,绝不覆盖有数据的表)
+ try {
+ $dump = dfGuardPickDump($side);
+ if ($dump === null) {
+ $done['skipped'][] = 'no_readable_dump';
+ } else {
+ $done['restored'] = dfGuardRestoreEmptyTables($mdb, $dump);
+ }
+ } catch (\Throwable $e) {
+ $done['err'][] = $e->getMessage();
+ }
+
+ dfGuardLog('dbGuard[' . $side . '] 自愈执行明细: ' . json_encode($done, JSON_UNESCAPED_UNICODE));
+ return $done;
+}
+
+/** 从 db_schema_mysql.sql 里把本侧缺失的表建出来(CREATE TABLE IF NOT EXISTS) */
+function dfGuardApplySchema($mdb, $side, $schemaFile)
+{
+ $javaTables = function_exists('dbJavaTables') ? dbJavaTables() : array();
+ $sql = (string)@file_get_contents($schemaFile);
+ if ($sql === '') return 0;
+ // 只认 CREATE TABLE 段:去掉 DROP/LOCK/UNLOCK(恢复期绝不能删表)
+ $lines = preg_split('/\r\n|\r|\n/', $sql);
+ $buf = '';
+ $statements = array();
+ foreach ($lines as $ln) {
+ $t = trim($ln);
+ if ($t === '' || $t[0] === '#' || strpos($t, '--') === 0) continue;
+ if (preg_match('/^(DROP|LOCK|UNLOCK)\b/i', $t)) continue;
+ $buf .= $ln . "\n";
+ if (substr(rtrim($t), -1) === ';') {
+ $statements[] = $buf;
+ $buf = '';
+ }
+ }
+ if (trim($buf) !== '') $statements[] = $buf;
+
+ $created = 0;
+ foreach ($statements as $s) {
+ if (!preg_match('/^CREATE TABLE\s+(?:IF NOT EXISTS\s+)?`?([A-Za-z0-9_]+)`?/i', ltrim($s), $m)) continue;
+ $tbl = $m[1];
+ // 归属过滤:java 组表只准建到 java 侧,其余建到 web 侧
+ if ($side === 'java') {
+ if (!in_array($tbl, $javaTables, true)) continue;
+ } else {
+ if (in_array($tbl, $javaTables, true)) continue;
+ }
+ // 表已存在就跳过
+ $v = dfGuardScalar($mdb, "SELECT COUNT(*) c FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='"
+ . $mdb->escapeString($tbl) . "'");
+ if ($v !== null && (int)$v > 0) continue;
+ try {
+ $stmt = preg_replace('/^CREATE TABLE\s+(?!IF NOT EXISTS)/i', 'CREATE TABLE IF NOT EXISTS ', ltrim($s), 1);
+ $mdb->exec($stmt);
+ $created++;
+ } catch (\Throwable $e) {
+ dfGuardLog('dbGuard: 建表失败 ' . $tbl . ': ' . $e->getMessage());
+ }
+ }
+ return $created;
+}
+
+/** 挑一份最近的可读备份(库名出现在文件名里、年龄合规、按 mtime 新优先) */
+function dfGuardPickDump($side)
+{
+ $st = dfGuardReadState();
+ $dbName = isset($st[$side]['db']) ? (string)$st[$side]['db'] : '';
+ if ($dbName === '') return null;
+ $cands = array_merge(
+ glob('/www/backup/dr*/*' . $dbName . '*.sql') ?: array(),
+ glob('/www/backup/*' . $dbName . '*.sql') ?: array(),
+ glob(__DIR__ . '/../backup/*' . $dbName . '*.sql') ?: array()
+ );
+ $best = null;
+ $bestMt = 0;
+ $now = time();
+ foreach ($cands as $f) {
+ if (!is_file($f) || !is_readable($f)) continue;
+ $mt = @filemtime($f);
+ if (!$mt || ($now - $mt) > DF_GUARD_BACKUP_MAX_AGE) continue;
+ if ($mt > $bestMt) { $bestMt = $mt; $best = $f; }
+ }
+ return $best;
+}
+
+/**
+ * 备份补推:只执行「目标表当前 0 行」的 INSERT / 缺失表的 CREATE,其余语句一律跳过。
+ * 返回 array('表名' => 补推行数)。
+ */
+function dfGuardRestoreEmptyTables($mdb, $dumpFile)
+{
+ $out = array();
+ $fh = @fopen($dumpFile, 'r');
+ if (!$fh) return $out;
+ $buf = '';
+ $executed = 0;
+ while (!feof($fh)) {
+ $line = fgets($fh);
+ if ($line === false) break;
+ $buf .= $line;
+ if (rtrim($line) === '' || substr(rtrim($line), -1) !== ';') continue;
+ $stmt = trim($buf);
+ $buf = '';
+ if ($stmt === '') continue;
+ if ($executed >= DF_GUARD_MAX_STMTS) break;
+ if (preg_match('/^INSERT INTO\s+`?([A-Za-z0-9_]+)`?/i', $stmt, $m)) {
+ $tbl = $m[1];
+ $v = dfGuardScalar($mdb, "SELECT COUNT(*) c FROM `" . $tbl . "`");
+ if ($v === null) continue; // 表不存在:跳过(不该由备份建表)
+ if ((int)$v > 0) continue; // 有数据的表绝不覆盖
+ try {
+ $mdb->exec($stmt);
+ $n = 1;
+ $out[$tbl] = isset($out[$tbl]) ? $out[$tbl] + $n : $n;
+ $executed++;
+ } catch (\Throwable $e) {
+ dfGuardLog('dbGuard: 补推 ' . $tbl . ' 失败: ' . $e->getMessage());
+ }
+ } elseif (preg_match('/^CREATE TABLE\s+(?:IF NOT EXISTS\s+)?`?([A-Za-z0-9_]+)`?/i', $stmt, $m)) {
+ $tbl = $m[1];
+ $v = dfGuardScalar($mdb, "SELECT COUNT(*) c FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='"
+ . $mdb->escapeString($tbl) . "'");
+ if ($v !== null && (int)$v > 0) continue;
+ try {
+ $stmt2 = preg_replace('/^CREATE TABLE\s+(?!IF NOT EXISTS)/i', 'CREATE TABLE IF NOT EXISTS ', $stmt, 1);
+ $mdb->exec($stmt2);
+ $executed++;
+ } catch (\Throwable $e) {
+ dfGuardLog('dbGuard: 恢复期建表 ' . $tbl . ' 失败: ' . $e->getMessage());
+ }
+ }
+ }
+ fclose($fh);
+ return $out;
+}
+
+/**
+ * 管理登录页横幅:有未消除的护栏告警时就亮出来(块/恢复态都会显示)。
+ * 告警是派生状态:数据库恢复健康后下一次体检自动转 ok,横幅随之消失。
+ */
+function dfGuardAdminBanner()
+{
+ $st = dfGuardReadState();
+ $msgs = array();
+ foreach ($st as $side => $s) {
+ if (is_array($s) && !empty($s['alert']) && in_array($s['mode'], array('block', 'recover'), true)) {
+ $label = ($side === 'java') ? 'Java 库(caoyuanjava)' : 'PHP 库(caoyuanphp)';
+ $msgs[] = '
[' . htmlspecialchars($label) . '] ' . htmlspecialchars((string)$s['alert'])
+ . '
(' . htmlspecialchars($s['mode'] === 'block' ? '已停止操作并拒绝握手' : '自愈进行中') . ')';
+ }
+ }
+ if (!$msgs) return '';
+ $css = '';
+ return $css . '
⚠ 数据库安全护栏告警:
' . implode('
', $msgs) . '
';
+}
diff --git a/db_schema_mysql.sql b/db_schema_mysql.sql
new file mode 100644
index 0000000..3014a21
--- /dev/null
+++ b/db_schema_mysql.sql
@@ -0,0 +1,845 @@
+-- SDF1 双库表结构快照(灾备自愈用,由生产 SHOW CREATE TABLE 生成)
+-- 用法:db_guard.php 恢复模式据此补建缺失表(CREATE TABLE IF NOT EXISTS)
+
+-- ================= aoyuanjava =================
+
+-- aoyuanjava.ond_cache
+CREATE TABLE IF NOT EXISTS `bond_cache` (
+ `player_name` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `amount` bigint(20) DEFAULT '0',
+ `updated_at` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`player_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.dk
+CREATE TABLE IF NOT EXISTS `cdk` (
+ `code` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `amount` bigint(20) DEFAULT '0',
+ `type` varchar(500) COLLATE utf8mb4_bin DEFAULT 'bond',
+ `used` bigint(20) DEFAULT '0',
+ `used_by` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `created_at` bigint(20) DEFAULT '0',
+ `used_at` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.dk_validate_requests
+CREATE TABLE IF NOT EXISTS `cdk_validate_requests` (
+ `request_id` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `code` text COLLATE utf8mb4_bin NOT NULL,
+ `player_name` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `status` varchar(500) COLLATE utf8mb4_bin DEFAULT 'pending',
+ `created_at` bigint(20) NOT NULL,
+ PRIMARY KEY (`request_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.dk_validate_results
+CREATE TABLE IF NOT EXISTS `cdk_validate_results` (
+ `request_id` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `code` text COLLATE utf8mb4_bin NOT NULL,
+ `status` text COLLATE utf8mb4_bin NOT NULL,
+ `amount` bigint(20) DEFAULT '0',
+ `message` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `used` bigint(20) DEFAULT '0',
+ `used_by` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `created_at` bigint(20) NOT NULL,
+ PRIMARY KEY (`request_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.onfirmed_transactions
+CREATE TABLE IF NOT EXISTS `confirmed_transactions` (
+ `tx_id` varchar(325) COLLATE utf8mb4_bin NOT NULL,
+ `tx_type` varchar(325) COLLATE utf8mb4_bin NOT NULL,
+ `confirmed_at` bigint(20) NOT NULL,
+ PRIMARY KEY (`tx_id`,`tx_type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.mail_codes
+CREATE TABLE IF NOT EXISTS `email_codes` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `player_name` text COLLATE utf8mb4_bin NOT NULL,
+ `email` text COLLATE utf8mb4_bin NOT NULL,
+ `code` text COLLATE utf8mb4_bin NOT NULL,
+ `expire_at` bigint(20) NOT NULL,
+ `used` bigint(20) DEFAULT '0',
+ `created_at` bigint(20) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.ame_transactions
+CREATE TABLE IF NOT EXISTS `game_transactions` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `java_id` bigint(20) NOT NULL,
+ `player_name` text COLLATE utf8mb4_bin NOT NULL,
+ `type` text COLLATE utf8mb4_bin NOT NULL,
+ `amount` bigint(20) NOT NULL,
+ `target_player` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `operator` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `reason` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `balance_before` bigint(20) DEFAULT '0',
+ `balance_after` bigint(20) DEFAULT '0',
+ `tx_time` bigint(20) NOT NULL,
+ `synced_at` bigint(20) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `java_id` (`java_id`),
+ KEY `idx_gt_player` (`player_name`(191),`tx_time`),
+ KEY `idx_gt_type` (`type`(191))
+) ENGINE=InnoDB AUTO_INCREMENT=61640 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.ourly_active_players
+CREATE TABLE IF NOT EXISTS `hourly_active_players` (
+ `player_name` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `first_seen` bigint(20) NOT NULL,
+ `last_seen` bigint(20) NOT NULL,
+ `hour_key` text COLLATE utf8mb4_bin NOT NULL,
+ PRIMARY KEY (`player_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.nline_player_hb
+CREATE TABLE IF NOT EXISTS `online_player_hb` (
+ `id` bigint(20) NOT NULL DEFAULT '1',
+ `last_seen` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.nline_players
+CREATE TABLE IF NOT EXISTS `online_players` (
+ `player_name` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `login_time` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`player_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.assword_reset_requests
+CREATE TABLE IF NOT EXISTS `password_reset_requests` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `player_name` text COLLATE utf8mb4_bin NOT NULL,
+ `requested_email` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `status` varchar(500) COLLATE utf8mb4_bin DEFAULT 'pending',
+ `admin_approved` bigint(20) DEFAULT '0',
+ `admin_email` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `created_at` bigint(20) NOT NULL,
+ `processed_at` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.assword_reset_tokens
+CREATE TABLE IF NOT EXISTS `password_reset_tokens` (
+ `token` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `player_name` text COLLATE utf8mb4_bin NOT NULL,
+ `expire_at` bigint(20) NOT NULL,
+ PRIMARY KEY (`token`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.layer_checkins
+CREATE TABLE IF NOT EXISTS `player_checkins` (
+ `player_name` varchar(325) COLLATE utf8mb4_bin NOT NULL,
+ `checkin_date` varchar(325) COLLATE utf8mb4_bin NOT NULL,
+ `checkin_time` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`player_name`,`checkin_date`),
+ KEY `idx_player_checkins_player` (`player_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.layer_daily_logins
+CREATE TABLE IF NOT EXISTS `player_daily_logins` (
+ `player_name` varchar(325) COLLATE utf8mb4_bin NOT NULL,
+ `login_date` varchar(325) COLLATE utf8mb4_bin NOT NULL,
+ `first_login` bigint(20) DEFAULT '0',
+ `last_login` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`player_name`,`login_date`),
+ KEY `idx_player_daily_logins_player` (`player_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.layer_ip_changes
+CREATE TABLE IF NOT EXISTS `player_ip_changes` (
+ `player_name` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `old_ip` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `new_ip` text COLLATE utf8mb4_bin NOT NULL,
+ `changed_at` bigint(20) NOT NULL,
+ `synced_at` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`player_name`),
+ KEY `idx_player_ip_changes_player` (`player_name`),
+ KEY `idx_player_ip_changes_time` (`changed_at`),
+ KEY `idx_player_ip_changes_newip` (`new_ip`(191))
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.layer_ip_locations
+CREATE TABLE IF NOT EXISTS `player_ip_locations` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `ip_address` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `location` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `updated_at` bigint(20) DEFAULT '0',
+ `player_name` varchar(500) COLLATE utf8mb4_bin DEFAULT 'global',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uq_player_ip_locations_975887f3` (`ip_address`(191),`player_name`(191)),
+ KEY `idx_player_ip_locations_ip` (`ip_address`),
+ KEY `idx_player_ip_locations_loc` (`location`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.hop_config
+CREATE TABLE IF NOT EXISTS `shop_config` (
+ `cfg_key` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `cfg_value` text COLLATE utf8mb4_bin NOT NULL,
+ PRIMARY KEY (`cfg_key`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.hop_items
+CREATE TABLE IF NOT EXISTS `shop_items` (
+ `id` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `category` text COLLATE utf8mb4_bin NOT NULL,
+ `display_name` text COLLATE utf8mb4_bin NOT NULL,
+ `material` text COLLATE utf8mb4_bin NOT NULL,
+ `buy_price` bigint(20) DEFAULT '0',
+ `sell_price` bigint(20) DEFAULT '-1',
+ `stock` bigint(20) DEFAULT '-1',
+ `hourly_sales` bigint(20) DEFAULT '0',
+ `total_sales` bigint(20) DEFAULT '0',
+ `last_sync` bigint(20) DEFAULT '0',
+ `admin_stock` bigint(20) DEFAULT NULL,
+ `admin_buy_price` bigint(20) DEFAULT NULL,
+ `admin_sell_price` bigint(20) DEFAULT NULL,
+ `last_modified` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.ync_requests
+CREATE TABLE IF NOT EXISTS `sync_requests` (
+ `player_name` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `created_at` bigint(20) NOT NULL,
+ PRIMARY KEY (`player_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.okens
+CREATE TABLE IF NOT EXISTS `tokens` (
+ `token` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `player_name` text COLLATE utf8mb4_bin NOT NULL,
+ `purpose` varchar(500) COLLATE utf8mb4_bin DEFAULT 'general',
+ `created_at` bigint(20) NOT NULL,
+ `expires_at` bigint(20) NOT NULL,
+ `used` bigint(20) DEFAULT '0',
+ `used_at` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`token`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.sers
+CREATE TABLE IF NOT EXISTS `users` (
+ `player_name` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `register_time` bigint(20) DEFAULT '0',
+ `last_login_time` bigint(20) DEFAULT '0',
+ `email` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `points` bigint(20) DEFAULT '0',
+ `gift_stage` bigint(20) DEFAULT '0',
+ `total_online_time` bigint(20) DEFAULT '0',
+ `last_login_location` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `last_login_ip` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `frozen` bigint(20) DEFAULT '0',
+ `freeze_token` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ PRIMARY KEY (`player_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eb_admin_changes
+CREATE TABLE IF NOT EXISTS `web_admin_changes` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `change_type` text COLLATE utf8mb4_bin NOT NULL,
+ `target_id` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `target_name` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `change_data` varchar(500) COLLATE utf8mb4_bin DEFAULT '{}',
+ `created_at` bigint(20) DEFAULT '0',
+ `acknowledged` bigint(20) DEFAULT '0',
+ `acked_at` bigint(20) DEFAULT '0',
+ `status` varchar(500) COLLATE utf8mb4_bin DEFAULT 'pending',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eb_area_config
+CREATE TABLE IF NOT EXISTS `web_area_config` (
+ `key` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `value` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ PRIMARY KEY (`key`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eb_area_lands
+CREATE TABLE IF NOT EXISTS `web_area_lands` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `name` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `owner` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `world` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `x1` bigint(20) DEFAULT '0',
+ `z1` bigint(20) DEFAULT '0',
+ `x2` bigint(20) DEFAULT '0',
+ `z2` bigint(20) DEFAULT '0',
+ `y1` bigint(20) DEFAULT '0',
+ `y2` bigint(20) DEFAULT '0',
+ `area_size` bigint(20) DEFAULT '0',
+ `created_at` bigint(20) DEFAULT '0',
+ `admin_changed` bigint(20) DEFAULT '0',
+ `deny_pvp` bigint(20) DEFAULT '0',
+ `deny_fall_damage` bigint(20) DEFAULT '0',
+ `deny_hunger` bigint(20) DEFAULT '0',
+ `deny_all_damage` bigint(20) DEFAULT '0',
+ `deny_fire_spread` bigint(20) DEFAULT '0',
+ `deny_explosion` bigint(20) DEFAULT '0',
+ `deny_mob_grief` bigint(20) DEFAULT '0',
+ `deny_block_place` bigint(20) DEFAULT '0',
+ `deny_block_break` bigint(20) DEFAULT '0',
+ `y_min` bigint(20) DEFAULT '0',
+ `y_max` bigint(20) DEFAULT '255',
+ `synced_at` bigint(20) DEFAULT '0',
+ `clear_effects` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `give_effects` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `clear_all_bad_effects` bigint(20) DEFAULT '0',
+ `deny_all_effects` bigint(20) DEFAULT '0',
+ `confiscate_items` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `deny_use_items` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `punish_commands` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `deny_drop` bigint(20) DEFAULT '0',
+ `deny_mount` bigint(20) DEFAULT '0',
+ `deny_ender_pearl` bigint(20) DEFAULT '0',
+ `deny_bow` bigint(20) DEFAULT '0',
+ `deny_potion` bigint(20) DEFAULT '0',
+ `deny_raid` bigint(20) DEFAULT '0',
+ `deny_item_frame` bigint(20) DEFAULT '0',
+ `deny_move` bigint(20) DEFAULT '0',
+ `deny_pickup` bigint(20) DEFAULT '0',
+ `deny_fire` bigint(20) DEFAULT '0',
+ `deny_thrown_projectiles` bigint(20) DEFAULT '0',
+ `deny_glowing` bigint(20) DEFAULT '0',
+ `deny_redstone_interaction` bigint(20) DEFAULT '0',
+ `deny_door_interaction` bigint(20) DEFAULT '0',
+ `deny_noteblock_jukebox` bigint(20) DEFAULT '0',
+ `deny_lead` bigint(20) DEFAULT '0',
+ `deny_crop_harvest` bigint(20) DEFAULT '0',
+ `deny_wool_shear` bigint(20) DEFAULT '0',
+ `deny_animal_feeding` bigint(20) DEFAULT '0',
+ `deny_mob_attack` bigint(20) DEFAULT '0',
+ `deny_container` bigint(20) DEFAULT '0',
+ `peace_mode` bigint(20) DEFAULT '0',
+ `peace_mode_duration` bigint(20) DEFAULT '0',
+ `peace_whitelist` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `enforce_game_mode` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `mode_exempt` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `enter_msg` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `leave_msg` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `confiscate_msg` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `warp_x` double DEFAULT '0',
+ `warp_y` double DEFAULT '0',
+ `warp_z` double DEFAULT '0',
+ `warp_yaw` double DEFAULT '0',
+ `warp_pitch` double DEFAULT '0',
+ `warp_world` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `enable_announce` bigint(20) DEFAULT '0',
+ `announce_template` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `txt_content` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `deny_fluid` bigint(20) DEFAULT '0',
+ `clear_all_bad` bigint(20) DEFAULT '0',
+ `is_public_building` bigint(20) DEFAULT '0',
+ `deny_entity_interact` bigint(20) DEFAULT '0',
+ `allow_visitor_teleport` bigint(20) DEFAULT '0',
+ `deny_sign_edit` bigint(20) DEFAULT '0',
+ `deny_spawn_egg` bigint(20) DEFAULT '0',
+ `deny_wax` bigint(20) DEFAULT '0',
+ `bad_effects` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=7072 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eb_area_permissions
+CREATE TABLE IF NOT EXISTS `web_area_permissions` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `land_id` bigint(20) NOT NULL,
+ `land_name` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `player_name` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `role` varchar(500) COLLATE utf8mb4_bin NOT NULL DEFAULT 'visitor',
+ `permissions` varchar(1000) COLLATE utf8mb4_bin DEFAULT '',
+ `granted_at` bigint(20) DEFAULT '0',
+ `expires_at` bigint(20) DEFAULT '0',
+ `synced_at` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `land_id` (`land_id`,`player_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eb_area_shop
+CREATE TABLE IF NOT EXISTS `web_area_shop` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `land_id` bigint(20) NOT NULL,
+ `land_name` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `seller` text COLLATE utf8mb4_bin NOT NULL,
+ `permission` varchar(500) COLLATE utf8mb4_bin DEFAULT 'visitor',
+ `price` bigint(20) DEFAULT '0',
+ `duration` bigint(20) DEFAULT '86400',
+ `status` varchar(500) COLLATE utf8mb4_bin DEFAULT 'active',
+ `buyer` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `bought_at` bigint(20) DEFAULT '0',
+ `created_at` bigint(20) DEFAULT '0',
+ `synced_at` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eb_ip_blacklist
+CREATE TABLE IF NOT EXISTS `web_ip_blacklist` (
+ `ip` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `reason` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `source` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `expire` bigint(20) DEFAULT '0',
+ `banned_at` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`ip`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eb_login_confirmations
+CREATE TABLE IF NOT EXISTS `web_login_confirmations` (
+ `player_name` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `confirmed_at` bigint(20) NOT NULL,
+ `consumed` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`player_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eb_login_requests
+CREATE TABLE IF NOT EXISTS `web_login_requests` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `player_name` text COLLATE utf8mb4_bin NOT NULL,
+ `password` text COLLATE utf8mb4_bin NOT NULL,
+ `request_time` bigint(20) NOT NULL,
+ `status` varchar(500) COLLATE utf8mb4_bin DEFAULT 'pending',
+ `result` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `result_time` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eb_login_verified
+CREATE TABLE IF NOT EXISTS `web_login_verified` (
+ `player_name` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `verified_at` bigint(20) NOT NULL,
+ PRIMARY KEY (`player_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eb_player_bans
+CREATE TABLE IF NOT EXISTS `web_player_bans` (
+ `target` varchar(325) COLLATE utf8mb4_bin NOT NULL,
+ `ban_type` varchar(325) COLLATE utf8mb4_bin NOT NULL DEFAULT 'name',
+ `reason` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `source` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `expire_time` bigint(20) DEFAULT '0',
+ `synced_at` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`target`,`ban_type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eb_plugin_admins
+CREATE TABLE IF NOT EXISTS `web_plugin_admins` (
+ `player_name` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `synced_at` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`player_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eb_register_requests
+CREATE TABLE IF NOT EXISTS `web_register_requests` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `player_name` text COLLATE utf8mb4_bin NOT NULL,
+ `password_hash` text COLLATE utf8mb4_bin NOT NULL,
+ `salt` text COLLATE utf8mb4_bin NOT NULL,
+ `email` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `ip_address` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `created_at` bigint(20) NOT NULL,
+ `status` varchar(500) COLLATE utf8mb4_bin DEFAULT 'pending',
+ `processed_at` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eb_service_providers
+CREATE TABLE IF NOT EXISTS `web_service_providers` (
+ `player_name` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `role` varchar(500) COLLATE utf8mb4_bin DEFAULT 'waiter',
+ `active` bigint(20) DEFAULT '1',
+ `join_time` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`player_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eb_session_log
+CREATE TABLE IF NOT EXISTS `web_session_log` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `player_name` text COLLATE utf8mb4_bin NOT NULL,
+ `ip_address` text COLLATE utf8mb4_bin NOT NULL,
+ `login_time` bigint(20) NOT NULL,
+ `session_token` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ PRIMARY KEY (`id`),
+ KEY `idx_web_session_log_player` (`player_name`(191)),
+ KEY `idx_web_session_log_time` (`login_time`)
+) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eb_transactions
+CREATE TABLE IF NOT EXISTS `web_transactions` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `player_name` text COLLATE utf8mb4_bin NOT NULL,
+ `type` text COLLATE utf8mb4_bin NOT NULL,
+ `amount` bigint(20) NOT NULL,
+ `operator` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `reason` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `detail` varchar(1059) COLLATE utf8mb4_bin DEFAULT '',
+ `status` varchar(500) COLLATE utf8mb4_bin DEFAULT 'pending',
+ `created_at` bigint(20) DEFAULT '0',
+ `processed_at` bigint(20) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=341 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eb_user_group_members
+CREATE TABLE IF NOT EXISTS `web_user_group_members` (
+ `player_name` varchar(325) COLLATE utf8mb4_bin NOT NULL,
+ `group_name` varchar(325) COLLATE utf8mb4_bin NOT NULL,
+ `added_by` varchar(500) COLLATE utf8mb4_bin DEFAULT 'system',
+ `added_time` bigint(20) DEFAULT '0',
+ `expiry_time` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`player_name`,`group_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eb_user_groups
+CREATE TABLE IF NOT EXISTS `web_user_groups` (
+ `group_name` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `display_name` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `display_color` varchar(500) COLLATE utf8mb4_bin DEFAULT '�f',
+ `display_emoji` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `priority` bigint(20) DEFAULT '0',
+ `land_price_per_sqm` bigint(20) DEFAULT '-1',
+ `max_lands` bigint(20) DEFAULT '-1',
+ `max_effects` bigint(20) DEFAULT '5',
+ `max_effect_level` bigint(20) DEFAULT '256',
+ `home_limit` bigint(20) DEFAULT '0',
+ `join_price` bigint(20) DEFAULT '0',
+ `auto_renew` bigint(20) DEFAULT '0',
+ `renew_price` bigint(20) DEFAULT '0',
+ `duration_minutes` bigint(20) DEFAULT '0',
+ `default_perms` varchar(500) COLLATE utf8mb4_bin DEFAULT '{}',
+ `synced_at` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`group_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eblogin_credentials
+CREATE TABLE IF NOT EXISTS `weblogin_credentials` (
+ `player_name` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `password_hash` text COLLATE utf8mb4_bin NOT NULL,
+ `salt` text COLLATE utf8mb4_bin NOT NULL,
+ `temp_password_hash` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `temp_pw_expire` bigint(20) DEFAULT '0',
+ `pushed_at` bigint(20) NOT NULL,
+ PRIMARY KEY (`player_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanjava.eblogin_tokens
+CREATE TABLE IF NOT EXISTS `weblogin_tokens` (
+ `player_name` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `web_token` text COLLATE utf8mb4_bin NOT NULL,
+ `created_at` bigint(20) NOT NULL,
+ `expire_seconds` bigint(20) DEFAULT '600',
+ `registered` bigint(20) DEFAULT '1',
+ PRIMARY KEY (`player_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- ================= aoyuanphp =================
+
+-- aoyuanphp.install_lock
+CREATE TABLE IF NOT EXISTS `_install_lock` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `installed_at` varchar(32) NOT NULL,
+ `ip` varchar(45) NOT NULL DEFAULT '',
+ `mode` varchar(16) NOT NULL DEFAULT 'mysql',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
+
+-- aoyuanphp.dmin_sessions
+CREATE TABLE IF NOT EXISTS `admin_sessions` (
+ `session_id` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `created_at` bigint(20) NOT NULL,
+ `expires_at` bigint(20) NOT NULL,
+ PRIMARY KEY (`session_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.ashier_orders
+CREATE TABLE IF NOT EXISTS `cashier_orders` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `order_no` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `operator_type` varchar(500) COLLATE utf8mb4_bin DEFAULT 'cashier',
+ `operator_name` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `player_name` text COLLATE utf8mb4_bin NOT NULL,
+ `items_detail` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `subtotal` bigint(20) DEFAULT '0',
+ `total_price` bigint(20) DEFAULT '0',
+ `discount_percent` bigint(20) DEFAULT '0',
+ `discount_amount` bigint(20) DEFAULT '0',
+ `settlement` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `payment_method` varchar(500) COLLATE utf8mb4_bin DEFAULT 'bond',
+ `status` varchar(500) COLLATE utf8mb4_bin DEFAULT 'completed',
+ `created_at` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.ashiers
+CREATE TABLE IF NOT EXISTS `cashiers` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `username` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `password_hash` text COLLATE utf8mb4_bin NOT NULL,
+ `salt` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `discount_limit_percent` bigint(20) DEFAULT '0',
+ `created_at` bigint(20) DEFAULT '0',
+ `created_by` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `can_cash` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.nline_snapshots
+CREATE TABLE IF NOT EXISTS `online_snapshots` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `snapshot_time` bigint(20) NOT NULL,
+ `online_count` bigint(20) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `idx_snap_time` (`snapshot_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.ay_orders
+CREATE TABLE IF NOT EXISTS `pay_orders` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `out_trade_no` varchar(500) COLLATE utf8mb4_bin DEFAULT NULL,
+ `trade_no` text COLLATE utf8mb4_bin,
+ `player_name` text COLLATE utf8mb4_bin NOT NULL,
+ `tier_id` bigint(20) DEFAULT '0',
+ `money` text COLLATE utf8mb4_bin,
+ `bond_amount` bigint(20) DEFAULT '0',
+ `status` varchar(500) COLLATE utf8mb4_bin DEFAULT 'created',
+ `name` text COLLATE utf8mb4_bin,
+ `platform_sign` text COLLATE utf8mb4_bin,
+ `created_at` bigint(20) DEFAULT NULL,
+ `paid_at` bigint(20) DEFAULT NULL,
+ `submit_params` text COLLATE utf8mb4_bin,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.ending_player_validations
+CREATE TABLE IF NOT EXISTS `pending_player_validations` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `player_name` text COLLATE utf8mb4_bin NOT NULL,
+ `request_type` varchar(500) COLLATE utf8mb4_bin DEFAULT 'general',
+ `request_data` varchar(500) COLLATE utf8mb4_bin DEFAULT '{}',
+ `status` varchar(500) COLLATE utf8mb4_bin DEFAULT 'pending',
+ `created_at` bigint(20) DEFAULT '0',
+ `validated_at` bigint(20) DEFAULT '0',
+ `result` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.eb_bond_records
+CREATE TABLE IF NOT EXISTS `web_bond_records` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `player_name` text COLLATE utf8mb4_bin NOT NULL,
+ `amount` bigint(20) DEFAULT '0',
+ `record_type` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `recorded_by` varchar(500) COLLATE utf8mb4_bin DEFAULT 'system',
+ `description` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `created_at` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.eb_group_renew
+CREATE TABLE IF NOT EXISTS `web_group_renew` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `player_name` text COLLATE utf8mb4_bin NOT NULL,
+ `group_name` text COLLATE utf8mb4_bin NOT NULL,
+ `renew_price` bigint(20) DEFAULT '0',
+ `duration_minutes` bigint(20) DEFAULT '0',
+ `req_id` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `status` varchar(500) COLLATE utf8mb4_bin DEFAULT 'pending',
+ `created_at` bigint(20) DEFAULT '0',
+ `processed_at` bigint(20) DEFAULT '0',
+ `remark` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.eb_item_sn
+CREATE TABLE IF NOT EXISTS `web_item_sn` (
+ `sn` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `item_type` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `owner` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `status` varchar(500) COLLATE utf8mb4_bin DEFAULT 'active',
+ `issue_time` bigint(20) DEFAULT '0',
+ `loc_type` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `loc_player` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `loc_world` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `loc_x` bigint(20) DEFAULT '0',
+ `loc_y` bigint(20) DEFAULT '0',
+ `loc_z` bigint(20) DEFAULT '0',
+ `container_type` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `in_land` bigint(20) DEFAULT '0',
+ `land_name` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `last_seen` bigint(20) DEFAULT '0',
+ `cancel_time` bigint(20) DEFAULT '0',
+ `lost_count` bigint(20) DEFAULT '0',
+ `lost_state` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `remark` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `bind_reason` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `bind_time` bigint(20) DEFAULT '0',
+ `updated_at` bigint(20) DEFAULT '0',
+ `detached_at` bigint(20) DEFAULT '0',
+ `own_chest` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`sn`),
+ KEY `idx_web_item_sn_owner` (`owner`(191),`item_type`(191))
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.eb_land_owner_changes
+CREATE TABLE IF NOT EXISTS `web_land_owner_changes` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `land_id` bigint(20) NOT NULL,
+ `land_name` text COLLATE utf8mb4_bin NOT NULL,
+ `old_owner` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `new_owner` text COLLATE utf8mb4_bin NOT NULL,
+ `created_at` bigint(20) DEFAULT '0',
+ `synced` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.eb_land_transfers
+CREATE TABLE IF NOT EXISTS `web_land_transfers` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `land_name` text COLLATE utf8mb4_bin NOT NULL,
+ `old_owner` text COLLATE utf8mb4_bin NOT NULL,
+ `new_owner` text COLLATE utf8mb4_bin NOT NULL,
+ `status` varchar(500) COLLATE utf8mb4_bin DEFAULT 'pending',
+ `created_at` bigint(20) DEFAULT '0',
+ `completed_at` bigint(20) DEFAULT '0',
+ `expires_at` bigint(20) DEFAULT '0',
+ `cooldown_until` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.eb_sn_commands
+CREATE TABLE IF NOT EXISTS `web_sn_commands` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `cmd` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `sn` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `player` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `item_type` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `reason` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `is_force` bigint(20) DEFAULT '0',
+ `link_type` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `link_id` bigint(20) DEFAULT '0',
+ `status` varchar(500) COLLATE utf8mb4_bin DEFAULT 'pending',
+ `result` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `created_at` bigint(20) DEFAULT '0',
+ `sent_at` bigint(20) DEFAULT '0',
+ `done_at` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `idx_web_sn_cmd_status` (`status`,`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.eb_sn_cooldown
+CREATE TABLE IF NOT EXISTS `web_sn_cooldown` (
+ `player` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `until` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`player`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.eb_sn_event
+CREATE TABLE IF NOT EXISTS `web_sn_event` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `action` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `sn` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `player` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `detail` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `created_at` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.eb_sn_held
+CREATE TABLE IF NOT EXISTS `web_sn_held` (
+ `sn` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `player` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `holder` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `owner` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `item_type` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `held` bigint(20) DEFAULT '0',
+ `time` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`sn`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.eb_sn_log
+CREATE TABLE IF NOT EXISTS `web_sn_log` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `sn` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `item_type` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `action` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `player` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `detail` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `time` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=372 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.eb_sn_lost
+CREATE TABLE IF NOT EXISTS `web_sn_lost` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `sn` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `player` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `reason` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `status` varchar(500) COLLATE utf8mb4_bin DEFAULT 'checking',
+ `result` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `report_count` bigint(20) DEFAULT '1',
+ `created_at` bigint(20) DEFAULT '0',
+ `handled_at` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `idx_web_sn_lost_sn` (`sn`,`created_at`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.eb_sn_stock
+CREATE TABLE IF NOT EXISTS `web_sn_stock` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `sn` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `item_type` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `action` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `world` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `x` bigint(20) DEFAULT '0',
+ `y` bigint(20) DEFAULT '0',
+ `z` bigint(20) DEFAULT '0',
+ `container_type` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `in_land` bigint(20) DEFAULT '0',
+ `land_name` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `player` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `time` bigint(20) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `idx_web_sn_stock_sn` (`sn`,`time`)
+) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.eb_ticket_replies
+CREATE TABLE IF NOT EXISTS `web_ticket_replies` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `ticket_id` bigint(20) NOT NULL,
+ `sender` text COLLATE utf8mb4_bin NOT NULL,
+ `role` varchar(500) COLLATE utf8mb4_bin DEFAULT 'user',
+ `message` text COLLATE utf8mb4_bin NOT NULL,
+ `created_at` bigint(20) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `idx_web_ticket_replies_ticket` (`ticket_id`),
+ KEY `idx_wtr_ticket` (`ticket_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.eb_tickets
+CREATE TABLE IF NOT EXISTS `web_tickets` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `type` text COLLATE utf8mb4_bin NOT NULL,
+ `status` varchar(500) COLLATE utf8mb4_bin DEFAULT 'submitted',
+ `requester` text COLLATE utf8mb4_bin NOT NULL,
+ `assigned_to` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `title` text COLLATE utf8mb4_bin NOT NULL,
+ `description` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `images` varchar(500) COLLATE utf8mb4_bin DEFAULT '[]',
+ `reject_reason` varchar(500) COLLATE utf8mb4_bin DEFAULT '',
+ `created_at` bigint(20) NOT NULL,
+ `updated_at` bigint(20) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `idx_web_tickets_requester` (`requester`(191)),
+ KEY `idx_web_tickets_status` (`status`),
+ KEY `idx_web_tickets_assigned` (`assigned_to`),
+ KEY `idx_wt_requester` (`requester`(191)),
+ KEY `idx_wt_status` (`status`),
+ KEY `idx_wt_assigned` (`assigned_to`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+
+-- aoyuanphp.eb_twofa_qr
+CREATE TABLE IF NOT EXISTS `web_twofa_qr` (
+ `token` varchar(500) COLLATE utf8mb4_bin NOT NULL,
+ `player` text COLLATE utf8mb4_bin NOT NULL,
+ `otpauth` text COLLATE utf8mb4_bin NOT NULL,
+ `expires_at` bigint(20) NOT NULL,
+ PRIMARY KEY (`token`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;