2025-06-30 08:00:06 +00:00
|
|
|
|
// src/db.js
|
|
|
|
|
import Dexie from 'dexie';
|
|
|
|
|
|
|
|
|
|
// 创建数据库实例
|
|
|
|
|
export const db = new Dexie('chatHistory');
|
|
|
|
|
|
|
|
|
|
// 定义数据库结构
|
|
|
|
|
db.version(2).stores({
|
|
|
|
|
// 表名: 索引字段列表
|
|
|
|
|
// ++id 表示自增主键
|
|
|
|
|
// name, age 表示这些字段将被索引
|
|
|
|
|
// friends: '++id, name, age',
|
|
|
|
|
|
|
|
|
|
// 聊天记录表
|
|
|
|
|
// 为常用查询字段创建索引
|
|
|
|
|
messages: 'msg_id, sequence, talk_type, msg_type, user_id, receiver_id, is_read, created_at',
|
|
|
|
|
|
|
|
|
|
// 会话表(包含私聊和群聊)
|
|
|
|
|
// 为常用查询字段创建索引
|
|
|
|
|
conversations: 'id, talk_type, receiver_id, index_name, updated_at, unread_num'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 数据库升级处理
|
|
|
|
|
db.on('ready', function() {
|
|
|
|
|
// 检查是否需要从服务器同步数据
|
|
|
|
|
console.log('数据库已就绪,版本:', db.verno);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 消息类型常量
|
|
|
|
|
*/
|
|
|
|
|
export const MessageType = {
|
|
|
|
|
TEXT: 1, // 文本消息
|
|
|
|
|
IMAGE: 2, // 图片消息
|
|
|
|
|
FILE: 3, // 文件消息
|
|
|
|
|
AUDIO: 4, // 语音消息
|
|
|
|
|
VIDEO: 5, // 视频消息
|
|
|
|
|
LOCATION: 6, // 位置消息
|
|
|
|
|
CARD: 7, // 名片消息
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 会话类型常量
|
|
|
|
|
*/
|
|
|
|
|
export const TalkType = {
|
|
|
|
|
PRIVATE: 1, // 私聊
|
|
|
|
|
GROUP: 2, // 群聊
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 添加一条聊天记录
|
|
|
|
|
* @param {Object} message - 消息对象
|
|
|
|
|
* @returns {Promise<string>} - 返回消息ID
|
|
|
|
|
*/
|
|
|
|
|
export async function addMessage(message) {
|
|
|
|
|
try {
|
|
|
|
|
// 确保消息有唯一ID
|
|
|
|
|
if (!message.msg_id) {
|
|
|
|
|
message.msg_id = generateUUID();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 添加时间戳(如果没有)
|
|
|
|
|
if (!message.created_at) {
|
|
|
|
|
message.created_at = new Date().toISOString().replace('T', ' ').substring(0, 19);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 添加到数据库
|
|
|
|
|
const id = await db.messages.add(message);
|
|
|
|
|
return message.msg_id;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('添加消息失败:', error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 批量添加或更新聊天记录
|
|
|
|
|
* @param {Array} messages - 消息对象数组
|
|
|
|
|
* @param {number} talkType - 会话类型(1:私聊, 2:群聊)
|
|
|
|
|
* @param {number} receiverId - 接收者ID(私聊为对方用户ID,群聊为群ID)
|
|
|
|
|
* @param {boolean} replaceExisting - 是否替换已存在的消息
|
|
|
|
|
* @param {string} sortKey - 排序和比较的键,可选值:'sequence'或'created_at'
|
|
|
|
|
* @returns {Promise<{added: number, updated: number, total: number}>} - 返回添加和更新的消息数量
|
|
|
|
|
*/
|
|
|
|
|
export async function batchAddOrUpdateMessages(messages, talkType, receiverId, replaceExisting = true, sortKey = 'sequence') {
|
|
|
|
|
try {
|
|
|
|
|
if (!Array.isArray(messages) || messages.length === 0) {
|
|
|
|
|
return { added: 0, updated: 0, total: 0 };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取现有消息
|
|
|
|
|
let existingMessages = [];
|
|
|
|
|
if (replaceExisting) {
|
|
|
|
|
// 不传入 maxSequence 参数,获取所有消息
|
|
|
|
|
existingMessages = await getMessages(talkType, null, receiverId, 1000, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建msg_id到消息的映射,用于快速查找
|
|
|
|
|
const existingMap = new Map();
|
|
|
|
|
existingMessages.forEach(msg => {
|
|
|
|
|
existingMap.set(msg.msg_id, msg);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 创建sequence/created_at到消息的映射,用于比较和替换
|
|
|
|
|
const existingKeyMap = new Map();
|
|
|
|
|
existingMessages.forEach(msg => {
|
|
|
|
|
if (msg[sortKey]) {
|
|
|
|
|
existingKeyMap.set(msg[sortKey], msg);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let added = 0;
|
|
|
|
|
let updated = 0;
|
|
|
|
|
|
|
|
|
|
// 批量处理事务
|
|
|
|
|
await db.transaction('rw', db.messages, async () => {
|
|
|
|
|
for (const message of messages) {
|
|
|
|
|
// 确保消息有必要的字段
|
|
|
|
|
if (!message.msg_id) {
|
|
|
|
|
message.msg_id = generateUUID();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!message.created_at) {
|
|
|
|
|
message.created_at = new Date().toISOString().replace('T', ' ').substring(0, 19);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查消息是否已存在
|
|
|
|
|
const existing = existingMap.get(message.msg_id);
|
|
|
|
|
if (existing) {
|
|
|
|
|
// 更新现有消息,使用 msg_id 而不是 id
|
|
|
|
|
await db.messages.update(existing.msg_id, message);
|
|
|
|
|
updated++;
|
|
|
|
|
} else if (replaceExisting && message[sortKey] && existingKeyMap.has(message[sortKey])) {
|
|
|
|
|
// 根据sortKey替换现有消息
|
|
|
|
|
const existingByKey = existingKeyMap.get(message[sortKey]);
|
|
|
|
|
await db.messages.update(existingByKey.msg_id, message);
|
|
|
|
|
updated++;
|
|
|
|
|
} else {
|
|
|
|
|
console.log('message',message)
|
|
|
|
|
// 添加新消息
|
|
|
|
|
await db.messages.add(message);
|
|
|
|
|
added++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 如果有新消息添加,更新会话的最后一条消息
|
|
|
|
|
if (added > 0 || updated > 0) {
|
|
|
|
|
// 假设消息数组中的最后一条消息是最新的(保持原始顺序)
|
|
|
|
|
// 如果需要找出最新消息,可以使用数组中的最后一条消息
|
|
|
|
|
const latestMessage = messages[messages.length - 1];
|
|
|
|
|
|
|
|
|
|
// 更新会话的最后一条消息
|
|
|
|
|
await updateConversationLastMessage(latestMessage);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { added, updated, total: added + updated };
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('批量添加或更新消息失败:', error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取指定会话的聊天记录
|
|
|
|
|
* @param {number} talkType - 会话类型(1:私聊, 2:群聊)
|
|
|
|
|
* @param {number} userId - 当前用户ID
|
|
|
|
|
* @param {number} receiverId - 接收者ID(私聊为对方用户ID,群聊为群ID)
|
|
|
|
|
* @param {number} limit - 限制返回的记录数量
|
|
|
|
|
* @param {number} offset - 偏移量,用于分页
|
|
|
|
|
* @returns {Promise<Array>} - 返回消息列表
|
|
|
|
|
*/
|
|
|
|
|
/**
|
|
|
|
|
* 获取消息列表
|
|
|
|
|
* @param {number} talkType - 会话类型(1:私聊, 2:群聊)
|
|
|
|
|
* @param {number} userId - 用户ID(私聊时用于区分消息方向)
|
|
|
|
|
* @param {number} receiverId - 接收者ID(私聊时为对方ID,群聊时为群ID)
|
|
|
|
|
* @param {number} limit - 每页条数
|
|
|
|
|
* @param {number} offset - 偏移量
|
|
|
|
|
* @param {number} maxSequence - 最大sequence值,用于分页加载更早的消息
|
|
|
|
|
* @returns {Promise<Array>} - 消息列表
|
|
|
|
|
*/
|
|
|
|
|
export async function getMessages(talkType, userId, receiverId, limit = 30, offset = 0, maxSequence = null) {
|
|
|
|
|
try {
|
|
|
|
|
let query;
|
|
|
|
|
|
|
|
|
|
if (talkType === TalkType.PRIVATE) {
|
|
|
|
|
// 私聊消息(双向获取)
|
|
|
|
|
query = db.messages
|
|
|
|
|
.where('talk_type').equals(TalkType.PRIVATE)
|
|
|
|
|
.and(item => {
|
|
|
|
|
return (item.user_id === userId && item.receiver_id === receiverId) ||
|
|
|
|
|
(item.user_id === receiverId && item.receiver_id === userId);
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// 群聊消息
|
|
|
|
|
query = db.messages
|
|
|
|
|
.where('talk_type').equals(TalkType.GROUP)
|
|
|
|
|
.and(item => item.receiver_id === receiverId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果提供了maxSequence,则只获取sequence小于该值的消息
|
|
|
|
|
if (maxSequence !== null) {
|
|
|
|
|
query = query.and(item => item.sequence < maxSequence);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 按sequence升序排序
|
|
|
|
|
let messages = await query
|
|
|
|
|
.sortBy('sequence');
|
|
|
|
|
|
|
|
|
|
// 获取尾部的limit条记录(最新的消息)
|
|
|
|
|
if (messages.length > limit) {
|
|
|
|
|
messages = messages.slice(Math.max(0, messages.length - limit));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return messages;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取消息失败:', error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 标记消息为已读
|
|
|
|
|
* @param {string} msgId - 消息ID
|
|
|
|
|
* @returns {Promise<boolean>} - 操作是否成功
|
|
|
|
|
*/
|
|
|
|
|
export async function markMessageAsRead(msgId) {
|
|
|
|
|
try {
|
|
|
|
|
await db.messages.update(msgId, { is_read: 1 });
|
|
|
|
|
return true;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('标记消息已读失败:', error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 批量标记消息为已读
|
|
|
|
|
* @param {number} talkType - 会话类型
|
|
|
|
|
* @param {number} userId - 当前用户ID
|
|
|
|
|
* @param {number} receiverId - 接收者ID
|
|
|
|
|
* @returns {Promise<number>} - 返回更新的消息数量
|
|
|
|
|
*/
|
|
|
|
|
export async function markMessagesAsRead(talkType, userId, receiverId) {
|
|
|
|
|
try {
|
|
|
|
|
let count;
|
|
|
|
|
|
|
|
|
|
if (talkType === TalkType.PRIVATE) {
|
|
|
|
|
// 私聊消息(只标记对方发给我的消息)
|
|
|
|
|
count = await db.messages
|
|
|
|
|
.where('talk_type').equals(TalkType.PRIVATE)
|
|
|
|
|
.and(item => item.user_id === receiverId && item.receiver_id === userId)
|
|
|
|
|
.and(item => item.is_read === 0)
|
|
|
|
|
.modify({ is_read: 1 });
|
|
|
|
|
} else {
|
|
|
|
|
// 群聊消息(标记该群的所有未读消息)
|
|
|
|
|
count = await db.messages
|
|
|
|
|
.where('talk_type').equals(TalkType.GROUP)
|
|
|
|
|
.and(item => item.receiver_id === receiverId)
|
|
|
|
|
.and(item => item.is_read === 0 && item.user_id !== userId)
|
|
|
|
|
.modify({ is_read: 1 });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return count;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('批量标记消息已读失败:', error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 撤回消息
|
|
|
|
|
* @param {string} msgId - 消息ID
|
|
|
|
|
* @returns {Promise<boolean>} - 操作是否成功
|
|
|
|
|
*/
|
|
|
|
|
export async function revokeMessage(msgId) {
|
|
|
|
|
try {
|
|
|
|
|
await db.messages.update(msgId, { is_revoke: 1 });
|
|
|
|
|
return true;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('撤回消息失败:', error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 删除消息
|
|
|
|
|
* @param {string} msgId - 消息ID
|
|
|
|
|
* @returns {Promise<boolean>} - 操作是否成功
|
|
|
|
|
*/
|
|
|
|
|
export async function deleteMessage(msgId) {
|
|
|
|
|
try {
|
|
|
|
|
await db.messages.delete(msgId);
|
|
|
|
|
return true;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('删除消息失败:', error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取未读消息数量
|
|
|
|
|
* @param {number} userId - 当前用户ID
|
|
|
|
|
* @returns {Promise<Object>} - 返回未读消息统计信息
|
|
|
|
|
*/
|
|
|
|
|
export async function getUnreadCount(userId) {
|
|
|
|
|
try {
|
|
|
|
|
// 私聊未读消息数量
|
|
|
|
|
const privateUnread = await db.messages
|
|
|
|
|
.where('talk_type').equals(TalkType.PRIVATE)
|
|
|
|
|
.and(item => item.receiver_id === userId && item.is_read === 0)
|
|
|
|
|
.count();
|
|
|
|
|
|
|
|
|
|
// 群聊未读消息数量
|
|
|
|
|
const groupUnread = await db.messages
|
|
|
|
|
.where('talk_type').equals(TalkType.GROUP)
|
|
|
|
|
.and(item => item.user_id !== userId && item.is_read === 0)
|
|
|
|
|
.count();
|
|
|
|
|
|
|
|
|
|
// 按会话分组的未读消息数量
|
|
|
|
|
const privateUnreadBySession = await db.messages
|
|
|
|
|
.where('talk_type').equals(TalkType.PRIVATE)
|
|
|
|
|
.and(item => item.receiver_id === userId && item.is_read === 0)
|
|
|
|
|
.toArray()
|
|
|
|
|
.then(messages => {
|
|
|
|
|
const result = {};
|
|
|
|
|
messages.forEach(msg => {
|
|
|
|
|
if (!result[msg.user_id]) {
|
|
|
|
|
result[msg.user_id] = 0;
|
|
|
|
|
}
|
|
|
|
|
result[msg.user_id]++;
|
|
|
|
|
});
|
|
|
|
|
return result;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 按群组分组的未读消息数量
|
|
|
|
|
const groupUnreadBySession = await db.messages
|
|
|
|
|
.where('talk_type').equals(TalkType.GROUP)
|
|
|
|
|
.and(item => item.user_id !== userId && item.is_read === 0)
|
|
|
|
|
.toArray()
|
|
|
|
|
.then(messages => {
|
|
|
|
|
const result = {};
|
|
|
|
|
messages.forEach(msg => {
|
|
|
|
|
if (!result[msg.receiver_id]) {
|
|
|
|
|
result[msg.receiver_id] = 0;
|
|
|
|
|
}
|
|
|
|
|
result[msg.receiver_id]++;
|
|
|
|
|
});
|
|
|
|
|
return result;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
total: privateUnread + groupUnread,
|
|
|
|
|
private: privateUnread,
|
|
|
|
|
group: groupUnread,
|
|
|
|
|
privateBySession: privateUnreadBySession,
|
|
|
|
|
groupBySession: groupUnreadBySession
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取未读消息数量失败:', error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 生成UUID
|
|
|
|
|
* @returns {string} - 返回UUID
|
|
|
|
|
*/
|
|
|
|
|
function generateUUID() {
|
|
|
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
|
|
|
const r = Math.random() * 16 | 0;
|
|
|
|
|
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
|
|
|
return v.toString(16);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 添加或更新会话
|
|
|
|
|
* @param {Object} conversation - 会话对象
|
|
|
|
|
* @returns {Promise<number>} - 返回会话ID
|
|
|
|
|
*/
|
|
|
|
|
export async function addOrUpdateConversation(conversation) {
|
|
|
|
|
try {
|
2025-06-30 08:15:44 +00:00
|
|
|
|
// 创建一个纯数据副本,移除所有不可序列化的内容
|
|
|
|
|
const serializableConversation = JSON.parse(JSON.stringify(conversation));
|
|
|
|
|
|
2025-06-30 08:00:06 +00:00
|
|
|
|
// 检查会话是否已存在
|
|
|
|
|
const existingConversation = await db.conversations
|
|
|
|
|
.where('index_name')
|
2025-06-30 08:15:44 +00:00
|
|
|
|
.equals(serializableConversation.index_name)
|
2025-06-30 08:00:06 +00:00
|
|
|
|
.first();
|
|
|
|
|
|
|
|
|
|
if (existingConversation) {
|
|
|
|
|
// 更新现有会话
|
2025-06-30 08:15:44 +00:00
|
|
|
|
await db.conversations.update(existingConversation.id, serializableConversation);
|
2025-06-30 08:00:06 +00:00
|
|
|
|
return existingConversation.id;
|
|
|
|
|
} else {
|
|
|
|
|
// 添加新会话
|
2025-06-30 08:15:44 +00:00
|
|
|
|
const id = await db.conversations.add(serializableConversation);
|
2025-06-30 08:00:06 +00:00
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('添加或更新会话失败:', error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取所有会话列表
|
|
|
|
|
* @param {boolean} includeEmpty - 是否包含没有消息的会话
|
|
|
|
|
* @returns {Promise<Array>} - 返回会话列表,按更新时间倒序排列
|
|
|
|
|
*/
|
|
|
|
|
export async function getConversations(includeEmpty = false) {
|
|
|
|
|
try {
|
|
|
|
|
let query = db.conversations;
|
|
|
|
|
|
|
|
|
|
if (!includeEmpty) {
|
|
|
|
|
// 只获取有消息的会话
|
|
|
|
|
query = query.filter(item => item.msg_text && item.msg_text.length > 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 按更新时间倒序排列
|
|
|
|
|
const conversations = await query
|
|
|
|
|
.toArray()
|
|
|
|
|
.then(items => items.sort((a, b) => {
|
|
|
|
|
// 置顶的会话优先
|
|
|
|
|
if (a.is_top !== b.is_top) return b.is_top - a.is_top;
|
|
|
|
|
// 然后按更新时间排序
|
|
|
|
|
return new Date(b.updated_at) - new Date(a.updated_at);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
return conversations;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取会话列表失败:', error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取指定会话
|
|
|
|
|
* @param {number} talkType - 会话类型(1:私聊, 2:群聊)
|
|
|
|
|
* @param {number} receiverId - 接收者ID(私聊为对方用户ID,群聊为群ID)
|
|
|
|
|
* @returns {Promise<Object|null>} - 返回会话对象,如果不存在则返回null
|
|
|
|
|
*/
|
|
|
|
|
export async function getConversation(talkType, receiverId) {
|
|
|
|
|
try {
|
|
|
|
|
const indexName = `${talkType}_${receiverId}`;
|
|
|
|
|
const conversation = await db.conversations
|
|
|
|
|
.where('index_name')
|
|
|
|
|
.equals(indexName)
|
|
|
|
|
.first();
|
|
|
|
|
|
|
|
|
|
return conversation || null;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取会话失败:', error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 更新会话的未读消息数
|
|
|
|
|
* @param {number} talkType - 会话类型
|
|
|
|
|
* @param {number} receiverId - 接收者ID
|
|
|
|
|
* @param {number} unreadNum - 未读消息数量,如果为null则增加1
|
|
|
|
|
* @returns {Promise<boolean>} - 操作是否成功
|
|
|
|
|
*/
|
|
|
|
|
export async function updateConversationUnreadNum(talkType, receiverId, unreadNum = null) {
|
|
|
|
|
try {
|
|
|
|
|
const indexName = `${talkType}_${receiverId}`;
|
|
|
|
|
const conversation = await db.conversations
|
|
|
|
|
.where('index_name')
|
|
|
|
|
.equals(indexName)
|
|
|
|
|
.first();
|
|
|
|
|
|
|
|
|
|
if (conversation) {
|
|
|
|
|
if (unreadNum === null) {
|
|
|
|
|
// 增加未读消息数
|
|
|
|
|
unreadNum = (conversation.unread_num || 0) + 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await db.conversations.update(conversation.id, { unread_num: unreadNum });
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('更新会话未读消息数失败:', error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 清空会话的未读消息数
|
|
|
|
|
* @param {number} talkType - 会话类型
|
|
|
|
|
* @param {number} receiverId - 接收者ID
|
|
|
|
|
* @returns {Promise<boolean>} - 操作是否成功
|
|
|
|
|
*/
|
|
|
|
|
export async function clearConversationUnreadNum(talkType, receiverId) {
|
|
|
|
|
return updateConversationUnreadNum(talkType, receiverId, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 删除会话
|
|
|
|
|
* @param {number} conversationId - 会话ID
|
|
|
|
|
* @param {boolean} deleteMessages - 是否同时删除相关的消息记录
|
|
|
|
|
* @returns {Promise<boolean>} - 操作是否成功
|
|
|
|
|
*/
|
|
|
|
|
export async function deleteConversation(conversationId, deleteMessages = false) {
|
|
|
|
|
try {
|
|
|
|
|
const conversation = await db.conversations.get(conversationId);
|
|
|
|
|
|
|
|
|
|
if (!conversation) return false;
|
|
|
|
|
|
|
|
|
|
// 删除会话
|
|
|
|
|
await db.conversations.delete(conversationId);
|
|
|
|
|
|
|
|
|
|
// 如果需要,同时删除相关的消息记录
|
|
|
|
|
if (deleteMessages) {
|
|
|
|
|
const { talk_type, receiver_id } = conversation;
|
|
|
|
|
|
|
|
|
|
if (talk_type === TalkType.PRIVATE) {
|
|
|
|
|
// 删除私聊消息
|
|
|
|
|
await db.messages
|
|
|
|
|
.where('talk_type').equals(TalkType.PRIVATE)
|
|
|
|
|
.and(item => {
|
|
|
|
|
return (item.user_id === receiver_id || item.receiver_id === receiver_id);
|
|
|
|
|
})
|
|
|
|
|
.delete();
|
|
|
|
|
} else {
|
|
|
|
|
// 删除群聊消息
|
|
|
|
|
await db.messages
|
|
|
|
|
.where('talk_type').equals(TalkType.GROUP)
|
|
|
|
|
.and(item => item.receiver_id === receiver_id)
|
|
|
|
|
.delete();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('删除会话失败:', error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 更新会话的最后一条消息
|
|
|
|
|
* @param {Object} message - 消息对象
|
|
|
|
|
* @returns {Promise<boolean>} - 操作是否成功
|
|
|
|
|
*/
|
|
|
|
|
export async function updateConversationLastMessage(message) {
|
|
|
|
|
try {
|
|
|
|
|
const { talk_type, user_id, receiver_id, msg_type } = message;
|
|
|
|
|
let targetReceiverId;
|
|
|
|
|
|
|
|
|
|
if (talk_type === TalkType.PRIVATE) {
|
|
|
|
|
// 私聊:对方ID作为会话的receiver_id
|
|
|
|
|
targetReceiverId = user_id === receiver_id ? user_id : receiver_id;
|
|
|
|
|
} else {
|
|
|
|
|
// 群聊:群ID作为会话的receiver_id
|
|
|
|
|
targetReceiverId = receiver_id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const indexName = `${talk_type}_${targetReceiverId}`;
|
|
|
|
|
const conversation = await db.conversations
|
|
|
|
|
.where('index_name')
|
|
|
|
|
.equals(indexName)
|
|
|
|
|
.first();
|
|
|
|
|
|
|
|
|
|
if (!conversation) return false;
|
|
|
|
|
|
|
|
|
|
// 根据消息类型生成显示文本
|
|
|
|
|
let msgText = '';
|
|
|
|
|
switch (msg_type) {
|
|
|
|
|
case MessageType.TEXT:
|
|
|
|
|
msgText = message.content || '';
|
|
|
|
|
break;
|
|
|
|
|
case MessageType.IMAGE:
|
|
|
|
|
msgText = '[图片消息]';
|
|
|
|
|
break;
|
|
|
|
|
case MessageType.FILE:
|
|
|
|
|
msgText = '[文件消息]';
|
|
|
|
|
break;
|
|
|
|
|
case MessageType.AUDIO:
|
|
|
|
|
msgText = '[语音消息]';
|
|
|
|
|
break;
|
|
|
|
|
case MessageType.VIDEO:
|
|
|
|
|
msgText = '[视频消息]';
|
|
|
|
|
break;
|
|
|
|
|
case MessageType.LOCATION:
|
|
|
|
|
msgText = '[位置消息]';
|
|
|
|
|
break;
|
|
|
|
|
case MessageType.CARD:
|
|
|
|
|
msgText = '[名片消息]';
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
msgText = '[未知类型消息]';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新会话的最后消息和时间
|
|
|
|
|
await db.conversations.update(conversation.id, {
|
|
|
|
|
msg_text: msgText,
|
|
|
|
|
content: message.content || '',
|
|
|
|
|
updated_at: message.created_at
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('更新会话最后消息失败:', error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 示例:如何使用聊天记录数据库
|
|
|
|
|
*
|
|
|
|
|
* // 1. 添加一条消息
|
|
|
|
|
* const newMessage = {
|
|
|
|
|
* msg_id: '5a266eb831594b2b8be7c9fdf34986df',
|
|
|
|
|
* sequence: 184,
|
|
|
|
|
* talk_type: 1, // 私聊
|
|
|
|
|
* msg_type: 4, // 语音消息
|
|
|
|
|
* user_id: 1496,
|
|
|
|
|
* receiver_id: 1774,
|
|
|
|
|
* nickname: '王一峰',
|
|
|
|
|
* avatar: 'https://cdn-test.szjixun.cn/fonchain-main/dev/image/4692/oa/36b09c00-c2c3-453d-9aaf-ac41d47d564b.png',
|
|
|
|
|
* is_revoke: 0,
|
|
|
|
|
* is_mark: 0,
|
|
|
|
|
* is_read: 0,
|
|
|
|
|
* created_at: '2025-06-30 09:11:14',
|
|
|
|
|
* extra: {
|
|
|
|
|
* duration: 0,
|
|
|
|
|
* name: '',
|
|
|
|
|
* size: 15984,
|
|
|
|
|
* url: 'https://cdn-test.szjixun.cn/fonchain-main/test/file/default/fonchain-chat/2b595dbc-6a2b-498e-802a-880e93d1e45a.mp3'
|
|
|
|
|
* },
|
|
|
|
|
* erp_user_id: 4692
|
|
|
|
|
* };
|
|
|
|
|
*
|
|
|
|
|
* addMessage(newMessage).then(msgId => {
|
|
|
|
|
* console.log('消息添加成功,ID:', msgId);
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // 2. 获取私聊消息列表
|
|
|
|
|
* getMessages(TalkType.PRIVATE, 1496, 1774, 20, 0).then(messages => {
|
|
|
|
|
* console.log('获取到20条私聊消息:', messages);
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // 获取特定sequence之前的消息
|
|
|
|
|
* getMessages(TalkType.PRIVATE, 1496, 1774, 20, 0, 1000).then(messages => {
|
|
|
|
|
* console.log('获取sequence小于1000的20条私聊消息:', messages);
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // 3. 标记消息为已读
|
|
|
|
|
* markMessageAsRead('5a266eb831594b2b8be7c9fdf34986df').then(success => {
|
|
|
|
|
* console.log('标记消息已读:', success ? '成功' : '失败');
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // 4. 批量标记会话消息为已读
|
|
|
|
|
* markMessagesAsRead(TalkType.PRIVATE, 1496, 1774).then(count => {
|
|
|
|
|
* console.log(`成功标记 ${count} 条消息为已读`);
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // 5. 撤回消息
|
|
|
|
|
* revokeMessage('5a266eb831594b2b8be7c9fdf34986df').then(success => {
|
|
|
|
|
* console.log('撤回消息:', success ? '成功' : '失败');
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // 6. 获取未读消息数量
|
|
|
|
|
* getUnreadCount(1496).then(result => {
|
|
|
|
|
* console.log('未读消息统计:', result);
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // 7. 添加或更新会话
|
|
|
|
|
* const newConversation = {
|
|
|
|
|
* talk_type: 1, // 私聊
|
|
|
|
|
* receiver_id: 1774,
|
|
|
|
|
* name: "周俊耀",
|
|
|
|
|
* remark: "",
|
|
|
|
|
* avatar: "https://e-cdn.fontree.cn/fonchain-main/prod/image/18248/avatar/a0b2bee7-947f-465a-986e-10a1b2b87032.png",
|
|
|
|
|
* is_disturb: 0,
|
|
|
|
|
* is_top: 1,
|
|
|
|
|
* is_online: 0,
|
|
|
|
|
* is_robot: 0,
|
|
|
|
|
* is_dismiss: 0,
|
|
|
|
|
* is_quit: 0,
|
|
|
|
|
* unread_num: 0,
|
|
|
|
|
* content: "......",
|
|
|
|
|
* draft_text: "",
|
|
|
|
|
* msg_text: "[语音消息]",
|
|
|
|
|
* index_name: "1_1774",
|
|
|
|
|
* updated_at: "2025-06-30 10:27:27",
|
|
|
|
|
* atsign_num: 0,
|
|
|
|
|
* erp_user_id: 3346,
|
|
|
|
|
* group_member_num: 0,
|
|
|
|
|
* group_type: 0
|
|
|
|
|
* };
|
|
|
|
|
*
|
|
|
|
|
* addOrUpdateConversation(newConversation).then(id => {
|
|
|
|
|
* console.log('会话添加或更新成功,ID:', id);
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // 8. 获取所有会话列表
|
|
|
|
|
* getConversations().then(conversations => {
|
|
|
|
|
* console.log('获取所有会话列表:', conversations);
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // 9. 获取指定会话
|
|
|
|
|
* getConversation(TalkType.PRIVATE, 1774).then(conversation => {
|
|
|
|
|
* console.log('获取指定会话:', conversation);
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // 10. 更新会话未读消息数
|
|
|
|
|
* updateConversationUnreadNum(TalkType.PRIVATE, 1774, 5).then(success => {
|
|
|
|
|
* console.log('更新会话未读消息数:', success ? '成功' : '失败');
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // 11. 清空会话未读消息数
|
|
|
|
|
* clearConversationUnreadNum(TalkType.PRIVATE, 1774).then(success => {
|
|
|
|
|
* console.log('清空会话未读消息数:', success ? '成功' : '失败');
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // 12. 更新会话最后一条消息
|
|
|
|
|
* updateConversationLastMessage(newMessage).then(success => {
|
|
|
|
|
* console.log('更新会话最后一条消息:', success ? '成功' : '失败');
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // 13. 删除会话
|
|
|
|
|
* deleteConversation(5811, false).then(success => {
|
|
|
|
|
* console.log('删除会话:', success ? '成功' : '失败');
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // 14. 批量添加或更新聊天记录
|
|
|
|
|
* const messagesFromServer = [
|
|
|
|
|
* {
|
|
|
|
|
* msg_id: 'server-msg-id-1',
|
|
|
|
|
* talk_type: TalkType.PRIVATE,
|
|
|
|
|
* msg_type: MessageType.TEXT,
|
|
|
|
|
* user_id: 1000,
|
|
|
|
|
* receiver_id: 2000,
|
|
|
|
|
* content: '你好,这是第一条消息',
|
|
|
|
|
* is_read: 1,
|
|
|
|
|
* sequence: 1,
|
|
|
|
|
* created_at: '2023-05-01 10:00:00'
|
|
|
|
|
* },
|
|
|
|
|
* {
|
|
|
|
|
* msg_id: 'server-msg-id-2',
|
|
|
|
|
* talk_type: TalkType.PRIVATE,
|
|
|
|
|
* msg_type: MessageType.TEXT,
|
|
|
|
|
* user_id: 2000,
|
|
|
|
|
* receiver_id: 1000,
|
|
|
|
|
* content: '你好,这是回复消息',
|
|
|
|
|
* is_read: 0,
|
|
|
|
|
* sequence: 2,
|
|
|
|
|
* created_at: '2023-05-01 10:01:00'
|
|
|
|
|
* }
|
|
|
|
|
* ];
|
|
|
|
|
*
|
|
|
|
|
* // 使用sequence作为排序和替换的键
|
|
|
|
|
* batchAddOrUpdateMessages(
|
|
|
|
|
* messagesFromServer,
|
|
|
|
|
* TalkType.PRIVATE,
|
|
|
|
|
* 2000, // 接收者ID(私聊为对方用户ID,群聊为群ID)
|
|
|
|
|
* true, // 替换已存在的消息
|
|
|
|
|
* 'sequence' // 使用sequence作为排序和替换的键
|
|
|
|
|
* ).then(result => {
|
|
|
|
|
* console.log(`添加了${result.added}条新消息,更新了${result.updated}条消息`);
|
|
|
|
|
* });
|
|
|
|
|
*/
|