uniapp结合腾讯云及时通信IM的聊天记录本地存储方案

Posted shuai_li_jun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了uniapp结合腾讯云及时通信IM的聊天记录本地存储方案相关的知识,希望对你有一定的参考价值。

uniapp结合腾讯云及时通信IM的聊天记录本地存储方案

UniApp 是一个跨平台的应用开发框架,可以使用 Vue.js 开发多端应用(如H5、小程序、App等)。在 UniApp 中,可以使用 uni-app 提供的文件系统 API 完成本地文件存储的操作。

1.具体实现方式如下:

创建一个用于存储聊天记录的目录,可以使用 uni-app 提供的 uni.getFileSystemManager API 创建:

uni.getFileSystemManager().mkdirSync(`$uni.env.USER_DATA_PATH/chat`);

将聊天记录以文件的形式存储在该目录下,可以使用 fs.writeFileSync API 实现:

const chatContent = '这是一条聊天记录'; // 假设聊天记录内容为字符串
const fileName = 'chatRecord_001.txt'; // 文件名可以按照一定的规则生成

try 
  uni.getFileSystemManager().writeFileSync(`$uni.env.USER_DATA_PATH/chat/$fileName`, chatContent, 'utf-8');
 catch (e) 
  console.error(e);


读取本地存储的聊天记录,可以使用 fs.readFileSync API 实现:

const fileName = 'chatRecord_001.txt';

try 
  const chatContent = uni.getFileSystemManager().readFileSync(`$uni.env.USER_DATA_PATH/chat/$fileName`, 'utf-8');
  console.log(chatContent);
 catch (e) 
  console.error(e);


结合腾讯云即时通信IM开发聊天软件时,可以使用uni-app提供的本地存储API将聊天记录存储在本地,具体实现方案如下:

1.1 初始化聊天记录:

当用户进入聊天界面时,可以通过查询本地存储是否存在聊天记录,如果不存在则新建一个聊天记录文件,同时将腾讯云IM服务端的聊天记录下载到本地存储中。

// 初始化聊天记录,userId表示当前用户的ID,toUserId表示聊天对象的ID
function initChatRecords(userId, toUserId) 
  const fileName = `chat_$userId_$toUserId.json`;
  const fileContent = 
    chatHistory: [], // 聊天记录数组
    lastReadTime: Date.now(), // 最后一条已读消息的时间戳
  ;
  try 
    const chatContent = uni.getStorageSync(fileName);
    if (chatContent) 
      // 本地存储中已经存在聊天记录,直接读取
      const chatRecords = JSON.parse(chatContent);
      fileContent.chatHistory = chatRecords.chatHistory;
      fileContent.lastReadTime = chatRecords.lastReadTime;
     else 
      // 本地存储中不存在聊天记录,从IM服务器下载并保存到本地
      getChatHistory(userId, toUserId).then((res) => 
        if (res.data) 
          const chatHistory = formatChatHistory(res.data);
          fileContent.chatHistory = chatHistory;
          uni.setStorageSync(fileName, JSON.stringify(fileContent));
        
      );
    
   catch (e) 
    console.error(e);
  
  return fileContent;


1.2 存储聊天记录:

当用户发送一条聊天消息时,将该消息存储到本地的聊天记录文件中,同时更新最后一条已读消息的时间戳。

// 存储聊天记录,userId表示当前用户的ID,toUserId表示聊天对象的ID,message表示聊天消息对象
function saveChatRecord(userId, toUserId, message) 
  const fileName = `chat_$userId_$toUserId.json`;
  try 
    const chatContent = uni.getStorageSync(fileName);
    if (chatContent) 
      const chatRecords = JSON.parse(chatContent);
      chatRecords.chatHistory.push(message);
      uni.setStorageSync(fileName, JSON.stringify(chatRecords));
    
   catch (e) 
    console.error(e);
  


1.3 更新聊天记录:

当用户接收到新的聊天消息时,将该消息存储到本地的聊天记录文件中,同时更新最后一条已读消息的时间戳。

