基于MongoDB实现聊天记录的存储

一、mongodb简介

1.1 mongodb简介

MongoDB是一个基于分布式文件存储的数据库,使用C++语言编写。它旨在为WEB应用提供可扩展的高性能数据存储解决方案。MongoDB介于关系数据库和非关系数据库之间,是非关系数据库当中功能最丰富、最像关系数据库的。

MongoDB将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB文档类似于JSON对象,字段值可以包含其他文档、数组及文档数组。它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。

MongoDB最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

此外,MongoDB还具有以下特点:

  1. 面向集合存储,易存储对象类型的数据。
  2. 模式自由。
  3. 支持动态查询。
  4. 支持完全索引,包含内部对象。
  5. 支持查询。
  6. 支持复制和故障恢复。
  7. 使用高效的二进制数据存储,包括大型对象(如视频等)。
  8. 自动处理碎片,以支持云计算层次的扩展性。
  9. 可通过网络访问。

在高负载情况下,添加更多的节点可以保证服务器性能。MongoDB也易于部署和使用,存储数据非常方便。

总的来说,MongoDB是一个高性能、易部署、易使用的数据库系统,具有丰富的功能和特点,适用于各种规模的应用程序和场景。

1.2 mongodb利弊

优点:

  1. 灵活性:MongoDB采用文档存储方式,这意味着数据以键值对的形式存储在BSON(二进制JSON)格式中,这使得它能够存储复杂的数据类型,包括数组、嵌套文档等。这种灵活性使得MongoDB能够轻松地适应各种数据模型。
  2. 易扩展性:MongoDB支持自动分片,这使得它能够轻松地扩展到大量数据和复杂查询场景。通过添加更多的节点,MongoDB可以自动地将数据分布到不同的节点上,从而提高整体性能。
  3. 高性能:MongoDB使用内存映射机制,将数据暂时存储在内存中,提高了IO效率,MongoDB支持快速的读写操作,尤其适用于大规模数据和高并发场景。它还提供了多种查询方式,包括范围查询、排序、聚合等,这使得查询操作比传统的关系型数据库更加快速。
  4. 社区支持:MongoDB有一个活跃的开源社区,这意味着用户可以很容易地找到帮助和资源,以及最新的技术更新和最佳实践。

缺点:

  1. 缺乏事务支持:MongoDB不支持传统的事务处理特性,这意味着在处理多个文档或集合之间的复杂操作时可能会遇到问题。虽然MongoDB提供了乐观并发控制和文档级锁定来解决并发问题,但在需要完整的事务支持的场景下可能不够用。
  2. 复杂性:由于MongoDB的灵活性,它可能比传统的关系型数据库更复杂。对于初学者来说,可能需要更长的时间来学习和理解其数据模型和查询语言。
  3. 数据一致性:MongoDB采用最终一致性模型,而不是强一致性模型。这意味着在某些情况下,数据可能不会立即反映所有的更改。这对于需要强一致性的应用来说可能是一个问题。
  4. 磁盘空间占用:由于MongoDB使用文件存储数据,因此可能会占用大量的磁盘空间。特别是在高写入负载的情况下,由于数据文件的增长和收缩,可能会导致磁盘碎片的产生。
1.3 mongodb使用场景

