Java全栈项目--校园快递管理与配送系统(3)

源代码续

package com.campus.express.service.impl;

import com.campus.express.dto.NotificationCreateRequest;
import com.campus.express.dto.NotificationDTO;
import com.campus.express.dto.NotificationSendRequest;
import com.campus.express.dto.NotificationUpdateRequest;
import com.campus.express.exception.BusinessException;
import com.campus.express.exception.ResourceNotFoundException;
import com.campus.express.model.Notification;
import com.campus.express.repository.NotificationRepository;
import com.campus.express.service.NotificationService;
import com.campus.express.service.NotificationTemplateService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;

/**
 * 通知服务实现类
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class NotificationServiceImpl implements NotificationService {

    private final NotificationRepository notificationRepository;
    private final NotificationTemplateService notificationTemplateService;

    /**
     * 创建通知
     *
     * @param request 创建通知请求
     * @return 通知DTO
     */
    @Override
    @Transactional
    public NotificationDTO createNotification(NotificationCreateRequest request) {
        log.info("Creating notification with title: {}", request.getTitle());
        
        Notification notification = new Notification();
        BeanUtils.copyProperties(request, notification);
        
        // 设置默认值
        notification.setReadStatus(0); // 默认未读
        notification.setSendStatus(0); // 默认未发送
        notification.setRetryCount(0); // 默认重试次数为0
        notification.setCreatedTime(LocalDateTime.now());
        notification.setUpdatedTime(LocalDateTime.now());
        
        Notification savedNotification = notificationRepository.save(notification);
        log.info("Notification created with ID: {}", savedNotification.getId());
        
        return convertToDTO(savedNotification);
    }

    /**
     * 根据ID查询通知
     *
     * @param id 通知ID
     * @return 通知DTO
     */
    @Override
    public NotificationDTO getNotificationById(Long id) {
        log.info("Getting notification by ID: {}", id);
        
        Notification notification = notificationRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Notification not found with id: " + id));
        
        return convertToDTO(notification);
    }

    /**
     * 更新通知
     *
     * @param id      通知ID
     * @param request 更新通知请求
     * @return 更新后的通知DTO
     */
    @Override
    @Transactional
    public NotificationDTO updateNotification(Long id, NotificationUpdateRequest request) {
        log.info("Updating notification with ID: {}", id);
        
        Notification notification = notificationRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Notification not found with id: " + id));
        
        // 只更新非空字段
        if (request.getTitle() != null) {
            notification.setTitle(request.getTitle());
        }
        if (request.getContent() != null) {
            notification.setContent(request.getContent());
        }
        if (request.getType() != null) {
            notification.setType(request.getType());
        }
        if (request.getChannel() != null) {
            notification.setChannel(request.getChannel());
        }
        if (request.getReadStatus() != null) {
            notification.setReadStatus(request.getReadStatus());
        }
        if (request.getSendStatus() != null) {
            notification.setSendStatus(request.getSendStatus());
        }
        if (request.getFailReason() != null) {
            notification.setFailReason(request.getFailReason());
        }
        if (request.getRetryCount() != null) {
            notification.setRetryCount(request.getRetryCount());
        }
        if (request.getRemark() != null) {
            notification.setRemark(request.getRemark());
        }
        
        notification.setUpdatedTime(LocalDateTime.now());
        
        Notification updatedNotification = notificationRepository.save(notification);
        log.info("Notification updated with ID: {}", updatedNotification.getId());
        
        return convertToDTO(updatedNotification);
    }

    /**
     * 删除通知
     *
     * @param id 通知ID
     */
    @Override
    @Transactional
    public void deleteNotification(Long id) {
        log.info("Deleting notification with ID: {}", id);
        
        if (!notificationRepository.existsById(id)) {
            throw new ResourceNotFoundException("Notification not found with id: " + id);
        }
        
        notificationRepository.deleteById(id);
        log.info("Notification deleted with ID: {}", id);
    }

    /**
     * 分页查询通知列表
     *
     * @param pageable 分页参数
     * @return 通知DTO分页列表
     */
    @Override
    public Page<NotificationDTO> getNotifications(Pageable pageable) {
        log.info("Getting notifications with pagination: {}", pageable);
        
        Page<Notification> notificationPage = notificationRepository.findAll(pageable);
        List<NotificationDTO> notificationDTOs = notificationPage.getContent().stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
        
        return new PageImpl<>(notificationDTOs, pageable, notificationPage.getTotalElements());
    }

    /**
     * 根据接收者ID和接收者类型分页查询通知列表
     *
     * @param receiverId   接收者ID
     * @param receiverType 接收者类型
     * @param pageable     分页参数
     * @return 通知DTO分页列表
     */
    @Override
    public Page<NotificationDTO> getNotificationsByReceiver(Long receiverId, Integer receiverType, Pageable pageable) {
        log.info("Getting notifications by receiver ID: {} and receiver type: {} with pagination: {}", 
                receiverId, receiverType, pageable);
        
        Page<Notification> notificationPage = notificationRepository.findByReceiverIdAndReceiverType(
                receiverId, receiverType, pageable);
        List<NotificationDTO> notificationDTOs = notificationPage.getContent().stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
        
        return new PageImpl<>(notificationDTOs, pageable, notificationPage.getTotalElements());
    }

    /**
     * 根据接收者ID、接收者类型和已读状态分页查询通知列表
     *
     * @param receiverId   接收者ID
     * @param receiverType 接收者类型
     * @param readStatus   已读状态
     * @param pageable     分页参数
     * @return 通知DTO分页列表
     */
    @Override
    public Page<NotificationDTO> getNotificationsByReceiverAndReadStatus(
            Long receiverId, Integer receiverType, Integer readStatus, Pageable pageable) {
        log.info("Getting notifications by receiver ID: {}, receiver type: {} and read status: {} with pagination: {}", 
                receiverId, receiverType, readStatus, pageable);
        
        Page<Notification> notificationPage = notificationRepository.findByReceiverIdAndReceiverTypeAndReadStatus(
                receiverId, receiverType, readStatus, pageable);
        List<NotificationDTO> notificationDTOs = notificationPage.getContent().stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
        
        return new PageImpl<>(notificationDTOs, pageable, notificationPage.getTotalElements());
    }

    /**
     * 根据通知类型分页查询通知列表
     *
     * @param type     通知类型
     * @param pageable 分页参数
     * @return 通知DTO分页列表
     */
    @Override
    public Page<NotificationDTO> getNotificationsByType(Integer type, Pageable pageable) {
        log.info("Getting notifications by type: {} with pagination: {}", type, pageable);
        
        Page<Notification> notificationPage = notificationRepository.findByType(type, pageable);
        List<NotificationDTO> notificationDTOs = notificationPage.getContent().stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
        
        return new PageImpl<>(notificationDTOs, pageable, notificationPage.getTotalElements());
    }

    /**
     * 根据通知渠道分页查询通知列表
     *
     * @param channel  通知渠道
     * @param pageable 分页参数
     * @return 通知DTO分页列表
     */
    @Override
    public Page<NotificationDTO> getNotificationsByChannel(Integer channel, Pageable pageable) {
        log.info("Getting notifications by channel: {} with pagination: {}", channel, pageable);
        
        Page<Notification> notificationPage = notificationRepository.findByChannel(channel, pageable);
        List<NotificationDTO> notificationDTOs = notificationPage.getContent().stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
        
        return new PageImpl<>(notificationDTOs, pageable, notificationPage.getTotalElements());
    }

    /**
     * 根据发送状态分页查询通知列表
     *
     * @param sendStatus 发送状态
     * @param pageable   分页参数
     * @return 通知DTO分页列表
     */
    @Override
    public Page<NotificationDTO> getNotificationsBySendStatus(Integer sendStatus, Pageable pageable) {
        log.info("Getting notifications by send status: {} with pagination: {}", sendStatus, pageable);
        
        Page<Notification> notificationPage = notificationRepository.findBySendStatus(sendStatus, pageable);
        List<NotificationDTO> notificationDTOs = notificationPage.getContent().stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
        
        return new PageImpl<>(notificationDTOs, pageable, notificationPage.getTotalElements());
    }

    /**
     * 根据创建时间范围分页查询通知列表
     *
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @param pageable  分页参数
     * @return 通知DTO分页列表
     */
    @Override
    public Page<NotificationDTO> getNotificationsByCreatedTimeBetween(
            LocalDateTime startTime, LocalDateTime endTime, Pageable pageable) {
        log.info("Getting notifications by created time between: {} and {} with pagination: {}", 
                startTime, endTime, pageable);
        
        Page<Notification> notificationPage = notificationRepository.findByCreatedTimeBetween(
                startTime, endTime, pageable);
        List<NotificationDTO> notificationDTOs = notificationPage.getContent().stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
        
        return new PageImpl<>(notificationDTOs, pageable, notificationPage.getTotalElements());
    }

    /**
     * 根据关联业务ID和关联业务类型查询通知列表
     *
     * @param businessId   关联业务ID
     * @param businessType 关联业务类型
     * @return 通知DTO列表
     */
    @Override
    public List<NotificationDTO> getNotificationsByBusiness(Long businessId, Integer businessType) {
        log.info("Getting notifications by business ID: {} and business type: {}", businessId, businessType);
        
        List<Notification> notifications = notificationRepository.findByBusinessIdAndBusinessType(
                businessId, businessType);
        
        return notifications.stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
    }

    /**
     * 发送通知
     *
     * @param request 发送通知请求
     * @return 通知DTO列表
     */
    @Override
    @Transactional
    public List<NotificationDTO> sendNotification(NotificationSendRequest request) {
        log.info("Sending notification to {} receivers", request.getReceiverIds().size());
        
        List<Notification> notifications = new ArrayList<>();
        
        // 如果使用模板,则渲染模板内容
        String title = request.getTitle();
        String content = request.getContent();
        
        if (request.getTemplateCode() != null && !request.getTemplateCode().isEmpty()) {
            Map<String, String> renderedContent = notificationTemplateService.renderTemplate(
                    request.getTemplateCode(), request.getTemplateParams());
            
            title = renderedContent.get("title");
            content = renderedContent.get("content");
            
            if (title == null || content == null) {
                throw new BusinessException("Failed to render template: " + request.getTemplateCode());
            }
        } else if (title == null || content == null) {
            throw new BusinessException("Title and content are required when not using template");
        }
        
        // 创建通知
        for (Long receiverId : request.getReceiverIds()) {
            Notification notification = new Notification();
            notification.setTitle(title);
            notification.setContent(content);
            notification.setType(request.getType());
            notification.setChannel(request.getChannel());
            notification.setReceiverId(receiverId);
            notification.setReceiverType(request.getReceiverType());
            notification.setSenderId(request.getSenderId());
            notification.setSenderType(request.getSenderType() != null ? request.getSenderType() : 1);
            notification.setBusinessId(request.getBusinessId());
            notification.setBusinessType(request.getBusinessType());
            notification.setReadStatus(0); // 默认未读
            notification.setSendStatus(0); // 默认未发送
            notification.setRetryCount(0);
            notification.setRemark(request.getRemark());
            notification.setCreatedTime(LocalDateTime.now());
            notification.setUpdatedTime(LocalDateTime.now());
            
            notifications.add(notification);
        }
        
        // 保存通知
        List<Notification> savedNotifications = notificationRepository.saveAll(notifications);
        
        // 如果需要立即发送,则发送通知
        if (request.getSendImmediately()) {
            // 实际发送逻辑,这里模拟发送
            for (Notification notification : savedNotifications) {
                try {
                    // 模拟发送通知
                    boolean success = sendNotificationByChannel(notification);
                    
                    if (success) {
                        notification.setSendStatus(1); // 发送成功
                    } else {
                        notification.setSendStatus(0); // 发送失败
                        notification.setFailReason("Failed to send notification");
                        notification.setRetryCount(notification.getRetryCount() + 1);
                        notification.setNextRetryTime(LocalDateTime.now().plusMinutes(5)); // 5分钟后重试
                    }
                } catch (Exception e) {
                    log.error("Failed to send notification: {}", e.getMessage(), e);
                    notification.setSendStatus(0); // 发送失败
                    notification.setFailReason(e.getMessage());
                    notification.setRetryCount(notification.getRetryCount() + 1);
                    notification.setNextRetryTime(LocalDateTime.now().plusMinutes(5)); // 5分钟后重试
                }
                
                notification.setUpdatedTime(LocalDateTime.now());
            }
            
            // 更新通知状态
            savedNotifications = notificationRepository.saveAll(savedNotifications);
        }
        
        return savedNotifications.stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
    }

    /**
     * 标记通知为已读
     *
     * @param id 通知ID
     * @return 更新后的通知DTO
     */
    @Override
    @Transactional
    public NotificationDTO markAsRead(Long id) {
        log.info("Marking notification as read with ID: {}", id);
        
        Notification notification = notificationRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Notification not found with id: " + id));
        
        if (notification.getReadStatus() == 1) {
            return convertToDTO(notification); // 已经是已读状态,无需更新
        }
        
        int updated = notificationRepository.updateReadStatus(id, 1, LocalDateTime.now());
        
        if (updated > 0) {
            notification.setReadStatus(1);
            notification.setUpdatedTime(LocalDateTime.now());
        }
        
        return convertToDTO(notification);
    }

    /**
     * 批量标记通知为已读
     *
     * @param ids 通知ID列表
     * @return 更新行数
     */
    @Override
    @Transactional
    public int batchMarkAsRead(List<Long> ids) {
        log.info("Batch marking notifications as read with IDs: {}", ids);
        
        if (ids == null || ids.isEmpty()) {
            return 0;
        }
        
        return notificationRepository.batchUpdateReadStatus(ids, 1, LocalDateTime.now());
    }

    /**
     * 标记接收者的所有通知为已读
     *
     * @param receiverId   接收者ID
     * @param receiverType 接收者类型
     * @return 更新行数
     */
    @Override
    @Transactional
    public int markAllAsRead(Long receiverId, Integer receiverType) {
        log.info("Marking all notifications as read for receiver ID: {} and receiver type: {}", 
                receiverId, receiverType);
        
        // 查询所有未读通知
        Page<Notification> unreadNotifications = notificationRepository.findByReceiverIdAndReceiverTypeAndReadStatus(
                receiverId, receiverType, 0, Pageable.unpaged());
        
        List<Long> unreadIds = unreadNotifications.getContent().stream()
                .map(Notification::getId)
                .collect(Collectors.toList());
        
        if (unreadIds.isEmpty()) {
            return 0;
        }
        
        return notificationRepository.batchUpdateReadStatus(unreadIds, 1, LocalDateTime.now());
    }

    /**
     * 重试发送失败的通知
     *
     * @param id 通知ID
     * @return 更新后的通知DTO
     */
    @Override
    @Transactional
    public NotificationDTO retrySendNotification(Long id) {
        log.info("Retrying to send notification with ID: {}", id);
        
        Notification notification = notificationRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Notification not found with id: " + id));
        
        if (notification.getSendStatus() == 1) {
            throw new BusinessException("Notification has already been sent successfully");
        }
        
        try {
            // 模拟发送通知
            boolean success = sendNotificationByChannel(notification);
            
            if (success) {
                notification.setSendStatus(1); // 发送成功
                notification.setFailReason(null);
            } else {
                notification.setSendStatus(0); // 发送失败
                notification.setFailReason("Failed to send notification");
                notification.setRetryCount(notification.getRetryCount() + 1);
                notification.setNextRetryTime(LocalDateTime.now().plusMinutes(5)); // 5分钟后重试
            }
        } catch (Exception e) {
            log.error("Failed to retry sending notification: {}", e.getMessage(), e);
            notification.setSendStatus(0); // 发送失败
            notification.setFailReason(e.getMessage());
            notification.setRetryCount(notification.getRetryCount() + 1);
            notification.setNextRetryTime(LocalDateTime.now().plusMinutes(5)); // 5分钟后重试
        }
        
        notification.setUpdatedTime(LocalDateTime.now());
        Notification updatedNotification = notificationRepository.save(notification);
        
        return convertToDTO(updatedNotification);
    }

    /**
     * 统计接收者未读通知数量
     *
     * @param receiverId   接收者ID
     * @param receiverType 接收者类型
     * @return 未读通知数量
     */
    @Override
    public long countUnreadNotifications(Long receiverId, Integer receiverType) {
        log.info("Counting unread notifications for receiver ID: {} and receiver type: {}", 
                receiverId, receiverType);
        
        return notificationRepository.countByReceiverIdAndReceiverTypeAndReadStatus(
                receiverId, receiverType, 0);
    }

    /**
     * 统计通知数量
     *
     * @param params 查询参数
     * @return 通知数量
     */
    @Override
    public Map<String, Long> countNotifications(Map<String, Object> params) {
        log.info("Counting notifications with params: {}", params);
        
        Map<String, Long> result = new HashMap<>();
        result.put("total", notificationRepository.count());
        
        // 统计各类型通知数量
        result.put("system", notificationRepository.countByType(1)); // 系统通知
        result.put("express", notificationRepository.countByType(2)); // 快递通知
        result.put("delivery", notificationRepository.countByType(3)); // 配送通知
        result.put("activity", notificationRepository.countByType(4)); // 活动通知
        
        // 统计各渠道通知数量
        result.put("inApp", notificationRepository.countByChannel(1)); // 站内信
        result.put("sms", notificationRepository.countByChannel(2)); // 短信
        result.put("email", notificationRepository.countByChannel(3)); // 邮件
        result.put("push", notificationRepository.countByChannel(4)); // 推送
        
        // 统计发送状态
        result.put("sent", notificationRepository.countBySendStatus(1)); // 已发送
        result.put("unsent", notificationRepository.countBySendStatus(0)); // 未发送
        
        return result;
    }

    /**
     * 根据通知渠道发送通知
     *
     * @param notification 通知对象
     * @return 是否发送成功
     */
    private boolean sendNotificationByChannel(Notification notification) {
        // 模拟发送通知,根据渠道不同,调用不同的发送方法
        switch (notification.getChannel()) {
            case 1: // 站内信
                return sendInAppNotification(notification);
            case 2: // 短信
                return sendSmsNotification(notification);
            case 3: // 邮件
                return sendEmailNotification(notification);
            case 4: // 推送
                return sendPushNotification(notification);
            default:
                log.warn("Unsupported notification channel: {}", notification.getChannel());
                return false;
        }
    }

    /**
     * 发送站内信
     *
     * @param notification 通知对象
     * @return 是否发送成功
     */
    private boolean sendInAppNotification(Notification notification) {
        // 模拟发送站内信,实际项目中应该调用相应的服务
        log.info("Sending in-app notification: {}", notification.getTitle());
        return true; // 模拟发送成功
    }

    /**
     * 发送短信
     *
     * @param notification 通知对象
     * @return 是否发送成功
     */
    private boolean sendSmsNotification(Notification notification) {
        // 模拟发送短信,实际项目中应该调用短信服务商的API
        log.info("Sending SMS notification: {}", notification.getTitle());
        
        // 模拟发送成功率为90%
        return ThreadLocalRandom.current().nextInt(100) < 90;
    }

    /**
     * 发送邮件
     *
     * @param notification 通知对象
     * @return 是否发送成功
     */
    private boolean sendEmailNotification(Notification notification) {
        // 模拟发送邮件,实际项目中应该调用邮件服务
        log.info("Sending email notification: {}", notification.getTitle());
        
        // 模拟发送成功率为95%
        return ThreadLocalRandom.current().nextInt(100) < 95;
    }

    /**
     * 发送推送
     *
     * @param notification 通知对象
     * @return 是否发送成功
     */
    private boolean sendPushNotification(Notification notification) {
        // 模拟发送推送,实际项目中应该调用推送服务
        log.info("Sending push notification: {}", notification.getTitle());
        
        // 模拟发送成功率为85%
        return ThreadLocalRandom.current().nextInt(100) < 85;
    }

    /**
     * 将实体对象转换为DTO对象
     *
     * @param notification 通知实体
     * @return 通知DTO
     */
    private NotificationDTO convertToDTO(Notification notification) {
        NotificationDTO dto = new NotificationDTO();
        BeanUtils.copyProperties(notification, dto);
        
        // 设置类型描述
        if (notification.getType() != null) {
            switch (notification.getType()) {
                case 1:
                    dto.setTypeDesc("系统通知");
                    break;
                case 2:
                    dto.setTypeDesc("快递通知");
                    break;
                case 3:
                    dto.setTypeDesc("配送通知");
                    break;
                case 4:
                    dto.setTypeDesc("活动通知");
                    break;
                default:
                    dto.setTypeDesc("未知类型");
            }
        }
        
        // 设置渠道描述
        if (notification.getChannel() != null) {
            switch (notification.getChannel()) {
                case 1:
                    dto.setChannelDesc("站内信");
                    break;
                case 2:
                    dto.setChannelDesc("短信");
                    break;
                case 3:
                    dto.setChannelDesc("邮件");
                    break;
                case 4:
                    dto.setChannelDesc("推送");
                    break;
                default:
                    dto.setChannelDesc("未知渠道");
            }
        }
        
        // 设置接收者类型描述
        if (notification.getReceiverType() != null) {
            switch (notification.getReceiverType()) {
                case 1:
                    dto.setReceiverTypeDesc("用户");
                    break;
                case 2:
                    dto.setReceiverTypeDesc("配送员");
                    break;
                case 3:
                    dto.setReceiverTypeDesc("管理员");
                    break;
                default:
                    dto.setReceiverTypeDesc("未知类型");
            }
        }
        
        // 设置发送者类型描述
        if (notification.getSenderType() != null) {
            switch (notification.getSenderType()) {
                case 1:
                    dto.setSenderTypeDesc("系统");
                    break;
                case 2:
                    dto.setSenderTypeDesc("用户");
                    break;
                case 3:
                    dto.setSenderTypeDesc("配送员");
                    break;
                case 4:
                    dto.setSenderTypeDesc("管理员");
                    break;
                default:
                    dto.setSenderTypeDesc("未知类型");
            }
        }
        
        // 设置业务类型描述
        if (notification.getBusinessType() != null) {
            switch (notification.getBusinessType()) {
                case 1:
                    dto.setBusinessTypeDesc("快递");
                    break;
                case 2:
                    dto.setBusinessTypeDesc("配送");
                    break;
                case 3:
                    dto.setBusinessTypeDesc("活动");
                    break;
                default:
                    dto.setBusinessTypeDesc("未知类型");
            }
        }
        
        // 设置已读状态描述
        if (notification.getReadStatus() != null) {
            switch (notification.getReadStatus()) {
                case 0:
                    dto.setReadStatusDesc("未读");
                    break;
                case 1:
                    dto.setReadStatusDesc("已读");
                    break;
                default:
                    dto.setReadStatusDesc("未知状态");
            }
        }
        
        // 设置发送状态描述
        if (notification.getSendStatus() != null) {
            switch (notification.getSendStatus()) {
                case 0:
                    dto.setSendStatusDesc("未发送");
                    break;
                case 1:
                    dto.setSendStatusDesc("已发送");
                    break;
                default:
                    dto.setSendStatusDesc("未知状态");
            }
        }
        
        return dto;
    }
}

