Initial commit

This commit is contained in:
2026-06-05 17:46:51 +08:00
commit c04c890186
96 changed files with 6477 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
const _ = db.command
exports.main = async (event, context) => {
const { OPENID } = cloud.getWXContext()
const { content, images = [], video, location, hashtags = [], mood, petId } = event
if (!content?.trim() && images.length === 0) {
return { code: -1, message: '内容不能为空' }
}
try {
const post = {
authorId: OPENID,
petId: petId || null,
content: (content || '').trim(),
images,
video: video || null,
location: location || null,
hashtags,
mood: mood || null,
likeCount: 0,
commentCount: 0,
createdAt: db.serverDate(),
updatedAt: db.serverDate(),
}
const res = await db.collection('posts').add({ data: post })
// 更新用户帖子统计
await db.collection('users')
.where({ openid: OPENID })
.update({ data: { 'stats.posts': _.inc(1) } })
// 更新话题统计
if (hashtags.length > 0) {
const batch = hashtags.map(tag =>
db.collection('topics')
.where({ tag })
.get()
.then(r => {
if (r.data.length === 0) {
return db.collection('topics').add({ data: { tag, count: 1, createdAt: db.serverDate() } })
}
return db.collection('topics').where({ tag }).update({ data: { count: _.inc(1) } })
})
)
await Promise.all(batch).catch(() => {})
}
return { code: 0, data: { _id: res._id, ...post } }
} catch (e) {
return { code: -1, message: e.message }
}
}

View File

@@ -0,0 +1 @@
{ "name": "createPost", "version": "1.0.0", "main": "index.js", "dependencies": { "wx-server-sdk": "~2.1.2" } }

View File

@@ -0,0 +1,76 @@
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
const _ = db.command
// 粗略经纬度范围换算1度≈111km
function latDelta(km) { return km / 111 }
function lngDelta(km, lat) { return km / (111 * Math.cos(lat * Math.PI / 180)) }
function calcDistance(lat1, lng1, lat2, lng2) {
const R = 6371
const dLat = (lat2 - lat1) * Math.PI / 180
const dLng = (lng2 - lng1) * Math.PI / 180
const a = Math.sin(dLat / 2) ** 2 +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLng / 2) ** 2
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) * 1000 // meters
}
exports.main = async (event, context) => {
const { OPENID } = cloud.getWXContext()
const { latitude, longitude, radiusKm = 5 } = event
if (!latitude || !longitude) {
return { code: -1, message: '缺少位置信息' }
}
// 在线判断60秒内更新过位置的用户为在线
const onlineThreshold = new Date(Date.now() - 60 * 1000).toISOString()
try {
const dLat = latDelta(radiusKm)
const dLng = lngDelta(radiusKm, latitude)
const usersRes = await db.collection('users')
.where(
_.and([
{ openid: _.neq(OPENID) }, // 排除自己
{ 'lastLocation.latitude': _.gt(latitude - dLat).and(_.lt(latitude + dLat)) },
{ 'lastLocation.longitude': _.gt(longitude - dLng).and(_.lt(longitude + dLng)) },
{ pets: _.exists(true) },
])
)
.field({
openid: true, nickName: true, avatarUrl: true,
lastLocation: true, lastSeen: true, isOnline: true,
pets: true, location: true, bio: true,
})
.limit(30)
.get()
const nearby = usersRes.data
.map(user => {
const userLat = user.lastLocation?.latitude
const userLng = user.lastLocation?.longitude
if (!userLat || !userLng) return null
const distance = calcDistance(latitude, longitude, userLat, userLng)
if (distance > radiusKm * 1000) return null
return {
userId: user.openid,
user,
pet: user.pets?.[0] || null,
distance,
isOnline: user.isOnline && user.lastSeen > onlineThreshold,
location: { latitude: userLat, longitude: userLng },
}
})
.filter(Boolean)
.sort((a, b) => a.distance - b.distance)
return { code: 0, data: nearby }
} catch (e) {
return { code: -1, message: e.message }
}
}

View File

@@ -0,0 +1 @@
{ "name": "getNearbyPets", "version": "1.0.0", "main": "index.js", "dependencies": { "wx-server-sdk": "~2.1.2" } }

View File