MongoDB的使用场景非常广泛,包括以下几个方面:

  1. 内容管理和发布系统:MongoDB的灵活文档模型和高性能写入能力使其成为内容管理和发布系统的理想选择。它可以存储和检索各种类型的内容,如文章、图片、视频等。
  2. 个性化推荐系统:MongoDB可以存储和查询用户的个人偏好和行为数据,从而支持个性化推荐。通过使用MongoDB的高性能索引和聚合功能,可以快速地分析和提供个性化的推荐结果。
  3. 实时分析和大数据处理:MongoDB的分布式架构和高可扩展性使其非常适合实时分析和大数据处理任务。它可以处理大量的并发读写操作,并且支持复杂的查询和聚合操作。
  4. 时序数据管理:MongoDB的存储引擎和索引结构对时序数据的管理非常高效。它可以存储和查询大量的时间序列数据,如传感器数据、日志数据等。
  5. 实时数据分析和监控:MongoDB的副本集和分片功能可以实现实时数据分析和监控。它可以处理大量的并发写入操作,并提供实时的查询结果。
  6. 社交网络和协作平台:MongoDB的文档模型非常适合存储和查询社交网络和协作平台的数据。它可以存储用户的个人资料、关系图谱、消息等。
  7. 位置数据管理和地理信息系统:MongoDB的地理空间索引和查询功能使其成为管理位置数据和地理信息系统的理想选择。它可以存储和查询地理位置、地理边界、地理特征等数据。
  8. 游戏场景:使用MongoDB存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、更新。
  9. 物流场景:使用MongoDB存储订单信息,订单状态在运送过程中会不断更新,以MongoDB内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来。

总的来说,MongoDB适用于各种场景,从网站数据到大数据处理,再到社交网络和游戏等领域,它都表现出强大的灵活性和可扩展性。

1.4 mongodb存储聊天记录和mysql存储的抉择

选择使用MySQL还是MongoDB来存储聊天记录取决于具体需求和场景。以下是两者的一些比较:

MySQL

  • 结构化数据:适用于存储结构化数据,如聊天记录中的文本、时间戳等。
  • 事务处理:支持事务处理,可以保证数据的一致性和完整性。
  • 成熟度与社区支持:是一个成熟的关系型数据库管理系统,拥有庞大的用户基础和丰富的社区支持。
  • 查询优化:适合对复杂查询和性能要求较高的场景。

MongoDB

  • 非结构化数据:适用于存储非结构化数据,如图片、语音消息等。
  • 灵活性:具有灵活的数据模型,可以轻松处理聊天记录中的各种格式和结构。
  • 水平扩展性:适用于大规模数据的存储和管理,具有水平扩展性。
  • 实时性:适合需要实时处理和快速响应的场景,如实时聊天应用。

综上所述,如果聊天记录主要是结构化数据并且需要事务处理和复杂查询,MySQL可能是一个更好的选择。如果聊天记录包含大量非结构化数据并且需要水平扩展和实时处理能力,对事务的完整性要求不高对存取速度要求较高我建议使用新兴的nosql类型数据 MongoDB可能更适合。

二、业务场景

需求:我们的需求是实现一个与AI对话的聊天系统,大概分为两个部分,一个是会话,一个是聊天
我给大家放张图帮助理解(左边是会话,右边是聊天)

在这里插入图片描述

三、聊天记录的存储和查询

3.1 聊天记录数据集合的设计,可以理解为数据表

会话collection:

@Data
@Document(value = "agents_session")
public class AgentsSession implements Serializable {
    private static final long serialVersionUID = 198529858452480909L;


    private String id;

    private String agentId;

    /**
     * session id
     */
    private String sessionId;
    /**
     * 发送者id
     */
    private String senderCode;

    /**
     * 消息(当前会话组中最早的一次提问(也就是用户想AI提问))
     */
    private String message;
    /**
     * 发送时间
     */
    private String sendTime;

    /**
     * 是否删除
     */
    private Boolean isDeleted;

}

聊天记录collection:

@Data
@Document(value = "agents_chat_messages")
public class AgentsChatMessages implements Serializable {
    private static final long serialVersionUID = 823228953137629152L;

    private String id;
    /**
     * 会话id
     */
    private String sessionId;
    /**
     * 消息内容
     */
    private String message;
    /**
     * 接收状态
     */
    private Integer receiveStatus;
    /**
     * 发送者id
     */
    private String senderCode;
    /**
     * 接收者id
     */
    private String recipientCode;
    /**
     * 发送时间
     */
    private String sendTime;
    /**
     * 消息类型 文本、图片、文件、语音等
     */
    private String messageType;
    /**
     * 消息内容汉字个数
     */
    private Integer tokens;

