基于 springboot+vue 进行多条件查询历史聊天记录

查询历史聊天记录

多条件查询记录是经常需要进行编写的功能,这里就以多条件查询历史聊天记录为例子来介绍如何进行基于关键字、日期、聊天记录类型 三种条件进行模糊查询、日期拼接、条件拼接查询。

在这里插入图片描述

前端

抽屉管理

首先我们控制 element ui 的 抽屉的打开与关闭

这里可能会出现父子组件传值的错误,详情请看这篇文章

https://blog.csdn.net/qq_45803593/article/details/125546395

由于 element ui 的 el-date-picker 组件也存在父子组件关系,所以这个还会报错,但是不影响使用了

日期查询配置

日期查询我们就使用 element ui 的 date-picker 组件

  • format:选中日期后显示在页面时的格式
  • value-format: 选中日期后传值时的格式
  • picker-options:这里我们绑定一个选中日期不可超过今天的规定
  • change:在我们更换日期后,检测到立刻重新请求聊天记录
<el-date-picker
        v-model="searchDate"
        type="date"
        placeholder="选择日期"
        size="mini"
        format="yyyy 年 MM 月 dd 日"
        value-format="yyyy-MM-dd"
        @change="searchDateChange"
        :picker-options="pickerOptions"/>

必须晚于今天

      pickerOptions: { // 必须晚于今天
        disabledDate (time) {
          return time.getTime() > Date.now()
        }
      },

首次加载数据

首次加载时,我们首先将起始的页码设置为了 1,首次应该加载第 0 页,所以我们直接将 pageIndex 进行 -1 操作,此时的分页组件上就会显示第一页的高亮了,而且访问到的数据也是真正的第一页

pageIndex: this.pageIndex - 1,

每次切换加载的记录

当切换的时候组件没有进行销毁,所以历史记录所在的抽屉的数据不会变化,因此我们每次加载时切换会话,每个会话都有唯一的 roomId,我们将其作为 key 进行属性的绑定,此时组件就会重新生成了

虚拟DOM中key的作用

  • key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】

  • 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下

对比规则:

  • 就虚拟DOM中找到了与新虚拟DOM相同的key

  • 若虚拟DOM中内容没变,直接使用之前的真实DOM!

  • 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM

  • 就虚拟DOM中未找到与新虚拟DOM相同的key创建新的真实DOM,随后渲染到页面。

      <history-message
        @changeHistoryDrawerVisible="changeHistoryDrawerVisible"
        :historyDrawer="historyDrawer"
        :roomId="currentConversation.roomId"
        :key="currentConversation.roomId"
        :conversationType="currentConversation.type">
	  </history-message>

前端全部代码

MyMain.vue

<template>
  <div class="chat-area">
    <div class="main">
      <div class="message-list-container">
        <!-- 聊天列表 -->
        <message-list ref='messagelist'
          @load-message="loadmessage"
          :messagelist="messages"
          :hasmore="hasMore"
          :scrollbottom="scrollBottom"
        >
        </message-list>
      </div>
    </div>
    <div class="message-edit-container">
      <div class="tool">
        <span class="tool-item">
          <i class="item iconfont icon-emoji" @click.stop="showEmojiCom = !showEmojiCom"></i>
        </span>
        <span class="tool-item">
          <label for="upImg">
            <i class="item el-icon-picture"></i>
          </label>
        </span>
        <span class="tool-item">
          <i class="item el-icon-folder"/>
        </span>
        <span class="tool-item">
          <i class="item iconfont icon-huaban"/>
        </span>
        <span class="tool-item">
          <i class="item iconfont icon-shipin"/>
        </span>
        <span class="tool-item">
          <i class="item el-icon-phone-outline"/>
        </span>
        <span class="tool-item" >
          <i class="item el-icon-caret-bottom" @click="changeHistoryDrawerVisible(true)">历史记录</i>
        </span>
      </div>
      <div class="operation">
        <el-button @click="send" type="success" size="small" plain>发送</el-button>
      </div>
      <textarea ref="chatInp" class="textarea" v-model="messageText" maxlength="200" @input="scrollBottom = true"
                @keydown.enter="send($event)"></textarea>
      <custom-emoji v-if="showEmojiCom" class="emoji-component" @addemoji="addEmoji"/>
      <history-message
        @changeHistoryDrawerVisible="changeHistoryDrawerVisible"
        :historyDrawer="historyDrawer"
        :roomId="currentConversation.roomId"
        :key="currentConversation.roomId"
        :conversationType="currentConversation.type"></history-message>
    </div>
  </div>