@@ -0,0 +1,34 @@
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
exports.main = async (event, context) => {
const { OPENID } = cloud.getWXContext()
const { postId } = event
if (!postId) return { code: -1, message: '缺少 postId' }
try {
const post = await db.collection('posts').doc(postId).get()
if (!post.data) return { code: -1, message: '帖子不存在' }
const authorRes = await db.collection('users')
.where({ openid: post.data.authorId })
.field({ nickName: true, avatarUrl: true, openid: true, pets: true })
.get()
const likeRes = await db.collection('likes')
.where({ userId: OPENID, postId })
.count()
return {
code: 0,
data: {
...post.data,
author: authorRes.data[0] || null,
isLiked: likeRes.total > 0,
},
}
} catch (e) {
return { code: -1, message: e.message }
}
}

View File

@@ -0,0 +1 @@
{ "name": "getPost", "version": "1.0.0", "main": "index.js", "dependencies": { "wx-server-sdk": "~2.1.2" } }

View File

@@ -0,0 +1,83 @@
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
const _ = db.command
const PAGE_SIZE = 10
exports.main = async (event, context) => {
const { OPENID } = cloud.getWXContext()
const { page = 0, type = 'hot', userId, latitude, longitude } = event
try {
let query = db.collection('posts')
const skip = page * PAGE_SIZE
if (userId) {
// 某用户的帖子
query = query.where({ authorId: userId })
} else if (type === 'feed') {
// 关注的用户帖子
const followsRes = await db.collection('follows')
.where({ followerId: OPENID })
.field({ followeeId: true })
.get()
const followeeIds = followsRes.data.map(f => f.followeeId)
followeeIds.push(OPENID)
query = query.where({ authorId: _.in(followeeIds) })
} else if (type === 'nearby' && latitude && longitude) {
// 附近帖子:使用地理围栏查询(简化版,用 geo_near
query = query.where(
_.and([
{ 'location.latitude': _.gt(latitude - 0.1) },
{ 'location.latitude': _.lt(latitude + 0.1) },
{ 'location.longitude': _.gt(longitude - 0.15) },
{ 'location.longitude': _.lt(longitude + 0.15) },
])
)
}
// type === 'hot' 全量按热度排序
const total = await query.count()
const postsRes = await query
.orderBy('createdAt', 'desc')
.skip(skip)
.limit(PAGE_SIZE)
.get()
const posts = postsRes.data
// 批量获取作者信息
const authorIds = [...new Set(posts.map(p => p.authorId))]
const authorsRes = await db.collection('users')
.where({ openid: _.in(authorIds) })
.field({ nickName: true, avatarUrl: true, openid: true, pets: true, location: true })
.get()
const authorMap = {}
authorsRes.data.forEach(u => { authorMap[u.openid] = u })
// 获取当前用户点赞状态
const postIds = posts.map(p => p._id)
const likesRes = await db.collection('likes')
.where({ userId: OPENID, postId: _.in(postIds) })
.field({ postId: true })
.get()
const likedSet = new Set(likesRes.data.map(l => l.postId))
const enriched = posts.map(p => ({
...p,
author: authorMap[p.authorId] || null,
isLiked: likedSet.has(p._id),
}))
return {
code: 0,
data: {
list: enriched,
total: total.total,
hasMore: skip + posts.length < total.total,
},
}
} catch (e) {
return { code: -1, message: e.message }
}
}

View File

@@ -0,0 +1 @@
{ "name": "getPosts", "version": "1.0.0", "main": "index.js", "dependencies": { "wx-server-sdk": "~2.1.2" } }

View File

@@ -0,0 +1,54 @@
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
const _ = db.command
exports.main = async (event, context) => {
const { OPENID } = cloud.getWXContext()
const { postId } = event
if (!postId) return { code: -1, message: '缺少 postId' }
try {
// 检查是否已点赞
const existRes = await db.collection('likes')
.where({ userId: OPENID, postId })
.get()
let liked
let delta
if (existRes.data.length > 0) {
// 取消点赞
await db.collection('likes').doc(existRes.data[0]._id).remove()
delta = -1
liked = false
} else {
// 点赞
await db.collection('likes').add({
data: {
userId: OPENID,
postId,
createdAt: db.serverDate(),
},
})
delta = 1
liked = true
}
// 原子更新帖子点赞数
await db.collection('posts').doc(postId).update({
data: { likeCount: _.inc(delta) },
})
// 获取最新计数
const postRes = await db.collection('posts').doc(postId).field({ likeCount: true }).get()
return {
code: 0,
data: { liked, count: postRes.data.likeCount },
}
} catch (e) {
return { code: -1, message: e.message }
}
}