    /**
     * 当前支持以下:
     * user: 表示用户
     * assistant: 表示对话助手
     */
    private String role;

    /**
     * 是否已读
     */
    private Boolean isRead;

    /**
     * 是否删除
     */
    private Boolean isDeleted;

    /**
     * 问答对匹配id
     */
    private String questionAnswerId;
}
3.2 聊天记录存取的实现

service实现

public interface ChatMessagesService {

    /**
     * 分页获取会话列表
     * @param dto
     * @return
     */
    PageModel<AgentsSessionVO> queryAgentSessionPage(AgentsSessionDTO dto, PageRequestDTO page);

    /**
     * 通过会话id分页获取会话列表
     * @param dto
     * @return
     */
    PageModel<AgentsChatMessagesVO> queryAgentsChatMessagesPage(AgentsChatMessagesDTO dto, PageRequestDTO page);

    /**
     * 保存会话和聊天
     * @param messagesDTO
     */
    void saveSessionAndMessages(AgentsChatMessagesDTO messagesDTO);
}

实现类:

@Service
@Slf4j
public class ChatMessagesServiceImpl implements ChatMessagesService {

    @Resource
    private MongoTemplate mongoTemplate;


    /**
     * 获取会话列表
     *
     * @param dto
     * @return
     */
    @Override
    public PageModel<AgentsSessionVO> queryAgentSessionPage(AgentsSessionDTO dto, PageRequestDTO page) {
        try {
            // 创建分页对象
            Pageable pageable = PageRequest.of(page.getPage() - 1, page.getSize(), Sort.Direction.DESC, "sendTime"); // 注意:页码从0开始,所以需要减1
            // 创建查询对象
            Query query = new Query();
            query.addCriteria(Criteria.where("senderCode").is(dto.getUserCode()).and("isDeleted").is(false));

            //设置模糊查询
            if (StringUtils.isNotEmpty(dto.getMessage())) {
                query.addCriteria(Criteria.where("message").regex(dto.getMessage()));
            }
            if (!CollectionUtils.isEmpty(dto.getAgentsIds())) {
                // in 条件查询
                Criteria criteria = Criteria.where("agentId").in(dto.getAgentsIds());
                query.addCriteria(criteria);
            }
            // 排序
            query.with(Sort.by(Sort.Order.desc("sendTime")));
            // 设置分页
            query.with(pageable);

            List<AgentsSessionVO> list = mongoTemplate.find(query, AgentsSessionVO.class, CommonConstant.AGENTS_SESSION);
            list.forEach(s ->{
                try {
                    s.setMessage(AesEncryptionUtil.decrypt(s.getMessage()));
                } catch (Exception e) {
                    throw new HxyAgentsXException("数据加载失败", e);
                }
            });
            long count = mongoTemplate.count(query, CommonConstant.AGENTS_SESSION);

            return new PageModel<AgentsSessionVO>(list, count, pageable);
        } catch (Exception e) {
            log.error("获取会话列表异常");
            throw new HxyAgentsXException("获取会话列表异常", e);
        }
    }

