package com.pharmacopoeia.service; import com.fasterxml.jackson.databind.ObjectMapper; import com.pharmacopoeia.entity.Conversation; import com.pharmacopoeia.entity.Message; import com.pharmacopoeia.repository.ConversationRepository; import com.pharmacopoeia.repository.MessageRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.stream.Collectors; @Service public class ChatPersistenceService { private final ConversationRepository conversationRepository; private final MessageRepository messageRepository; private final ObjectMapper mapper = new ObjectMapper(); public ChatPersistenceService(ConversationRepository conversationRepository, MessageRepository messageRepository) { this.conversationRepository = conversationRepository; this.messageRepository = messageRepository; } @Transactional public void saveMessage(String userKey, String conversationId, String role, String content, String intent, List> sources) { conversationRepository.findByConversationId(conversationId).orElseGet(() -> { var conv = Conversation.builder() .conversationId(conversationId) .userKey(userKey) .userId(0L) .title(role.equals("user") && content.length() > 0 ? content.substring(0, Math.min(50, content.length())) : "") .build(); return conversationRepository.save(conv); }); var msg = Message.builder() .conversationId(conversationId) .userKey(userKey) .role(role) .content(content) .intent(intent) .build(); msg.setSourcesList(sources); messageRepository.save(msg); } public Map getHistory(String userKey, int page, int pageSize) { int safePage = Math.max(1, page); int safePageSize = Math.max(1, pageSize); Pageable pageable = PageRequest.of(safePage - 1, safePageSize); Page convPage = conversationRepository .findByUserKeyOrderByCreatedAtDesc(userKey, pageable); List cids = convPage.getContent().stream() .map(Conversation::getConversationId) .collect(Collectors.toList()); Map countMap = new HashMap<>(); if (!cids.isEmpty()) { for (Object[] row : messageRepository.countByConversationIds(cids)) { countMap.put((String) row[0], (Long) row[1]); } } List> items = new ArrayList<>(); for (Conversation c : convPage.getContent()) { long count = countMap.getOrDefault(c.getConversationId(), 0L); items.add(Map.of( "conversation_id", c.getConversationId(), "title", c.getTitle() != null && !c.getTitle().isBlank() ? c.getTitle() : "新的对话", "created_at", c.getCreatedAt() != null ? c.getCreatedAt().toString() : "", "message_count", count )); } return Map.of( "items", items, "page", safePage, "page_size", safePageSize, "total", convPage.getTotalElements(), "total_pages", convPage.getTotalPages() ); } public List> getConversationDetail(String cid) { List> result = new ArrayList<>(); for (var msg : messageRepository.findByConversationIdOrderByCreatedAtAsc(cid)) { result.add(Map.of( "id", msg.getId(), "role", msg.getRole(), "content", msg.getContent(), "intent", msg.getIntent() != null ? msg.getIntent() : "", "sources", msg.getSourcesList(), "created_at", msg.getCreatedAt() != null ? msg.getCreatedAt().toString() : "" )); } return result; } public Optional findMessageById(Long messageId) { return messageRepository.findById(messageId); } @Transactional public void updateFeedback(Long messageId, String feedback) { messageRepository.findById(messageId).ifPresent(msg -> { msg.setFeedback(feedback); messageRepository.save(msg); }); } /** 获取当前用户最近 N 条消息,用于前端恢复对话 */ public List> getRecentMessages(String userKey, int limit) { List> result = new ArrayList<>(); var page = messageRepository.findRecentByUserKey(userKey, PageRequest.of(0, limit)); var msgs = new ArrayList<>(page.getContent()); java.util.Collections.reverse(msgs); for (var msg : msgs) { result.add(Map.of( "role", msg.getRole(), "content", msg.getContent(), "intent", msg.getIntent() != null ? msg.getIntent() : "" )); } return result; } }