From aa67b3cb0fe26fab576f7710c978ff3594426cca Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期四, 28 五月 2026 15:23:30 +0800
Subject: [PATCH] 添加微信公众号登录功能并优化系统配置

---
 backend/src/main/java/com/sinata/appointment/service/MiniAppointmentApplicationService.java |  192 ++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 178 insertions(+), 14 deletions(-)

diff --git a/backend/src/main/java/com/sinata/appointment/service/MiniAppointmentApplicationService.java b/backend/src/main/java/com/sinata/appointment/service/MiniAppointmentApplicationService.java
index 1436d30..5345cfe 100644
--- a/backend/src/main/java/com/sinata/appointment/service/MiniAppointmentApplicationService.java
+++ b/backend/src/main/java/com/sinata/appointment/service/MiniAppointmentApplicationService.java
@@ -14,6 +14,7 @@
 import com.sinata.appointment.constants.QrcodeTicketStatus;
 import com.sinata.appointment.constants.SystemSettingKeys;
 import com.sinata.appointment.dto.MiniAppointmentCreateRequest;
+import com.sinata.appointment.dto.MiniAppointmentCreateResultVo;
 import com.sinata.appointment.dto.MiniAppointmentListVo;
 import com.sinata.appointment.dto.MiniApprovalHistoryRow;
 import com.sinata.appointment.dto.MiniAppointmentVehicleItemRequest;
@@ -72,6 +73,7 @@
 import org.springframework.util.StringUtils;
 import org.springframework.context.ApplicationEventPublisher;
 import com.sinata.appointment.service.event.AppointmentApprovedEvent;
+import com.sinata.appointment.service.event.AppointmentApproversNotifyEvent;
 
 @Slf4j
 @Service
@@ -99,14 +101,13 @@
     private final BizHolidayDateCacheService bizHolidayDateCacheService;
     private final SysBizSettingCacheService sysBizSettingCacheService;
     private final ApprovalEngineService approvalEngineService;
-    private final IntegrationWechatService integrationWechatService;
     private final PostApprovalOrchestrationService postApprovalOrchestrationService;
     private final ApprovalFlowViewApplicationService approvalFlowViewApplicationService;
     private final ObjectMapper objectMapper;
     private final ApplicationEventPublisher applicationEventPublisher;
 
     @Transactional(rollbackFor = Exception.class)
-    public Long create(MiniAppointmentCreateRequest req) {
+    public MiniAppointmentCreateResultVo create(MiniAppointmentCreateRequest req) {
         MpUser current = miniAuthApplicationService.currentMpUser();
         FieldRequiredContext required = loadFieldRequiredContext();
         MpUser visited = loadVisitedUser(req.getVisitedUserId());
@@ -194,14 +195,14 @@
             if (inst == null) {
                 applicationEventPublisher.publishEvent(new AppointmentApprovedEvent(order.getId()));
             } else {
-                try {
-                    integrationWechatService.notifyCurrentApprovers(order, inst.getId());
-                } catch (Exception ex) {
-                    log.warn("notify current approvers failed, orderId={}", order.getId(), ex);
-                }
+                applicationEventPublisher.publishEvent(
+                        new AppointmentApproversNotifyEvent(order.getId(), inst.getId()));
             }
         }
-        return order.getId();
+        MiniAppointmentCreateResultVo result = new MiniAppointmentCreateResultVo();
+        result.setId(order.getId());
+        result.setUuid(order.getUuid());
+        return result;
     }
 
     @Transactional(rollbackFor = Exception.class)
@@ -276,6 +277,9 @@
         List<AppointmentOrder> orders = rows.stream().map(this::toOrderSnapshot).toList();
         Map<Long, String> deptNameMap = loadDeptNameMap(orders);
         Map<String, String> reasonNameMap = loadReasonNameMap(orders);