// 更新聊天记录,userId表示当前用户的ID,toUserId表示聊天对象的ID,message表示聊天消息对象
function updateChatRecord(userId, toUserId, message) 
  const fileName = `chat_$userId_$toUserId.json`;
  try 
    const chatContent = uni.getStorageSync(fileName);
    if (chatContent) 
      const chatRecords = JSON.parse(chatContent);
      chatRecords.chatHistory.push(message);
      chatRecords.lastReadTime = Date.now(); // 更新最后一条已读消息的时间戳

2.更详细的实现方案:

2.1 聊天记录的本地存储

在uni-app中,可以使用uni-storage插件来实现本地存储。为了方便管理,可以将每个聊天对象的聊天记录存储在单独的文件中。例如,对于一个名为A的用户和一个名为B的用户的聊天记录,可以将它们存储在一个名为chat_A_B.json的文件中。

为了确保在保存数据时出现任何错误时能够及时捕获和处理异常,我们可以在代码中使用try-catch块。当我们从本地存储中读取聊天记录时,需要检查该文件是否存在,如果不存在,则需要创建一个空的聊天记录文件。

以下是一个简单的实现示例:

// 存储聊天记录
function saveChatRecord(userId, toUserId, message) 
  const fileName = `chat_$userId_$toUserId.json`;
  try 
    const chatContent = uni.getStorageSync(fileName) || '"chatHistory": []';
    const chatRecords = JSON.parse(chatContent);
    chatRecords.chatHistory.push(message);
    uni.setStorageSync(fileName, JSON.stringify(chatRecords));
   catch (e) 
    console.error(e);
  


// 读取聊天记录
function readChatRecords(userId, toUserId) 
  const fileName = `chat_$userId_$toUserId.json`;
  try 
    const chatContent = uni.getStorageSync(fileName) || '"chatHistory": []';
    return JSON.parse(chatContent).chatHistory;
   catch (e) 
    console.error(e);
    return [];
  


2.2 聊天记录的初始化

在用户打开聊天窗口时,我们需要确保该聊天记录文件存在。如果文件不存在,则需要从服务器获取聊天记录,并将其存储到本地文件中。

以下是一个示例代码:

function initChatRecords(userId, toUserId) 
  const fileName = `chat_$userId_$toUserId.json`;
  const fileContent = 
    chatHistory: [],
    lastReadTime: Date.now(),
  ;

  try 
    const chatContent = uni.getStorageSync(fileName);
    if (chatContent) 
      // 如果文件存在,则读取文件中的聊天记录
      const chatRecords = JSON.parse(chatContent);
      fileContent.chatHistory = chatRecords.chatHistory;
      fileContent.lastReadTime = chatRecords.lastReadTime;
     else 
      // 如果文件不存在,则从服务器获取聊天记录,并存储到本地文件中
      getChatHistory(userId, toUserId).then((res) => 
        if (res.data) 
          const chatHistory = formatChatHistory(res.data);
          fileContent.chatHistory = chatHistory;
          uni.setStorageSync(fileName, JSON.stringify(fileContent));
        
      );
    
   catch (e) 
    console.error(e);
  

  return fileContent;


在上述代码中,getChatHistory函数用于从服务器获取聊天记录,formatChatHistory函数用于将服务器返回的聊天记录格式化为符合本地存储要求的格式。

2.3 如何标记已读消息

我们可以在本地存储中为每个聊天记录文件添加一个lastReadTime属性来标记最后一次已读消息的时间戳。当用户打开聊天窗口时,我们可以将该属性更新为当前时间戳,表示用户已经查看了该聊天记录中的所有消息。

以下是一个示例代码:

// 标记聊天记录已读
function markChatRecordAsRead(userId, toUserId) 
  const fileName = `chat_$userId_$toUserId.json`;
  const chatContent = uni.getStorageSync(fileName);
  if (chatContent) 
    try 
      const chatRecords = JSON.parse(chatContent);
      chatRecords.lastReadTime = Date.now();
      uni.setStorageSync(fileName, JSON.stringify(chatRecords));
     catch (e) 
      console.error(e);
    
  


2.4 何时修改本地存储

