Просмотр исходного кода

feat: 推送消息已读改为显式接口(新增单条/全部已读)

- 新增 GET /system/message/readMessage?id=x 单条置已读(按 id+user_id 过滤防越权)
- 新增 GET /system/message/readAllMessage 全部置已读(清角标)
- getPushMessageList 摘除进入列表即全部置已读的副作用,恢复纯查询
- 测试:列表断言只查不改(never update);单条/全部已读各 1 用例,共 4 用例通过
qmj 5 часов с этого момента
Родитель
Сommit
91b0c3e1f9

+ 33 - 7
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/PushMessageController.java

@@ -36,7 +36,7 @@ public class PushMessageController extends BaseController
     private IPushMessageService pushMessageService;
 
     /**
-     * 骑手、商家端获取推送消息分页列表(进入列表即全部置为已读)
+     * 骑手、商家端获取推送消息分页列表
      */
     @Anonymous
     @Auth
@@ -48,12 +48,6 @@ public class PushMessageController extends BaseController
         LambdaQueryWrapper<PushMessage> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.eq(PushMessage::getUserId,uid).orderByDesc(PushMessage::getTime);
         IPage<PushMessage> data = pushMessageService.page(page,queryWrapper);
-        // 先查后置:本次返回保留原已读状态供前端画红点,随后将该用户全部未读置为已读(清角标)
-        LambdaUpdateWrapper<PushMessage> updateWrapper = new LambdaUpdateWrapper<>();
-        updateWrapper.set(PushMessage::getIsRead, 1)
-                .eq(PushMessage::getUserId, uid)
-                .eq(PushMessage::getIsRead, 0);
-        pushMessageService.update(updateWrapper);
         return success(data);
     }
 
@@ -71,6 +65,38 @@ public class PushMessageController extends BaseController
         return success(pushMessageService.count(queryWrapper));
     }
 
+    /**
+     * 骑手、商家端单条消息置为已读
+     */
+    @Anonymous
+    @Auth
+    @GetMapping("/readMessage")
+    public AjaxResult readMessage(@RequestHeader String token, @RequestParam Long id) {
+        JwtUtil jwtUtil = new JwtUtil();
+        String uid = jwtUtil.getusid(token);
+        LambdaUpdateWrapper<PushMessage> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(PushMessage::getIsRead, 1)
+                .eq(PushMessage::getId, id)
+                .eq(PushMessage::getUserId, uid);
+        return toAjax(pushMessageService.update(updateWrapper));
+    }
+
+    /**
+     * 骑手、商家端全部消息置为已读(清角标)
+     */
+    @Anonymous
+    @Auth
+    @GetMapping("/readAllMessage")
+    public AjaxResult readAllMessage(@RequestHeader String token) {
+        JwtUtil jwtUtil = new JwtUtil();
+        String uid = jwtUtil.getusid(token);
+        LambdaUpdateWrapper<PushMessage> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(PushMessage::getIsRead, 1)
+                .eq(PushMessage::getUserId, uid)
+                .eq(PushMessage::getIsRead, 0);
+        return toAjax(pushMessageService.update(updateWrapper));
+    }
+
     /**
      * 查询推送消息列表
      */

+ 46 - 15
ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/PushMessageControllerTest.java

@@ -17,7 +17,6 @@ import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
 import org.springframework.beans.factory.support.DefaultListableBeanFactory;
 import org.springframework.context.support.StaticMessageSource;