+        List<Long> orderIds = orders.stream().map(AppointmentOrder::getId).filter(Objects::nonNull).toList();
+        Map<Long, String> visitorDisplayMap = loadVisitorDisplayMapByOrderIds(orderIds);
+        Map<Long, String> plateTextMap = loadPlateTextMapByOrderIds(orderIds);
         List<MiniAppointmentListVo> records = new ArrayList<>();
         for (MiniApprovalHistoryRow row : rows) {
             AppointmentOrder order = toOrderSnapshot(row);
@@ -286,6 +290,8 @@
             vo.setVisitTimeText(buildVisitTimeText(order));
             vo.setVisitReasonText(buildVisitReasonText(order, reasonNameMap));
             vo.setAccessAreaText(buildAccessAreaText(order.getPassAccessAreaNames()));
+            vo.setVisitorDisplayText(visitorDisplayMap.get(order.getId()));
+            vo.setPlateNoText(plateTextMap.get(order.getId()));
             records.add(vo);
         }
         result.setRecords(records);
@@ -306,13 +312,20 @@
             result.setRecords(List.of());
             return result;
         }
+        Map<String, String> reasonNameMap = loadReasonNameMap(orders);
+        List<Long> orderIds = orders.stream().map(AppointmentOrder::getId).filter(Objects::nonNull).toList();
         Map<Long, List<String>> visitorNamesMap = loadVisitorNamesMap(orders.stream().map(AppointmentOrder::getId).toList());
+        Map<Long, String> visitorDisplayMap = loadVisitorDisplayMapByOrderIds(orderIds);
+        Map<Long, String> plateTextMap = loadPlateTextMapByOrderIds(orderIds);
         List<MiniVisitRecordListVo> records = new ArrayList<>();
         for (AppointmentOrder order : orders) {
             MiniVisitRecordListVo vo = new MiniVisitRecordListVo();
             vo.setOrderId(order.getId());
             vo.setOrderNo(order.getOrderNo());
             vo.setVisitorNames(visitorNamesMap.getOrDefault(order.getId(), List.of()));
+            vo.setVisitorDisplayText(visitorDisplayMap.get(order.getId()));
+            vo.setPlateNoText(plateTextMap.get(order.getId()));
+            vo.setVisitReasonText(buildVisitReasonText(order, reasonNameMap));
             vo.setVisitStartDate(order.getVisitStartDate());
             vo.setVisitEndDate(order.getVisitEndDate());
             vo.setVisitTimeMode(order.getVisitTimeMode());
@@ -387,6 +400,36 @@
             return vo;
         }
         return rows.get(0);
+    }
+
+    public MiniMyVisitorCodeOrderVo getSharedVisitorCodeByTicketUuid(String ticketUuid) {
+        QrcodeTicket ticket = loadTicketByUuid(ticketUuid);
+        AppointmentOrder order = loadApprovedOrderById(ticket.getOrderId());
+        String deptName = loadDeptName(order.getVisitedDeptId());
+
+        MiniMyVisitorCodeOrderVo vo = new MiniMyVisitorCodeOrderVo();
+        vo.setOrderId(order.getId());
+        vo.setOrderUuid(order.getUuid());
+        vo.setVisitedUserDisplay(buildVisitedUserDisplay(order.getVisitedUserName(), deptName));
+        vo.setAccessAreaText(buildAccessAreaText(order.getPassAccessAreaNames()));
+        vo.setVisitTimeText(buildVisitTimeText(order));
+        vo.setValidStartTime(ticket.getGenerateTime());
+        vo.setValidEndTime(ticket.getExpireTime());
+
+        if (!QrcodeTicketStatus.UNUSED.equals(ticket.getTicketStatus())) {
+            vo.setQrCodes(List.of());
+            return vo;
+        }
+
+        Map<Long, MpVisitor> visitorMap =
+                loadMpVisitorMap(
+                        ticket.getMpVisitorId() == null
+                                ? Set.of()
+                                : Set.of(ticket.getMpVisitorId()));
+        List<AppointmentVisitor> orderVisitors =
+                loadOrderVisitorRows(List.of(order.getId())).getOrDefault(order.getId(), List.of());
+        vo.setQrCodes(List.of(toMyVisitorCodeTicketVo(ticket, orderVisitors, visitorMap)));
+        return vo;
     }
 
     private List<MiniMyVisitorCodeOrderVo> buildVisitorCodeOrders(
@@ -473,6 +516,40 @@
         return order;
     }
 
