From c3894186c9f466f8c9fb3da2886759a1370db11e Mon Sep 17 00:00:00 2001 From: "COMPUTER\\EDY" <249929363@qq.com> Date: Fri, 11 Sep 2026 15:11:43 +0800 Subject: [PATCH] refactor: read onboarding AB assignments within server functions --- server/laf-cloud/functions/SignInActivity.ts | 10 +++++-- server/laf-cloud/functions/Utils.ts | 10 ------- server/laf-cloud/functions/userLevel.ts | 9 +++++- tools/new-player-ab-test-support.cjs | 2 +- tools/test-new-player-ab.cjs | 29 ++++++++++++++------ 5 files changed, 37 insertions(+), 23 deletions(-) diff --git a/server/laf-cloud/functions/SignInActivity.ts b/server/laf-cloud/functions/SignInActivity.ts index 4cff5f6..bcb1ef7 100644 --- a/server/laf-cloud/functions/SignInActivity.ts +++ b/server/laf-cloud/functions/SignInActivity.ts @@ -1,5 +1,4 @@ import cloud from '@lafjs/cloud' -import Utils from "@/Utils"; import { createHash } from 'crypto'; const db = cloud.database(); @@ -47,7 +46,14 @@ export function getChinaDateKey(now = Date.now()) { } export function getUserSignInTriggerLevel(user: any, config: SignInActivityConfig): number { - return Utils.isNewPlayerB(user) ? 15 : Number(config.triggerLevel); + let assignments = user?.abTests; + if (typeof assignments === "string") { + try { assignments = JSON.parse(assignments); } catch { assignments = null; } + } + const assignment = assignments?.layer_1; + const isNewPlayerB = !!assignment && assignment.name === "new1_50" && assignment.enabled !== false + && ["B", "2"].includes(String(assignment.group).toUpperCase()); + return isNewPlayerB ? 15 : Number(config.triggerLevel); } export function getUserSignInLevel(user: any) { diff --git a/server/laf-cloud/functions/Utils.ts b/server/laf-cloud/functions/Utils.ts index 6925670..e14b4f9 100644 --- a/server/laf-cloud/functions/Utils.ts +++ b/server/laf-cloud/functions/Utils.ts @@ -1,16 +1,6 @@ import cloud from '@lafjs/cloud'; const db = cloud.database(); export default class Utils { - static isNewPlayerB(user: any): boolean { - let assignments = user?.abTests; - if (typeof assignments === "string") { - try { assignments = JSON.parse(assignments); } catch { return false; } - } - const assignment = assignments?.layer_1; - return !!assignment && assignment.name === "new1_50" && assignment.enabled !== false - && ["B", "2"].includes(String(assignment.group).toUpperCase()); - } - static checkToken(token, token1) { if (token == token1) { //console.error("校验成功") diff --git a/server/laf-cloud/functions/userLevel.ts b/server/laf-cloud/functions/userLevel.ts index d57b13c..67a8b06 100644 --- a/server/laf-cloud/functions/userLevel.ts +++ b/server/laf-cloud/functions/userLevel.ts @@ -53,7 +53,14 @@ export default async function (ctx: FunctionContext) { return; } // B组前15关只推进主线;补报跨过15关时也只计算门槛之后的胜利。 - const careerCount = Utils.isNewPlayerB(res1.data) + let assignments = res1.data?.abTests; + if (typeof assignments === "string") { + try { assignments = JSON.parse(assignments); } catch { assignments = null; } + } + const assignment = assignments?.layer_1; + const isNewPlayerB = !!assignment && assignment.name === "new1_50" && assignment.enabled !== false + && ["B", "2"].includes(String(assignment.group).toUpperCase()); + const careerCount = isNewPlayerB ? Math.max(levelAmount - 15, 0) - Math.max(oldlevelAmount - 15, 0) : count; let add = oldAdd + careerCount; diff --git a/tools/new-player-ab-test-support.cjs b/tools/new-player-ab-test-support.cjs index 981b5cc..c4ba617 100644 --- a/tools/new-player-ab-test-support.cjs +++ b/tools/new-player-ab-test-support.cjs @@ -31,7 +31,7 @@ function withNewPlayerConfig(config) { return config; } -const ServerUtils = loadMembers('server/laf-cloud/functions/Utils.ts', ['isNewPlayerB']); +const ServerUtils = loadMembers('server/laf-cloud/functions/Utils.ts', ['checkToken']); function loadModule(file, deps = {}, globals = {}) { const exports = {}; const code = ts.transpileModule(fs.readFileSync(file, 'utf8'), { diff --git a/tools/test-new-player-ab.cjs b/tools/test-new-player-ab.cjs index 3a470d9..643a3ab 100644 --- a/tools/test-new-player-ab.cjs +++ b/tools/test-new-player-ab.cjs @@ -12,6 +12,9 @@ const config = (level = 0, group = 'B') => withNewPlayerConfig({ GM_INFO: { const json = file => JSON.parse(fs.readFileSync(file, 'utf8')); test('front and back agree on B/2; A, missing, disabled and unrelated experiments keep defaults', () => { + const service = loadModule('server/laf-cloud/functions/SignInActivity.ts', { + '@lafjs/cloud': { default: { database: () => ({}) } }, crypto: require('node:crypto'), + }); for (const [raw, expected] of [ [assignment('B'), true], [assignment(2), true], [assignment('2'), true], [assignment('A'), false], [assignment(1), false], [assignment('1'), false], @@ -21,10 +24,10 @@ test('front and back agree on B/2; A, missing, disabled and unrelated experiment ]) { const c = config(); c.GM_INFO.abTestAssignments = raw; assert.equal(c.isNewPlayerB(), expected); - assert.equal(ServerUtils.isNewPlayerB({ abTests: raw }), expected); - assert.equal(ServerUtils.isNewPlayerB({ abTests: JSON.stringify(raw) }), expected); + assert.equal(service.getUserSignInTriggerLevel({ abTests: raw }, { triggerLevel: 23 }), expected ? 15 : 23); + assert.equal(service.getUserSignInTriggerLevel({ abTests: JSON.stringify(raw) }, { triggerLevel: 23 }), expected ? 15 : 23); } - assert.equal(ServerUtils.isNewPlayerB({ abTests: 'invalid' }), false); + assert.equal(service.getUserSignInTriggerLevel({ abTests: 'invalid' }, { triggerLevel: 23 }), 23); }); test('B mainline switches at 50/51; A, help and endless keep Json', async () => { @@ -144,11 +147,19 @@ test('front-end Career is hidden without counting through 15 and reused node app }); test('server counts only victories after 15, including batches crossing the boundary', async () => { - for (const [group, oldLevel, level, expected] of [ + const cases = [ ['B', 0, 1, 0], ['B', 14, 15, 0], ['B', 15, 16, 1], ['B', 13, 18, 3], ['B', 15, 20, 5], ['B', 18, 18, 0], ['B', 50, 51, 1], ['A', 14, 15, 1], [0, 0, 1, 1], - ]) { - const user = { _id: 'user', levelAmount: oldLevel, addLevel: 0, abTests: assignment(group), film: 100, isWhite: true, address: '浙江' }; + [2, 14, 15, 0], ['2', 15, 16, 1], + ].map(([group, oldLevel, level, expected]) => [assignment(group), oldLevel, level, expected]); + cases.push( + [JSON.stringify(assignment('B')), 14, 15, 0], [JSON.stringify(assignment('A')), 14, 15, 1], + [undefined, 14, 15, 1], ['invalid', 14, 15, 1], + [{ layer_1: { name: 'another', group: 'B' } }, 14, 15, 1], + [{ layer_1: { name: 'new1_50', group: 'B', enabled: false } }, 14, 15, 1], + ); + for (const [abTests, oldLevel, level, expected] of cases) { + const user = { _id: 'user', levelAmount: oldLevel, addLevel: 0, abTests, film: 100, isWhite: true, address: '浙江' }; let update; const cloud = { database: () => ({ collection: () => ({ where: () => ({ getOne: async () => ({ data: user }), update: async payload => { update = payload; return { updated: 1 }; } }) }) }) }; @@ -158,7 +169,7 @@ test('server counts only victories after 15, including batches crossing the boun }, { process: { env: { SRANK_ID: 'rank' } } }).default; const result = await api({ body: { uid: 'user', action: 'save', levelAmount: level }, headers: {} }); assert.equal(result.code, 1); - assert.equal(update.addLevel, expected, `${group}: ${oldLevel} -> ${level}`); + assert.equal(update.addLevel, expected, `${JSON.stringify(abTests)}: ${oldLevel} -> ${level}`); assert.equal(update.film, 100 + level - oldLevel, 'film still follows all cleared levels'); assert.equal(update.levelAmount, level); } @@ -166,7 +177,7 @@ test('server counts only victories after 15, including batches crossing the boun test('seven-day activity uses B=15 while other players keep the configured trigger', () => { const service = loadModule('server/laf-cloud/functions/SignInActivity.ts', { - '@lafjs/cloud': { default: { database: () => ({}) } }, '@/Utils': { default: ServerUtils }, crypto: require('node:crypto'), + '@lafjs/cloud': { default: { database: () => ({}) } }, crypto: require('node:crypto'), }); for (const group of ['A', 'B', 0, '2']) { assert.equal(service.getUserSignInTriggerLevel({ abTests: assignment(group) }, { triggerLevel: 23 }), ['B', '2'].includes(group) ? 15 : 23); @@ -179,7 +190,7 @@ test('B seven-day activity activates at cleared level 15 and retains the origina get: async () => ({ data: [activity] }), update: async () => ({ updated: 1 }), }) }) }) }; const service = loadModule('server/laf-cloud/functions/SignInActivity.ts', { - '@lafjs/cloud': { default: cloud }, '@/Utils': { default: ServerUtils }, crypto: require('node:crypto'), + '@lafjs/cloud': { default: cloud }, crypto: require('node:crypto'), }); for (const [group, level, expected] of [['B', 14, false], ['B', 15, true], ['A', 15, false], ['A', 23, true]]) { const result = await service.activateSignInActivity({ _id: 'player', levelAmount: level, abTests: assignment(group) });