express-service\src\main\java\com\campus\express\service\impl\NotificationTemplateServiceImpl.java

package com.campus.express.service.impl;

import com.campus.express.dto.NotificationTemplateCreateRequest;
import com.campus.express.dto.NotificationTemplateDTO;
import com.campus.express.dto.NotificationTemplateUpdateRequest;
import com.campus.express.exception.BusinessException;
import com.campus.express.exception.ResourceNotFoundException;
import com.campus.express.model.NotificationTemplate;
import com.campus.express.repository.NotificationTemplateRepository;
import com.campus.express.service.NotificationTemplateService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 通知模板服务实现类
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class NotificationTemplateServiceImpl implements NotificationTemplateService {

    private final NotificationTemplateRepository notificationTemplateRepository;

    /**
     * 创建通知模板
     *
     * @param request 创建通知模板请求
     * @return 通知模板DTO
     */
    @Override
    @Transactional
    public NotificationTemplateDTO createNotificationTemplate(NotificationTemplateCreateRequest request) {
        log.info("Creating notification template with code: {}", request.getCode());
        
        // 检查模板编码是否已存在
        if (notificationTemplateRepository.existsByCode(request.getCode())) {
            throw new BusinessException("Template code already exists: " + request.getCode());
        }
        
        NotificationTemplate template = new NotificationTemplate();
        BeanUtils.copyProperties(request, template);
        
        // 设置默认值
        template.setStatus(1); // 默认启用
        template.setCreatedTime(LocalDateTime.now());
        template.setUpdatedTime(LocalDateTime.now());
        
        NotificationTemplate savedTemplate = notificationTemplateRepository.save(template);
        log.info("Notification template created with ID: {}", savedTemplate.getId());
        
        return convertToDTO(savedTemplate);
    }

    /**
     * 根据ID查询通知模板
     *
     * @param id 通知模板ID
     * @return 通知模板DTO
     */
    @Override
    public NotificationTemplateDTO getNotificationTemplateById(Long id) {
        log.info("Getting notification template by ID: {}", id);
        
        NotificationTemplate template = notificationTemplateRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Notification template not found with id: " + id));
        
        return convertToDTO(template);
    }

    /**
     * 根据模板编码查询通知模板
     *
     * @param code 模板编码
     * @return 通知模板DTO
     */
    @Override
    public NotificationTemplateDTO getNotificationTemplateByCode(String code) {
        log.info("Getting notification template by code: {}", code);
        
        NotificationTemplate template = notificationTemplateRepository.findByCode(code)
                .orElseThrow(() -> new ResourceNotFoundException("Notification template not found with code: " + code));
        
        return convertToDTO(template);
    }

    /**
     * 更新通知模板
     *
     * @param id      通知模板ID
     * @param request 更新通知模板请求
     * @return 更新后的通知模板DTO
     */
    @Override
    @Transactional
    public NotificationTemplateDTO updateNotificationTemplate(Long id, NotificationTemplateUpdateRequest request) {
        log.info("Updating notification template with ID: {}", id);
        
        NotificationTemplate template = notificationTemplateRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Notification template not found with id: " + id));
        
        // 只更新非空字段
        if (request.getName() != null) {
            template.setName(request.getName());
        }
        if (request.getTitle() != null) {
            template.setTitle(request.getTitle());
        }
        if (request.getContent() != null) {
            template.setContent(request.getContent());
        }
        if (request.getType() != null) {
            template.setType(request.getType());
        }
        if (request.getChannel() != null) {
            template.setChannel(request.getChannel());
        }
        if (request.getStatus() != null) {
            template.setStatus(request.getStatus());
        }
        if (request.getRemark() != null) {
            template.setRemark(request.getRemark());
        }
        
        template.setUpdatedTime(LocalDateTime.now());
        
        NotificationTemplate updatedTemplate = notificationTemplateRepository.save(template);
        log.info("Notification template updated with ID: {}", updatedTemplate.getId());
        
        return convertToDTO(updatedTemplate);
    }

    /**
     * 删除通知模板
     *
     * @param id 通知模板ID
     */
    @Override
    @Transactional
    public void deleteNotificationTemplate(Long id) {
        log.info("Deleting notification template with ID: {}", id);
        
        if (!notificationTemplateRepository.existsById(id)) {
            throw new ResourceNotFoundException("Notification template not found with id: " + id);
        }
        
        notificationTemplateRepository.deleteById(id);
        log.info("Notification template deleted with ID: {}", id);
    }

    /**
     * 分页查询通知模板列表
     *
     * @param pageable 分页参数
     * @return 通知模板DTO分页列表
     */
    @Override
    public Page<NotificationTemplateDTO> getNotificationTemplates(Pageable pageable) {
        log.info("Getting notification templates with pagination: {}", pageable);
        
        Page<NotificationTemplate> templatePage = notificationTemplateRepository.findAll(pageable);
        List<NotificationTemplateDTO> templateDTOs = templatePage.getContent().stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
        
        return new PageImpl<>(templateDTOs, pageable, templatePage.getTotalElements());
    }

    /**
     * 根据模板名称分页查询通知模板列表
     *
     * @param name     模板名称
     * @param pageable 分页参数
     * @return 通知模板DTO分页列表
     */
    @Override
    public Page<NotificationTemplateDTO> getNotificationTemplatesByName(String name, Pageable pageable) {
        log.info("Getting notification templates by name: {} with pagination: {}", name, pageable);
        
        Page<NotificationTemplate> templatePage = notificationTemplateRepository.findByNameContaining(name, pageable);
        List<NotificationTemplateDTO> templateDTOs = templatePage.getContent().stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
        
        return new PageImpl<>(templateDTOs, pageable, templatePage.getTotalElements());
    }

    /**
     * 根据模板类型分页查询通知模板列表
     *
     * @param type     模板类型
     * @param pageable 分页参数
     * @return 通知模板DTO分页列表
     */
    @Override
    public Page<NotificationTemplateDTO> getNotificationTemplatesByType(Integer type, Pageable pageable) {
        log.info("Getting notification templates by type: {} with pagination: {}", type, pageable);
        
        Page<NotificationTemplate> templatePage = notificationTemplateRepository.findByType(type, pageable);
        List<NotificationTemplateDTO> templateDTOs = templatePage.getContent().stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
        
        return new PageImpl<>(templateDTOs, pageable, templatePage.getTotalElements());
    }

    /**
     * 根据适用渠道分页查询通知模板列表
     *
     * @param channel  适用渠道
     * @param pageable 分页参数
     * @return 通知模板DTO分页列表
     */
    @Override
    public Page<NotificationTemplateDTO> getNotificationTemplatesByChannel(Integer channel, Pageable pageable) {
        log.info("Getting notification templates by channel: {} with pagination: {}", channel, pageable);
        
        Page<NotificationTemplate> templatePage = notificationTemplateRepository.findByChannel(channel, pageable);
        List<NotificationTemplateDTO> templateDTOs = templatePage.getContent().stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
        
        return new PageImpl<>(templateDTOs, pageable, templatePage.getTotalElements());
    }

    /**
     * 根据状态分页查询通知模板列表
     *
     * @param status   状态
     * @param pageable 分页参数
     * @return 通知模板DTO分页列表
     */
    @Override
    public Page<NotificationTemplateDTO> getNotificationTemplatesByStatus(Integer status, Pageable pageable) {
        log.info("Getting notification templates by status: {} with pagination: {}", status, pageable);
        
        Page<NotificationTemplate> templatePage = notificationTemplateRepository.findByStatus(status, pageable);
        List<NotificationTemplateDTO> templateDTOs = templatePage.getContent().stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
        
        return new PageImpl<>(templateDTOs, pageable, templatePage.getTotalElements());
    }

    /**
     * 根据模板类型和适用渠道查询通知模板列表
     *
     * @param type    模板类型
     * @param channel 适用渠道
     * @return 通知模板DTO列表
     */
    @Override
    public List<NotificationTemplateDTO> getNotificationTemplatesByTypeAndChannel(Integer type, Integer channel) {
        log.info("Getting notification templates by type: {} and channel: {}", type, channel);
        
        List<NotificationTemplate> templates = notificationTemplateRepository.findByTypeAndChannel(type, channel);
        
        return templates.stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
    }

    /**
     * 根据模板类型和状态查询通知模板列表
     *
     * @param type   模板类型
     * @param status 状态
     * @return 通知模板DTO列表
     */
    @Override
    public List<NotificationTemplateDTO> getNotificationTemplatesByTypeAndStatus(Integer type, Integer status) {
        log.info("Getting notification templates by type: {} and status: {}", type, status);
        
        List<NotificationTemplate> templates = notificationTemplateRepository.findByTypeAndStatus(type, status);
        
        return templates.stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
    }

    /**
     * 根据适用渠道和状态查询通知模板列表
     *
     * @param channel 适用渠道
     * @param status  状态
     * @return 通知模板DTO列表
     */
    @Override
    public List<NotificationTemplateDTO> getNotificationTemplatesByChannelAndStatus(Integer channel, Integer status) {
        log.info("Getting notification templates by channel: {} and status: {}", channel, status);
        
        List<NotificationTemplate> templates = notificationTemplateRepository.findByChannelAndStatus(channel, status);
        
        return templates.stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
    }

    /**
     * 更新通知模板状态
     *
     * @param id     通知模板ID
     * @param status 状态
     * @return 更新后的通知模板DTO
     */
    @Override
    @Transactional
    public NotificationTemplateDTO updateTemplateStatus(Long id, Integer status) {
        log.info("Updating notification template status with ID: {} to status: {}", id, status);
        
        NotificationTemplate template = notificationTemplateRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Notification template not found with id: " + id));
        
        int updated = notificationTemplateRepository.updateStatus(id, status, LocalDateTime.now());
        
        if (updated > 0) {
            template.setStatus(status);
            template.setUpdatedTime(LocalDateTime.now());
        }
        
        return convertToDTO(template);
    }

    /**
     * 检查模板编码是否存在
     *
     * @param code 模板编码
     * @return 是否存在
     */
    @Override
    public boolean isTemplateCodeExists(String code) {
        log.info("Checking if template code exists: {}", code);
        
        return notificationTemplateRepository.existsByCode(code);
    }

    /**
     * 渲染模板内容
     *
     * @param templateCode 模板编码
     * @param params       模板参数
     * @return 渲染后的内容
     */
    @Override
    public Map<String, String> renderTemplate(String templateCode, Map<String, Object> params) {
        log.info("Rendering template with code: {} and params: {}", templateCode, params);
        
        NotificationTemplate template = notificationTemplateRepository.findByCode(templateCode)
                .orElseThrow(() -> new ResourceNotFoundException("Notification template not found with code: " + templateCode));
        
        // 检查模板状态
        if (template.getStatus() != 1) {
            throw new BusinessException("Notification template is disabled: " + templateCode);
        }
        
        String title = template.getTitle();
        String content = template.getContent();
        
        // 渲染标题
        title = renderContent(title, params);
        
        // 渲染内容
        content = renderContent(content, params);
        
        Map<String, String> result = new HashMap<>();
        result.put("title", title);
        result.put("content", content);
        
        return result;
    }

    /**
     * 统计通知模板数量
     *
     * @param params 查询参数
     * @return 通知模板数量
     */
    @Override
    public Map<String, Long> countNotificationTemplates(Map<String, Object> params) {
        log.info("Counting notification templates with params: {}", params);
        
        Map<String, Long> result = new HashMap<>();
        result.put("total", notificationTemplateRepository.count());
        
        // 统计各类型模板数量
        result.put("system", notificationTemplateRepository.countByType(1)); // 系统通知
        result.put("express", notificationTemplateRepository.countByType(2)); // 快递通知
        result.put("delivery", notificationTemplateRepository.countByType(3)); // 配送通知
        result.put("activity", notificationTemplateRepository.countByType(4)); // 活动通知
        
        // 统计各渠道模板数量
        result.put("inApp", notificationTemplateRepository.countByChannel(1)); // 站内信
        result.put("sms", notificationTemplateRepository.countByChannel(2)); // 短信
        result.put("email", notificationTemplateRepository.countByChannel(3)); // 邮件
        result.put("push", notificationTemplateRepository.countByChannel(4)); // 推送
        
        // 统计状态
        result.put("enabled", notificationTemplateRepository.countByStatus(1)); // 启用
        result.put("disabled", notificationTemplateRepository.countByStatus(0)); // 停用
        
        return result;
    }

    /**
     * 渲染内容
     *
     * @param content 模板内容
     * @param params  模板参数
     * @return 渲染后的内容
     */
    private String renderContent(String content, Map<String, Object> params) {
        if (content == null || content.isEmpty() || params == null || params.isEmpty()) {
            return content;
        }
        
        // 使用正则表达式匹配 ${key} 格式的占位符
        Pattern pattern = Pattern.compile("\\$\\{([^}]*)\\}");
        Matcher matcher = pattern.matcher(content);
        
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            String key = matcher.group(1);
            Object value = params.get(key);
            
            // 如果参数中没有对应的值,保留原占位符
            String replacement = (value != null) ? value.toString() : matcher.group();
            
            // 替换特殊字符
            replacement = replacement.replace("\\", "\\\\").replace("$", "\\$");
            
            matcher.appendReplacement(sb, replacement);
        }
        matcher.appendTail(sb);
        
        return sb.toString();
    }

    /**
     * 将实体对象转换为DTO对象
     *
     * @param template 通知模板实体
     * @return 通知模板DTO
     */
    private NotificationTemplateDTO convertToDTO(NotificationTemplate template) {
        NotificationTemplateDTO dto = new NotificationTemplateDTO();
        BeanUtils.copyProperties(template, dto);
        
        // 设置类型描述
        if (template.getType() != null) {
            switch (template.getType()) {
                case 1:
                    dto.setTypeDesc("系统通知");
                    break;
                case 2:
                    dto.setTypeDesc("快递通知");
                    break;
                case 3:
                    dto.setTypeDesc("配送通知");
                    break;
                case 4:
                    dto.setTypeDesc("活动通知");
                    break;
                default:
                    dto.setTypeDesc("未知类型");
            }
        }
        
        // 设置渠道描述
        if (template.getChannel() != null) {
            switch (template.getChannel()) {
                case 1:
                    dto.setChannelDesc("站内信");
                    break;
                case 2:
                    dto.setChannelDesc("短信");
                    break;
                case 3:
                    dto.setChannelDesc("邮件");
                    break;
                case 4:
                    dto.setChannelDesc("推送");
                    break;
                default:
                    dto.setChannelDesc("未知渠道");
            }
        }
        
        // 设置状态描述
        if (template.getStatus() != null) {
            switch (template.getStatus()) {
                case 0:
                    dto.setStatusDesc("停用");
                    break;
                case 1:
                    dto.setStatusDesc("启用");
                    break;
                default:
                    dto.setStatusDesc("未知状态");
            }
        }
        
        return dto;
    }
}

