first commit

This commit is contained in:
ytc1012
2026-02-04 16:11:55 +08:00
commit 0f3ee050dc
165 changed files with 25795 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
// 云函数入口文件
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 wxContext = cloud.getWXContext()
const { roomId, nickName } = event
if (!roomId) {
return { success: false, msg: '缺少 roomId' }
}
try {
const roomRef = db.collection('rooms').doc(roomId)
const room = await roomRef.get()
if (!room.data) {
return { success: false, msg: '房间不存在' }
}
// 创建新成员(无位置,需要手动添加)
// 随机生成头像颜色
const avatarColors = [
'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0', // 微信默认头像
'https://thirdwx.qlogo.cn/mmopen/vi_32/POgEwh4mIHO4nibH0KlMECNjjGxQUq24ZEaGT4poC6icRiccVGKSyXwibcPq4BWmiaIGuG1icwxaQX6grC9VemZoJ8rg/132', // 示例头像
'/images/default-avatar.png' // 本地默认头像
]
const randomAvatar = avatarColors[Math.floor(Math.random() * avatarColors.length)]
const newMember = {
openid: `test_member_${Date.now()}`,
avatarUrl: randomAvatar,
nickName: nickName || '测试成员',
location: null, // 初始无位置
isTestMember: true, // 标记为测试成员
joinedAt: new Date()
}
await roomRef.update({
data: {
members: _.push(newMember)
}
})
return {
success: true,
msg: '成员添加成功',
member: newMember
}
} catch (err) {
console.error(err)
return {
success: false,
msg: '添加失败',
error: err
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
{
"name": "addTestMember",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {},
"author": "",
"license": "ISC",
"dependencies": {
"wx-server-sdk": "3.0.1"
}
}

View File

@@ -0,0 +1,152 @@
// 云函数入口文件
const cloud = require('wx-server-sdk')
const axios = require('axios')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
const _ = db.command
exports.main = async (event, context) => {
const { roomId } = event
try {
// 1. 获取房间信息
const room = await db.collection('rooms').doc(roomId).get()
const data = room.data
if (!data) return { success: false, msg: '房间不存在' }
// 检查是否正在计算中(防并发)
if (data.status === 'calculating') {
return {
success: false,
msg: '正在计算中,请稍候...',
isCalculating: true
}
}
// 2. 提取有效坐标
const validMembers = data.members.filter(m => m.location && m.location.lat)
const locations = validMembers.map(m => ({
lng: m.location.lng,
lat: m.location.lat,
address: m.location.address || '',
name: m.location.name || ''
}))
// 验证至少需要2个位置才能计算
if (locations.length < 2) {
return { success: false, msg: '至少需要2人添加位置才能计算推荐地点' }
}
// 检查成员列表是否有变化(防止成员未变时重复计算)
if (data.status === 'calculated' && data.result && data.result.success) {
// 比较当前成员位置的数量和组成
const currentMemberIds = validMembers
.map(m => `${m.openid}_${m.location.lat}_${m.location.lng}`)
.sort()
.join(',')
// 存储上次计算时的成员快照
const lastMemberIds = data.lastCalculatedMembers || ''
if (currentMemberIds === lastMemberIds) {
return {
success: false,
msg: '成员位置未变化,无需重复计算',
isDuplicate: true,
existingResult: data.result
}
}
}
// 加锁:设置状态为计算中
await db.collection('rooms').doc(roomId).update({
data: {
status: _.set('calculating'),
calculateStartTime: _.set(db.serverDate())
}
})
console.log(`Room ${roomId} 状态已锁定为 calculating`)
// 3. 配置后端服务 URL请替换为实际部署的后端服务地址
// 示例:
// - 本地开发: http://localhost:8000
// - Render 部署: https://meetspot-xxx.onrender.com
// - Railway 部署: https://meetspot-production.railway.app
const BACKEND_URL = process.env.BACKEND_URL || 'https://meetspot.onrender.com'
console.log(`调用后端服务: ${BACKEND_URL}/api/miniprogram/calculate`)
// 4. 使用 HTTP 调用后端服务
const response = await axios.post(
`${BACKEND_URL}/api/miniprogram/calculate`,
{
locations: locations,
keywords: data.keywords || '咖啡馆',
requirements: data.requirements || ''
},
{
headers: {
'Content-Type': 'application/json'
},
timeout: 30000 // 30秒超时
}
)
const result = response.data
if (response.status !== 200 || !result) {
console.error('Backend Error:', response.status, result)
return { success: false, msg: '计算服务异常' }
}
// 5. 将结果回写到数据库
if (result.success) {
// 生成成员位置快照用于下次比对
const memberSnapshot = validMembers
.map(m => `${m.openid}_${m.location.lat}_${m.location.lng}`)
.sort()
.join(',')
await db.collection('rooms').doc(roomId).update({
data: {
result: _.set(result), // 使用 _.set 强制覆写 result 字段
status: _.set('calculated'), // 使用 _.set 强制覆写 status 字段
lastCalculateTime: _.set(db.serverDate()), // 记录计算时间
lastCalculatedMembers: _.set(memberSnapshot) // 记录成员快照
}
})
} else {
// 计算失败,释放锁
await db.collection('rooms').doc(roomId).update({
data: {
status: _.set('active')
}
})
}
return result
} catch (err) {
console.error('CalculateMeetSpot Error:', err)
// 发生异常时,释放锁
try {
await db.collection('rooms').doc(roomId).update({
data: {
status: _.set('active')
}
})
} catch (unlockErr) {
console.error('Failed to unlock room:', unlockErr)
}
return {
success: false,
msg: '计算失败',
error: err.message || err.toString()
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
{
"name": "calculateMeetSpot",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"wx-server-sdk": "3.0.1",
"axios": "^1.6.0"
}
}

View File

@@ -0,0 +1,49 @@
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) // 使用当前云环境
const db = cloud.database()
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
const openid = wxContext.OPENID
const { userInfo, keywords, requirements, name, meetTime } = event
try {
const res = await db.collection('rooms').add({
data: {
_openid: openid, // 创建者openid
createdAt: db.serverDate(),
status: 'active',
name: name || '未命名聚会',
meetTime: meetTime || '',
keywords: keywords || '咖啡馆',
requirements: requirements || '',
members: [{
openid: openid,
avatarUrl: userInfo?.avatarUrl || 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
nickName: userInfo?.nickName || '发起人',
location: null, // 初始无位置
joinedAt: new Date()
}],
result: null
}
})
return {
success: true,
roomId: res._id,
msg: '创建成功'
}
} catch (err) {
console.error(err)
return {
success: false,
msg: '创建房间失败',
error: err
}
}
}

1291
cloudfunctions/createRoom/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
{
"name": "createRoom",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"wx-server-sdk": "3.0.1"
}
}

View File

@@ -0,0 +1,5 @@
{
"permissions": {
"openapi": []
}
}

View File

@@ -0,0 +1,55 @@
// 云函数入口文件
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 wxContext = cloud.getWXContext()
const openid = wxContext.OPENID
const { roomId } = event
if (!roomId) {
return {
success: false,
msg: '房间ID不能为空'
}
}
try {
// 查询房间信息
const room = await db.collection('rooms').doc(roomId).get()
if (!room.data) {
return {
success: false,
msg: '房间不存在'
}
}
// 检查是否是创建者
if (room.data._openid !== openid) {
return {
success: false,
msg: '只有创建者可以删除聚会'
}
}
// 删除房间
await db.collection('rooms').doc(roomId).remove()
return {
success: true,
msg: '删除成功'
}
} catch (err) {
console.error(err)
return {
success: false,
msg: '删除失败',
error: err
}
}
}

