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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 MongoDB,通用型 2核4GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 基于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可能更适合。


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

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, AgentsSessionVO.class, 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, page.getSize(), 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("数据加载失败", e);
                }
            });
            long count = mongoTemplate.count(query, AgentsChatMessagesVO.class, CommonConstant.AGENTS_CHAT_MESSAGES);
            return new PageModel<AgentsChatMessagesVO>(list, count, 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 保存问答聊天

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

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

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

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

相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
相关文章
|
8月前
|
存储 NoSQL 分布式数据库
Hbase+ES和MongoDB存储大数据的选用
Hbase+ES和MongoDB存储大数据的选用
239 0
|
1天前
|
存储 NoSQL MongoDB
【MongoDB 专栏】MongoDB 的 GridFS:存储与检索大文件
【5月更文挑战第10天】MongoDB&#39;s GridFS 是用于大规模数据和大文件存储的解决方案,它将文件分割成小块存储在不同文档中,以提升管理效率和避免性能问题。文件上传时记录元数据,通过唯一标识符实现快速检索。GridFS 提供高效存储、便捷检索和扩展性,适用于大文件管理。然而,应注意文件大小限制、数据一致性和性能优化。通过案例分析,展示了 GridFS 在实际应用中的优势和适用场景,为构建强大应用提供支持。
【MongoDB 专栏】MongoDB 的 GridFS:存储与检索大文件
|
1天前
|
存储 JSON NoSQL
MongoDB的文档存储格式BSON和JSON的区别
MongoDB的文档存储格式BSON和JSON的区别
|
1天前
|
存储 SQL NoSQL
第3期 MongoDB与SQL存储
第3期 MongoDB与SQL存储
67 0
|
1天前
|
存储 NoSQL MongoDB
MongoDB【部署 04】Windows系统实现MongoDB多磁盘存储
MongoDB【部署 04】Windows系统实现MongoDB多磁盘存储
219 0
|
存储 JSON 自然语言处理
Python3.7+Django2.0.4配合Mongodb打造高性能高扩展标签云存储方案
书接上回,之前有一篇文章提到了标签云系统的构建:[Python3.7+jieba(结巴分词)配合Wordcloud2.js来构造网站标签云(关键词集合)](https://v3u.cn/a_id_138),但是这篇只是浅显的说明了一下如何进行切词以及前端如何使用wordcloud2.js进行前端展示,本次主要讨论下标签分词切出来之后,如何进行存储。
Python3.7+Django2.0.4配合Mongodb打造高性能高扩展标签云存储方案
|
存储 JSON NoSQL
SpringBoot2.x使用MongoDB存储数据
`MongoDB`在企业级项目中一般用于存储文档信息、图片资源等,`MongoDB`的内容完全是以 `JSON`字符串的形式进行存储的,所以我们在获取数据时通过简单的 `反序列化`就可以完成与项目内的实体类转换,不过这个过程是自动的,不需要我们手动进行反序列化处理。
|
存储 监控 NoSQL
【活动】MongoDB 5.0时序存储特性线上研讨会
了解时间序列数据能力,提高数据存储效率
【活动】MongoDB 5.0时序存储特性线上研讨会
|
存储 SQL JSON
第3期 全栈开发MongoDB与SQL存储
全栈开发MongoDB与SQL存储
139 0
第3期 全栈开发MongoDB与SQL存储
|
存储 NoSQL Linux
Mongodb存储特性与内部原理(下)
Mongodb存储特性与内部原理(下)
218 0
Mongodb存储特性与内部原理(下)
http://www.vxiaotou.com