express-service\src\main\java\com\campus\express\util\NotificationUtils.java

package com.campus.express.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 通知工具类
 */
@Component
@Slf4j
public class NotificationUtils {

    /**
     * 通知类型常量
     */
    public static final class NotificationType {
        public static final int SYSTEM = 1; // 系统通知
        public static final int EXPRESS = 2; // 快递通知
        public static final int DELIVERY = 3; // 配送通知
        public static final int ACTIVITY = 4; // 活动通知
    }

    /**
     * 通知渠道常量
     */
    public static final class NotificationChannel {
        public static final int IN_APP = 1; // 站内信
        public static final int SMS = 2; // 短信
        public static final int EMAIL = 3; // 邮件
        public static final int PUSH = 4; // 推送
    }

    /**
     * 通知读取状态常量
     */
    public static final class NotificationReadStatus {
        public static final int UNREAD = 0; // 未读
        public static final int READ = 1; // 已读
    }

    /**
     * 通知发送状态常量
     */
    public static final class NotificationSendStatus {
        public static final int UNSENT = 0; // 未发送
        public static final int SENT = 1; // 已发送
        public static final int FAILED = 2; // 发送失败
    }

    /**
     * 接收者类型常量
     */
    public static final class ReceiverType {
        public static final int USER = 1; // 用户
        public static final int DELIVERYMAN = 2; // 配送员
        public static final int ADMIN = 3; // 管理员
    }