</template>
<script>
import './../../../static/iconfont/iconfont.css'
import customEmoji from '@/components/customEmoji'
import historyMessage from '@/views/chat/HistoryMessage'
import messageList from '../chat/MessageList'
import {conversationTypes} from '@/const'
import singleMessageApi from '@/api/modules/friend'
import groupMessageApi from '@/api/modules/group'
import {fromatTime} from '@/utils'
export default {
  name: 'MyMain',
  components: {
    customEmoji,
    historyMessage,
    messageList
  },
  props: ['currentConversation'],
  data () {
    return {
      messageText: '', // 当前编辑消息内容
      messages: [], // 消息列表
      showEmojiCom: false, // 表情栏显示
      historyDrawer: false, // 历史记录显示
      pageIndex: 1, // 消息页数
      pageSize: 10, // 一次加载消息量
      hasMore: true, // 更多消息
      scrollBottom: true // 滚到底部
    }
  },
  computed: {
    userInfo () {
      return this.$store.state.user.userInfo
    }
  },
  watch: {
    currentConversation (newVal, oldVal) {
      if (newVal && newVal.id) {
        this.chatInpAutoFocus()
        this.pageIndex = 1
        this.messageText = ''
        this.scrollBottom = true
        this.messages = []
        this.hasMore = true
        this.getRecentMessages()
      }
    },
    deep: true,
    immediate: true
  },
  created () {
    this.getRecentMessages()
    document.addEventListener('click', this.handlerShowEmoji)
  },
  beforeDestroy () {
    // console.log('chatArea BeforeDestroy')
    document.removeEventListener('click', this.handlerShowEmoji)
  },
  sockets: {
    receiveMessage (news) {
      // 收到消息
      if (news.roomId === this.currentConversation.roomId) {
        // 是自己正所处的房间就添加消息并更新会话列表
        this.messages = [...this.messages, news]
        // 进来设置该房间未读消息数为0
        setTimeout(() => {
          this.$store.dispatch('conversation/SET_UNREAD_NUM', {type: 'clear', data: news})
        }, 0)
      }
    }
  },
  methods: {
    // 控制历史记录抽屉
    changeHistoryDrawerVisible (val) {
      this.historyDrawer = val
    },
    // 控制表情面板
    handlerShowEmoji () {
      this.showEmojiCom = false
      // this.showUpFileCom = false
    },
    // 加载更多消息
    loadmessage () {
      if (this.hasMore) {
        this.scrollBottom = false
        if (this.hasMore) {
          this.getRecentMessages()
        }
      }
    },
    // 添加表情
    addEmoji (emoji = '') {
      this.messageText += emoji
    },
    // 发送消息
    send (e) {
      e.preventDefault()
      if (!this.messageText) {
        return
      }
      const common = this.generatorMessageCommon()
      const newMessage = {
        ...common,
        message: this.messageText,
        messageType: 0
      }
      let selfMessage = {
        ...newMessage,
        createTime: fromatTime(new Date())
      }
      this.messages = [...this.messages, selfMessage]

      this.$socket.emit('sendNewMessage', newMessage)
      this.messageText = ''
      this.showEmojiCom = false
      // 发完消息更新自己会话列表的最新消息
      if (selfMessage.conversationType === conversationTypes.group) selfMessage.message = selfMessage.senderNickname + ':' + selfMessage.message
      this.$store.dispatch('conversation/SET_UNREAD_NUM', {type: 'clear', data: selfMessage})
    },
    // 生成发送消息部分字段
    generatorMessageCommon () {
      return {
        roomId: this.currentConversation.roomId,
        senderId: this.userInfo.id,
        senderName: this.userInfo.username,
        senderNickname: this.userInfo.nickname,
        senderAvatar: this.userInfo.avatar,
        conversationType: this.currentConversation.type
      }
    },
    // 进入聊天信息页面,清除消息未读
    getRecentMessages () {
      const {roomId, type} = this.currentConversation
      const params = {
        roomId,
        pageIndex: this.pageIndex,
        pageSize: this.pageSize
      }
      // 私聊信息
      if (type === conversationTypes.single) {
        singleMessageApi.getRecentSingleMessage(params).then(res => {
          if (res.code === 2000) {
          // reverse() 会改变原数组,并且当前作用域的对象都会改变
            res.data.recentMessage.reverse()
            this.messages = [...res.data.recentMessage, ...this.messages]
            if (res.data.recentMessage.length < this.pageSize) {
              this.hasMore = false
              this.pageIndex = 2
              return
            }
            this.pageIndex++
          }
        })
      } else if (type === conversationTypes.group) {
        groupMessageApi.getRecentGroupMessage(params).then(res => {
          if (res.code === 2000) {
          // reverse() 会改变原数组,并且当前作用域的对象都会改变
            res.data.recentMessage.reverse()
            this.messages = [...res.data.recentMessage, ...this.messages]
            if (res.data.recentMessage.length < this.pageSize) {
              this.hasMore = false
              this.pageIndex = 2
              return
            }
            this.pageIndex++
          }
        })
        console.log('获取群聊信息')
      }
      this.chatInpAutoFocus()
    },
    /** 聊天内容输入框自动聚焦 */
    chatInpAutoFocus () {
      this.$nextTick(() => {
        this.$refs.chatInp.focus()
      })
    }
  }
}
</script>
<style scoped>
.chat-area {
  position: relative;
  height: 100%;
}
.main {
  display: flex;
  position: relative;
  height: calc(100% - 160px);
  width: 100%;
}
.main .message-list-container {
  position: relative;
  height: 100%;
  width: 75%;
  flex: 1;
}
.message-edit-container {
  box-sizing: border-box;
  position: relative;
  height: 150px;
  border-top: 1px solid #cccccc;
}
.message-edit-container .tool {
  width: 100%;
  height: 28px;
  line-height: 28px;
  text-align: left;
  background-color: rgba(233, 235, 238, 0.5);
  padding: 0 10px;
  box-sizing: border-box;
}
.message-edit-container .tool .tool-item {
  cursor: pointer;
  display: inline-block;
  height: 100%;
  position: relative;

}
.message-edit-container .tool .tool-item i {
  padding: 0 5px;
}
.message-edit-container .tool .tool-item .emoji-container {
  width: 400px;
  height: 260px;
  position: absolute;
  bottom: 30px;
  left: 0;
  z-index: 10;
  transition: all 0.2s;
}

