Selaa lähdekoodia

Merge remote-tracking branch 'origin/store-plantform' into store-plantform

zjy 1 kuukausi sitten
vanhempi
commit
d8b4218e31

+ 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 缓存未读数量,减少数据库压力

+ 815 - 0
alien-store-platform/doc/LOGIN_USER_UTIL_GUIDE.md

@@ -0,0 +1,815 @@
+# LoginUserUtil 工具类使用文档
+
+## 概述
+
+`LoginUserUtil` 是一个用于在web端商户平台(`alien-store-platform`)中获取当前登录用户信息的工具类。它封装了从JWT Token中提取用户信息以及从数据库查询完整用户信息的常用方法。
+
+---
+
+## 核心特性
+
+- ✅ **静态方法**: 所有方法都是静态方法,可直接调用,无需实例化
+- ✅ **自动注入**: 使用`@Autowired`自动注入`StoreUserMapper`
+- ✅ **异常处理**: 所有方法都包含完善的异常处理,确保不会抛出异常
+- ✅ **日志记录**: 详细的日志记录,便于调试和问题排查
+- ✅ **多种获取方式**: 支持从Token获取基本信息,或从数据库查询完整信息
+
+---
+
+## 类结构
+
+```java
+@Slf4j
+@Component
+public class LoginUserUtil {
+    // 核心方法
+    public static Integer getCurrentUserId()           // 获取用户ID
+    public static String getCurrentUserPhone()         // 获取手机号
+    public static String getCurrentUserType()          // 获取用户类型
+    public static JSONObject getCurrentUserInfo()      // 获取Token中的用户信息
+    public static StoreUser getCurrentStoreUser()      // 获取完整用户信息(数据库)
+    public static StoreUser getCurrentStoreUserByPhone() // 根据手机号获取用户信息
+    
+    // 辅助方法
+    public static boolean isCurrentUser(Integer userId)  // 验证是否为当前用户
+    public static boolean isLogin()                      // 验证是否已登录
+    public static boolean isStorePlatformUser()          // 验证是否为商户平台用户
+    
+    // 便捷方法
+    public static Integer getCurrentStoreId()            // 获取门店ID
+    public static String getCurrentNickName()            // 获取昵称
+    public static String getCurrentName()                // 获取真实姓名
+}
+```
+
+---
+
+## 方法详解
+
+### 1. getCurrentUserId()
+
+获取当前登录用户的ID(从Token中获取,无需查询数据库)
+
+```java
+/**
+ * 获取当前登录用户的ID
+ * @return 用户ID,如果未登录返回null
+ */
+public static Integer getCurrentUserId()
+```
+
+**使用示例**:
+```java
+Integer userId = LoginUserUtil.getCurrentUserId();
+if (userId != null) {
+    System.out.println("当前用户ID: " + userId);
+} else {
+    System.out.println("用户未登录");
+}
+```
+
+**返回值**:
+- 成功: 返回用户ID(Integer)
+- 未登录: 返回`null`
+
+**性能**: ⚡ 快速(只解析Token,不查询数据库)
+
+---
+
+### 2. getCurrentUserPhone()
+
+获取当前登录用户的手机号(从Token中获取)
+
+```java
+/**
+ * 获取当前登录用户的手机号
+ * @return 手机号,如果未登录返回null
+ */
+public static String getCurrentUserPhone()
+```
+
+**使用示例**:
+```java
+String phone = LoginUserUtil.getCurrentUserPhone();
+if (phone != null) {
+    System.out.println("当前用户手机号: " + phone);
+}
+```
+
+**返回值**:
+- 成功: 返回手机号(String)
+- 未登录: 返回`null`
+
+**性能**: ⚡ 快速(只解析Token)
+
+---
+
+### 3. getCurrentUserType()
+
+获取当前登录用户的用户类型(从Token中获取)
+
+```java
+/**
+ * 获取当前登录用户的用户类型
+ * @return 用户类型,如果未登录返回null
+ */
+public static String getCurrentUserType()
+```
+
+**使用示例**:
+```java
+String userType = LoginUserUtil.getCurrentUserType();
+if ("storePlatform".equals(userType)) {
+    System.out.println("商户平台用户");
+}
+```
+
+**返回值**:
+- 成功: 返回用户类型(String),如 `"storePlatform"`
+- 未登录: 返回`null`
+
+**性能**: ⚡ 快速(只解析Token)
+
+---
+
+### 4. getCurrentUserInfo()
+
+获取当前登录用户的Token信息(JSONObject格式)
+
+```java
+/**
+ * 获取当前登录用户的Token信息(JSONObject格式)
+ * @return 用户信息JSONObject,包含userId、phone、userType等字段
+ */
+public static JSONObject getCurrentUserInfo()
+```
+
+**使用示例**:
+```java
+JSONObject userInfo = LoginUserUtil.getCurrentUserInfo();
+if (userInfo != null) {
+    Integer userId = userInfo.getInteger("userId");
+    String phone = userInfo.getString("phone");
+    String userType = userInfo.getString("userType");
+    
+    System.out.println("用户ID: " + userId);
+    System.out.println("手机号: " + phone);
+    System.out.println("用户类型: " + userType);
+}
+```
+
+**返回值**:
+- 成功: 返回包含用户信息的`JSONObject`
+- 未登录: 返回`null`
+
+**包含字段**:
+- `userId`: 用户ID
+- `phone`: 手机号
+- `userType`: 用户类型
+
+**性能**: ⚡ 快速(只解析Token)
+
+---
+
+### 5. getCurrentStoreUser()
+
+获取当前登录用户的完整商户用户信息(从数据库查询)
+
+```java
+/**
+ * 获取当前登录用户的完整商户用户信息(从数据库查询)
+ * @return StoreUser对象,如果未登录或查询失败返回null
+ */
+public static StoreUser getCurrentStoreUser()
+```
+
+**使用示例**:
+```java
+StoreUser storeUser = LoginUserUtil.getCurrentStoreUser();
+if (storeUser != null) {
+    System.out.println("用户ID: " + storeUser.getId());
+    System.out.println("手机号: " + storeUser.getPhone());
+    System.out.println("昵称: " + storeUser.getNickName());
+    System.out.println("真实姓名: " + storeUser.getName());
+    System.out.println("门店ID: " + storeUser.getStoreId());
+    System.out.println("账户余额: " + storeUser.getMoney());
+}
+```
+
+**返回值**:
+- 成功: 返回`StoreUser`对象(包含所有字段)
+- 未登录: 返回`null`
+- 查询失败: 返回`null`
+
+**性能**: 🐢 较慢(需要查询数据库)
+
+**注意**: 
+- 适用于需要完整用户信息的场景
+- 会产生数据库查询,建议在需要时才调用
+
+---
+
+### 6. getCurrentStoreUserByPhone()
+
+根据手机号获取商户用户信息(从数据库查询)
+
+```java
+/**
+ * 根据手机号获取商户用户信息
+ * @return StoreUser对象,如果未登录或查询失败返回null
+ */
+public static StoreUser getCurrentStoreUserByPhone()
+```
+
+**使用示例**:
+```java
+StoreUser storeUser = LoginUserUtil.getCurrentStoreUserByPhone();
+if (storeUser != null) {
+    System.out.println("用户信息: " + storeUser);
+}
+```
+
+**返回值**:
+- 成功: 返回`StoreUser`对象
+- 未登录: 返回`null`
+- 查询失败: 返回`null`
+
+**性能**: 🐢 较慢(需要查询数据库)
+
+**与 getCurrentStoreUser() 的区别**:
+- `getCurrentStoreUser()`: 根据用户ID查询(主键查询,更快)
+- `getCurrentStoreUserByPhone()`: 根据手机号查询(索引查询,稍慢)
+
+---
+
+### 7. isCurrentUser(Integer userId)
+
+验证当前登录用户是否为指定用户
+
+```java
+/**
+ * 验证当前登录用户是否为指定用户
+ * @param userId 要验证的用户ID
+ * @return true-是当前用户, false-不是当前用户
+ */
+public static boolean isCurrentUser(Integer userId)
+```
+
+**使用示例**:
+```java
+Integer targetUserId = 12345;
+if (LoginUserUtil.isCurrentUser(targetUserId)) {
+    System.out.println("是当前用户,允许操作");
+} else {
+    System.out.println("不是当前用户,拒绝操作");
+}
+```
+
+**使用场景**:
+- 权限验证:确保用户只能修改自己的信息
+- 安全检查:防止用户操作其他用户的数据
+
+**返回值**:
+- `true`: 是当前登录用户
+- `false`: 不是当前登录用户或未登录
+
+---
+
+### 8. isLogin()
+
+验证当前用户是否已登录
+
+```java
+/**
+ * 验证当前用户是否已登录
+ * @return true-已登录, false-未登录
+ */
+public static boolean isLogin()
+```
+
+**使用示例**:
+```java
+if (LoginUserUtil.isLogin()) {
+    System.out.println("用户已登录");
+} else {
+    System.out.println("用户未登录");
+}
+```
+
+**返回值**:
+- `true`: 已登录
+- `false`: 未登录
+
+---
+
+### 9. isStorePlatformUser()
+
+验证当前用户类型是否为web端商户平台
+
+```java
+/**
+ * 验证当前用户类型是否为web端商户平台
+ * @return true-是商户平台用户, false-不是
+ */
+public static boolean isStorePlatformUser()
+```
+
+**使用示例**:
+```java
+if (LoginUserUtil.isStorePlatformUser()) {
+    System.out.println("商户平台用户,允许访问");
+} else {
+    System.out.println("非商户平台用户,拒绝访问");
+}
+```
+
+**返回值**:
+- `true`: 用户类型为 `storePlatform`
+- `false`: 用户类型不是 `storePlatform` 或未登录
+
+---
+
+### 10. getCurrentStoreId()
+
+获取当前登录用户的门店ID
+
+```java
+/**
+ * 获取当前登录用户的门店ID
+ * @return 门店ID,如果未登录或用户无门店返回null
+ */
+public static Integer getCurrentStoreId()
+```
+
+**使用示例**:
+```java
+Integer storeId = LoginUserUtil.getCurrentStoreId();
+if (storeId != null) {
+    System.out.println("当前用户的门店ID: " + storeId);
+}
+```
+
+**返回值**:
+- 成功: 返回门店ID(Integer)
+- 未登录: 返回`null`
+- 用户无门店: 返回`null`
+
+**性能**: 🐢 较慢(需要查询数据库)
+
+---
+
+### 11. getCurrentNickName()
+
+获取当前登录用户的昵称
+
+```java
+/**
+ * 获取当前登录用户的昵称
+ * @return 昵称,如果未登录返回null
+ */
+public static String getCurrentNickName()
+```
+
+**使用示例**:
+```java
+String nickName = LoginUserUtil.getCurrentNickName();
+if (nickName != null) {
+    System.out.println("欢迎," + nickName);
+}
+```
+
+**返回值**:
+- 成功: 返回昵称(String)
+- 未登录: 返回`null`
+
+**性能**: 🐢 较慢(需要查询数据库)
+
+---
+
+### 12. getCurrentName()
+
+获取当前登录用户的真实姓名
+
+```java
+/**
+ * 获取当前登录用户的真实姓名
+ * @return 真实姓名,如果未登录返回null
+ */
+public static String getCurrentName()
+```
+
+**使用示例**:
+```java
+String name = LoginUserUtil.getCurrentName();
+if (name != null) {
+    System.out.println("真实姓名: " + name);
+}
+```
+
+**返回值**:
+- 成功: 返回真实姓名(String)
+- 未登录: 返回`null`
+
+**性能**: 🐢 较慢(需要查询数据库)
+
+---
+
+## 实际应用场景
+
+### 场景1: 获取当前用户的基本信息
+
+```java
+@RestController
+@RequestMapping("/user")
+public class UserController {
+    
+    @GetMapping("/profile")
+    public R<StoreUser> getUserProfile() {
+        // 获取当前登录用户的完整信息
+        StoreUser storeUser = LoginUserUtil.getCurrentStoreUser();
+        if (storeUser == null) {
+            return R.fail("请先登录");
+        }
+        return R.data(storeUser);
+    }
+}
+```
+
+---
+
+### 场景2: 验证用户权限(只能操作自己的数据)
+
+```java
+@Service
+public class StoreServiceImpl implements StoreService {
+    
+    @Override
+    public boolean updateStore(Integer storeId, StoreInfo storeInfo) {
+        // 获取当前用户的门店ID
+        Integer currentStoreId = LoginUserUtil.getCurrentStoreId();
+        
+        // 验证用户只能修改自己的门店信息
+        if (!storeId.equals(currentStoreId)) {
+            throw new RuntimeException("您只能修改自己的门店信息");
+        }
+        
+        // 执行更新操作
+        return updateStoreInfo(storeInfo);
+    }
+}
+```
+
+---
+
+### 场景3: 自动填充当前用户信息
+
+```java
+@Service
+public class OrderServiceImpl implements OrderService {
+    
+    @Override
+    public boolean createOrder(Order order) {
+        // 自动填充当前用户信息
+        Integer userId = LoginUserUtil.getCurrentUserId();
+        Integer storeId = LoginUserUtil.getCurrentStoreId();
+        
+        order.setUserId(userId);
+        order.setStoreId(storeId);
+        order.setCreateTime(new Date());
+        
+        return orderMapper.insert(order) > 0;
+    }
+}
+```
+
+---
+
+### 场景4: 根据用户类型返回不同数据
+
+```java
+@Service
+public class DashboardServiceImpl implements DashboardService {
+    
+    @Override
+    public JSONObject getDashboardData() {
+        JSONObject result = new JSONObject();
+        
+        // 验证用户类型
+        if (!LoginUserUtil.isStorePlatformUser()) {
+            throw new RuntimeException("只有商户平台用户可以访问");
+        }
+        
+        // 获取用户信息
+        Integer storeId = LoginUserUtil.getCurrentStoreId();
+        
+        // 查询仪表盘数据
+        result.put("todayIncome", getTodayIncome(storeId));
+        result.put("todayOrderCount", getTodayOrderCount(storeId));
+        
+        return result;
+    }
+}
+```
+
+---
+
+### 场景5: 日志记录(记录操作人)
+
+```java
+@Service
+public class AuditLogServiceImpl implements AuditLogService {
+    
+    @Override
+    public void logOperation(String action, String details) {
+        AuditLog log = new AuditLog();
+        
+        // 记录操作人信息
+        log.setUserId(LoginUserUtil.getCurrentUserId());
+        log.setUserPhone(LoginUserUtil.getCurrentUserPhone());
+        log.setUserName(LoginUserUtil.getCurrentName());
+        log.setAction(action);
+        log.setDetails(details);
+        log.setOperateTime(new Date());
+        
+        auditLogMapper.insert(log);
+    }
+}
+```
+
+---
+
+### 场景6: 条件查询(只查询当前用户的数据)
+
+```java
+@Service
+public class OrderServiceImpl implements OrderService {
+    
+    @Override
+    public List<Order> getMyOrders(Integer page, Integer size) {
+        // 获取当前用户ID
+        Integer userId = LoginUserUtil.getCurrentUserId();
+        if (userId == null) {
+            throw new RuntimeException("请先登录");
+        }
+        
+        // 查询当前用户的订单
+        LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(Order::getUserId, userId)
+               .orderByDesc(Order::getCreateTime);
+        
+        return orderMapper.selectPage(new Page<>(page, size), wrapper).getRecords();
+    }
+}
+```
+
+---
+
+### 场景7: 简化Controller代码
+
+**之前的写法**:
+```java
+@PostMapping("/updateProfile")
+public R<Boolean> updateProfile(@RequestBody StoreUser storeUser) {
+    // 需要手动获取用户信息
+    JSONObject userInfo = JwtUtil.getCurrentUserInfo();
+    if (userInfo == null) {
+        return R.fail("请先登录");
+    }
+    Integer userId = userInfo.getInteger("userId");
+    storeUser.setId(userId);
+    
+    boolean success = storeUserService.updateById(storeUser);
+    return success ? R.success("修改成功") : R.fail("修改失败");
+}
+```
+
+**使用工具类后**:
+```java
+@PostMapping("/updateProfile")
+public R<Boolean> updateProfile(@RequestBody StoreUser storeUser) {
+    // 直接获取用户ID
+    Integer userId = LoginUserUtil.getCurrentUserId();
+    if (userId == null) {
+        return R.fail("请先登录");
+    }
+    storeUser.setId(userId);
+    
+    boolean success = storeUserService.updateById(storeUser);
+    return success ? R.success("修改成功") : R.fail("修改失败");
+}
+```
+
+---
+
+## 性能对比
+
+| 方法 | 性能 | 是否查询数据库 | 适用场景 |
+|------|------|---------------|----------|
+| `getCurrentUserId()` | ⚡⚡⚡ 极快 | ❌ 否 | 只需要用户ID |
+| `getCurrentUserPhone()` | ⚡⚡⚡ 极快 | ❌ 否 | 只需要手机号 |
+| `getCurrentUserType()` | ⚡⚡⚡ 极快 | ❌ 否 | 验证用户类型 |
+| `getCurrentUserInfo()` | ⚡⚡⚡ 极快 | ❌ 否 | 需要Token中的多个字段 |
+| `getCurrentStoreUser()` | 🐢 较慢 | ✅ 是 | 需要完整用户信息 |
+| `getCurrentStoreUserByPhone()` | 🐢 较慢 | ✅ 是 | 根据手机号查询 |
+| `getCurrentStoreId()` | 🐢 较慢 | ✅ 是 | 只需要门店ID |
+| `getCurrentNickName()` | 🐢 较慢 | ✅ 是 | 只需要昵称 |
+| `getCurrentName()` | 🐢 较慢 | ✅ 是 | 只需要真实姓名 |
+
+**优化建议**:
+- ✅ 优先使用不查询数据库的方法(`getCurrentUserId()`、`getCurrentUserPhone()`等)
+- ✅ 如果需要多个用户字段,使用`getCurrentStoreUser()`一次性获取,避免多次查询
+- ✅ 不要在循环中调用需要查询数据库的方法
+
+---
+
+## 注意事项
+
+### 1. 空值处理
+
+所有方法在获取失败时都返回`null`,使用时需要判空:
+
+```java
+Integer userId = LoginUserUtil.getCurrentUserId();
+if (userId != null) {
+    // 处理业务逻辑
+} else {
+    // 处理未登录情况
+}
+```
+
+### 2. 异常处理
+
+工具类内部已处理所有异常,不会向外抛出异常,确保调用安全。
+
+### 3. 静态方法限制
+
+由于使用静态方法,需要通过`@Autowired`注入`StoreUserMapper`到静态字段。这是通过`@Component`和setter方法实现的。
+
+### 4. 性能考虑
+
+- **频繁调用**: 如果需要频繁获取用户信息,建议在Service层缓存
+- **数据库查询**: 避免在循环中调用需要查询数据库的方法
+
+### 5. 线程安全
+
+工具类是线程安全的,可以在多线程环境下使用。
+
+---
+
+## 与 @LoginRequired 注解配合使用
+
+`LoginUserUtil` 与 `@LoginRequired` 注解配合使用效果更佳:
+
+```java
+@RestController
+@RequestMapping("/store")
+public class StoreController {
+    
+    @LoginRequired  // AOP切面验证登录
+    @GetMapping("/myStore")
+    public R<StoreInfo> getMyStore() {
+        // 登录验证已通过,直接获取门店ID
+        Integer storeId = LoginUserUtil.getCurrentStoreId();
+        StoreInfo storeInfo = storeService.getById(storeId);
+        return R.data(storeInfo);
+    }
+}
+```
+
+---
+
+## 配置要求
+
+### 1. Spring Bean注册
+
+工具类使用`@Component`注解,需要被Spring扫描到:
+
+```java
+@Component  // 必须
+public class LoginUserUtil {
+    // ...
+}
+```
+
+### 2. 包扫描配置
+
+确保`LoginUserUtil`所在的包被Spring Boot扫描:
+
+```java
+@SpringBootApplication
+@ComponentScan(basePackages = {"shop.alien.storeplatform"})
+public class Application {
+    // ...
+}
+```
+
+### 3. Mapper依赖
+
+确保`StoreUserMapper`已注册为Spring Bean:
+
+```java
+@Mapper
+public interface StoreUserMapper extends BaseMapper<StoreUser> {
+}
+```
+
+---
+
+## 常见问题
+
+### Q1: 为什么有时返回null?
+
+**原因**:
+1. 用户未登录(Token不存在或无效)
+2. Token已过期
+3. 用户在数据库中不存在
+4. 网络或数据库异常
+
+**解决方案**:
+- 使用前先判断是否已登录:`LoginUserUtil.isLogin()`
+- 检查日志中的错误信息
+
+---
+
+### Q2: 如何在非Controller层使用?
+
+**答案**: 工具类可以在任何Spring管理的Bean中使用:
+
+```java
+@Service
+public class MyService {
+    
+    public void someMethod() {
+        Integer userId = LoginUserUtil.getCurrentUserId();
+        // 使用userId
+    }
+}
+```
+
+---
+
+### Q3: 性能是否有问题?
+
+**答案**: 
+- Token解析方法(如`getCurrentUserId()`)性能很高,可以频繁调用
+- 数据库查询方法(如`getCurrentStoreUser()`)性能较低,建议缓存结果
+
+---
+
+### Q4: 如何缓存用户信息?
+
+**建议方案**:
+
+```java
+@Service
+public class MyService {
+    
+    private StoreUser currentUser;
+    
+    public void processOrder() {
+        // 第一次获取
+        if (currentUser == null) {
+            currentUser = LoginUserUtil.getCurrentStoreUser();
+        }
+        
+        // 后续使用缓存的数据
+        Integer storeId = currentUser.getStoreId();
+        // ...
+    }
+}
+```
+
+或使用ThreadLocal缓存(适用于同一次请求)。
+
+---
+
+## 更新日志
+
+### 2025-11-17
+
+**新增工具类**:
+- ✅ 创建`LoginUserUtil`工具类
+
+**核心功能**:
+- ✅ 获取当前登录用户ID、手机号、用户类型
+- ✅ 获取Token中的用户信息(JSONObject)
+- ✅ 从数据库查询完整用户信息
+- ✅ 验证用户登录状态
+- ✅ 验证用户类型
+- ✅ 验证是否为当前用户
+- ✅ 获取门店ID、昵称、姓名等便捷方法
+
+**技术特点**:
+- ✅ 静态方法,方便调用
+- ✅ 完善的异常处理
+- ✅ 详细的日志记录
+- ✅ 支持多种获取方式
+
+**开发人员**: ssk
+
+---
+
+**文档版本**: v1.0  
+**最后更新**: 2025-11-17  
+**维护人员**: ssk
+