    /**
     * 发送者类型常量
     */
    public static final class SenderType {
        public static final int SYSTEM = 1; // 系统
        public static final int USER = 2; // 用户
        public static final int DELIVERYMAN = 3; // 配送员
        public static final int ADMIN = 4; // 管理员
    }

    /**
     * 业务类型常量
     */
    public static final class BusinessType {
        public static final int EXPRESS = 1; // 快递
        public static final int DELIVERY = 2; // 配送
        public static final int ACTIVITY = 3; // 活动
    }

    /**
     * 模板状态常量
     */
    public static final class TemplateStatus {
        public static final int DISABLED = 0; // 停用
        public static final int ENABLED = 1; // 启用
    }

    /**
     * 获取通知类型描述
     *
     * @param type 通知类型
     * @return 通知类型描述
     */
    public static String getNotificationTypeDesc(Integer type) {
        if (type == null) {
            return "未知类型";
        }
        
        switch (type) {
            case NotificationType.SYSTEM:
                return "系统通知";
            case NotificationType.EXPRESS:
                return "快递通知";
            case NotificationType.DELIVERY:
                return "配送通知";
            case NotificationType.ACTIVITY:
                return "活动通知";
            default:
                return "未知类型";
        }
    }

    /**
     * 获取通知渠道描述
     *
     * @param channel 通知渠道
     * @return 通知渠道描述
     */
    public static String getNotificationChannelDesc(Integer channel) {
        if (channel == null) {
            return "未知渠道";
        }
        
        switch (channel) {
            case NotificationChannel.IN_APP:
                return "站内信";
            case NotificationChannel.SMS:
                return "短信";
            case NotificationChannel.EMAIL:
                return "邮件";
            case NotificationChannel.PUSH:
                return "推送";
            default:
                return "未知渠道";
        }
    }

    /**
     * 获取接收者类型描述
     *
     * @param receiverType 接收者类型
     * @return 接收者类型描述
     */
    public static String getReceiverTypeDesc(Integer receiverType) {
        if (receiverType == null) {
            return "未知类型";
        }
        
        switch (receiverType) {
            case ReceiverType.USER:
                return "用户";
            case ReceiverType.DELIVERYMAN:
                return "配送员";
            case ReceiverType.ADMIN:
                return "管理员";
            default:
                return "未知类型";
        }
    }

    /**
     * 获取发送者类型描述
     *
     * @param senderType 发送者类型
     * @return 发送者类型描述
     */
    public static String getSenderTypeDesc(Integer senderType) {
        if (senderType == null) {
            return "未知类型";
        }
        
        switch (senderType) {
            case SenderType.SYSTEM:
                return "系统";
            case SenderType.USER:
                return "用户";
            case SenderType.DELIVERYMAN:
                return "配送员";
            case SenderType.ADMIN:
                return "管理员";
            default:
                return "未知类型";
        }
    }

    /**
     * 获取业务类型描述
     *
     * @param businessType 业务类型
     * @return 业务类型描述
     */
    public static String getBusinessTypeDesc(Integer businessType) {
        if (businessType == null) {
            return "未知类型";
        }
        
        switch (businessType) {
            case BusinessType.EXPRESS:
                return "快递";
            case BusinessType.DELIVERY:
                return "配送";
            case BusinessType.ACTIVITY:
                return "活动";
            default:
                return "未知类型";
        }
    }

    /**
     * 获取通知读取状态描述
     *
     * @param readStatus 通知读取状态
     * @return 通知读取状态描述
     */
    public static String getReadStatusDesc(Integer readStatus) {
        if (readStatus == null) {
            return "未知状态";
        }
        
        switch (readStatus) {
            case NotificationReadStatus.UNREAD:
                return "未读";
            case NotificationReadStatus.READ:
                return "已读";
            default:
                return "未知状态";
        }
    }

    /**
     * 获取通知发送状态描述
     *
     * @param sendStatus 通知发送状态
     * @return 通知发送状态描述
     */
    public static String getSendStatusDesc(Integer sendStatus) {
        if (sendStatus == null) {
            return "未知状态";
        }
        
        switch (sendStatus) {
            case NotificationSendStatus.UNSENT:
                return "未发送";
            case NotificationSendStatus.SENT:
                return "已发送";
            case NotificationSendStatus.FAILED:
                return "发送失败";
            default:
                return "未知状态";
        }
    }

    /**
     * 获取模板状态描述
     *
     * @param status 模板状态
     * @return 模板状态描述
     */
    public static String getTemplateStatusDesc(Integer status) {
        if (status == null) {
            return "未知状态";
        }
        
        switch (status) {
            case TemplateStatus.DISABLED:
                return "停用";
            case TemplateStatus.ENABLED:
                return "启用";
            default:
                return "未知状态";
        }
    }

    /**
     * 渲染模板内容
     *
     * @param content 模板内容
     * @param params  模板参数
     * @return 渲染后的内容
     */
    public static String renderTemplate(String content, Map<String, Object> params) {
        if (content == null || content.isEmpty() || params == null || params.isEmpty()) {
            return content;
        }
        
        // 使用正则表达式匹配 ${key} 格式的占位符
        Pattern pattern = Pattern.compile("\\$\\{([^}]*)\\}");
        Matcher matcher = pattern.matcher(content);
        
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            String key = matcher.group(1);
            Object value = params.get(key);
            
            // 如果参数中没有对应的值,保留原占位符
            String replacement = (value != null) ? value.toString() : matcher.group();
            
            // 替换特殊字符
            replacement = replacement.replace("\\", "\\\\").replace("$", "\\$");
            
            matcher.appendReplacement(sb, replacement);
        }
        matcher.appendTail(sb);
        
        return sb.toString();
    }

    /**
     * 获取短信内容(去除HTML标签)
     *
     * @param content HTML内容
     * @return 纯文本内容
     */
    public static String getSmsContent(String content) {
        if (content == null || content.isEmpty()) {
            return content;
        }
        
        // 去除HTML标签
        return content.replaceAll("<[^>]*>", "");
    }

    /**
     * 截断内容(适用于短信等有长度限制的渠道)
     *
     * @param content 内容
     * @param maxLength 最大长度
     * @return 截断后的内容
     */
    public static String truncateContent(String content, int maxLength) {
        if (content == null || content.isEmpty() || content.length() <= maxLength) {
            return content;
        }
        
        return content.substring(0, maxLength - 3) + "...";
    }

    /**
     * 生成通知摘要
     *
     * @param content 通知内容
     * @param maxLength 最大长度
     * @return 通知摘要
     */
    public static String generateSummary(String content, int maxLength) {
        if (content == null || content.isEmpty()) {
            return "";
        }
        
        // 去除HTML标签
        String plainText = content.replaceAll("<[^>]*>", "");
        
        // 截断内容
        return truncateContent(plainText, maxLength);
    }

    /**
     * 验证邮箱格式
     *
     * @param email 邮箱地址
     * @return 是否有效
     */
    public static boolean isValidEmail(String email) {
        if (email == null || email.isEmpty()) {
            return false;
        }
        
        // 简单的邮箱格式验证
        String regex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
        return email.matches(regex);
    }

    /**
     * 验证手机号格式
     *
     * @param mobile 手机号
     * @return 是否有效
     */
    public static boolean isValidMobile(String mobile) {
        if (mobile == null || mobile.isEmpty()) {
            return false;
        }
        
        // 简单的手机号格式验证(中国大陆手机号)
        String regex = "^1[3-9]\\d{9}$";
        return mobile.matches(regex);
    }

    /**
     * 获取通知统计信息
     *
     * @return 统计信息
     */
    public static Map<String, String> getNotificationStatLabels() {
        Map<String, String> labels = new HashMap<>();
        labels.put("total", "总数");
        labels.put("system", "系统通知");
        labels.put("express", "快递通知");
        labels.put("delivery", "配送通知");
        labels.put("activity", "活动通知");
        labels.put("inApp", "站内信");
        labels.put("sms", "短信");
        labels.put("email", "邮件");
        labels.put("push", "推送");
        labels.put("sent", "已发送");
        labels.put("unsent", "未发送");
        labels.put("failed", "发送失败");
        labels.put("read", "已读");
        labels.put("unread", "未读");
        labels.put("enabled", "启用");
        labels.put("disabled", "停用");
        return labels;
    }
}

express-service\src\main\resources\application.yml

server:
  port: 8082
  servlet:
    context-path: /api/express

spring:
  application:
    name: express-service
  datasource:
    url: jdbc:mysql://localhost:3306/campus_express_db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL8Dialect
        format_sql: true

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true

# JWT配置
jwt:
  header: Authorization
  prefix: Bearer
  secret: campusExpressSecretKey123456789012345678901234567890
  expiration: 86400000  # 24小时,单位:毫秒

# Feign配置
feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000
  hystrix:
    enabled: true

# 日志配置
logging:
  level:
    com.campus.express: INFO
    org.springframework.web: INFO
    org.hibernate: ERROR

express-service\src\main\resources\db\data.sql

-- 校园快递管理与配送系统初始数据

-- 初始化角色数据
INSERT INTO `role` (`id`, `role_name`, `role_key`, `role_sort`, `status`, `created_time`, `updated_time`, `remark`) VALUES
(1, '超级管理员', 'admin', 1, 1, NOW(), NOW(), '超级管理员'),
(2, '配送员', 'deliveryman', 2, 1, NOW(), NOW(), '配送员'),
(3, '普通用户', 'user', 3, 1, NOW(), NOW(), '普通用户');

-- 初始化用户数据(密码统一为123456的MD5加密)
INSERT INTO `user` (`id`, `username`, `password`, `salt`, `real_name`, `mobile`, `email`, `avatar`, `gender`, `student_id`, `status`, `type`, `created_time`, `updated_time`, `remark`) VALUES
(1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 'salt', '系统管理员', '13800138000', '[email protected]', NULL, 1, NULL, 1, 3, NOW(), NOW(), '系统管理员'),
(2, 'deliveryman1', 'e10adc3949ba59abbe56e057f20f883e', 'salt', '张三', '13800138001', '[email protected]', NULL, 1, NULL, 1, 2, NOW(), NOW(), '配送员'),
(3, 'deliveryman2', 'e10adc3949ba59abbe56e057f20f883e', 'salt', '李四', '13800138002', '[email protected]', NULL, 1, NULL, 1, 2, NOW(), NOW(), '配送员'),
(4, 'user1', 'e10adc3949ba59abbe56e057f20f883e', 'salt', '王五', '13800138003', '[email protected]', NULL, 1, '2021001', 1, 1, NOW(), NOW(), '普通用户'),
(5, 'user2', 'e10adc3949ba59abbe56e057f20f883e', 'salt', '赵六', '13800138004', '[email protected]', NULL, 2, '2021002', 1, 1, NOW(), NOW(), '普通用户');

-- 初始化用户角色关联数据
INSERT INTO `user_role` (`user_id`, `role_id`) VALUES
(1, 1),
(2, 2),
(3, 2),
(4, 3),
(5, 3);

-- 初始化配送员数据
INSERT INTO `deliveryman` (`id`, `user_id`, `status`, `work_status`, `score`, `delivery_count`, `created_time`, `updated_time`, `remark`) VALUES
(1, 2, 1, 1, 4.8, 120, NOW(), NOW(), '资深配送员'),
(2, 3, 1, 0, 4.5, 80, NOW(), NOW(), '配送员');

-- 初始化配送区域数据
INSERT INTO `delivery_area` (`id`, `name`, `description`, `status`, `created_time`, `updated_time`, `remark`) VALUES
(1, '东校区', '东校区包括1-5号宿舍楼', 1, NOW(), NOW(), '东校区配送范围'),
(2, '西校区', '西校区包括6-10号宿舍楼', 1, NOW(), NOW(), '西校区配送范围'),
(3, '南校区', '南校区包括11-15号宿舍楼', 1, NOW(), NOW(), '南校区配送范围'),
(4, '北校区', '北校区包括16-20号宿舍楼', 1, NOW(), NOW(), '北校区配送范围');

-- 初始化配送路线数据
INSERT INTO `delivery_route` (`id`, `name`, `area_id`, `description`, `status`, `created_time`, `updated_time`, `remark`) VALUES
(1, '东校区1号线', 1, '东校区1-3号宿舍楼配送路线', 1, NOW(), NOW(), '东校区1号配送路线'),
(2, '东校区2号线', 1, '东校区4-5号宿舍楼配送路线', 1, NOW(), NOW(), '东校区2号配送路线'),
(3, '西校区1号线', 2, '西校区6-8号宿舍楼配送路线', 1, NOW(), NOW(), '西校区1号配送路线'),
(4, '西校区2号线', 2, '西校区9-10号宿舍楼配送路线', 1, NOW(), NOW(), '西校区2号配送路线'),
(5, '南校区1号线', 3, '南校区11-15号宿舍楼配送路线', 1, NOW(), NOW(), '南校区1号配送路线'),
(6, '北校区1号线', 4, '北校区16-20号宿舍楼配送路线', 1, NOW(), NOW(), '北校区1号配送路线');