.message-edit-container .tool .tool-item input {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
}
.message-edit-container .tool .tool-item:hover {
  background-color: rgba(255, 255, 255, 0.3);
}

.message-edit-container .tool .tool-item.active {
  background-color: rgba(255, 255, 255, 0.3);
}

.message-edit-container .tool .tool-item.active .emoji-container {
  transform: scaleX(1);
  opacity: 1;
}
.message-edit-container .tool i {
  margin: 0;
}
.message-edit-container .operation {
  position: absolute;
  bottom: 10px;
  right: 20px;
}
.message-edit-container .textarea {
  overflow-x: hidden;
  box-sizing: border-box;
  height: calc(100% - 30px);
  width: 100%;
  outline: none;
  border: none;
  padding: 0 10px;
  border: 0;
  border-radius: 5px;
  background-color: #e9ebee;
  padding: 10px;
  resize: none;
}
.message-edit-container .emoji-component {
  position: absolute;
  bottom: 101%;
}
</style>

HistoryMessage.vue

<template>
    <div>
        <el-drawer
            title="历史记录"
            :visible.sync="historyDrawerVisible"
            v-if="historyDrawerVisible">
            <div class="history-msg-cmp" v-loading="isLoading">
                <div class="search">
                    <el-input
                        placeholder="请输入搜索内容"
                        prefix-icon="el-icon-search"
                        size="mini"
                        v-model="searchWord"
                        @keydown.native="searchWordChange"
                    />
                </div>
                <div class="type">
                    <el-radio-group v-model="searchType" size="mini" @change="searchTypeChange">
                        <el-radio-button value="all" label="全部"></el-radio-button>
                        <el-radio-button value="img" label="图片"></el-radio-button>
                        <el-radio-button value="file" label="文件"></el-radio-button>
                    </el-radio-group>
                    <el-date-picker
                        v-model="searchDate"
                        type="date"
                        placeholder="选择日期"
                        size="mini"
                        format="yyyy 年 MM 月 dd 日"
                        value-format="yyyy-MM-dd"
                        @change="searchDateChange"
                        :picker-options="pickerOptions"/>
                </div>
                <div class="history-msg__body">
                    <div class="msg-list-container" v-if="historyMessageList.length">
                        <div class="msg-item" v-for="item in historyMessageList" :key="item.id">
                          <historyMessageItem :msg-item="item"/>
                        </div>
                        <div class="history-msg__footer">
                          <el-pagination
                            background
                            hide-on-single-page
                            @current-change="handleCurrentChange"
                            :current-page="pageIndex"
                            :page-size="pageSize"
                            :total="total"
                            layout="prev, pager, next"
                          />
                        </div>
                    </div>
                    <div class="no-data" v-else>
                        <p class="text">没有数据~</p>
                        <empty width="150" heigth="150"></empty>
                    </div>
                </div>
            </div>
        </el-drawer>
    </div>