+ 127 - 0
alien-store-platform/doc/LOGIN_USER_UTIL_README.md

@@ -0,0 +1,127 @@
+# LoginUserUtil 工具类 - 快速开始
+
+## 简介
+
+`LoginUserUtil` 是一个用于在web端商户平台中快速获取当前登录用户信息的工具类。所有方法都是静态方法,可以直接调用。
+
+---
+
+## 快速使用
+
+### 1. 获取用户ID(最常用)
+
+```java
+Integer userId = LoginUserUtil.getCurrentUserId();
+if (userId != null) {
+    // 用户已登录,使用userId
+} else {
+    // 用户未登录
+}
+```
+
+### 2. 获取完整用户信息
+
+```java
+StoreUser storeUser = LoginUserUtil.getCurrentStoreUser();
+if (storeUser != null) {
+    Integer userId = storeUser.getId();
+    String phone = storeUser.getPhone();
+    Integer storeId = storeUser.getStoreId();
+    // ... 使用其他字段
+}
+```
+
+### 3. 验证用户登录
+
+```java
+if (LoginUserUtil.isLogin()) {
+    // 已登录
+} else {
+    // 未登录
+}
+```
+
+### 4. 权限验证
+
+```java
+Integer targetUserId = 12345;
+if (LoginUserUtil.isCurrentUser(targetUserId)) {
+    // 是当前用户,允许操作
+} else {
+    // 不是当前用户,拒绝操作
+}
+```
+
+---
+
+## 核心方法速查
+
+| 方法 | 返回值 | 说明 | 是否查数据库 |
+|------|--------|------|-------------|
+| `getCurrentUserId()` | Integer | 用户ID | ❌ |
+| `getCurrentUserPhone()` | String | 手机号 | ❌ |
+| `getCurrentUserType()` | String | 用户类型 | ❌ |
+| `getCurrentUserInfo()` | JSONObject | Token中的用户信息 | ❌ |
+| `getCurrentStoreUser()` | StoreUser | 完整用户信息 | ✅ |
+| `getCurrentStoreId()` | Integer | 门店ID | ✅ |
+| `isLogin()` | boolean | 是否已登录 | ❌ |
+| `isCurrentUser(userId)` | boolean | 是否为当前用户 | ❌ |
+
+---
+
+## 实际应用示例
+
+### Service层自动填充
+
+```java
+@Service
+public class OrderServiceImpl {
+    
+    public void createOrder(Order order) {
+        // 自动填充当前用户信息
+        order.setUserId(LoginUserUtil.getCurrentUserId());
+        order.setStoreId(LoginUserUtil.getCurrentStoreId());
+        orderMapper.insert(order);
+    }
+}
+```
+
+### 权限控制
+
+```java
+@Service
+public class StoreServiceImpl {
+    
+    public void updateStore(Integer storeId, StoreInfo info) {
+        Integer currentStoreId = LoginUserUtil.getCurrentStoreId();
+        if (!storeId.equals(currentStoreId)) {
+            throw new RuntimeException("只能修改自己的门店");
+        }
+        // 执行更新
+    }
+}
+```
+
+---
+
+## 注意事项
+
+1. ⚠️ 所有方法失败时返回`null`,使用前需判空
+2. ⚠️ 带数据库查询的方法(如`getCurrentStoreUser()`)性能较慢,避免频繁调用
+3. ⚠️ 如需多个字段,使用`getCurrentStoreUser()`一次性获取,避免多次查询
+4. ⚠️ 需要用户已登录(有有效Token)才能正常工作
+
+---
+
+## 详细文档
+
+请查看完整文档:[LOGIN_USER_UTIL_GUIDE.md](LOGIN_USER_UTIL_GUIDE.md)
+
+使用示例代码:[LoginUserUtilExample.java](../src/main/java/shop/alien/storeplatform/example/LoginUserUtilExample.java)
+
+---
+
+**版本**: v1.0  
+**日期**: 2025-11-17  
+**作者**: ssk
+

