liujie
2026-07-21 f453ee77ba5eb2704d5a8e967a130dc43b8ff799
Merge branch 'master' of http://120.76.84.145:10101/gitblit/r/java/fangkeji
6个文件已添加
19个文件已修改
675 ■■■■■ 已修改文件
ruoyi-applet/src/main/java/com/ruoyi/RuoYiAppletApplication.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/EmployeeVerifyController.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/SysApiDoorMachineController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/SysApiProtocolConfigController.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/SysApiVisitorConfigController.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/SysApiVisitorRecordController.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/web/controller/service/ApiPullSignOutDataService.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/web/controller/service/ApiPullSingInDataService.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/web/controller/service/VisitorRegisterSyncService.java 191 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysVisitArea.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysVisitorMachine.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysVisitorRecord.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysVisitorSignoutTemp.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/api/ApiSysVisitorRecord.java 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/AreasList.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/dto/ApiVisitorMachineRequest.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/dto/ApiVisitorRecodeSingoutRequest.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/dto/ApiVisitorRecordSinginRequest.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/dto/SysVisitorRecordAddRequest.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysVisitorRecordMapper.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/api/SysApiVisitorRecordMapper.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysVisitorMachineService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysVisitorMachineServiceImpl.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/EmployeeMapper.xml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/RuoYiAppletApplication.java
@@ -29,8 +29,6 @@
public class RuoYiAppletApplication
{
    public static void main(String[] args) {
        ConfigurableApplicationContext application = SpringApplication.run(RuoYiAppletApplication.class, args);
        try {
            Environment env = application.getEnvironment();
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/EmployeeVerifyController.java
@@ -62,12 +62,12 @@
     * 刷卡验证:现场读卡器读取卡号后调用本接口,与接待人绑定的卡号比对。
     */
    @PostMapping("/allEmployee")
    @ApiOperation(value = "查询所有员工接口", notes = "必传设备ID")
    public AjaxResult getAllEmployee(@RequestBody @Valid ApiVisitorMachineRequest request) {
        SysVisitorMachine detail = visitorMachineMapper.checkDeviceIdUnique(request.getMachineId(),null);
        if (detail == null || !detail.getEnabled()) {
            throw new ServiceException("未找到有效设备");
        }
    @ApiOperation(value = "查询所有员工接口")
    public AjaxResult getAllEmployee() {
//        SysVisitorMachine detail = visitorMachineMapper.checkDeviceIdUnique(request.getMachineId(),null);
//        if (detail == null || !detail.getEnabled()) {
//            throw new ServiceException("未找到有效设备");
//        }
        List<Employee> employees = employeeMapper.selectAllEnabled();
        if (employees == null || employees.isEmpty()) {
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/SysApiDoorMachineController.java
@@ -4,6 +4,8 @@
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.dto.*;
import com.ruoyi.system.service.ISysVisitorMachineService;
import io.swagger.annotations.Api;
@@ -36,7 +38,11 @@
    @PostMapping("/activate")
    @ApiOperation(value = "激活访客机设备")
    public AjaxResult activate(@Valid @RequestBody ApiVisitorMachineRequest request) {
        int result = visitorMachineService.activateDevice(request.getMachineId());
        if (StringUtils.isEmpty(request.getFingerprint())) {
            throw new ServiceException("缺少参数");
        }
        int result = visitorMachineService.activateDevice(request.getMachineId(), request.getFingerprint());
        return result > 0 ? success("设备激活成功") : error("设备激活失败");
    }
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/SysApiProtocolConfigController.java
New file
@@ -0,0 +1,51 @@
package com.ruoyi.web.controller.api;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.dto.SysProtocolConfigDetailRequest;
import com.ruoyi.system.dto.SysProtocolConfigSaveRequest;
import com.ruoyi.system.service.ISysProtocolConfigService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * 入园承诺
 *
 * @author ruoyi
 */
@RestController
@RequestMapping("/api/admission")
@Api(tags = "入园承诺" , value = "入园承诺")
public class SysApiProtocolConfigController extends BaseController
{
    @Autowired
    private ISysProtocolConfigService protocolConfigService;
    /**
     * 入园承诺
     */
    @Log(title = "入园承诺", businessType = BusinessType.SELECT)
    @PostMapping("/detail")
    @ApiOperation(value = "入园承诺")
    @ApiResponses({
            @ApiResponse(code = 200, message = "操作成功", response = SysProtocolConfigDetailRequest.class),
            @ApiResponse(code = 500, message = "操作失败")
    })
    public AjaxResult<SysProtocolConfigDetailRequest> detail()
    {
        SysProtocolConfigDetailRequest request = new SysProtocolConfigDetailRequest();
        request.setProtocolType("ENTRY_COMMITMENT");//入园承诺
        return success(protocolConfigService.selectConfig(request));
    }
}
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/SysApiVisitorConfigController.java
New file
@@ -0,0 +1,53 @@
package com.ruoyi.web.controller.api;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.dto.SysVisitorConfigSaveRequest;
import com.ruoyi.system.service.ISysVisitorConfigService;
import com.ruoyi.system.vo.SysVisitorConfigVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * 访客系统配置接口
 *
 * @author ruoyi
 */
@RestController
@RequestMapping("/api/visitorconfig")
@Api(tags = "访客系统配置接口")
public class SysApiVisitorConfigController extends BaseController
{
    @Autowired
    private ISysVisitorConfigService visitorConfigService;
    /**
     * 查询当前配置
     */
    @Log(title = "访客系统配置", businessType = BusinessType.SELECT)
    @PostMapping("/detail")
    @ApiOperation(
            value = "查询访客系统配置",
            notes = "获取当前访客系统的配置信息,包括访客登记、审批流程等设置",
            response = SysVisitorConfigVO.class
    )
    @ApiResponses({
            @ApiResponse(code = 200, message = "查询成功", response = SysVisitorConfigVO.class),
            @ApiResponse(code = 500, message = "查询失败")
    })
    public AjaxResult<SysVisitorConfigVO> detail()
    {
        SysVisitorConfigVO data = visitorConfigService.selectConfig();
        return new AjaxResult<>(200, "操作成功", data);
    }
}
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/SysApiVisitorRecordController.java
@@ -1,6 +1,8 @@
package com.ruoyi.web.controller.api;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
@@ -12,6 +14,8 @@
import com.ruoyi.system.domain.Employee;
import com.ruoyi.system.domain.SysVisitArea;
import com.ruoyi.system.domain.SysVisitorRecord;
import com.ruoyi.system.domain.vo.AreasList;
import com.ruoyi.system.dto.SysVisitAreaTreeRequest;
import com.ruoyi.system.dto.SysVisitorIdCardOutRequest;
import com.ruoyi.system.dto.SysVisitorRecordAddRequest;
import com.ruoyi.system.mapper.EmployeeMapper;
@@ -19,26 +23,28 @@
import com.ruoyi.system.mapper.SysVisitAreaMapper;
import com.ruoyi.system.mapper.SysVisitorRecordMapper;
import com.ruoyi.system.service.ISysVisitAreaService;
import com.ruoyi.system.vo.SysVisitAreaVO;
import com.ruoyi.web.controller.service.SysVisitorRecordService;
import com.ruoyi.web.controller.service.VisitorRegisterSyncService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;
import java.util.*;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/visitorRecord")
@Api(tags = "现场访客登记-签离")
public class SysApiVisitorRecordController extends BaseController {
    @Autowired
    private VisitorRegisterSyncService visitorRegisterSyncService;
    @Autowired
    private SysVisitorRecordService sysVisitorRecordService;
    @Autowired
@@ -52,6 +58,7 @@
    @PostMapping("/add")
    @ApiOperation(value = "访客登记", notes = "现场访客登记")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult add(
            @RequestBody
            @ApiParam(value = "访客登记信息", required = true)
@@ -103,6 +110,9 @@
        BeanUtils.copyProperties(request, record);
        //查询地区
        List<AreasList> areasList = null;
        //查询地区
        if (StringUtils.isNotEmpty(record.getVisitAreaIds())) {
            String[] visitAreaIds = record.getVisitAreaIds().split(",");
            String visitArea = Arrays.stream(visitAreaIds)
@@ -113,10 +123,77 @@
                    .filter(Objects::nonNull)
                    .collect(Collectors.joining(","));
            // ====== 新增:构建 areas 数组 ======
            areasList = Arrays.stream(visitAreaIds)
                    .map(String::trim)
                    .filter(StringUtils::isNotEmpty)
                    .map(id -> {
                        SysVisitArea area = sysVisitAreaMapper.selectById(Long.parseLong(id));
                        if (area == null) {
                            return null;
                        }
                        AreasList areas = new AreasList();
                        areas.setAreaCode(area.getAreaCode());
                        areas.setAreaName(area.getAreaName());
                        return areas;
                    })
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
            if (!areasList.isEmpty()) {
                record.setAreasList(areasList);
            }
            // 使用 visitArea
            if (StringUtils.isNotEmpty(visitArea)) {
                record.setVisitAreaNames(visitArea);
                record.setRemark("来访中,允许访问区域:" + visitArea);
            }
        }
// 如果 visitAreaIds 为空,但 visitAreaNames 不为空,根据名称查询填充 visitAreaIds
        else if (StringUtils.isNotEmpty(record.getVisitAreaNames())) {
            String[] visitAreaNames = record.getVisitAreaNames().split(",");
            // 根据区域名称查询对应的ID,构建ID字符串
            String visitAreaIdsStr = Arrays.stream(visitAreaNames)
                    .map(String::trim)
                    .filter(StringUtils::isNotEmpty)
                    .map(name -> {
                        // 根据区域名称查询区域信息(这里假设您有根据名称查询的方法)
                        SysVisitAreaTreeRequest query = new SysVisitAreaTreeRequest();
                        query.setAreaName(name);
                        List<SysVisitAreaVO> area = sysVisitAreaMapper.selectAreaTreeList(query);
                        return area != null ? String.valueOf(area.get(0).getAreaId()) : null;
                    })
                    .filter(Objects::nonNull)
                    .collect(Collectors.joining(","));
            if (StringUtils.isNotEmpty(visitAreaIdsStr)) {
                record.setVisitAreaIds(visitAreaIdsStr);
            }
            // 同时构建 areas 数组
            areasList = Arrays.stream(visitAreaNames)
                    .map(String::trim)
                    .filter(StringUtils::isNotEmpty)
                    .map(name -> {
                        SysVisitAreaTreeRequest query = new SysVisitAreaTreeRequest();
                        query.setAreaName(name);
                        List<SysVisitAreaVO> area = sysVisitAreaMapper.selectAreaTreeList(query);
                        if (area == null || area.isEmpty()) {
                            return null;
                        }
                        AreasList areas = new AreasList();
                        areas.setAreaCode(area.get(0).getAreaCode());
                        areas.setAreaName(area.get(0).getAreaName());
                        return areas;
                    })
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
            if (!areasList.isEmpty()) {
                record.setAreasList(areasList);
                record.setRemark("来访中,允许访问区域:" + record.getVisitAreaNames());
            }
        }
@@ -132,25 +209,51 @@
        }
        record.setRegisterNo(generateRegisterNo());//登记编号
        record.setRegisterType("ONSITE");//现场登记
//        record.setRegisterType("ONSITE");//现场登记
        record.setRegisterSource("访客机");
        record.setStatus("VISITING");//来访中
        record.setRegisterTime(new Date());
        record.setCreateTime(new Date());
        record.setDelFlag("0");
        // ================= 同步到对端服务器 =================
        try {
            AjaxResult syncResult = visitorRegisterSyncService.syncVisitorRegister(record);
            if (syncResult.isSuccess()) {
                logger.info("访客登记数据已同步到对端,远程ID:{}", syncResult.get("data"));
                // 可将远程ID保存到本地记录
//                record.setRemoteId(syncResult.getRemoteId());
                UpdateWrapper<SysVisitorRecord> wrapper = new UpdateWrapper<>();
                wrapper.eq("record_id", record.getRecordId());  // 指定ID条件
                visitorRecordMapper.update(record, wrapper);
            } else {
                String msg = (String) syncResult.get("msg");
                logger.error("访客登记同步失败:{}", msg);
                // 可选择是否允许本地登记成功但同步失败
                // 如果要保证数据一致性,这里可以抛出异常回滚事务
                throw new ServiceException(msg);
            }
        } catch (Exception e) {
            logger.error("访客登记同步异常", e);
            throw new ServiceException("服务器登记失败");
        }
        int rows = visitorRecordMapper.insert(record);
        sysVisitorRecordService.registerVisitor(record);
        if (rows > 0) {
            return AjaxResult.success(record);
        }
        return AjaxResult.error("登记失败");
    }
    @PostMapping("/idCardSingOut")
    @ApiOperation(value = "访客身份证签离", notes = "访客身份证签离")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult idCardSingOut(
            @RequestBody
            @ApiParam(value = "访客身份证签离", required = true)
@@ -195,7 +298,10 @@
        visitorRecord.setStatus(record.getStatus());
        visitorRecord.setRecordId(record.getRecordId());
        visitorRecord.setSignTime(new Date());
        visitorRecordMapper.updateById(visitorRecord);
        UpdateWrapper<SysVisitorRecord> wrapperUpdate = new UpdateWrapper<>();
        wrapperUpdate.eq("record_id", visitorRecord.getRecordId());  // 指定ID条件
        visitorRecordMapper.update(visitorRecord, wrapperUpdate);
        return success();
    }
ruoyi-applet/src/main/java/com/ruoyi/web/controller/service/ApiPullSignOutDataService.java
@@ -63,7 +63,7 @@
                // 1. 参数校验
                if (!StringUtils.hasText(request.getIdCard())) {
                    log.warn("身份证号为空,已跳过,设备编号: {}", request.getDeviceNo());
                    log.warn("身份证号为空,已跳过,人员姓名: {}", request.getIntervieweeName());
                    errorCount++;
                    continue;
                }
@@ -73,12 +73,12 @@
                    request.setSignImageUrl(url);
                }
                // 2. 防重校验(基于设备编号+身份证号+签离时间)
                // 2. 防重校验(身份证号+签离时间)
                String uniqueKey = generateUniqueKey(request);
                String redisKey = "offline:signout:" + uniqueKey;
                if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) {
                    log.warn("签离数据重复,已跳过, 设备编号: {}, 身份证号: {}, 签离时间: {}",
                            request.getDeviceNo(), request.getIdCard(), request.getSignTime());
                    log.warn("签离数据重复,已跳过, 身份证号: {}, 签离时间: {}",
                            request.getIdCard(), request.getSignTime());
                    skipCount++;
                    continue;
                }
@@ -94,8 +94,8 @@
                }
            } catch (Exception e) {
                log.error("处理签离数据失败, 身份证号: {}, 设备编号: {}",
                        request.getIdCard(), request.getDeviceNo(), e);
                log.error("处理签离数据失败, 身份证号: {}",
                        request.getIdCard(), e);
                errorCount++;
            }
        }
@@ -362,7 +362,7 @@
     */
    private String generateUniqueKey(ApiVisitorRecodeSingoutRequest request) {
        StringBuilder sb = new StringBuilder();
        sb.append(request.getDeviceNo()).append(":");
//        sb.append(request.getDeviceNo()).append(":");
        sb.append(request.getIdCard()).append(":");
        sb.append(request.getSignTime() != null ?
                request.getSignTime().getTime() : System.currentTimeMillis());
ruoyi-applet/src/main/java/com/ruoyi/web/controller/service/ApiPullSingInDataService.java
@@ -59,7 +59,7 @@
                // 1. 参数校验
                if (!StringUtils.hasText(request.getIdCard())) {
                    log.warn("身份证号为空,已跳过,设备编号: {}", request.getDeviceNo());
                    log.warn("身份证号为空,已跳过,访问人姓名: {}", request.getVisitorName());
                    errorCount++;
                    continue;
                }
@@ -73,8 +73,8 @@
                String uniqueKey = generateUniqueKey(request);
                String redisKey = "offline:signin:" + uniqueKey;
                if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) {
                    log.warn("签到数据重复,已跳过, 设备编号: {}, 身份证号: {}, 登记时间: {}",
                            request.getDeviceNo(), request.getIdCard(), request.getRegisterTime());
                    log.warn("签到数据重复,已跳过, 身份证号: {}, 登记时间: {}",
                            request.getIdCard(), request.getRegisterTime());
                    skipCount++;
                    continue;
                }
@@ -135,7 +135,7 @@
        BeanUtils.copyProperties(request, record);
        record.setRegisterTime(registerTime);
        record.setRegisterSource("访客机");
        record.setRegisterType("ONSITE");
//        record.setRegisterType("ONSITE");
        record.setCreateTime(new Date());
        record.setDelFlag("0");
        record.setRegisterNo(generateRegisterNo());
@@ -148,8 +148,6 @@
        // 4. 如果有未关联的签离数据,直接关联
        if (signoutTemp != null) {
            // 设置签离信息
            record.setSignTime(signoutTemp.getSignTime());
            record.setSignReceiverName(signoutTemp.getSignReceiverName());
@@ -321,7 +319,7 @@
     */
    private String generateUniqueKey(ApiVisitorRecordSinginRequest request) {
        StringBuilder sb = new StringBuilder();
        sb.append(request.getDeviceNo()).append(":");
//        sb.append(request.getDeviceNo()).append(":");
        sb.append(request.getIdCard()).append(":");
        sb.append(request.getRegisterTime() != null ?
                request.getRegisterTime().getTime() : System.currentTimeMillis());
ruoyi-applet/src/main/java/com/ruoyi/web/controller/service/VisitorRegisterSyncService.java
New file
@@ -0,0 +1,191 @@
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.Map;
/**
 * 访客登记同步服务
 * 将本地访客登记数据同步到对端服务器
 */
@Slf4j
@Service
public class VisitorRegisterSyncService {
    @Value("${visitor.sync.url:}")
    private String syncUrl;
    @Value("${visitor.sync.enabled:false}")
    private Boolean syncEnabled;
    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 (!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) {
                String remoteId = jsonNode.has("data") && jsonNode.get("data").has("id")
                        ? jsonNode.get("data").get("id").asText() : null;
                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());
        }
    }
    /**
     * 构建同步请求参数
     */
    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 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);
    }
}
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java
@@ -44,16 +44,16 @@
    private String remark;
    @ApiModelProperty(value = "禁用备注")
    @TableField("disable_remark")
    @TableField(value = "disable_remark" ,exist = false)
    private String disableRemark;
    @ApiModelProperty(value = "操作时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @TableField("operating_time")
    @TableField(value = "operating_time" ,exist = false)
    private LocalDateTime operatingTime;
    @ApiModelProperty(value = "操作人  登录名(登录账号)")
    @TableField("operating_person")
    @TableField(value = "operating_person",exist = false)
    private String operatingPerson;
    public String getDisableRemark() {
@@ -82,6 +82,7 @@
    /** 请求参数 */
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    @TableField(exist = false)
    private Map<String, Object> params;
    public String getSearchValue()
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysVisitArea.java
@@ -1,5 +1,6 @@
package com.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -11,6 +12,7 @@
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("t_visit_area")
public class SysVisitArea extends BaseEntity
{
    private static final long serialVersionUID = 1L;
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysVisitorMachine.java
@@ -38,4 +38,7 @@
    /** 删除标识,0正常 1删除 */
    private String delFlag;
    /** 百度SDK指纹 */
    private String fingerprint;
}
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysVisitorRecord.java
@@ -1,10 +1,14 @@
package com.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.system.domain.vo.AreasList;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
import java.util.List;
/**
 * 访客登记记录 t_visitor_record
@@ -13,6 +17,7 @@
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("t_visitor_record")
public class SysVisitorRecord extends BaseEntity
{
    private static final long serialVersionUID = 1L;
@@ -88,4 +93,9 @@
    /** 删除标识:0正常 1删除 */
    private String delFlag;
    @TableField(exist = false)
    private List<AreasList> areasList;
    @TableField(exist = false)
    private String deviceId;
}
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysVisitorSignoutTemp.java
@@ -1,6 +1,7 @@
package com.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -47,6 +48,7 @@
     * 签离时间
     */
    @TableField("sign_time")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date signTime;
    /**
@@ -95,12 +97,14 @@
     * 创建时间
     */
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;
    /**
     * 更新时间
     */
    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date updateTime;
    /**
ruoyi-system/src/main/java/com/ruoyi/system/domain/api/ApiSysVisitorRecord.java
New file
@@ -0,0 +1,102 @@
package com.ruoyi.system.domain.api;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.system.domain.vo.AreasList;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
import java.util.List;
/**
 * 访客登记记录 t_visitor_record
 *
 * @author ruoyi
 */
@Data
@TableName("t_visitor_record")
public class ApiSysVisitorRecord
{
    private static final long serialVersionUID = 1L;
    /** 访客登记记录主键 */
    private Long recordId;
    /** 登记编号 */
    private String registerNo;
    /** 访客姓名 */
    private String visitorName;
    /** 访客身份证号 */
    private String idCard;
    /** 登记类型:APPOINTMENT预约登记 ONSITE现场登记 */
    private String registerType;
    /** 被访人 */
    private String intervieweeName;
    /** 被访人部门 */
    private String intervieweeDept;
    /** 登记接待人 */
    private String receiverName;
    /** 接待人验证方式 */
    private String receiverVerifyMethod;
    /** 访问区域ID,英文逗号分隔 */
    private String visitAreaIds;
    /** 访问区域名称,英文逗号分隔 */
    private String visitAreaNames;
    /** 车牌号 */
    private String plateNo;
    /** 来访事由 */
    private String visitReason;
    /** 有效期开始时间 */
    private Date validStartTime;
    /** 有效期结束时间 */
    private Date validEndTime;
    /** 登记状态 */
    private String status;
    /** 登记时间 */
    private Date registerTime;
    /** 登记来源 */
    private String registerSource;
    /** 访客人像地址 */
    private String faceImageUrl;
    /** 签离时间 */
    private Date signTime;
    /** 签离接待人 */
    private String signReceiverName;
    /** 签离验证方式 */
    private String signVerifyMethod;
    /** 签离人像地址 */
    private String signImageUrl;
    /** 删除标识:0正常 1删除 */
    private String delFlag;
    @TableField(exist = false)
    private List<AreasList> areasList;
    @TableField(exist = false)
    private String deviceId;
    private String remark;
    private Date createTime;
}
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/AreasList.java
New file
@@ -0,0 +1,9 @@
package com.ruoyi.system.domain.vo;
import lombok.Data;
@Data
public class AreasList {
    private String areaCode;
    private String areaName;
}
ruoyi-system/src/main/java/com/ruoyi/system/dto/ApiVisitorMachineRequest.java
@@ -17,4 +17,7 @@
    @ApiModelProperty("设备ID")
    @NotNull(message = "未找到该设备")
    private String machineId;
    @ApiModelProperty("百度指纹")
    private String fingerprint;
}
ruoyi-system/src/main/java/com/ruoyi/system/dto/ApiVisitorRecodeSingoutRequest.java
@@ -1,5 +1,6 @@
package com.ruoyi.system.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -16,8 +17,8 @@
@ApiModel(value = "ApiVisitorRecodeSingoutRequest", description = "离线上传的访客身份证签离")
public class ApiVisitorRecodeSingoutRequest
{
    @NotBlank(message = "设备编号不能为空")
    @ApiModelProperty(value = "设备编号")
//    @NotBlank(message = "设备编号不能为空")
    @ApiModelProperty(value = "设备编号,非必填")
    private String deviceNo;
    /**
@@ -42,8 +43,13 @@
    /** 签离时间 */
    @ApiModelProperty(value = "签离时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date signTime;
//    @NotBlank(message = "登记类型不能为空")
//    @ApiModelProperty(value = "登记类型:APPOINTMENT预约登记 ONSITE现场登记")
//    private String registerType;
    /** 签离接待人 */
    @ApiModelProperty(value = "签离接待人")
    private String signReceiverName;
ruoyi-system/src/main/java/com/ruoyi/system/dto/ApiVisitorRecordSinginRequest.java
@@ -17,8 +17,8 @@
@ApiModel(value = "ApiVisitorRecordSinginRequest", description = "离线模式签到数据")
public class ApiVisitorRecordSinginRequest
{
    @NotBlank(message = "设备编号不能为空")
    @ApiModelProperty(value = "设备编号")
//    @NotBlank(message = "设备编号不能为空")
    @ApiModelProperty(value = "设备编号,非必填")
    private String deviceNo;
    /**
@@ -48,6 +48,10 @@
    @ApiModelProperty(value = "接待人验证方式")
    private String receiverVerifyMethod;
    @NotBlank(message = "登记类型不能为空")
    @ApiModelProperty(value = "登记类型:APPOINTMENT预约登记 ONSITE现场登记")
    private String registerType;
    /**
     * 访问区域ID,英文逗号分隔
     */
@@ -70,21 +74,21 @@
     * 有效期开始时间
     */
    @ApiModelProperty(value = "有效期开始时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyyMMddHHmmss")
    private Date validStartTime;
    /**
     * 有效期结束时间
     */
    @ApiModelProperty(value = "有效期结束时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyyMMddHHmmss")
    private Date validEndTime;
    /**
     * 登记时间
     */
    @ApiModelProperty(value = "登记时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyyMMddHHmmss")
    private Date registerTime;
    /**
ruoyi-system/src/main/java/com/ruoyi/system/dto/SysVisitorRecordAddRequest.java
@@ -20,12 +20,22 @@
public class SysVisitorRecordAddRequest
{
    /**
     * 设备编号
     */
    @ApiModelProperty(value = "设备编号,非必传")
    private String deviceId;
    /**
     * 访客姓名
     */
    @NotBlank(message = "访客姓名不能为空")
    @ApiModelProperty(value = "访客姓名")
    private String visitorName;
    @NotBlank(message = "登记类型不能为空")
    @ApiModelProperty(value = "登记类型:APPOINTMENT预约登记 ONSITE现场登记")
    private String registerType;
    /**
     * 访客身份证号
     */
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysVisitorRecordMapper.java
@@ -21,6 +21,10 @@
@Mapper
public interface SysVisitorRecordMapper extends BaseMapper<SysVisitorRecord>
{
    // 显式覆盖 updateById 方法
    @Override
    int updateById(@Param("et") SysVisitorRecord entity);
    public List<SysVisitorRecordVO> pageList(@Param("query") SysVisitorRecordPageRequest query,
                                             @Param("pageInfo") PageInfo<SysVisitorRecordVO> pageInfo);
ruoyi-system/src/main/java/com/ruoyi/system/mapper/api/SysApiVisitorRecordMapper.java
New file
@@ -0,0 +1,27 @@
package com.ruoyi.system.mapper.api;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.system.domain.SysVisitorRecord;
import com.ruoyi.system.domain.api.ApiSysVisitorRecord;
import com.ruoyi.system.dto.SysVisitorRecordPageRequest;
import com.ruoyi.system.vo.SysVisitorPassRecordVO;
import com.ruoyi.system.vo.SysVisitorRecordDetailVO;
import com.ruoyi.system.vo.SysVisitorRecordVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.Date;
import java.util.List;
/**
 * 访客登记记录数据层
 *
 * @author ruoyi
 */
@Repository
public interface SysApiVisitorRecordMapper extends BaseMapper<ApiSysVisitorRecord>
{
}
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysVisitorMachineService.java
@@ -37,7 +37,7 @@
     * @param deviceId 设备ID
     * @return 影响行数
     */
    int activateDevice(String deviceId);
    int activateDevice(String deviceId, String fingerprint);
    /**
     * 注销设备
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysVisitorMachineServiceImpl.java
@@ -153,7 +153,7 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int activateDevice(String deviceId) {
    public int activateDevice(String deviceId, String fingerprint) {
        // 1. 根据deviceId查询设备
        SysVisitorMachine device = visitorMachineMapper.checkDeviceIdUnique(deviceId, null);
        if (device == null || device.getDelFlag().equals("1")) {
@@ -175,6 +175,7 @@
        update.setMachineId(device.getMachineId());
        update.setOnlineStatus("ONLINE");  // 或 "ACTIVATED",根据你的枚举定义
        update.setUpdateTime(new Date());
        update.setFingerprint(fingerprint);
        return visitorMachineMapper.updateMachine(update);
    }
ruoyi-system/src/main/resources/mapper/system/EmployeeMapper.xml
@@ -22,7 +22,7 @@
    <!-- 人脸图片不参与本接口逻辑,且 longtext 体积较大,列表中排除,减少不必要的数据传输 -->
    <sql id="Base_Column_List">
        employee_id, dept_id, employee_name, employee_no, contact_phone_cipher,
        employee_id, dept_id, employee_name, face_image, employee_no, contact_phone_cipher,
        contact_phone_hash, `position`, card_no, enabled, del_flag,
        create_by, create_time, update_by, update_time
    </sql>
@@ -50,7 +50,7 @@
        SELECT
        <include refid="Base_Column_List"/>
        FROM t_employee
        AND enabled = 1
        where enabled = 1
        AND del_flag = '0'
    </select>