🛠️ 售后服务系统

# 🛠️ 售后服务系统

在跨境电商的完整交易链中,售后服务是维护客户关系、提升品牌价值的关键环节。当商品送达客户手中后,可能会遇到质量问题、尺寸不合适、或者客户不满意等情况。一个完善的售后服务系统能够快速响应客户需求,提供退换货、维修、投诉处理等服务,最终实现客户满意和品牌忠诚度的提升。

# 📋 系统概述

# 核心功能

  • 退换货管理:支持多种退换货场景和流程
  • 质量问题处理:快速响应和解决产品质量问题
  • 客户投诉管理:系统化处理客户投诉和建议
  • 保修服务管理:产品保修期内的维修服务
  • 补偿方案管理:灵活的补偿策略和执行
  • 客服工单系统:高效的客服工作流管理
  • 满意度调研:客户满意度跟踪和分析

# 🏗️ 系统架构设计

graph TB
    A[客户服务入口] --> B[售后服务网关]
    B --> C[退换货服务]
    B --> D[质量问题服务]
    B --> E[投诉管理服务]
    B --> F[保修服务]
    
    C --> G[退货审核]
    C --> H[换货处理]
    C --> I[退款处理]
    
    D --> J[质检报告]
    D --> K[问题分类]
    D --> L[解决方案]
    
    E --> M[投诉分级]
    E --> N[处理流程]
    E --> O[满意度跟踪]
    
    F --> P[保修验证]
    F --> Q[维修安排]
    F --> R[配件管理]
    
    S[工单系统] --> T[任务分配]
    S --> U[进度跟踪]
    S --> V[绩效统计]
    
    W[通知服务] --> X[邮件通知]
    W --> Y[短信通知]
    W --> Z[推送通知]
    
    AA[数据分析] --> BB[趋势分析]
    AA --> CC[问题统计]
    AA --> DD[客户画像]

# 💻 核心代码实现

# 1. 售后服务订单实体模型

