Files
艾贤凌 6d4a72161f inint
2026-03-16 12:05:55 +08:00

195 lines
8.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Router from 'koa-router'
import mysql from '../mysql/index.js'
import getGameDB from '../mysql/gameDB.js'
import * as log4js from '../log4js.js'
import config from '../config/index.js'
import { time, unixTime, getClientIp } from '../utils.js'
import { readFileSync, existsSync } from 'fs'
import { fileURLToPath } from 'url'
import { dirname, join } from 'path'
const __dirname = dirname(fileURLToPath(import.meta.url))
const router = new Router()
function ok(ctx, data = {}, message = '操作成功') {
ctx.body = { code: 0, message, ...data }
}
function fail(ctx, message = '操作失败', code = 1) {
ctx.body = { code, message }
}
// ─── GET /api/server/list 区服列表 ──────────────────────────────────────────
router.get('/api/server/list', async (ctx) => {
const account = ctx.query.account || ''
const nowTime = unixTime()
const newSrvTime = 7 * 24 * 60 * 60
const [rows] = await mysql.query(
'SELECT id, server_id, name, host, port, status, UNIX_TIMESTAMP(time) as time, merge_id FROM server WHERE status >= 1 ORDER BY server_id ASC LIMIT 1000'
)
const serverlist = rows.map(row => {
const sid = row.merge_id || row.server_id
return {
id: row.id,
serverName: (row.name || config.game.firstName) + row.server_id + '区',
srvaddr: (row.host && row.host !== '127.0.0.1') ? row.host : config.game.host,
srvport: row.port || (config.game.port + sid),
srvid: sid,
type: row.status === 3 ? 3 : (nowTime - row.time <= newSrvTime ? 1 : 2), // 1:新 2:火爆 3:维护
opentime: time(row.time * 1000),
pf: config.game.pf,
serverAlias: 's' + sid,
originalSrvid: sid,
}
})
return ok(ctx, {
login: [999, 997, 990],
serverlist: [{ name: '全部区服', serverlist }]
}, '获取服务器列表成功')
})
// ─── GET /api/misc/agree 用户协议 ───────────────────────────────────────────
// 优先从 config/agreement.html 文件读取,不存在则回退到 config.agree 字符串配置
router.get('/api/misc/agree', async (ctx) => {
const agreePath = join(__dirname, '../config/agreement.html')
if (existsSync(agreePath)) {
ctx.type = 'html'
ctx.body = readFileSync(agreePath, 'utf-8')
} else {
ctx.body = config.agree
}
})
// ─── POST /api/report/chat 上报聊天 ─────────────────────────────────────────
router.post('/api/report/chat', async (ctx) => {
const { server_id, account, role_id, channel_id, content, cross } = ctx.request.body
const serverId = parseInt((server_id || '').toString().replace(/^s/, ''))
if (!serverId || !account || !role_id || !content) return fail(ctx, 'param error')
if (account.length > 26) return fail(ctx, 'param error')
if (parseInt(channel_id) > 10) return fail(ctx, 'param error')
if (content.length > 255) return fail(ctx, 'param error')
// 验证账号 token
const token = ctx.request.headers.authorization?.split(' ')[1]
if (!token) return fail(ctx, '未授权', 401)
const nowTime = time()
await mysql.query(
'INSERT INTO chat (account, server_id, role_id, channel_id, content, is_cross, time) VALUES (?, ?, ?, ?, ?, ?, ?)',
[account, serverId, parseInt(role_id), parseInt(channel_id) || 0, content, cross == 1 ? 1 : 0, nowTime]
)
return ok(ctx)
})
// ─── POST /api/game/withdraw 提现 ───────────────────────────────────────────
router.post('/api/game/withdraw', async (ctx) => {
const {
server_id, account, role_id, role_name, pay_type, pay_account, amount
} = ctx.request.body
const serverId = parseInt((server_id || '').toString().replace(/^s/, ''))
const roleId = parseInt(role_id)
const payType = parseInt(pay_type)
const amountInt = parseInt(amount)
if (!serverId || !account || !roleId || !role_name || !pay_account || !amountInt) return fail(ctx, '参数错误!')
if (account.length > 26) return fail(ctx, '参数错误!')
if (role_name.length > 24) return fail(ctx, '参数错误!')
if (![0, 1].includes(payType)) return fail(ctx, '收款账户类型不正确!')
if (pay_account.length > 30) return fail(ctx, '收款账户格式不正确!')
const withdrawCfg = config.withdraw
const currencyName = config.currency.list[withdrawCfg.type]
const currencyField = config.currency.field[withdrawCfg.type]
if (amountInt < withdrawCfg.ratio) return fail(ctx, `最低提现数量为${withdrawCfg.ratio}`)
const minAmount = withdrawCfg.ratio * withdrawCfg.minOnce
if (amountInt < minAmount) return fail(ctx, `单次提现数量不能低于${minAmount}`)
// 验证账号
const [playerRows] = await mysql.query(
'SELECT id FROM player WHERE username = ?', [account]
)
if (!playerRows.length) return fail(ctx, '账号不存在!')
// 提现间隔限制
const nowTime = unixTime()
const [lastWithdraw] = await mysql.query(
'SELECT UNIX_TIMESTAMP(time) as time FROM withdraw WHERE server_id = ? AND role_id = ? ORDER BY id DESC LIMIT 1',
[serverId, roleId]
)
if (lastWithdraw.length > 0 && nowTime - lastWithdraw[0].time < withdrawCfg.intervalSec) {
const leftSec = withdrawCfg.intervalSec - (nowTime - lastWithdraw[0].time)
return fail(ctx, `请等待 ${leftSec} 秒后再试~`)
}
// ── 连接游戏区服数据库,验证货币余额 ─────────────────────────────────────────
let gameDB
try {
gameDB = getGameDB(serverId)
} catch (e) {
log4js.koa.error('连接游戏DB失败', serverId, e.message)
return fail(ctx, '游戏服务器连接失败,请稍后再试!')
}
// 查询角色货币余额(表名为 characters字段由 currency.field 配置)
let currentBalance = 0
if (gameDB && currencyField) {
try {
const [charRows] = await gameDB.query(
`SELECT \`${currencyField}\` as balance FROM characters WHERE id = ? LIMIT 1`,
[roleId]
)
if (!charRows.length) return fail(ctx, '角色不存在,请确认区服和角色是否正确!')
currentBalance = parseInt(charRows[0].balance) || 0
} catch (e) {
log4js.koa.error('查询角色余额失败', serverId, roleId, e.message)
return fail(ctx, '查询角色数据失败,请稍后再试!')
}
if (currentBalance < amountInt) {
return fail(ctx, `您的${currencyName}余额不足(当前:${currentBalance},需要:${amountInt}`)
}
}
const money = Math.floor(amountInt / withdrawCfg.ratio)
// ── 调用游戏 GM 命令接口扣除货币 ─────────────────────────────────────────────
const gmHost = config.game.host
const gmPort = config.game.gmPort
// GM 接口格式operid=10030扣除货币
const gmUrl = `http://${gmHost}:${gmPort}/?operid=10030&serverid=${serverId}&roleid=${roleId}&type=${withdrawCfg.type}&num=${amountInt}`
let gmSuccess = false
try {
const gmRes = await fetch(gmUrl, { signal: AbortSignal.timeout(5000) })
const gmText = await gmRes.text()
// GM 接口返回 0 表示成功
gmSuccess = gmText.trim() === '0' || gmText.includes('"result":0') || gmText.includes('success')
log4js.koa.info(`GM 命令返回: ${gmText.trim()}`, gmUrl)
} catch (e) {
log4js.koa.error('GM 命令调用失败', e.message, gmUrl)
return fail(ctx, '扣除货币失败,请联系客服!')
}
if (!gmSuccess) {
log4js.koa.warn('GM 命令返回失败', gmUrl)
return fail(ctx, '货币扣除失败,请联系客服处理!')
}
// ── 写入提现记录 ──────────────────────────────────────────────────────────────
await mysql.query(
'INSERT INTO withdraw (account, server_id, role_id, pay_type, pay_account, amount, money, time) VALUES (?, ?, ?, ?, ?, ?, ?, NOW())',
[account, serverId, roleId, payType, pay_account, amountInt, money]
)
log4js.koa.info(`提现成功: ${account} s${serverId} ${role_name} ${amountInt}${currencyName}=${money}`)
return ok(ctx, {}, `成功提现:${amountInt}${currencyName}\n收益人民币:${money}\n\n请留意您的收款账户余额。`)
})
export default router.routes()