From 3e51bcc95e6c9511b1a002aa6a32b415be347a0f Mon Sep 17 00:00:00 2001 From: huanghaipeng Date: Mon, 7 Sep 2026 13:54:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(daily-task):=20=E6=96=B0=E5=A2=9E=E6=AF=8F?= =?UTF-8?q?=E6=97=A5=E5=92=8C=E6=AF=8F=E5=91=A8=E4=BB=BB=E5=8A=A1=E8=B0=83?= =?UTF-8?q?=E8=AF=95=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- laf-cloud/dailyTaskDebug.API.md | 45 +++++++++++ laf-cloud/functions/dailyTaskDebug.ts | 90 ++++++++++++++++++++++ laf-cloud/tests/daily-task-debug.test.mjs | 93 +++++++++++++++++++++++ 3 files changed, 228 insertions(+) create mode 100644 laf-cloud/dailyTaskDebug.API.md create mode 100644 laf-cloud/functions/dailyTaskDebug.ts create mode 100644 laf-cloud/tests/daily-task-debug.test.mjs diff --git a/laf-cloud/dailyTaskDebug.API.md b/laf-cloud/dailyTaskDebug.API.md new file mode 100644 index 0000000..bea43ca --- /dev/null +++ b/laf-cloud/dailyTaskDebug.API.md @@ -0,0 +1,45 @@ +# 每日/周任务调试接口 + +云函数:`dailyTaskDebug` + +仅用于开发和体验环境。部署前配置服务端环境变量 `DAILY_TASK_DEBUG_KEY`;未配置时接口不可用。请求还必须携带当前用户 token,且用户数据中的 `isDebug` 为 `true` 或字符串 `"true"`。 + +公共请求字段: + +```json +{ + "uid": "用户ID", + "token": "登录token", + "debugKey": "服务端配置的DAILY_TASK_DEBUG_KEY", + "gameName": "可选,iaa时使用usersAd", + "action": "操作名称" +} +``` + +## 每日任务操作 + +- 完成指定任务:`action=complete_daily`,附加 `taskId` 或 `slot`(0~4)。 +- 设置指定任务进度:`action=set_daily_progress`,附加 `taskId` 或 `slot`,以及 `value`。 +- 完成当天全部任务:`action=complete_all_daily`。任务仍遵守阶段锁定规则,需要依次调用正式领奖接口解锁。 +- 重置当天任务:`action=reset_daily`。保留本周星星和周奖励状态。 + +示例: + +```json +{ + "uid": "debug-user", + "token": "token", + "debugKey": "secret", + "action": "complete_daily", + "slot": 0 +} +``` + +## 周任务操作 + +- 设置周星星:`action=set_weekly_stars`,附加 `value`,范围自动限制为 0~140。 +- 增加周星星:`action=add_weekly_stars`,附加正数或负数 `value`。 +- 重置周任务:`action=reset_weekly`。保留当天任务及进度。 +- 重置每日和周任务:`action=reset_all`。 + +成功响应会返回修改后的完整 `data.task`。测试接口只修改任务数据,不直接发放金币或道具;奖励仍通过正式 `getTaskReward` 接口领取。 diff --git a/laf-cloud/functions/dailyTaskDebug.ts b/laf-cloud/functions/dailyTaskDebug.ts new file mode 100644 index 0000000..4aedb3f --- /dev/null +++ b/laf-cloud/functions/dailyTaskDebug.ts @@ -0,0 +1,90 @@ +import cloud from "@lafjs/cloud"; +import Utils from "@/Utils"; +import { WEEKLY_REWARDS } from "@/dailyTaskConfig"; +import { refreshTaskData } from "@/dailyTaskService"; + +const db = cloud.database(); + +export default async function (ctx: FunctionContext) { + const body = ctx.body || {}; + const uid = body.uid; + if (!uid) return { code: 0, data: null, msg: "未获取到uid" }; + + const dbname = body.gameName === "iaa" ? "usersAd" : "users"; + const result = await db.collection(dbname).where({ _id: uid }).getOne(); + const user = result.data; + if (!user) return { code: 0, data: null, msg: "未获取到用户信息" }; + if (user.token && !Utils.checkToken(body.token, user.token)) { + return { code: 0, data: null, msg: "token校验失败" }; + } + if (user.isDebug !== true && user.isDebug !== "true") { + return { code: 403, data: null, msg: "仅调试账号可调用" }; + } + const debugKey = process.env.DAILY_TASK_DEBUG_KEY; + if (!debugKey || body.debugKey !== debugKey) { + return { code: 403, data: null, msg: "调试密钥校验失败" }; + } + + const level = Number(user.levelAmount) + 1 || 1; + let task = refreshTaskData(user.task, uid, level).data; + const action = String(body.action || ""); + + if (action === "set_daily_progress" || action === "complete_daily") { + const dailyTask = findDailyTask(task.dailyTasks, body.taskId, body.slot); + if (!dailyTask) return { code: 0, data: { task }, msg: "未找到指定每日任务" }; + const value = action === "complete_daily" ? dailyTask.target : Number(body.value); + if (!Number.isFinite(value)) return { code: 0, data: { task }, msg: "value必须是数字" }; + dailyTask.value = Math.max(0, Math.min(dailyTask.target, value)); + if (dailyTask.state !== 2) dailyTask.state = dailyTask.value >= dailyTask.target ? 1 : 0; + } + else if (action === "complete_all_daily") { + task.dailyTasks.forEach(dailyTask => { + if (dailyTask.state === 2) return; + dailyTask.value = dailyTask.target; + dailyTask.state = 1; + }); + } + else if (action === "set_weekly_stars" || action === "add_weekly_stars") { + const value = Number(body.value); + if (!Number.isFinite(value)) return { code: 0, data: { task }, msg: "value必须是数字" }; + const stars = action === "add_weekly_stars" ? task.weeklyStars + value : value; + task.weeklyStars = Math.max(0, Math.min(140, stars)); + } + else if (action === "reset_daily") { + task.dayKey = ""; + task = refreshTaskData(task, uid, level).data; + } + else if (action === "reset_weekly") { + task.weeklyStars = 0; + task.weeklyRewards = cloneWeeklyRewards(); + } + else if (action === "reset_all") { + task.dayKey = ""; + task.weekKey = ""; + task = refreshTaskData(task, uid, level).data; + } + else { + return { code: 0, data: { task }, msg: "不支持的调试操作" }; + } + + task = refreshTaskData(task, uid, level).data; + await db.collection(dbname).where({ _id: uid }).update({ + task: JSON.stringify(task), + taskTime: Date.now(), + }); + return { code: 1, data: { task }, msg: "调试任务数据更新成功" }; +} + +function findDailyTask(tasks: any[], taskId: unknown, slot: unknown) { + if (taskId != null && taskId !== "") return tasks.find(task => task.taskId === String(taskId)); + const slotNumber = Number(slot); + return Number.isInteger(slotNumber) ? tasks.find(task => task.slot === slotNumber) : null; +} + +function cloneWeeklyRewards() { + return WEEKLY_REWARDS.map(reward => ({ + ...reward, + state: 0 as const, + rewards: reward.rewards.map(item => ({ ...item })), + })); +} diff --git a/laf-cloud/tests/daily-task-debug.test.mjs b/laf-cloud/tests/daily-task-debug.test.mjs new file mode 100644 index 0000000..451f72d --- /dev/null +++ b/laf-cloud/tests/daily-task-debug.test.mjs @@ -0,0 +1,93 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { registerHooks } from "node:module"; + +const configUrl = new URL("../functions/dailyTaskConfig.ts", import.meta.url); +const serviceUrl = new URL("../functions/dailyTaskService.ts", import.meta.url); +const functionUrl = new URL("../functions/dailyTaskDebug.ts", import.meta.url); +const state = { user: null }; + +registerHooks({ + resolve(specifier, context, nextResolve) { + if (specifier === "@/dailyTaskConfig") return { url: configUrl.href, shortCircuit: true }; + if (specifier === "@/dailyTaskService") return { url: serviceUrl.href, shortCircuit: true }; + if (specifier === "@/Utils") { + return { url: "data:text/javascript,export default { checkToken: (a, b) => a === b }", shortCircuit: true }; + } + if (specifier === "@lafjs/cloud") { + return { + url: `data:text/javascript,export default globalThis.__dailyTaskDebugCloud`, + shortCircuit: true, + }; + } + return nextResolve(specifier, context); + }, +}); + +globalThis.__dailyTaskDebugCloud = { + database() { + return { + collection() { + return { + where() { + return { + async getOne() { return { data: state.user }; }, + async update(data) { Object.assign(state.user, data); return { updated: 1 }; }, + }; + }, + }; + }, + }; + }, +}; +process.env.DAILY_TASK_DEBUG_KEY = "debug-key"; + +const handler = (await import(functionUrl)).default; + +function makeUser(isDebug = "true") { + state.user = { _id: "debug-user", token: "token", isDebug, levelAmount: 20, task: null }; +} + +async function call(action, extra = {}) { + return handler({ body: { uid: "debug-user", token: "token", debugKey: "debug-key", action, ...extra } }); +} + +test("rejects non-debug users", async () => { + makeUser(false); + const result = await call("complete_all_daily"); + assert.equal(result.code, 403); +}); + +test("rejects an invalid debug key", async () => { + makeUser(); + const result = await call("complete_all_daily", { debugKey: "wrong-key" }); + assert.equal(result.code, 403); +}); + +test("completes one daily task by slot", async () => { + makeUser(); + const result = await call("complete_daily", { slot: 0 }); + assert.equal(result.code, 1); + assert.equal(result.data.task.dailyTasks[0].value, result.data.task.dailyTasks[0].target); + assert.equal(result.data.task.dailyTasks[0].state, 1); +}); + +test("sets weekly stars and unlocks matching rewards", async () => { + makeUser(); + const result = await call("set_weekly_stars", { value: 105 }); + assert.equal(result.data.task.weeklyStars, 105); + assert.deepEqual(result.data.task.weeklyRewards.map(item => item.state), [1, 1, 1, 0]); +}); + +test("daily reset preserves weekly progress and weekly reset preserves daily tasks", async () => { + makeUser(); + const weekly = await call("set_weekly_stars", { value: 70 }); + state.user.task = JSON.stringify(weekly.data.task); + const resetDaily = await call("reset_daily"); + assert.equal(resetDaily.data.task.weeklyStars, 70); + const dailyIds = resetDaily.data.task.dailyTasks.map(item => item.taskId); + state.user.task = JSON.stringify(resetDaily.data.task); + const resetWeekly = await call("reset_weekly"); + assert.equal(resetWeekly.data.task.weeklyStars, 0); + assert.deepEqual(resetWeekly.data.task.dailyTasks.map(item => item.taskId), dailyIds); +});