const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database() const _ = db.command const SERVER_MESSAGE_LIMIT = 50 function timeText(ts) { if (!ts) return '' const d = new Date(ts) const hh = String(d.getHours()).padStart(2, '0') const mm = String(d.getMinutes()).padStart(2, '0') const sameDay = new Date().toDateString() === d.toDateString() if (sameDay) return `${hh}:${mm}` return `${d.getMonth() + 1}/${d.getDate()} ${hh}:${mm}` } async function userToPeer(u) { return { id: u._id, name: u.nickname || '用户', avatarKey: u.avatarKey || 'gradient-avatar-1', avatarUrl: u.avatarUrl || '' } } function normalizeLimit(value) { const limit = Number(value || SERVER_MESSAGE_LIMIT) if (!Number.isFinite(limit)) return SERVER_MESSAGE_LIMIT return Math.max(1, Math.min(Math.floor(limit), SERVER_MESSAGE_LIMIT)) } function messageToClient(m, openid) { return { _id: m._id, fromMe: m.fromOpenid === openid, content: m.content, type: m.type || 'text', createdAt: m.createdAt, timeText: timeText(m.createdAt) } } exports.main = async event => { const { OPENID } = cloud.getWXContext() if (!OPENID) throw new Error('no openid in wx context') let convId = event.conversationId || '' let peer = null const pageSize = normalizeLimit(event.pageSize || event.limit) // Open by target user — resolve peer and find an existing conversation. if (!convId && event.targetUserId) { const tRes = await db.collection('users').doc(event.targetUserId).get().catch(() => null) const t = tRes && tRes.data if (t) { peer = await userToPeer(t) const ex = await db.collection('conversations') .where({ type: 'single', memberOpenids: _.all([OPENID, t.openid]) }) .limit(1) .get() .catch(() => ({ data: [] })) if (ex.data.length) convId = ex.data[0]._id } } // Open by conversation — resolve the other member as peer. if (convId && !peer) { const c = await db.collection('conversations').doc(convId).get().catch(() => null) if (c && c.data) { const otherOpenid = (c.data.memberOpenids || []).find(o => o !== OPENID) if (otherOpenid) { const oRes = await db.collection('users').where({ openid: otherOpenid }).limit(1).get() if (oRes.data[0]) peer = await userToPeer(oRes.data[0]) } if (!peer) { peer = { id: '', name: c.data.title || '对话', avatarKey: c.data.avatarKey || 'gradient-avatar-1', avatarUrl: '' } } } } let messages = [] let nextCursor = '' if (convId) { const query = { conversationId: convId } const cursor = Number(event.cursor || 0) if (Number.isFinite(cursor) && cursor > 0) query.createdAt = _.lt(cursor) const res = await db.collection('messages') .where(query) .orderBy('createdAt', 'desc') .limit(pageSize + 1) .get() .catch(() => ({ data: [] })) const page = res.data.slice(0, pageSize) messages = page.reverse().map(m => messageToClient(m, OPENID)) if (res.data.length > pageSize && messages[0]) { nextCursor = String(messages[0].createdAt || '') } // Mark this conversation read for me only when the client asks for it. // Silent polling and loading older history should not write the conversation. if (event.markRead !== false) { await db.collection('conversations').doc(convId).update({ data: { unreadOpenids: _.pull(OPENID), [`unreadCounts.${OPENID}`]: 0 } }).catch(() => {}) } } return { conversationId: convId, peer, messages, nextCursor } }