Files
2026-06-25 18:24:29 +08:00

112 lines
4.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 云函数 walkManual — 手动补记docs/05 R7
// 规则isManual=true、countsForRanking=false不计排行榜防刷可维持连续打卡不断签。
const cloud = require('wx-server-sdk');
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV });
const db = cloud.database();
const _ = db.command;
const DAY = 86400000;
function toDateStr(ts, tz = 8) {
const d = new Date(ts + tz * 3600 * 1000);
const y = d.getUTCFullYear();
const m = String(d.getUTCMonth() + 1).padStart(2, '0');
const day = String(d.getUTCDate()).padStart(2, '0');
return `${y}-${m}-${day}`;
}
function diffDays(a, b) {
return Math.round((Date.parse(b + 'T00:00:00Z') - Date.parse(a + 'T00:00:00Z')) / DAY);
}
// 连续天数:从该狗全部 walkDate 重算(与 walkSave 一致),保证补记任意顺序都能正确续/断签docs/05 R10
async function computeStreak(openid, dogId) {
const res = await db.collection('walks')
.where({ _openid: openid, dogIds: dogId })
.field({ walkDate: true })
.orderBy('walkDate', 'desc')
.limit(1000)
.get();
const dates = Array.from(new Set(res.data.map((w) => w.walkDate).filter(Boolean))).sort(); // 升序
if (!dates.length) return { currentStreak: 0, lastWalkDate: '' };
const lastWalkDate = dates[dates.length - 1];
let streak = 1;
for (let i = dates.length - 1; i > 0; i--) {
if (diffDays(dates[i - 1], dates[i]) === 1) streak++;
else break;
}
return { currentStreak: streak, lastWalkDate };
}
// 补记仅触发连续/累计类成就(不触发分享/场景类)
const STAT_ACHIEVEMENTS = [
{ key: 'first_walk', name: '首遛', cond: (s) => s.totalWalks >= 1 },
{ key: 'week_streak', name: '一周不断', cond: (s) => s.currentStreak >= 7 },
{ key: 'month_streak', name: '满月坚持', cond: (s) => s.currentStreak >= 30 },
{ key: 'hundred_km', name: '百里同行', cond: (s) => s.totalDistance >= 100 },
];
async function unlockAchievements(openid, dogId, candidates) {
if (!candidates.length) return [];
const col = db.collection('dog_achievements');
const existing = await col.where({ dogId }).field({ achievementKey: true }).get();
const have = new Set(existing.data.map((d) => d.achievementKey));
const fresh = candidates.filter((c) => !have.has(c.key));
const now = Date.now();
for (const c of fresh) {
await col.add({ data: { _openid: openid, dogId, achievementKey: c.key, unlockedAt: now } });
}
return fresh.map((c) => ({ key: c.key, name: c.name }));
}
exports.main = async (event) => {
const { OPENID } = cloud.getWXContext();
const { dogId, startAt, duration } = event;
const distance = event.distance != null ? event.distance : null;
if (!dogId || !startAt || !duration) {
return { ok: false, code: 'INVALID', msg: '缺少必要信息' };
}
// 校验归属
const dogRes = await db.collection('dogs').doc(dogId).get().catch(() => null);
if (!dogRes || !dogRes.data || dogRes.data._openid !== OPENID) {
return { ok: false, code: 'FORBIDDEN', msg: '无权操作' };
}
const now = Date.now();
const walkDate = toDateStr(startAt);
const addRes = await db.collection('walks').add({
data: {
_openid: OPENID,
dogIds: [dogId],
startAt,
endAt: startAt + duration * 1000,
duration,
distance, dogSteps: null, pooped: false,
weather: null, mood: null, note: null, photos: [],
source: 'manual', // 来源标识
isManual: true, // 补记标识
countsForRanking: false, // 不计排行榜
walkDate,
createdAt: now,
},
});
// 更新狗狗统计(维持连续);连续天数按全部历史重算(已含本条补记)
const dog = dogRes.data;
const s = dog.stats || { totalWalks: 0, totalDistance: 0, totalSteps: 0, currentStreak: 0, lastWalkDate: '' };
const streakInfo = await computeStreak(OPENID, dogId);
const newStats = {
totalWalks: (s.totalWalks || 0) + 1,
totalDistance: +(((s.totalDistance || 0) + (distance || 0)).toFixed(2)),
totalSteps: s.totalSteps || 0,
earlyWalks: s.earlyWalks || 0, // 补记不触发场景类成就,早遛计数保持不变
currentStreak: streakInfo.currentStreak,
lastWalkDate: streakInfo.lastWalkDate,
};
await db.collection('dogs').doc(dogId).update({ data: { stats: newStats } });
const satisfied = STAT_ACHIEVEMENTS.filter((a) => a.cond(newStats)).map((a) => ({ key: a.key, name: a.name }));
const newAchievements = await unlockAchievements(OPENID, dogId, satisfied);
await db.collection('users').where({ _openid: OPENID }).update({ data: { walkCount: _.inc(1) } });
return { ok: true, data: { walkId: addRes._id, newAchievements } };
};