我们需要在以下情况下更新本地存储:

  • 当用户发送一条新的聊天消息时,将该消息存储到本地文件中。
  • 当用户打开聊天窗口时,需要初始化该聊天记录文件。如果该文件不存在,则需要从服务器获取聊天记录,并将其存储到本地文件中。
  • 当用户查看聊天记录时,需要将lastReadTime属性更新为当前时间戳,表示已读。

需要注意的是,我们需要确保所有本地存储的操作都是异步的,以避免阻塞应用程序。

2.5 如何在App中读取本地聊天记录

在App中,我们可以根据用户选择的聊天对象,读取该聊天对象的聊天记录文件。例如,如果用户选择与B用户聊天,则我们可以读取chat_A_B.json文件中的聊天记录。

以下是一个示例代码:

function loadChatRecords(userId, toUserId) 
  const fileName = `chat_$userId_$toUserId.json`;
  try 
    const chatContent = uni.getStorageSync(fileName);
    if (chatContent) 
      const chatRecords = JSON.parse(chatContent);
      return chatRecords.chatHistory;
    
   catch (e) 
    console.error(e);
  

  return [];


在上述代码中,loadChatRecords函数用于从本地存储中读取指定聊天记录文件中的聊天记录。

总结

综上所述,我们可以使用uni-storage插件实现聊天记录的本地存储,并使用lastReadTime属性来标记已读消息。当用户打开聊天窗口时,我们需要初始化该聊天记录文件。如果该文件不存在,则需要从服务器获取聊天记录,并将其存储到本地文件中。当用户发送一条新的聊天消息时,将该消息存储到本地文件中。在App中,我们可以根据用户选择的聊天对象,读取该聊天对象的聊天记录文件。

3.遇到的问题

除了以上提到的实现方案外,还有一些细节问题需要考虑。以下是一些需要注意的点:

3.1 本地存储容量限制

不同的平台和设备可能对本地存储容量有不同的限制,因此我们需要根据实际情况来确定每个聊天记录文件的最大容量,并及时清理过期的聊天记录。否则,当本地存储空间不足时,将无法存储新的聊天记录。

3.2 聊天记录加密

为了保护用户隐私,我们应该将聊天记录进行加密处理。可以使用一些加密算法来对聊天记录文件进行加密,以确保用户的聊天记录不会被窃取。

3.3 消息同步

如果用户在不同的设备上使用同一个账号登录,那么他们的聊天记录应该能够同步到所有设备上。因此,在本地存储方案中,我们需要考虑如何实现聊天记录的同步。

可以使用云存储服务,如腾讯云对象存储,来实现聊天记录的云端存储和同步。每当用户在一个设备上发送了一条新的聊天消息时,我们可以将该消息同步到云端存储中。而当用户在另一个设备上打开聊天窗口时,我们可以从云端存储中获取最新的聊天记录,并将其更新到本地存储中。

3.4 数据备份

由于本地存储存在数据丢失的风险,我们需要定期对聊天记录进行备份。可以将聊天记录文件上传到云存储服务中,并在需要恢复数据时,从云存储中下载备份数据。

总结

以上是实现uniapp结合腾讯云及时通信IM开发聊天软件的聊天记录本地存储方案。在实际应用中,我们需要考虑到各种细节问题,如本地存储容量限制、聊天记录加密、消息同步和数据备份等。通过对这些问题进行细致的考虑和处理,我们可以为用户提供更加稳定和安全的聊天体验。

4.怎么解决本地存储容量限制问题?

当本地存储空间不足时,我们可以通过以下几种方式来解决:

4.1 清理过期的聊天记录

我们可以在应用中实现一个定期清理聊天记录的功能,将过期的聊天记录文件删除,从而释放存储空间。这可以通过设置定期清理的时间间隔或根据存储空间的占用率来触发清理操作。

4.2 使用本地缓存

可以使用一些本地缓存机制来存储聊天记录,如使用 uni-app 内置的本地存储 API(如 localStorage、sessionStorage)来存储数据。这种方式可以帮助我们充分利用设备的存储空间,并且可以通过设置缓存过期时间来控制存储的数据大小。

4.3 利用云存储

