0.0.0
This commit is contained in:
111
cloudfunctions/walkManual/index.js
Normal file
111
cloudfunctions/walkManual/index.js
Normal file
@@ -0,0 +1,111 @@
|
||||
// 云函数 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 } };
|
||||
};
|
||||
9
cloudfunctions/walkManual/package.json
Normal file
9
cloudfunctions/walkManual/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "walkManual",
|
||||
"version": "1.0.0",
|
||||
"description": "手动补记:维持连续但不计排行榜",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "~2.6.3"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user