feat: 迁移渠道画像累计指标和玩家付费分层
This commit is contained in:
@@ -86,7 +86,7 @@ const { chromium } = require(process.env.SW_PLAYWRIGHT_MODULE || 'playwright')
|
||||
await page.locator('.kra-search-box').getByRole('button', { name: '搜索', exact: true }).click()
|
||||
await page.getByText('渠道数据尚未接入。', { exact: true }).waitFor()
|
||||
assert.deepEqual(errors, [])
|
||||
const result = { passed: true, level: 1, noEditEntry: true, createAllowedForUpperBusiness: true, platformSwitchClearsBinding: true, crossPlatformSameID: true, zeroGameCountClickable: true, missingMetricsVisible: true, sourceDatabaseCreatePayload: true, portraitPending: true, pageErrors: errors }
|
||||
const result = { passed: true, level: 1, noEditEntry: true, createAllowedForUpperBusiness: true, platformSwitchClearsBinding: true, crossPlatformSameID: true, zeroGameCountClickable: true, missingMetricsVisible: true, sourceDatabaseCreatePayload: true, portraitPending: false, pageErrors: errors }
|
||||
await fs.writeFile('docs/sw6-to-kra/channel-page-2026-11-03.json', JSON.stringify({ method: '真实 Chrome 渲染;受控页面数据、身份及平台创建响应,不创建真实平台渠道', ...result }, null, 2) + '\n')
|
||||
console.log(JSON.stringify(result))
|
||||
await context.close()
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
"""只读验证渠道画像路由及现有数据的缺表处理,不修改业务记录。"""
|
||||
import argparse
|
||||
import json
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
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 = []
|
||||
|
||||
def get(path, expected=0):
|
||||
req = urllib.request.Request(args.base_url + path, headers={"x-token": token})
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
result = json.load(response)
|
||||
item = {"path": path, "http": response.status, "code": result.get("code"), "msg": result.get("msg"),
|
||||
"passed": response.status == 200 and result.get("code") == expected}
|
||||
data = result.get("data", {})
|
||||
if isinstance(data, dict) and "available" in data:
|
||||
item["available"] = data["available"]
|
||||
checks.append(item)
|
||||
return data
|
||||
|
||||
channels = get("/sw/promote/list?pageNum=1&pageSize=1")
|
||||
if channels.get("available") is False:
|
||||
get("/sw/promote/portrait?sourceDb=lx&promoteId=1&period=total", expected=7)
|
||||
elif channels.get("rows"):
|
||||
row = channels["rows"][0]
|
||||
get("/sw/promote/portrait?" + urllib.parse.urlencode({"sourceDb": row["sourceDb"], "promoteId": row["id"], "period": "today"}))
|
||||
get("/sw/promote/portrait?sourceDb=lx&promoteId=0", expected=7)
|
||||
get("/sw/promote/portrait?sourceDb=invalid&promoteId=1", expected=7)
|
||||
report = {"scope": "当前 sw7 登录会话,只读检查;完整累计、分层、日期与权限在隔离 TCP HTTP 中验证。",
|
||||
"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()
|
||||
@@ -0,0 +1,75 @@
|
||||
// 真实 Chrome 对照渠道画像;接口数据受控,验证累计、分层及关联预警参数。
|
||||
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 })
|
||||
try {
|
||||
const context = await browser.newContext({ viewport: { width: 1500, height: 1050 } })
|
||||
await context.addInitScript(value => localStorage.setItem('token', value), token)
|
||||
const page = await context.newPage()
|
||||
const errors = [], requests = [], alerts = [], games = []
|
||||
let unavailable = 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)
|
||||
const ok = data => route.fulfill({ json: { code: 0, msg: '成功', data } })
|
||||
if (api === '/sw/promote/list') return ok({ available: true, total: 2, rows: ['lx', 'hw'].map(sourceDb => ({ id: 100, sourceDb, platformName: sourceDb === 'lx' ? '龙翔' : '汇玩', account: `${sourceDb}-channel`, nickname: `${sourceDb}测试渠道`, promoteLevel: 1, gameCount: 2, playerCount: 4, totalPayAmount: '15.00', lockStatus: 0 })) })
|
||||
if (api === '/sw/promote/portrait') {
|
||||
requests.push(Object.fromEntries(url.searchParams))
|
||||
const period = url.searchParams.get('period'), source = url.searchParams.get('sourceDb')
|
||||
const length = period === 'total' ? 21 : period === 'last7' ? 7 : 24
|
||||
const trend = Array.from({ length }, (_, i) => ({ label: length === 24 ? `${String(i).padStart(2, '0')}:00` : new Date(Date.UTC(2026, 10, 3 - length + 1 + i)).toISOString().slice(5, 10), value: i === 1 ? '4' : '0' }))
|
||||
const metric = value => ({ value, compareValue: period === 'total' ? null : '1', changeRate: period === 'total' ? null : '100.0', changeDiff: null, compareLabel: period === 'total' ? null : '较上一周期' })
|
||||
return ok({ available: !unavailable, unavailableReason: unavailable ? '付费分层所需数据尚未接入' : '', period,
|
||||
basic: { promoteId: 100, nickname: `${source}测试渠道`, account: `${source}-channel`, busierDisplayName: 'SDK(商务)', promoteLevelLabel: '一级渠道', registerTypeLabel: '公会', status: -3, statusLabel: '商务新增', gameCount: 2, createTime: '2026-11-01 08:00:00', lastLoginTime: '2026-11-03 08:00:00' },
|
||||
metrics: unavailable ? null : { register: metric('4'), payAmount: metric('15.00'), payRate: metric('25.0'), active: metric('4') },
|
||||
registerTrend: unavailable ? [] : trend, payTrend: unavailable ? [] : trend.map(point => ({ ...point, value: point.value === '4' ? '15.00' : '0.00' })),
|
||||
payTiers: unavailable ? [] : ['whale', 'mid', 'small', 'free'].map((tier, index) => ({ tier, tierLabel: ['大R', '中R', '小R', '免费'][index], count: 1, ratio: '25.0' })), gameDistribution: [] })
|
||||
}
|
||||
if (api === '/sw/alertRecord/list') { alerts.push(Object.fromEntries(url.searchParams)); return ok({ available: true, pageData: { rows: [{ id: '1', createdAt: '2026-11-03 10:00:00', ruleType: 'register_anomaly', alertContent: '对应渠道预警', status: 1 }], total: 1 }, statusCount: { 1: 1 } }) }
|
||||
if (api === '/sw/promote/game-distribution') { games.push(Object.fromEntries(url.searchParams)); return ok([{ gameId: 1, gameName: '累计推广游戏', registerCount: 4, payAmount: '31.00' }]) }
|
||||
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-BusMgmt/ChannelList')
|
||||
const row = source => page.locator('.el-table__body-wrapper tr').filter({ hasText: `${source}-channel` })
|
||||
await row('lx').getByRole('button', { name: '画像', exact: true }).click()
|
||||
const dialog = page.getByRole('dialog', { name: '渠道画像 — lx测试渠道', exact: true })
|
||||
await dialog.getByText('活跃玩家', { exact: true }).waitFor()
|
||||
await dialog.locator('.el-loading-mask').first().waitFor({ state: 'hidden' })
|
||||
assert.equal(await dialog.getByText('人均充值', { exact: true }).count(), 0)
|
||||
assert.equal(await dialog.locator('.pay-tier-item').count(), 4)
|
||||
await dialog.getByText('对应渠道预警', { exact: true }).waitFor()
|
||||
assert.equal(alerts.at(-1).targetPromoteId, '100'); assert.equal(alerts.at(-1).platformCodes, 'lx'); assert.equal(alerts.at(-1).businessId, undefined)
|
||||
await Promise.all([page.waitForResponse(response => response.url().includes('/sw/promote/portrait') && response.url().includes('period=last7')), dialog.getByText('近7天', { exact: true }).click()])
|
||||
assert.equal(requests.at(-1).period, 'last7')
|
||||
await dialog.locator('.el-loading-mask').first().waitFor({ state: 'hidden' })
|
||||
await Promise.all([page.waitForResponse(response => response.url().includes('/sw/promote/portrait') && response.url().includes('period=total')), dialog.getByText('累计', { exact: true }).click()])
|
||||
await dialog.locator('.el-loading-mask').first().waitFor({ state: 'hidden' })
|
||||
assert.equal(await dialog.locator('.metric-compare').count(), 0)
|
||||
await page.screenshot({ path: path.join(process.env.TEMP, 'sw-channel-portrait.png'), animations: 'disabled' })
|
||||
await dialog.locator('.profile-info').getByRole('button', { name: '2 款', exact: true }).click()
|
||||
const gameDialog = page.getByRole('dialog', { name: '推广游戏数据 — lx测试渠道', exact: true })
|
||||
await gameDialog.getByText('累计推广游戏', { exact: true }).waitFor()
|
||||
assert.equal(games.at(-1).sourceDb, 'lx'); assert.equal(games.at(-1).promoteId, '100')
|
||||
await gameDialog.locator('.el-drawer__close-btn').click(); await gameDialog.waitFor({ state: 'hidden' })
|
||||
await dialog.getByRole('button', { name: '关闭', exact: true }).click(); await dialog.waitFor({ state: 'hidden' })
|
||||
await row('hw').getByRole('button', { name: '画像', exact: true }).click()
|
||||
const other = page.getByRole('dialog', { name: '渠道画像 — hw测试渠道', exact: true })
|
||||
await other.getByText('对应渠道预警', { exact: true }).waitFor()
|
||||
assert.equal(requests.at(-1).sourceDb, 'hw'); assert.equal(alerts.at(-1).platformCodes, 'hw')
|
||||
await other.getByRole('button', { name: '关闭', exact: true }).click(); await other.waitFor({ state: 'hidden' })
|
||||
unavailable = true
|
||||
await row('lx').getByRole('button', { name: '画像', exact: true }).click()
|
||||
await dialog.getByText('付费分层所需数据尚未接入', { exact: true }).waitFor()
|
||||
assert.equal(await dialog.locator('.pay-tier-item').count(), 0)
|
||||
assert.deepEqual(errors, [])
|
||||
const report = { method: '真实 Chrome,画像、游戏与预警数据为受控响应,不改业务数据', passed: true, activeMetric: true, totalNoComparison: true, canonicalLast7: true, fourPayTiers: true, gameDrawer: true, platformAndChannelAlertFilter: true, crossPlatformSameID: true, unavailableIsNotZero: true, pageErrors: errors }
|
||||
await fs.writeFile('docs/sw6-to-kra/channel-portrait-page-2026-11-03.json', JSON.stringify(report, null, 2) + '\n')
|
||||
console.log(JSON.stringify(report)); await context.close()
|
||||
} finally { await browser.close() }
|
||||
})().catch(error => { console.error(error); process.exitCode = 1 })
|
||||
Reference in New Issue
Block a user