diff --git a/open-isle-cli/src/components/MenuComponent.vue b/open-isle-cli/src/components/MenuComponent.vue index 4ce27c9b3..99d8bb54c 100644 --- a/open-isle-cli/src/components/MenuComponent.vue +++ b/open-isle-cli/src/components/MenuComponent.vue @@ -9,6 +9,7 @@ 我的消息 + @@ -31,6 +32,9 @@ @@ -94,6 +114,15 @@ export default { opacity: 0.5; } +.unread-dot { + display: inline-block; + width: 8px; + height: 8px; + margin-left: 4px; + border-radius: 50%; + background-color: red; +} + .menu-footer { height: 30px; display: flex; diff --git a/open-isle-cli/src/utils/notification.js b/open-isle-cli/src/utils/notification.js new file mode 100644 index 000000000..8e912d8f9 --- /dev/null +++ b/open-isle-cli/src/utils/notification.js @@ -0,0 +1,17 @@ +import { API_BASE_URL } from '../main' +import { getToken } from './auth' + +export async function fetchUnreadCount() { + try { + const token = getToken() + if (!token) return 0 + const res = await fetch(`${API_BASE_URL}/api/notifications/unread-count`, { + headers: { Authorization: `Bearer ${token}` } + }) + if (!res.ok) return 0 + const data = await res.json() + return data.count + } catch (e) { + return 0 + } +} diff --git a/src/main/java/com/openisle/controller/NotificationController.java b/src/main/java/com/openisle/controller/NotificationController.java index 45df2240d..90bcdb2e5 100644 --- a/src/main/java/com/openisle/controller/NotificationController.java +++ b/src/main/java/com/openisle/controller/NotificationController.java @@ -29,6 +29,14 @@ public class NotificationController { .collect(Collectors.toList()); } + @GetMapping("/unread-count") + public UnreadCount unreadCount(Authentication auth) { + long count = notificationService.countUnread(auth.getName()); + UnreadCount uc = new UnreadCount(); + uc.setCount(count); + return uc; + } + @PostMapping("/read") public void markRead(@RequestBody MarkReadRequest req, Authentication auth) { notificationService.markRead(auth.getName(), req.getIds()); @@ -115,4 +123,9 @@ public class NotificationController { private String username; private String avatar; } + + @Data + private static class UnreadCount { + private long count; + } } diff --git a/src/main/java/com/openisle/repository/NotificationRepository.java b/src/main/java/com/openisle/repository/NotificationRepository.java index 0ed952243..06bf87881 100644 --- a/src/main/java/com/openisle/repository/NotificationRepository.java +++ b/src/main/java/com/openisle/repository/NotificationRepository.java @@ -10,4 +10,5 @@ import java.util.List; public interface NotificationRepository extends JpaRepository { List findByUserOrderByCreatedAtDesc(User user); List findByUserAndReadOrderByCreatedAtDesc(User user, boolean read); + long countByUserAndRead(User user, boolean read); } diff --git a/src/main/java/com/openisle/service/NotificationService.java b/src/main/java/com/openisle/service/NotificationService.java index c2b7e9551..5f0f21c37 100644 --- a/src/main/java/com/openisle/service/NotificationService.java +++ b/src/main/java/com/openisle/service/NotificationService.java @@ -45,4 +45,10 @@ public class NotificationService { } notificationRepository.saveAll(notifs); } + + public long countUnread(String username) { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new IllegalArgumentException("User not found")); + return notificationRepository.countByUserAndRead(user, false); + } } diff --git a/src/test/java/com/openisle/controller/NotificationControllerTest.java b/src/test/java/com/openisle/controller/NotificationControllerTest.java index 90a0dda0f..53a8c80f8 100644 --- a/src/test/java/com/openisle/controller/NotificationControllerTest.java +++ b/src/test/java/com/openisle/controller/NotificationControllerTest.java @@ -60,4 +60,14 @@ class NotificationControllerTest { verify(notificationService).markRead("alice", List.of(1L,2L)); } + + @Test + void unreadCountEndpoint() throws Exception { + Mockito.when(notificationService.countUnread("alice")).thenReturn(3L); + + mockMvc.perform(get("/api/notifications/unread-count") + .principal(new UsernamePasswordAuthenticationToken("alice","p"))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.count").value(3)); + } } diff --git a/src/test/java/com/openisle/service/NotificationServiceTest.java b/src/test/java/com/openisle/service/NotificationServiceTest.java index 10a31bc5f..6efd06498 100644 --- a/src/test/java/com/openisle/service/NotificationServiceTest.java +++ b/src/test/java/com/openisle/service/NotificationServiceTest.java @@ -60,4 +60,22 @@ class NotificationServiceTest { assertEquals(1, list.size()); verify(nRepo).findByUserOrderByCreatedAtDesc(user); } + + @Test + void countUnreadReturnsRepositoryValue() { + NotificationRepository nRepo = mock(NotificationRepository.class); + UserRepository uRepo = mock(UserRepository.class); + NotificationService service = new NotificationService(nRepo, uRepo); + + User user = new User(); + user.setId(3L); + user.setUsername("carl"); + when(uRepo.findByUsername("carl")).thenReturn(Optional.of(user)); + when(nRepo.countByUserAndRead(user, false)).thenReturn(5L); + + long count = service.countUnread("carl"); + + assertEquals(5L, count); + verify(nRepo).countByUserAndRead(user, false); + } }