@Entity
@Table(name = "after_sales_orders")
public class AfterSalesOrder {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "after_sales_number", unique = true, nullable = false)
    private String afterSalesNumber;
    
    @Column(name = "original_order_id", nullable = false)
    private String originalOrderId;
    
    @Column(name = "customer_id", nullable = false)
    private Long customerId;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "service_type")
    private AfterSalesType serviceType;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "status")
    private AfterSalesStatus status;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "reason")
    private AfterSalesReason reason;
    
    @Column(name = "description", length = 2000)
    private String description;
    
    @Column(name = "refund_amount", precision = 10, scale = 2)
    private BigDecimal refundAmount;
    
    @Column(name = "compensation_amount", precision = 10, scale = 2)
    private BigDecimal compensationAmount;
    
    @OneToMany(mappedBy = "afterSalesOrder", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<AfterSalesItem> items = new ArrayList<>();
    
    @OneToMany(mappedBy = "afterSalesOrder", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<AfterSalesEvidence> evidences = new ArrayList<>();
    
    @OneToMany(mappedBy = "afterSalesOrder", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<AfterSalesEvent> events = new ArrayList<>();
    
    @Column(name = "assigned_agent_id")
    private Long assignedAgentId;
    
    @Column(name = "priority_level")
    private Integer priorityLevel;
    
    @Column(name = "expected_resolution_date")
    private LocalDateTime expectedResolutionDate;
    
    @Column(name = "actual_resolution_date")
    private LocalDateTime actualResolutionDate;
    
    @Column(name = "customer_satisfaction_score")
    private Integer customerSatisfactionScore;
    
    @CreationTimestamp
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    @UpdateTimestamp
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    // 构造函数
    public AfterSalesOrder() {}
    
    public AfterSalesOrder(String originalOrderId, Long customerId, 
                          AfterSalesType serviceType, AfterSalesReason reason) {
        this.afterSalesNumber = generateAfterSalesNumber();
        this.originalOrderId = originalOrderId;
        this.customerId = customerId;
        this.serviceType = serviceType;
        this.reason = reason;
        this.status = AfterSalesStatus.PENDING;
        this.priorityLevel = calculatePriority(serviceType, reason);
    }
    
    // 添加售后事件
    public void addEvent(AfterSalesEventType eventType, String description, Long operatorId) {
        AfterSalesEvent event = new AfterSalesEvent(
                this, eventType, description, operatorId);
        this.events.add(event);
    }
    
    // 更新状态
    public void updateStatus(AfterSalesStatus newStatus, Long operatorId) {
        AfterSalesStatus oldStatus = this.status;
        this.status = newStatus;
        
        addEvent(AfterSalesEventType.STATUS_CHANGE, 
                String.format("状态从 %s 变更为 %s", oldStatus, newStatus), operatorId);
        
        if (newStatus == AfterSalesStatus.RESOLVED) {
            this.actualResolutionDate = LocalDateTime.now();
        }
    }
    
    // 计算优先级
    private Integer calculatePriority(AfterSalesType serviceType, AfterSalesReason reason) {
        // 质量问题和安全问题优先级最高
        if (reason == AfterSalesReason.QUALITY_ISSUE || 
            reason == AfterSalesReason.SAFETY_ISSUE) {
            return 1; // 最高优先级
        }
        
        // 退货退款次之
        if (serviceType == AfterSalesType.RETURN_REFUND) {
            return 2;
        }
        
        // 其他情况
        return 3;
    }
    
    // 生成售后单号
    private String generateAfterSalesNumber() {
        return "AS" + System.currentTimeMillis() + 
               String.format("%04d", new Random().nextInt(10000));
    }
    
    // getter和setter方法省略...
}

public enum AfterSalesType {
    RETURN_REFUND,    // 退货退款
    EXCHANGE,         // 换货
    REPAIR,           // 维修
    COMPLAINT,        // 投诉
    CONSULTATION      // 咨询
}

public enum AfterSalesStatus {
    PENDING,          // 待处理
    IN_PROGRESS,      // 处理中
    WAITING_CUSTOMER, // 等待客户
    RESOLVED,         // 已解决
    CLOSED,           // 已关闭
    ESCALATED         // 已升级
}

public enum AfterSalesReason {
    QUALITY_ISSUE,    // 质量问题
    SIZE_MISMATCH,    // 尺寸不符
    DESCRIPTION_MISMATCH, // 描述不符
    DAMAGED_IN_TRANSIT,   // 运输损坏
    NOT_AS_EXPECTED,      // 不符合预期
    SAFETY_ISSUE,         // 安全问题
    DEFECTIVE,            // 有缺陷
    OTHER                 // 其他
}

# 2. 售后服务核心业务逻辑

@Service
@Slf4j
public class AfterSalesService {
    
    @Autowired
    private AfterSalesOrderRepository afterSalesOrderRepository;
    
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private RefundService refundService;
    
    @Autowired
    private InventoryService inventoryService;
    
    @Autowired
    private NotificationService notificationService;
    
    @Autowired
    private WorkflowEngine workflowEngine;
    
    /**
     * 创建售后申请
     */
    @Transactional
    public AfterSalesOrder createAfterSalesRequest(AfterSalesRequest request) {
        log.info("创建售后申请 - 订单: {}, 类型: {}", 
                request.getOriginalOrderId(), request.getServiceType());
        
        // 1. 验证原订单
        Order originalOrder = orderService.getOrderById(request.getOriginalOrderId());
        validateOrderForAfterSales(originalOrder);
        
        // 2. 创建售后订单
        AfterSalesOrder afterSalesOrder = new AfterSalesOrder(
                request.getOriginalOrderId(),
                request.getCustomerId(),
                request.getServiceType(),
                request.getReason()
        );
        
        afterSalesOrder.setDescription(request.getDescription());
        
        // 3. 添加售后商品
        for (AfterSalesItemRequest itemRequest : request.getItems()) {
            AfterSalesItem item = new AfterSalesItem(
                    afterSalesOrder,
                    itemRequest.getProductId(),
                    itemRequest.getQuantity(),
                    itemRequest.getUnitPrice()
            );
            afterSalesOrder.getItems().add(item);
        }
        
        // 4. 添加证据材料
        for (String evidenceUrl : request.getEvidenceUrls()) {
            AfterSalesEvidence evidence = new AfterSalesEvidence(
                    afterSalesOrder, evidenceUrl, EvidenceType.IMAGE);
            afterSalesOrder.getEvidences().add(evidence);
        }
        
        // 5. 保存售后订单
        afterSalesOrder = afterSalesOrderRepository.save(afterSalesOrder);
        
        // 6. 启动工作流
        workflowEngine.startAfterSalesWorkflow(afterSalesOrder.getId());
        
        // 7. 发送通知
        notificationService.sendAfterSalesCreatedNotification(afterSalesOrder);
        
        log.info("售后申请创建成功 - 售后单号: {}", afterSalesOrder.getAfterSalesNumber());
        
        return afterSalesOrder;
    }
    
    /**
     * 处理退货退款
     */
    @Transactional
    public void processReturnRefund(Long afterSalesOrderId, ReturnRefundDecision decision) {
        AfterSalesOrder afterSalesOrder = getAfterSalesOrderById(afterSalesOrderId);
        
        if (afterSalesOrder.getServiceType() != AfterSalesType.RETURN_REFUND) {
            throw new AfterSalesException("售后类型不匹配");
        }
        
        if (decision.isApproved()) {
            // 批准退货退款
            approveReturnRefund(afterSalesOrder, decision);
        } else {
            // 拒绝退货退款
            rejectReturnRefund(afterSalesOrder, decision.getRejectionReason());
        }
    }
    
    /**
     * 批准退货退款
     */
    private void approveReturnRefund(AfterSalesOrder afterSalesOrder, 
                                   ReturnRefundDecision decision) {
        log.info("批准退货退款 - 售后单号: {}", afterSalesOrder.getAfterSalesNumber());
        
        // 1. 更新状态
        afterSalesOrder.updateStatus(AfterSalesStatus.IN_PROGRESS, decision.getOperatorId());
        
        // 2. 计算退款金额
        BigDecimal refundAmount = calculateRefundAmount(afterSalesOrder, decision);
        afterSalesOrder.setRefundAmount(refundAmount);
        
        // 3. 生成退货标签
        String returnLabel = generateReturnLabel(afterSalesOrder);
        
        // 4. 发送退货指引
        notificationService.sendReturnInstructions(
                afterSalesOrder.getCustomerId(), returnLabel);
        
        // 5. 创建退款预处理
        refundService.createPendingRefund(
                afterSalesOrder.getOriginalOrderId(),
                refundAmount,
                "退货退款"
        );
        
        afterSalesOrderRepository.save(afterSalesOrder);
    }
    
    /**
     * 处理换货申请
     */
    @Transactional
    public void processExchange(Long afterSalesOrderId, ExchangeDecision decision) {
        AfterSalesOrder afterSalesOrder = getAfterSalesOrderById(afterSalesOrderId);
        
        if (afterSalesOrder.getServiceType() != AfterSalesType.EXCHANGE) {
            throw new AfterSalesException("售后类型不匹配");
        }
        
        if (decision.isApproved()) {
            approveExchange(afterSalesOrder, decision);
        } else {
            rejectExchange(afterSalesOrder, decision.getRejectionReason());
        }
    }
    
    /**
     * 批准换货
     */
    private void approveExchange(AfterSalesOrder afterSalesOrder, ExchangeDecision decision) {
        log.info("批准换货 - 售后单号: {}", afterSalesOrder.getAfterSalesNumber());
        
        // 1. 检查库存
        for (AfterSalesItem item : afterSalesOrder.getItems()) {
            boolean hasStock = inventoryService.checkStock(
                    decision.getNewProductId(), item.getQuantity());
            if (!hasStock) {
                throw new AfterSalesException("换货商品库存不足");
            }
        }
        
        // 2. 预留库存
        for (AfterSalesItem item : afterSalesOrder.getItems()) {
            inventoryService.reserveStock(
                    decision.getNewProductId(), item.getQuantity());
        }
        
        // 3. 更新状态
        afterSalesOrder.updateStatus(AfterSalesStatus.IN_PROGRESS, decision.getOperatorId());
        
        // 4. 生成换货订单
        String exchangeOrderId = createExchangeOrder(afterSalesOrder, decision);
        
        // 5. 发送换货通知
        notificationService.sendExchangeApprovedNotification(
                afterSalesOrder.getCustomerId(), exchangeOrderId);
        
        afterSalesOrderRepository.save(afterSalesOrder);
    }
    
    /**
     * 处理质量问题
     */
    @Transactional
    public void processQualityIssue(Long afterSalesOrderId, QualityIssueDecision decision) {
        AfterSalesOrder afterSalesOrder = getAfterSalesOrderById(afterSalesOrderId);
        
        // 1. 记录质量问题
        QualityIssueRecord qualityRecord = new QualityIssueRecord(
                afterSalesOrder.getOriginalOrderId(),
                afterSalesOrder.getReason(),
                afterSalesOrder.getDescription(),
                decision.getSeverityLevel()
        );
        
        // 2. 根据严重程度处理
        switch (decision.getSeverityLevel()) {
            case CRITICAL:
                handleCriticalQualityIssue(afterSalesOrder, decision);
                break;
            case HIGH:
                handleHighQualityIssue(afterSalesOrder, decision);
                break;
            case MEDIUM:
                handleMediumQualityIssue(afterSalesOrder, decision);
                break;
            case LOW:
                handleLowQualityIssue(afterSalesOrder, decision);
                break;
        }
        
        // 3. 通知质量管理部门
        notificationService.sendQualityIssueAlert(qualityRecord);
    }
    
    /**
     * 处理严重质量问题
     */
    private void handleCriticalQualityIssue(AfterSalesOrder afterSalesOrder, 
                                           QualityIssueDecision decision) {
        // 1. 立即全额退款
        BigDecimal fullRefundAmount = calculateFullRefundAmount(afterSalesOrder);
        afterSalesOrder.setRefundAmount(fullRefundAmount);
        
        // 2. 提供额外补偿
        BigDecimal compensationAmount = fullRefundAmount.multiply(new BigDecimal("0.2"));
        afterSalesOrder.setCompensationAmount(compensationAmount);
        
        // 3. 启动产品召回流程
        initiateProductRecall(afterSalesOrder);
        
        // 4. 升级到高级管理层
        escalateToManagement(afterSalesOrder, "严重质量问题");
        
        afterSalesOrder.updateStatus(AfterSalesStatus.ESCALATED, decision.getOperatorId());
    }
    
    /**
     * 自动分配客服代表
     */
    public void autoAssignAgent(Long afterSalesOrderId) {
        AfterSalesOrder afterSalesOrder = getAfterSalesOrderById(afterSalesOrderId);
        
        // 根据优先级和客服工作负载分配
        Long agentId = findBestAvailableAgent(
                afterSalesOrder.getPriorityLevel(),
                afterSalesOrder.getServiceType()
        );
        
        if (agentId != null) {
            afterSalesOrder.setAssignedAgentId(agentId);
            afterSalesOrder.addEvent(AfterSalesEventType.AGENT_ASSIGNED, 
                    "系统自动分配客服代表: " + agentId, null);
            
            afterSalesOrderRepository.save(afterSalesOrder);
            
            // 通知客服代表
            notificationService.sendAgentAssignmentNotification(agentId, afterSalesOrderId);
        }
    }
    
    /**
     * 查找最佳可用客服代表
     */
    private Long findBestAvailableAgent(Integer priorityLevel, AfterSalesType serviceType) {
        // 实现客服代表分配算法
        // 考虑因素:工作负载、专业技能、在线状态等
        return null; // 简化实现
    }
    
    // 其他辅助方法省略...
}

### 4. 客户满意度管理

```java
@Service
@Slf4j
public class CustomerSatisfactionService {
    
    @Autowired
    private SatisfactionSurveyRepository surveyRepository;
    
    @Autowired
    private AfterSalesOrderRepository afterSalesOrderRepository;
    
    @Autowired
    private NotificationService notificationService;
    
    @Autowired
    private AnalyticsService analyticsService;
    
    /**
     * 发送满意度调研
     */
    public void sendSatisfactionSurvey(Long afterSalesOrderId) {
        AfterSalesOrder afterSalesOrder = afterSalesOrderRepository
                .findById(afterSalesOrderId)
                .orElseThrow(() -> new AfterSalesException("售后订单不存在"));
        
        if (afterSalesOrder.getStatus() != AfterSalesStatus.RESOLVED) {
            throw new AfterSalesException("只能对已解决的售后订单发送满意度调研");
        }
        
        // 创建满意度调研
        SatisfactionSurvey survey = new SatisfactionSurvey();
        survey.setAfterSalesOrderId(afterSalesOrderId);
        survey.setCustomerId(afterSalesOrder.getCustomerId());
        survey.setSurveyToken(generateSurveyToken());
        survey.setStatus(SurveyStatus.SENT);
        survey.setExpiresAt(LocalDateTime.now().plusDays(7));
        
        survey = surveyRepository.save(survey);
        
        // 发送调研邮件
        notificationService.sendSatisfactionSurveyEmail(
                afterSalesOrder.getCustomerId(), survey.getSurveyToken());
        
        log.info("已发送满意度调研 - 售后单号: {}, 调研ID: {}", 
                afterSalesOrder.getAfterSalesNumber(), survey.getId());
    }
    
    /**
     * 提交满意度评价
     */
    @Transactional
    public void submitSatisfactionRating(String surveyToken, SatisfactionRatingRequest request) {
        SatisfactionSurvey survey = surveyRepository.findBySurveyToken(surveyToken)
                .orElseThrow(() -> new SurveyNotFoundException("调研不存在或已过期"));
        
        if (survey.getStatus() != SurveyStatus.SENT) {
            throw new SurveyException("调研状态不正确");
        }
        
        if (survey.getExpiresAt().isBefore(LocalDateTime.now())) {
            throw new SurveyException("调研已过期");
        }
        
        // 更新调研结果
        survey.setOverallRating(request.getOverallRating());
        survey.setServiceQualityRating(request.getServiceQualityRating());
        survey.setResponseTimeRating(request.getResponseTimeRating());
        survey.setProfessionalismRating(request.getProfessionalismRating());
        survey.setResolutionRating(request.getResolutionRating());
        survey.setComments(request.getComments());
        survey.setStatus(SurveyStatus.COMPLETED);
        survey.setCompletedAt(LocalDateTime.now());
        
        surveyRepository.save(survey);
        
        // 更新售后订单的满意度评分
        AfterSalesOrder afterSalesOrder = afterSalesOrderRepository
                .findById(survey.getAfterSalesOrderId())
                .orElseThrow(() -> new AfterSalesException("售后订单不存在"));
        
        afterSalesOrder.setCustomerSatisfactionScore(request.getOverallRating());
        afterSalesOrderRepository.save(afterSalesOrder);
        
        // 如果评分较低,触发改进流程
        if (request.getOverallRating() <= 3) {
            triggerImprovementProcess(survey, afterSalesOrder);
        }
        
        // 更新客服代表的绩效
        if (afterSalesOrder.getAssignedAgentId() != null) {
            updateAgentPerformance(afterSalesOrder.getAssignedAgentId(), request);
        }
        
        log.info("满意度评价已提交 - 调研ID: {}, 总体评分: {}", 
                survey.getId(), request.getOverallRating());
    }
    
    /**
     * 触发改进流程
     */
    private void triggerImprovementProcess(SatisfactionSurvey survey, AfterSalesOrder afterSalesOrder) {
        // 创建改进任务
        ImprovementTask task = new ImprovementTask();
        task.setAfterSalesOrderId(afterSalesOrder.getId());
        task.setSurveyId(survey.getId());
        task.setIssueDescription(survey.getComments());
        task.setPriority(calculateImprovementPriority(survey.getOverallRating()));
        task.setStatus(ImprovementStatus.PENDING);
        
        // 分配给质量管理团队
        task.setAssignedTeam("QUALITY_MANAGEMENT");
        
        // 发送改进通知
        notificationService.sendImprovementTaskNotification(task);
        
        log.warn("触发改进流程 - 售后单号: {}, 满意度评分: {}", 
                afterSalesOrder.getAfterSalesNumber(), survey.getOverallRating());
    }
    
    /**
     * 更新客服代表绩效
     */
    private void updateAgentPerformance(Long agentId, SatisfactionRatingRequest rating) {
        AgentPerformance performance = getOrCreateAgentPerformance(agentId);
        
        // 更新满意度统计
        performance.addSatisfactionRating(
                rating.getOverallRating(),
                rating.getServiceQualityRating(),
                rating.getResponseTimeRating(),
                rating.getProfessionalismRating(),
                rating.getResolutionRating()
        );
        
        // 计算新的绩效评分
        performance.calculatePerformanceScore();
        
        // 如果绩效持续下降,触发培训计划
        if (performance.getPerformanceScore() < 3.0) {
            triggerTrainingPlan(agentId, performance);
        }
    }
    
    /**
     * 生成满意度报告
     */
    public SatisfactionReport generateSatisfactionReport(LocalDate startDate, LocalDate endDate) {
        List<SatisfactionSurvey> surveys = surveyRepository
                .findCompletedSurveysBetween(startDate.atStartOfDay(), endDate.atTime(23, 59, 59));
        
        SatisfactionReport report = new SatisfactionReport();
        report.setPeriod(startDate + " to " + endDate);
        report.setTotalSurveys(surveys.size());
        
        if (!surveys.isEmpty()) {
            // 计算平均评分
            double avgOverallRating = surveys.stream()
                    .mapToInt(SatisfactionSurvey::getOverallRating)
                    .average().orElse(0.0);
            report.setAverageOverallRating(avgOverallRating);
            
            double avgServiceQuality = surveys.stream()
                    .mapToInt(SatisfactionSurvey::getServiceQualityRating)
                    .average().orElse(0.0);
            report.setAverageServiceQualityRating(avgServiceQuality);
            
            double avgResponseTime = surveys.stream()
                    .mapToInt(SatisfactionSurvey::getResponseTimeRating)
                    .average().orElse(0.0);
            report.setAverageResponseTimeRating(avgResponseTime);
            
            // 计算满意度分布
            Map<Integer, Long> ratingDistribution = surveys.stream()
                    .collect(Collectors.groupingBy(
                            SatisfactionSurvey::getOverallRating,
                            Collectors.counting()
                    ));
            report.setRatingDistribution(ratingDistribution);
            
            // 分析评论关键词
            List<String> keywordAnalysis = analyzeCommentKeywords(surveys);
            report.setCommentKeywords(keywordAnalysis);
        }
        
        return report;
    }
    
    // 其他辅助方法省略...
}

# 5. 数据分析服务

@Service
public class AfterSalesAnalyticsService {
    
    @Autowired
    private AfterSalesOrderRepository afterSalesOrderRepository;
    
    @Autowired
    private SatisfactionSurveyRepository surveyRepository;
    
    @Autowired
    private TicketRepository ticketRepository;
    
    /**
     * 售后趋势分析
     */
    public AfterSalesTrendAnalysis analyzeTrends(LocalDate startDate, LocalDate endDate) {
        List<AfterSalesOrder> orders = afterSalesOrderRepository
                .findByCreatedAtBetween(startDate.atStartOfDay(), endDate.atTime(23, 59, 59));
        
        AfterSalesTrendAnalysis analysis = new AfterSalesTrendAnalysis();
        analysis.setPeriod(startDate + " to " + endDate);
        
        // 按类型统计
        Map<AfterSalesType, Long> typeDistribution = orders.stream()
                .collect(Collectors.groupingBy(
                        AfterSalesOrder::getServiceType,
                        Collectors.counting()
                ));
        analysis.setTypeDistribution(typeDistribution);
        
        // 按原因统计
        Map<AfterSalesReason, Long> reasonDistribution = orders.stream()
                .collect(Collectors.groupingBy(
                        AfterSalesOrder::getReason,
                        Collectors.counting()
                ));
        analysis.setReasonDistribution(reasonDistribution);
        
        // 按状态统计
        Map<AfterSalesStatus, Long> statusDistribution = orders.stream()
                .collect(Collectors.groupingBy(
                        AfterSalesOrder::getStatus,
                        Collectors.counting()
                ));
        analysis.setStatusDistribution(statusDistribution);
        
        // 计算解决时间统计
        List<Long> resolutionTimes = orders.stream()
                .filter(order -> order.getActualResolutionDate() != null)
                .map(order -> ChronoUnit.HOURS.between(
                        order.getCreatedAt(), order.getActualResolutionDate()))
                .collect(Collectors.toList());
        
        if (!resolutionTimes.isEmpty()) {
            analysis.setAverageResolutionTimeHours(
                    resolutionTimes.stream().mapToLong(Long::longValue).average().orElse(0.0));
            analysis.setMedianResolutionTimeHours(
                    calculateMedian(resolutionTimes));
        }
        
        return analysis;
    }
    
    /**
     * 客服绩效分析
     */
    public AgentPerformanceAnalysis analyzeAgentPerformance(Long agentId, 
                                                           LocalDate startDate, LocalDate endDate) {
        // 获取客服处理的售后订单
        List<AfterSalesOrder> agentOrders = afterSalesOrderRepository
                .findByAssignedAgentIdAndCreatedAtBetween(
                        agentId, startDate.atStartOfDay(), endDate.atTime(23, 59, 59));
        
        // 获取客服处理的工单
        List<Ticket> agentTickets = ticketRepository
                .findByAssignedAgentIdAndCreatedAtBetween(
                        agentId, startDate.atStartOfDay(), endDate.atTime(23, 59, 59));
        
        AgentPerformanceAnalysis analysis = new AgentPerformanceAnalysis();
        analysis.setAgentId(agentId);
        analysis.setPeriod(startDate + " to " + endDate);
        
        // 处理数量统计
        analysis.setTotalAfterSalesOrders(agentOrders.size());
        analysis.setTotalTickets(agentTickets.size());
        
        // 解决率统计
        long resolvedOrders = agentOrders.stream()
                .mapToLong(order -> order.getStatus() == AfterSalesStatus.RESOLVED ? 1 : 0)
                .sum();
        analysis.setResolutionRate(
                agentOrders.isEmpty() ? 0.0 : (double) resolvedOrders / agentOrders.size() * 100);
        
        // 平均处理时间
        double avgResolutionTime = agentOrders.stream()
                .filter(order -> order.getActualResolutionDate() != null)
                .mapToLong(order -> ChronoUnit.HOURS.between(
                        order.getCreatedAt(), order.getActualResolutionDate()))
                .average().orElse(0.0);
        analysis.setAverageResolutionTimeHours(avgResolutionTime);
        
        // 客户满意度统计
        List<Integer> satisfactionScores = agentOrders.stream()
                .filter(order -> order.getCustomerSatisfactionScore() != null)
                .map(AfterSalesOrder::getCustomerSatisfactionScore)
                .collect(Collectors.toList());
        
        if (!satisfactionScores.isEmpty()) {
            analysis.setAverageSatisfactionScore(
                    satisfactionScores.stream().mapToInt(Integer::intValue).average().orElse(0.0));
        }
        
        return analysis;
    }
    
    /**
     * 产品质量分析
     */
    public ProductQualityAnalysis analyzeProductQuality(String productId, 
                                                       LocalDate startDate, LocalDate endDate) {
        // 获取产品相关的售后订单
        List<AfterSalesOrder> productOrders = afterSalesOrderRepository
                .findByProductIdAndCreatedAtBetween(
                        productId, startDate.atStartOfDay(), endDate.atTime(23, 59, 59));
        
        ProductQualityAnalysis analysis = new ProductQualityAnalysis();
        analysis.setProductId(productId);
        analysis.setPeriod(startDate + " to " + endDate);
        
        // 售后率计算(需要结合销售数据)
        // 这里简化处理,实际需要从订单系统获取销售数据
        analysis.setTotalAfterSalesCount(productOrders.size());
        
        // 问题类型分布
        Map<AfterSalesReason, Long> issueDistribution = productOrders.stream()
                .collect(Collectors.groupingBy(
                        AfterSalesOrder::getReason,
                        Collectors.counting()
                ));
        analysis.setIssueDistribution(issueDistribution);
        
        // 质量问题严重程度分析
        long qualityIssues = productOrders.stream()
                .mapToLong(order -> 
                        (order.getReason() == AfterSalesReason.QUALITY_ISSUE || 
                         order.getReason() == AfterSalesReason.DEFECTIVE) ? 1 : 0)
                .sum();
        analysis.setQualityIssueCount(qualityIssues);
        
        // 客户满意度统计
        List<Integer> satisfactionScores = productOrders.stream()
                .filter(order -> order.getCustomerSatisfactionScore() != null)
                .map(AfterSalesOrder::getCustomerSatisfactionScore)
                .collect(Collectors.toList());
        
        if (!satisfactionScores.isEmpty()) {
            analysis.setAverageSatisfactionScore(
                    satisfactionScores.stream().mapToInt(Integer::intValue).average().orElse(0.0));
        }
        
        return analysis;
    }
    
    /**
     * 成本效益分析
     */
    public CostBenefitAnalysis analyzeCostBenefit(LocalDate startDate, LocalDate endDate) {
        List<AfterSalesOrder> orders = afterSalesOrderRepository
                .findByCreatedAtBetween(startDate.atStartOfDay(), endDate.atTime(23, 59, 59));
        
        CostBenefitAnalysis analysis = new CostBenefitAnalysis();
        analysis.setPeriod(startDate + " to " + endDate);
        
        // 计算总成本
        BigDecimal totalRefundAmount = orders.stream()
                .filter(order -> order.getRefundAmount() != null)
                .map(AfterSalesOrder::getRefundAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        
        BigDecimal totalCompensationAmount = orders.stream()
                .filter(order -> order.getCompensationAmount() != null)
                .map(AfterSalesOrder::getCompensationAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        
        analysis.setTotalRefundAmount(totalRefundAmount);
        analysis.setTotalCompensationAmount(totalCompensationAmount);
        analysis.setTotalCost(totalRefundAmount.add(totalCompensationAmount));
        
        // 计算人力成本(简化计算)
        long totalProcessingHours = orders.stream()
                .filter(order -> order.getActualResolutionDate() != null)
                .mapToLong(order -> ChronoUnit.HOURS.between(
                        order.getCreatedAt(), order.getActualResolutionDate()))
                .sum();
        
        BigDecimal laborCost = BigDecimal.valueOf(totalProcessingHours)
                .multiply(new BigDecimal("50")); // 假设每小时成本50元
        analysis.setLaborCost(laborCost);
        
        // 计算客户保留价值(需要结合客户生命周期价值)
        long retainedCustomers = calculateRetainedCustomers(orders);
        analysis.setRetainedCustomers(retainedCustomers);
        
        return analysis;
    }
    
    // 其他辅助方法省略...
}

# 🔧 技术亮点

# 1. 智能工单分配

  • 负载均衡算法:基于客服工作负载和技能匹配的智能分配
  • 优先级管理:多维度优先级计算和动态调整
  • SLA监控:实时监控服务水平协议执行情况

# 2. 自动化处理流程

  • 规则引擎:基于业务规则的自动化决策
  • 工作流引擎:灵活的售后处理工作流配置
  • 异常升级机制:自动识别和升级复杂问题

# 3. 客户满意度管理

  • 多维度评价:全方位的服务质量评估
  • 实时反馈:即时的客户满意度收集
  • 改进闭环:基于反馈的持续改进机制

# ⚡ 性能优化

# 1. 缓存策略

@Configuration
@EnableCaching
public class AfterSalesCacheConfig {
    
    @Bean
    public CacheManager afterSalesCacheManager() {
        RedisCacheManager.Builder builder = RedisCacheManager
                .RedisCacheManagerBuilder
                .fromConnectionFactory(redisConnectionFactory())
                .cacheDefaults(afterSalesCacheConfiguration());
        
        return builder.build();
    }
    
    private RedisCacheConfiguration afterSalesCacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(2))
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }
    
    // 客服代表缓存配置
    @Bean
    public RedisCacheConfiguration agentCacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(30))
                .prefixCacheNameWith("after-sales:agent:");
    }
    
    // 工单缓存配置
    @Bean
    public RedisCacheConfiguration ticketCacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(1))
                .prefixCacheNameWith("after-sales:ticket:");
    }
}

# 2. 异步处理配置

@Configuration
@EnableAsync
public class AfterSalesAsyncConfig {
    
    @Bean(name = "afterSalesTaskExecutor")
    public TaskExecutor afterSalesTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(8);
        executor.setMaxPoolSize(32);
        executor.setQueueCapacity(150);
        executor.setThreadNamePrefix("after-sales-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
    
    @Bean(name = "notificationTaskExecutor")
    public TaskExecutor notificationTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(16);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("notification-");
        executor.initialize();
        return executor;
    }
}

# 📊 监控与分析

# 1. 售后服务监控指标

@Component
public class AfterSalesMetrics {
    
    private final MeterRegistry meterRegistry;
    private final Counter afterSalesCreatedCounter;
    private final Counter afterSalesResolvedCounter;
    private final Timer resolutionTimeTimer;
    private final Gauge pendingOrdersGauge;
    
    public AfterSalesMetrics(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        
        this.afterSalesCreatedCounter = Counter.builder("after_sales.orders.created")
                .description("创建的售后订单数量")
                .register(meterRegistry);
        
        this.afterSalesResolvedCounter = Counter.builder("after_sales.orders.resolved")
                .description("解决的售后订单数量")
                .register(meterRegistry);
        
        this.resolutionTimeTimer = Timer.builder("after_sales.resolution.time")
                .description("售后问题解决时间")
                .register(meterRegistry);
        
        this.pendingOrdersGauge = Gauge.builder("after_sales.orders.pending")
                .description("待处理售后订单数量")
                .register(meterRegistry, this, AfterSalesMetrics::getPendingOrdersCount);
    }
    
    public void recordAfterSalesCreated(AfterSalesType type, AfterSalesReason reason) {
        afterSalesCreatedCounter.increment(
                Tags.of(
                        "type", type.name(),
                        "reason", reason.name()
                )
        );
    }
    
    public void recordAfterSalesResolved(AfterSalesType type, Duration resolutionTime) {
        afterSalesResolvedCounter.increment(Tags.of("type", type.name()));
        resolutionTimeTimer.record(resolutionTime, Tags.of("type", type.name()));
    }
    
    public void recordCustomerSatisfaction(int satisfactionScore, AfterSalesType type) {
        Gauge.builder("after_sales.customer.satisfaction")
                .description("客户满意度评分")
                .tags("type", type.name())
                .register(meterRegistry, satisfactionScore, score -> score);
    }
    
    private double getPendingOrdersCount() {
        // 实现获取待处理订单数量的逻辑
        return 0.0;
    }
}

# 2. 实时监控面板

@RestController
@RequestMapping("/api/after-sales/dashboard")
public class AfterSalesDashboardController {
    
    @Autowired
    private AfterSalesAnalyticsService analyticsService;
    
    @Autowired
    private AfterSalesOrderRepository afterSalesOrderRepository;
    
    @Autowired
    private TicketRepository ticketRepository;
    
    /**
     * 获取实时监控数据
     */
    @GetMapping("/realtime")
    public RealtimeDashboard getRealtimeDashboard() {
        RealtimeDashboard dashboard = new RealtimeDashboard();
        
        // 今日统计
        LocalDate today = LocalDate.now();
        dashboard.setTodayCreated(afterSalesOrderRepository
                .countByCreatedAtBetween(today.atStartOfDay(), today.atTime(23, 59, 59)));
        
        dashboard.setTodayResolved(afterSalesOrderRepository
                .countByStatusAndActualResolutionDateBetween(
                        AfterSalesStatus.RESOLVED, today.atStartOfDay(), today.atTime(23, 59, 59)));
        
        // 待处理统计
        dashboard.setPendingCount(afterSalesOrderRepository
                .countByStatusIn(Arrays.asList(
                        AfterSalesStatus.PENDING,
                        AfterSalesStatus.IN_PROGRESS
                )));
        
        // 超时统计
        dashboard.setOverdueCount(afterSalesOrderRepository
                .countOverdueOrders(LocalDateTime.now()));
        
        // 客服工作负载
        dashboard.setAgentWorkload(getAgentWorkloadSummary());
        
        return dashboard;
    }
    
    /**
     * 获取趋势数据
     */
    @GetMapping("/trends")
    public TrendData getTrendData(@RequestParam int days) {
        LocalDate endDate = LocalDate.now();
        LocalDate startDate = endDate.minusDays(days);
        
        return analyticsService.analyzeTrends(startDate, endDate);
    }
    
    // 其他监控接口省略...
}

# 📝 关键要点

# 技术特色

  1. 智能工单系统:基于AI的工单分配和优先级管理
  2. 全流程自动化:从问题识别到解决的端到端自动化
  3. 多维度质量管控:客户满意度、处理时效、解决质量全方位监控
  4. 数据驱动决策:基于大数据分析的服务优化和预测
  5. 个性化服务:根据客户历史和偏好提供定制化服务

# 业务价值

  1. 提升客户满意度:快速响应和专业处理提升客户体验
  2. 降低运营成本:自动化流程减少人工干预和处理时间
  3. 提高服务质量:标准化流程确保服务一致性
  4. 增强品牌信任:优质售后服务建立客户信任和忠诚度

# 未来发展

  1. AI智能客服:集成自然语言处理的智能客服机器人
  2. 预测性服务:基于机器学习的问题预测和主动服务
  3. 全渠道整合:统一的多渠道客户服务体验
  4. 区块链溯源:利用区块链技术实现服务过程的透明化

# 3. 工单管理系统

@Service
@Slf4j
public class TicketManagementService {
    
    @Autowired
    private TicketRepository ticketRepository;
    
    @Autowired
    private AgentRepository agentRepository;
    
    @Autowired
    private TicketWorkflowEngine ticketWorkflowEngine;
    
    @Autowired
    private NotificationService notificationService;
    
    /**
     * 创建工单
     */
    @Transactional
    public Ticket createTicket(TicketRequest request) {
        log.info("创建工单 - 类型: {}, 优先级: {}", 
                request.getType(), request.getPriority());
        
        Ticket ticket = new Ticket();
        ticket.setTicketNumber(generateTicketNumber());
        ticket.setType(request.getType());
        ticket.setPriority(request.getPriority());
        ticket.setSubject(request.getSubject());
        ticket.setDescription(request.getDescription());
        ticket.setCustomerId(request.getCustomerId());
        ticket.setStatus(TicketStatus.OPEN);
        
        // 设置SLA时间
        ticket.setSlaDeadline(calculateSlaDeadline(request.getPriority()));
        
        ticket = ticketRepository.save(ticket);
        
        // 自动分配客服
        autoAssignTicket(ticket);
        
        // 启动工单工作流
        ticketWorkflowEngine.startTicketWorkflow(ticket.getId());
        
        return ticket;
    }
    
    /**
     * 自动分配工单
     */
    private void autoAssignTicket(Ticket ticket) {
        // 1. 查找可用的客服代表
        List<Agent> availableAgents = agentRepository.findAvailableAgents(
                ticket.getType(), ticket.getPriority());
        
        if (availableAgents.isEmpty()) {
            log.warn("没有可用的客服代表处理工单: {}", ticket.getTicketNumber());
            return;
        }
        
        // 2. 选择最佳客服代表
        Agent bestAgent = selectBestAgent(availableAgents, ticket);
        
        // 3. 分配工单
        assignTicketToAgent(ticket, bestAgent);
    }
    
    /**
     * 选择最佳客服代表
     */
    private Agent selectBestAgent(List<Agent> agents, Ticket ticket) {
        return agents.stream()
                .min(Comparator.comparing(Agent::getCurrentWorkload)
                        .thenComparing(agent -> -agent.getSkillScore(ticket.getType()))
                        .thenComparing(Agent::getResponseTime))
                .orElse(agents.get(0));
    }
    
    /**
     * 分配工单给客服代表
     */
    @Transactional
    public void assignTicketToAgent(Ticket ticket, Agent agent) {
        ticket.setAssignedAgentId(agent.getId());
        ticket.setAssignedAt(LocalDateTime.now());
        ticket.setStatus(TicketStatus.ASSIGNED);
        
        // 增加客服工作负载
        agent.incrementWorkload();
        
        ticketRepository.save(ticket);
        agentRepository.save(agent);
        
        // 通知客服代表
        notificationService.sendTicketAssignmentNotification(agent.getId(), ticket.getId());
        
        log.info("工单 {} 已分配给客服代表 {}", ticket.getTicketNumber(), agent.getName());
    }
    
    /**
     * 更新工单状态
     */
    @Transactional
    public void updateTicketStatus(Long ticketId, TicketStatus newStatus, 
                                 String comment, Long operatorId) {
        Ticket ticket = ticketRepository.findById(ticketId)
                .orElseThrow(() -> new TicketNotFoundException("工单不存在"));
        
        TicketStatus oldStatus = ticket.getStatus();
        ticket.setStatus(newStatus);
        
        // 添加状态变更记录
        TicketStatusHistory statusHistory = new TicketStatusHistory(
                ticket, oldStatus, newStatus, comment, operatorId);
        ticket.getStatusHistory().add(statusHistory);
        
        // 处理特殊状态
        switch (newStatus) {
            case IN_PROGRESS:
                ticket.setStartedAt(LocalDateTime.now());
                break;
            case RESOLVED:
                ticket.setResolvedAt(LocalDateTime.now());
                // 释放客服工作负载
                releaseAgentWorkload(ticket.getAssignedAgentId());
                break;
            case CLOSED:
                ticket.setClosedAt(LocalDateTime.now());
                break;
        }
        
        ticketRepository.save(ticket);
        
        // 发送状态更新通知
        notificationService.sendTicketStatusUpdateNotification(ticket);
    }
    
    /**
     * 工单升级处理
     */
    @Transactional
    public void escalateTicket(Long ticketId, EscalationReason reason, Long operatorId) {
        Ticket ticket = ticketRepository.findById(ticketId)
                .orElseThrow(() -> new TicketNotFoundException("工单不存在"));
        
        // 提升优先级
        TicketPriority newPriority = escalatePriority(ticket.getPriority());
        ticket.setPriority(newPriority);
        
        // 重新计算SLA
        ticket.setSlaDeadline(calculateSlaDeadline(newPriority));
        
        // 分配给高级客服
        Agent seniorAgent = findSeniorAgent(ticket.getType());
        if (seniorAgent != null) {
            assignTicketToAgent(ticket, seniorAgent);
        }
        
        // 记录升级事件
        TicketEscalation escalation = new TicketEscalation(
                ticket, reason, operatorId, LocalDateTime.now());
        ticket.getEscalations().add(escalation);
        
        ticketRepository.save(ticket);
        
        // 通知管理层
        notificationService.sendEscalationNotification(ticket, reason);
        
        log.warn("工单 {} 已升级,原因: {}", ticket.getTicketNumber(), reason);
    }
    
    /**
     * SLA监控
     */
    @Scheduled(fixedDelay = 300000) // 每5分钟检查一次
    public void monitorSlaViolations() {
        LocalDateTime now = LocalDateTime.now();
        
        // 查找即将违反SLA的工单
        List<Ticket> nearViolationTickets = ticketRepository
                .findTicketsNearSlaViolation(now.plusHours(1));
        
        for (Ticket ticket : nearViolationTickets) {
            // 发送SLA预警
            notificationService.sendSlaWarningNotification(ticket);
        }
        
        // 查找已违反SLA的工单
        List<Ticket> violatedTickets = ticketRepository
                .findSlaViolatedTickets(now);
        
        for (Ticket ticket : violatedTickets) {
            // 自动升级
            escalateTicket(ticket.getId(), EscalationReason.SLA_VIOLATION, null);
        }
    }
    
    // 其他辅助方法省略...
}