一、mongodb简介
1.1 mongodb简介
MongoDB是一个基于分布式文件存储的数据库,使用C++语言编写。它旨在为WEB应用提供可扩展的高性能数据存储解决方案。MongoDB介于关系数据库和非关系数据库之间,是非关系数据库当中功能最丰富、最像关系数据库的。
MongoDB将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB文档类似于JSON对象,字段值可以包含其他文档、数组及文档数组。它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。
MongoDB最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
此外,MongoDB还具有以下特点:
- 面向集合存储,易存储对象类型的数据。
- 模式自由。
- 支持动态查询。
- 支持完全索引,包含内部对象。
- 支持查询。
- 支持复制和故障恢复。
- 使用高效的二进制数据存储,包括大型对象(如视频等)。
- 自动处理碎片,以支持云计算层次的扩展性。
- 可通过网络访问。
在高负载情况下,添加更多的节点可以保证服务器性能。MongoDB也易于部署和使用,存储数据非常方便。
总的来说,MongoDB是一个高性能、易部署、易使用的数据库系统,具有丰富的功能和特点,适用于各种规模的应用程序和场景。
1.2 mongodb利弊
优点:
- 灵活性:MongoDB采用文档存储方式,这意味着数据以键值对的形式存储在BSON(二进制JSON)格式中,这使得它能够存储复杂的数据类型,包括数组、嵌套文档等。这种灵活性使得MongoDB能够轻松地适应各种数据模型。
- 易扩展性:MongoDB支持自动分片,这使得它能够轻松地扩展到大量数据和复杂查询场景。通过添加更多的节点,MongoDB可以自动地将数据分布到不同的节点上,从而提高整体性能。
- 高性能:MongoDB使用内存映射机制,将数据暂时存储在内存中,提高了IO效率,MongoDB支持快速的读写操作,尤其适用于大规模数据和高并发场景。它还提供了多种查询方式,包括范围查询、排序、聚合等,这使得查询操作比传统的关系型数据库更加快速。
- 社区支持:MongoDB有一个活跃的开源社区,这意味着用户可以很容易地找到帮助和资源,以及最新的技术更新和最佳实践。
缺点:
- 缺乏事务支持:MongoDB不支持传统的事务处理特性,这意味着在处理多个文档或集合之间的复杂操作时可能会遇到问题。虽然MongoDB提供了乐观并发控制和文档级锁定来解决并发问题,但在需要完整的事务支持的场景下可能不够用。
- 复杂性:由于MongoDB的灵活性,它可能比传统的关系型数据库更复杂。对于初学者来说,可能需要更长的时间来学习和理解其数据模型和查询语言。
- 数据一致性:MongoDB采用最终一致性模型,而不是强一致性模型。这意味着在某些情况下,数据可能不会立即反映所有的更改。这对于需要强一致性的应用来说可能是一个问题。
- 磁盘空间占用:由于MongoDB使用文件存储数据,因此可能会占用大量的磁盘空间。特别是在高写入负载的情况下,由于数据文件的增长和收缩,可能会导致磁盘碎片的产生。
1.3 mongodb使用场景
MongoDB的使用场景非常广泛,包括以下几个方面:
- 内容管理和发布系统:MongoDB的灵活文档模型和高性能写入能力使其成为内容管理和发布系统的理想选择。它可以存储和检索各种类型的内容,如文章、图片、视频等。
- 个性化推荐系统:MongoDB可以存储和查询用户的个人偏好和行为数据,从而支持个性化推荐。通过使用MongoDB的高性能索引和聚合功能,可以快速地分析和提供个性化的推荐结果。
- 实时分析和大数据处理:MongoDB的分布式架构和高可扩展性使其非常适合实时分析和大数据处理任务。它可以处理大量的并发读写操作,并且支持复杂的查询和聚合操作。
- 时序数据管理:MongoDB的存储引擎和索引结构对时序数据的管理非常高效。它可以存储和查询大量的时间序列数据,如传感器数据、日志数据等。
- 实时数据分析和监控:MongoDB的副本集和分片功能可以实现实时数据分析和监控。它可以处理大量的并发写入操作,并提供实时的查询结果。
- 社交网络和协作平台:MongoDB的文档模型非常适合存储和查询社交网络和协作平台的数据。它可以存储用户的个人资料、关系图谱、消息等。
- 位置数据管理和地理信息系统:MongoDB的地理空间索引和查询功能使其成为管理位置数据和地理信息系统的理想选择。它可以存储和查询地理位置、地理边界、地理特征等数据。
- 游戏场景:使用MongoDB存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、更新。
- 物流场景:使用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, 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 保存问答聊天
在用户问答的时候保存聊天内容
在模型回答结束的时候保存聊天内容
最后大家可以结合自己的业务来实现聊天记录的存取。
最后送大家一句话白驹过隙,沧海桑田
文末送福利啦~
1、Java(SE、JVM)、算法数据结构、数据库(Mysql、redis)、Maven、Netty、RocketMq、Zookeeper、多线程、IO、SSM、Git、Linux、Docker、Web前端相关学习笔记
2、2023最新BATJ大厂面试题集
3、项目源码
4、学习小组
领取方式:关注下方公主号,回复:【笔记】、【面试】获取相关福利。
文章持续更新,可以关注下方公众号或者微信搜一搜「 迷迭香编程 」获取项目源码、干货笔记、面试题集,第一时间阅读,获取更完整的链路资料。