22-批量标记通知已读接口-DTO版.md 18 KB

批量标记通知已读接口(一键已读)- DTO版本

更新说明

本文档为 markAllNoticesAsRead 接口的最新版本,该接口已从使用Query参数改为使用DTO接收JSON请求体。


接口信息

批量标记通知为已读(一键已读)

接口详情

  • 接口名称: 批量标记通知为已读(一键已读)
  • 接口路径: POST /notice/markAllNoticesAsRead
  • 请求方式: POST
  • Content-Type: application/json
  • 接口描述: 批量将指定用户的未读通知标记为已读,支持按通知类型筛选
  • 登录验证: ❌ 不需要(公开接口)

请求参数

请求体(JSON格式)

使用 MarkAllNoticesReadDTO 作为请求体:

参数名 类型 必填 说明 示例值
receiverId String 接收人ID(商户ID) "store_18241052019"
noticeType Integer 通知类型 1

noticeType 说明

  • 0: 系统通知和订单提醒之外的类型
  • 1: 系统通知
  • 2: 订单提醒
  • 不传或传 null: 标记所有类型的通知

DTO定义

@Data
@ApiModel(description = "批量标记通知已读请求参数")
public class MarkAllNoticesReadDTO implements Serializable {
    
    @ApiModelProperty(value = "接收人ID(商户ID)", required = true, example = "store_18241052019")
    private String receiverId;
    
    @ApiModelProperty(value = "通知类型(0-系统通知和订单提醒之外的类型,1-系统通知,2-订单提醒)。不传则标记所有类型", required = false, example = "1")
    private Integer noticeType;
}

请求示例

示例1: 标记所有类型的通知为已读

POST /notice/markAllNoticesAsRead
Content-Type: application/json

{
    "receiverId": "store_18241052019"
}
curl -X POST "http://localhost:8080/notice/markAllNoticesAsRead" \
  -H "Content-Type: application/json" \
  -d '{
    "receiverId": "store_18241052019"
  }'

示例2: 只标记系统通知为已读

POST /notice/markAllNoticesAsRead
Content-Type: application/json

{
    "receiverId": "store_18241052019",
    "noticeType": 1
}
curl -X POST "http://localhost:8080/notice/markAllNoticesAsRead" \
  -H "Content-Type: application/json" \
  -d '{
    "receiverId": "store_18241052019",
    "noticeType": 1
  }'

示例3: 只标记订单提醒为已读

POST /notice/markAllNoticesAsRead
Content-Type: application/json

{
    "receiverId": "store_18241052019",
    "noticeType": 2
}
curl -X POST "http://localhost:8080/notice/markAllNoticesAsRead" \
  -H "Content-Type: application/json" \
  -d '{
    "receiverId": "store_18241052019",
    "noticeType": 2
  }'

响应参数

成功响应

{
    "code": 200,
    "success": true,
    "data": 15,
    "msg": "批量标记已读成功,共标记 15 条通知"
}

响应字段说明

字段名 类型 说明
code Integer 状态码,200表示成功
success Boolean 是否成功
data Integer 被标记的通知数量
msg String 提示信息

响应示例

场景1: 成功标记多条通知

请求:

{
    "receiverId": "store_18241052019",
    "noticeType": 1
}

响应:

{
    "code": 200,
    "success": true,
    "data": 15,
    "msg": "批量标记已读成功,共标记 15 条通知"
}

场景2: 没有需要标记的通知

请求:

{
    "receiverId": "store_18241052019",
    "noticeType": 1
}

响应:

{
    "code": 200,
    "success": true,
    "data": 0,
    "msg": "没有需要标记的通知"
}

说明: 当前用户该类型的通知已全部已读,或不存在该类型的通知。


场景3: 参数错误

请求:

{
    "noticeType": 1
}

响应:

{
    "code": 500,
    "success": false,
    "data": null,
    "msg": "receiverId不能为空"
}

场景4: 系统异常

响应:

{
    "code": 500,
    "success": false,
    "data": null,
    "msg": "标记失败:{异常信息}"
}

业务逻辑说明

处理流程

┌─────────────────────────────────────────────┐
│  1. 接收DTO参数                               │
│  - receiverId (必填)                         │
│  - noticeType (可选)                         │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  2. 参数验证                                  │
│  IF receiverId == null OR receiverId.isEmpty()│
│    RETURN 错误:receiverId不能为空           │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  3. 构建查询条件                              │
│  wrapper.eq("receiver_id", receiverId)       │
│  wrapper.eq("is_read", 0)  // 未读           │
│  IF noticeType != null                       │
│    wrapper.eq("notice_type", noticeType)     │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  4. 查询符合条件的未读通知                    │
│  List<LifeNotice> notices = selectList(wrapper)│
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  5. 批量更新为已读状态                        │
│  updateWrapper.set("is_read", 1)             │
│  updateWrapper.eq("receiver_id", receiverId)  │
│  updateWrapper.eq("is_read", 0)              │
│  IF noticeType != null                       │
│    updateWrapper.eq("notice_type", noticeType)│
│  int count = update(null, updateWrapper)     │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  6. 返回结果                                  │
│  IF count > 0                                │
│    RETURN "批量标记已读成功,共标记 {count} 条"│
│  ELSE                                        │
│    RETURN "没有需要标记的通知"               │
└─────────────────────────────────────────────┘