</template>
<script>
import {conversationTypes} from '@/const'
import groupApi from '@/api/modules/group'
import friendApi from '@/api/modules/friend'
const typeTextToValue = {
  '全部': 'all',
  '图片': 'img',
  '文件': 'file'
}
export default {
  name: 'HistoryMessage',
  components: {
    empty: () => import('@/SVGComponents/empty'),
    historyMessageItem: () => import('@/views/chat/HistoryMessageItem')
  },
  props: {
    historyDrawer: {
      type: Boolean,
      default: false
    },
    roomId: {
      type: String
    },
    conversationType: {
      type: Number
    }
  },
  mounted () {
    this.getHistoryMsg()
  },
  data () {
    return {
      historyMessageList: [],
      searchType: '全部', // 消息类型
      searchWord: '', // 搜索关键字
      searchDate: '', // 搜索日期
      pageIndex: 1, // 页码
      pageSize: 10, // 页大小
      total: 0, // 总记录
      pickerOptions: { // 必须晚于今天
        disabledDate (time) {
          return time.getTime() > Date.now()
        }
      },
      isLoading: false
    }
  },
  computed: {
    historyDrawerVisible: {
      get () {
        return this.historyDrawer
      },
      set (val) {
        this.$emit('changeHistoryDrawerVisible', val)
      }
    }
  },
  methods: {
    // 关闭重置
    handleReset () {
      this.historyMessageList = []
      this.searchType = '全部'
      this.searchWord = ''
      this.searchDate = ''
      this.pageIndex = 1
      this.pageSize = 10
      this.total = 0
      this.isLoading = false
    },
    // 切换页码
    handleCurrentChange (currentPage) {
      this.pageIndex = currentPage
      this.getHistoryMsg()
    },
    // 更换搜索关键字
    searchWordChange () {
      this.pageIndex = 1
      this.getHistoryMsg()
    },
    // 更换搜索消息记录类型
    searchTypeChange () {
      this.pageIndex = 1
      this.getHistoryMsg()
    },
    // 更换搜索日期
    searchDateChange () {
      this.pageIndex = 1
      this.getHistoryMsg()
    },
    // 得到历史记录
    getHistoryMsg () {
      if (this.isLoading) return
      this.isLoading = true
      const params = {
        roomId: this.roomId,
        type: typeTextToValue[this.searchType],
        keyword: this.searchWord,
        date: this.searchDate,
        pageIndex: this.pageIndex - 1,
        pageSize: this.pageSize
      }
      console.log('历史记录params:', params)
      let fetch = this.conversationType === conversationTypes.group ? groupApi : friendApi
      fetch.getHistoryMessages(params).then(res => {
        // console.log('历史消息:', res)
        this.historyMessageList = res.data.historyMessageList.records
        this.total = res.data.historyMessageList.total
        this.isLoading = false
      })
    }
  }
}
</script>
<style>
.history-msg-cmp {
    display: flex;
    flex-direction: column;
    padding: 10px 10px 5px;
    align-items: center;
    height: 100%;
}
.history-msg-cmp .search {
    width: 89%;
    margin-bottom: 10px;
}
.history-msg__body {
  margin: 0 25px;
  flex: 1;
  overflow-x: hidden;
}
.msg-list-container {
  margin-top: 10px;
}
.msg-list-container .msg-item {
  border-top: 1px solid #ededed;
}
.history-msg__footer {
  text-align: center;
}
.no-data {
  position: relative;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%)
}
.no-data .text {
  color: #909399;
  font-size: 20px;
  text-align: center;
}
</style>

HistoryMessageItem

<template>
    <div class="historyMsgItem">
        <div class="historyMsgItem-avatar">
            <el-avatar shape="square" :size="40" :src="msgItem.senderAvatar"></el-avatar>
        </div>
        <div class="historyMsgItem-content">
            <div class="historyMsgItem-header">
                <span class="secondary-font">{{ msgItem.senderNickname }}</span>
                <span class="secondary-font">{{ msgItem.createTime | fromatTime }}</span>
            </div>
            <div class="historyMsgItem-text">
                <span>{{ msgItem.message }}</span>
            </div>
        </div>
    </div>
</template>
<script>
import {fromatTime} from '@/utils/index'
export default {
  name: 'HistoryMessageItem',
  props: [ 'msgItem' ],
  filters: {
    fromatTime (val) {
      return fromatTime(val)
    }
  }
}
</script>
<style>
.historyMsgItem {
    display: flex;
    flex-direction: row;
    justify-content: flex-start;
    padding: 10px 0;
}
.historyMsgItem-avatar {
    margin-right: 10px;
}
.historyMsgItem-content {
    display: flex;
    flex-direction: column;
}
.historyMsgItem-header {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    width: 320px;
    margin-bottom: 4px;
}
</style>

