From b2783a0168c536bca4e0cda8b98dfb8e7abc5d73 Mon Sep 17 00:00:00 2001 From: Tim <135014430+nagisa77@users.noreply.github.com> Date: Sat, 23 Aug 2025 02:28:06 +0800 Subject: [PATCH] chore: remove obsolete channel unread hook --- .../controller/ChannelController.java | 7 ++ .../com/openisle/service/MessageService.java | 19 ++++ frontend_nuxt/components/HeaderComponent.vue | 4 +- frontend_nuxt/composables/useChannelUnread.js | 38 -------- .../composables/useChannelsUnreadCount.js | 92 +++++++++++++++++++ frontend_nuxt/composables/useWebSocket.js | 5 +- frontend_nuxt/pages/message-box/[id].vue | 4 +- frontend_nuxt/pages/message-box/index.vue | 7 +- 8 files changed, 131 insertions(+), 45 deletions(-) delete mode 100644 frontend_nuxt/composables/useChannelUnread.js create mode 100644 frontend_nuxt/composables/useChannelsUnreadCount.js diff --git a/backend/src/main/java/com/openisle/controller/ChannelController.java b/backend/src/main/java/com/openisle/controller/ChannelController.java index 69dcc8f97..03b5a6952 100644 --- a/backend/src/main/java/com/openisle/controller/ChannelController.java +++ b/backend/src/main/java/com/openisle/controller/ChannelController.java @@ -4,6 +4,7 @@ import com.openisle.dto.ChannelDto; import com.openisle.model.User; import com.openisle.repository.UserRepository; import com.openisle.service.ChannelService; +import com.openisle.service.MessageService; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; @@ -15,6 +16,7 @@ import java.util.List; @RequiredArgsConstructor public class ChannelController { private final ChannelService channelService; + private final MessageService messageService; private final UserRepository userRepository; private Long getCurrentUserId(Authentication auth) { @@ -32,4 +34,9 @@ public class ChannelController { public ChannelDto joinChannel(@PathVariable Long channelId, Authentication auth) { return channelService.joinChannel(channelId, getCurrentUserId(auth)); } + + @GetMapping("/unread-count") + public long unreadCount(Authentication auth) { + return messageService.getUnreadChannelCount(getCurrentUserId(auth)); + } } diff --git a/backend/src/main/java/com/openisle/service/MessageService.java b/backend/src/main/java/com/openisle/service/MessageService.java index 7f28d4809..8fd0b9307 100644 --- a/backend/src/main/java/com/openisle/service/MessageService.java +++ b/backend/src/main/java/com/openisle/service/MessageService.java @@ -120,6 +120,9 @@ public class MessageService { long unreadCount = getUnreadMessageCount(participant.getUser().getId()); String username = participant.getUser().getUsername(); messagingTemplate.convertAndSendToUser(username, "/queue/unread-count", unreadCount); + + long channelUnread = getUnreadChannelCount(participant.getUser().getId()); + messagingTemplate.convertAndSendToUser(username, "/queue/channel-unread", channelUnread); } return message; @@ -260,10 +263,26 @@ public class MessageService { List participations = participantRepository.findByUserId(userId); long totalUnreadCount = 0; for (MessageParticipant p : participations) { + if (p.getConversation().isChannel()) continue; LocalDateTime lastRead = p.getLastReadAt() == null ? LocalDateTime.of(1970, 1, 1, 0, 0) : p.getLastReadAt(); // 只计算别人发送给当前用户的未读消息 totalUnreadCount += messageRepository.countByConversationIdAndCreatedAtAfterAndSenderIdNot(p.getConversation().getId(), lastRead, userId); } return totalUnreadCount; } + + @Transactional(readOnly = true) + public long getUnreadChannelCount(Long userId) { + List participations = participantRepository.findByUserId(userId); + long unreadChannelCount = 0; + for (MessageParticipant p : participations) { + if (!p.getConversation().isChannel()) continue; + LocalDateTime lastRead = p.getLastReadAt() == null ? LocalDateTime.of(1970, 1, 1, 0, 0) : p.getLastReadAt(); + long unread = messageRepository.countByConversationIdAndCreatedAtAfterAndSenderIdNot(p.getConversation().getId(), lastRead, userId); + if (unread > 0) { + unreadChannelCount++; + } + } + return unreadChannelCount; + } } \ No newline at end of file diff --git a/frontend_nuxt/components/HeaderComponent.vue b/frontend_nuxt/components/HeaderComponent.vue index 8f3e3573d..37a824c93 100644 --- a/frontend_nuxt/components/HeaderComponent.vue +++ b/frontend_nuxt/components/HeaderComponent.vue @@ -89,7 +89,7 @@ import ToolTip from '~/components/ToolTip.vue' import SearchDropdown from '~/components/SearchDropdown.vue' import { authState, clearToken, loadCurrentUser } from '~/utils/auth' import { useUnreadCount } from '~/composables/useUnreadCount' -import { useChannelUnread } from '~/composables/useChannelUnread' +import { useChannelsUnreadCount } from '~/composables/useChannelsUnreadCount' import { useIsMobile } from '~/utils/screen' import { themeState, cycleTheme, ThemeMode } from '~/utils/theme' import { toast } from '~/main' @@ -108,7 +108,7 @@ const props = defineProps({ const isLogin = computed(() => authState.loggedIn) const isMobile = useIsMobile() const { count: unreadMessageCount, fetchUnreadCount } = useUnreadCount() -const { hasUnread: hasChannelUnread, fetchChannelUnread } = useChannelUnread() +const { hasUnread: hasChannelUnread, fetchChannelUnread } = useChannelsUnreadCount() const avatar = ref('') const showSearch = ref(false) const searchDropdown = ref(null) diff --git a/frontend_nuxt/composables/useChannelUnread.js b/frontend_nuxt/composables/useChannelUnread.js deleted file mode 100644 index 9ebd02500..000000000 --- a/frontend_nuxt/composables/useChannelUnread.js +++ /dev/null @@ -1,38 +0,0 @@ -import { ref } from 'vue' -import { getToken } from '~/utils/auth' - -const hasUnread = ref(false) - -export function useChannelUnread() { - const config = useRuntimeConfig() - const API_BASE_URL = config.public.apiBaseUrl - - const setFromList = (channels) => { - hasUnread.value = Array.isArray(channels) && channels.some((c) => c.unreadCount > 0) - } - - const fetchChannelUnread = async () => { - const token = getToken() - if (!token) { - hasUnread.value = false - return - } - try { - const response = await fetch(`${API_BASE_URL}/api/channels`, { - headers: { Authorization: `Bearer ${token}` }, - }) - if (response.ok) { - const data = await response.json() - setFromList(data) - } - } catch (e) { - console.error('Failed to fetch channel unread status:', e) - } - } - - return { - hasUnread, - fetchChannelUnread, - setFromList, - } -} diff --git a/frontend_nuxt/composables/useChannelsUnreadCount.js b/frontend_nuxt/composables/useChannelsUnreadCount.js new file mode 100644 index 000000000..6db3d0c64 --- /dev/null +++ b/frontend_nuxt/composables/useChannelsUnreadCount.js @@ -0,0 +1,92 @@ +import { ref, computed, watch } from 'vue' +import { useWebSocket } from './useWebSocket' +import { getToken } from '~/utils/auth' + +const count = ref(0) +let isInitialized = false +let wsSubscription = null + +export function useChannelsUnreadCount() { + const config = useRuntimeConfig() + const API_BASE_URL = config.public.apiBaseUrl + const { subscribe, isConnected, connect } = useWebSocket() + + const fetchChannelUnread = async () => { + const token = getToken() + if (!token) { + count.value = 0 + return + } + try { + const response = await fetch(`${API_BASE_URL}/api/channels/unread-count`, { + headers: { Authorization: `Bearer ${token}` }, + }) + if (response.ok) { + const data = await response.json() + count.value = data + } + } catch (e) { + console.error('Failed to fetch channel unread count:', e) + } + } + + const initialize = () => { + const token = getToken() + if (!token) { + count.value = 0 + return + } + fetchChannelUnread() + if (!isConnected.value) { + connect(token) + } + setupWebSocketListener() + } + + const setupWebSocketListener = () => { + if (!wsSubscription) { + watch( + isConnected, + (newValue) => { + if (newValue && !wsSubscription) { + wsSubscription = subscribe('/user/queue/channel-unread', (message) => { + const unread = parseInt(message.body, 10) + if (!isNaN(unread)) { + count.value = unread + } + }) + } + }, + { immediate: true }, + ) + } + } + + const setFromList = (channels) => { + count.value = Array.isArray(channels) ? channels.filter((c) => c.unreadCount > 0).length : 0 + } + + const hasUnread = computed(() => count.value > 0) + + const token = getToken() + if (token) { + if (!isInitialized) { + isInitialized = true + initialize() + } else { + fetchChannelUnread() + if (!isConnected.value) { + connect(token) + } + setupWebSocketListener() + } + } + + return { + count, + hasUnread, + fetchChannelUnread, + initialize, + setFromList, + } +} diff --git a/frontend_nuxt/composables/useWebSocket.js b/frontend_nuxt/composables/useWebSocket.js index 4fc74a5ab..c07d3a4ea 100644 --- a/frontend_nuxt/composables/useWebSocket.js +++ b/frontend_nuxt/composables/useWebSocket.js @@ -55,7 +55,10 @@ const subscribe = (destination, callback) => { try { const subscription = client.value.subscribe(destination, (message) => { try { - if (destination.includes('/queue/unread-count')) { + if ( + destination.includes('/queue/unread-count') || + destination.includes('/queue/channel-unread') + ) { callback(message) } else { const parsedMessage = JSON.parse(message.body) diff --git a/frontend_nuxt/pages/message-box/[id].vue b/frontend_nuxt/pages/message-box/[id].vue index ff2066924..e91eca09f 100644 --- a/frontend_nuxt/pages/message-box/[id].vue +++ b/frontend_nuxt/pages/message-box/[id].vue @@ -69,7 +69,7 @@ import { renderMarkdown } from '~/utils/markdown' import MessageEditor from '~/components/MessageEditor.vue' import { useWebSocket } from '~/composables/useWebSocket' import { useUnreadCount } from '~/composables/useUnreadCount' -import { useChannelUnread } from '~/composables/useChannelUnread' +import { useChannelsUnreadCount } from '~/composables/useChannelsUnreadCount' import TimeManager from '~/utils/time' import BaseTimeline from '~/components/BaseTimeline.vue' import BasePlaceholder from '~/components/BasePlaceholder.vue' @@ -79,7 +79,7 @@ const route = useRoute() const API_BASE_URL = config.public.apiBaseUrl const { connect, disconnect, subscribe, isConnected } = useWebSocket() const { fetchUnreadCount: refreshGlobalUnreadCount } = useUnreadCount() -const { fetchChannelUnread: refreshChannelUnread } = useChannelUnread() +const { fetchChannelUnread: refreshChannelUnread } = useChannelsUnreadCount() let subscription = null const messages = ref([]) diff --git a/frontend_nuxt/pages/message-box/index.vue b/frontend_nuxt/pages/message-box/index.vue index e1ace7119..b205eef09 100644 --- a/frontend_nuxt/pages/message-box/index.vue +++ b/frontend_nuxt/pages/message-box/index.vue @@ -120,7 +120,7 @@ import { getToken, fetchCurrentUser } from '~/utils/auth' import { toast } from '~/main' import { useWebSocket } from '~/composables/useWebSocket' import { useUnreadCount } from '~/composables/useUnreadCount' -import { useChannelUnread } from '~/composables/useChannelUnread' +import { useChannelsUnreadCount } from '~/composables/useChannelsUnreadCount' import TimeManager from '~/utils/time' import { stripMarkdownLength } from '~/utils/markdown' import SearchPersonDropdown from '~/components/SearchPersonDropdown.vue' @@ -136,7 +136,7 @@ const API_BASE_URL = config.public.apiBaseUrl const { connect, disconnect, subscribe, isConnected } = useWebSocket() const { fetchUnreadCount: refreshGlobalUnreadCount } = useUnreadCount() const { fetchChannelUnread: refreshChannelUnread, setFromList: setChannelUnreadFromList } = - useChannelUnread() + useChannelsUnreadCount() let subscription = null const activeTab = ref('messages') @@ -257,6 +257,9 @@ watch(isConnected, (newValue) => { subscription = subscribe(destination, (message) => { fetchConversations() + if (activeTab.value === 'channels') { + fetchChannels() + } }) } })