更新SQL

UPDATE life_notice
SET is_read = 1
WHERE receiver_id = ?
  AND is_read = 0
  AND notice_type = ?  -- 如果指定了noticeType

前端集成示例

Vue.js 示例

export default {
    methods: {
        // 一键已读(所有类型)
        async markAllAsRead() {
            try {
                const response = await this.$axios.post('/notice/markAllNoticesAsRead', {
                    receiverId: this.currentUserId
                });
                
                if (response.data.success) {
                    this.$message.success(response.data.msg);
                    // 刷新通知列表
                    this.fetchNoticeList();
                } else {
                    this.$message.error(response.data.msg);
                }
            } catch (error) {
                console.error('标记失败:', error);
                this.$message.error('操作失败,请稍后重试');
            }
        },
        
        // 标记指定类型的通知为已读
        async markTypeAsRead(noticeType) {
            try {
                const response = await this.$axios.post('/notice/markAllNoticesAsRead', {
                    receiverId: this.currentUserId,
                    noticeType: noticeType
                });
                
                if (response.data.success) {
                    const count = response.data.data;
                    this.$message.success(`成功标记 ${count} 条通知为已读`);
                    this.fetchNoticeList();
                }
            } catch (error) {
                console.error('标记失败:', error);
            }
        }
    }
}

React 示例

import { useState } from 'react';
import axios from 'axios';
import { message } from 'antd';

function NoticeComponent() {
    const [userId, setUserId] = useState('store_18241052019');
    
    // 一键已读(所有类型)
    const markAllAsRead = async () => {
        try {
            const response = await axios.post('/notice/markAllNoticesAsRead', {
                receiverId: userId
            });
            
            if (response.data.success) {
                message.success(response.data.msg);
                // 刷新列表
                fetchNoticeList();
            }
        } catch (error) {
            message.error('操作失败');
        }
    };
    
    // 标记指定类型
    const markTypeAsRead = async (noticeType) => {
        try {
            const response = await axios.post('/notice/markAllNoticesAsRead', {
                receiverId: userId,
                noticeType: noticeType
            });
            
            if (response.data.success) {
                const count = response.data.data;
                message.success(`成功标记 ${count} 条通知为已读`);
            }
        } catch (error) {
            message.error('操作失败');
        }
    };
    
    return (
        <div>
            <button onClick={markAllAsRead}>一键已读(全部)</button>
            <button onClick={() => markTypeAsRead(1)}>标记系统通知已读</button>
            <button onClick={() => markTypeAsRead(2)}>标记订单提醒已读</button>
        </div>
    );
}

jQuery/原生JS示例

// 使用jQuery
function markAllNoticesAsRead(receiverId, noticeType) {
    $.ajax({
        url: '/notice/markAllNoticesAsRead',
        type: 'POST',
        contentType: 'application/json',
        data: JSON.stringify({
            receiverId: receiverId,
            noticeType: noticeType
        }),
        success: function(response) {
            if (response.success) {
                alert(response.msg);
                // 刷新通知列表
                loadNoticeList();
            } else {
                alert('操作失败:' + response.msg);
            }
        },
        error: function(xhr, status, error) {
            alert('网络错误,请稍后重试');
        }
    });
}

// 使用原生Fetch API
async function markAllNoticesAsRead(receiverId, noticeType = null) {
    try {
        const response = await fetch('/notice/markAllNoticesAsRead', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                receiverId: receiverId,
                noticeType: noticeType
            })
        });
        
        const data = await response.json();
        
        if (data.success) {
            console.log('标记成功:', data.msg);
            return data.data; // 返回标记的数量
        } else {
            console.error('标记失败:', data.msg);
            return 0;
        }
    } catch (error) {
        console.error('请求失败:', error);
        return 0;
    }
}

// 使用示例
markAllNoticesAsRead('store_18241052019'); // 标记所有类型
markAllNoticesAsRead('store_18241052019', 1); // 只标记系统通知

与旧版本对比

旧版本(Query参数)

@PostMapping("/markAllNoticesAsRead")
public R<Integer> markAllNoticesAsRead(
    @RequestParam("receiverId") String receiverId,
    @RequestParam(value = "noticeType", required = false) Integer noticeType
)

请求方式:

POST /notice/markAllNoticesAsRead?receiverId=store_18241052019&noticeType=1

新版本(JSON Body + DTO)

@PostMapping("/markAllNoticesAsRead")
public R<Integer> markAllNoticesAsRead(@RequestBody MarkAllNoticesReadDTO dto)

请求方式:

POST /notice/markAllNoticesAsRead
Content-Type: application/json

{
    "receiverId": "store_18241052019",
    "noticeType": 1
}

差异对比