View File

@@ -0,0 +1 @@
{ "name": "likePost", "version": "1.0.0", "main": "index.js", "dependencies": { "wx-server-sdk": "~2.1.2" } }

View File

@@ -0,0 +1,48 @@
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
exports.main = async (event, context) => {
const { OPENID, APPID } = cloud.getWXContext()
try {
// 查找或创建用户
const userRes = await db.collection('users').where({ openid: OPENID }).get()
let userInfo
if (userRes.data.length === 0) {
// 新用户 - 创建档案
const newUser = {
openid: OPENID,
nickName: event.nickName || '宠物爱好者',
avatarUrl: event.avatarUrl || '',
handle: '',
location: '',
bio: '',
stats: { posts: 0, friends: 0, likes: 0 },
lastLocation: null,
isOnline: true,
lastSeen: new Date().toISOString(),
pets: [],
createdAt: db.serverDate(),
updatedAt: db.serverDate(),
}
const addRes = await db.collection('users').add({ data: newUser })
userInfo = { _id: addRes._id, ...newUser }
} else {
// 老用户 - 更新在线状态
userInfo = userRes.data[0]
await db.collection('users').doc(userInfo._id).update({
data: {
isOnline: true,
lastSeen: db.serverDate(),
updatedAt: db.serverDate(),
},
})
}
return { code: 0, data: { userInfo } }
} catch (e) {
return { code: -1, message: e.message }
}
}

View File

@@ -0,0 +1,9 @@
{
"name": "login",
"version": "1.0.0",
"description": "微信云函数 - 用户登录",
"main": "index.js",
"dependencies": {
"wx-server-sdk": "~2.1.2"
}
}

View File

@@ -0,0 +1,53 @@
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
exports.main = async (event, context) => {
const { OPENID } = cloud.getWXContext()
const { toUserId, petId } = event
if (!toUserId) return { code: -1, message: '缺少目标用户' }
if (toUserId === OPENID) return { code: -1, message: '不能给自己打招呼' }
try {
// 防刷5分钟内同一对用户只能打一次招呼
const cooldownMs = 5 * 60 * 1000
const recent = await db.collection('messages')
.where({
fromId: OPENID,
toId: toUserId,
type: 'greeting',
createdAt: db.command.gt(new Date(Date.now() - cooldownMs).toISOString()),
})
.count()
if (recent.total > 0) {
return { code: -1, message: '打招呼太频繁了,稍后再试' }
}
// 获取发送者信息
const senderRes = await db.collection('users')
.where({ openid: OPENID })
.field({ nickName: true, pets: true })
.get()
const sender = senderRes.data[0]
const pet = sender?.pets?.find(p => p._id === petId) || sender?.pets?.[0]
const greeting = `${sender?.nickName || '小伙伴'} 想认识你的宠物!${pet ? `TA 的 ${pet.name} 向你打了个招呼 🐾` : '来聊聊吧 🐾'}`
await db.collection('messages').add({
data: {
fromId: OPENID,
toId: toUserId,
content: greeting,
type: 'greeting',
isRead: false,
createdAt: db.serverDate(),
},
})
return { code: 0, data: null }
} catch (e) {
return { code: -1, message: e.message }
}
}

View File

@@ -0,0 +1 @@
{ "name": "sendGreeting", "version": "1.0.0", "main": "index.js", "dependencies": { "wx-server-sdk": "~2.1.2" } }

View File

@@ -0,0 +1,25 @@
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
exports.main = async (event, context) => {
const { OPENID } = cloud.getWXContext()
const { latitude, longitude } = event
if (!latitude || !longitude) return { code: -1, message: '缺少位置' }
try {
await db.collection('users')
.where({ openid: OPENID })
.update({
data: {
lastLocation: { latitude, longitude },
isOnline: true,
lastSeen: db.serverDate(),
},
})
return { code: 0, data: null }
} catch (e) {
return { code: -1, message: e.message }
}
}

View File

@@ -0,0 +1 @@
{ "name": "updateLocation", "version": "1.0.0", "main": "index.js", "dependencies": { "wx-server-sdk": "~2.1.2" } }