package jiekou; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * TCP客户端工具类 * 实现发卡机与服务器的所有通信接口 */ public class TcpClientUtil { private static final Logger logger = LoggerFactory.getLogger(TcpClientUtil.class); private Socket socket; private OutputStream outputStream; private InputStream inputStream; private BufferedReader reader; private volatile boolean running = false; private MessageListener messageListener; // 重连相关配置 private String serverIp; private int serverPort; private String deviceId; private final AtomicBoolean reconnecting = new AtomicBoolean(false); private static final int RECONNECT_INTERVAL = 3000; // 重连间隔3秒 // JSON解析模式 private static final Pattern JSON_PATTERN = Pattern.compile("\"([^\"]+)\"\\s*:\\s*(\"[^\"]*\"|[^,\\}\\s]*)"); private static final Pattern ARRAY_PATTERN = Pattern.compile("\\{([^}]*)\\}"); /** * 连接TCP服务器 * @param ip 服务器IP地址 * @param port 服务器端口 * @return 连接是否成功 */ public boolean connect(String ip, int port) { try { // 保存连接参数用于重连 this.serverIp = ip; this.serverPort = port; // 1. 创建Socket连接服务器 this.socket = new Socket(ip, port); // 移除超时设置,让监听线程可以无限等待 socket.setSoTimeout(0); // 2. 获取输入输出流 this.outputStream = socket.getOutputStream(); this.inputStream = socket.getInputStream(); this.reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); logger.info("成功连接到服务器: {}:{}", ip, port); reconnecting.set(false); // 重置重连状态 return true; } catch (IOException e) { logger.error("TCP连接异常: {}", e.getMessage(), e); return false; } } /** * 连接并登录服务器 * @param ip 服务器IP地址 * @param port 服务器端口 * @param deviceId 设备ID * @return 登录是否成功 */ public boolean connectAndLogin(String ip, int port, String deviceId) { this.deviceId = deviceId; // 保存设备ID用于重连后重新登录 if (!connect(ip, port)) { return false; } Map response = sendLoginRequest(deviceId); return response != null && "logined".equals(response.get("cmd")) && "100".equals(String.valueOf(response.get("code"))); } /** * 发送登录请求 * @param deviceId 设备ID * @return 登录响应数据 */ public Map sendLoginRequest(String deviceId) { try { // 构建登录请求JSON String loginRequest = buildLoginRequestJson(deviceId); logger.debug("发送登录请求: {}", loginRequest); // 发送登录请求 boolean sendSuccess = sendMessage(loginRequest); if (!sendSuccess) { return null; } // 读取服务器响应 String response = reader.readLine(); logger.info("收到服务器登录响应: {}", response); // 解析响应并返回 return parseJson(response); } catch (IOException e) { logger.error("登录请求异常: {}", e.getMessage(), e); return null; } } /** * 请求人员数据 * @param deviceId 设备ID * @return 人员数据列表 */ public List> requestPersonData(String deviceId) { try { // 构建人员数据请求JSON String batchGetRequest = buildBatchGetRequestJson(deviceId); logger.debug("发送人员数据请求: {}", batchGetRequest); // 发送请求 boolean sendSuccess = sendMessage(batchGetRequest); if (!sendSuccess) { return null; } // 读取服务器响应(人员数据) String response = reader.readLine(); logger.info("收到人员数据: {}", response); // 解析响应 Map responseMap = parseJson(response); if ("send".equals(responseMap.get("cmd")) && "101".equals(String.valueOf(responseMap.get("code")))) { // 解析人员数据 String dataStr = (String) responseMap.get("data"); List> personList = parsePersonDataArray(dataStr); // 发送成功应答 sendSentAck(); return personList; } return null; } catch (IOException e) { logger.error("请求人员数据异常: {}", e.getMessage(), e); return null; } } /** * 发送人员数据接收成功应答 */ public boolean sendSentAck() { try { String ackJson = "{\"code\":101,\"cmd\":\"sent\",\"data\":null}"; return sendMessage(ackJson); } catch (Exception e) { logger.error("发送成功应答异常: {}", e.getMessage(), e); return false; } } /** * 发送删除人员成功应答 * @param userId 用户ID * @return 发送是否成功 */ public boolean sendDeleteAck(String userId) { try { String ackJson = "{\"code\":102,\"cmd\":\"deleted\",\"data\":null}"; boolean success = sendMessage(ackJson); if (success) { logger.info("发送删除人员成功应答,用户ID: {}", userId); } return success; } catch (Exception e) { logger.error("发送删除人员成功应答异常: {}", e.getMessage(), e); return false; } } /** * 发送开锁成功应答 * @param doorNo 门编号 * @return 发送是否成功 */ public boolean sendOpenDoorAck(String doorNo) { try { String ackJson = "{\"code\":103,\"cmd\":\"opened\",\"data\":null}"; boolean success = sendMessage(ackJson); if (success) { logger.info("发送开锁成功应答,门编号: {}", doorNo); } return success; } catch (Exception e) { logger.error("发送开锁成功应答异常: {}", e.getMessage(), e); return false; } } /** * 发送升级成功应答 * @param updateUrl 升级文件URL * @return 发送是否成功 */ public boolean sendUpdateAck(String updateUrl) { try { String ackJson = "{\"code\":104,\"cmd\":\"upDated\",\"data\":null}"; boolean success = sendMessage(ackJson); if (success) { logger.info("发送升级成功应答,升级URL: {}", updateUrl); } return success; } catch (Exception e) { logger.error("发送升级成功应答异常: {}", e.getMessage(), e); return false; } } /** * 发送自定义命令 * @param code 命令代码 * @param cmd 命令类型 * @param data 数据 * @return 发送是否成功 */ public boolean sendCommand(String code, String cmd, String data) { try { String json = buildCommandJson(code, cmd, data); boolean success = sendMessage(json); if (success) { logger.debug("发送命令: {}", json); } return success; } catch (Exception e) { logger.error("发送命令异常: {}", e.getMessage(), e); return false; } } /** * 通用消息发送方法 * @param message 要发送的消息 * @return 发送是否成功 */ public boolean sendMessage(String message) { if (!isConnected()) { logger.error("连接未建立,无法发送消息"); return false; } try { outputStream.write((message + "\n").getBytes(StandardCharsets.UTF_8)); outputStream.flush(); return true; } catch (IOException e) { logger.error("发送消息异常: {}", e.getMessage(), e); return false; } } /** * 读取服务器响应 * @return 服务器响应数据 */ public String readResponse() { try { return reader.readLine(); } catch (IOException e) { logger.error("读取响应异常: {}", e.getMessage(), e); return null; } } /** * 读取并解析服务器响应 * @return 解析后的响应Map */ public Map readAndParseResponse() { String response = readResponse(); if (response != null) { return parseJson(response); } return null; } /** * 断线重连功能 */ private void reconnect() { if (reconnecting.getAndSet(true)) { logger.debug("重连操作已在进行中,跳过本次重连"); return; } logger.info("开始断线重连..."); while (running) { try { logger.info("尝试重连,目标服务器: {}:{}", serverIp, serverPort); // 关闭旧连接 closeResources(reader, inputStream, outputStream, socket); // 等待一段时间后重连 Thread.sleep(RECONNECT_INTERVAL); // 尝试重新连接 if (connect(serverIp, serverPort)) { // 重新登录 if (deviceId != null) { Map loginResponse = sendLoginRequest(deviceId); if (loginResponse != null && "logined".equals(loginResponse.get("cmd")) && "100".equals(String.valueOf(loginResponse.get("code")))) { logger.info("重连并登录成功"); reconnecting.set(false); // 重新启动消息监听 if (messageListener != null) { startMessageListener(messageListener); } return; } } else { // 如果没有设备ID,只连接不登录 logger.info("重连成功"); reconnecting.set(false); if (messageListener != null) { startMessageListener(messageListener); } return; } } logger.warn("重连失败,{} 毫秒后重试", RECONNECT_INTERVAL); } catch (InterruptedException e) { Thread.currentThread().interrupt(); logger.warn("重连过程被中断"); break; } catch (Exception e) { logger.error("重连异常: {}", e.getMessage()); } } reconnecting.set(false); if (running) { logger.error("重连循环退出,将在下次断开后重新尝试"); } } /** * 启动消息监听器 * @param listener 消息监听回调接口 */ public void startMessageListener(MessageListener listener) { this.messageListener = listener; running = true; new Thread(() -> { try { while (running && socket != null && !socket.isClosed()) { try { String message = reader.readLine(); if (message != null) { logger.debug("收到服务器消息: {}", message); // 解析消息 Map parsedMessage = parseJson(message); // 回调给监听器 if (messageListener != null) { messageListener.onMessageReceived(message, parsedMessage); } } else { // 读到null表示连接已关闭 logger.warn("连接被服务器关闭"); break; } } catch (IOException e) { if (running) { logger.error("读取消息异常: {}", e.getMessage()); // 如果不是主动停止,尝试重连 if (running && !reconnecting.get()) { reconnect(); } break; } } } } finally { logger.info("消息监听线程结束"); // 如果还在运行状态但连接已断开,尝试重连 if (running && !reconnecting.get()) { reconnect(); } } }, "TCP-Client-Listener").start(); } /** * 停止消息监听 */ public void stopMessageListener() { running = false; messageListener = null; reconnecting.set(false); // 停止重连 } /** * 解析简单JSON字符串为Map * @param json JSON字符串 * @return 解析后的Map */ public Map parseJson(String json) { Map result = new HashMap<>(); if (json == null || json.trim().isEmpty()) { return result; } // 移除首尾的{} String content = json.trim(); if (content.startsWith("{") && content.endsWith("}")) { content = content.substring(1, content.length() - 1).trim(); } Matcher matcher = JSON_PATTERN.matcher(content); while (matcher.find()) { String key = matcher.group(1); String value = matcher.group(2).trim(); // 移除值的引号(如果存在) if (value.startsWith("\"") && value.endsWith("\"")) { value = value.substring(1, value.length() - 1); } // 处理null值 if ("null".equals(value)) { result.put(key, null); } else { result.put(key, value); } } return result; } /** * 解析人员数据数组 * @param dataStr 数据字符串 * @return 人员列表 */ private List> parsePersonDataArray(String dataStr) { List> result = new ArrayList<>(); if (dataStr == null || dataStr.trim().isEmpty()) { return result; } // 使用正则表达式匹配数组中的每个对象 Matcher matcher = ARRAY_PATTERN.matcher(dataStr); while (matcher.find()) { String objStr = matcher.group(1); Map person = parsePersonObject(objStr); if (!person.isEmpty()) { result.add(person); } } return result; } /** * 解析单个人员对象 * @param objStr 对象字符串 * @return 人员信息Map */ private Map parsePersonObject(String objStr) { Map person = new HashMap<>(); if (objStr == null || objStr.trim().isEmpty()) { return person; } // 分割键值对 String[] pairs = objStr.split(","); for (String pair : pairs) { String[] keyValue = pair.split(":", 2); if (keyValue.length == 2) { String key = keyValue[0].trim(); String value = keyValue[1].trim(); // 移除值的引号(如果存在) if (value.startsWith("'") && value.endsWith("'")) { value = value.substring(1, value.length() - 1); } else if (value.startsWith("\"") && value.endsWith("\"")) { value = value.substring(1, value.length() - 1); } person.put(key, value); } } return person; } /** * 构建登录请求JSON * @return JSON字符串 */ private String buildLoginRequestJson(String deviceId) { return "{\"code\":100,\"cmd\":\"login\",\"deviceId\":\"" + deviceId + "\"}"; } /** * 构建人员数据请求JSON * @return JSON字符串 */ private String buildBatchGetRequestJson(String deviceId) { return "{\"code\":101,\"cmd\":\"batchGet\",\"deviceId\":\"" + deviceId + "\"}"; } /** * 构建通用命令JSON * @param code 命令代码 * @param cmd 命令类型 * @param data 数据 * @return JSON字符串 */ private String buildCommandJson(String code, String cmd, String data) { if (data != null) { return "{\"code\":\"" + code + "\",\"cmd\":\"" + cmd + "\",\"data\":\"" + data + "\"}"; } else { return "{\"code\":\"" + code + "\",\"cmd\":\"" + cmd + "\",\"data\":null}"; } } /** * 关闭连接 */ public void disconnect() { stopMessageListener(); closeResources(reader, inputStream, outputStream, socket); logger.info("断开与服务器的连接"); } /** * 检查连接状态 * @return 是否连接 */ public boolean isConnected() { return socket != null && !socket.isClosed() && socket.isConnected(); } /** * 关闭所有资源 */ private static void closeResources(Closeable... resources) { for (Closeable resource : resources) { if (resource != null) { try { resource.close(); } catch (IOException e) { logger.error("关闭资源时发生异常: {}", e.getMessage(), e); } } } } /** * 消息监听回调接口 */ public interface MessageListener { /** * 当收到服务器消息时回调 * @param rawMessage 原始消息字符串 * @param parsedMessage 解析后的消息Map */ void onMessageReceived(String rawMessage, Map parsedMessage); } }