feat: 周竞赛预采集
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { createHash } from 'crypto';
|
||||
import cloud from '@lafjs/cloud'
|
||||
const db = cloud.database();
|
||||
import Utils from "@/Utils";
|
||||
@@ -45,7 +46,8 @@ export default async function (ctx: FunctionContext) {
|
||||
return;
|
||||
}
|
||||
let totalLevel = userLevel + (endLevelNum / 10000)
|
||||
await db.collection(dbname).where({ _id: uid }).update({ userLevel: userLevel, endLevelNum: endLevelNum, totalLevel: totalLevel });
|
||||
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;
|
||||
@@ -60,3 +62,73 @@ export default async function (ctx: FunctionContext) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createHash } from 'crypto';
|
||||
import * as GoldMinerServiceModule from "@/goldMiner/service";
|
||||
import * as GoldMinerRulesModule from "@/goldMiner/config";
|
||||
const GoldMinerService: any = GoldMinerServiceModule;
|
||||
@@ -94,6 +95,7 @@ export default async function (ctx: FunctionContext) {
|
||||
}
|
||||
film += count;
|
||||
let ret = await db.collection(dbname).where({ _id: uid }).update({ address: address, film: film, careerAmount: careerAmount, levelTJ: levelTJ, levelAmount: levelAmount, addLevel: add, timestamp: Date.now() });
|
||||
await recordWeeklyProgress(uid, count, ret);
|
||||
if (ctx.body.goldMiner != null) {
|
||||
try {
|
||||
GoldMinerService.authorize(res1.data, ctx.body);
|
||||
@@ -311,3 +313,73 @@ function getProvinceByIP(ip) {
|
||||
return '其他';
|
||||
}
|
||||
}
|
||||
|
||||
/** 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 });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user