@@ -30,14 +29,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
- * 推送消息已读标识:列表接口"先查后置已读"(进入列表即清角标,
- * 本次返回的数据保留原已读状态供前端画红点),未读数接口按当前用户过滤。
+ * 推送消息已读标识:列表接口只查不改;已读由 readMessage(单条)
+ * 与 readAllMessage(全部)显式变更;未读数接口按当前用户过滤。
  */
 class PushMessageControllerTest {
 
@@ -71,7 +70,7 @@ class PushMessageControllerTest {
     }
 
     @Test
-    void listEndpointMarksAllUserMessagesReadAfterPageQuery() {
+    void listEndpointReturnsPageQueryWithoutChangingReadState() {
         Page<PushMessage> page = new Page<>(1, 10);
         page.setRecords(new ArrayList<>());
         when(pushMessageService.page(any(Page.class), any(Wrapper.class))).thenReturn(page);
@@ -80,18 +79,13 @@ class PushMessageControllerTest {
 
         assertEquals(200, ((Number) result.get(AjaxResult.CODE_TAG)).intValue());
         assertSame(page, result.get(AjaxResult.DATA_TAG));
-        // 必须先分页查询后置已读:否则返回页永远全是已读,前端画不出红点
-        InOrder inOrder = inOrder(pushMessageService);
-        inOrder.verify(pushMessageService).page(any(Page.class), any(Wrapper.class));
         @SuppressWarnings("unchecked")
         ArgumentCaptor<Wrapper<PushMessage>> captor = ArgumentCaptor.forClass(Wrapper.class);
-        inOrder.verify(pushMessageService).update(captor.capture());
-        LambdaUpdateWrapper<PushMessage> updateWrapper = (LambdaUpdateWrapper<PushMessage>) captor.getValue();
-        assertTrue(updateWrapper.getSqlSet().contains("is_read"));
-        assertTrue(updateWrapper.getSqlSegment().contains("user_id"));
-        assertTrue(updateWrapper.getSqlSegment().contains("is_read"));
-        assertTrue(updateWrapper.getParamNameValuePairs().containsValue("9"));
-        assertTrue(updateWrapper.getParamNameValuePairs().containsValue(0));
+        verify(pushMessageService).page(any(Page.class), captor.capture());
+        LambdaQueryWrapper<PushMessage> queryWrapper = (LambdaQueryWrapper<PushMessage>) captor.getValue();
+        assertTrue(queryWrapper.getSqlSegment().contains("user_id"));
+        // 列表接口只查不改:已读由 readMessage(单条)/ readAllMessage(全部)显式变更
+        verify(pushMessageService, never()).update(any(Wrapper.class));
     }
 
     @Test
@@ -111,4 +105,41 @@ class PushMessageControllerTest {
         assertTrue(queryWrapper.getParamNameValuePairs().containsValue("9"));
         assertTrue(queryWrapper.getParamNameValuePairs().containsValue(0));
     }
+
+    @Test
+    void readMessageEndpointMarksSingleMessageReadForTokenUserOnly() {
+        when(pushMessageService.update(any(Wrapper.class))).thenReturn(true);
+
+        AjaxResult result = controller.readMessage(JwtUtil.token("9", "user"), 5L);
+
+        assertEquals(200, ((Number) result.get(AjaxResult.CODE_TAG)).intValue());
+        @SuppressWarnings("unchecked")
+        ArgumentCaptor<Wrapper<PushMessage>> captor = ArgumentCaptor.forClass(Wrapper.class);
+        verify(pushMessageService).update(captor.capture());
+        LambdaUpdateWrapper<PushMessage> updateWrapper = (LambdaUpdateWrapper<PushMessage>) captor.getValue();
+        assertTrue(updateWrapper.getSqlSet().contains("is_read"));
+        // 必须同时按消息 id 与当前用户过滤:既定位单条,也防越权把别人消息置已读
+        assertTrue(updateWrapper.getSqlSegment().contains("user_id"));
+        assertTrue(updateWrapper.getParamNameValuePairs().containsValue(5L));
+        assertTrue(updateWrapper.getParamNameValuePairs().containsValue("9"));
+    }
+
+    @Test
+    void readAllMessageEndpointMarksAllUnreadForTokenUser() {
+        when(pushMessageService.update(any(Wrapper.class))).thenReturn(true);
+
+        AjaxResult result = controller.readAllMessage(JwtUtil.token("9", "user"));
+
+        assertEquals(200, ((Number) result.get(AjaxResult.CODE_TAG)).intValue());
+        @SuppressWarnings("unchecked")
+        ArgumentCaptor<Wrapper<PushMessage>> captor = ArgumentCaptor.forClass(Wrapper.class);
+        verify(pushMessageService).update(captor.capture());
+        LambdaUpdateWrapper<PushMessage> updateWrapper = (LambdaUpdateWrapper<PushMessage>) captor.getValue();
+        assertTrue(updateWrapper.getSqlSet().contains("is_read"));
+        // 全部已读 = 该用户所有 is_read=0 的记录置 1,不夹带其它条件
+        assertTrue(updateWrapper.getSqlSegment().contains("user_id"));
+        assertTrue(updateWrapper.getSqlSegment().contains("is_read"));
+        assertTrue(updateWrapper.getParamNameValuePairs().containsValue("9"));
+        assertTrue(updateWrapper.getParamNameValuePairs().containsValue(0));
+    }
 }