-- 初始化快递数据
INSERT INTO `express` (`id`, `express_no`, `express_company`, `sender_name`, `sender_mobile`, `sender_address`, `receiver_name`, `receiver_mobile`, `receiver_address`, `user_id`, `weight`, `express_type`, `status`, `arrival_time`, `pickup_code`, `created_time`, `updated_time`, `remark`) VALUES
(1, 'SF1001001', '顺丰速运', '张三', '13900001111', '北京市海淀区中关村', '王五', '13800138003', '东校区3号宿舍楼303', 4, 1.5, 1, 5, DATE_SUB(NOW(), INTERVAL 1 DAY), '123456', DATE_SUB(NOW(), INTERVAL 3 DAY), NOW(), '书籍'),
(2, 'YT1001002', '圆通速递', '李四', '13900002222', '上海市浦东新区', '王五', '13800138003', '东校区3号宿舍楼303', 4, 2.0, 1, 6, DATE_SUB(NOW(), INTERVAL 2 DAY), '234567', DATE_SUB(NOW(), INTERVAL 4 DAY), NOW(), '衣物'),
(3, 'ZT1001003', '中通快递', '王五', '13900003333', '广州市天河区', '赵六', '13800138004', '西校区8号宿舍楼808', 5, 0.5, 1, 7, DATE_SUB(NOW(), INTERVAL 3 DAY), '345678', DATE_SUB(NOW(), INTERVAL 5 DAY), NOW(), '文件'),
(4, 'YD1001004', '韵达快递', '赵六', '13900004444', '深圳市南山区', '赵六', '13800138004', '西校区8号宿舍楼808', 5, 3.0, 2, 4, NOW(), '456789', DATE_SUB(NOW(), INTERVAL 1 DAY), NOW(), '电子产品'),
(5, 'JD1001005', '京东物流', '电商平台', '4008888888', '北京市朝阳区', '王五', '13800138003', '东校区3号宿舍楼303', 4, 1.0, 1, 5, DATE_SUB(NOW(), INTERVAL 1 DAY), '567890', DATE_SUB(NOW(), INTERVAL 2 DAY), NOW(), '日用品');

-- 初始化配送任务数据
INSERT INTO `delivery_task` (`id`, `express_id`, `deliveryman_id`, `route_id`, `status`, `expected_delivery_time`, `actual_delivery_time`, `created_time`, `updated_time`, `remark`) VALUES
(1, 1, 1, 1, 3, DATE_ADD(NOW(), INTERVAL 2 HOUR), NULL, NOW(), NOW(), '东校区配送任务'),
(2, 2, 1, 1, 4, DATE_ADD(NOW(), INTERVAL 1 HOUR), NULL, NOW(), NOW(), '东校区配送任务'),
(3, 3, 2, 3, 5, DATE_SUB(NOW(), INTERVAL 1 HOUR), DATE_SUB(NOW(), INTERVAL 30 MINUTE), NOW(), NOW(), '西校区配送任务'),
(4, 4, NULL, 3, 1, NULL, NULL, NOW(), NOW(), '西校区待分配配送任务'),
(5, 5, 1, 1, 2, DATE_ADD(NOW(), INTERVAL 3 HOUR), NULL, NOW(), NOW(), '东校区配送任务');

-- 初始化快递跟踪记录数据
INSERT INTO `express_tracking` (`id`, `express_id`, `status`, `operator_id`, `operator_type`, `operation_time`, `location`, `remark`) VALUES
(1, 1, 1, NULL, 1, DATE_SUB(NOW(), INTERVAL 3 DAY), '北京市海淀区中关村', '快件已揽收'),
(2, 1, 2, NULL, 1, DATE_SUB(NOW(), INTERVAL 2 DAY, 12 HOUR), '北京市海淀区中关村', '快件在运输中'),
(3, 1, 3, NULL, 1, DATE_SUB(NOW(), INTERVAL 2 DAY), '北京市海淀区中关村', '快件运输中'),
(4, 1, 4, NULL, 1, DATE_SUB(NOW(), INTERVAL 1 DAY), '校园快递驿站', '快件已到达'),
(5, 1, 5, 1, 4, DATE_SUB(NOW(), INTERVAL 12 HOUR), '校园快递驿站', '快件待配送'),
(6, 2, 1, NULL, 1, DATE_SUB(NOW(), INTERVAL 4 DAY), '上海市浦东新区', '快件已揽收'),
(7, 2, 2, NULL, 1, DATE_SUB(NOW(), INTERVAL 3 DAY), '上海市浦东新区', '快件在运输中'),
(8, 2, 3, NULL, 1, DATE_SUB(NOW(), INTERVAL 2 DAY, 12 HOUR), '上海市浦东新区', '快件运输中'),
(9, 2, 4, NULL, 1, DATE_SUB(NOW(), INTERVAL 2 DAY), '校园快递驿站', '快件已到达'),
(10, 2, 5, 1, 4, DATE_SUB(NOW(), INTERVAL 1 DAY), '校园快递驿站', '快件待配送'),
(11, 2, 6, 2, 3, DATE_SUB(NOW(), INTERVAL 2 HOUR), '校园快递驿站', '快件配送中');

-- 初始化通知模板数据
INSERT INTO `notification_template` (`id`, `code`, `name`, `title`, `content`, `type`, `channel`, `status`, `created_time`, `updated_time`, `remark`) VALUES
(1, 'EXPRESS_ARRIVAL', '快递到达通知', '您的快递已到达', '尊敬的${receiverName},您的快递(${expressNo})已到达校园驿站,取件码:${pickupCode},请及时取件。', 2, 1, 1, NOW(), NOW(), '快递到达站内信通知'),
(2, 'EXPRESS_ARRIVAL_SMS', '快递到达短信', '【校园快递】您的快递已到达', '【校园快递】尊敬的${receiverName},您的快递(${expressNo})已到达校园驿站,取件码:${pickupCode},请及时取件。', 2, 2, 1, NOW(), NOW(), '快递到达短信通知'),
(3, 'EXPRESS_DELIVERY', '快递配送通知', '您的快递正在配送中', '尊敬的${receiverName},您的快递(${expressNo})正在配送中,预计送达时间:${expectedTime},配送员:${deliverymanName},联系电话:${deliverymanMobile}。', 2, 1, 1, NOW(), NOW(), '快递配送站内信通知'),
(4, 'EXPRESS_DELIVERY_SMS', '快递配送短信', '【校园快递】您的快递正在配送中', '【校园快递】尊敬的${receiverName},您的快递(${expressNo})正在配送中,预计送达时间:${expectedTime},配送员:${deliverymanName},联系电话:${deliverymanMobile}。', 2, 2, 1, NOW(), NOW(), '快递配送短信通知'),
(5, 'EXPRESS_SIGNED', '快递签收通知', '您的快递已签收', '尊敬的${receiverName},您的快递(${expressNo})已签收,感谢您使用校园快递服务。', 2, 1, 1, NOW(), NOW(), '快递签收站内信通知'),
(6, 'EXPRESS_SIGNED_SMS', '快递签收短信', '【校园快递】您的快递已签收', '【校园快递】尊敬的${receiverName},您的快递(${expressNo})已签收,感谢您使用校园快递服务。', 2, 2, 1, NOW(), NOW(), '快递签收短信通知'),
(7, 'DELIVERY_TASK_ASSIGN', '配送任务分配通知', '您有新的配送任务', '尊敬的${deliverymanName},您有新的配送任务(${taskId}),请及时查看并接单。', 3, 1, 1, NOW(), NOW(), '配送任务分配站内信通知'),
(8, 'DELIVERY_TASK_ASSIGN_SMS', '配送任务分配短信', '【校园快递】您有新的配送任务', '【校园快递】尊敬的${deliverymanName},您有新的配送任务(${taskId}),请及时查看并接单。', 3, 2, 1, NOW(), NOW(), '配送任务分配短信通知'),
(9, 'SYSTEM_NOTICE', '系统通知', '系统通知:${title}', '${content}', 1, 1, 1, NOW(), NOW(), '系统站内信通知'),
(10, 'SYSTEM_NOTICE_SMS', '系统通知短信', '【校园快递】系统通知', '【校园快递】${content}', 1, 2, 1, NOW(), NOW(), '系统短信通知');

-- 初始化通知数据
INSERT INTO `notification` (`id`, `title`, `content`, `type`, `channel`, `receiver_id`, `receiver_type`, `sender_id`, `sender_type`, `business_id`, `business_type`, `read_status`, `send_status`, `created_time`, `updated_time`, `remark`) VALUES
(1, '您的快递已到达', '尊敬的王五,您的快递(SF1001001)已到达校园驿站,取件码:123456,请及时取件。', 2, 1, 4, 1, NULL, 1, 1, 1, 1, 1, DATE_SUB(NOW(), INTERVAL 1 DAY), NOW(), '快递到达通知'),
(2, '您的快递正在配送中', '尊敬的王五,您的快递(YT1001002)正在配送中,预计送达时间:2小时后,配送员:张三,联系电话:13800138001。', 2, 1, 4, 1, NULL, 1, 2, 1, 0, 1, NOW(), NOW(), '快递配送通知'),
(3, '您的快递已签收', '尊敬的赵六,您的快递(ZT1001003)已签收,感谢您使用校园快递服务。', 2, 1, 5, 1, NULL, 1, 3, 1, 0, 1, DATE_SUB(NOW(), INTERVAL 3 HOUR), NOW(), '快递签收通知'),
(4, '您有新的配送任务', '尊敬的张三,您有新的配送任务(5),请及时查看并接单。', 3, 1, 2, 2, NULL, 1, 5, 2, 1, 1, NOW(), NOW(), '配送任务分配通知'),
(5, '系统通知:系统维护', '尊敬的用户,系统将于2025年4月10日凌晨2:00-4:00进行系统维护,期间服务可能暂时不可用,给您带来的不便敬请谅解。', 1, 1, 4, 1, NULL, 1, NULL, NULL, 0, 1, NOW(), NOW(), '系统维护通知'),
(6, '系统通知:系统维护', '尊敬的用户,系统将于2025年4月10日凌晨2:00-4:00进行系统维护,期间服务可能暂时不可用,给您带来的不便敬请谅解。', 1, 1, 5, 1, NULL, 1, NULL, NULL, 0, 1, NOW(), NOW(), '系统维护通知'),
(7, '系统通知:系统维护', '尊敬的配送员,系统将于2025年4月10日凌晨2:00-4:00进行系统维护,期间服务可能暂时不可用,给您带来的不便敬请谅解。', 1, 1, 2, 2, NULL, 1, NULL, NULL, 1, 1, NOW(), NOW(), '系统维护通知'),
(8, '系统通知:系统维护', '尊敬的配送员,系统将于2025年4月10日凌晨2:00-4:00进行系统维护,期间服务可能暂时不可用,给您带来的不便敬请谅解。', 1, 1, 3, 2, NULL, 1, NULL, NULL, 0, 1, NOW(), NOW(), '系统维护通知'),
(9, '系统通知:系统维护', '尊敬的管理员,系统将于2025年4月10日凌晨2:00-4:00进行系统维护,期间服务可能暂时不可用,请提前做好相关准备工作。', 1, 1, 1, 3, NULL, 1, NULL, NULL, 1, 1, NOW(), NOW(), '系统维护通知');