+ 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());
+        }
+    }
+}
+

+ 11 - 3
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StorePlatformRenovationController.java

@@ -24,11 +24,19 @@ public class StorePlatformRenovationController {
     private final StorePlatformRenovationService storePlatformRenovationService;
 
     @ApiOperation(value = "web端查询经营板块信息")
-    @ApiOperationSupport(order = 1)
+    @ApiOperationSupport(order = 6)
     @GetMapping("/getBusinessSection")
     public R<List<StoreDictionaryVo>> getBusinessSection() {
-        log.info("StorePlatformRenovationController.getBusinessSection");
-        return R.data(new ArrayList<>());
+        log.info("StoreInfoController.getBusinessSection");
+        return R.data(storePlatformRenovationService.getBusinessSection());
+    }
+
+    @ApiOperation(value = "web端查询经营板块的经营种类信息")
+    @ApiOperationSupport(order = 6)
+    @GetMapping("/getBusinessSectionTypes")
+    public R<List<StoreDictionaryVo>> getBusinessSectionTypes(@RequestParam("parentId") String parentId) {
+        log.info("StoreInfoController.getBusinessSectionTypes?parentId={}", parentId);
+        return R.data(storePlatformRenovationService.getBusinessSectionTypes(parentId));
     }
 
     @ApiOperation(value = "门店装修-详细信息")

+ 254 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/example/LoginUserUtilExample.java

@@ -0,0 +1,254 @@
+package shop.alien.storeplatform.example;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import shop.alien.entity.store.StoreUser;
+import shop.alien.storeplatform.util.LoginUserUtil;
+
+/**
+ * LoginUserUtil 工具类使用示例
+ * 
+ * 本类展示了如何在实际项目中使用 LoginUserUtil 工具类
+ * 注意:这只是一个示例类,实际使用时可以直接在Service或Controller中调用
+ *
+ * @author ssk
+ * @since 2025-11-17
+ */
+@Slf4j
+public class LoginUserUtilExample {
+
+    /**
+     * 示例1: 获取当前用户的基本信息(从Token)
+     */
+    public void example1_GetBasicInfo() {
+        log.info("========== 示例1: 获取基本信息 ==========");
+        
+        // 获取用户ID
+        Integer userId = LoginUserUtil.getCurrentUserId();
+        log.info("用户ID: {}", userId);
+        
+        // 获取手机号
+        String phone = LoginUserUtil.getCurrentUserPhone();
+        log.info("手机号: {}", phone);
+        
+        // 获取用户类型
+        String userType = LoginUserUtil.getCurrentUserType();
+        log.info("用户类型: {}", userType);
+        
+        // 获取完整Token信息
+        JSONObject userInfo = LoginUserUtil.getCurrentUserInfo();
+        log.info("完整用户信息: {}", userInfo);
+    }
+
+    /**
+     * 示例2: 获取完整用户信息(从数据库)
+     */
+    public void example2_GetCompleteInfo() {
+        log.info("========== 示例2: 获取完整用户信息 ==========");
+        
+        StoreUser storeUser = LoginUserUtil.getCurrentStoreUser();
+        if (storeUser != null) {
+            log.info("用户ID: {}", storeUser.getId());
+            log.info("手机号: {}", storeUser.getPhone());
+            log.info("昵称: {}", storeUser.getNickName());
+            log.info("真实姓名: {}", storeUser.getName());
+            log.info("身份证号: {}", storeUser.getIdCard());
+            log.info("门店ID: {}", storeUser.getStoreId());
+            log.info("账户余额: {}", storeUser.getMoney());
+            log.info("头像: {}", storeUser.getHeadImg());
+            log.info("账号简介: {}", storeUser.getAccountBlurb());
+        } else {
+            log.warn("用户未登录或查询失败");
+        }
+    }
+
+    /**
+     * 示例3: 验证用户登录状态
+     */
+    public void example3_CheckLoginStatus() {
+        log.info("========== 示例3: 验证登录状态 ==========");
+        
+        // 检查是否登录
+        if (LoginUserUtil.isLogin()) {
+            log.info("用户已登录");
+        } else {
+            log.info("用户未登录");
+        }
+        
+        // 检查是否为商户平台用户
+        if (LoginUserUtil.isStorePlatformUser()) {
+            log.info("是商户平台用户");
+        } else {
+            log.info("不是商户平台用户");
+        }
+    }
+
+    /**
+     * 示例4: 验证是否为当前用户(权限控制)
+     */
+    public void example4_CheckCurrentUser() {
+        log.info("========== 示例4: 验证是否为当前用户 ==========");
+        
+        Integer targetUserId = 12345; // 要操作的目标用户ID
+        
+        if (LoginUserUtil.isCurrentUser(targetUserId)) {
+            log.info("是当前用户,允许操作");
+            // 执行业务逻辑
+        } else {
+            log.warn("不是当前用户,拒绝操作");
+            // 抛出异常或返回错误
+        }
+    }
+
+    /**
+     * 示例5: 获取便捷字段
+     */
+    public void example5_GetConvenientFields() {
+        log.info("========== 示例5: 获取便捷字段 ==========");
+        
+        // 获取门店ID
+        Integer storeId = LoginUserUtil.getCurrentStoreId();
+        log.info("门店ID: {}", storeId);
+        
+        // 获取昵称
+        String nickName = LoginUserUtil.getCurrentNickName();
+        log.info("昵称: {}", nickName);
+        
+        // 获取真实姓名
+        String name = LoginUserUtil.getCurrentName();
+        log.info("真实姓名: {}", name);
+    }
+
+    /**
+     * 示例6: 在Service中使用(自动填充用户信息)
+     */
+    public void example6_UseInService() {
+        log.info("========== 示例6: 在Service中使用 ==========");
+        
+        // 场景:创建订单时自动填充当前用户信息
+        Integer userId = LoginUserUtil.getCurrentUserId();
+        Integer storeId = LoginUserUtil.getCurrentStoreId();
+        
+        if (userId == null || storeId == null) {
+            log.error("用户未登录或无门店信息");
+            return;
+        }
+        
+        // 模拟创建订单
+        log.info("创建订单 - 用户ID: {}, 门店ID: {}", userId, storeId);
+        // Order order = new Order();
+        // order.setUserId(userId);
+        // order.setStoreId(storeId);
+        // orderService.createOrder(order);
+    }
+
+    /**
+     * 示例7: 安全的空值处理
+     */
+    public void example7_SafeNullHandling() {
+        log.info("========== 示例7: 安全的空值处理 ==========");
+        
+        // 方式1: 直接判空
+        Integer userId = LoginUserUtil.getCurrentUserId();
+        if (userId != null) {
+            log.info("用户ID: {}", userId);
+        } else {
+            log.warn("用户未登录");
+        }
+        
+        // 方式2: 使用 isLogin() 先判断
+        if (LoginUserUtil.isLogin()) {
+            Integer id = LoginUserUtil.getCurrentUserId();
+            log.info("已登录,用户ID: {}", id);
+        } else {
+            log.warn("用户未登录");
+        }
+    }
+
+    /**
+     * 示例8: 权限验证(只能修改自己的数据)
+     */
+    public boolean example8_PermissionCheck(Integer targetUserId) {
+        log.info("========== 示例8: 权限验证 ==========");
+        
+        // 验证用户只能修改自己的数据
+        if (!LoginUserUtil.isCurrentUser(targetUserId)) {
+            log.error("权限不足:用户 {} 尝试修改用户 {} 的数据", 
+                    LoginUserUtil.getCurrentUserId(), targetUserId);
+            throw new RuntimeException("您只能修改自己的信息");
+        }
+        
+        log.info("权限验证通过");
+        return true;
+    }
+
+    /**
+     * 示例9: 记录操作日志
+     */
+    public void example9_LogOperation(String action, String details) {
+        log.info("========== 示例9: 记录操作日志 ==========");
+        
+        // 获取当前用户信息用于日志记录
+        Integer userId = LoginUserUtil.getCurrentUserId();
+        String phone = LoginUserUtil.getCurrentUserPhone();
+        String name = LoginUserUtil.getCurrentName();
+        
+        // 记录操作日志
+        log.info("操作日志 - 用户ID: {}, 手机号: {}, 姓名: {}, 操作: {}, 详情: {}", 
+                userId, phone, name, action, details);
+        
+        // 实际项目中可以保存到数据库
+        // AuditLog auditLog = new AuditLog();
+        // auditLog.setUserId(userId);
+        // auditLog.setUserPhone(phone);
+        // auditLog.setUserName(name);
+        // auditLog.setAction(action);
+        // auditLog.setDetails(details);
+        // auditLog.setOperateTime(new Date());
+        // auditLogMapper.insert(auditLog);
+    }
+
+    /**
+     * 示例10: 性能优化(缓存用户信息)
+     */
+    public void example10_CacheUserInfo() {
+        log.info("========== 示例10: 性能优化 ==========");
+        
+        // 一次性获取完整用户信息并缓存
+        StoreUser storeUser = LoginUserUtil.getCurrentStoreUser();
+        
+        if (storeUser != null) {
+            // 后续使用缓存的对象,避免重复查询数据库
+            Integer userId = storeUser.getId();
+            Integer storeId = storeUser.getStoreId();
+            String nickName = storeUser.getNickName();
+            
+            log.info("使用缓存 - 用户ID: {}, 门店ID: {}, 昵称: {}", 
+                    userId, storeId, nickName);
+            
+            // ❌ 不推荐:多次调用查询数据库的方法
+            // Integer storeId1 = LoginUserUtil.getCurrentStoreId();
+            // String nickName1 = LoginUserUtil.getCurrentNickName();
+            // String name1 = LoginUserUtil.getCurrentName();
+        }
+    }
+
+    /**
+     * 主方法:运行所有示例
+     */
+    public static void main(String[] args) {
+        LoginUserUtilExample example = new LoginUserUtilExample();
+        
+        // 注意:实际运行需要在Spring容器中,且用户已登录
+        example.example1_GetBasicInfo();
+        example.example2_GetCompleteInfo();
+        example.example3_CheckLoginStatus();
+        example.example4_CheckCurrentUser();
+        example.example5_GetConvenientFields();
+        example.example6_UseInService();
+        example.example7_SafeNullHandling();
+        example.example9_LogOperation("更新用户信息", "修改昵称和头���");
+        example.example10_CacheUserInfo();
+    }
+}
+

+ 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);
+}
+

