longz
2026-04-27 25df88d0ab9d7444ed1d162b9efa1259ac2f6f66
backend/src/main/java/com/sinata/appointment/service/IntegrationWechatService.java
@@ -1,22 +1,263 @@
package com.sinata.appointment.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.sinata.appointment.constants.AppointmentBizConstants;
import com.sinata.appointment.constants.AppointmentOrderStatus;
import com.sinata.appointment.constants.ApprovalNodeCode;
import com.sinata.appointment.constants.ApprovalTaskStatus;
import com.sinata.appointment.entity.ApprovalTask;
import com.sinata.appointment.entity.AppointmentOrder;
import com.sinata.appointment.entity.BizVisitReason;
import com.sinata.appointment.entity.MpUser;
import com.sinata.appointment.integration.WechatMiniGateway;
import com.sinata.appointment.mapper.ApprovalTaskMapper;
import com.sinata.appointment.mapper.BizVisitReasonMapper;
import com.sinata.appointment.mapper.MpUserMapper;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
/**
 * 微信公众号模板消息适配桩。
 * 小程序订阅消息发送。
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class IntegrationWechatService {
    private static final String APPROVAL_RESULT_TEMPLATE_ID =
            "4FQovCBNFM7rth42AWHTusHajO788eT9VpplSN4ZG2s";
    private static final String NOTICE_RESULT_TEMPLATE_ID =
            "GC1S3T2qHCbz8ow130u7elRczuqW6Z8q54mKRCYE-xo";
    private static final String APPROVAL_RESULT_PAGE = "pages/record/index";
    private static final String APPROVAL_TODO_TEMPLATE_ID =
            "BX49OBHvRcoYiCw0ZLqx04BiWQ9KaEQwuVth_Lh_M2A";
    private static final String APPROVAL_TODO_PAGE = "pages/reviewList/index";
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private final WechatMiniGateway wechatMiniGateway;
    private final MpUserMapper mpUserMapper;
    private final ApprovalTaskMapper approvalTaskMapper;
    private final BizVisitReasonMapper bizVisitReasonMapper;
    public void notifyApprovalFinalResult(AppointmentOrder order, boolean approved) {
        if (order == null || order.getSubmitterId() == null) {
            return;
        }
        MpUser submitter =
                mpUserMapper.selectOne(
                        new LambdaQueryWrapper<MpUser>()
                                .eq(MpUser::getDelFlag, 0)
                                .eq(MpUser::getId, order.getSubmitterId())
                                .last("limit 1"));
        if (submitter == null || !StringUtils.hasText(submitter.getOpenId())) {
            log.info(
                    "Skip mini subscribe notify, submitter not found or openId empty. submitterId={} orderNo={}",
                    order.getSubmitterId(),
                    order.getOrderNo());
            return;
        }
        Map<String, String> templateData = new LinkedHashMap<>();
        templateData.put("thing1", approved ? "审核通过" : "审核未通过");
        templateData.put("thing2", abbreviate(resolveSubmitterName(submitter), 20));
        templateData.put("time3", buildVisitTimeText(order));
        templateData.put("thing4", abbreviate(buildVisitReasonText(order), 20));
        wechatMiniGateway.sendSubscribeMessage(
                submitter.getOpenId(), APPROVAL_RESULT_TEMPLATE_ID, APPROVAL_RESULT_PAGE, templateData);
        log.info(
                "WeChat stub: notify submitterId={} orderNo={} result={}",
                "Mini subscribe notify sent submitterId={} orderNo={} result={}",
                order.getSubmitterId(),
                order.getOrderNo(),
                approved ? AppointmentOrderStatus.APPROVED : AppointmentOrderStatus.REJECTED);
    }
    public void notifyApprovalNoticeUsers(
            AppointmentOrder order, boolean approved, java.util.Collection<MpUser> noticeUsers, int visitorCount) {
        if (order == null || noticeUsers == null || noticeUsers.isEmpty()) {
            return;
        }
        Map<String, String> templateData = new LinkedHashMap<>();
        templateData.put("thing6", buildVisitorCountText(visitorCount));
        templateData.put("time18", buildVisitTimeText(order));
        templateData.put("thing20", abbreviate(buildNoticeBusinessTypeText(order, approved), 20));
        for (MpUser noticeUser : noticeUsers) {
            if (noticeUser == null || !StringUtils.hasText(noticeUser.getOpenId())) {
                continue;
            }
            wechatMiniGateway.sendSubscribeMessage(
                    noticeUser.getOpenId(), NOTICE_RESULT_TEMPLATE_ID, APPROVAL_RESULT_PAGE, templateData);
            log.info(
                    "Mini subscribe notice sent noticeUserId={} orderNo={} result={}",
                    noticeUser.getId(),
                    order.getOrderNo(),
                    approved ? AppointmentOrderStatus.APPROVED : AppointmentOrderStatus.REJECTED);
        }
    }
    public void notifyCurrentApprovers(AppointmentOrder order, Long instanceId) {
        if (order == null || instanceId == null) {
            return;
        }
        java.util.List<ApprovalTask> pendingTasks =
                approvalTaskMapper.selectList(
                        new LambdaQueryWrapper<ApprovalTask>()
                                .eq(ApprovalTask::getDelFlag, 0)
                                .eq(ApprovalTask::getInstanceId, instanceId)
                                .eq(ApprovalTask::getTaskStatus, ApprovalTaskStatus.PENDING)
                                .orderByAsc(ApprovalTask::getId));
        if (pendingTasks.isEmpty()) {
            return;
        }
        java.util.LinkedHashMap<Long, java.util.LinkedHashSet<String>> userNodeNames = new java.util.LinkedHashMap<>();
        for (ApprovalTask task : pendingTasks) {
            if (task.getAssigneeUserId() == null) {
                continue;
            }
            userNodeNames.computeIfAbsent(task.getAssigneeUserId(), key -> new java.util.LinkedHashSet<>());
            userNodeNames.get(task.getAssigneeUserId()).add(resolveNodeName(task.getNodeCode()));
        }
        if (userNodeNames.isEmpty()) {
            return;
        }
        java.util.List<MpUser> users =
                mpUserMapper.selectList(
                        new LambdaQueryWrapper<MpUser>()
                                .eq(MpUser::getDelFlag, 0)
                                .in(MpUser::getId, userNodeNames.keySet()));
        java.util.Map<Long, MpUser> userMap = new java.util.LinkedHashMap<>();
        for (MpUser user : users) {
            userMap.putIfAbsent(user.getId(), user);
        }
        for (java.util.Map.Entry<Long, java.util.LinkedHashSet<String>> entry : userNodeNames.entrySet()) {
            MpUser user = userMap.get(entry.getKey());
            if (user == null || !StringUtils.hasText(user.getOpenId())) {
                continue;
            }
            Map<String, String> templateData = new LinkedHashMap<>();
            templateData.put("thing1", abbreviate(buildTodoTaskName(order), 20));
            templateData.put("thing3", abbreviate(String.join("、", entry.getValue()), 20));
            wechatMiniGateway.sendSubscribeMessage(
                    user.getOpenId(), APPROVAL_TODO_TEMPLATE_ID, APPROVAL_TODO_PAGE, templateData);
            log.info(
                    "Mini subscribe todo notify sent approverId={} orderNo={} stages={}",
                    user.getId(),
                    order.getOrderNo(),
                    entry.getValue());
        }
    }
    private String resolveSubmitterName(MpUser submitter) {
        if (StringUtils.hasText(submitter.getNickName())) {
            return submitter.getNickName().trim();
        }
        if (StringUtils.hasText(submitter.getMobile())) {
            return submitter.getMobile().trim();
        }
        return "预约人";
    }
    private String buildVisitTimeText(AppointmentOrder order) {
        LocalDate start = order.getVisitStartDate();
        LocalDate end = order.getVisitEndDate();
        if (start == null && end == null) {
            return "待定";
        }
        if (start == null) {
            start = end;
        }
        if (end == null) {
            end = start;
        }
        return switch (safeTrim(order.getVisitTimeMode())) {
            case "MULTI" -> formatDate(start);
            default -> formatDate(start) + " " + mapTimeSlotLabel(order.getVisitTimeSlot());
        };
    }
    private String buildVisitReasonText(AppointmentOrder order) {
        String reasonCode = safeTrim(order.getVisitReasonCode());
        if (!StringUtils.hasText(reasonCode)) {
            return "拜访";
        }
        if (AppointmentBizConstants.VISIT_REASON_OTHER.equalsIgnoreCase(reasonCode)) {
            String other = safeTrim(order.getVisitReasonOther());
            return StringUtils.hasText(other) ? other : "其他";
        }
        BizVisitReason reason =
                bizVisitReasonMapper.selectOne(
                        new LambdaQueryWrapper<BizVisitReason>()
                                .eq(BizVisitReason::getDelFlag, 0)
                                .eq(BizVisitReason::getReasonCode, reasonCode)
                                .last("limit 1"));
        if (reason != null && StringUtils.hasText(reason.getReasonName())) {
            return reason.getReasonName().trim();
        }
        return reasonCode;
    }
    private String buildVisitorCountText(int visitorCount) {
        return Math.max(visitorCount, 0) + "人";
    }
    private String buildNoticeBusinessTypeText(AppointmentOrder order, boolean approved) {
        String reason = buildVisitReasonText(order);
        if (!StringUtils.hasText(reason)) {
            return approved ? "预约通过" : "预约驳回";
        }
        return reason;
    }
    private String buildTodoTaskName(AppointmentOrder order) {
        if (order == null || !StringUtils.hasText(order.getOrderNo())) {
            return "预约审核";
        }
        return "预约审核-" + order.getOrderNo();
    }
    private String resolveNodeName(String nodeCode) {
        return switch (safeTrim(nodeCode)) {
            case ApprovalNodeCode.LEVEL1_HANDLER -> "经办人(一级)";
            case ApprovalNodeCode.LEVEL2_DEPT_LEADER -> "部门负责人(二级)";
            case ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER -> "属地管理部门领导(三级)";
            case ApprovalNodeCode.LEVEL4_VISIT_DEPT -> "访客主管部门(四级)";
            case ApprovalNodeCode.LEVEL5_VISIT_DEPT_LEADER -> "访客主管部门领导(五级)";
            default -> "审批待办";
        };
    }
    private String mapTimeSlotLabel(String slot) {
        return switch (safeTrim(slot)) {
            case "AM" -> "上午";
            case "PM" -> "下午";
            case "ALL_DAY" -> "全天";
            default -> "全天";
        };
    }
    private String formatDate(LocalDate date) {
        return date.format(DATE_FORMATTER);
    }
    private String abbreviate(String value, int maxLength) {
        if (!StringUtils.hasText(value)) {
            return "";
        }
        String trimmed = value.trim();
        if (trimmed.length() <= maxLength) {
            return trimmed;
        }
        return trimmed.substring(0, maxLength);
    }
    private String safeTrim(String value) {
        if (!StringUtils.hasText(value)) {
            return "";
        }
        return value.trim();
    }
}