我们可以将聊天记录存储到云端,使用云存储服务来解决本地存储空间不足的问题。当用户在本地存储空间不足时,我们可以通过下载云端聊天记录来缓解本地存储空间的压力。

4.4 优化聊天记录文件大小

我们可以通过优化聊天记录文件的大小来减少本地存储空间的占用。比如可以压缩聊天记录中的图片、视频等多媒体文件,或者将多个聊天记录文件合并成一个文件等方式。

总之,我们需要在开发应用的过程中注意本地存储空间的使用情况,并在必要时采取相应的措施来解决空间不足的问题。同时,为了提高用户体验,我们需要在保证数据安全的前提下,尽可能地扩大应用所能使用的存储空间。

5.怎么解决聊天记录加密问题,比如加密和解密?

聊天记录的加密问题一般可以通过对消息内容进行加密来解决。在实现聊天记录加密时,我们可以采用以下的方式:

5.1 使用对称加密算法

对称加密算法可以使用同一个密钥进行加密和解密操作,比如常用的 AES、DES 等算法。在使用对称加密算法时,发送方需要先使用密钥对消息内容进行加密,然后再发送给接收方,接收方则需要使用同样的密钥对消息内容进行解密。

5.2 使用非对称加密算法

非对称加密算法需要使用公钥和私钥来进行加密和解密操作,比如常用的 RSA 算法。在使用非对称加密算法时,发送方需要使用接收方的公钥对消息内容进行加密,然后再发送给接收方,接收方则需要使用自己的私钥对消息内容进行解密。

5.3 采用混合加密方案

混合加密方案是将对称加密算法和非对称加密算法结合起来使用,利用非对称加密算法来安全地传输对称加密算法的密钥,然后使用对称加密算法对消息内容进行加密和解密。这种方案既保证了数据的安全性,又能够提高加密和解密的效率。

无论采用哪种方式,实现聊天记录加密时还需要注意以下几点:

  1. 安全存储密钥

密钥的安全存储对于加密方案的安全性至关重要。我们应该采用安全的方式存储密钥,比如使用硬件模块来存储密钥,或者将密钥加密后再存储。

  1. 避免密钥泄露

密钥的泄露会导致加密方案失去作用,因此我们需要采取措施来避免密钥的泄露。比如限制密钥的使用范围、限制密钥的使用次数等。

  1. 处理加解密性能

加解密算法的性能会影响应用的响应速度,因此我们需要在实现加密方案时注意优化加解密算法的性能,比如使用硬件加速、缓存加解密结果等方式。

总之,在实现聊天记录加密时,我们需要权衡安全性和性能,采用合适的加密方案来保障数据的安全。同时还需要考虑密钥的管理、密钥的安全存储等问题。

5.4 示例代码

结合了聊天记录的存储和读取过程,同时采用了对称加密算法 AES 进行加密和解密。

// 加密消息
function encryptMessage(message, key) 
  const iv = crypto.getRandomValues(new Uint8Array(16)); // 生成随机的 IV
  const encodedMessage = new TextEncoder().encode(message); // 将消息编码为字节数组
  const cipher = await crypto.subtle.encrypt( name: 'AES-GCM', iv , key, encodedMessage); // 使用 AES-GCM 加密消息
  const encryptedMessage = new Uint8Array(iv.length + new Uint8Array(cipher).length);
  encryptedMessage.set(iv);
  encryptedMessage.set(new Uint8Array(cipher), iv.length); // 将 IV 和加密后的消息组合成一个字节数组
  return encryptedMessage;


// 解密消息
async function decryptMessage(encryptedMessage, key) 
  const iv = encryptedMessage.slice(0, 16); // 从字节数组中获取 IV
  const ciphertext = encryptedMessage.slice(16); // 从字节数组中获取加密后的消息
  const plaintext = await crypto.subtle.decrypt( name: 'AES-GCM', iv , key, ciphertext); // 使用 AES-GCM 解密消息
  return new TextDecoder().decode(plaintext); // 将解密后的字节数组转换为字符串