View File

@@ -0,0 +1,9 @@
{
"name": "deleteRoom",
"version": "1.0.0",
"description": "删除聚会",
"main": "index.js",
"dependencies": {
"wx-server-sdk": "~3.0.1"
}
}

View File

@@ -0,0 +1,5 @@
{
"permissions": {
"openapi": []
}
}

View File

@@ -0,0 +1,13 @@
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
return {
openid: wxContext.OPENID
}
}

View File

@@ -0,0 +1,9 @@
{
"name": "getOpenId",
"version": "1.0.0",
"description": "获取用户OpenID",
"main": "index.js",
"dependencies": {
"wx-server-sdk": "~3.0.1"
}
}

View File

@@ -0,0 +1,60 @@
// 云函数入口文件
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 wxContext = cloud.getWXContext()
const openid = wxContext.OPENID
const { roomId, userInfo } = event
if (!roomId) {
return { success: false, msg: '缺少 roomId' }
}
try {
const roomRef = db.collection('rooms').doc(roomId)
const room = await roomRef.get()
if (!room.data) {
return { success: false, msg: '房间不存在' }
}
// 检查是否已经在房间内
const members = room.data.members || []
const isMember = members.some(m => m.openid === openid)
if (!isMember) {
// 加入房间
const newMember = {
openid: openid,
avatarUrl: userInfo?.avatarUrl || 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
nickName: userInfo?.nickName || '微信用户',
location: null,
joinedAt: new Date()
}
await roomRef.update({
data: {
members: _.push(newMember)
}
})
}
return {
success: true,
msg: '加入成功'
}
} catch (err) {
console.error(err)
return {
success: false,
msg: '加入失败',
error: err
}
}
}

1290
cloudfunctions/joinRoom/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
{
"name": "joinRoom",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"wx-server-sdk": "3.0.1"
}
}

View File