+ 12 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/StorePlatformRenovationService.java

@@ -1,9 +1,21 @@
 package shop.alien.storeplatform.service;
 
+import shop.alien.entity.store.vo.StoreDictionaryVo;
 import shop.alien.entity.store.vo.StoreMainInfoVo;
 
+import java.util.List;
+
 public interface StorePlatformRenovationService {
 
+    /**
+     * web端查询经营板块信息
+     */
+    List<StoreDictionaryVo> getBusinessSection();
+
+    /**
+     * web端查询经营板块的经营种类信息
+     */
+    List<StoreDictionaryVo> getBusinessSectionTypes(String parentId);
     StoreMainInfoVo getDecorationDetail(Integer id);
 
 }

+ 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;
+    }
+}
+

+ 29 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformRenovationServiceImpl.java

@@ -4,9 +4,11 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
 import org.springframework.cloud.context.config.annotation.RefreshScope;
 import org.springframework.stereotype.Service;
 import shop.alien.entity.store.*;
+import shop.alien.entity.store.vo.StoreDictionaryVo;
 import shop.alien.entity.store.vo.StoreMainInfoVo;
 import shop.alien.mapper.*;
 import shop.alien.storeplatform.service.StorePlatformRenovationService;
@@ -15,6 +17,7 @@ import java.text.SimpleDateFormat;
 import java.time.Duration;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -29,6 +32,32 @@ public class StorePlatformRenovationServiceImpl extends ServiceImpl<StoreInfoMap
     private final StoreLabelMapper storeLabelMapper;
     private final StoreBusinessInfoMapper storeBusinessInfoMapper;
     private final StoreImgMapper storeImgMapper;