-- 初始化系统配置数据
INSERT INTO `system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `status`, `created_time`, `updated_time`, `remark`) VALUES
(1, 'system_name', '校园快递管理与配送系统', 'system', '系统名称', 1, NOW(), NOW(), '系统名称配置'),
(2, 'system_logo', '/static/logo.png', 'system', '系统Logo', 1, NOW(), NOW(), '系统Logo配置'),
(3, 'admin_email', '[email protected]', 'system', '管理员邮箱', 1, NOW(), NOW(), '管理员邮箱配置'),
(4, 'sms_enabled', 'true', 'notification', '是否启用短信通知', 1, NOW(), NOW(), '短信通知开关'),
(5, 'email_enabled', 'true', 'notification', '是否启用邮件通知', 1, NOW(), NOW(), '邮件通知开关'),
(6, 'push_enabled', 'true', 'notification', '是否启用推送通知', 1, NOW(), NOW(), '推送通知开关'),
(7, 'express_pickup_expiration_days', '7', 'express', '快递取件过期天数', 1, NOW(), NOW(), '快递取件过期天数配置'),
(8, 'delivery_timeout_minutes', '120', 'delivery', '配送超时时间(分钟)', 1, NOW(), NOW(), '配送超时时间配置'),
(9, 'max_delivery_count_per_day', '30', 'delivery', '配送员每日最大配送量', 1, NOW(), NOW(), '配送员每日最大配送量配置'),
(10, 'system_announcement', '欢迎使用校园快递管理与配送系统!', 'system', '系统公告', 1, NOW(), NOW(), '系统公告配置');

-- 初始化菜单数据
INSERT INTO `menu` (`id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `created_time`, `updated_time`, `remark`) VALUES
(1, '系统管理', 0, 1, 'system', NULL, 0, 0, 'M', 1, 1, '', 'system', NOW(), NOW(), '系统管理目录'),
(2, '用户管理', 1, 1, 'user', 'system/user/index', 0, 0, 'C', 1, 1, 'system:user:list', 'user', NOW(), NOW(), '用户管理菜单'),
(3, '角色管理', 1, 2, 'role', 'system/role/index', 0, 0, 'C', 1, 1, 'system:role:list', 'role', NOW(), NOW(), '角色管理菜单'),
(4, '菜单管理', 1, 3, 'menu', 'system/menu/index', 0, 0, 'C', 1, 1, 'system:menu:list', 'menu', NOW(), NOW(), '菜单管理菜单'),
(5, '配送管理', 0, 2, 'delivery', NULL, 0, 0, 'M', 1, 1, '', 'delivery', NOW(), NOW(), '配送管理目录'),
(6, '配送员管理', 5, 1, 'deliveryman', 'delivery/deliveryman/index', 0, 0, 'C', 1, 1, 'delivery:deliveryman:list', 'deliveryman', NOW(), NOW(), '配送员管理菜单'),
(7, '配送区域管理', 5, 2, 'area', 'delivery/area/index', 0, 0, 'C', 1, 1, 'delivery:area:list', 'area', NOW(), NOW(), '配送区域管理菜单'),
(8, '配送路线管理', 5, 3, 'route', 'delivery/route/index', 0, 0, 'C', 1, 1, 'delivery:route:list', 'route', NOW(), NOW(), '配送路线管理菜单'),
(9, '配送任务管理', 5, 4, 'task', 'delivery/task/index', 0, 0, 'C', 1, 1, 'delivery:task:list', 'task', NOW(), NOW(), '配送任务管理菜单'),
(10, '快递管理', 0, 3, 'express', NULL, 0, 0, 'M', 1, 1, '', 'express', NOW(), NOW(), '快递管理目录'),
(11, '快递管理', 10, 1, 'express', 'express/express/index', 0, 0, 'C', 1, 1, 'express:express:list', 'express', NOW(), NOW(), '快递管理菜单'),
(12, '快递跟踪', 10, 2, 'tracking', 'express/tracking/index', 0, 0, 'C', 1, 1, 'express:tracking:list', 'tracking', NOW(), NOW(), '快递跟踪菜单'),
(13, '通知管理', 0, 4, 'notification', NULL, 0, 0, 'M', 1, 1, '', 'notification', NOW(), NOW(), '通知管理目录'),
(14, '通知管理', 13, 1, 'notification', 'notification/notification/index', 0, 0, 'C', 1, 1, 'notification:notification:list', 'notification', NOW(), NOW(), '通知管理菜单'),
(15, '通知模板管理', 13, 2, 'template', 'notification/template/index', 0, 0, 'C', 1, 1, 'notification:template:list', 'template', NOW(), NOW(), '通知模板管理菜单'),
(16, '系统监控', 0, 5, 'monitor', NULL, 0, 0, 'M', 1, 1, '', 'monitor', NOW(), NOW(), '系统监控目录'),
(17, '操作日志', 16, 1, 'operlog', 'monitor/operlog/index', 0, 0, 'C', 1, 1, 'monitor:operlog:list', 'log', NOW(), NOW(), '操作日志菜单'),
(18, '登录日志', 16, 2, 'logininfor', 'monitor/logininfor/index', 0, 0, 'C', 1, 1, 'monitor:logininfor:list', 'logininfor', NOW(), NOW(), '登录日志菜单'),
(19, '系统配置', 1, 4, 'config', 'system/config/index', 0, 0, 'C', 1, 1, 'system:config:list', 'config', NOW(), NOW(), '系统配置菜单'),
(20, '文件管理', 1, 5, 'file', 'system/file/index', 0, 0, 'C', 1, 1, 'system:file:list', 'file', NOW(), NOW(), '文件管理菜单');

-- 初始化角色菜单关联数据
INSERT INTO `role_menu` (`role_id`, `menu_id`) VALUES
(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9), (1, 10), (1, 11), (1, 12), (1, 13), (1, 14), (1, 15), (1, 16), (1, 17), (1, 18), (1, 19), (1, 20),
(2, 5), (2, 9), (2, 10), (2, 11), (2, 12),
(3, 10), (3, 11), (3, 12);

express-service\src\main\resources\db\schema.sql

-- 校园快递管理与配送系统数据库表结构

