msg.class.js 14.5 KB
Newer Older
DCloud_JSON's avatar
3.4.31  
DCloud_JSON 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
import {watch} from 'vue'
import $state from '../state/index.js'
import $methods from '@/uni_modules/uni-im/sdk/methods/index.js';
import $utils from '@/uni_modules/uni-im/sdk/utils/index.js';
import CloudData from '@/uni_modules/uni-im/sdk/ext/CloudData.class.js'
import MsgItem from './MsgItem.class.js'
const dbJQL = uniCloud.databaseForJQL()
const dbJQLcmd = dbJQL.command;

export default class Msg extends CloudData {
  constructor(conversation_id) {
    super()
    this.loadLimit = 10 //每次拉取的条数
    this.conversation_id = conversation_id
    this.isFull = false
  }
  // 可见的消息列表(过滤掉不可见的消息,比如:消息撤回指令、更新群头像指令...)
  visibleDataList(){
    return this.dataList.filter(msg => $utils.isReadableMsg(msg))
  }
  __saveToLocal(datas,{canSetIsFull}){
    // #ifndef H5
    return
    // #endif
    
    let transaction = $state.indexDB.transaction('uni-im-msg', 'readwrite')
    let objectStore = transaction.objectStore('uni-im-msg') // 仓库对象
    let index = 0
    let length = datas.length
    const addData = (data) =>{
      data.__isLocal = true
      const res = objectStore.add(data)
      res.onsuccess = (e) => {
        // console.log('resolve',e, index, length);
        index++
        if (index == length) {
          // console.log('add - success', e);
        }
      }
      res.onerror = (e) => {
        console.error('add - failed', e);
        reject(e)
      }
    }
    // 拷贝一份数据,避免修改原数据
    const dataList = JSON.parse(JSON.stringify(datas))
    dataList.forEach(data => {
      // console.log('data._id',data._id);
      if(data._id){
        objectStore.index("_id").get(data._id).onsuccess = event => {
          // console.log('get',event.target.result);
          if (event.target.result) {
            // console.error('canSetIsFull',canSetIsFull)
            if(canSetIsFull && !this.isFull){
              // console.log('数据已存在,设置canSetIsFull', canSetIsFull, data.body);
              this.isFull = true
            }
            // console.log('数据已存在', data._id);
            return
          }else{
            // console.log('可以新增', data._id);
            addData(data)
          }
        }
      }else{
        addData(data)
      }
    })
  }
  __beforeAdd(datas){
    const currentConversation = $state.conversation.find(this.conversation_id)
    // 如果插入的消息,包含指令(撤回、新用户进群、用户离群、@我 等)则执行指令
    // 允许插入消息之前,执行一些操作
    $state.extensions.invokeExts('before-add-msg', datas)
    datas.forEach(async data => {
      // console.log('beforeAdd',data);
      // 如果此消息,创建时间在当前设备创建此会话之前,则无需执行
      const conversation_time =  currentConversation.update_time || currentConversation.client_create_time || 0
      if (data.create_time < conversation_time) {
        return //console.log('消息创建时间早于会话最新时间,不执行',data.create_time,conversation_time);
      }
      // 调用扩展程序,使扩展程序可以在消息插入之前执行一些操作
      $methods.msgTypes.get(data.type)?.beforeLocalAdd?.(data,currentConversation)
      // 如果收到的是撤回消息指令
      if (data.type === "revoke_msg") {
        return currentConversation.revokeMsg(data.body.msg_id, false)
      }
      // 订单支付通知
      if(data.type === 'pay-notify' && data.from_uid === "system"){
        // console.log('收到订单支付通知',data);
        const {order_id,group_id,pay_channel,status} = data.body
        let currentConversation = $state.conversation.find({group_id})
        if(currentConversation){
          const msg = currentConversation.msg.find({body:{"order_id":order_id}})
          if(!msg){
            console.log('pay-notify 未找到订单消息',data);
            return
          }
          console.log('pay-notify msg',msg);
          msg.body.pay_channel = pay_channel
          msg.body.status = status || 1
          msg.body.pay_time = data.create_time
          msg.body.pay_notify = data
        }else{
          console.log('pay-notify 未找到会话',data);
        }
      }
      const current_uid = $state.currentUser._id
      //  系统通知
      if (data.type === "system") {
        // 用户退出、被踢出群
        if (["group-exit", "group-expel"].includes(data.action)) {
          data.body.user_id_list.forEach(uid=>{
            // 从列表中移除这些用户
            currentConversation.group.member.remove({"users":{"_id":uid}})
            currentConversation.group.member_count --
          })
          if (data.body.user_id_list.includes(current_uid)) {
            // 移除相关会话
            $state.conversation.remove(currentConversation.id)
            // 删除相关群
            $state.group.remove(currentConversation.group_id)
          }
        } else if (data.action === "group-dissolved") {
          // 解散群
          $state.conversation.remove(currentConversation.id)
          $state.group.remove(currentConversation.group_id)
        }
        // 新用户加群
        else if (data.action === "join-group") {
          const {new_member_list,user_id_list} = data.body
          // 如果新加入此群的用户id列表,包括当前用户 
          if(user_id_list.includes(current_uid)){
            // 此会话当前设备之前就加载过,需要重新加载群会话和成员列表(因为被踢出群期间可能有其他用户进群、发消息等)
            if(currentConversation.client_create_time < data.create_time){
              const uniImCo = uniCloud.importObject("uni-im-co",{customUI: true})
              let res = await uniImCo.getConversationList({"conversation_id":currentConversation.id})
              Object.assign(currentConversation, res.data[0])
              //先清空 群成员
              currentConversation.msg.reset()
              currentConversation.group.reset()
              //重新拉取 群成员
              await currentConversation.group.loadMore()
              // 获取群公告
              currentConversation.has_unread_group_notification = !!currentConversation.group.notification
            }
          }else{
            // 将新成员加入到群成员列表
            // 获取成员信息 get方法会从云端拉取本地不存在的用户数据
            await $state.users.get(new_member_list.map(i=>i.user_id))
            new_member_list.forEach(member=>{
              // 补全用户信息
              member.users = $state.users[member.user_id]
              currentConversation.group.member.add(member)
              currentConversation.group.member_count ++
            })
          }
        }
        //更新群资料
        else if (data.action.indexOf("update-group-info-") === 0) {
          if(data.action === "update-group-info-mute_all_members" && currentConversation?.group?.mute_all_members != data?.body?.updateData?.mute_all_members){
            const {mute_all_members} = data.body.updateData
            currentConversation.group.member.dataList.forEach(member => {
              member.mute_type += (mute_all_members ? 1 : -1)
            })
          }
          currentConversation.group = Object.assign(currentConversation.group,data.body.updateData)
          const {notification} = data.body.updateData
          if(data.action === "update-group-info-notification"){
            console.log('收到群公告');
            currentConversation.has_unread_group_notification = true
          }
        }
        else if(data.action === "set-group-admin"){
          const {user_id,addRole,delRole} = data.body
          const {role} = currentConversation.group.member.find(user_id)
          if(addRole.length != 0){
            role.push(...addRole)
          }else if(delRole.length != 0){
            console.log('delRole',delRole);
            delRole.forEach(r=>{
              console.log('r',r);
              console.log('role',role);
              const index = role.findIndex(i => i === r)
              console.log('index',index);
              if(index > -1){
                role.splice(index,1)
              }
            })
          }
          // console.log('设置群管理员',currentConversation.group.member.find(user_id));
        }
        // 
        else if(data.action === "set-group-member-ext-plugin-order-info"){
          const {user_id,group_id,dcloud_plugin_order_info} = data.body
          const member = currentConversation.group.member.find(user_id)
          if(!member.ext){
            member.ext = {}
          }
          console.log('设置群成员插件订单信息',member);
          member.ext.dcloud_plugin_order_info = dcloud_plugin_order_info
        }
      }
      
      if (
        // 如果收到的是群聊且带@的消息
        data.group_id &&
        data.call_uid && 
        (
          // @所有人 或 @我
          data.call_uid == '__ALL' ||
          data.call_uid.includes(current_uid)
        )
      ) {
        currentConversation.call_list.push(data._id)
      }
    })
    
    return datas.map(item => new MsgItem(item))
  }
  __afterAdd(datas,{canSetIsFull}){
    datas.forEach(msg => {
      watch(msg, (newMsg)=>msg.__updateAfter(newMsg), {deep: true})
    })
    // console.log('canSetIsFull',canSetIsFull)
    // 存到客户端数据库中
    if(datas[0] && !datas[0].__isLocal){
      const msgs = datas.filter(data => {
        let needSave = $utils.isReadableMsg(data)
        if (needSave) {
          return true
        }else{
          // console.log('不需要保存',data);
          return false
        }
      })
      this.__saveToLocal(msgs,{canSetIsFull})
    }
    return datas
  }
  async __getLocalData({
    minTime = 0,
    maxTime = Date.now(),
    limit = this.loadLimit,
    _id = false,
    orderBy = {
      // asc 升序,desc 降序
      "create_time": "desc"
    }
  }){
    // #ifndef H5
    return []
    // #endif
    
    // console.log('minTime',minTime,'maxTime',maxTime,'limit',limit,'_id',_id,'orderBy',orderBy)
    const start = Date.now()
    let datas = await new Promise((resolve, reject) => {
      //  根据 id 查询
      if (_id) {
        // console.error('$state.indexDB~~~',$state.indexDB)
        let getRequest = $state.indexDB.transaction("uni-im-msg")
          .objectStore("uni-im-msg")
          .index("_id")
          .get(_id)
        getRequest.onsuccess = function(event) {
          if (getRequest.result) {
            resolve([getRequest.result])
          } else {
            resolve([])
          }
        };
        getRequest.onerror = function(event) {
          console.log('Error getting data');
          resolve([])
        };
        return
      }

      // 设置查询索引
      // console.log('kkkkkkkk',[this.conversation_id, minTime], [this.conversation_id,maxTime]);
      let range = IDBKeyRange.bound([this.conversation_id, minTime], [this.conversation_id, maxTime])
      // 传入的 prev 表示是降序遍历游标,默认是next表示升序;
      // $state.indexDB 在im的场景下,查始终是降序遍历游标 orderBy指的是查询结果的排序方式
      let sort = "prev"
      // console.log('sortsortsortsortsortsortsort',sort);
      // console.error('$state.indexDB~~~',$state.indexDB)
      let task = $state.indexDB.transaction("uni-im-msg")
        .objectStore("uni-im-msg")
        .index("conversation_id-create_time")
        .openCursor(range, sort)
      
      let datas = [], index = 0;
      task.onsuccess = function(event) {
        let cursor = event.target.result;
        if (cursor) {
          // console.log('cursor',cursor.value);
          // 排除边界值
          if (![minTime, maxTime].includes(cursor.value.create_time)) {
            datas.push(cursor.value)
            index++
          }else{
            // console.log('边界值',cursor.value.create_time);
          }
          
          // console.log('index',index,limit);
          if (limit && index === limit) {
            resolve(datas)
          } else {
            cursor.continue();
          }
        } else {
          resolve(datas)
        }
      }
      task.onerror = function(err) {
        console.error(err);
        resolve([])
      }
    })
    // console.log('本地查找耗时',Date.now() - start);
    datas.sort((a, b) => {
      if (orderBy.create_time == 'asc') {
        return a.create_time - b.create_time
      } else {
        return b.create_time - a.create_time
      }
    })
    // console.log('本地查到的datas:',datas);
    return datas
  }
  async __get(){
    //已经拉取的数据中时间最小的值,作为最大
    let maxTime = this.dataList[0]?.create_time || Date.now()
    let localData = []
    if(this.isFull){
      // 如果云端和本地数据重合了,则可以先从本地查
      // console.log('如果云端和本地数据重合了,则可以先从本地查',this.isFull);
      localData = await this.__getLocalData({
        maxTime: maxTime,
        limit: this.loadLimit,
        orderBy: {
          "create_time": "asc",
        },
      })
      // 如果本地有数据,直接返回
      if (localData.length === this.loadLimit) {
        // console.log('本地有数据',localData);
        return localData
      }else{
        // console.error('本地未拉满'+this.loadLimit+'条,从云端拉取 this.isFull',this.isFull);
      }
    }else{
      // console.log('本地数据未与云端重合,需从云端拉取',this.isFull);
    }
    
    maxTime = localData[0]?.create_time || maxTime
    
    // 本地没有数据,从云端拉取
    const where = {
      "conversation_id": this.conversation_id
    }
    if(maxTime){
      where.update_time = dbJQLcmd.lt(maxTime)
    }
    let data;
    let res = await dbJQL.collection('uni-im-msg')
      .where(where)
      .limit(this.loadLimit - localData.length)
      .orderBy('update_time', 'desc')
      .get()
    // console.error('云端查找到', res.data.length ,'条',res.data);
    
    // 服务端查找“应当”按消息“更新”时间排序,但显示需要按“创建”时间倒序,所以这里需要重新排序
    res.data.sort((a,b) => b.create_time - a.create_time)
    data = res.data.concat(localData)
    // console.error('合并本地与云端为', data.length ,'条',data);
    // console.error('where', where,data);
    return data
  }
  __canSeaveToDataMap(msg){
    // 本地创建的消息,不保存到dataMap中。等发送成功后,服务端回调_id再由__updateAfter方法更新到dataMap中
    return msg._id?.indexOf('temp_') === -1
  }
}