项目 旧版本 新版本 说明
参数传递方式 Query参数 JSON Body 更符合RESTful规范
参数定义 直接在方法参数中 使用DTO 更易维护和扩展
Content-Type application/x-www-form-urlencoded application/json 标准JSON格式
Swagger文档 @ApiImplicitParams @ApiModel/@ApiModelProperty 自动生成完整文档
参数验证 手动验证 可使用@Valid + 验证注解 更规范
可扩展性 新增参数只需修改DTO

参数验证增强(可选)

如果需要更严格的参数验证,可以在DTO中添加验证注解:

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Min;
import javax.validation.constraints.Max;

@Data
@ApiModel(description = "批量标记通知已读请求参数")
public class MarkAllNoticesReadDTO implements Serializable {
    
    @NotBlank(message = "接收人ID不能为空")
    @ApiModelProperty(value = "接收人ID(商户ID)", required = true)
    private String receiverId;
    
    @Min(value = 0, message = "通知类型必须大于等于0")
    @Max(value = 2, message = "通知类型必须小于等于2")
    @ApiModelProperty(value = "通知类型", required = false)
    private Integer noticeType;
}

然后在Controller中启用验证:

@PostMapping("/markAllNoticesAsRead")
public R<Integer> markAllNoticesAsRead(@RequestBody @Valid MarkAllNoticesReadDTO dto) {
    // ...
}

常见问题

Q1: 为什么要改用DTO?

答案:

  1. 更符合RESTful规范: POST请求应使用JSON Body传递数据
  2. 易于扩展: 新增参数只需修改DTO,不影响方法签名
  3. 类型安全: 强类型DTO,编译时检查
  4. 易于验证: 可以使用Bean Validation注解
  5. 文档更清晰: Swagger自动生成完整的请求体文档

Q2: 旧版本的调用方式还能用吗?

答案: 不能。接口已改为使用JSON Body,必须按新的方式调用。


Q3: 如何迁移现有代码?

答案:

旧代码:

$.post('/notice/markAllNoticesAsRead?receiverId=' + userId + '&noticeType=1');

新代码:

$.ajax({
    url: '/notice/markAllNoticesAsRead',
    type: 'POST',
    contentType: 'application/json',
    data: JSON.stringify({
        receiverId: userId,
        noticeType: 1
    })
});

Q4: noticeType 不传和传null有区别吗?

答案: 没有区别。不传或传null都表示标记所有类型的通知。


Q5: 返回的data是什么?

答案: data是被标记为已读的通知数量(Integer类型)。如果为0表示没有需要标记的通知。


测试用例

测试场景1: 标记所有类型的通知

请求:

{
    "receiverId": "store_18241052019"
}

预期结果:

  • 返回标记的数量 > 0
  • 所有未读通知被标记为已读

测试场景2: 只标记系统通知

请求:

{
    "receiverId": "store_18241052019",
    "noticeType": 1
}

预期结果:

  • 只有系统通知(noticeType=1)被标记
  • 其他类型的通知保持不变

测试场景3: receiverId为空

请求:

{
    "noticeType": 1
}

预期结果:

  • 返回错误:"receiverId不能为空"

测试场景4: 无未读通知

前置条件:

  • 用户的所有通知都已读

请求:

{
    "receiverId": "store_18241052019"
}

预期结果:

{
    "code": 200,
    "data": 0,
    "msg": "没有需要标记的通知"
}

性能优化

1. 批量更新

使用MyBatis-Plus的批量更新功能,一次SQL更新所有符合条件的记录:

UPDATE life_notice 
SET is_read = 1 
WHERE receiver_id = ? 
  AND is_read = 0 
  AND notice_type = ?

2. 索引优化

确保以下字段有索引:

-- 复合索引
ALTER TABLE life_notice 
ADD INDEX idx_receiver_read_type (receiver_id, is_read, notice_type);

3. 返回更新数量

使用 update() 方法的返回值直接获取更新的记录数,避免额外查询。


注意事项

1. Content-Type必须正确

⚠️ 必须设置 Content-Type: application/json,否则服务器无法解析JSON请求体。

2. JSON格式

⚠️ 请求体必须是有效的JSON格式,字段名需要与DTO中的字段名完全匹配。

3. noticeType可选

⚠️ noticeType 是可选参数,不传或传null表示标记所有类型的通知。

4. 返回值类型

⚠️ data 字段是Integer类型,表示被标记的数量,不是布尔值。


更新日志

2025-11-17

接口重构:

  • ✅ 从Query参数改为JSON Body + DTO
  • ✅ 创建 MarkAllNoticesReadDTO
  • ✅ 修改Controller方法签名
  • ✅ 移除 @ApiImplicitParams 注解
  • ✅ 使用 @RequestBody 接收参数

优势:

  • ✅ 更符合RESTful规范
  • ✅ 更易扩展和维护
  • ✅ Swagger文档更清晰
  • ✅ 支持参数验证注解

涉及文件:

  • MarkAllNoticesReadDTO.java - 新增
  • NoticeController.java - 修改

代码质量:

  • ✅ Linter检查:无错误
  • ✅ 代码规范:符合Java规范
  • ✅ 注释完整:完整的注释和文档

开发人员: ssk


文档版本: v2.0
最后更新: 2025-11-17
维护人员: ssk