+    private final StoreDictionaryMapper storeDictionaryMapper;
+
+    @Override
+    public List<StoreDictionaryVo> getBusinessSection() {
+        List<StoreDictionary> businessSection = storeDictionaryMapper.selectList(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getTypeName, "business_section").ne(StoreDictionary::getDictId, 0));
+        List<StoreDictionaryVo> voList = new ArrayList<>();
+        for (StoreDictionary storeDictionary : businessSection) {
+            StoreDictionaryVo vo = new StoreDictionaryVo();
+            BeanUtils.copyProperties(storeDictionary, vo);
+            voList.add(vo);
+        }
+        return voList;
+    }
+
+    @Override
+    public List<StoreDictionaryVo> getBusinessSectionTypes(String parentId) {
+        StoreDictionary businessSection = storeDictionaryMapper.selectOne(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getTypeName, "business_section").eq(StoreDictionary::getDictId, parentId));
+        List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getParentId, businessSection.getId()));
+        List<StoreDictionaryVo> voList = new ArrayList<>();
+        for (StoreDictionary storeDictionary : storeDictionaries) {
+            StoreDictionaryVo vo = new StoreDictionaryVo();
+            BeanUtils.copyProperties(storeDictionary, vo);
+            voList.add(vo);
+        }
+        return voList;
+    }
 
     @Override
     public StoreMainInfoVo getDecorationDetail(Integer id) {

+ 246 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/util/LoginUserUtil.java

@@ -0,0 +1,246 @@
+package shop.alien.storeplatform.util;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.store.StoreUser;
+import shop.alien.mapper.StoreUserMapper;
+import shop.alien.util.common.JwtUtil;
+
+/**
+ * 登录用户工具类
+ * 用于在web端商户平台中获取当前登录用户的信息
+ *
+ * @author ssk
+ * @since 2025-11-17
+ */
+@Slf4j
+@Component
+public class LoginUserUtil {
+
+    private static StoreUserMapper storeUserMapper;
+
+    @Autowired
+    public void setStoreUserMapper(StoreUserMapper storeUserMapper) {
+        LoginUserUtil.storeUserMapper = storeUserMapper;
+    }
+
+    /**
+     * 获取当前登录用户的ID
+     *
+     * @return 用户ID,如果未登录返回null
+     */
+    public static Integer getCurrentUserId() {
+        try {
+            JSONObject userInfo = JwtUtil.getCurrentUserInfo();
+            if (userInfo != null) {
+                return userInfo.getInteger("userId");
+            }
+        } catch (Exception e) {
+            log.error("LoginUserUtil.getCurrentUserId - 获取用户ID失败: {}", e.getMessage(), e);
+        }
+        return null;
+    }
+
+    /**
+     * 获取当前登录用户的手机号
+     *
+     * @return 手机号,如果未登录返回null
+     */
+    public static String getCurrentUserPhone() {
+        try {
+            JSONObject userInfo = JwtUtil.getCurrentUserInfo();
+            if (userInfo != null) {
+                return userInfo.getString("phone");
+            }
+        } catch (Exception e) {
+            log.error("LoginUserUtil.getCurrentUserPhone - 获取用户手机号失败: {}", e.getMessage(), e);
+        }
+        return null;
+    }
+
+    /**
+     * 获取当前登录用户的用户类型
+     *
+     * @return 用户类型,如果未登录返回null
+     */
+    public static String getCurrentUserType() {
+        try {
+            JSONObject userInfo = JwtUtil.getCurrentUserInfo();
+            if (userInfo != null) {
+                return userInfo.getString("userType");
+            }
+        } catch (Exception e) {
+            log.error("LoginUserUtil.getCurrentUserType - 获取用户类型失败: {}", e.getMessage(), e);
+        }
+        return null;
+    }
+
+    /**
+     * 获取当前登录用户的Token信息(JSONObject格式)
+     *
+     * @return 用户信息JSONObject,包含userId、phone、userType等字段,如果未登录返回null
+     */
+    public static JSONObject getCurrentUserInfo() {
+        try {
+            JSONObject userInfo = JwtUtil.getCurrentUserInfo();
+            if (userInfo != null) {
+                log.debug("LoginUserUtil.getCurrentUserInfo - 获取用户信息成功: userId={}, phone={}", 
+                        userInfo.getString("userId"), userInfo.getString("phone"));
+                return userInfo;
+            }
+        } catch (Exception e) {
+            log.error("LoginUserUtil.getCurrentUserInfo - 获取用户信息失败: {}", e.getMessage(), e);
+        }
+        return null;
+    }
+
+    /**
+     * 获取当前登录用户的完整商户用户信息(从数据库查询)
+     *
+     * @return StoreUser对象,如果未登录或查询失败返回null
+     */
+    public static StoreUser getCurrentStoreUser() {
+        try {
+            Integer userId = getCurrentUserId();
+            if (userId == null) {
+                log.warn("LoginUserUtil.getCurrentStoreUser - 用户未登录");
+                return null;
+            }
+
+            // 从数据库查询完整用户信息
+            StoreUser storeUser = storeUserMapper.selectById(userId);
+            if (storeUser != null) {
+                log.debug("LoginUserUtil.getCurrentStoreUser - 查询用户信息成功: userId={}, phone={}", 
+                        storeUser.getId(), storeUser.getPhone());
+            } else {
+                log.warn("LoginUserUtil.getCurrentStoreUser - 用户不存在: userId={}", userId);
+            }
+            return storeUser;
+        } catch (Exception e) {
+            log.error("LoginUserUtil.getCurrentStoreUser - 查询用户信息失败: {}", e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 根据手机号获取商户用户信息
+     *
+     * @return StoreUser对象,如果未登录或查询失败返回null
+     */
+    public static StoreUser getCurrentStoreUserByPhone() {
+        try {
+            String phone = getCurrentUserPhone();
+            if (phone == null) {
+                log.warn("LoginUserUtil.getCurrentStoreUserByPhone - 用户未登录");
+                return null;
+            }
+
+            // 从数据库根据手机号查询用户信息
+            LambdaQueryWrapper<StoreUser> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(StoreUser::getPhone, phone);
+            StoreUser storeUser = storeUserMapper.selectOne(queryWrapper);
+            
+            if (storeUser != null) {
+                log.debug("LoginUserUtil.getCurrentStoreUserByPhone - 查询用户信息成功: userId={}, phone={}", 
+                        storeUser.getId(), storeUser.getPhone());
+            } else {
+                log.warn("LoginUserUtil.getCurrentStoreUserByPhone - 用户不存在: phone={}", phone);
+            }
+            return storeUser;
+        } catch (Exception e) {
+            log.error("LoginUserUtil.getCurrentStoreUserByPhone - 查询用户信息失败: {}", e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 验证当前登录用户是否为指定用户
+     *
+     * @param userId 要验证的用户ID
+     * @return true-是当前用户, false-不是当前用户
+     */
+    public static boolean isCurrentUser(Integer userId) {
+        if (userId == null) {
+            return false;
+        }
+        Integer currentUserId = getCurrentUserId();
+        return userId.equals(currentUserId);
+    }
+
+    /**
+     * 验证当前用户是否已登录
+     *
+     * @return true-已登录, false-未登录
+     */
+    public static boolean isLogin() {
+        return getCurrentUserId() != null;
+    }
+
+    /**
+     * 验证当前用户类型是否为web端商户平台
+     *
+     * @return true-是商户平台用户, false-不是
+     */
+    public static boolean isStorePlatformUser() {
+        String userType = getCurrentUserType();
+        return "storePlatform".equals(userType);
+    }
+
+    /**
+     * 获取当前登录用户的门店ID
+     * 注意:需要先查询数据库获取完整用户信息
+     *
+     * @return 门店ID,如果未登录或用户无门店返回null
+     */
+    public static Integer getCurrentStoreId() {
+        try {
+            StoreUser storeUser = getCurrentStoreUser();
+            if (storeUser != null) {
+                return storeUser.getStoreId();
+            }
+        } catch (Exception e) {
+            log.error("LoginUserUtil.getCurrentStoreId - 获取门店ID失败: {}", e.getMessage(), e);
+        }
+        return null;
+    }
+
+    /**
+     * 获取当前登录用户的昵称
+     * 注意:需要先查询数据库获取完整用户信息
+     *
+     * @return 昵称,如果未登录返回null
+     */
+    public static String getCurrentNickName() {
+        try {
+            StoreUser storeUser = getCurrentStoreUser();
+            if (storeUser != null) {
+                return storeUser.getNickName();
+            }
+        } catch (Exception e) {
+            log.error("LoginUserUtil.getCurrentNickName - 获取昵称失败: {}", e.getMessage(), e);
+        }
+        return null;
+    }
+
+    /**
+     * 获取当前登录用户的真实姓名
+     * 注意:需要先查询数据库获取完整用户信息
+     *
+     * @return 真实姓名,如果未登录返回null
+     */
+    public static String getCurrentName() {
+        try {
+            StoreUser storeUser = getCurrentStoreUser();
+            if (storeUser != null) {
+                return storeUser.getName();
+            }
+        } catch (Exception e) {
+            log.error("LoginUserUtil.getCurrentName - 获取姓名失败: {}", e.getMessage(), e);
+        }
+        return null;
+    }
+}
+

+ 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
+
+