新增数据库相关语句脚本

This commit is contained in:
2026-04-14 12:02:21 +08:00
parent a7c2448207
commit 32f0d87499
9 changed files with 1680 additions and 0 deletions

View File

@@ -1453,3 +1453,81 @@ echo "Done"
| 清空 api/logs 今天日志 | `ssh root@47.98.203.17 "docker exec youle-api truncate -s 0 /var/www/html/logs/$(Get-Date -Format 'yyyy-MM-dd').log"` | | 清空 api/logs 今天日志 | `ssh root@47.98.203.17 "docker exec youle-api truncate -s 0 /var/www/html/logs/$(Get-Date -Format 'yyyy-MM-dd').log"` |
| 清空同步调试日志 | `ssh root@47.98.203.17 "docker exec youle-dlweb truncate -s 0 /var/www/html/api/ext/debug/synchronize/$(Get-Date -Format 'yyyy-MM-dd').log"` | | 清空同步调试日志 | `ssh root@47.98.203.17 "docker exec youle-dlweb truncate -s 0 /var/www/html/api/ext/debug/synchronize/$(Get-Date -Format 'yyyy-MM-dd').log"` |
| 删除全部调试日志 | `ssh root@47.98.203.17 "docker exec youle-dlweb sh -c 'rm -f /var/www/html/api/ext/debug/synchronize/*.log /var/www/html/api/ext/debug/SynchronizeReportData/*.log /var/www/html/api/ext/debug/autotask/*.log'"` | | 删除全部调试日志 | `ssh root@47.98.203.17 "docker exec youle-dlweb sh -c 'rm -f /var/www/html/api/ext/debug/synchronize/*.log /var/www/html/api/ext/debug/SynchronizeReportData/*.log /var/www/html/api/ext/debug/autotask/*.log'"` |
---
## 数据库远程管理Navicat SSH 隧道)
> RDS 白名单仅开放服务器 `47.98.203.17`,任何设备需通过 SSH 隧道访问数据库,无需开放公网 IP安全可控。
### 原理
```
本机 Navicat ──SSH加密隧道──▶ 47.98.203.17 ──内网──▶ RDS MySQL
```
### Navicat 连接配置(以 agent_db 为例)
#### 第一步SSH 标签页
| 字段 | 值 |
|------|----|
| 使用 SSH 通道 | ✅ 勾选 |
| 主机 | `47.98.203.17` |
| 端口 | `22` |
| 用户名 | `root` |
| 验证方法 | **公钥** |
| 私钥文件 | `C:\Users\{你的用户名}\.ssh\id_ed25519` |
> **注意**:当前服务器已授权的密钥为 `id_ed25519`(标识 `youle-deploy`),选错密钥会导致认证失败。
#### 第二步:常规标签页
| 字段 | 值 |
|------|----|
| 主机名/IP | `rm-bp1btyuwq77591x0jpo.mysql.rds.aliyuncs.com` |
| 端口 | `3306` |
| 用户名 | `games` |
| 密码 | `Games0791!!` |
| 数据库 | `agent_db`(可留空后再选) |
> ⚠️ **常见错误2013 错误)**:常规标签的主机名填了 `127.0.0.1` 或 `localhost`,会连接到服务器本机而非 RDS导致握手失败。必须填 RDS 完整域名。
#### 数据库与 RDS 实例对应关系
| 数据库 | RDS 主机 |
|--------|---------|
| `agent_db``agent_db_temp``game_field``youlehudong` | `rm-bp1btyuwq77591x0jpo.mysql.rds.aliyuncs.com` |
| `game_db``grade_db` | `rm-bp1749tfxu2rpq670lo.mysql.rds.aliyuncs.com` |
### 新设备接入
在新设备上执行以下步骤:
```powershell
# 1. 生成密钥对(如没有)
ssh-keygen -t ed25519 -C "设备标识备注"
# 2. 查看公钥内容
Get-Content "$env:USERPROFILE\.ssh\id_ed25519.pub"
```
将公钥内容追加到服务器:
```bash
ssh root@47.98.203.17 "echo 'ssh-ed25519 AAAA...你的公钥内容...' >> ~/.ssh/authorized_keys"
```
之后 Navicat SSH 标签填写该设备的私钥路径即可。
### 命令行手动隧道(可选)
如需在命令行工具(如 mysql CLI中使用可手动建立隧道
```bash
# 建立隧道(保持此终端运行,另开终端连接)
ssh -L 3307:rm-bp1btyuwq77591x0jpo.mysql.rds.aliyuncs.com:3306 root@47.98.203.17 -N
# 另开终端,连接本地转发端口
mysql -h 127.0.0.1 -P 3307 -u games -p agent_db
```

View File

@@ -0,0 +1,21 @@
@echo off
chcp 65001 >nul
cd /d "%~dp0"
where node >nul 2>&1
if %errorlevel% neq 0 (
echo [错误] 未找到 Node.js请先安装 Node.js
pause
exit /b 1
)
if not exist "node_modules" (
echo [初始化] 首次运行,正在安装依赖...
npm install
echo.
)
node export_summary.js %*
echo.
pause

View File

@@ -0,0 +1,260 @@
#!/usr/bin/env node
/**
* export_summary.js
* 通过 SSH 隧道连接 RDS导出转卡汇总报表为 Excel
*
* 用法:
* node export_summary.js # 交互式输入年月
* node export_summary.js -y 2026 -m 3 # 直接指定年月
*
* 依赖: npm install mysql2 exceljs tunnel-ssh
*/
"use strict";
const path = require("path");
const fs = require("fs");
const readline = require("readline");
const { createTunnel } = require("tunnel-ssh");
const mysql = require("mysql2/promise");
const ExcelJS = require("exceljs");
// ─────────────────────────────────────────────
// SSH 配置(跳板机)
// ─────────────────────────────────────────────
const SSH_CONFIG = {
host: "47.98.203.17",
port: 22,
username: "root",
privateKey: fs.readFileSync(
path.join(process.env.USERPROFILE || process.env.HOME, ".ssh", "id_ed25519")
),
};
// ─────────────────────────────────────────────
// 数据库配置RDS通过 SSH 隧道访问)
// ─────────────────────────────────────────────
const DB_HOST = "rm-bp1btyuwq77591x0jpo.mysql.rds.aliyuncs.com";
const DB_PORT = 3306;
const DB_CONFIG = {
host: "127.0.0.1",
port: 13306, // 本地隧道转发端口
user: "games",
password: "Games0791!!",
database: "agent_db",
charset: "utf8mb4",
connectTimeout: 15000,
};
// ─────────────────────────────────────────────
// 默认业务参数
// ─────────────────────────────────────────────
const DEFAULT_FROM_SALES = 10437216;
const DEFAULT_AGENT_ID = "veRa0qrBf0df2K1G4de2tgfmVxB2jxpv";
// ─────────────────────────────────────────────
// Excel 样式
// ─────────────────────────────────────────────
const COLOR_HEADER = "2E6DA4";
const COLOR_TOTAL_ROW = "FFF2CC";
const COLOR_ALT_ROW = "EBF3FB";
function headerStyle() {
return {
font: { bold: true, color: { argb: "FFFFFFFF" }, size: 11 },
fill: { type: "pattern", pattern: "solid", fgColor: { argb: "FF" + COLOR_HEADER } },
alignment: { horizontal: "center", vertical: "middle", wrapText: true },
border: thinBorder(),
};
}
function thinBorder() {
const s = { style: "thin", color: { argb: "FFBFBFBF" } };
return { left: s, right: s, top: s, bottom: s };
}
function applyStyle(cell, style) {
Object.assign(cell, style);
}
// ─────────────────────────────────────────────
// 汇总 SQL不含 ROLLUP合计行由 JS 端计算)
// ─────────────────────────────────────────────
const SQL_SUMMARY = `
SELECT
t.satr_salesid AS agentId,
MAX(su.saus_nickname) AS agentName,
COUNT(t.idx) AS transferCount,
SUM(t.satr_amount) AS transferTotal
FROM
sales_transferbill t
LEFT JOIN sales_user su
ON t.satr_agentid = su.saus_agentid
AND t.channel_id = su.saus_channelid
AND t.satr_salesid = su.saus_salesid
WHERE
t.satr_agentid = ?
AND t.from_sales = ?
AND YEAR(t.satr_transfertime) = ?
AND MONTH(t.satr_transfertime) = ?
GROUP BY t.satr_salesid
ORDER BY SUM(t.satr_amount) DESC
`;
// ─────────────────────────────────────────────
// 建立 SSH 隧道tunnel-ssh v5
// ─────────────────────────────────────────────
async function openTunnel() {
const tunnelOptions = { autoClose: true };
const serverOptions = { port: DB_CONFIG.port };
const sshOptions = SSH_CONFIG;
const forwardOptions = {
srcAddr: "127.0.0.1",
srcPort: DB_CONFIG.port,
dstAddr: DB_HOST,
dstPort: DB_PORT,
};
const [server] = await createTunnel(tunnelOptions, serverOptions, sshOptions, forwardOptions);
return server;
}
// ─────────────────────────────────────────────
// 写 Excel
// ─────────────────────────────────────────────
async function writeExcel(rows, year, month, fromSales, outputPath) {
const wb = new ExcelJS.Workbook();
const ws = wb.addWorksheet("转卡汇总");
ws.properties.tabColor = { argb: "FF" + COLOR_HEADER };
ws.views = [{ state: "frozen", ySplit: 1 }];
// 列定义
const columns = [
{ header: "代理ID", key: "agentId", width: 14 },
{ header: "代理昵称", key: "agentName", width: 20 },
{ header: "转卡次数", key: "transferCount", width: 12 },
{ header: "转卡总量", key: "transferTotal", width: 12 },
];
ws.columns = columns;
// 表头样式
const hs = headerStyle();
ws.getRow(1).height = 22;
ws.getRow(1).eachCell(cell => applyStyle(cell, hs));
// 数据行
let totalCount = 0, totalAmount = 0;
rows.forEach((row, idx) => {
const r = ws.addRow({
agentId: row.agentId,
agentName: row.agentName || "—",
transferCount: Number(row.transferCount),
transferTotal: Number(row.transferTotal),
});
r.height = 20;
const isAlt = (idx % 2 === 1);
r.eachCell(cell => {
cell.border = thinBorder();
cell.alignment = { horizontal: "center", vertical: "middle" };
if (isAlt) cell.fill = { type: "pattern", pattern: "solid", fgColor: { argb: "FF" + COLOR_ALT_ROW } };
});
totalCount += Number(row.transferCount);
totalAmount += Number(row.transferTotal);
});
// 合计行
const totalRow = ws.addRow({
agentId: "★ 合计",
agentName: "—",
transferCount: totalCount,
transferTotal: totalAmount,
});
totalRow.height = 22;
totalRow.eachCell(cell => {
cell.border = thinBorder();
cell.font = { bold: true, size: 11 };
cell.alignment = { horizontal: "center", vertical: "middle" };
cell.fill = { type: "pattern", pattern: "solid", fgColor: { argb: "FF" + COLOR_TOTAL_ROW } };
});
await wb.xlsx.writeFile(outputPath);
}
// ─────────────────────────────────────────────
// 交互式询问
// ─────────────────────────────────────────────
async function prompt(question) {
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
return new Promise(resolve => rl.question(question, ans => { rl.close(); resolve(ans.trim()); }));
}
async function getParams() {
const args = process.argv.slice(2);
const get = (flag) => { const i = args.indexOf(flag); return i !== -1 ? args[i + 1] : null; };
const now = new Date();
let year = get("-y") ? parseInt(get("-y")) : null;
let month = get("-m") ? parseInt(get("-m")) : null;
let fromSales = get("-f") ? parseInt(get("-f")) : DEFAULT_FROM_SALES;
let agentId = get("-a") ? get("-a") : DEFAULT_AGENT_ID;
if (!year) {
const raw = await prompt(`请输入年份(直接回车使用 ${now.getFullYear()}: `);
year = raw ? parseInt(raw) : now.getFullYear();
}
if (!month) {
const raw = await prompt(`请输入月份 1-12直接回车使用 ${now.getMonth() + 1}: `);
month = raw ? parseInt(raw) : now.getMonth() + 1;
}
if (month < 1 || month > 12) { console.error("[错误] 月份必须在 1-12 之间"); process.exit(1); }
return { year, month, fromSales, agentId };
}
// ─────────────────────────────────────────────
// 主流程
// ─────────────────────────────────────────────
async function main() {
const { year, month, fromSales, agentId } = await getParams();
const outputDir = path.join(__dirname, "output");
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
const outputFile = path.join(outputDir, `转卡汇总_${year}${String(month).padStart(2, "0")}_${fromSales}.xlsx`);
console.log("=".repeat(55));
console.log(` 导出参数: ${year}${String(month).padStart(2, "0")}`);
console.log(` 发卡代理: ${fromSales}`);
console.log(` 代理商ID: ${agentId}`);
console.log(` 输出文件: ${outputFile}`);
console.log("=".repeat(55));
console.log("\n[SSH] 正在建立隧道 47.98.203.17 → RDS ...");
const tunnel = await openTunnel();
console.log("[SSH] 隧道建立成功");
let conn;
try {
console.log("[数据库] 正在连接 ...");
conn = await mysql.createConnection(DB_CONFIG);
console.log("[数据库] 连接成功");
console.log(`[查询] 执行汇总查询 (${year}${String(month).padStart(2, "0")}月) ...`);
const [rows] = await conn.execute(SQL_SUMMARY, [agentId, fromSales, year, month]);
console.log(`${rows.length} 条代理记录`);
if (rows.length === 0) {
console.log("[提示] 查询结果为空,未生成文件");
} else {
await writeExcel(rows, year, month, fromSales, outputFile);
console.log(`\n[完成] 文件已保存: ${outputFile}`);
}
} finally {
if (conn) await conn.end();
tunnel.close();
console.log("[SSH] 隧道已关闭");
}
}
main().catch(err => {
console.error("\n[错误]", err.message);
process.exit(1);
});

1118
codes/games/sql/export/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
{
"name": "youle-sql-export",
"version": "1.0.0",
"description": "转卡报表导出工具",
"private": true,
"scripts": {
"export": "node export_summary.js"
},
"dependencies": {
"exceljs": "^4.4.0",
"mysql2": "^3.22.0",
"tunnel-ssh": "^5.2.0"
}
}

View File

@@ -0,0 +1,49 @@
-- ============================================================
-- 脚本名称: room_players.sql
-- 功 能: 查询指定房间号的所有历史对局及每局玩家信息
-- 数据库 : grade_db + game_db跨库同一RDS实例
-- RDS: rm-bp1749tfxu2rpq670lo
-- 说明 :
-- 同一房间号可能在不同时间出现多次,结果按开房时间分组。
-- 输出按「开房时间 DESC → 得分 DESC」排序
-- 相同开房时间的行即为同一场对局,可在结果中直接区分。
-- ============================================================
-- ============================================================
-- 【参数设置】 ← 修改这里
-- ============================================================
SET @p_agentid = 'veRa0qrBf0df2K1G4de2tgfmVxB2jxpv'; -- 代理商ID
SET @p_roomcode = 0; -- 房间号plgr_roomcode
-- ============================================================
-- 【查询】指定房间号的所有历史对局玩家明细
-- 结果按开房时间倒序排列,同一开房时间为同一场对局
-- ============================================================
SELECT
DATE_FORMAT(pg.plgr_createtime, '%Y-%m-%d %H:%i:%s') AS '创建时间',
DATE_FORMAT(pg.plgr_overtime, '%Y-%m-%d %H:%i:%s') AS '结束时间',
pg.plgr_roomcode AS '房间号',
pg.plgr_playerid AS '玩家ID',
p.play_nickname AS '玩家昵称',
pg.plgr_score AS '得分',
pg.plgr_winner AS '是否大赢家1=是)',
pg.plgr_deductcard AS '扣除房卡',
DATE_FORMAT(pg.plgr_makewartime, '%Y-%m-%d %H:%i:%s') AS '开战时间',
pg.plgr_roomtype AS '房间类型',
pg.plgr_gameid AS '游戏ID',
g.game_name AS '游戏名称'
FROM
grade_db.player_grade pg
LEFT JOIN game_db.player p
ON pg.plgr_agentid = p.play_agentid
AND pg.plgr_playerid = p.play_playerid
LEFT JOIN game_db.game g
ON pg.plgr_gameid = g.game_gameid
WHERE
pg.plgr_agentid = @p_agentid
AND pg.plgr_roomcode = @p_roomcode
ORDER BY
pg.plgr_createtime DESC,
pg.plgr_score DESC;

View File

@@ -0,0 +1,46 @@
-- ============================================================
-- 脚本名称: transfer_detail.sql
-- 功 能: 指定年月的转卡明细流水(每笔转卡一行)
-- 数据库 : agent_db
-- 用 法:
-- 1. 修改下方【参数设置】中的变量值
-- 2. 在 Navicat 中执行全部脚本
-- 3. 结果集右键 → Export → Excel 可导出表格
-- ============================================================
-- ============================================================
-- 【参数设置】 ← 修改这里
-- ============================================================
SET @p_year = 2026; -- 年份
SET @p_month = 3; -- 月份1-12
SET @p_from_sales = 10437216; -- 发卡来源代理ID上级代理
SET @p_agent_id = 'veRa0qrBf0df2K1G4de2tgfmVxB2jxpv'; -- 代理商ID
-- ============================================================
-- 【明细报表】每一笔转卡流水记录,按转卡时间升序排列
-- ============================================================
SELECT
t.from_sales AS '发卡代理ID',
su_from.saus_nickname AS '发卡代理昵称',
t.satr_salesid AS '接收代理ID',
su_to.saus_nickname AS '接收代理昵称',
t.satr_amount AS '转卡数量',
DATE_FORMAT(t.satr_transfertime, '%Y-%m-%d %H:%i:%s') AS '转卡时间'
FROM
sales_transferbill t
LEFT JOIN sales_user su_to
ON t.satr_agentid = su_to.saus_agentid
AND t.channel_id = su_to.saus_channelid
AND t.satr_salesid = su_to.saus_salesid
LEFT JOIN sales_user su_from
ON t.satr_agentid = su_from.saus_agentid
AND t.from_sales = su_from.saus_salesid
WHERE
t.satr_agentid = @p_agent_id
AND t.from_sales = @p_from_sales
AND YEAR(t.satr_transfertime) = @p_year
AND MONTH(t.satr_transfertime) = @p_month
ORDER BY
t.satr_transfertime ASC;

View File

@@ -0,0 +1,49 @@
-- ============================================================
-- 脚本名称: transfer_summary.sql
-- 功 能: 指定年月的转卡汇总报表(按下级代理分组,含合计行)
-- 数据库 : agent_db
-- 用 法:
-- 1. 修改下方【参数设置】中的变量值
-- 2. 在 Navicat 中执行全部脚本
-- 3. 结果集右键 → Export → Excel 可导出表格
-- ============================================================
-- ============================================================
-- 【参数设置】 ← 修改这里
-- ============================================================
SET @p_year = 2026; -- 年份
SET @p_month = 3; -- 月份1-12
SET @p_from_sales = 10437216; -- 发卡来源代理ID上级代理
SET @p_agent_id = 'veRa0qrBf0df2K1G4de2tgfmVxB2jxpv'; -- 代理商ID
-- ============================================================
-- 【汇总报表】按下级代理分组,最后一行为转卡总计
-- ============================================================
SELECT
IF(
GROUPING(t.satr_salesid),
'★ 合计',
CAST(t.satr_salesid AS CHAR)
) AS '代理ID',
IF(
GROUPING(t.satr_salesid),
'',
MAX(su.saus_nickname)
) AS '代理昵称',
COUNT(t.idx) AS '转卡次数',
SUM(t.satr_amount) AS '转卡总量'
FROM
sales_transferbill t
LEFT JOIN sales_user su
ON t.satr_agentid = su.saus_agentid
AND t.channel_id = su.saus_channelid
AND t.satr_salesid = su.saus_salesid
WHERE
t.satr_agentid = @p_agent_id
AND t.from_sales = @p_from_sales
AND YEAR(t.satr_transfertime) = @p_year
AND MONTH(t.satr_transfertime) = @p_month
GROUP BY
t.satr_salesid WITH ROLLUP;

View File

@@ -0,0 +1,45 @@
-- ============================================================
-- 脚本名称: update_player_roomcard.sql
-- 功 能: 更新指定玩家的房卡数量
-- 数据库 : game_dbRDS: rm-bp1749tfxu2rpq670lo
-- 用 法:
-- 1. 修改下方【参数设置】中的变量值
-- 2. 先执行【查询确认】,核对目标玩家信息无误
-- 3. 确认无误后再执行【执行更新】
-- ============================================================
-- ============================================================
-- 【参数设置】 ← 修改这里
-- ============================================================
SET @p_agentid = 'veRa0qrBf0df2K1G4de2tgfmVxB2jxpv'; -- 代理商ID
SET @p_playerid = 0; -- 玩家IDplay_playerid
SET @p_roomcard = 0; -- 要设置的房卡数量
-- ============================================================
-- 【第一步:查询确认】执行后确认玩家信息正确再继续
-- ============================================================
SELECT
play_agentid AS '代理商ID',
play_playerid AS '玩家ID',
play_nickname AS '玩家昵称',
play_roomcard AS '当前房卡数',
@p_roomcard AS '更新后房卡数'
FROM
player
WHERE
play_agentid = @p_agentid
AND play_playerid = @p_playerid;
-- ============================================================
-- 【第二步:执行更新】确认玩家信息无误后执行此段
-- ============================================================
UPDATE player
SET play_roomcard = @p_roomcard
WHERE play_agentid = @p_agentid
AND play_playerid = @p_playerid;
-- 输出影响行数1 = 更新成功0 = 未找到对应玩家)
SELECT ROW_COUNT() AS '影响行数1=成功0=未找到玩家)';