# 商户通知管理接口文档 ## 模块概述 商户通知管理模块提供通知统计、通知列表查询、通知已读标记(单个/批量)等功能,支持系统通知、订单提醒等多种通知类型。 --- ## 接口列表 1. [获取系统通知和订单提醒统计](#接口一获取系统通知和订单提醒统计) 2. [获取通知列表](#接口二获取通知列表) 3. [标记通知为已读](#接口三标记通知为已读) 4. [批量标记通知为已读(一键已读)](#接口四批量标记通知为已读一键已读) ⭐ **新增** --- ## 接口一:获取系统通知和订单提醒统计 ### 接口信息 - **接口名称**:获取系统通知和订单提醒统计 - **接口路径**:`GET /notice/getNoticeStatistics` - **接口描述**:查询指定商户的系统通知和订单提醒统计信息,包括最新一条通知内容和未读数量 ### 请求参数 | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | receiverId | String | 是 | 接收人ID(商户ID,格式如:store_18241052019) | ### 请求示例 ```http GET /notice/getNoticeStatistics?receiverId=store_18241052019 ``` ### 响应参数 ```json { "code": 200, "success": true, "data": { "systemNotice": { "id": "12345", "title": "店铺审核通知", "context": "您的店铺已通过审核", "noticeType": 1, "isRead": 0, "createdTime": "2025-11-12 14:30:00", "systemNum": 5 }, "orderNotice": { "id": "12346", "title": "新订单提醒", "context": "您有一笔新订单", "noticeType": 2, "isRead": 0, "createdTime": "2025-11-12 15:20:00", "orderNum": 3 } }, "msg": "查询成功" } ``` --- ## 接口二:获取通知列表 ### 接口信息 - **接口名称**:获取通知列表 - **接口路径**:`GET /notice/getNoticeList` - **接口描述**:分页查询指定商户的通知列表,支持按通知类型筛选 ### 请求参数 | 参数名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | pageNum | int | 是 | - | 页码(从1开始) | | pageSize | int | 是 | - | 每页条数 | | receiverId | String | 是 | - | 接收人ID(商户ID) | | noticeType | int | 否 | 0 | 通知类型(0-其他,1-系统通知,2-订单提醒) | ### 请求示例 ```http GET /notice/getNoticeList?receiverId=store_18241052019&pageNum=1&pageSize=10¬iceType=0 ``` ### 响应参数 ```json { "code": 200, "success": true, "data": { "records": [ { "id": "12347", "title": "新评论通知", "context": "用户对您的店铺进行了评论", "noticeType": 0, "isRead": 0, "userName": "张三", "userImage": "https://example.com/avatar.jpg", "createdTime": "2025-11-12 16:00:00" } ], "total": 50, "size": 10, "current": 1, "pages": 5 } } ``` --- ## 接口三:标记通知为已读 ### 接口信息 - **接口名称**:标记通知为已读 - **接口路径**:`POST /notice/markNoticeAsRead` - **接口描述**:将指定ID的通知标记为已读状态 ### 请求参数 | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | id | Integer | 是 | 通知ID | ### 请求示例 ```http POST /notice/markNoticeAsRead?id=2589 ``` ### 响应参数 ```json { "code": 200, "success": true, "data": null, "msg": "标记已读成功" } ``` --- ## 接口四:批量标记通知为已读(一键已读) ### 接口信息 - **接口名称**:批量标记通知为已读(一键已读) - **接口路径**:`POST /notice/markAllNoticesAsRead` - **接口描述**:批量将指定商户的未读通知标记为已读,支持按通知类型筛选 ### 请求参数 | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | receiverId | String | 是 | 接收人ID(商户ID) | | noticeType | Integer | 否 | 通知类型(0-其他,1-系统通知,2-订单提醒)。不传则标记所有类型的未读通知 | ### 请求示例 #### 示例1:标记所有未读通知 ```http POST /notice/markAllNoticesAsRead?receiverId=store_18241052019 ``` #### 示例2:只标记系统通知 ```http POST /notice/markAllNoticesAsRead?receiverId=store_18241052019¬iceType=1 ``` #### 示例3:只标记订单提醒 ```http POST /notice/markAllNoticesAsRead?receiverId=store_18241052019¬iceType=2 ``` ### 响应参数 #### 成功响应(有未读通知) ```json { "code": 200, "success": true, "data": 15, "msg": "批量标记已读成功,共标记 15 条通知" } ``` #### 成功响应(无未读通知) ```json { "code": 200, "success": true, "data": 0, "msg": "没有需要标记的通知" } ``` #### 失败响应 ```json { "code": 500, "success": false, "data": null, "msg": "批量标记失败:数据库异常" } ``` ### 响应字段说明 | 字段名 | 类型 | 说明 | |--------|------|------| | code | Integer | 响应状态码,200表示成功 | | success | Boolean | 是否成功 | | data | Integer | 标记的通知数量 | | msg | String | 响应消息 | --- ## 业务逻辑说明 ### 通知类型 | noticeType | 说明 | |------------|------| | 0 | 系统通知和订单提醒之外的类型(如评论、关注、举报等) | | 1 | 系统通知(店铺审核、违规处理等) | | 2 | 订单提醒(新订单、订单核销等) | ### 批量标记逻辑 **接口四(markAllNoticesAsRead)** 的核心逻辑: 1. **条件组合**: - 必须条件:`receiverId = ?` AND `isRead = 0` - 可选条件:`noticeType = ?`(如果传入) 2. **更新操作**: - 将符合条件的所有通知的 `isRead` 字段更新为 `1` 3. **返回结果**: - 返回实际更新的行数 4. **SQL 等效**: ```sql -- 标记所有未读通知 UPDATE life_notice SET is_read = 1 WHERE receiver_id = 'store_18241052019' AND is_read = 0 AND delete_flag = 0; -- 只标记系统通知 UPDATE life_notice SET is_read = 1 WHERE receiver_id = 'store_18241052019' AND is_read = 0 AND notice_type = 1 AND delete_flag = 0; ``` --- ## 技术实现 ### Controller 层 ```java @ApiOperation("批量标记通知为已读(一键已读)") @PostMapping("/markAllNoticesAsRead") public R markAllNoticesAsRead( @RequestParam("receiverId") String receiverId, @RequestParam(value = "noticeType", required = false) Integer noticeType) { try { int result = noticeService.markAllNoticesAsRead(receiverId, noticeType); if (result > 0) { return R.data(result, "批量标记已读成功,共标记 " + result + " 条通知"); } return R.data(0, "没有需要标记的通知"); } catch (Exception e) { log.error("批量标记失败: {}", e.getMessage(), e); return R.fail(e.getMessage()); } } ``` ### Service 层 ```java /** * 批量标记通知为已读(一键已读) * * @param receiverId 接收人ID(商户ID) * @param noticeType 通知类型(可选,不传则标记所有类型) * @return 影响行数 */ int markAllNoticesAsRead(String receiverId, Integer noticeType); ``` ### Service 实现层 ```java @Override public int markAllNoticesAsRead(String receiverId, Integer noticeType) { log.info("批量标记通知为已读: receiverId={}, noticeType={}", receiverId, noticeType); // 使用 LambdaUpdateWrapper 批量更新 isRead 字段 LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); // 1. 接收人条件(必须) wrapper.eq(LifeNotice::getReceiverId, receiverId); // 2. 只更新未读的通知 wrapper.eq(LifeNotice::getIsRead, 0); // 3. 通知类型条件(可选) if (noticeType != null) { wrapper.eq(LifeNotice::getNoticeType, noticeType); } // 4. 设置为已读 wrapper.set(LifeNotice::getIsRead, 1); int result = lifeNoticeMapper.update(null, wrapper); log.info("批量标记完成: 影响行数={}", result); return result; } ``` --- ## 使用场景 ### 场景一:通知中心一键清除 用户进入通知中心,点击"一键已读"按钮: ```javascript // 前端调用示例 async function markAllAsRead() { const response = await axios.post('/notice/markAllNoticesAsRead', null, { params: { receiverId: 'store_18241052019' } }); if (response.data.success) { console.log(`成功标记 ${response.data.data} 条通知为已读`); // 刷新通知列表和统计 refreshNoticeList(); refreshNoticeStatistics(); } } ``` ### 场景二:分类一键已读 用户在"系统通知"标签页点击"全部已读": ```javascript // 只标记系统通知为已读 async function markSystemNoticesAsRead() { const response = await axios.post('/notice/markAllNoticesAsRead', null, { params: { receiverId: 'store_18241052019', noticeType: 1 // 系统通知 } }); if (response.data.success) { console.log(`成功标记 ${response.data.data} 条系统通知为已读`); } } ``` ### 场景三:定时自动标记 后台定时任务,自动标记超过7天的未读通知(需扩展接口) --- ## 接口调用流程 ### 完整业务流程 ``` 用户进入通知中心 ↓ ① 调用 getNoticeStatistics ↓ 显示:系统通知(5)、订单提醒(3) ↓ ② 调用 getNoticeList(noticeType=1) ↓ 显示系统通知列表 ↓ 用户点击"全部已读"按钮 ↓ ③ 调用 markAllNoticesAsRead(receiverId, noticeType=1) ↓ 返回:成功标记 5 条通知 ↓ ④ 重新调用 getNoticeStatistics ↓ 显示:系统通知(0)、订单提醒(3) ``` --- ## 对比:单个 vs 批量标记 | 特性 | 单个标记 | 批量标记 | |------|----------|----------| | 接口 | markNoticeAsRead | markAllNoticesAsRead | | 参数 | id(通知ID) | receiverId + noticeType(可选) | | 更新范围 | 单条通知 | 多条未读通知 | | 返回值 | 影响行数(0或1) | 影响行数(实际标记数量) | | 使用场景 | 点击单条通知 | 一键清除未读 | | 性能 | 单次更新 | 批量更新(更高效) | ### 性能对比 假设有 100 条未读通知: **方式一:循环调用单个标记** ```javascript // ❌ 效率低:需要 100 次网络请求 for (let id of noticeIds) { await markNoticeAsRead(id); // 100次请求 } ``` **方式二:调用批量标记** ```javascript // ✅ 效率高:只需 1 次网络请求 await markAllNoticesAsRead(receiverId, noticeType); // 1次请求 ``` --- ## 错误处理 ### 异常情况 1. **参数错误**:receiverId 为空 2. **数据库异常**:批量更新失败 3. **无权限**:尝试标记其他商户的通知 ### 错误响应示例 ```json { "code": 500, "success": false, "data": null, "msg": "批量标记失败:receiverId 不能为空" } ``` --- ## 测试建议 ### 测试场景 1. ✅ **标记所有类型**:`noticeType` 不传,验证所有未读通知被标记 2. ✅ **标记指定类型**:传入 `noticeType=1`,验证只标记系统通知 3. ✅ **重复调用**:多次调用,验证返回 0(已无未读通知) 4. ✅ **空数据场景**:商户无未读通知,验证返回 0 5. ✅ **并发测试**:多个请求同时标记,验证数据一致性 6. ✅ **边界测试**: - receiverId 不存在 - noticeType 为无效值(如 99) ### 测试SQL ```sql -- 查询商户的未读通知数量(标记前) SELECT notice_type, COUNT(*) AS unread_count FROM life_notice WHERE receiver_id = 'store_18241052019' AND is_read = 0 AND delete_flag = 0 GROUP BY notice_type; -- 执行批量标记(模拟) UPDATE life_notice SET is_read = 1 WHERE receiver_id = 'store_18241052019' AND is_read = 0 AND delete_flag = 0; -- 验证标记结果(标记后) SELECT notice_type, COUNT(*) AS unread_count FROM life_notice WHERE receiver_id = 'store_18241052019' AND is_read = 0 AND delete_flag = 0 GROUP BY notice_type; -- 应该返回 0 条记录 ``` --- ## 注意事项 1. **幂等性**:支持重复调用,多次调用不会产生副作用 2. **性能优化**:使用批量更新,避免循环调用单个标记接口 3. **逻辑删除**:MyBatis-Plus 自动过滤 `delete_flag=1` 的记录 4. **事务安全**:更新操作在事务中执行,保证数据一致性 5. **日志记录**:记录操作参数和影响行数,便于问题追踪 6. **返回信息**:明确告知用户标记了多少条通知 7. **可选参数**:`noticeType` 不传时标记所有类型,传入时只标记指定类型 8. **安全性**:需要验证 `receiverId` 的合法性,防止越权操作 --- ## 接口汇总表 | 序号 | 接口名称 | 接口路径 | 请求方式 | 主要功能 | |------|----------|----------|----------|----------| | 1 | 获取通知统计 | /notice/getNoticeStatistics | GET | 查询未读数量和最新通知 | | 2 | 获取通知列表 | /notice/getNoticeList | GET | 分页查询通知列表 | | 3 | 标记单个已读 | /notice/markNoticeAsRead | POST | 标记指定通知为已读 | | 4 | 批量标记已读 | /notice/markAllNoticesAsRead | POST | 一键标记多个通知为已读 | --- ## 更新日志 ### 2025-11-12 **新增接口**: - ✅ **markAllNoticesAsRead**(批量标记通知为已读) - Controller 层:添加 `/markAllNoticesAsRead` 接口 - Service 层:添加 `markAllNoticesAsRead` 方法定义 - Service 实现层:实现批量更新逻辑 - 支持可选参数 `noticeType`,可按类型筛选 - 返回实际标记的通知数量 - 完整的日志记录和异常处理 - Linter 检查:无错误 **其他接口**: - ✅ getNoticeStatistics(通知统计) - ✅ getNoticeList(通知列表) - ✅ markNoticeAsRead(单个标记已读) --- ## 开发者信息 - **迁移时间**:2025-11-12 - **原服务**:alien-store(app端商户) - **目标服务**:alien-store-platform(web端商户) - **技术栈**:Spring Boot + MyBatis-Plus + FastJSON + Java 8 - **开发人员**:ssk - **文档版本**:v2.0(新增批量标记接口) --- ## 常见问题 ### Q1:批量标记和单个标记有什么区别? **A**: - **单个标记**:针对指定ID的通知,适用于用户点击单条通知时 - **批量标记**:针对指定商户的所有未读通知(可按类型筛选),适用于"一键已读"功能 ### Q2:批量标记是否支持按时间范围筛选? **A**:当前版本不支持按时间范围筛选。如需此功能,可以扩展接口,添加 `startTime` 和 `endTime` 参数。 ### Q3:批量标记是否会标记已读的通知? **A**:不会。批量标记只会更新 `isRead = 0`(未读)的通知,已读通知(`isRead = 1`)会被自动跳过。 ### Q4:如何实现"标记最近7天的未读通知"? **A**:需要扩展接口,添加时间范围参数: ```java wrapper.ge(LifeNotice::getCreatedTime, LocalDate.now().minusDays(7)); ``` ### Q5:批量标记的性能如何? **A**:性能优异。使用 MyBatis-Plus 的批量更新,一次SQL执行完成所有更新操作,比循环调用单个标记接口效率高数百倍。 ### Q6:批量标记后如何通知前端更新? **A**:建议的方案: 1. 批量标记成功后,前端重新调用 `getNoticeStatistics` 更新角标 2. 刷新当前通知列表页面 3. 如果使用 WebSocket,可以推送更新事件 --- ## 扩展建议 ### 未来可扩展功能 1. **按时间范围标记**: - 添加 `startTime` 和 `endTime` 参数 - 实现"标记最近7天的通知"等功能 2. **按ID列表标记**: - 添加 `ids` 数组参数 - 支持勾选多个通知后批量标记 3. **标记并删除**: - 一次操作完成标记已读+逻辑删除 - 适用于"清空通知"功能 4. **定时自动标记**: - 后台定时任务 - 自动标记超过指定天数的未读通知 5. **标记统计**: - 返回更详细的统计信息 - 如:按类型分别显示标记数量