# 商户通知管理接口文档 ## 模块概述 商户通知管理模块提供通知统计、通知列表查询等功能,支持系统通知、订单提醒等多种通知类型。 --- ## 接口列表 1. [获取系统通知和订单提醒统计](#接口一获取系统通知和订单提醒统计) 2. [获取通知列表](#接口二获取通知列表) --- ## 接口一:获取系统通知和订单提醒统计 ### 接口信息 - **接口名称**:获取系统通知和订单提醒统计 - **接口路径**:`GET /notice/getNoticeStatistics` - **接口描述**:查询指定商户的系统通知和订单提醒统计信息,包括最新一条通知内容和未读数量 ### 请求参数 #### Query 参数 | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | receiverId | String | 是 | 接收人ID(商户ID,格式如:store_18241052019) | #### 请求示例 ```http GET /notice/getNoticeStatistics?receiverId=store_18241052019 ``` ### 响应参数 #### 有通知数据时 ```json { "code": 200, "success": true, "data": { "systemNotice": { "id": "12345", "senderId": "system", "receiverId": "store_18241052019", "businessId": 102, "title": "店铺审核通知", "context": "您的店铺已通过审核", "noticeType": 1, "isRead": 0, "createdTime": "2025-11-12 14:30:00", "systemNum": 5 }, "orderNotice": { "id": "12346", "senderId": "system", "receiverId": "store_18241052019", "businessId": 2001, "title": "新订单提醒", "context": "您有一笔新订单", "noticeType": 2, "isRead": 0, "createdTime": "2025-11-12 15:20:00", "orderNum": 3 } }, "msg": "查询成功" } ``` #### 响应字段说明 | 字段名 | 类型 | 说明 | |--------|------|------| | systemNotice | Object/String | 系统通知对象或字符串"null" | | systemNotice.systemNum | Long | 系统通知未读数量 | | orderNotice | Object/String | 订单提醒对象或字符串"null" | | orderNotice.orderNum | Long | 订单提醒未读数量 | --- ## 接口二:获取通知列表 ### 接口信息 - **接口名称**:获取通知列表 - **接口路径**:`GET /notice/getNoticeList` - **接口描述**:分页查询指定商户的通知列表,支持按通知类型筛选 ### 请求参数 #### Query 参数 | 参数名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | 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", "senderId": "user_13800138000", "receiverId": "store_18241052019", "businessId": 301, "title": "新评论通知", "context": "用户对您的店铺进行了评论", "noticeType": 0, "isRead": 0, "userName": "张三", "userImage": "https://example.com/avatar.jpg", "platformType": "1", "createdTime": "2025-11-12 16:00:00" } ], "total": 50, "size": 10, "current": 1, "pages": 5 }, "msg": null } ``` #### 响应字段说明 | 字段名 | 类型 | 说明 | |--------|------|------| | records | Array | 当前页通知列表 | | records[].id | String | 通知ID | | records[].senderId | String | 发送人ID | | records[].receiverId | String | 接收人ID | | records[].businessId | Integer | 业务ID | | records[].title | String | 通知标题 | | records[].context | String | 通知内容 | | records[].noticeType | Integer | 通知类型 | | records[].isRead | Integer | 是否已读(0-未读,1-已读) | | records[].userName | String | 发送用户名称 | | records[].userImage | String | 发送用户头像 | | records[].platformType | String | 平台类型(1-默认平台,2-其他) | | records[].createdTime | String | 创建时间 | | total | Long | 总记录数 | | size | Integer | 每页条数 | | current | Integer | 当前页码 | | pages | Integer | 总页数 | --- ## 业务逻辑说明 ### 通知类型 | noticeType | 说明 | |------------|------| | 0 | 系统通知和订单提醒之外的类型(如评论、关注、举报等) | | 1 | 系统通知(店铺审核、违规处理等) | | 2 | 订单提醒(新订单、订单核销等) | ### 平台类型判断逻辑 **平台类型 `platformType`** 用于标识通知来源平台: - `"1"`:默认平台(本平台) - `"2"`:其他平台 **判断规则**: 1. `businessId` 为空 → 默认平台 2. 标题为 "店铺审核通知" → 默认平台 3. 举报内容分类 `reportContextType` 为 1、2、3(商户、用户、动态) → 默认平台 ### 用户信息关联 通知中的 `senderId` 格式: - **系统通知**:`"system"` - **商户发送**:`"store_手机号"`(如:`store_18241052019`) - **普通用户发送**:`"user_手机号"`(如:`user_13800138000`) 系统会自动查询并关联发送人的 `userName` 和 `userImage`。 --- ## 技术实现 ### Controller 层 **文件路径**:`alien-store-platform/src/main/java/shop/alien/storeplatform/controller/NoticeController.java` ```java @ApiOperation("获取系统通知和订单提醒统计") @GetMapping("/getNoticeStatistics") public R getNoticeStatistics(@RequestParam("receiverId") String receiverId) { // ... } @ApiOperation("获取通知列表") @GetMapping("/getNoticeList") public R> getNoticeList(@RequestParam("pageNum") int pageNum, @RequestParam("pageSize") int pageSize, @RequestParam("receiverId") String receiverId, @RequestParam(value = "noticeType", defaultValue = "0") int noticeType) { // ... } ``` ### Service 层 **文件路径**:`alien-store-platform/src/main/java/shop/alien/storeplatform/service/NoticeService.java` ```java JSONObject getNoticeStatistics(String receiverId); IPage getNoticeList(int pageNum, int pageSize, String receiverId, int noticeType); ``` ### Service 实现层 **文件路径**:`alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/NoticeServiceImpl.java` #### getNoticeList 核心逻辑 ```java @Override public IPage getNoticeList(int pageNum, int pageSize, String receiverId, int noticeType) { // 1. 查询通知列表 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(LifeNotice::getReceiverId, receiverId); queryWrapper.eq(LifeNotice::getNoticeType, noticeType); queryWrapper.eq(LifeNotice::getDeleteFlag, 0); queryWrapper.orderByDesc(LifeNotice::getCreatedTime); List lifeNoticeList = lifeNoticeMapper.selectList(queryWrapper); // 2. 解析 senderId,分组为 store 和 user Map> senderIdMap = lifeNoticeList.stream() .map(LifeNotice::getSenderId) .filter(item -> item != null && !"system".equals(item) && item.contains("_")) .collect(Collectors.groupingBy( item -> item.split("_")[0], // 按 store 或 user 分组 Collectors.mapping( item -> item.split("_")[1], // 提取手机号 Collectors.toList() ))); // 3. 查询违规举报信息(用于判断平台类型) List businessIdList = lifeNoticeList.stream() .map(LifeNotice::getBusinessId) .filter(Objects::nonNull) .collect(Collectors.toList()); List lifeUserViolationList = lifeUserViolationMapper.selectBatchIds(businessIdList); Map lifeUserViolationMap = lifeUserViolationList.stream() .filter(item -> item.getReportContextType() != null) .collect(Collectors.toMap( LifeUserViolation::getId, LifeUserViolation::getReportContextType, (existing, replacement) -> existing )); // 4. 查询用户信息(商户和普通用户) String storePhones = senderIdMap.containsKey("store") ? "'" + String.join("','", senderIdMap.get("store")) + "'" : "''"; String userPhones = senderIdMap.containsKey("user") ? "'" + String.join("','", senderIdMap.get("user")) + "'" : "''"; List userList = lifeMessageMapper.getLifeUserAndStoreUserByPhone(storePhones, userPhones); // 5. 组装 LifeNoticeVo,关联用户信息 List noticeVoList = new ArrayList<>(); lifeNoticeList.forEach(item -> { LifeNoticeVo noticeVo = new LifeNoticeVo(); BeanUtils.copyProperties(item, noticeVo); // 设置用户信息(如果不是系统通知) if (!"system".equals(noticeVo.getSenderId())) { LifeMessageVo userinfo = userList.stream() .filter(user -> user.getPhoneId().equals(item.getSenderId())) .findFirst() .orElse(null); if (userinfo != null) { noticeVo.setUserName(userinfo.getUserName()); noticeVo.setUserImage(userinfo.getUserImage()); } } noticeVoList.add(noticeVo); }); // 6. 设置平台类型标识 for (LifeNoticeVo lifeNoticeVo : noticeVoList) { // businessId 为空时,设置为默认平台 if (lifeNoticeVo.getBusinessId() == null) { lifeNoticeVo.setPlatformType("1"); } // 店铺审核通知设置为默认平台 if ("店铺审核通知".equals(lifeNoticeVo.getTitle())) { lifeNoticeVo.setPlatformType("1"); } // 根据举报类型判断平台类型 if (lifeNoticeVo.getBusinessId() != null && lifeUserViolationMap.containsKey(lifeNoticeVo.getBusinessId())) { String reportContextType = lifeUserViolationMap.get(lifeNoticeVo.getBusinessId()); if ("1,2,3".contains(reportContextType)) { lifeNoticeVo.setPlatformType("1"); } } } // 7. 手动分页 List pageList = noticeVoList.stream() .skip((long) (pageNum - 1) * pageSize) .limit(pageSize) .collect(Collectors.toList()); // 8. 构建分页结果 IPage result = new Page<>(); result.setRecords(pageList); result.setTotal(noticeVoList.size()); result.setPages((int) Math.ceil(noticeVoList.size() / (double) pageSize)); result.setSize(pageSize); result.setCurrent(pageNum); return result; } ``` --- ## 依赖注入 ### Service 实现类 ```java private final LifeNoticeMapper lifeNoticeMapper; // 通知Mapper private final LifeMessageMapper lifeMessageMapper; // 消息Mapper(用于查询用户信息) private final LifeUserViolationMapper lifeUserViolationMapper; // 违规举报Mapper ``` ### 导入依赖 ```java import cn.hutool.core.collection.CollectionUtil; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; import shop.alien.entity.store.LifeNotice; import shop.alien.entity.store.LifeUserViolation; import shop.alien.entity.store.vo.LifeMessageVo; import shop.alien.entity.store.vo.LifeNoticeVo; import shop.alien.mapper.LifeMessageMapper; import shop.alien.mapper.LifeNoticeMapper; import shop.alien.mapper.LifeUserViolationMapper; import java.util.*; import java.util.stream.Collectors; ``` --- ## 原接口对比 ### 接口一:通知统计 | 项目 | 原接口 | 新接口 | |------|--------|--------| | 服务 | alien-store(app端) | alien-store-platform(web端) | | 路径 | /alienStore/notice/getSystemAndOrderNoticeSum | /notice/getNoticeStatistics | | 方法名 | getSystemAndOrderNoticeSum | getNoticeStatistics | ### 接口二:通知列表 | 项目 | 原接口 | 新接口 | |------|--------|--------| | 服务 | alien-store(app端) | alien-store-platform(web端) | | 路径 | /alienStore/notice/getNoticeByPhoneId | /notice/getNoticeList | | 方法名 | getNoticeList | getNoticeList | | Controller | LifeNoticeController | NoticeController | | Service | LifeNoticeService | NoticeService | --- ## 数据表说明 ### life_notice(通知表) | 字段名 | 类型 | 说明 | |--------|------|------| | id | String | 主键ID | | sender_id | String | 发送人ID | | receiver_id | String | 接收人ID | | business_id | Integer | 业务ID | | title | String | 通知标题 | | context | String | 通知内容 | | notice_type | Integer | 通知类型(0/1/2) | | is_read | Integer | 是否已读(0-未读,1-已读) | | delete_flag | Integer | 删除标记(0-未删除,1-已删除) | | created_time | Date | 创建时间 | ### life_user_violation(用户举报表) | 字段名 | 类型 | 说明 | |--------|------|------| | id | Integer | 主键ID | | report_context_type | String | 举报内容分类(0-商户,1-用户,2-动态,3-评论,4-二手商品,5-二手用户) | | violation_type | String | 违规类型 | | processing_status | String | 处理状态(0-未处理,1-违规,2-未违规) | --- ## 错误处理 ### 异常情况 1. **参数错误**:receiverId、pageNum、pageSize 为空或无效 2. **数据库异常**:查询失败 3. **分页参数错误**:pageNum < 1 或 pageSize < 1 ### 错误响应示例 ```json { "code": 500, "success": false, "data": null, "msg": "查询失败:数据库连接异常" } ``` --- ## 使用场景 ### 1. 首页通知角标 调用 `getNoticeStatistics` 获取未读数量,在首页显示通知角标。 ### 2. 通知中心列表 调用 `getNoticeList` 获取通知列表,展示不同类型的通知: - `noticeType=0`:其他通知(评论、关注等) - `noticeType=1`:系统通知 - `noticeType=2`:订单提醒 ### 3. 实时更新 通过 WebSocket 或定时轮询更新通知数量和列表。 --- ## 测试建议 ### 测试场景 **接口一(通知统计)**: 1. 有系统通知和订单提醒 2. 只有系统通知 3. 只有订单提醒 4. 没有任何通知 5. 未读数量统计准确性 **接口二(通知列表)**: 1. 正常分页查询 2. 空数据场景 3. 不同 noticeType 查询 4. 用户信息关联正确性 5. 平台类型标识正确性 6. 分页边界测试(第1页、最后1页、超出页数) ### 测试SQL ```sql -- 查询商户的通知统计 SELECT notice_type, COUNT(*) AS total_count, SUM(CASE WHEN is_read = 0 THEN 1 ELSE 0 END) AS unread_count, MAX(created_time) AS latest_time FROM life_notice WHERE receiver_id = 'store_18241052019' AND notice_type IN (1, 2) AND delete_flag = 0 GROUP BY notice_type; -- 查询通知列表 SELECT id, sender_id, receiver_id, business_id, title, context, notice_type, is_read, created_time FROM life_notice WHERE receiver_id = 'store_18241052019' AND notice_type = 0 AND delete_flag = 0 ORDER BY created_time DESC LIMIT 10 OFFSET 0; ``` --- ## 注意事项 1. **receiverId 格式**:通常为 `store_` + 手机号(如:`store_18241052019`) 2. **逻辑删除**:MyBatis-Plus 自动处理 `delete_flag` 字段 3. **手动分页**:由于需要关联多表和复杂逻辑,采用手动分页而非数据库分页 4. **性能优化**: - 一次性查询所有通知,避免N+1问题 - 批量查询用户信息和违规举报信息 - 使用 Stream API 进行数据转换和过滤 5. **空值处理**:通知统计接口返回字符串 `"null"` 而非 null 对象 6. **日志记录**:详细记录查询参数、结果数量,便于问题追踪 --- ## 更新日志 ### 2025-11-12 **接口一(通知统计)**: - ✅ 完成接口迁移:从 alien-store 迁移到 alien-store-platform - ✅ Controller 层:创建 `NoticeController`,添加 `getNoticeStatistics` 接口 - ✅ Service 层:创建 `NoticeService`,添加方法定义 - ✅ Service 实现层:创建 `NoticeServiceImpl`,实现统计逻辑 - ✅ 业务逻辑:完全复用原接口逻辑 - ✅ 命名规范:符合web端命名规范 **接口二(通知列表)**: - ✅ 完成接口迁移:从 alien-store 迁移到 alien-store-platform - ✅ Controller 层:添加 `getNoticeList` 接口 - ✅ Service 层:添加 `getNoticeList` 方法定义 - ✅ Service 实现层:实现复杂的通知列表查询逻辑 - ✅ 依赖注入:添加 `LifeMessageMapper` 和 `LifeUserViolationMapper` - ✅ 用户信息关联:实现商户和普通用户信息查询 - ✅ 平台类型判断:实现复杂的平台类型标识逻辑 - ✅ 手动分页:实现内存分页功能 - ✅ Linter 检查:修复所有警告 - ✅ 业务逻辑:完全复用原接口逻辑 - ✅ 命名规范:符合web端命名规范 --- ## 开发者信息 - **迁移时间**:2025-11-12 - **原服务**:alien-store(app端商户) - **目标服务**:alien-store-platform(web端商户) - **技术栈**:Spring Boot + MyBatis-Plus + FastJSON + Hutool + Java 8