const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database() const _ = db.command async function fetchUsers(ids) { if (!ids.length) return [] const out = [] for (let i = 0; i < ids.length; i += 100) { const chunk = ids.slice(i, i + 100) const res = await db.collection('users').where({ _id: _.in(chunk) }).limit(100).get() out.push(...res.data) } return out } function hashId(id) { let h = 5381 for (let i = 0; i < id.length; i++) { h = ((h << 5) + h) ^ id.charCodeAt(i) h = h >>> 0 // keep as unsigned 32-bit } return h } // Generate a stable map pin position from pet id (fallback abstract layer) function stablePin(id) { const h = hashId(id) const left = 10 + (h % 70) const top = 15 + ((h >> 8) % 55) return { left, top } } // Scatter a pet deterministically within roughly ±0.9km of the user, so on a // real map markers cluster nearby (stable per pet id across requests). function syntheticCoord(id, userLoc) { const h = hashId(id) const dLat = ((h % 1600) - 800) / 100000 // ±0.008° ≈ ±0.9km const dLng = (((h >> 8) % 1600) - 800) / 100000 return { latitude: userLoc.latitude + dLat, longitude: userLoc.longitude + dLng } } // Rough distance text from coordinate delta (fallback when no real distance) function distanceText(petLoc, userLoc) { if (!petLoc || !userLoc) return null const R = 6371000 const dLat = ((petLoc.latitude - userLoc.latitude) * Math.PI) / 180 const dLon = ((petLoc.longitude - userLoc.longitude) * Math.PI) / 180 const a = Math.sin(dLat / 2) ** 2 + Math.cos((userLoc.latitude * Math.PI) / 180) * Math.cos((petLoc.latitude * Math.PI) / 180) * Math.sin(dLon / 2) ** 2 const meters = Math.round(R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))) if (meters < 1000) return `${meters}m` return `${(meters / 1000).toFixed(1)}km` } exports.main = async event => { const { OPENID } = cloud.getWXContext() const filter = event.filters?.type || 'all' const userLoc = event.location || null const query = {} if (filter === 'dog' || filter === 'cat') query.species = filter if (filter === 'online') query.online = true let meId = '' let followingSet = new Set() let followerSet = new Set() if (OPENID) { const meRes = await db.collection('users').where({ openid: OPENID }).limit(1).get() const me = meRes.data[0] if (me) { meId = me._id const [followingRes, followerRes] = await Promise.all([ db.collection('follows').where({ followerId: meId }).limit(500).get(), db.collection('follows').where({ followeeId: meId }).limit(500).get() ]) followingSet = new Set(followingRes.data.map(f => f.followeeId)) followerSet = new Set(followerRes.data.map(f => f.followerId)) } } const res = await db.collection('pets').where(query).limit(50).get() const ownerIds = [...new Set(res.data.map(pet => pet.ownerId).filter(Boolean))] const owners = await fetchUsers(ownerIds) const ownerMap = new Map(owners.map(owner => [owner._id, owner])) const list = res.data.map(pet => { const realCoord = pet.location && typeof pet.location.latitude === 'number' ? pet.location : null const coord = realCoord || (userLoc ? syntheticCoord(pet._id, userLoc) : null) const owner = ownerMap.get(pet.ownerId) const isFollowing = followingSet.has(pet.ownerId) const isFollowedBy = followerSet.has(pet.ownerId) return { ...pet, ownerName: owner?.nickname || '', isMine: Boolean(meId && pet.ownerId === meId), isFollowing, isFriend: isFollowing && isFollowedBy, latitude: coord ? coord.latitude : undefined, longitude: coord ? coord.longitude : undefined, distanceText: distanceText(coord, userLoc) || pet.distanceText || null, pin: stablePin(pet._id) } }) return { list } }