    /**
     * 通过会话id分页获取聊天记录
     *
     * @param dto
     * @return
     */
    @Override
    public PageModel<AgentsChatMessagesVO> queryAgentsChatMessagesPage(AgentsChatMessagesDTO dto, PageRequestDTO page) {

        try {
            // 创建分页对象
            Pageable pageable = PageRequest.of(page.getPage() - 1, Sort.Direction.ASC,"sendTime"); // 注意:页码从0开始,所以需要减1
            // 创建查询对象
            Query query = new Query();
            //设置模糊查询
            if (StringUtils.isNotEmpty(dto.getMessage())) {
                query.addCriteria(Criteria.where("message").regex(dto.getMessage()));
            }
            query.addCriteria(Criteria.where("sessionId").is(dto.getSessionId()).and("isDeleted").is(false));

            query.addCriteria(new Criteria().orOperator(Criteria.where("senderCode").is(dto.getUserCode()),Criteria.where("recipientCode").is(dto.getUserCode())));
            // 排序
            query.with(Sort.by(Sort.Order.asc("sendTime")));
            // 设置分页
            query.with(pageable);

            List<AgentsChatMessagesVO> list = mongoTemplate.find(query, AgentsChatMessagesVO.class, CommonConstant.AGENTS_CHAT_MESSAGES);
            list.forEach(m ->{
                try {
                    m.setMessage(AesEncryptionUtil.decrypt(m.getMessage()));
                } catch (Exception e) {
                    throw new HxyAgentsXException("数据加载失败", CommonConstant.AGENTS_CHAT_MESSAGES);

            return new PageModel<AgentsChatMessagesVO>(list, pageable);
        } catch (Exception e) {
            log.error("获取聊天记录列表异常");
            throw new HxyAgentsXException("获取聊天记录列表异常", e);
        }
    }

    /**
     * 存会话聊天
     * @param messagesDTO
     */
    @Override
    public void saveSessionAndMessages(AgentsChatMessagesDTO messagesDTO) {
        try {
            Criteria.where("sessionId").is(messagesDTO.getSessionId());
            AgentsSession agentsSessionOne = mongoTemplate.findOne(new Query(Criteria.where("sessionId").is(messagesDTO.getSessionId()).
                    and("isDeleted").is(false)), AgentsSession.class);

            // 会话
            if (agentsSessionOne == null){
                AgentsSession agentsSession = new AgentsSession();
                agentsSession.setId(UUIDUtils.getUUID());
                agentsSession.setSessionId(messagesDTO.getSessionId());
                agentsSession.setAgentId(messagesDTO.getAgentId());
                agentsSession.setMessage(AesEncryptionUtil.encrypt(messagesDTO.getMessage()));
                agentsSession.setSenderCode(messagesDTO.getSenderCode());
                agentsSession.setIsDeleted(false);
                agentsSession.setSendTime(LocalDateUtil.localDateTimeToString(LocalDateUtil.getLocalDateTime(),"yyyy-MM-dd HH:mm:ss"));
                mongoTemplate.insert(agentsSession);
            }

            // 聊天
            AgentsChatMessages agentsChatMessages = new AgentsChatMessages();
            agentsChatMessages.setId(UUIDUtils.getUUID());
            agentsChatMessages.setSessionId(messagesDTO.getSessionId());
            agentsChatMessages.setMessage(AesEncryptionUtil.encrypt(messagesDTO.getMessage()));
            agentsChatMessages.setMessageType("text");
            agentsChatMessages.setRole(messagesDTO.getRole());
            agentsChatMessages.setIsRead(true);
            agentsChatMessages.setIsDeleted(false);
            agentsChatMessages.setSenderCode(messagesDTO.getSenderCode());
            agentsChatMessages.setRecipientCode(messagesDTO.getRecipientCode());
            agentsChatMessages.setSendTime(LocalDateUtil.localDateTimeToString(LocalDateUtil.getLocalDateTime(),"yyyy-MM-dd HH:mm:ss"));
            agentsChatMessages.setQuestionAnswerId(messagesDTO.getQuestionAnswerId());
            mongoTemplate.insert(agentsChatMessages);

        } catch (Exception e) {
            log.error("保存会话聊天失败",e);
            throw new HxyAgentsXException("保存会话聊天失败",e);
        }

    }
}

聊天内容加密:

public class AesEncryptionUtil {

    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";
    private static final byte[] keyValue = "yourSecretKey".getBytes(StandardCharsets.UTF_8);

    public static String encrypt(String valueToEncrypt) throws Exception {
        SecretKeySpec key = new SecretKeySpec(keyValue, ALGORITHM);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] encryptedByteValue = cipher.doFinal(valueToEncrypt.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encryptedByteValue);
    }

    public static String decrypt(String encryptedValue) throws Exception {
        SecretKeySpec key = new SecretKeySpec(keyValue, ALGORITHM);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] originalValue = cipher.doFinal(Base64.getDecoder().decode(encryptedValue));
        return new String(originalValue, StandardCharsets.UTF_8);
    }

    public static void main(String[] args) throws NoSuchAlgorithmException {
        // 创建AES密钥生成器
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        // 设置密钥长度为256位
        keyGenerator.init(128);
        // 生成密钥
        SecretKey secretKey = keyGenerator.generateKey();

        // 将密钥转换为字符串
        String keyString = Base64.getEncoder().encodeToString(secretKey.getEncoded());
        System.out.println("Generated AES key (Base64): " + keyString);
    }
}
3.3 保存问答聊天

在用户问答的时候保存聊天内容

在这里插入图片描述

在模型回答结束的时候保存聊天内容

在这里插入图片描述


最后大家可以结合自己的业务来实现聊天记录的存取。

最后送大家一句话白驹过隙,沧海桑田

文末送福利啦~

1、Java(SE、JVM)、算法数据结构、数据库(Mysql、redis)、Maven、Netty、RocketMq、Zookeeper、多线程、IO、SSM、Git、Linux、Docker、Web前端相关学习笔记
2、2023最新BATJ大厂面试题集
3、项目源码
4、学习小组
领取方式:关注下方公主号,回复:【笔记】、【面试】获取相关福利。

文章持续更新,可以关注下方公众号或者微信搜一搜「 迷迭香编程 」获取项目源码、干货笔记、面试题集,第一时间阅读,获取更完整的链路资料。

原文地址:https://blog.csdn.net/qq_38374397/article/details/135951810

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


文章浏览阅读552次。com.mongodb.MongoQueryException: Query failed with error code 292 and error message 'Executor error during find command :: caused by :: Sort exceeded memory limit of 104857600 bytes, but did not opt in to external sorting.' on server 11.51.141.63:27017 _mongodb 大文件 下载失败
文章浏览阅读635次,点赞9次,收藏8次。MongoDB 是一种 NoSQL 数据库,它将每个数据存储为一个文档,这里的文档类似于 JSON/BSON 对象,具体数据结构由键值(key/value)对组成。
文章浏览阅读2.1k次。和。_mongodb 日期类型
文章浏览阅读1.7k次。Scalestack等客户期待使用MongoDB Atlas Vector Search和Amazon Bedrock构建下一代应用程序
文章浏览阅读970次。SpringBoot整合中间件mongodb、ES_springboot3 elasticsearch json数据
文章浏览阅读673次。MongoDB 简介_尚医通sql
文章浏览阅读1k次,点赞8次,收藏9次。官网下载MongoDB安装包后进行解压(因了解并不深入,故暂不进行详细说明,自行查找其他安装方法,后期了解深入后将进行该教程的完善)在bin目录下使用命令启动:./mongod --config …/mongodb.conf。该文章任然处于完善中,如果存在错误遗漏的地方,欢迎私信联系。安装相关的nuget包后即可通过以下方法连接数据。YX9010_0@的第二十篇文章。
文章浏览阅读1.2k次,点赞17次,收藏26次。社交场景, 使用 MongoDB 存储存储用户信息, 以及用户发表的朋友圈信息, 通过地理位置索引实现附近的人, 地点等功能.游戏场景, 使用 MongoDB 存储游戏用户信息, 用户的装备, 积分等直接以内嵌文档的形式存储, 方便查询, 高效率存储和访问.物流场景, 使用 MongoDB 存储订单信息, 订单状态在运送过程中会不断更新, 以 MongoDB 内嵌数组的形式来存储, 一次查询就能将订单所有的变更读取出来.物联网场景, 使用 MongoDB 存储所有接入的智能设备信息, 以及设备汇报的日
文章浏览阅读686次。您可以使用 update_one() 方法来更新 MongoDB 中调用的记录或文档。update_one() 方法的第一个参数是 query 对象,用于定义要更新的文档。注释:如果查询找到多个记录,则仅更新第一个匹配项。第二个参数是定义文档新值的对象。_python 更新 mongodb 数据
文章浏览阅读1.3k次。首先来学习一下nosql这里安装就不进行介绍 只记录一下让自己了解mongodb。_nosql注入
文章浏览阅读4.1k次,点赞8次,收藏7次。在data的目录下,创建一个db文件。因为启动MongoDB服务之前必须创建数据库文件的存放文件夹,否则命令不会自动创建,而且不能启动成功。第一步:安装时,Custom是指可以自定义安装路径,然后傻瓜式安装即可(注意:先不要安装图形化工具,否则安装时间会特别长):如果要想连接成功,必须要开服务,即mongod -dbpath C:MongoDBdatadb的cmd要一直开着。然后回车,ctrl+F输入port找到端口号,一般为:27017。打开命令行,然后找到bin文件地址,并输入。_mongodb windows安装
文章浏览阅读5.1k次,点赞3次,收藏43次。详细介绍MongoDB数据库的基本知识,安装方法,基本操作,_mongodb数据库
文章浏览阅读3.2k次。安装教程翻看以往文章。_navicat 连接mongodb
文章浏览阅读426次,点赞9次,收藏12次。win10开放端口:https://blog.csdn.net/m0_43605481/article/details/119255256。我的是阿里云服务器,所以直接在安全组中加入规则,端口范围:27017,授权对象:0.0.0.0。windows在mongodb安装文件夹的bin文件夹中的mongod.cfg。数据库名字是test,打算创建一个用户,账号aaa,密码bbb,权限readWrite。因为该用户是创建在test数据库的,所以在最后要加上test。O了,然后恢复了test的数据。
文章浏览阅读1.1k次。聚合操作主要用于处理数据并返回计算结果。聚合操作将来自多个文档的值组合在一起,按条件分组后,再进行一系列操作(如求和、平均值、最大值、最小值)以返回单个结果。MongoDB的聚合查询​聚合是MongoDB的高级查询语言,它允许我们通过转化合并由多个文档的数据来生成新的在单个文档里不存在的文档信息。MongoDB中聚合(aggregate)主要用于处理数据(例如分组统计平均值、求和、最大值等),并返回计算后的数据结果,有点类似sql语句中的count(*)、groupby。..._如何将几个db的数据统整在一起做查询
文章浏览阅读680次,点赞7次,收藏8次。(2)application.properties配置文件。(4)UserService类。(5)测试和测试结果。
文章浏览阅读1k次,点赞17次,收藏25次。Studio 3T 2023.9 (macOS, Linux, Windows) - MongoDB 的专业 GUI、IDE 和 客户端,支持自然语言查询_mongodb客户端
文章浏览阅读1.1k次,点赞32次,收藏27次。插件式的存储引擎架构可以实现 Server 层和存储引擎层的解耦,可以支持多种存储引擎,如 MySQL 既可以支持 B-Tree 结构的 InnoDB 存储引擎,还可以支持 LSM 结构的 RocksDB 存储引擎。MongoDB 中的记录就是一个 BSON 文档,它是由键值对组成的数据结构,类似于 JSON 对象,是 MongoDB 中的基本数据单元。的简称,是 JSON 文档的二进制表示,支持将文档和数组嵌入到其他文档和数组中,还包含允许表示不属于 JSON 规范的数据类型的扩展。
文章浏览阅读5.1k次,点赞6次,收藏96次。本文设计了一种基于智能室内温度控制的自动调速风扇。以STM32系列单片机为核心主控板,通过程序代码驱动和使用温度传感器模块实现对环境温度的实时监测,并可以实时显示环境温度。同时,可以设置温度检测的上下警告值,根据需求自行调节。_stm32 温控风扇
文章浏览阅读898次,点赞13次,收藏21次。在MongoDB中,我们使用find()和find_one()方法来在集合中查找数据,就像在MySQL数据库中使用SELECT语句来在表中查找数据一样。_pymongo find_one