# 商户通知管理接口文档

## 模块概述

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

---

## 接口列表

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. **标记统计**：
   - 返回更详细的统计信息
   - 如：按类型分别显示标记数量