@@ -0,0 +1,80 @@
// 云函数入口文件
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 wxContext = cloud.getWXContext()
const openid = wxContext.OPENID
const { roomId, memberOpenid } = event
if (!roomId) {
return { success: false, msg: '缺少 roomId' }
}
try {
const roomRef = db.collection('rooms').doc(roomId)
const room = await roomRef.get()
if (!room.data) {
return { success: false, msg: '房间不存在' }
}
const roomData = room.data
const members = roomData.members || []
// 确定要移除的成员
const targetOpenid = memberOpenid || openid // 如果没有指定,默认移除自己
// 检查是否在房间内
const memberIndex = members.findIndex(m => m.openid === targetOpenid)
if (memberIndex === -1) {
return { success: false, msg: '该成员不在房间内' }
}
// 权限检查:只有创建者可以移除其他人,任何人都可以退出自己
if (targetOpenid !== openid && roomData._openid !== openid) {
return { success: false, msg: '只有创建者可以移除其他成员' }
}
// 检查是否是创建者试图退出
if (targetOpenid === roomData._openid) {
return { success: false, msg: '创建者不能退出,请删除聚会' }
}
// 移除成员
const updatedMembers = members.filter(m => m.openid !== targetOpenid)
await roomRef.update({
data: {
members: _.set(updatedMembers)
}
})
// 如果房间已有计算结果,清除结果(因为成员变化了)
if (roomData.status === 'calculated') {
await roomRef.update({
data: {
status: _.set('active'),
lastCalculatedMembers: _.set('') // 清除成员快照
}
})
}
return {
success: true,
msg: targetOpenid === openid ? '已退出聚会' : '已移除成员'
}
} catch (err) {
console.error('RemoveMember Error:', err)
return {
success: false,
msg: '操作失败',
error: err.message || err.toString()
}
}
}

View File

@@ -0,0 +1,12 @@
{
"name": "removeMember",
"version": "1.0.0",
"description": "移除聚会成员",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"wx-server-sdk": "3.0.1"
}
}

View File

@@ -0,0 +1,60 @@
// 云函数入口文件
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 wxContext = cloud.getWXContext()
const openid = wxContext.OPENID
const { roomId, location } = event
// location format: { lng: 116.1, lat: 39.1, address: "...", name: "..." }
if (!roomId || !location) {
return { success: false, msg: '缺少参数' }
}
try {
// 2026-02-02 Fix: 使用 Read-Modify-Write 模式避免复杂更新操作符报错
// 先读取房间数据
const roomRes = await db.collection('rooms').doc(roomId).get()
const roomData = roomRes.data
if (!roomData) {
return { success: false, msg: '房间不存在' }
}
const members = roomData.members || []
const memberIndex = members.findIndex(m => m.openid === openid)
if (memberIndex === -1) {
return { success: false, msg: '您尚未加入该房间' }
}
// 直接修改内存中的数组对象
members[memberIndex].location = location
// 2026-02-02 Fix: 使用 _.set 强制覆写 members 字段,解决 DuplicateWrite 索引冲突问题
// 这通常是因为数据结构不一致或底层索引冲突导致的set 指令会完全替换字段值
await db.collection('rooms').doc(roomId).update({
data: {
members: _.set(members)
}
})
return {
success: true,
msg: '位置更新成功'
}
} catch (err) {
console.error(err)
return {
success: false,
msg: '位置更新失败',
error: err
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
{
"name": "updateLocation",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"wx-server-sdk": "3.0.1"
}
}

View File

@@ -0,0 +1,53 @@
// 云函数入口文件
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 { roomId, memberOpenid, location } = event
if (!roomId || !memberOpenid || !location) {
return { success: false, msg: '缺少必要参数' }
}
try {
const roomRef = db.collection('rooms').doc(roomId)
const room = await roomRef.get()
if (!room.data) {
return { success: false, msg: '房间不存在' }
}
// 查找成员索引
const members = room.data.members || []
const memberIndex = members.findIndex(m => m.openid === memberOpenid)
if (memberIndex === -1) {
return { success: false, msg: '成员不存在' }
}
// 更新成员位置
members[memberIndex].location = location
await roomRef.update({
data: {
members: _.set(members) // 使用 _.set 强制覆写 members 字段
}
})
return {
success: true,
msg: '位置更新成功'
}
} catch (err) {
console.error(err)
return {
success: false,
msg: '更新失败',
error: err
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
{
"name": "updateMemberLocation",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {},
"author": "",
"license": "ISC",
"dependencies": {
"wx-server-sdk": "3.0.1"
}
}

View File

@@ -0,0 +1,5 @@
{
"permissions": {
"openapi": []
}
}

View File

@@ -0,0 +1,52 @@
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
const openid = wxContext.OPENID
const { roomId, name, meetTime, keywords } = event
if (!roomId) {
return {
success: false,
msg: '房间ID不能为空'
}
}
try {
// 构建更新数据
const updateData = {}
if (name !== undefined) {
updateData.name = name
}
if (meetTime !== undefined) {
updateData.meetTime = meetTime
}
if (keywords !== undefined) {
updateData.keywords = keywords
}
// 更新房间信息
await db.collection('rooms').doc(roomId).update({
data: updateData
})
return {
success: true,
msg: '更新成功'
}
} catch (err) {
console.error(err)
return {
success: false,
msg: '更新失败',
error: err
}
}
}

View File

@@ -0,0 +1,9 @@
{
"name": "updateRoomInfo",
"version": "1.0.0",
"description": "更新聚会信息(名称、时间)",
"main": "index.js",
"dependencies": {
"wx-server-sdk": "~3.0.1"
}
}