135 lines
6.0 KiB
TypeScript
135 lines
6.0 KiB
TypeScript
import { createHash } from 'crypto';
|
|
import cloud from '@lafjs/cloud'
|
|
const db = cloud.database();
|
|
import Utils from "@/Utils";
|
|
export default async function (ctx: FunctionContext) {
|
|
const action = ctx.body.action;
|
|
const uid = ctx.body.uid;
|
|
let dbname = "users";
|
|
// const gameName = ctx.body.gameName;
|
|
// if (gameName == "iaa") {
|
|
// dbname = "usersAd";
|
|
// }
|
|
if (!action) {
|
|
return { code: 0, data: null, msg: "未获取到action" };
|
|
}
|
|
|
|
if (!uid) {
|
|
return { code: 0, data: null, msg: "未获取到uid" };
|
|
}
|
|
let res = await db.collection(dbname).where({ _id: uid }).getOne();
|
|
const token1 = ctx.body.token;
|
|
if (res.data && res.data.token) {
|
|
let istoken = Utils.checkToken(token1, res.data.token);
|
|
if (!istoken) {
|
|
return { code: 0, data: null, msg: "token校验失败" };
|
|
}
|
|
}
|
|
let endLevelNum1 = res.data?.endLevelNum;
|
|
switch (action) {
|
|
case 'save':
|
|
let userLevel = ctx.body.userLevel;
|
|
if (!userLevel) {
|
|
return { code: 0, data: null, msg: "未获取到userLevel" };
|
|
};
|
|
userLevel = Number(userLevel);
|
|
let endLevelNum = ctx.body.endLevelNum;
|
|
if (!endLevelNum) {
|
|
endLevelNum = 0;
|
|
}
|
|
if (endLevelNum < Number(endLevelNum1)) {
|
|
return { code: 1, data: { userLevel: userLevel, endLevelNum: endLevelNum1 }, msg: "用户数据更新成功" };
|
|
}
|
|
endLevelNum = Number(endLevelNum);
|
|
if (endLevelNum - Number(endLevelNum1) > 20) {
|
|
console.error((endLevelNum - Number(endLevelNum1)) + " 有作弊嫌疑 :" + uid);
|
|
return;
|
|
}
|
|
let totalLevel = userLevel + (endLevelNum / 10000)
|
|
const saved = await db.collection(dbname).where({ _id: uid }).update({ userLevel: userLevel, endLevelNum: endLevelNum, totalLevel: totalLevel });
|
|
await recordWeeklyProgress(uid, endLevelNum - Number(endLevelNum1), saved);
|
|
return { code: 1, data: { userLevel: userLevel, endLevelNum: endLevelNum }, msg: "用户数据更新成功" }
|
|
case 'read':
|
|
let userLevel1 = res.data?.userLevel;
|
|
//let endLevelNum1=res.data?.endLevelNum;
|
|
return {
|
|
code: 1, data: { userLevel: userLevel1, endLevelNum: endLevelNum1 }, msg: "成功"
|
|
}
|
|
default:
|
|
return {
|
|
code: 400,
|
|
message: '无效的操作类型,请使用 save 或 read'
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Monday 09:00 Asia/Shanghai. weekId identifies the period, not midnight. */
|
|
function progressWeek(at: number) {
|
|
const shifted = new Date(at - 3600000);
|
|
const startAt = Date.UTC(shifted.getUTCFullYear(), shifted.getUTCMonth(), shifted.getUTCDate())
|
|
- ((shifted.getUTCDay() + 6) % 7) * 86400000 + 3600000;
|
|
return { weekId: new Date(startAt).toISOString().slice(0, 10), startAt, endAt: startAt + 7 * 86400000 };
|
|
}
|
|
|
|
/** Only confirmed legacy saves add positive integral deltas. Do not retry a write
|
|
* after an ambiguous error: its $inc may already have reached the database. */
|
|
async function recordWeeklyProgress(uid: string, delta: number, saved: any) {
|
|
if (!saved || saved.ok === false || saved.error || saved.updated !== 1) return;
|
|
if (!Number.isSafeInteger(delta) || delta <= 0 || delta > 20) return;
|
|
try {
|
|
const environment = process.env.PAYMENT_APP_ENV === 'test' ? 'test' : 'production';
|
|
const _id = createHash('sha256').update(JSON.stringify(['weeklyCompetition', environment, uid])).digest('hex');
|
|
const table = (cloud.mongo.db as any).collection('weekly_competition');
|
|
for (let attempt = 0; attempt < 12; attempt++) {
|
|
const week = progressWeek(Date.now());
|
|
const row = await table.findOne({ _id });
|
|
if (!row) {
|
|
try {
|
|
// Match the existing competition document shape, without joining a round.
|
|
await table.insertOne({
|
|
_id, uid, environment, revision: 0, current: null, pending: null, receipts: {},
|
|
progress: { weekId: week.weekId, weeklyWins: 0, previousWeeklyWins: 0 }
|
|
});
|
|
} catch (error: any) {
|
|
if (error.code !== 11000) throw error;
|
|
// Another request initialized the same document. No score was added here.
|
|
}
|
|
continue;
|
|
}
|
|
const progress = row.progress;
|
|
const beforeCutoff = { $lt: ['$$NOW', new Date(week.endAt)] };
|
|
if (progress?.weekId === week.weekId) {
|
|
const result = await table.updateOne({ _id, 'progress.weekId': week.weekId, $expr: beforeCutoff },
|
|
{ $inc: { 'progress.weeklyWins': delta } });
|
|
if (result.matchedCount === 1) return;
|
|
if (result.matchedCount === 0) continue; // Confirmed no write: safe to reread the week.
|
|
throw new Error('weekly progress increment result is unknown');
|
|
}
|
|
let previousWeeklyWins = 0;
|
|
const condition: any = { _id, $expr: beforeCutoff };
|
|
if (progress == null) {
|
|
condition.progress = progress === undefined ? { $exists: false } : null;
|
|
} else {
|
|
const oldStartAt = Date.parse(progress.weekId + 'T01:00:00Z');
|
|
if (!Number.isFinite(oldStartAt) || oldStartAt >= week.startAt
|
|
|| !Number.isSafeInteger(progress.weeklyWins) || progress.weeklyWins < 0) {
|
|
throw new Error('weekly progress period or score is invalid');
|
|
}
|
|
if (week.startAt - oldStartAt === 7 * 86400000) previousWeeklyWins = progress.weeklyWins;
|
|
// Include the old score so a concurrent old-week $inc cannot be overwritten.
|
|
condition['progress.weekId'] = progress.weekId;
|
|
condition['progress.weeklyWins'] = progress.weeklyWins;
|
|
}
|
|
const rotated = await table.updateOne(condition,
|
|
{ $set: { progress: { weekId: week.weekId, weeklyWins: 0, previousWeeklyWins } } });
|
|
if (rotated.matchedCount !== 0 && rotated.matchedCount !== 1) {
|
|
throw new Error('weekly progress rollover result is unknown');
|
|
}
|
|
// Reread after initialization, rotation or a known conditional-update conflict.
|
|
}
|
|
throw new Error('weekly progress conditional updates exhausted');
|
|
} catch (error) {
|
|
console.error('weekly progress recording failed; legacy save unchanged', { uid, delta, error });
|
|
}
|
|
}
|