liujie
2026-09-11 4d15e6b553c448e2e07527e9c06aa4b5f9b099cf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
package com.ruoyi.web.controller.service;
 
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.ContentType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.SysVisitorRecord;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
 
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
 
/**
 * 访客登记同步服务
 * 将本地访客登记数据同步到对端服务器
 */
@Slf4j
@Service
public class VisitorRegisterSyncService {
 
    @Value("${visitor.sync.url:}")
    private String syncUrl;
 
    @Value("${visitor.sync.enabled:false}")
    private Boolean syncEnabled;
 
    /** 中台访客状态更新地址;未配置时由登记地址自动推导。 */
    @Value("${visitor.sync.status-url:}")
    private String statusUrl;
 
    private final ObjectMapper objectMapper = new ObjectMapper();
 
    private static final DateTimeFormatter DATE_FORMATTER =
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 
    /**
     * 同步访客登记信息到对端服务器
     *
     * @param record 本地访客登记记录
     * @return 同步结果
     */
    public AjaxResult syncVisitorRegister(SysVisitorRecord record) {
        // 检查同步开关
        if (!Boolean.TRUE.equals(syncEnabled)) {
            log.info("访客登记同步已关闭,跳过同步");
            return AjaxResult.success("同步功能未开启");
        }
 
        if (StringUtils.isBlank(syncUrl)) {
            log.warn("同步URL未配置,跳过同步");
            return AjaxResult.warn("同步URL未配置");
        }
 
        try {
            // 1. 构建同步请求参数
            Map<String, Object> requestBody = buildSyncRequest(record);
 
            String jsonBody = objectMapper.writeValueAsString(requestBody);
            log.info("同步访客登记数据:{}", jsonBody);
 
            // 2. 发送HTTP请求
            HttpResponse response = HttpRequest.post(syncUrl)
                    .header("Content-Type", ContentType.JSON.getValue())
                    .body(jsonBody)
                    .timeout(30000)
                    .execute();
 
            // 3. 解析响应
            String responseBody = response.body();
            log.info("同步响应:{}", responseBody);
 
            JsonNode jsonNode = objectMapper.readTree(responseBody);
            boolean success = jsonNode.has("success") && jsonNode.get("success").asBoolean();
 
            if (success) {
                JsonNode dataNode = jsonNode.get("data");
                String remoteId = null;
                if (dataNode != null) {
                    if (dataNode.isObject() && dataNode.has("id")) {
                        remoteId = dataNode.get("id").asText();
                    } else if (dataNode.isValueNode()) {
                        remoteId = dataNode.asText();
                    }
                }
                log.info("访客登记同步成功,远程ID:{}", remoteId);
                return AjaxResult.success(remoteId);
            } else {
                String errorMsg = jsonNode.has("description")
                        ? jsonNode.get("description").asText() : "未知错误";
                log.error("访客登记同步失败:{}", errorMsg);
                return AjaxResult.error(errorMsg);
            }
        } catch (Exception e) {
            log.error("访客登记同步异常", e);
            return AjaxResult.error("同步异常:" + e.getMessage());
        }
    }
 
    /**
     * 将本地签离状态同步到事件中心。
     *
     * <p>状态同步采用尽力而为策略:中台不可用时保留本地签离结果,避免访客机因网络问题无法完成签离。</p>
     */
    public AjaxResult syncVisitorStatus(SysVisitorRecord record) {
        if (!Boolean.TRUE.equals(syncEnabled)) {
            return AjaxResult.success("访客状态同步已关闭");
        }
        if (record == null || StringUtils.isBlank(record.getMiddlePlatformId())) {
            log.warn("访客状态同步跳过:缺少中台访客登记记录ID,本地recordId={}",
                    record == null ? null : record.getRecordId());
            return AjaxResult.warn("缺少中台访客登记记录ID");
        }
 
        String middleStatus = toMiddleStatus(record.getStatus());
        if (middleStatus == null) {
            log.info("访客状态 {} 无需同步到中台,middlePlatformId={}",
                    record.getStatus(), record.getMiddlePlatformId());
            return AjaxResult.success("当前状态无需同步");
        }
 
        String targetUrl = resolveStatusUrl();
        if (StringUtils.isBlank(targetUrl)) {
            log.warn("访客状态同步跳过:未配置中台状态更新地址");
            return AjaxResult.warn("中台状态更新地址未配置");
        }
 
        try {
            Map<String, Object> requestBody = new LinkedHashMap<>();
            requestBody.put("id", record.getMiddlePlatformId());
            requestBody.put("status", middleStatus);
            if (record.getSignTime() != null) {
                requestBody.put("signOutTime", formatTime(record.getSignTime()));
            }
 
            String jsonBody = objectMapper.writeValueAsString(requestBody);
            log.info("同步访客状态到中台:middlePlatformId={}, status={}, signOutTime={}",
                    record.getMiddlePlatformId(), middleStatus, requestBody.get("signOutTime"));
 
            HttpResponse response = HttpRequest.post(targetUrl)
                    .header("Content-Type", ContentType.JSON.getValue())
                    .body(jsonBody)
                    .timeout(30000)
                    .execute();
            String responseBody = response.body();
            JsonNode jsonNode = objectMapper.readTree(responseBody);
            boolean success = jsonNode.has("success") && jsonNode.get("success").asBoolean();
            if (success) {
                return AjaxResult.success("访客状态同步成功");
            }
 
            String errorMessage = jsonNode.has("description")
                    ? jsonNode.get("description").asText() : "中台访客状态更新失败";
            log.error("中台访客状态更新失败,middlePlatformId={}, status={}, message={}",
                    record.getMiddlePlatformId(), middleStatus, errorMessage);
            return AjaxResult.error(errorMessage);
        } catch (Exception e) {
            log.error("中台访客状态同步异常,middlePlatformId={}, status={}",
                    record.getMiddlePlatformId(), middleStatus, e);
            return AjaxResult.error("中台状态同步异常:" + e.getMessage());
        }
    }
 
