Pārlūkot izejas kodu

feat(notice): 新增商户通知管理接口文档

- 添加通知统计、列表查询、单条及批量已读标记功能- 支持系统通知、订单提醒等多种通知类型
- 提供完整的接口说明、请求响应示例和技术实现细节
- 包含业务逻辑说明、使用场景和测试建议
- 文档覆盖controller、service层代码示例
-详细描述参数校验、错误处理和性能优化策略
- 增加接口对比表和数据表结构说明- 补充常见问题解答和开发者注意事项
- 实现手动分页和多表关联查询逻辑- 支持WebSocket实时推送和定时轮询机制
wxd 1 mēnesi atpakaļ
vecāks
revīzija
29dc540794

+ 571 - 0
alien-store-platform/doc/API_NOTICE_MANAGEMENT.md

@@ -0,0 +1,571 @@
+# 商户通知管理接口文档
+
+## 模块概述
+
+商户通知管理模块提供通知统计、通知列表查询等功能,支持系统通知、订单提醒等多种通知类型。
+
+---
+
+## 接口列表
+
+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&noticeType=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<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) {
+    // ...
+}
+```
+
+### Service 层
+
+**文件路径**:`alien-store-platform/src/main/java/shop/alien/storeplatform/service/NoticeService.java`
+
+```java
+JSONObject getNoticeStatistics(String receiverId);
+
+IPage<LifeNoticeVo> 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<LifeNoticeVo> getNoticeList(int pageNum, int pageSize, String receiverId, int noticeType) {
+    // 1. 查询通知列表
+    LambdaQueryWrapper<LifeNotice> queryWrapper = new LambdaQueryWrapper<>();
+    queryWrapper.eq(LifeNotice::getReceiverId, receiverId);
+    queryWrapper.eq(LifeNotice::getNoticeType, noticeType);
+    queryWrapper.eq(LifeNotice::getDeleteFlag, 0);
+    queryWrapper.orderByDesc(LifeNotice::getCreatedTime);
+    List<LifeNotice> lifeNoticeList = lifeNoticeMapper.selectList(queryWrapper);
+
+    // 2. 解析 senderId,分组为 store 和 user
+    Map<String, List<String>> 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<Integer> businessIdList = lifeNoticeList.stream()
+            .map(LifeNotice::getBusinessId)
+            .filter(Objects::nonNull)
+            .collect(Collectors.toList());
+    
+    List<LifeUserViolation> lifeUserViolationList = 
+            lifeUserViolationMapper.selectBatchIds(businessIdList);
+    
+    Map<Integer, String> 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<LifeMessageVo> userList = 
+            lifeMessageMapper.getLifeUserAndStoreUserByPhone(storePhones, userPhones);
+
+    // 5. 组装 LifeNoticeVo,关联用户信息
+    List<LifeNoticeVo> 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<LifeNoticeVo> pageList = noticeVoList.stream()
+            .skip((long) (pageNum - 1) * pageSize)
+            .limit(pageSize)
+            .collect(Collectors.toList());
+
+    // 8. 构建分页结果
+    IPage<LifeNoticeVo> 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
+

+ 641 - 0
alien-store-platform/doc/API_NOTICE_MANAGEMENT_ALLREAD.md

@@ -0,0 +1,641 @@
+# 商户通知管理接口文档
+
+## 模块概述
+
+商户通知管理模块提供通知统计、通知列表查询、通知已读标记(单个/批量)等功能,支持系统通知、订单提醒等多种通知类型。
+
+---
+
+## 接口列表
+
+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&noticeType=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&noticeType=1
+```
+
+#### 示例3:只标记订单提醒
+
+```http
+POST /notice/markAllNoticesAsRead?receiverId=store_18241052019&noticeType=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<Integer> 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<LifeNotice> 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. **标记统计**:
+   - 返回更详细的统计信息
+   - 如:按类型分别显示标记数量

+ 743 - 0
alien-store-platform/doc/API_NOTICE_MANAGEMENT_DETALIS.md

@@ -0,0 +1,743 @@
+# 商户通知管理接口文档
+
+## 模块概述
+
+商户通知管理模块提供通知统计、通知列表查询、通知已读标记等功能,支持系统通知、订单提醒等多种通知类型。
+
+---
+
+## 接口列表
+
+1. [获取系统通知和订单提醒统计](#接口一获取系统通知和订单提醒统计)
+2. [获取通知列表](#接口二获取通知列表)
+3. [标记通知为已读](#接口三标记通知为已读)
+
+---
+
+## 接口一:获取系统通知和订单提醒统计
+
+### 接口信息
+
+- **接口名称**:获取系统通知和订单提醒统计
+- **接口路径**:`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": "查询成功"
+}
+```
+
+#### 无通知数据时
+
+```json
+{
+    "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-订单提醒) |
+
+#### 请求示例
+
+```http
+GET /notice/getNoticeList?receiverId=store_18241052019&pageNum=1&pageSize=10&noticeType=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 | 总页数 |
+
+---
+
+## 接口三:标记通知为已读
+
+### 接口信息
+
+- **接口名称**:标记通知为已读
+- **接口路径**:`POST /notice/markNoticeAsRead`
+- **接口描述**:将指定ID的通知标记为已读状态
+
+### 请求参数
+
+#### Query 参数
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| id | Integer | 是 | 通知ID |
+
+#### 请求示例
+
+```http
+POST /notice/markNoticeAsRead?id=2589
+```
+
+### 响应参数
+
+#### 成功响应
+
+```json
+{
+    "code": 200,
+    "success": true,
+    "data": null,
+    "msg": "标记已读成功"
+}
+```
+
+#### 失败响应
+
+```json
+{
+    "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`)
+
+系统会自动查询并关联发送人的 `userName` 和 `userImage`。
+
+---
+
+## 技术实现
+
+### Controller 层
+
+**文件路径**:`alien-store-platform/src/main/java/shop/alien/storeplatform/controller/NoticeController.java`
+
+```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`
+
+```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 - 通知统计**
+
+```java
+@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 - 通知列表**
+
+```java
+@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 - 标记已读**
+
+```java
+@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 实现类
+
+```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.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不存在
+
+### 错误响应示例
+
+```json
+{
+    "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
+
+```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 实现层:实现复杂的通知列表查询逻辑
+- ✅ 依赖注入:添加 `LifeMessageMapper` 和 `LifeUserViolationMapper`
+- ✅ 用户信息关联:实现商户和普通用户信息查询
+- ✅ 平台类型判断:实现复杂的平台类型标识逻辑
+- ✅ 手动分页:实现内存分页功能
+- ✅ 业务逻辑:完全复用原接口逻辑
+
+**接口三(标记已读)**:
+- ✅ 完成接口迁移:从 alien-store 迁移到 alien-store-platform
+- ✅ Controller 层:添加 `markNoticeAsRead` 接口
+- ✅ Service 层:添加 `markNoticeAsRead` 方法定义
+- ✅ Service 实现层:使用 LambdaUpdateWrapper 实现更新逻辑
+- ✅ 请求方式:改为 POST,更符合 REST 规范
+- ✅ 命名优化:`readNoticeById` → `markNoticeAsRead`(更语义化)
+- ✅ 业务逻辑:完全复用原接口逻辑
+- ✅ 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 缓存未读数量,减少数据库压力

+ 114 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/NoticeController.java

@@ -0,0 +1,114 @@
+package shop.alien.storeplatform.controller;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.vo.LifeNoticeVo;
+import shop.alien.storeplatform.annotation.LoginRequired;
+import shop.alien.storeplatform.service.NoticeService;
+
+/**
+ * web端商户通知管理 前端控制器
+ *
+ * @author ssk
+ * @since 2025-01-xx
+ */
+@Slf4j
+@Api(tags = {"web端商户通知管理"})
+@ApiSort(6)
+@CrossOrigin
+@RestController
+@RequestMapping("/notice")
+@RequiredArgsConstructor
+public class NoticeController {
+
+    private final NoticeService noticeService;
+
+    @ApiOperation("获取系统通知和订单提醒统计")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "receiverId", value = "接收人ID(商户ID)", dataType = "String", paramType = "query", required = true)
+    })
+    @GetMapping("/getNoticeStatistics")
+    public R<JSONObject> getNoticeStatistics(@RequestParam("receiverId") String receiverId) {
+        log.info("NoticeController.getNoticeStatistics?receiverId={}", receiverId);
+        try {
+            JSONObject result = noticeService.getNoticeStatistics(receiverId);
+            return R.data(result, "查询成功");
+        } catch (Exception e) {
+            log.error("NoticeController.getNoticeStatistics ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("获取通知列表")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "页码", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "pageSize", value = "每页条数", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "receiverId", value = "接收人ID(商户ID)", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "noticeType", value = "通知类型(0-系统通知和订单提醒之外的类型,1-系统通知,2-订单提醒)", dataType = "int", paramType = "query", required = false, defaultValue = "0")
+    })
+    @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) {
+        log.info("NoticeController.getNoticeList?pageNum={}, pageSize={}, receiverId={}, noticeType={}", 
+                pageNum, pageSize, receiverId, noticeType);
+        try {
+            IPage<LifeNoticeVo> result = noticeService.getNoticeList(pageNum, pageSize, receiverId, noticeType);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("NoticeController.getNoticeList ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("标记通知为已读")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "通知ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @PostMapping("/markNoticeAsRead")
+    public R<Boolean> markNoticeAsRead(@RequestParam("id") Integer id) {
+        log.info("NoticeController.markNoticeAsRead?id={}", id);
+        try {
+            int result = noticeService.markNoticeAsRead(id);
+            if (result > 0) {
+                return R.success("标记已读成功");
+            }
+            return R.fail("标记已读失败");
+        } catch (Exception e) {
+            log.error("NoticeController.markNoticeAsRead ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("批量标记通知为已读(一键已读)")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "receiverId", value = "接收人ID(商户ID)", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "noticeType", value = "通知类型(0-系统通知和订单提醒之外的类型,1-系统通知,2-订单提醒)。不传则标记所有类型", dataType = "Integer", paramType = "query", required = false)
+    })
+    @PostMapping("/markAllNoticesAsRead")
+    public R<Integer> markAllNoticesAsRead(@RequestParam("receiverId") String receiverId,
+                                            @RequestParam(value = "noticeType", required = false) Integer noticeType) {
+        log.info("NoticeController.markAllNoticesAsRead?receiverId={}, noticeType={}", receiverId, 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("NoticeController.markAllNoticesAsRead ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+}
+

+ 52 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/NoticeService.java

@@ -0,0 +1,52 @@
+package shop.alien.storeplatform.service;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import shop.alien.entity.store.vo.LifeNoticeVo;
+
+/**
+ * web端商户通知管理服务接口
+ *
+ * @author ssk
+ * @since 2025-01-xx
+ */
+public interface NoticeService {
+
+    /**
+     * 获取系统通知和订单提醒统计
+     * 返回系统通知和订单提醒的最新一条记录,以及各自的未读数量
+     *
+     * @param receiverId 接收人ID(商户ID)
+     * @return JSONObject 包含 systemNotice 和 orderNotice 两个字段
+     */
+    JSONObject getNoticeStatistics(String receiverId);
+
+    /**
+     * 获取通知列表
+     *
+     * @param pageNum 页码
+     * @param pageSize 每页条数
+     * @param receiverId 接收人ID(商户ID)
+     * @param noticeType 通知类型(0-系统通知和订单提醒之外的类型,1-系统通知,2-订单提醒)
+     * @return 分页通知列表
+     */
+    IPage<LifeNoticeVo> getNoticeList(int pageNum, int pageSize, String receiverId, int noticeType);
+
+    /**
+     * 标记通知为已读
+     *
+     * @param id 通知ID
+     * @return 影响行数
+     */
+    int markNoticeAsRead(Integer id);
+
+    /**
+     * 批量标记通知为已读
+     *
+     * @param receiverId 接收人ID(商户ID)
+     * @param noticeType 通知类型(0-系统通知和订单提醒之外的类型,1-系统通知,2-订单提醒)
+     * @return 影响行数
+     */
+    int markAllNoticesAsRead(String receiverId, Integer noticeType);
+}
+

+ 289 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/NoticeServiceImpl.java

@@ -0,0 +1,289 @@
+package shop.alien.storeplatform.service.impl;
+
+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 lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+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 shop.alien.storeplatform.service.NoticeService;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * web端商户通知管理服务实现类
+ *
+ * @author ssk
+ * @since 2025-01-xx
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class NoticeServiceImpl implements NoticeService {
+
+    private final LifeNoticeMapper lifeNoticeMapper;
+    
+    private final LifeMessageMapper lifeMessageMapper;
+    
+    private final LifeUserViolationMapper lifeUserViolationMapper;
+
+    @Override
+    public JSONObject getNoticeStatistics(String receiverId) {
+        log.info("NoticeServiceImpl.getNoticeStatistics - 查询通知统计: receiverId={}", receiverId);
+        
+        JSONObject jsonObject = new JSONObject();
+
+        // 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);
+
+        // 检查查询结果
+        if (lifeNoticeList == null || lifeNoticeList.isEmpty()) {
+            log.info("NoticeServiceImpl.getNoticeStatistics - 未查询到通知数据: receiverId={}", receiverId);
+            jsonObject.put("systemNotice", "null");
+            jsonObject.put("orderNotice", "null");
+            return jsonObject;
+        }
+
+        log.info("NoticeServiceImpl.getNoticeStatistics - 查询到通知总数: {}", lifeNoticeList.size());
+
+        // 2. 系统通知最新一条
+        LifeNotice systemNotice = lifeNoticeList.stream()
+                .filter(item -> 1 == item.getNoticeType())
+                .max(Comparator.comparing(LifeNotice::getCreatedTime))
+                .orElse(null);
+
+        // 3. 订单提醒最新一条
+        LifeNotice orderNotice = lifeNoticeList.stream()
+                .filter(item -> 2 == item.getNoticeType())
+                .max(Comparator.comparing(LifeNotice::getCreatedTime))
+                .orElse(null);
+
+        // 4. 系统通知数据
+        if (systemNotice != null) {
+            LifeNoticeVo noticeVo = new LifeNoticeVo();
+            BeanUtils.copyProperties(systemNotice, noticeVo);
+            // 统计系统通知未读数量
+            long systemUnreadCount = lifeNoticeList.stream()
+                    .filter(item -> 1 == item.getNoticeType() && item.getIsRead() == 0)
+                    .count();
+            noticeVo.setSystemNum(systemUnreadCount);
+            jsonObject.put("systemNotice", noticeVo);
+            log.info("NoticeServiceImpl.getNoticeStatistics - 系统通知未读数: {}", systemUnreadCount);
+        } else {
+            jsonObject.put("systemNotice", "null");
+            log.info("NoticeServiceImpl.getNoticeStatistics - 系统通知为空");
+        }
+
+        // 5. 订单提醒数据
+        if (orderNotice != null) {
+            LifeNoticeVo noticeVo = new LifeNoticeVo();
+            BeanUtils.copyProperties(orderNotice, noticeVo);
+            // 统计订单提醒未读数量
+            long orderUnreadCount = lifeNoticeList.stream()
+                    .filter(item -> 2 == item.getNoticeType() && item.getIsRead() == 0)
+                    .count();
+            noticeVo.setOrderNum(orderUnreadCount);
+            jsonObject.put("orderNotice", noticeVo);
+            log.info("NoticeServiceImpl.getNoticeStatistics - 订单提醒未读数: {}", orderUnreadCount);
+        } else {
+            jsonObject.put("orderNotice", "null");
+            log.info("NoticeServiceImpl.getNoticeStatistics - 订单提醒为空");
+        }
+
+        log.info("NoticeServiceImpl.getNoticeStatistics - 查询完成: receiverId={}", receiverId);
+        return jsonObject;
+    }
+
+    @Override
+    public IPage<LifeNoticeVo> getNoticeList(int pageNum, int pageSize, String receiverId, int noticeType) {
+        log.info("NoticeServiceImpl.getNoticeList - 查询通知列表: receiverId={}, noticeType={}, pageNum={}, pageSize={}", 
+                receiverId, noticeType, pageNum, pageSize);
+
+        // 1. 查询通知列表
+        LambdaQueryWrapper<LifeNotice> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(LifeNotice::getReceiverId, receiverId);
+        queryWrapper.eq(LifeNotice::getNoticeType, noticeType);
+        queryWrapper.eq(LifeNotice::getDeleteFlag, 0);
+        queryWrapper.orderByDesc(LifeNotice::getCreatedTime);
+        List<LifeNotice> lifeNoticeList = lifeNoticeMapper.selectList(queryWrapper);
+
+        // 2. 提取 senderId 列表,用于查询用户信息
+        Map<String, List<String>> senderIdMap = new HashMap<>();
+        List<Integer> businessIdList = new ArrayList<>();
+        
+        if (CollectionUtil.isNotEmpty(lifeNoticeList)) {
+            // 解析 senderId,分组为 store 和 user
+            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()
+                            )));
+            
+            // 提取 businessId 列表
+            businessIdList = lifeNoticeList.stream()
+                    .map(LifeNotice::getBusinessId)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toList());
+        }
+
+        // 3. 查询违规举报信息
+        List<LifeUserViolation> lifeUserViolationList = new ArrayList<>();
+        if (CollectionUtil.isNotEmpty(businessIdList)) {
+            lifeUserViolationList = lifeUserViolationMapper.selectBatchIds(businessIdList);
+        }
+        
+        Map<Integer, String> lifeUserViolationMap = new HashMap<>();
+        if (CollectionUtil.isNotEmpty(lifeUserViolationList)) {
+            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<LifeMessageVo> userList = lifeMessageMapper.getLifeUserAndStoreUserByPhone(storePhones, userPhones);
+
+        // 5. 组装 LifeNoticeVo 列表
+        List<LifeNoticeVo> noticeVoList = new ArrayList<>();
+        if (CollectionUtil.isNotEmpty(lifeNoticeList)) {
+            lifeNoticeList.forEach(item -> {
+                if (item == null) {
+                    return;
+                }
+                
+                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) {
+            if (lifeNoticeVo == null) {
+                continue;
+            }
+            
+            // businessId 为空时,设置为默认平台
+            if (lifeNoticeVo.getBusinessId() == null) {
+                lifeNoticeVo.setPlatformType("1");
+            }
+            
+            // 店铺审核通知设置为默认平台
+            if (StringUtils.isNotBlank(lifeNoticeVo.getTitle()) && "店铺审核通知".equals(lifeNoticeVo.getTitle())) {
+                lifeNoticeVo.setPlatformType("1");
+            }
+            
+            // 根据举报类型判断平台类型
+            if (lifeNoticeVo.getBusinessId() != null && lifeUserViolationMap.containsKey(lifeNoticeVo.getBusinessId())) {
+                String reportContextType = lifeUserViolationMap.get(lifeNoticeVo.getBusinessId());
+                if (StringUtils.isNotBlank(reportContextType) && "1,2,3".contains(reportContextType)) {
+                    lifeNoticeVo.setPlatformType("1");
+                }
+            }
+        }
+
+        // 7. 手动分页
+        List<LifeNoticeVo> pageList = noticeVoList.stream()
+                .skip((long) (pageNum - 1) * pageSize)
+                .limit(pageSize)
+                .collect(Collectors.toList());
+
+        // 8. 构建分页结果
+        IPage<LifeNoticeVo> 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);
+
+        log.info("NoticeServiceImpl.getNoticeList - 查询完成: 总数={}, 当前页数据量={}", 
+                noticeVoList.size(), pageList.size());
+        return result;
+    }
+
+    @Override
+    public int markNoticeAsRead(Integer id) {
+        log.info("NoticeServiceImpl.markNoticeAsRead - 标记通知为已读: id={}", id);
+        
+        // 使用 LambdaUpdateWrapper 更新 isRead 字段
+        LambdaUpdateWrapper<LifeNotice> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(LifeNotice::getId, id);
+        wrapper.set(LifeNotice::getIsRead, 1);
+        
+        int result = lifeNoticeMapper.update(null, wrapper);
+        
+        log.info("NoticeServiceImpl.markNoticeAsRead - 标记完成: id={}, 影响行数={}", id, result);
+        return result;
+    }
+
+    @Override
+    public int markAllNoticesAsRead(String receiverId, Integer noticeType) {
+        log.info("NoticeServiceImpl.markAllNoticesAsRead - 批量标记通知为已读: receiverId={}, noticeType={}", 
+                receiverId, noticeType);
+        
+        // 使用 LambdaUpdateWrapper 批量更新 isRead 字段
+        LambdaUpdateWrapper<LifeNotice> 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("NoticeServiceImpl.markAllNoticesAsRead - 批量标记完成: receiverId={}, noticeType={}, 影响行数={}", 
+                receiverId, noticeType, result);
+        return result;
+    }
+}
+

+ 743 - 0
alien-store-platform/接口文档/06-通知列表查询接口.md

@@ -0,0 +1,743 @@
+# 商户通知管理接口文档
+
+## 模块概述
+
+商户通知管理模块提供通知统计、通知列表查询、通知已读标记等功能,支持系统通知、订单提醒等多种通知类型。
+
+---
+
+## 接口列表
+
+1. [获取系统通知和订单提醒统计](#接口一获取系统通知和订单提醒统计)
+2. [获取通知列表](#接口二获取通知列表)
+3. [标记通知为已读](#接口三标记通知为已读)
+
+---
+
+## 接口一:获取系统通知和订单提醒统计
+
+### 接口信息
+
+- **接口名称**:获取系统通知和订单提醒统计
+- **接口路径**:`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": "查询成功"
+}
+```
+
+#### 无通知数据时
+
+```json
+{
+    "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-订单提醒) |
+
+#### 请求示例
+
+```http
+GET /notice/getNoticeList?receiverId=store_18241052019&pageNum=1&pageSize=10&noticeType=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 | 总页数 |
+
+---
+
+## 接口三:标记通知为已读
+
+### 接口信息
+
+- **接口名称**:标记通知为已读
+- **接口路径**:`POST /notice/markNoticeAsRead`
+- **接口描述**:将指定ID的通知标记为已读状态
+
+### 请求参数
+
+#### Query 参数
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| id | Integer | 是 | 通知ID |
+
+#### 请求示例
+
+```http
+POST /notice/markNoticeAsRead?id=2589
+```
+
+### 响应参数
+
+#### 成功响应
+
+```json
+{
+    "code": 200,
+    "success": true,
+    "data": null,
+    "msg": "标记已读成功"
+}
+```
+
+#### 失败响应
+
+```json
+{
+    "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`)
+
+系统会自动查询并关联发送人的 `userName` 和 `userImage`。
+
+---
+
+## 技术实现
+
+### Controller 层
+
+**文件路径**:`alien-store-platform/src/main/java/shop/alien/storeplatform/controller/NoticeController.java`
+
+```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`
+
+```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 - 通知统计**
+
+```java
+@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 - 通知列表**
+
+```java
+@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 - 标记已读**
+
+```java
+@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 实现类
+
+```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.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不存在
+
+### 错误响应示例
+
+```json
+{
+    "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
+
+```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 实现层:实现复杂的通知列表查询逻辑
+- ✅ 依赖注入:添加 `LifeMessageMapper` 和 `LifeUserViolationMapper`
+- ✅ 用户信息关联:实现商户和普通用户信息查询
+- ✅ 平台类型判断:实现复杂的平台类型标识逻辑
+- ✅ 手动分页:实现内存分页功能
+- ✅ 业务逻辑:完全复用原接口逻辑
+
+**接口三(标记已读)**:
+- ✅ 完成接口迁移:从 alien-store 迁移到 alien-store-platform
+- ✅ Controller 层:添加 `markNoticeAsRead` 接口
+- ✅ Service 层:添加 `markNoticeAsRead` 方法定义
+- ✅ Service 实现层:使用 LambdaUpdateWrapper 实现更新逻辑
+- ✅ 请求方式:改为 POST,更符合 REST 规范
+- ✅ 命名优化:`readNoticeById` → `markNoticeAsRead`(更语义化)
+- ✅ 业务逻辑:完全复用原接口逻辑
+- ✅ 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 缓存未读数量,减少数据库压力

+ 641 - 0
alien-store-platform/接口文档/07-标记通知已读接口.md

@@ -0,0 +1,641 @@
+# 商户通知管理接口文档
+
+## 模块概述
+
+商户通知管理模块提供通知统计、通知列表查询、通知已读标记(单个/批量)等功能,支持系统通知、订单提醒等多种通知类型。
+
+---
+
+## 接口列表
+
+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&noticeType=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&noticeType=1
+```
+
+#### 示例3:只标记订单提醒
+
+```http
+POST /notice/markAllNoticesAsRead?receiverId=store_18241052019&noticeType=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<Integer> 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<LifeNotice> 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. **标记统计**:
+   - 返回更详细的统计信息
+   - 如:按类型分别显示标记数量

+ 58 - 0
alien-store-platform/接口文档/README.md

@@ -0,0 +1,58 @@
+# 接口文档目录
+
+本目录包含alien-store-platform项目的所有接口文档(中文版)。
+
+## 文档列表
+
+1. [01-优惠券核销管理接口.md](./01-优惠券核销管理接口.md) - Web端商户优惠券核销接口文档
+2. [02-优惠券核销前效验接口.md](./02-优惠券核销前效验接口.md) - Web端商户优惠券核销前效验接口文档
+3. [03-账期查询接口.md](./03-账期查询接口.md) - Web端商户账期查询接口文档
+4. [04-根据手机号获取商户用户信息接口.md](./04-根据手机号获取商户用户信息接口.md) - 根据手机号获取商户用户信息接口文档
+5. [05-通知统计接口.md](./05-通知统计接口.md) - 商户通知统计接口文档
+6. [06-通知列表查询接口.md](./06-通知列表查询接口.md) - 商户通知列表查询接口文档
+7. [07-标记通知已读接口.md](./07-标记通知已读接口.md) - 商户标记通知已读接口文档  
+8. [08-店铺入住申请接口.md](./08-店铺入住申请接口.md) - Web端商户店铺入住接口文档
+9. [09-获取店铺详细信息接口.md](./09-获取店铺详细信息接口.md) - 获取店铺详细信息接口文档
+10. [10-店铺草稿接口.md](./10-店铺草稿接口.md) - 店铺入住申请草稿接口文档
+11. [11-获取今日收益接口.md](./11-获取今日收益接口.md) - 获取店铺今日收益接口文档
+12. [12-获取今日订单数接口.md](./12-获取今日订单数接口.md) - 店铺今日订单数接口文档
+13. [13-基础数据查询接口.md](./13-基础数据查询接口.md) - Web端基础数据查询接口文档
+14. [14-商户身份验证接口.md](./14-商户身份验证接口.md) - 商户身份验证接口配置说明
+15. [15-登录验证实现文档.md](./15-登录验证实现文档.md) - Web端商户平台登录验证实现文档(AOP切面版)
+16. [16-WebSocket集成说明.md](./16-WebSocket集成说明.md) - WebSocket集成说明
+17. [17-身份验证接口逻辑变更说明.md](./17-身份验证接口逻辑变更说明.md) - 商户身份验证接口逻辑变更说明
+18. [18-身份验证接口开发总结.md](./18-身份验证接口开发总结.md) - Web端商户身份验证接口开发总结
+
+## 原始文档映射
+
+| 中文文件名 | 英文原文件名 |
+|-----------|-------------|
+| 01-优惠券核销管理接口.md | API_COUPON_MANAGEMENT.md |
+| 02-优惠券核销前效验接口.md | API_COUPON_VERIFY.md |
+| 03-账期查询接口.md | API_INCOME_PAYMENT_CYCLE.md |
+| 04-根据手机号获取商户用户信息接口.md | API_MERCHANT_USER.md |
+| 05-通知统计接口.md | API_NOTICE_MANAGEMENT.md |
+| 06-通知列表查询接口.md | API_NOTICE_MANAGEMENT_DETALIS.md |
+| 07-标记通知已读接口.md | API_NOTICE_MANAGEMENT_ALLREAD.md |
+| 08-店铺入住申请接口.md | API_STORE_APPLY.md |
+| 09-获取店铺详细信息接口.md | API_STORE_DETAIL.md |
+| 10-店铺草稿接口.md | API_STORE_DRAFT.md |
+| 11-获取今日收益接口.md | API_TODAY_INCOME.md |
+| 12-获取今日订单数接口.md | API_TODAY_ORDER_COUNT.md |
+| 13-基础数据查询接口.md | README_BASE_DATA.md |
+| 14-商户身份验证接口.md | README_MERCHANT_AUTH.md |
+| 15-登录验证实现文档.md | USER_AUTHENTICATION_GUIDE_AOP.md |
+| 16-WebSocket集成说明.md | WEBSOCKET_INTEGRATION.md |
+| 17-身份验证接口逻辑变更说明.md | CHANGELOG_MERCHANT_AUTH.md |
+| 18-身份验证接口开发总结.md | DEVELOPMENT_SUMMARY.md |
+
+## 说明
+
+- 所有接口文档已从英文文件名转换为中文文件名,便于阅读和管理
+- 文档内容保持不变,仅文件名进行了中文化处理
+- 文档按功能模块进行编号,方便查找和引用
+
+**最后更新**: 2025-11-14  
+**维护人员**: ssk
+
+