Laravel 10考勤机数据同步问题:连接成功但返回空字符串
考勤机数据同步到Laravel应用时连接成功但返回空字符串的问题解决
问题描述
我在将考勤机数据同步到Laravel 10(PHP 8.1)应用时遇到问题:连接考勤机成功,但返回空字符串,fgets()调用返回false。之前试过ZKLib但响应太慢,所以自己用fsockopen实现同步逻辑。
相关代码
控制器同步方法
public function tarikDataAbsen(AttendanceMachineInformation $attendanceMachineInformation): JsonResponse { $ip = $attendanceMachineInformation->ip_address; $port = $attendanceMachineInformation->port; $key = $attendanceMachineInformation->key; $connect = fsockopen($ip, $port, $errno, $errstr, 1); dd($connect); // 打印显示连接资源正常 $buffer = ""; if ($connect) { $soapRequest = "<GetAttLog><ArgComKey xsi:type=\"xsd:integer\">" . $key . "</ArgComKey><Arg><PIN xsi:type=\"xsd:integer\">All</PIN></Arg></GetAttLog>"; $newLine = "\r\n"; fwrite($connect, "POST /iWsService HTTP/1.0" . $newLine); fwrite($connect, "Content-Type: text/xml" . $newLine); fwrite($connect, "Content-Length: " . strlen($soapRequest) . $newLine . $newLine); fwrite($connect, $soapRequest . $newLine); while ($response = fgets($connect, 1024)) { $buffer .= $response; } } else { echo "Koneksi Gagal"; } $buffer = $this->parseData($buffer, "<GetAttLogResponse>", "</GetAttLogResponse>"); // dd($buffer); $buffer = explode("\r\n", $buffer); $export = []; for ($a = 0, $aMax = count($buffer); $a < $aMax; $a++) { $data = $this->parseData($buffer[$a], "<Row>", "</Row>"); $export[$a]['pin'] = $this->parseData($data, "<PIN>", "</PIN>"); $export[$a]['waktu'] = $this->parseData($data, "<DateTime>", "</DateTime>"); $export[$a]['status'] = $this->parseData($data, "<Status>", "</Status>"); } return response()->json($export); }
数据解析方法
private function parseData($data, $p1, $p2): string { $data = ' ' . $data; $hasil = ''; $awal = strpos($data, $p1); if ($awal != '') { $akhir = strpos(strstr($data, $p1), $p2); if ($akhir != '') { $hasil = substr($data, ($awal + strlen($p1)), ($akhir - strlen($p1))); } } return $hasil; }
连接资源打印显示正常,但fgets()返回false,导致最终无数据返回。
解决步骤
1. 移除代码中断语句
代码中dd($connect)会直接终止后续所有逻辑执行,包括请求发送和响应读取,这是导致无数据的直接原因。
处理: 删除dd($connect);这一行,让代码完整执行。
2. 修正HTTP请求格式
考勤机SOAP接口对请求格式通常有严格要求,调整以下几点:
- 将HTTP协议版本从
HTTP/1.0改为HTTP/1.1,并增加必填的Host头 - 用
mb_strlen($soapRequest, '8bit')准确计算请求内容的字节长度(避免strlen()处理UTF-8时的误差) - 明确指定字符集和连接关闭策略
修改后的请求发送代码:
if ($connect) { $soapRequest = "<GetAttLog><ArgComKey xsi:type=\"xsd:integer\">" . $key . "</ArgComKey><Arg><PIN xsi:type=\"xsd:integer\">All</PIN></Arg></GetAttLog>"; $newLine = "\r\n"; $contentLength = mb_strlen($soapRequest, '8bit'); fwrite($connect, "POST /iWsService HTTP/1.1" . $newLine); fwrite($connect, "Host: " . $ip . $newLine); fwrite($connect, "Content-Type: text/xml; charset=utf-8" . $newLine); fwrite($connect, "Content-Length: " . $contentLength . $newLine); fwrite($connect, "Connection: Close" . $newLine . $newLine); fwrite($connect, $soapRequest); // 读取响应 while (!feof($connect)) { $buffer .= fgets($connect, 1024); } fclose($connect); // 主动关闭连接 }
3. 调整超时与读取逻辑
- 延长
fsockopen的超时时间,从1秒改为5秒:fsockopen($ip, $port, $errno, $errstr, 5); - 用
feof()判断连接状态,避免fgets()提前返回false
4. 优化解析方法逻辑
原parseData方法中,strpos返回0时会被错误判断为空字符串,导致无法匹配开头的标签。修改判断逻辑为严格的!== false:
private function parseData($data, $p1, $p2): string { $hasil = ''; $awal = strpos($data, $p1); if ($awal !== false) { $subData = substr($data, $awal + strlen($p1)); $akhir = strpos($subData, $p2); if ($akhir !== false) { $hasil = substr($subData, 0, $akhir); } } return $hasil; }
5. 调试完整响应
在解析SOAP响应前,先打印完整的$buffer,确认是否存在HTTP错误(如400 Bad Request),以便定位接口问题:
// 在解析前添加 dd($buffer);
内容的提问来源于stack exchange,提问作者Firman
相关产品推荐
相关产品推荐