    /**
     * 构建同步请求参数
     */
    private Map<String, Object> buildSyncRequest(SysVisitorRecord record) {
        Map<String, Object> params = new HashMap<>();
 
        // 必填字段
        params.put("name", record.getVisitorName());
        params.put("certType", "111");
        params.put("certNo", record.getIdCard());
//        params.put("qrCode", generateQrCode(record));
        params.put("person", record.getIntervieweeName());
        params.put("reason", StringUtils.isNotBlank(record.getVisitReason())
                ? record.getVisitReason() : "来访");
        params.put("estimatedTimeStart", formatTime(record.getValidStartTime()));
        params.put("estimatedTimeEnd", formatTime(record.getValidEndTime()));
 
        if (record.getAreasList() != null && !record.getAreasList().isEmpty()) {
            params.put("areas", record.getAreasList());
        }
 
        // 可选字段
//        params.put("gender", convertGender(record.getGender()));
//        params.put("phone", record.getPhone());
        params.put("licensePlateNumber", record.getPlateNo());
//        params.put("visitorDept", record.getVisitorCompany());
        params.put("dept", record.getIntervieweeDept());
        params.put("areaName", record.getVisitAreaNames());
        params.put("registerGate", record.getRegisterSource());
        params.put("registerMachineCode", record.getDeviceId());
        params.put("status", "1"); // 1-已登记
 
        // 如果有证件照片或现场照片,可以传递
        // params.put("idPhoto", record.getIdCardPhoto());
         params.put("pic", record.getFaceImageUrl());
 
        return params;
    }
 
    /** 根据登记接口地址推导同一事件中心下的状态更新接口。 */
    private String resolveStatusUrl() {
        if (StringUtils.isNotBlank(statusUrl)) {
            return statusUrl.trim();
        }
        if (StringUtils.isBlank(syncUrl)) {
            return null;
        }
        String registerPath = "/visitor-register";
        if (syncUrl.contains(registerPath)) {
            return syncUrl.replace(registerPath, "/visitor-status-update");
        }
        return null;
    }
 
    /** 将本地状态转换为事件中心字典值。 */
    private String toMiddleStatus(String status) {
        if ("SIGNED_OUT".equals(status)) {
            return "6";
        }
        if ("OVERDUE_AUTO_SIGNED_OUT".equals(status)) {
            return "5";
        }
        if ("PENDING_CONFIRM".equals(status)) {
            return "7";
        }
        return null;
    }
 
    /**
     * 转换证件类型:本地字典 → 对方字典值
     */
    private String convertCertType(String localCertType) {
        if (StringUtils.isBlank(localCertType)) {
            return "111"; // 默认身份证
        }
        // 根据实际情况映射
        switch (localCertType) {
            case "ID_CARD": return "111";      // 居民身份证
            case "PASSPORT": return "414";     // 护照
            case "DRIVER_LICENSE": return "335"; // 驾驶证
            case "HK_MACAU_PASS": return "337";  // 港澳通行证
            default: return "111";
        }
    }
 
    /**
     * 转换性别:本地字典 → 对方字典值
     */
    private String convertGender(String localGender) {
        if (StringUtils.isBlank(localGender)) {
            return null;
        }
        switch (localGender) {
            case "MALE": return "1";
            case "FEMALE": return "2";
            default: return null;
        }
    }
 
    /**
     * 格式化时间
     */
    private String formatTime(Date date) {
        if (date == null) {
            return null;
        }
        LocalDateTime localDateTime = date.toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDateTime();
        return localDateTime.format(DATE_FORMATTER);
    }
 
    /**
     * 生成二维码内容
     * 格式:vms:visitor:{日期}:{登记编号}
     */
    private String generateQrCode(SysVisitorRecord record) {
        String dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String registerNo = record.getRegisterNo();
        return String.format("vms:visitor:%s:%s", dateStr, registerNo);
    }
}