+    private AppointmentOrder loadApprovedOrderById(Long orderId) {
+        if (orderId == null || orderId <= 0) {
+            throw new BizException(BizErrorCode.ORDER_NOT_FOUND, "预约单不存在");
+        }
+        AppointmentOrder order =
+                appointmentOrderMapper.selectOne(
+                        new LambdaQueryWrapper<AppointmentOrder>()
+                                .eq(AppointmentOrder::getDelFlag, 0)
+                                .eq(AppointmentOrder::getId, orderId)
+                                .eq(AppointmentOrder::getStatus, AppointmentOrderStatus.APPROVED)
+                                .last("limit 1"));
+        if (order == null) {
+            throw new BizException(BizErrorCode.ORDER_NOT_FOUND, "预约单不存在");
+        }
+        return order;
+    }
+
+    private QrcodeTicket loadTicketByUuid(String ticketUuid) {
+        String normalizedTicketUuid = trimToNull(ticketUuid);
+        if (!StringUtils.hasText(normalizedTicketUuid)) {
+            throw new BizException(BizErrorCode.ORDER_NOT_FOUND, "访客码不存在");
+        }
+        QrcodeTicket ticket =
+                qrcodeTicketMapper.selectOne(
+                        new LambdaQueryWrapper<QrcodeTicket>()
+                                .eq(QrcodeTicket::getDelFlag, 0)
+                                .eq(QrcodeTicket::getUuid, normalizedTicketUuid)
+                                .last("limit 1"));
+        if (ticket == null) {
+            throw new BizException(BizErrorCode.ORDER_NOT_FOUND, "访客码不存在");
+        }
+        return ticket;
+    }
+
     public MiniMyAppointmentDetailVo detailMyOrder(Long orderId, String uuid) {
         if (orderId == null || orderId <= 0) {
             throw new BizException(BizErrorCode.ORDER_NOT_FOUND, "预约单不存在");
@@ -546,8 +623,8 @@
             if (!StringUtils.hasText(txt)) {
                 throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "选择其他时须填写自定义内容");
             }
-            if (txt.length() > 255) {
-                throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "自定义内容不能超过255字");
+            if (txt.length() > 15) {
+                throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "自定义内容不能超过15字");
             }
             return;
         }
@@ -793,6 +870,7 @@
         vo.setVisitStartDate(row.getVisitStartDate());
         vo.setVisitEndDate(row.getVisitEndDate());
         vo.setVisitReasonCode(row.getVisitReasonCode());
+        vo.setVisitReasonOther(row.getVisitReasonOther());
         vo.setCreateTime(row.getCreateTime());
         return vo;
     }
@@ -906,6 +984,92 @@
         return StringUtils.hasText(dept) ? dept : "-";
     }
 