// 存储聊天记录
function storeChatRecord(userId, chatRecord, key) 
  const encryptedRecord = encryptMessage(JSON.stringify(chatRecord), key); // 对聊天记录进行加密
  const fileName = `$userId-chat-record.dat`;
  const blob = new Blob([encryptedRecord],  type: 'application/octet-stream' );
  const file = new File([blob], fileName);
  // 将加密后的聊天记录存储为本地文件
  window.requestFileSystem(window.PERSISTENT, 1024 * 1024, async (fs) => 
    const fileEntry = await createFile(fs.root, fileName);
    await writeFile(fileEntry, file);
  );


// 读取聊天记录
async function loadChatRecord(userId, key) 
  const fileName = `$userId-chat-record.dat`;
  // 从本地文件中读取加密后的聊天记录
  const file = await getFile(fileName);
  const encryptedRecord = await readFile(file);
  const chatRecord = await decryptMessage(encryptedRecord, key); // 对加密后的聊天记录进行解密
  return JSON.parse(chatRecord);


// 生成 AES 密钥
async function generateAesKey() 
  return crypto.subtle.generateKey(
     name: 'AES-GCM', length: 256 ,
    true,
    ['encrypt', 'decrypt']
  );


// 初始化聊天记录加密密钥
async function initChatRecordEncryption(userId) 
  const keyName = `$userId-encryption-key`;
  let key = await getKey(keyName);
  if (!key) 
    key = await generateAesKey();
    await setKey(keyName, key);
  
  return key;


// 存储密钥
async function setKey(keyName, key) 
  const exportedKey = await crypto.subtle.exportKey('jwk', key);
  localStorage.setItem(keyName, JSON.stringify(exportedKey));


// 获取密钥
async function getKey(keyName) 
  const exportedKey = localStorage.getItem(keyName);
  if (!exportedKey) 
    return null;
  
  return crypto.subtle.importKey(
    'jwk',
    JSON.parse(exportedKey),
     name: 'AES-GCM', length: 256 ,
    true,
    ['encrypt', 'decrypt']
  );


// 创建文件
async function createFile(rootDirEntry, fileName) 
  return new Promise((resolve, reject) => 
    rootDirEntry.getFile(
      fileName,
       create: true, exclusive: false ,
      resolve,
      reject
    );
  );


// 写入文件
async function writeFile(fileEntry, file) 
  return new Promise((resolve, reject) => 
    fileEntry.createWriter((fileWriter) => 
      fileWriter.onwriteend = resolve;
      fileWriter.onerror = reject;
      fileWriter.write(file);
    );
  );


// 获取文件
async function getFile(fileName) 
  return new Promise((resolve, reject) => 
    window.requestFileSystem(window.PERSISTENT, 1024 * 1024, (fs) => 
      fs.root.getFile(
        fileName,
        ,
        resolve,
        (error) => 
          if (error.code === FileError.NOT_FOUND_ERR) 
            // 如果文件不存在,返回空文件对象
            resolve(new File([], fileName));
           else 
            reject(error);
          
        
      );
    );
  );


// 读取文件
async function readFile(file) 
  return new Promise((resolve, reject) => 
    const reader = new FileReader();
    reader.onloadend = () => 
      resolve(new Uint8Array(reader.result));
    ;
    reader.onerror = reject;
    reader.readAsArrayBuffer(file);
  );


在实现过程中,我们使用 AES-GCM 对称加密算法进行加密和解密,同时采用了随机生成的 IV,增加了加密的强度和安全性。加密密钥使用 JWK 格式进行存储和导入,可以更方便地在不同的设备和浏览器之间共享和传输。在存储聊天记录时,我们将加密后的记录存储为一个二进制文件,通过 File API 和 File System API 实现对本地文件的读写操作。在读取聊天记录时,我们首先读取本地文件,并对其中的加密数据进行解密,再将解密后的数据转换为 JSON 格式,最后返回聊天记录对象。

uniapp app & 腾讯云 IM 通讯 封装基础登录方法

登录用户的机制

1、了解登录机制
2、加密用户数据生成会话



前言

