159 lines
6.9 KiB
JavaScript
159 lines
6.9 KiB
JavaScript
import http from "node:http";
|
|
import { execFile } from "node:child_process";
|
|
import { readFile } from "node:fs/promises";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const pagePaths = new Map([
|
|
["/", join(__dirname, "baishun-game-test.html")],
|
|
["/baishun-game-test.html", join(__dirname, "baishun-game-test.html")],
|
|
["/lucky-gift-test.html", join(__dirname, "lucky-gift-test.html")],
|
|
]);
|
|
const port = Number(process.env.PORT || 8787);
|
|
const upstreamBase = (process.env.BAISHUN_API_BASE || "https://api-test.global-interaction.com").replace(/\/+$/, "");
|
|
|
|
function readBody(req) {
|
|
return new Promise((resolve, reject) => {
|
|
const chunks = [];
|
|
req.on("data", (chunk) => chunks.push(chunk));
|
|
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
req.on("error", reject);
|
|
});
|
|
}
|
|
|
|
function copyProxyHeaders(req) {
|
|
const headers = new Headers();
|
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
if (!value) continue;
|
|
const lower = key.toLowerCase();
|
|
if (["host", "origin", "referer", "connection", "content-length", "accept-encoding"].includes(lower)) continue;
|
|
headers.set(key, Array.isArray(value) ? value.join(", ") : value);
|
|
}
|
|
headers.set("Accept-Encoding", "identity");
|
|
return headers;
|
|
}
|
|
|
|
async function proxyApi(req, res) {
|
|
const targetUrl = `${upstreamBase}${req.url}`;
|
|
const body = req.method === "GET" || req.method === "HEAD" ? undefined : await readBody(req);
|
|
const upstream = await fetch(targetUrl, {
|
|
method: req.method,
|
|
headers: copyProxyHeaders(req),
|
|
body,
|
|
redirect: "manual",
|
|
});
|
|
const responseBody = Buffer.from(await upstream.arrayBuffer());
|
|
res.writeHead(upstream.status, {
|
|
"Content-Type": upstream.headers.get("content-type") || "application/octet-stream",
|
|
"Cache-Control": "no-store",
|
|
});
|
|
res.end(responseBody);
|
|
}
|
|
|
|
function sqlString(value) {
|
|
return `'${String(value ?? "").replaceAll("\\", "\\\\").replaceAll("'", "''")}'`;
|
|
}
|
|
|
|
function runMysqlJSON(sql) {
|
|
return new Promise((resolve, reject) => {
|
|
execFile(
|
|
"docker",
|
|
["exec", "hyapp-mysql", "mysql", "-uroot", "-proot", "--batch", "--raw", "--skip-column-names", "-e", sql],
|
|
{ maxBuffer: 1024 * 1024 },
|
|
(err, stdout, stderr) => {
|
|
if (err) {
|
|
reject(new Error((stderr || err.message).trim()));
|
|
return;
|
|
}
|
|
const raw = stdout.trim();
|
|
if (!raw) {
|
|
resolve({});
|
|
return;
|
|
}
|
|
try {
|
|
resolve(JSON.parse(raw.split(/\r?\n/).at(-1)));
|
|
} catch {
|
|
reject(new Error(`mysql json parse failed: ${raw}`));
|
|
}
|
|
},
|
|
);
|
|
});
|
|
}
|
|
|
|
async function luckyGiftStats(req, res, url) {
|
|
const appCode = url.searchParams.get("app_code") || "lalu";
|
|
const userID = url.searchParams.get("user_id") || "";
|
|
const poolID = url.searchParams.get("pool_id") || "";
|
|
if (!/^\d+$/.test(userID)) {
|
|
res.writeHead(400, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" });
|
|
res.end(JSON.stringify({ code: "INVALID_ARGUMENT", message: "user_id is required" }));
|
|
return;
|
|
}
|
|
const appSQL = sqlString(appCode);
|
|
const userSQL = sqlString(userID);
|
|
const poolSQL = sqlString(poolID);
|
|
const poolFilter = poolID ? `AND pool_id = ${poolSQL}` : "";
|
|
const statePoolFilter = poolID ? `AND gift_id = ${poolSQL}` : "";
|
|
const sql = `
|
|
SELECT JSON_OBJECT(
|
|
'app_code', ${appSQL},
|
|
'user_id', CAST(${userSQL} AS UNSIGNED),
|
|
'pool_id', ${poolSQL},
|
|
'draw_count', COALESCE((SELECT COUNT(*) FROM hyapp_activity.lucky_draw_records WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${poolFilter}), 0),
|
|
'win_count', COALESCE((SELECT COUNT(*) FROM hyapp_activity.lucky_draw_records WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${poolFilter} AND effective_reward_coins > 0), 0),
|
|
'granted_count', COALESCE((SELECT COUNT(*) FROM hyapp_activity.lucky_draw_records WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${poolFilter} AND reward_status = 'granted'), 0),
|
|
'coin_spent', COALESCE((SELECT SUM(coin_spent) FROM hyapp_activity.lucky_draw_records WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${poolFilter}), 0),
|
|
'base_reward_coins', COALESCE((SELECT SUM(base_reward_coins) FROM hyapp_activity.lucky_draw_records WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${poolFilter}), 0),
|
|
'effective_reward_coins', COALESCE((SELECT SUM(effective_reward_coins) FROM hyapp_activity.lucky_draw_records WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${poolFilter}), 0),
|
|
'state_paid_draws', COALESCE((SELECT SUM(paid_draws) FROM hyapp_activity.lucky_user_states WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${statePoolFilter}), 0),
|
|
'state_cumulative_wager_coins', COALESCE((SELECT SUM(cumulative_wager_coins) FROM hyapp_activity.lucky_user_states WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${statePoolFilter}), 0),
|
|
'state_equivalent_draws', COALESCE((SELECT MAX(equivalent_draws) FROM hyapp_activity.lucky_user_states WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${statePoolFilter}), 0),
|
|
'loss_streak', COALESCE((SELECT MAX(loss_streak) FROM hyapp_activity.lucky_user_states WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${statePoolFilter}), 0)
|
|
)
|
|
`;
|
|
const data = await runMysqlJSON(sql);
|
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" });
|
|
res.end(JSON.stringify({ code: "OK", data }));
|
|
}
|
|
|
|
const server = http.createServer(async (req, res) => {
|
|
try {
|
|
if (req.method === "OPTIONS") {
|
|
res.writeHead(204);
|
|
res.end();
|
|
return;
|
|
}
|
|
const url = new URL(req.url || "/", `http://${req.headers.host || "127.0.0.1"}`);
|
|
const pagePath = pagePaths.get(url.pathname);
|
|
if (pagePath) {
|
|
const html = await readFile(pagePath);
|
|
res.writeHead(200, {
|
|
"Content-Type": "text/html; charset=utf-8",
|
|
"Cache-Control": "no-store",
|
|
});
|
|
res.end(html);
|
|
return;
|
|
}
|
|
if (req.url?.startsWith("/api/")) {
|
|
await proxyApi(req, res);
|
|
return;
|
|
}
|
|
if (url.pathname === "/__local/lucky-gift/stats") {
|
|
await luckyGiftStats(req, res, url);
|
|
return;
|
|
}
|
|
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
res.end("not found");
|
|
} catch (err) {
|
|
res.writeHead(502, { "Content-Type": "text/plain; charset=utf-8" });
|
|
res.end(err instanceof Error ? err.message : String(err));
|
|
}
|
|
});
|
|
|
|
server.listen(port, "127.0.0.1", () => {
|
|
console.log(`Baishun game test page: http://127.0.0.1:${port}/baishun-game-test.html`);
|
|
console.log(`Lucky gift test page: http://127.0.0.1:${port}/lucky-gift-test.html`);
|
|
console.log(`Proxy upstream: ${upstreamBase}`);
|
|
});
|