-- 用户表
CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(100) NOT NULL COMMENT '密码',
  `salt` varchar(50) NOT NULL COMMENT '密码盐',
  `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
  `mobile` varchar(20) DEFAULT NULL COMMENT '手机号',
  `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
  `avatar` varchar(200) DEFAULT NULL COMMENT '头像URL',
  `gender` tinyint(1) DEFAULT '0' COMMENT '性别:0-未知,1-男,2-女',
  `student_id` varchar(50) DEFAULT NULL COMMENT '学号',
  `id_card` varchar(50) DEFAULT NULL COMMENT '身份证号',
  `address` varchar(200) DEFAULT NULL COMMENT '地址',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
  `type` tinyint(1) DEFAULT '1' COMMENT '用户类型:1-普通用户,2-配送员,3-管理员',
  `created_time` datetime NOT NULL COMMENT '创建时间',
  `updated_time` datetime NOT NULL COMMENT '更新时间',
  `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
  `remark` varchar(200) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`),
  UNIQUE KEY `uk_mobile` (`mobile`),
  UNIQUE KEY `uk_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

-- 配送员表
CREATE TABLE `deliveryman` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '配送员ID',
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `id_card_front` varchar(200) DEFAULT NULL COMMENT '身份证正面照片URL',
  `id_card_back` varchar(200) DEFAULT NULL COMMENT '身份证背面照片URL',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
  `work_status` tinyint(1) DEFAULT '0' COMMENT '工作状态:0-休息中,1-工作中',
  `score` decimal(3,1) DEFAULT '5.0' COMMENT '评分',
  `delivery_count` int(11) DEFAULT '0' COMMENT '配送次数',
  `created_time` datetime NOT NULL COMMENT '创建时间',
  `updated_time` datetime NOT NULL COMMENT '更新时间',
  `remark` varchar(200) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送员表';

-- 快递表
CREATE TABLE `express` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '快递ID',
  `express_no` varchar(50) NOT NULL COMMENT '快递单号',
  `express_company` varchar(50) NOT NULL COMMENT '快递公司',
  `sender_name` varchar(50) DEFAULT NULL COMMENT '寄件人姓名',
  `sender_mobile` varchar(20) DEFAULT NULL COMMENT '寄件人手机号',
  `sender_address` varchar(200) DEFAULT NULL COMMENT '寄件人地址',
  `receiver_name` varchar(50) NOT NULL COMMENT '收件人姓名',
  `receiver_mobile` varchar(20) NOT NULL COMMENT '收件人手机号',
  `receiver_address` varchar(200) NOT NULL COMMENT '收件人地址',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `weight` decimal(10,2) DEFAULT NULL COMMENT '重量(kg)',
  `express_type` tinyint(1) DEFAULT '1' COMMENT '快递类型:1-普通,2-易碎品,3-贵重物品',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:1-待揽收,2-已揽收,3-运输中,4-已到达,5-待配送,6-配送中,7-已签收,8-已取消',
  `arrival_time` datetime DEFAULT NULL COMMENT '到达时间',
  `pickup_code` varchar(10) DEFAULT NULL COMMENT '取件码',
  `remark` varchar(200) DEFAULT NULL COMMENT '备注',
  `created_time` datetime NOT NULL COMMENT '创建时间',
  `updated_time` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_express_no` (`express_no`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_status` (`status`),
  KEY `idx_arrival_time` (`arrival_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='快递表';

-- 配送区域表
CREATE TABLE `delivery_area` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '配送区域ID',
  `name` varchar(50) NOT NULL COMMENT '区域名称',
  `description` varchar(200) DEFAULT NULL COMMENT '区域描述',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
  `created_time` datetime NOT NULL COMMENT '创建时间',
  `updated_time` datetime NOT NULL COMMENT '更新时间',
  `remark` varchar(200) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送区域表';

-- 配送路线表
CREATE TABLE `delivery_route` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '配送路线ID',
  `name` varchar(50) NOT NULL COMMENT '路线名称',
  `area_id` bigint(20) NOT NULL COMMENT '配送区域ID',
  `description` varchar(200) DEFAULT NULL COMMENT '路线描述',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
  `created_time` datetime NOT NULL COMMENT '创建时间',
  `updated_time` datetime NOT NULL COMMENT '更新时间',
  `remark` varchar(200) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  KEY `idx_area_id` (`area_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送路线表';

-- 配送任务表
CREATE TABLE `delivery_task` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '配送任务ID',
  `express_id` bigint(20) NOT NULL COMMENT '快递ID',
  `deliveryman_id` bigint(20) DEFAULT NULL COMMENT '配送员ID',
  `route_id` bigint(20) DEFAULT NULL COMMENT '配送路线ID',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:1-待分配,2-待接单,3-已接单,4-配送中,5-已完成,6-已取消',
  `expected_delivery_time` datetime DEFAULT NULL COMMENT '预计配送时间',
  `actual_delivery_time` datetime DEFAULT NULL COMMENT '实际配送时间',
  `signature_image` varchar(200) DEFAULT NULL COMMENT '签收图片URL',
  `score` tinyint(1) DEFAULT NULL COMMENT '评分:1-5',
  `evaluation` varchar(500) DEFAULT NULL COMMENT '评价内容',
  `created_time` datetime NOT NULL COMMENT '创建时间',
  `updated_time` datetime NOT NULL COMMENT '更新时间',
  `remark` varchar(200) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_express_id` (`express_id`),
  KEY `idx_deliveryman_id` (`deliveryman_id`),
  KEY `idx_route_id` (`route_id`),
  KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送任务表';

-- 快递跟踪记录表
CREATE TABLE `express_tracking` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '跟踪记录ID',
  `express_id` bigint(20) NOT NULL COMMENT '快递ID',
  `status` tinyint(1) NOT NULL COMMENT '状态:1-待揽收,2-已揽收,3-运输中,4-已到达,5-待配送,6-配送中,7-已签收,8-已取消',
  `operator_id` bigint(20) DEFAULT NULL COMMENT '操作人ID',
  `operator_type` tinyint(1) DEFAULT NULL COMMENT '操作人类型:1-系统,2-用户,3-配送员,4-管理员',
  `operation_time` datetime NOT NULL COMMENT '操作时间',
  `location` varchar(200) DEFAULT NULL COMMENT '位置信息',
  `remark` varchar(200) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  KEY `idx_express_id` (`express_id`),
  KEY `idx_operation_time` (`operation_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='快递跟踪记录表';

-- 通知表
CREATE TABLE `notification` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '通知ID',
  `title` varchar(100) NOT NULL COMMENT '通知标题',
  `content` text NOT NULL COMMENT '通知内容',
  `type` tinyint(1) NOT NULL COMMENT '通知类型:1-系统通知,2-快递通知,3-配送通知,4-活动通知',
  `channel` tinyint(1) NOT NULL COMMENT '通知渠道:1-站内信,2-短信,3-邮件,4-推送',
  `receiver_id` bigint(20) NOT NULL COMMENT '接收者ID',
  `receiver_type` tinyint(1) NOT NULL COMMENT '接收者类型:1-用户,2-配送员,3-管理员',
  `sender_id` bigint(20) DEFAULT NULL COMMENT '发送者ID',
  `sender_type` tinyint(1) DEFAULT '1' COMMENT '发送者类型:1-系统,2-用户,3-配送员,4-管理员',
  `business_id` bigint(20) DEFAULT NULL COMMENT '关联业务ID',
  `business_type` tinyint(1) DEFAULT NULL COMMENT '关联业务类型:1-快递,2-配送,3-活动',
  `read_status` tinyint(1) DEFAULT '0' COMMENT '已读状态:0-未读,1-已读',
  `send_status` tinyint(1) DEFAULT '0' COMMENT '发送状态:0-未发送,1-已发送,2-发送失败',
  `fail_reason` varchar(200) DEFAULT NULL COMMENT '失败原因',
  `retry_count` int(11) DEFAULT '0' COMMENT '重试次数',
  `next_retry_time` datetime DEFAULT NULL COMMENT '下次重试时间',
  `created_time` datetime NOT NULL COMMENT '创建时间',
  `updated_time` datetime NOT NULL COMMENT '更新时间',
  `remark` varchar(200) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  KEY `idx_receiver` (`receiver_id`,`receiver_type`),
  KEY `idx_business` (`business_id`,`business_type`),
  KEY `idx_created_time` (`created_time`),
  KEY `idx_read_status` (`read_status`),
  KEY `idx_send_status` (`send_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='通知表';

-- 通知模板表
CREATE TABLE `notification_template` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '模板ID',
  `code` varchar(50) NOT NULL COMMENT '模板编码',
  `name` varchar(100) NOT NULL COMMENT '模板名称',
  `title` varchar(100) NOT NULL COMMENT '模板标题',
  `content` text NOT NULL COMMENT '模板内容',
  `type` tinyint(1) NOT NULL COMMENT '模板类型:1-系统通知,2-快递通知,3-配送通知,4-活动通知',
  `channel` tinyint(1) NOT NULL COMMENT '适用渠道:1-站内信,2-短信,3-邮件,4-推送',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-停用,1-启用',
  `created_time` datetime NOT NULL COMMENT '创建时间',
  `updated_time` datetime NOT NULL COMMENT '更新时间',
  `remark` varchar(200) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_code` (`code`),
  KEY `idx_type` (`type`),
  KEY `idx_channel` (`channel`),
  KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='通知模板表';

-- 系统配置表
CREATE TABLE `system_config` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '配置ID',
  `config_key` varchar(50) NOT NULL COMMENT '配置键',
  `config_value` varchar(500) NOT NULL COMMENT '配置值',
  `config_type` varchar(50) DEFAULT NULL COMMENT '配置类型',
  `description` varchar(200) DEFAULT NULL COMMENT '配置描述',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
  `created_time` datetime NOT NULL COMMENT '创建时间',
  `updated_time` datetime NOT NULL COMMENT '更新时间',
  `remark` varchar(200) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_config_key` (`config_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统配置表';

-- 操作日志表
CREATE TABLE `operation_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `username` varchar(50) DEFAULT NULL COMMENT '用户名',
  `operation` varchar(50) NOT NULL COMMENT '操作',
  `method` varchar(200) DEFAULT NULL COMMENT '方法名',
  `params` text COMMENT '请求参数',
  `result` text COMMENT '返回结果',
  `ip` varchar(50) DEFAULT NULL COMMENT 'IP地址',
  `user_agent` varchar(500) DEFAULT NULL COMMENT '用户代理',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-失败,1-成功',
  `error_msg` text COMMENT '错误信息',
  `operation_time` datetime NOT NULL COMMENT '操作时间',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_operation_time` (`operation_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='操作日志表';

-- 登录日志表
CREATE TABLE `login_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `username` varchar(50) DEFAULT NULL COMMENT '用户名',
  `ip` varchar(50) DEFAULT NULL COMMENT 'IP地址',
  `location` varchar(100) DEFAULT NULL COMMENT '登录地点',
  `browser` varchar(50) DEFAULT NULL COMMENT '浏览器',
  `os` varchar(50) DEFAULT NULL COMMENT '操作系统',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-失败,1-成功',
  `msg` varchar(200) DEFAULT NULL COMMENT '提示消息',
  `login_time` datetime NOT NULL COMMENT '登录时间',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_login_time` (`login_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='登录日志表';

-- 文件上传记录表
CREATE TABLE `file_upload` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '文件ID',
  `original_name` varchar(200) NOT NULL COMMENT '原始文件名',
  `file_name` varchar(200) NOT NULL COMMENT '存储文件名',
  `file_path` varchar(500) NOT NULL COMMENT '文件路径',
  `file_url` varchar(500) DEFAULT NULL COMMENT '文件URL',
  `file_size` bigint(20) NOT NULL COMMENT '文件大小(字节)',
  `file_type` varchar(50) DEFAULT NULL COMMENT '文件类型',
  `file_ext` varchar(20) DEFAULT NULL COMMENT '文件扩展名',
  `user_id` bigint(20) DEFAULT NULL COMMENT '上传用户ID',
  `business_type` varchar(50) DEFAULT NULL COMMENT '业务类型',
  `business_id` bigint(20) DEFAULT NULL COMMENT '业务ID',
  `created_time` datetime NOT NULL COMMENT '创建时间',
  `updated_time` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_business` (`business_type`,`business_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件上传记录表';

-- 角色表
CREATE TABLE `role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `role_name` varchar(50) NOT NULL COMMENT '角色名称',
  `role_key` varchar(50) NOT NULL COMMENT '角色权限字符串',
  `role_sort` int(11) DEFAULT NULL COMMENT '显示顺序',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
  `created_time` datetime NOT NULL COMMENT '创建时间',
  `updated_time` datetime NOT NULL COMMENT '更新时间',
  `remark` varchar(200) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_role_key` (`role_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';

-- 用户角色关联表
CREATE TABLE `user_role` (
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `role_id` bigint(20) NOT NULL COMMENT '角色ID',
  PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';

-- 菜单权限表
CREATE TABLE `menu` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
  `menu_name` varchar(50) NOT NULL COMMENT '菜单名称',
  `parent_id` bigint(20) DEFAULT '0' COMMENT '父菜单ID',
  `order_num` int(11) DEFAULT '0' COMMENT '显示顺序',
  `path` varchar(200) DEFAULT '' COMMENT '路由地址',
  `component` varchar(200) DEFAULT NULL COMMENT '组件路径',
  `is_frame` tinyint(1) DEFAULT '0' COMMENT '是否为外链:0-否,1-是',
  `is_cache` tinyint(1) DEFAULT '0' COMMENT '是否缓存:0-否,1-是',
  `menu_type` char(1) DEFAULT '' COMMENT '菜单类型:M-目录,C-菜单,F-按钮',
  `visible` tinyint(1) DEFAULT '1' COMMENT '菜单状态:0-隐藏,1-显示',
  `status` tinyint(1) DEFAULT '1' COMMENT '菜单状态:0-禁用,1-启用',
  `perms` varchar(100) DEFAULT NULL COMMENT '权限标识',
  `icon` varchar(100) DEFAULT '#' COMMENT '菜单图标',
  `created_time` datetime NOT NULL COMMENT '创建时间',
  `updated_time` datetime NOT NULL COMMENT '更新时间',
  `remark` varchar(200) DEFAULT '' COMMENT '备注',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜单权限表';

-- 角色菜单关联表
CREATE TABLE `role_menu` (
  `role_id` bigint(20) NOT NULL COMMENT '角色ID',
  `menu_id` bigint(20) NOT NULL COMMENT '菜单ID',
  PRIMARY KEY (`role_id`,`menu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色菜单关联表';

express-ui\package.json

{
  "name": "campus-express-ui",
  "version": "1.0.0",
  "private": true,
  "description": "校园快递管理与配送系统前端",
  "author": "Campus Express Team",
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "axios": "^0.21.1",
    "core-js": "^3.6.5",
    "element-ui": "^2.15.6",
    "js-cookie": "^3.0.1",
    "nprogress": "^0.2.0",
    "vue": "^2.6.11",
    "vue-router": "^3.2.0",
    "vuex": "^3.4.0"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-plugin-router": "~4.5.0",
    "@vue/cli-plugin-vuex": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "@vue/eslint-config-standard": "^5.1.2",
    "babel-eslint": "^10.1.0",
    "eslint": "^6.7.2",
    "eslint-plugin-import": "^2.20.2",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-standard": "^4.0.0",
    "eslint-plugin-vue": "^6.2.2",
    "sass": "^1.26.5",
    "sass-loader": "^8.0.2",
    "vue-template-compiler": "^2.6.11"
  }
}

express-ui\src\App.vue






express-ui\src\main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import './assets/styles/index.scss'
import './permission' // 权限控制
import NotificationPlugin from './plugins/notification' // 通知插件

Vue.use(ElementUI, { size: 'medium' })
Vue.use(NotificationPlugin) // 注册通知插件

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

express-ui\src\api\notification.js

import request from '@/utils/request'

// 通知列表
export function getNotificationList(query) {
  return request({
    url: '/notification/list',
    method: 'get',
    params: query
  })
}

// 获取通知详情
export function getNotification(id) {
  return request({
    url: `/notification/${id}`,
    method: 'get'
  })
}

// 添加通知
export function addNotification(data) {
  return request({
    url: '/notification',
    method: 'post',
    data
  })
}

// 更新通知
export function updateNotification(id, data) {
  return request({
    url: `/notification/${id}`,
    method: 'put',
    data
  })
}

// 删除通知
export function deleteNotification(id) {
  return request({
    url: `/notification/${id}`,
    method: 'delete'
  })
}

// 发送通知
export function sendNotification(data) {
  return request({
    url: '/notification/send',
    method: 'post',
    data
  })
}

// 模板列表
export function getTemplateList(query) {
  return request({
    url: '/notification/template/list',
    method: 'get',
    params: query
  })
}

// 获取模板详情
export function getTemplate(id) {
  return request({
    url: `/notification/template/${id}`,
    method: 'get'
  })
}

// 添加模板
export function addTemplate(data) {
  return request({
    url: '/notification/template',
    method: 'post',
    data
  })
}

// 更新模板
export function updateTemplate(id, data) {
  return request({
    url: `/notification/template/${id}`,
    method: 'put',
    data
  })
}

// 删除模板
export function deleteTemplate(id) {
  return request({
    url: `/notification/template/${id}`,
    method: 'delete'
  })
}

// 获取用户列表
export function getUserList(query) {
  return request({
    url: '/user/list',
    method: 'get',
    params: query
  })
}

// 获取用户组列表
export function getUserGroupList() {
  return request({
    url: '/user/group/list',
    method: 'get'
  })
}

// 获取通知发送记录
export function getSendRecords(notificationId) {
  return request({
    url: `/notification/send-records/${notificationId}`,
    method: 'get'
  })
}

// 获取通知阅读记录
export function getReadRecords(notificationId) {
  return request({
    url: `/notification/read-records/${notificationId}`,
    method: 'get'
  })
}

// 获取我的通知列表
export function getMyNotifications(query) {
  return request({
    url: '/notification/my-notifications',
    method: 'get',
    params: query
  })
}

// 标记通知为已读
export function markAsRead(id) {
  return request({
    url: `/notification/mark-read/${id}`,
    method: 'put'
  })
}

// 标记所有通知为已读
export function markAllAsRead() {
  return request({
    url: '/notification/mark-all-read',
    method: 'put'
  })
}

// 获取未读通知数量
export function getUnreadCount() {
  return request({
    url: '/notification/unread-count',
    method: 'get'
  })
}

// 获取通知设置
export function getNotificationSettings() {
  return request({
    url: '/notification/settings',
    method: 'get'
  })
}

// 更新通知设置
export function updateNotificationSettings(data) {
  return request({
    url: '/notification/settings',
    method: 'put',
    data
  })
}

// 发送测试通知
export function sendTestNotification() {
  return request({
    url: '/notification/send-test',
    method: 'post'
  })
}

// 获取通知统计数据
export function getNotificationStatistics(params) {
  return request({
    url: '/notification/statistics',
    method: 'get',
    params
  })
}

// 获取通知趋势数据
export function getNotificationTrend(params) {
  return request({
    url: '/notification/trend',
    method: 'get',
    params
  })
}

// 获取通知类型分布数据
export function getTypeDistribution(params) {
  return request({
    url: '/notification/type-distribution',
    method: 'get',
    params
  })
}

// 获取通知渠道分布数据
export function getChannelDistribution(params) {
  return request({
    url: '/notification/channel-distribution',
    method: 'get',
    params
  })
}

// 导出通知统计数据
export function exportNotificationStatistics(params) {
  return request({
    url: '/notification/export-statistics',
    method: 'get',
    params,
    responseType: 'blob'
  })
}

// 获取通知历史记录
export function getNotificationHistory(params) {
  return request({
    url: '/notification/history',
    method: 'get',
    params
  })
}

// 批量删除通知
export function batchDeleteNotifications(ids) {
  return request({
    url: '/notification/batch-delete',
    method: 'delete',
    data: { ids }
  })
}

// 回复通知
export function replyNotification(data) {
  return request({
    url: '/notification/reply',
    method: 'post',
    data
  })
}

// 下载通知附件
export function downloadAttachment(id) {
  return request({
    url: `/notification/attachment/${id}`,
    method: 'get',
    responseType: 'blob'
  })
}

// 测试模板
export function testTemplate(data) {
  return request({
    url: '/notification/template/test',
    method: 'post',
    data
  })
}

// 绑定微信
export function bindWechat() {
  return request({
    url: '/notification/bind-wechat',
    method: 'get'
  })
}

// 确认微信绑定
export function confirmWechatBind() {
  return request({
    url: '/notification/confirm-wechat-bind',
    method: 'post'
  })
}

// 解绑微信
export function unbindWechat() {
  return request({
    url: '/notification/unbind-wechat',
    method: 'delete'
  })
}

// 解绑APP设备
export function unbindApp() {
  return request({
    url: '/notification/unbind-app',
    method: 'delete'
  })
}

// 获取通知订阅列表
export function getSubscriptionList() {
  return request({
    url: '/notification/subscription/list',
    method: 'get'
  })
}

// 更新通知订阅
export function updateSubscription(data) {
  return request({
    url: '/notification/subscription',
    method: 'put',
    data
  })
}

express-ui\src\components\Notification\index.vue








express-ui\src\components\Notification\NotificationList.vue






express-ui\src\components\Notification\NotificationManager.js

import Vue from 'vue'
import { getUnreadCount, markAsRead } from '@/api/notification'
import { NotificationType } from '@/utils/notification'

// 创建一个Vue实例作为事件总线
const NotificationBus = new Vue()

// 通知管理器
const NotificationManager = {
  // 事件总线
  bus: NotificationBus,
  
  // 初始化
  init() {
    // 设置轮询定时器,定期检查新通知
    this.startPolling()
    
    // 监听页面可见性变化,当用户切换回页面时刷新通知
    document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this))
    
    // 初始化获取一次未读通知数量
    this.fetchUnreadCount()
    
    console.log('NotificationManager initialized')
  },
  
  // 销毁
  destroy() {
    // 清除轮询定时器
    this.stopPolling()
    
    // 移除事件监听
    document.removeEventListener('visibilitychange', this.handleVisibilityChange.bind(this))
    
    console.log('NotificationManager destroyed')
  },
  
  // 开始轮询
  startPolling() {
    // 清除可能存在的旧定时器
    this.stopPolling()
    
    // 创建新的定时器,每分钟检查一次
    this._pollingTimer = setInterval(() => {
      this.fetchUnreadCount()
    }, 60000)
  },
  
  // 停止轮询
  stopPolling() {
    if (this._pollingTimer) {
      clearInterval(this._pollingTimer)
      this._pollingTimer = null
    }
  },
  
  // 处理页面可见性变化
  handleVisibilityChange() {
    if (document.visibilityState === 'visible') {
      // 页面变为可见时,立即刷新通知
      this.fetchUnreadCount()
    }
  },
  
  // 获取未读通知数量
  fetchUnreadCount() {
    getUnreadCount().then(response => {
      // 实际项目中应该使用API返回的数据
      // const count = response.data
      
      // 模拟数据
      const count = Math.floor(Math.random() * 10)
      
      // 触发未读数量更新事件
      this.bus.$emit('unread-count-updated', count)
      
      // 如果有新通知且浏览器支持通知API,显示桌面通知
      if (count > 0 && this.isDesktopNotificationEnabled()) {
        this.showDesktopNotification('您有' + count + '条未读通知', '点击查看详情')
      }
    }).catch(error => {
      console.error('Failed to fetch unread count:', error)
    })
  },
  
  // 标记通知为已读
  markAsRead(notificationId) {
    return markAsRead(notificationId).then(() => {
      // 触发通知已读事件
      this.bus.$emit('notification-read', notificationId)
      
      // 重新获取未读数量
      this.fetchUnreadCount()
      
      return Promise.resolve()
    })
  },
  
  // 显示新通知
  showNotification(notification) {
    // 触发新通知事件
    this.bus.$emit('new-notification', notification)
    
    // 如果启用了桌面通知,显示桌面通知
    if (this.isDesktopNotificationEnabled()) {
      this.showDesktopNotification(notification.title, notification.content)
    }
    
    // 如果启用了声音提醒,播放提示音
    if (this.isSoundEnabled()) {
      this.playNotificationSound()
    }
  },
  
  // 检查是否启用了桌面通知
  isDesktopNotificationEnabled() {
    // 从本地存储获取设置
    return localStorage.getItem('enableDesktopNotification') === 'true'
  },
  
  // 检查是否启用了声音提醒
  isSoundEnabled() {
    // 从本地存储获取设置
    return localStorage.getItem('enableNotificationSound') === 'true'
  },
  
  // 显示桌面通知
  showDesktopNotification(title, body) {
    // 检查浏览器是否支持通知API
    if (!('Notification' in window)) {
      console.warn('This browser does not support desktop notifications')
      return
    }
    
    // 检查通知权限
    if (Notification.permission === 'granted') {
      // 已获得权限,直接显示通知
      this._createNotification(title, body)
    } else if (Notification.permission !== 'denied') {
      // 未获得权限且未被拒绝,请求权限
      Notification.requestPermission().then(permission => {
        if (permission === 'granted') {
          this._createNotification(title, body)
        }
      })
    }
  },
  
  // 创建通知
  _createNotification(title, body) {
    const notification = new Notification(title, {
      body: body,
      icon: '/favicon.ico'
    })
    
    // 点击通知时的行为
    notification.onclick = () => {
      // 激活窗口
      window.focus()
      
      // 关闭通知
      notification.close()
      
      // 触发通知点击事件
      this.bus.$emit('desktop-notification-clicked')
    }
    
    // 5秒后自动关闭
    setTimeout(() => {
      notification.close()
    }, 5000)
  },
  
  // 播放通知提示音
  playNotificationSound() {
    try {
      // 创建音频元素
      const audio = new Audio('/static/sounds/notification.mp3')
      
      // 播放
      audio.play().catch(error => {
        console.warn('Failed to play notification sound:', error)
      })
    } catch (error) {
      console.error('Error playing notification sound:', error)
    }
  },
  
  // 获取通知类型名称
  getTypeName(type) {
    switch (type) {
      case NotificationType.SYSTEM:
        return '系统通知'
      case NotificationType.EXPRESS:
        return '快递通知'
      case NotificationType.ACTIVITY:
        return '活动通知'
      default:
        return '通知'
    }
  },
  
  // 生成测试通知(仅用于开发测试)
  generateTestNotification() {
    const types = [
      NotificationType.SYSTEM,
      NotificationType.EXPRESS,
      NotificationType.ACTIVITY
    ]
    
    const type = types[Math.floor(Math.random() * types.length)]
    const id = Date.now()
    
    let title, content
    
    switch (type) {
      case NotificationType.SYSTEM:
        title = '系统维护通知'
        content = '系统将于今晚22:00-24:00进行例行维护,请提前做好准备。'
        break
      case NotificationType.EXPRESS:
        title = '新快递到达通知'
        content = '您有一个新的快递已到达校园快递中心,请及时领取。'
        break
      case NotificationType.ACTIVITY:
        title = '校园活动邀请'
        content = '诚邀您参加本周六下午的校园文化节活动,地点:中央广场。'
        break
      default:
        title = '新通知'
        content = '您有一条新的通知,请查看。'
    }
    
    const notification = {
      id,
      type,
      title,
      content,
      sender: '系统',
      sendTime: new Date().toISOString(),
      read: false
    }
    
    this.showNotification(notification)
    
    return notification
  }
}

export default NotificationManager

express-ui\src\layout\index.vue






express-ui\src\layout\components\AppMain.vue






express-ui\src\layout\components\Navbar.vue






express-ui\src\layout\components\Sidebar\index.vue




express-ui\src\layout\components\Sidebar\Item.vue




express-ui\src\layout\components\Sidebar\Link.vue




express-ui\src\layout\components\Sidebar\Logo.vue






express-ui\src\layout\components\Sidebar\SidebarItem.vue




express-ui\src\layout\components\TagsView\index.vue






express-ui\src\layout\components\TagsView\ScrollPane.vue






express-ui\src\plugins\notification.js

import NotificationManager from '@/components/Notification/NotificationManager'

// Vue插件:通知系统
const NotificationPlugin = {
  install(Vue) {
    // 初始化通知管理器
    NotificationManager.init()
    
    // 将通知管理器添加到Vue原型,使所有组件都能访问
    Vue.prototype.$notification = {
      // 获取事件总线
      get bus() {
        return NotificationManager.bus
      },
      
      // 显示通知
      show(notification) {
        NotificationManager.showNotification(notification)
      },
      
      // 标记为已读
      markAsRead(notificationId) {
        return NotificationManager.markAsRead(notificationId)
      },
      
      // 刷新未读数量
      refreshUnreadCount() {
        NotificationManager.fetchUnreadCount()
      },
      
      // 生成测试通知(仅用于开发测试)
      test() {
        return NotificationManager.generateTestNotification()
      },
      
      // 检查是否启用了桌面通知
      isDesktopNotificationEnabled() {
        return NotificationManager.isDesktopNotificationEnabled()
      },
      
      // 检查是否启用了声音提醒
      isSoundEnabled() {
        return NotificationManager.isSoundEnabled()
      },
      
      // 启用桌面通知
      enableDesktopNotification(enable = true) {
        localStorage.setItem('enableDesktopNotification', enable.toString())
        
        // 如果启用,请求通知权限
        if (enable && 'Notification' in window && Notification.permission !== 'granted' && Notification.permission !== 'denied') {
          Notification.requestPermission()
        }
      },
      
      // 启用声音提醒
      enableSound(enable = true) {
        localStorage.setItem('enableNotificationSound', enable.toString())
      }
    }
    
    // 在Vue实例销毁前清理通知管理器
    const destroyApp = Vue.prototype.$destroy
    Vue.prototype.$destroy = function() {
      NotificationManager.destroy()
      destroyApp.apply(this, arguments)
    }
  }
}

export default NotificationPlugin

express-ui\src\router\index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Layout from '@/layout'

Vue.use(VueRouter)

// 公共路由
export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },
  {
    path: '/404',
    component: () => import('@/views/error/404'),
    hidden: true
  },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [
      {
        path: 'dashboard',
        component: () => import('@/views/dashboard/index'),
        name: 'Dashboard',
        meta: { title: '首页', icon: 'dashboard', affix: true }
      }
    ]
  },
  // 通知管理路由
  {
    path: '/notification',
    component: Layout,
    redirect: '/notification/list',
    name: 'Notification',
    meta: { title: '通知管理', icon: 'notification' },
    children: [
      {
        path: 'list',
        component: () => import('@/views/notification/list'),
        name: 'NotificationList',
        meta: { title: '通知列表', icon: 'list' }
      },
      {
        path: 'template',
        component: () => import('@/views/notification/template'),
        name: 'NotificationTemplate',
        meta: { title: '通知模板', icon: 'template' }
      },
      {
        path: 'send',
        component: () => import('@/views/notification/send'),
        name: 'NotificationSend',
        meta: { title: '发送通知', icon: 'send' }
      },
      {
        path: 'detail/:id',
        component: () => import('@/views/notification/detail'),
        name: 'NotificationDetail',
        meta: { title: '通知详情', icon: 'detail' },
        hidden: true
      }
    ]
  },
  // 快递管理路由
  {
    path: '/express',
    component: Layout,
    redirect: '/express/list',
    name: 'Express',
    meta: { title: '快递管理', icon: 'express' },
    children: [
      {
        path: 'list',
        component: () => import('@/views/express/list'),
        name: 'ExpressList',
        meta: { title: '快递列表', icon: 'list' }
      },
      {
        path: 'tracking',
        component: () => import('@/views/express/tracking'),
        name: 'ExpressTracking',
        meta: { title: '快递跟踪', icon: 'tracking' }
      },
      {
        path: 'detail/:id',
        component: () => import('@/views/express/detail'),
        name: 'ExpressDetail',
        meta: { title: '快递详情', icon: 'detail' },
        hidden: true
      }
    ]
  },
  // 配送管理路由
  {
    path: '/delivery',
    component: Layout,
    redirect: '/delivery/task',
    name: 'Delivery',
    meta: { title: '配送管理', icon: 'delivery' },
    children: [
      {
        path: 'task',
        component: () => import('@/views/delivery/task'),
        name: 'DeliveryTask',
        meta: { title: '配送任务', icon: 'task' }
      },
      {
        path: 'area',
        component: () => import('@/views/delivery/area'),
        name: 'DeliveryArea',
        meta: { title: '配送区域', icon: 'area' }
      },
      {
        path: 'route',
        component: () => import('@/views/delivery/route'),
        name: 'DeliveryRoute',
        meta: { title: '配送路线', icon: 'route' }
      },
      {
        path: 'man',
        component: () => import('@/views/delivery/man'),
        name: 'DeliveryMan',
        meta: { title: '配送员管理', icon: 'deliveryman' }
      },
      {
        path: 'detail/:id',
        component: () => import('@/views/delivery/detail'),
        name: 'DeliveryDetail',
        meta: { title: '配送详情', icon: 'detail' },
        hidden: true
      }
    ]
  },
  // 系统管理路由
  {
    path: '/system',
    component: Layout,
    redirect: '/system/user',
    name: 'System',
    meta: { title: '系统管理', icon: 'system' },
    children: [
      {
        path: 'user',
        component: () => import('@/views/system/user'),
        name: 'User',
        meta: { title: '用户管理', icon: 'user' }
      },
      {
        path: 'role',
        component: () => import('@/views/system/role'),
        name: 'Role',
        meta: { title: '角色管理', icon: 'role' }
      },
      {
        path: 'menu',
        component: () => import('@/views/system/menu'),
        name: 'Menu',
        meta: { title: '菜单管理', icon: 'menu' }
      },
      {
        path: 'config',
        component: () => import('@/views/system/config'),
        name: 'Config',
        meta: { title: '参数设置', icon: 'config' }
      },
      {
        path: 'log',
        component: () => import('@/views/system/log'),
        name: 'Log',
        meta: { title: '操作日志', icon: 'log' }
      }
    ]
  },
  // 404 页面必须放在末尾
  { path: '*', redirect: '/404', hidden: true }
]

const createRouter = () => new VueRouter({
  mode: 'history',
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})

const router = createRouter()

// 重置路由
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher
}

export default router

express-ui\src\utils\notification.js

你可能感兴趣的:(Java,java,python,算法)