+    private Map<Long, String> loadVisitorDisplayMapByOrderIds(List<Long> orderIds) {
+        if (orderIds == null || orderIds.isEmpty()) {
+            return Map.of();
+        }
+        List<AppointmentVisitor> rows =
+                appointmentVisitorMapper.selectList(
+                        new LambdaQueryWrapper<AppointmentVisitor>()
+                                .eq(AppointmentVisitor::getDelFlag, 0)
+                                .in(AppointmentVisitor::getOrderId, orderIds)
+                                .orderByAsc(AppointmentVisitor::getId));
+        Set<Long> visitorIds =
+                rows.stream()
+                        .map(AppointmentVisitor::getMpVisitorId)
+                        .filter(Objects::nonNull)
+                        .collect(Collectors.toCollection(LinkedHashSet::new));
+        Map<Long, String> companyMap = loadMpVisitorCompanyMap(visitorIds);
+        Map<Long, List<String>> grouped = new LinkedHashMap<>();
+        for (AppointmentVisitor row : rows) {
+            if (row.getOrderId() == null) {
+                continue;
+            }
+            String name = trimToNull(row.getName());
+            if (!StringUtils.hasText(name)) {
+                continue;
+            }
+            String company = companyMap.get(row.getMpVisitorId());
+            String display = StringUtils.hasText(company) ? name + "(" + company + ")" : name;
+            grouped.computeIfAbsent(row.getOrderId(), key -> new ArrayList<>()).add(display);
+        }
+        Map<Long, String> result = new LinkedHashMap<>();
+        for (Map.Entry<Long, List<String>> entry : grouped.entrySet()) {
+            String text = entry.getValue().stream().distinct().collect(Collectors.joining("、"));
+            if (StringUtils.hasText(text)) {
+                result.put(entry.getKey(), text);
+            }
+        }
+        return result;
+    }
+
+    private Map<Long, String> loadMpVisitorCompanyMap(Set<Long> visitorIds) {
+        if (visitorIds == null || visitorIds.isEmpty()) {
+            return Map.of();
+        }
+        List<MpVisitor> rows =
+                mpVisitorMapper.selectList(
+                        new LambdaQueryWrapper<MpVisitor>()
+                                .eq(MpVisitor::getDelFlag, 0)
+                                .in(MpVisitor::getId, visitorIds));
+        Map<Long, String> result = new LinkedHashMap<>();
+        for (MpVisitor row : rows) {
+            result.put(row.getId(), trimToNull(row.getVisitorCompany()));
+        }
+        return result;
+    }
+
+    private Map<Long, String> loadPlateTextMapByOrderIds(List<Long> orderIds) {
+        if (orderIds == null || orderIds.isEmpty()) {
+            return Map.of();
+        }
+        List<AppointmentVehicle> rows =
+                appointmentVehicleMapper.selectList(
+                        new LambdaQueryWrapper<AppointmentVehicle>()
+                                .eq(AppointmentVehicle::getDelFlag, 0)
+                                .in(AppointmentVehicle::getOrderId, orderIds)
+                                .orderByAsc(AppointmentVehicle::getId));
+        Map<Long, List<String>> grouped = new LinkedHashMap<>();
+        for (AppointmentVehicle row : rows) {
+            if (row.getOrderId() == null) {
+                continue;
+            }
+            String plateNo = trimToNull(row.getPlateNo());
+            if (!StringUtils.hasText(plateNo)) {
+                continue;
+            }
+            grouped.computeIfAbsent(row.getOrderId(), key -> new ArrayList<>()).add(plateNo);
+        }
+        Map<Long, String> result = new LinkedHashMap<>();
+        for (Map.Entry<Long, List<String>> entry : grouped.entrySet()) {
+            String text = entry.getValue().stream().distinct().collect(Collectors.joining("、"));
+            if (StringUtils.hasText(text)) {
+                result.put(entry.getKey(), text);
+            }
+        }
+        return result;
+    }
+
     private String resolveOrderStatusText(AppointmentOrder row) {
         return switch (trimToNull(row.getStatus())) {
             case AppointmentOrderStatus.PENDING_APPROVAL -> "审批中";
@@ -947,9 +1111,9 @@
     private String buildVisitTimeText(AppointmentOrder row) {
         if (AppointmentBizConstants.TIME_MODE_SINGLE.equals(row.getVisitTimeMode())) {
             String slotLabel = switch (trimToNull(row.getVisitTimeSlot())) {
-                case AppointmentBizConstants.TIME_SLOT_ALL_DAY -> "全天";
-                case AppointmentBizConstants.TIME_SLOT_AM -> "上午";
-                case AppointmentBizConstants.TIME_SLOT_PM -> "下午";
+                case AppointmentBizConstants.TIME_SLOT_ALL_DAY -> "00:00-23:59";
+                case AppointmentBizConstants.TIME_SLOT_AM -> "00:00-11:59";
+                case AppointmentBizConstants.TIME_SLOT_PM -> "12:00-23:59";
                 default -> "";
             };
             if (row.getVisitStartDate() == null) {
@@ -971,7 +1135,7 @@
     private String buildVisitReasonText(AppointmentOrder row, Map<String, String> reasonNameMap) {
         if (AppointmentBizConstants.VISIT_REASON_OTHER.equalsIgnoreCase(trimToNull(row.getVisitReasonCode()))) {
             String other = trimToNull(row.getVisitReasonOther());
-            return StringUtils.hasText(other) ? "其他-" + other : "其他";
+            return StringUtils.hasText(other) ? "其他:" + other : "其他";
         }
         String reasonName = reasonNameMap.get(trimToNull(row.getVisitReasonCode()));
         return StringUtils.hasText(reasonName) ? reasonName : trimToNull(row.getVisitReasonCode());

--
Gitblit v1.7.1