feat: 迁移注册充值明细和筛选导出

This commit is contained in:
Yvan
2026-11-04 00:32:49 +08:00
parent f5b7ce21b9
commit 6703a66f0d
34 changed files with 1964 additions and 9 deletions
+59
View File
@@ -0,0 +1,59 @@
"""只读检查明细接口,并用确定为空的商务筛选验证真实 XLSX 导出。"""
import argparse
import io
import json
import urllib.parse
import urllib.request
import zipfile
import xml.etree.ElementTree as ET
from pathlib import Path
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--base-url", default="http://127.0.0.1:18000")
parser.add_argument("--token-file", required=True, type=Path)
parser.add_argument("--output", required=True, type=Path)
args = parser.parse_args()
token = args.token_file.read_text(encoding="utf-8").strip()
checks = []
keyword = "__kra_empty_export_probe_20261104__"
def call(method, path, payload=None, expected=0, excel=False):
body = urllib.parse.urlencode(payload).encode() if payload is not None else None
request = urllib.request.Request(args.base_url + path, method=method, data=body,
headers={"x-token": token, "Content-Type": "application/x-www-form-urlencoded"})
with urllib.request.urlopen(request, timeout=30) as response:
raw = response.read()
item = {"method": method, "path": path, "http": response.status}
if excel:
with zipfile.ZipFile(io.BytesIO(raw)) as archive:
sheet = ET.fromstring(archive.read("xl/worksheets/sheet1.xml"))
rows = sheet.findall(".//{http://schemas.openxmlformats.org/spreadsheetml/2006/main}row")
item.update(xlsx=True, worksheet_rows=len(rows), passed=response.headers.get("success") == "true" and len(rows) == 1)
else:
value = json.loads(raw)
data = value.get("data", {})
item.update(code=value.get("code"), msg=value.get("msg"), passed=value.get("code") == expected)
if isinstance(data, dict) and "rows" in data:
item.update(total=data.get("total"), available=data.get("available"))
checks.append(item)
return item
for kind in ["register", "spend"]:
item = call("GET", f"/sw/{kind}/list?sourceDb=lx,hw,yq")
empty = call("GET", f"/sw/{kind}/list?" + urllib.parse.urlencode({"busierAccount": keyword}))
empty["passed"] &= empty.get("total") == 0 and empty.get("available") is True
call("POST", f"/sw/{kind}/export", {"busierAccount": keyword}, excel=True)
if item.get("available") is False:
call("POST", f"/sw/{kind}/export", {"sourceDb": "lx"}, expected=7)
call("GET", "/sw/register/list?beginRegisterTime=bad&endRegisterTime=2026-11-04", expected=7)
report = {"scope": "真实 sw7 会话,只读查询;导出使用确定为空的商务筛选,仅校验表头工作簿,不导出真实业务行。", "checks": checks, "passed": all(item["passed"] for item in checks)}
args.output.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(json.dumps(report, ensure_ascii=False))
if not report["passed"]:
raise SystemExit(1)
if __name__ == "__main__":
main()
+72
View File
@@ -0,0 +1,72 @@
// 明细页面采用受控列表数据;下载通过真实服务的确定空查询生成 XLSX,不读取实际业务行。
const fs = require('node:fs/promises')
const path = require('node:path')
const assert = require('node:assert/strict')
const { chromium } = require(process.env.SW_PLAYWRIGHT_MODULE || 'playwright')
;(async () => {
const token = (await fs.readFile(path.join(process.env.TEMP, 'sw-kra-http-test-token'), 'utf8')).trim()
const browser = await chromium.launch({ channel: 'chrome', headless: true })
const reports = []
try {
for (const kind of ['register', 'spend']) {
const context = await browser.newContext({ viewport: { width: 1650, height: 1000 }, acceptDownloads: true })
await context.addInitScript(value => localStorage.setItem('token', value), token)
const page = await context.newPage()
const errors = [], queries = [], exports = []
let failed = false
page.on('pageerror', error => errors.push(error.message))
await page.route('http://127.0.0.1:8080/api/**', async route => {
const request = route.request(), url = new URL(request.url()), api = url.pathname.slice(4)
if (api === `/sw/${kind}/list`) {
queries.push(Object.fromEntries(url.searchParams))
if (failed) return route.fulfill({ json: { code: 7, msg: '测试查询失败', data: {} } })
const row = kind === 'register'
? { id: 7, userId: 7, sourceDb: 'lx', sourceDbLabel: '龙翔', registerTime: 1793664000, fgameName: '测试游戏', totalPayAmount: '9007199254740993.10', registerIp: '192.0.2.1', deviceName: '测试机型', promoteAccount: '测试渠道', busierName: 'SDK(测试商务)' }
: { id: 71, userId: 7, sourceDb: 'lx', sourceDbLabel: '龙翔', payOrderNumber: '00000000000071', gameName: '测试游戏', serverName: '测试区服', gamePlayerName: '测试角色', payAmount: '5000.00', payTime: 1793664000, payWay: 3, payStatus: 0, spendIp: '192.0.2.2', promoteAccount: '测试渠道', busierName: 'SDK(测试商务)' }
return route.fulfill({ json: { code: 0, msg: '成功', data: { rows: [row], total: 1, totalPayAmount: '2300.00', available: true } } })
}
if (api === '/sw/serverAnalysis/games') return route.fulfill({ json: { code: 0, data: [{ sourceDb: 'lx', gameId: 1, gameName: '可选游戏' }, { sourceDb: 'hw', gameId: 2, gameName: '可选游戏' }] } })
if (api === `/sw/${kind}/export`) {
exports.push(Object.fromEntries(new URLSearchParams(request.postData())))
const response = await route.fetch({ url: 'http://127.0.0.1:18000' + api, method: 'POST', postData: 'busierAccount=__kra_empty_export_probe_20261104__', headers: { ...request.headers(), 'content-type': 'application/x-www-form-urlencoded', 'x-token': token } })
return route.fulfill({ response })
}
const response = await route.fetch({ url: 'http://127.0.0.1:18000' + api + url.search, headers: { ...request.headers(), 'x-token': token } })
return route.fulfill({ response })
})
await page.goto(`http://127.0.0.1:8080/#/layout/sw-DataAnalysis/${kind === 'register' ? 'RegistrationDetails' : 'RechargeDetails'}`)
await page.getByText('测试渠道', { exact: true }).waitFor()
const first = queries[0], begin = first[kind === 'register' ? 'beginRegisterTime' : 'beginPayTime'], end = first[kind === 'register' ? 'endRegisterTime' : 'endPayTime']
assert.equal((Date.parse(end) - Date.parse(begin)) / 86400000, 29)
if (kind === 'register') {
assert.equal(await page.getByRole('columnheader', { name: 'ID', exact: true }).count(), 0)
await page.getByText('¥9,007,199,254,740,993.10', { exact: true }).waitFor()
} else {
assert.equal(await page.locator('.large-recharge-row').count(), 1)
await page.getByText('¥2,300.00', { exact: true }).waitFor()
}
const form = page.locator('.kra-search-box')
await form.locator('.el-form-item').filter({ has: page.locator('label').getByText('游戏', { exact: true }) }).locator('.el-select__wrapper').click()
await page.getByRole('option', { name: '可选游戏', exact: true }).click()
await page.keyboard.press('Escape')
await form.getByPlaceholder('玩家ID', { exact: true }).fill('42')
await Promise.all([page.waitForResponse(response => response.url().includes(`/sw/${kind}/list`) && response.url().includes('userId=42')), form.getByRole('button', { name: '搜索', exact: true }).click()])
assert.equal(queries.at(-1).gameName, '可选游戏')
const [download] = await Promise.all([page.waitForEvent('download'), page.getByRole('button', { name: '导出', exact: true }).click()])
const bytes = await fs.readFile(await download.path())
assert.equal(bytes.subarray(0, 2).toString(), 'PK')
assert.equal(exports.at(-1).userId, '42')
assert.equal(exports.at(-1).gameName, '可选游戏')
await page.screenshot({ path: path.join(process.env.TEMP, `sw-${kind}-details.png`), animations: 'disabled' })
failed = true
await form.getByRole('button', { name: '搜索', exact: true }).click()
await page.locator('.el-alert').getByText('测试查询失败', { exact: true }).waitFor()
if (kind === 'spend') assert.equal(await page.getByText('¥2,300.00', { exact: true }).count(), 0)
assert.deepEqual(errors, [])
const report = { kind, passed: true, defaultThirtyDays: true, gameNameSelection: true, exactAmountOrSuccessSummary: true, xlsxDownload: true, exportKeepsFilters: true, failedQueryClearsOldData: true, pageErrors: errors }
reports.push(report); console.log(JSON.stringify(report)); await context.close()
}
await fs.writeFile('docs/sw6-to-kra/ledger-page-2026-11-04.json', JSON.stringify({ method: '真实 Chrome,列表数据受控;导出使用真实服务的空筛选生成 XLSX,不读取业务数据。', reports }, null, 2) + '\n')
} finally { await browser.close() }
})().catch(error => { console.error(error); process.exitCode = 1 })