06-通知列表查询接口.md 21 KB

商户通知管理接口文档

模块概述

商户通知管理模块提供通知统计、通知列表查询、通知已读标记等功能,支持系统通知、订单提醒等多种通知类型。


接口列表

  1. 获取系统通知和订单提醒统计
  2. 获取通知列表
  3. 标记通知为已读

接口一:获取系统通知和订单提醒统计

接口信息

  • 接口名称:获取系统通知和订单提醒统计
  • 接口路径GET /notice/getNoticeStatistics
  • 接口描述:查询指定商户的系统通知和订单提醒统计信息,包括最新一条通知内容和未读数量

请求参数

Query 参数

参数名 类型 必填 说明
receiverId String 接收人ID(商户ID,格式如:store_18241052019)

请求示例

GET /notice/getNoticeStatistics?receiverId=store_18241052019

响应参数

有通知数据时

{
    "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": "查询成功"
}

无通知数据时

{
    "code": 200,
    "success": true,
    "data": {
        "systemNotice": "null",
        "orderNotice": "null"
    },
    "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-订单提醒)

请求示例

GET /notice/getNoticeList?receiverId=store_18241052019&pageNum=1&pageSize=10&noticeType=0

响应参数

响应数据结构

{
    "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 总页数

接口三:标记通知为已读

接口信息

  • 接口名称:标记通知为已读
  • 接口路径POST /notice/markNoticeAsRead
  • 接口描述:将指定ID的通知标记为已读状态

请求参数

Query 参数

参数名 类型 必填 说明
id Integer 通知ID

请求示例

POST /notice/markNoticeAsRead?id=2589

响应参数

成功响应

{
    "code": 200,
    "success": true,
    "data": null,
    "msg": "标记已读成功"
}

失败响应

{
    "code": 500,
    "success": false,
    "data": null,
    "msg": "标记已读失败"
}

响应字段说明

字段名 类型 说明
code Integer 响应状态码,200表示成功
success Boolean 是否成功
data Any 数据(此接口为null)
msg String 响应消息

业务逻辑说明

通知类型

noticeType 说明
0 系统通知和订单提醒之外的类型(如评论、关注、举报等)
1 系统通知(店铺审核、违规处理等)
2 订单提醒(新订单、订单核销等)

已读状态

isRead 说明
0 未读
1 已读

平台类型判断逻辑

平台类型 platformType 用于标识通知来源平台:

  • "1":默认平台(本平台)
  • "2":其他平台

判断规则

  1. businessId 为空 → 默认平台
  2. 标题为 "店铺审核通知" → 默认平台
  3. 举报内容分类 reportContextType 为 1、2、3(商户、用户、动态) → 默认平台

用户信息关联

通知中的 senderId 格式:

  • 系统通知"system"
  • 商户发送"store_手机号"(如:store_18241052019
  • 普通用户发送"user_手机号"(如:user_13800138000

系统会自动查询并关联发送人的 userNameuserImage


技术实现

Controller 层

文件路径alien-store-platform/src/main/java/shop/alien/storeplatform/controller/NoticeController.java

@Slf4j
@Api(tags = {"web端商户通知管理"})
@ApiSort(6)
@CrossOrigin
@RestController
@RequestMapping("/notice")
@RequiredArgsConstructor
public class NoticeController {

    private final NoticeService noticeService;

    @ApiOperation("获取系统通知和订单提醒统计")
    @GetMapping("/getNoticeStatistics")
    public R<JSONObject> getNoticeStatistics(@RequestParam("receiverId") String receiverId) {
        // 查询通知统计信息
    }

    @ApiOperation("获取通知列表")
    @GetMapping("/getNoticeList")
    public R<IPage<LifeNoticeVo>> getNoticeList(
            @RequestParam("pageNum") int pageNum,
            @RequestParam("pageSize") int pageSize,
            @RequestParam("receiverId") String receiverId,
            @RequestParam(value = "noticeType", defaultValue = "0") int noticeType) {
        // 查询通知列表
    }

    @ApiOperation("标记通知为已读")
    @PostMapping("/markNoticeAsRead")
    public R<Boolean> markNoticeAsRead(@RequestParam("id") Integer id) {
        // 标记通知为已读
    }
}

Service 层

文件路径alien-store-platform/src/main/java/shop/alien/storeplatform/service/NoticeService.java

public interface NoticeService {

    /**
     * 获取系统通知和订单提醒统计
     */
    JSONObject getNoticeStatistics(String receiverId);

    /**
     * 获取通知列表
     */
    IPage<LifeNoticeVo> getNoticeList(int pageNum, int pageSize, 
                                      String receiverId, int noticeType);

    /**
     * 标记通知为已读
     */
    int markNoticeAsRead(Integer id);
}

Service 实现层

文件路径alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/NoticeServiceImpl.java

核心实现

1. getNoticeStatistics - 通知统计

@Override
public JSONObject getNoticeStatistics(String receiverId) {
    // 1. 查询系统通知和订单提醒(noticeType: 1, 2)
    LambdaQueryWrapper<LifeNotice> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(LifeNotice::getReceiverId, receiverId);
    queryWrapper.in(LifeNotice::getNoticeType, 1, 2);
    List<LifeNotice> lifeNoticeList = lifeNoticeMapper.selectList(queryWrapper);

    // 2. 获取最新一条系统通知和订单提醒
    LifeNotice systemNotice = lifeNoticeList.stream()
            .filter(item -> 1 == item.getNoticeType())
            .max(Comparator.comparing(LifeNotice::getCreatedTime))
            .orElse(null);

    // 3. 统计未读数量
    long systemUnreadCount = lifeNoticeList.stream()
            .filter(item -> 1 == item.getNoticeType() && item.getIsRead() == 0)
            .count();

    // 4. 构建返回结果
    // ...
}

2. getNoticeList - 通知列表

@Override
public IPage<LifeNoticeVo> getNoticeList(int pageNum, int pageSize, 
                                          String receiverId, int noticeType) {
    // 1. 查询通知列表
    List<LifeNotice> lifeNoticeList = lifeNoticeMapper.selectList(queryWrapper);

    // 2. 解析 senderId,分组为 store 和 user
    Map<String, List<String>> senderIdMap = /* 分组逻辑 */;

    // 3. 查询违规举报信息(用于平台类型判断)
    List<LifeUserViolation> lifeUserViolationList = /* 查询逻辑 */;

    // 4. 查询用户信息(商户和普通用户)
    List<LifeMessageVo> userList = lifeMessageMapper
            .getLifeUserAndStoreUserByPhone(storePhones, userPhones);

    // 5. 组装 LifeNoticeVo,关联用户信息
    // 6. 设置平台类型标识
    // 7. 手动分页
    // 8. 构建分页结果
}

3. markNoticeAsRead - 标记已读

@Override
public int markNoticeAsRead(Integer id) {
    // 使用 LambdaUpdateWrapper 更新 isRead 字段
    LambdaUpdateWrapper<LifeNotice> wrapper = new LambdaUpdateWrapper<>();
    wrapper.eq(LifeNotice::getId, id);
    wrapper.set(LifeNotice::getIsRead, 1);
    
    return lifeNoticeMapper.update(null, wrapper);
}

依赖注入

Service 实现类

private final LifeNoticeMapper lifeNoticeMapper;         // 通知Mapper
private final LifeMessageMapper lifeMessageMapper;       // 消息Mapper(用于查询用户信息)
private final LifeUserViolationMapper lifeUserViolationMapper;  // 违规举报Mapper

导入依赖

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
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

接口三:标记已读

项目 原接口 新接口
服务 alien-store(app端) alien-store-platform(web端)
路径 /alienStore/notice/readNoticeById /notice/markNoticeAsRead
方法名 readNoticeById markNoticeAsRead
请求方式 GET POST

数据表说明

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、id 为空或无效
  2. 数据库异常:查询或更新失败
  3. 分页参数错误:pageNum < 1 或 pageSize < 1
  4. 通知不存在:标记已读时通知ID不存在

错误响应示例

{
    "code": 500,
    "success": false,
    "data": null,
    "msg": "查询失败:数据库连接异常"
}

使用场景

场景一:通知中心首页

  1. 调用 getNoticeStatistics 获取未读数量,显示通知角标
  2. 调用 getNoticeList 获取最新通知列表

场景二:通知分类浏览

根据不同 noticeType 查询不同类型的通知:

  • noticeType=0:其他通知(评论、关注等)
  • noticeType=1:系统通知
  • noticeType=2:订单提醒

场景三:通知已读管理

  1. 用户点击某条通知
  2. 调用 markNoticeAsRead 标记为已读
  3. 重新调用 getNoticeStatistics 更新未读数量

场景四:实时更新

通过 WebSocket 或定时轮询:

  1. 定期调用 getNoticeStatistics 更新未读数量
  2. 有新通知时刷新 getNoticeList

测试建议

接口一(通知统计)测试场景

  1. ✅ 有系统通知和订单提醒
  2. ✅ 只有系统通知
  3. ✅ 只有订单提醒
  4. ✅ 没有任何通知
  5. ✅ 未读数量统计准确性
  6. ✅ 最新通知时间排序正确性

接口二(通知列表)测试场景

  1. ✅ 正常分页查询
  2. ✅ 空数据场景
  3. ✅ 不同 noticeType 查询
  4. ✅ 用户信息关联正确性
  5. ✅ 平台类型标识正确性
  6. ✅ 分页边界测试(第1页、最后1页、超出页数)
  7. ✅ 大数据量性能测试

接口三(标记已读)测试场景

  1. ✅ 标记未读通知为已读
  2. ✅ 重复标记(应该仍然返回成功)
  3. ✅ 不存在的ID(返回失败)
  4. ✅ 已删除的通知(MyBatis-Plus 自动过滤)
  5. ✅ 并发标记测试

测试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;

-- 标记通知为已读
UPDATE life_notice 
SET is_read = 1 
WHERE id = 2589 
  AND delete_flag = 0;

注意事项

  1. receiverId 格式:通常为 store_ + 手机号(如:store_18241052019
  2. 逻辑删除:MyBatis-Plus 自动处理 delete_flag 字段
  3. 手动分页:由于需要关联多表和复杂逻辑,采用手动分页而非数据库分页
  4. 性能优化
    • 一次性查询所有通知,避免N+1问题
    • 批量查询用户信息和违规举报信息
    • 使用 Stream API 进行数据转换和过滤
  5. 空值处理:通知统计接口返回字符串 "null" 而非 null 对象
  6. 日志记录:详细记录查询参数、结果数量、操作结果,便于问题追踪
  7. 幂等性:标记已读接口支持重复调用,不会产生副作用
  8. 安全性:需要验证 receiverId 和 id 的合法性,防止越权访问

接口调用流程示例

完整业务流程

用户进入通知中心
    ↓
① 调用 getNoticeStatistics
    ↓
显示:系统通知(5)、订单提醒(3)
    ↓
② 调用 getNoticeList(noticeType=1)
    ↓
显示系统通知列表(分页)
    ↓
用户点击某条通知
    ↓
③ 调用 markNoticeAsRead(id=2589)
    ↓
标记成功后,再次调用 ① 更新未读数量
    ↓
显示:系统通知(4)、订单提醒(3)

更新日志

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 实现层:实现复杂的通知列表查询逻辑
  • ✅ 依赖注入:添加 LifeMessageMapperLifeUserViolationMapper
  • ✅ 用户信息关联:实现商户和普通用户信息查询
  • ✅ 平台类型判断:实现复杂的平台类型标识逻辑
  • ✅ 手动分页:实现内存分页功能
  • ✅ 业务逻辑:完全复用原接口逻辑

接口三(标记已读)

  • ✅ 完成接口迁移:从 alien-store 迁移到 alien-store-platform
  • ✅ Controller 层:添加 markNoticeAsRead 接口
  • ✅ Service 层:添加 markNoticeAsRead 方法定义
  • ✅ Service 实现层:使用 LambdaUpdateWrapper 实现更新逻辑
  • ✅ 请求方式:改为 POST,更符合 REST 规范
  • ✅ 命名优化:readNoticeByIdmarkNoticeAsRead(更语义化)
  • ✅ 业务逻辑:完全复用原接口逻辑
  • ✅ Linter 检查:无错误

开发者信息

  • 迁移时间:2025-11-12
  • 原服务:alien-store(app端商户)
  • 目标服务:alien-store-platform(web端商户)
  • 技术栈:Spring Boot + MyBatis-Plus + FastJSON + Hutool + Java 8
  • 开发人员:ssk
  • 文档版本:v1.0

附录:常见问题

Q1:为什么通知列表使用手动分页?

A:因为需要关联多个表(用户信息、违规举报信息)并进行复杂的数据处理(平台类型判断),如果使用数据库分页,SQL会非常复杂且难以维护。手动分页虽然会查询全部数据,但在通知数量不是特别大的情况下(通常每个商户的通知数量有限),性能是可以接受的。

Q2:为什么通知统计返回字符串 "null" 而不是 null?

A:这是为了保持与原接口的兼容性。原接口就是这样设计的,可能是为了避免前端处理 null 值的麻烦。

Q3:如何批量标记多个通知为已读?

A:当前接口只支持单个通知标记。如需批量标记,可以扩展接口,接收 ID 数组参数,循环调用更新逻辑。

Q4:通知的删除和标记已读有什么区别?

A

  • 标记已读is_read = 1,通知仍然存在,只是标记为已读状态
  • 删除通知delete_flag = 1,逻辑删除,通知不再显示在列表中

Q5:如何保证通知的实时性?

A:建议结合以下方案:

  1. 后端:使用 WebSocket 推送新通知
  2. 前端:定时轮询通知统计接口(如每30秒)
  3. 缓存:使用 Redis 缓存未读数量,减少数据库压力