腾讯是国内最早也是最大的即时通信开发商,QQ 和微信已经成为每个互联网用户必不可少的应用。顺应行业数字化转型的趋势,腾讯将高并发、高可靠的即时通信能力进行开放,您可以轻易地根据腾讯提供的 SDK 将即时通信功能集成到 App 中,来满足您业务的各种需求。
针对开发者的不同阶段需求及不同场景,即时通信 IM 团队提供了一系列解决方案,包括:Android、iOS、Windows、Web 的 SDK 组件、服务端集成 REST API 接口、第三方回调接口 等。利用这些组件和能力,开发者可以简单快捷地构建高可靠且稳定的即时通信产品,随心所想,触达全球。

官方开发文档:点击前往
官方示例DEMO:Gitee


一、UserSign的意义

用户登录后 携带的UserSign为实例化模型操作信息的关键信息,可当做我们熟悉的HTTP/HTTPS请求的token信息,如果失效或者不存在,tim实例化接口则会判定无效请求,提示当前用户未登录、暂无法处理接口。

二、生成UserSign

1.引入库

代码如下(示例):

// IM Web SDK
npm install tim-js-sdk --save
// 发送图片、文件等消息需要的上传插件
npm install tim-upload-plugin --save

2.图示

3.实例化模型

//APP.VUE
onLaunch(){
	let that = this;
	let options = { SDKAppID:that.globalData.SDKAPPID };
	let tim = TIM.create(options); //初始化,生成tim实例
	console.log(tim,'实例化')
	tim.setLogLevel(1); //日志级别
	tim.registerPlugin({ 'tim-upload-plugin': TIMUploadPlugin });
	//监听 tim not ready
	let onSdkNotReady = function (event) {
	  console.log('tim not ready:', event);
	  that.globalData.tim_ready = false;
	};
	tim.on(TIM.EVENT.SDK_NOT_READY, onSdkNotReady);
	console.log(this.globalData.tim_ready);
	that.$tim = tim;
}

页面登录 文章所需JS文件在此处
GenerateTestUserSig /// 加密方法 配置SDKappid & 秘钥
lib-generate-test-usersig-es.min.js /// 加密文件


具体文件可以向这个GITEE的开源地址寻找

https://gitee.com/zhou_fan/tencent-im-demo?_from=gitee_search
图例

import { genTestUserSig } from "../../utils/GenerateTestUserSig.js";
// const TIM = require('../../utils/tim-wx.js');
import TIM from 'tim-js-sdk';
let onSdkReady; //监听tim ready
//登录
login: function (e) {
  let that = this;
  let userID = e;
  // 请求接口获取用户会话的UserSign
  let data = genTestUserSig(userID); //生成userSig
  let promise = app.$tim.login({ userID: userID, userSig: data.userSig });
  promise.then(function (imResponse) { //登录成功
    console.log('登录成功:', imResponse.data);
	that.login_user = userID;
    app.globalData.login_user = userID;
    app.globalData.tim_login_info = imResponse.data;
    //监听 tim ready
    onSdkReady = function (event) {
      console.log('tim ready:', event);
      app.globalData.tim_ready = true; //记录 tim ready
	  uni.switchTab({url:"./index"})
    };
    app.$tim.on(TIM.EVENT.SDK_READY, onSdkReady);
    if (imResponse.data.repeatLogin === true) { //标识账号已登录,本次登录操作为重复登录。v2.5.1 起支持
      console.log('重复登录:', imResponse.data.errorInfo);
    }
  }).catch(function (imError) { //登录失败的相关信息
    console.warn('login error:', imError);
  });
},

业务执行中需要保持用户的会话,ws链接正常


总结

1、收集参数 sdkappid 秘钥
2、加密参数 生成usersign
3、校验会话 token有效
4、执行登录 login https://web.sdk.qcloud.com/im/doc/zh-cn/SDK.html#login

以上是关于uniapp结合腾讯云及时通信IM的聊天记录本地存储方案的主要内容,如果未能解决你的问题,请参考以下文章

uniapp app & 腾讯云 IM 通讯 封装基础登录方法

服务端搭建——腾讯云通信(IM)

腾讯云IM使用总结

关于腾讯云IM的一些浅见

腾讯云即时通信 IM 服务端 SDK for PHP

微信小程序使用腾讯云IM:登录