后端

分页信息

首先我们将页码与页面大小取出,这里我们将起始页码进行 + 1 操作,是因为 element ui 的分页组件默认第一页为 0,而我们的 mybatis-plus 默认的第一页为 1,所以我们需要为其进行调整

Page<GroupMessage> page = new Page<>(historyMsgRequestVo.getPageIndex() + 1, historyMsgRequestVo.getPageSize());

查询指定日期记录

由于我们使用 mybatis-plus,所以在查询时我们直接按照官方提供的函数来进行 sql 语句的拼接

apply

apply(String applySql, Object... params)
apply(boolean condition, String applySql, Object... params)
  • 拼接 sql

注意事项:

该方法可用于数据库函数 动态入参的params对应前面applySql内部的{index}部分.这样是不会有sql注入风险的,反之会有!

  • 例: apply("id = 1")—>id = 1

  • 例: apply("date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")—>date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")

  • 例: apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2008-08-08")—>date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")

        if(StringUtils.isNotEmpty(historyMsgRequestVo.getDate())){
            wrapper.apply("date_format(create_time,'%Y-%m-%d') = '" + historyMsgRequestVo.getDate() + "'");
        }

模糊查询

最后就是我们要进行模糊查询了,我们的思路是

  • 如果为全部聊天记录
    • 如果关键字不为空,就对聊天文本内容 message 和聊天文件原名 file_original_name 进行查询
    • 注意我们使用了 .or() 来进行查询
  • 如果不为全部内容
    • 判断是否为文件或者图片类型,然后我们对其原文件名进行模糊查询
        // 如果为文件或者图片
        if(!historyMsgRequestVo.getType().equals(ConstValueEnum.MESSAGE_TYPE_ALL)){
            wrapper.eq(historyMsgRequestVo.getType().equals(ConstValueEnum.MESSAGE_TYPE_IMG), "message_type", 2)
                    .eq(historyMsgRequestVo.getType().equals(ConstValueEnum.MESSAGE_TYPE_FILE), "message_type", 3)
                    .like(StringUtils.isNotEmpty(historyMsgRequestVo.getKeyword()), "file_original_name", historyMsgRequestVo.getKeyword());
        }else {
            wrapper.like(StringUtils.isNotEmpty(historyMsgRequestVo.getKeyword()), "message", historyMsgRequestVo.getKeyword())
                    .or().like(StringUtils.isNotEmpty(historyMsgRequestVo.getKeyword()), "file_original_name", historyMsgRequestVo.getKeyword());
        }

后端全部代码

    /**
     * 获取分页历史聊天记录
     * @param historyMsgRequestVo 包括模糊关键字
     * @return
     */
    @Override
    public IPage<GroupMessage> getGroupHistoryMessages(HistoryMsgRequestVo historyMsgRequestVo) {
        Page<GroupMessage> page = new Page<>(historyMsgRequestVo.getPageIndex() + 1, historyMsgRequestVo.getPageSize());
        QueryWrapper<GroupMessage> wrapper = new QueryWrapper<>();
        wrapper.eq("room_id", historyMsgRequestVo.getRoomId());
        // 日期条件存在
        if(StringUtils.isNotEmpty(historyMsgRequestVo.getDate())){
            wrapper.apply("date_format(create_time,'%Y-%m-%d') = '" + historyMsgRequestVo.getDate() + "'");
        }
        // 如果为文件或者图片
        if(!historyMsgRequestVo.getType().equals(ConstValueEnum.MESSAGE_TYPE_ALL)){
            wrapper.eq(historyMsgRequestVo.getType().equals(ConstValueEnum.MESSAGE_TYPE_IMG), "message_type", 2)
                    .eq(historyMsgRequestVo.getType().equals(ConstValueEnum.MESSAGE_TYPE_FILE), "message_type", 3)
                    .like(StringUtils.isNotEmpty(historyMsgRequestVo.getKeyword()), "file_original_name", historyMsgRequestVo.getKeyword());
        }else {
            wrapper.like(StringUtils.isNotEmpty(historyMsgRequestVo.getKeyword()), "message", historyMsgRequestVo.getKeyword())
                    .or().like(StringUtils.isNotEmpty(historyMsgRequestVo.getKeyword()), "file_original_name", historyMsgRequestVo.getKeyword());
        }

        Page<GroupMessage> groupMessagePage = baseMapper.selectPage(page, wrapper);
        return groupMessagePage;
    }
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值