修复动态发布与互动问题
This commit is contained in:
@@ -3,14 +3,148 @@ const cloud = require('wx-server-sdk')
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
const _ = db.command
|
||||
const SERVER_MESSAGE_LIMIT = 500
|
||||
const CONVERSATIONS_COLLECTION = 'conversations'
|
||||
const MESSAGES_COLLECTION = 'messages'
|
||||
const CHAT_MESSAGE_TEMPLATE_ID = process.env.WQ_MESSAGE_TEMPLATE_ID || process.env.TARO_APP_MESSAGE_TEMPLATE_ID || ''
|
||||
|
||||
function isCollectionMissing(error) {
|
||||
const text = String(error?.errMsg || error?.message || error || '')
|
||||
return (
|
||||
text.includes('collection not exist') ||
|
||||
text.includes('collection not exists') ||
|
||||
text.includes('collection is not exists') ||
|
||||
text.includes('DATABASE_COLLECTION_NOT_EXIST') ||
|
||||
text.includes('-502005')
|
||||
)
|
||||
}
|
||||
|
||||
function isCollectionAlreadyExists(error) {
|
||||
const text = String(error?.errMsg || error?.message || error || '')
|
||||
return text.includes('collection already exists') || text.includes('DATABASE_COLLECTION_ALREADY_EXIST')
|
||||
}
|
||||
|
||||
async function ensureCollection(name) {
|
||||
if (typeof db.createCollection !== 'function') {
|
||||
throw new Error(`${name} collection does not exist`)
|
||||
}
|
||||
|
||||
try {
|
||||
await db.createCollection(name)
|
||||
} catch (error) {
|
||||
if (!isCollectionAlreadyExists(error)) throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function getExistingConversation(openid, toOpenid) {
|
||||
try {
|
||||
return await db.collection(CONVERSATIONS_COLLECTION)
|
||||
.where({ type: 'single', memberOpenids: _.all([openid, toOpenid]) })
|
||||
.limit(1)
|
||||
.get()
|
||||
} catch (error) {
|
||||
if (!isCollectionMissing(error)) throw error
|
||||
await ensureCollection(CONVERSATIONS_COLLECTION)
|
||||
return { data: [] }
|
||||
}
|
||||
}
|
||||
|
||||
async function getConversationById(conversationId) {
|
||||
try {
|
||||
return await db.collection(CONVERSATIONS_COLLECTION).doc(conversationId).get()
|
||||
} catch (error) {
|
||||
if (!isCollectionMissing(error)) throw error
|
||||
return { data: null }
|
||||
}
|
||||
}
|
||||
|
||||
async function addConversation(data) {
|
||||
try {
|
||||
return await db.collection(CONVERSATIONS_COLLECTION).add({ data })
|
||||
} catch (error) {
|
||||
if (!isCollectionMissing(error)) throw error
|
||||
await ensureCollection(CONVERSATIONS_COLLECTION)
|
||||
return db.collection(CONVERSATIONS_COLLECTION).add({ data })
|
||||
}
|
||||
}
|
||||
|
||||
async function addMessage(data) {
|
||||
try {
|
||||
return await db.collection(MESSAGES_COLLECTION).add({ data })
|
||||
} catch (error) {
|
||||
if (!isCollectionMissing(error)) throw error
|
||||
await ensureCollection(MESSAGES_COLLECTION)
|
||||
return db.collection(MESSAGES_COLLECTION).add({ data })
|
||||
}
|
||||
}
|
||||
|
||||
async function trimConversationMessages(conversationId) {
|
||||
for (;;) {
|
||||
const oldMessages = await db.collection(MESSAGES_COLLECTION)
|
||||
.where({ conversationId })
|
||||
.orderBy('createdAt', 'desc')
|
||||
.skip(SERVER_MESSAGE_LIMIT)
|
||||
.limit(100)
|
||||
.get()
|
||||
.catch(() => ({ data: [] }))
|
||||
|
||||
if (!oldMessages.data.length) return
|
||||
|
||||
await Promise.all(
|
||||
oldMessages.data.map(item =>
|
||||
db.collection(MESSAGES_COLLECTION).doc(item._id).remove().catch(() => null)
|
||||
)
|
||||
)
|
||||
|
||||
if (oldMessages.data.length < 100) return
|
||||
}
|
||||
}
|
||||
|
||||
function pad(value) {
|
||||
return String(value).padStart(2, '0')
|
||||
}
|
||||
|
||||
function formatDateTime(ts) {
|
||||
const d = new Date(ts)
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
|
||||
}
|
||||
|
||||
function templateValue(value, max = 20) {
|
||||
const text = String(value || '').trim()
|
||||
return text.length > max ? `${text.slice(0, max - 1)}…` : text
|
||||
}
|
||||
|
||||
async function sendChatNotifications({ recipients, conversationId, senderName, content, type, createdAt }) {
|
||||
if (!CHAT_MESSAGE_TEMPLATE_ID || !recipients.length || !cloud.openapi?.subscribeMessage?.send) return
|
||||
|
||||
const preview = type === 'image' ? '[图片]' : content
|
||||
await Promise.all(
|
||||
recipients.map(touser =>
|
||||
cloud.openapi.subscribeMessage.send({
|
||||
touser,
|
||||
templateId: CHAT_MESSAGE_TEMPLATE_ID,
|
||||
page: `pages/chat/index?conversationId=${conversationId}`,
|
||||
data: {
|
||||
thing31: { value: templateValue(senderName || '汪友') },
|
||||
thing2: { value: templateValue(preview || '你收到一条新消息') },
|
||||
time3: { value: formatDateTime(createdAt) }
|
||||
}
|
||||
}).catch(error => {
|
||||
console.log('[messageSend] subscribe message skipped', error?.errMsg || error?.message || error)
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
const content = String(event.content || '').trim()
|
||||
if (!content) throw new Error('content is required')
|
||||
if (!OPENID) throw new Error('no openid in wx context')
|
||||
|
||||
const now = Date.now()
|
||||
let convId = event.conversationId
|
||||
let conversation = null
|
||||
|
||||
// Create a 1:1 conversation if not provided
|
||||
if (!convId) {
|
||||
@@ -19,53 +153,64 @@ exports.main = async event => {
|
||||
if (!toOpenid && event.toUserId) {
|
||||
const toUserById = await db.collection('users').doc(event.toUserId).get().catch(() => null)
|
||||
if (toUserById && toUserById.data) toOpenid = toUserById.data.openid
|
||||
if (!toOpenid) {
|
||||
const toUserByOpenid = await db.collection('users')
|
||||
.where({ openid: event.toUserId })
|
||||
.limit(1)
|
||||
.get()
|
||||
.catch(() => ({ data: [] }))
|
||||
if (toUserByOpenid.data[0]) toOpenid = toUserByOpenid.data[0].openid
|
||||
}
|
||||
}
|
||||
if (!toOpenid) throw new Error('conversationId or toOpenid/toUserId is required')
|
||||
if (!toOpenid) throw new Error('target user not found')
|
||||
|
||||
// Look for existing conversation between these two users
|
||||
const existing = await db.collection('conversations')
|
||||
.where({ type: 'single', memberOpenids: _.all([OPENID, toOpenid]) })
|
||||
.limit(1)
|
||||
.get()
|
||||
const existing = await getExistingConversation(OPENID, toOpenid)
|
||||
|
||||
if (existing.data.length) {
|
||||
convId = existing.data[0]._id
|
||||
conversation = existing.data[0]
|
||||
convId = conversation._id
|
||||
} else {
|
||||
const toUserRes = await db.collection('users').where({ openid: toOpenid }).limit(1).get()
|
||||
const toUser = toUserRes.data[0] || { nickname: '用户', avatarKey: 'gradient-avatar-1' }
|
||||
const newConv = await db.collection('conversations').add({
|
||||
data: {
|
||||
type: 'single',
|
||||
memberOpenids: [OPENID, toOpenid],
|
||||
unreadOpenids: [toOpenid],
|
||||
unreadCounts: { [toOpenid]: 1 },
|
||||
title: toUser.nickname,
|
||||
avatarKey: toUser.avatarKey,
|
||||
lastMessage: { content: content.slice(0, 50), createdAt: now },
|
||||
pinned: false,
|
||||
muted: false,
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
}
|
||||
})
|
||||
conversation = {
|
||||
type: 'single',
|
||||
memberOpenids: [OPENID, toOpenid],
|
||||
unreadOpenids: [],
|
||||
unreadCounts: {},
|
||||
title: toUser.nickname,
|
||||
avatarKey: toUser.avatarKey,
|
||||
lastMessage: { content: content.slice(0, 50), createdAt: now },
|
||||
pinned: false,
|
||||
muted: false,
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
}
|
||||
const newConv = await addConversation(conversation)
|
||||
convId = newConv._id
|
||||
}
|
||||
}
|
||||
|
||||
if (!conversation) {
|
||||
const convRes = await getConversationById(convId)
|
||||
conversation = convRes.data
|
||||
}
|
||||
if (!conversation) throw new Error('conversation not found')
|
||||
|
||||
const members = conversation.memberOpenids || []
|
||||
if (!members.includes(OPENID)) throw new Error('permission denied')
|
||||
if ((conversation.type || 'single') !== 'single') throw new Error('unsupported conversation type')
|
||||
|
||||
// Add message to messages collection
|
||||
const msgResult = await db.collection('messages').add({
|
||||
data: {
|
||||
conversationId: convId,
|
||||
fromOpenid: OPENID,
|
||||
content: content.slice(0, 1000),
|
||||
type: event.msgType || 'text',
|
||||
createdAt: now
|
||||
}
|
||||
const msgResult = await addMessage({
|
||||
conversationId: convId,
|
||||
fromOpenid: OPENID,
|
||||
content: content.slice(0, 1000),
|
||||
type: event.msgType || 'text',
|
||||
createdAt: now
|
||||
})
|
||||
|
||||
// Update conversation: last message + unread counts for all recipients
|
||||
const convRes = await db.collection('conversations').doc(convId).get()
|
||||
const members = convRes.data.memberOpenids || []
|
||||
const recipients = members.filter(id => id !== OPENID)
|
||||
|
||||
const updateData = {
|
||||
@@ -78,11 +223,21 @@ exports.main = async event => {
|
||||
})
|
||||
|
||||
// Rebuild unreadOpenids to include all recipients (remove duplicates)
|
||||
const existingUnread = convRes.data.unreadOpenids || []
|
||||
const existingUnread = conversation.unreadOpenids || []
|
||||
const newUnread = [...new Set([...existingUnread, ...recipients])]
|
||||
updateData.unreadOpenids = newUnread
|
||||
|
||||
await db.collection('conversations').doc(convId).update({ data: updateData })
|
||||
await db.collection(CONVERSATIONS_COLLECTION).doc(convId).update({ data: updateData })
|
||||
await trimConversationMessages(convId)
|
||||
const senderRes = await db.collection('users').where({ openid: OPENID }).limit(1).get().catch(() => ({ data: [] }))
|
||||
await sendChatNotifications({
|
||||
recipients,
|
||||
conversationId: convId,
|
||||
senderName: senderRes.data[0]?.nickname || conversation.title || '汪友',
|
||||
content,
|
||||
type: event.msgType || 'text',
|
||||
createdAt: now
|
||||
})
|
||||
|
||||
return { messageId: msgResult._id, conversationId: convId }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user