get_one("select * from tb_pos_device where serialno = '".addslashes($serialno)."'"); $device_id = intval($deviceInfo['id']); // 获取远程地址,即访问的客户端ip地址 $ip = $_SERVER["REMOTE_ADDR"]; $s1 = $json; if(strlen($s1) > 2000) { $s1 = substr($s1, 0, 2000).'..'; } // 于请求中获取了serialno、remote_addr信息 // 通过serialno从数据库中获取了设备信息,在顶层中获取了记录id、company_id、dining_hall_id和channel_id信息 // 并于请求的POST参数中获取了sign和其他详细信息 // 同时将记录插入到日志中 // 插入POS机日志记录 $db->query("insert into tb_pos_device_log set device_id = {$device_id}, serialno = '".addslashes($serialno)."', ip = '".addslashes($ip)."', path = '".addslashes($qs)."', request = '".addslashes($s1)."', response = '', addtime = now() "); $log_id = $db->insert_id(); if(!$deviceInfo) { $db->query("update tb_pos_device_log set response='no_device' where id=".$log_id); exit; } // 从数据记录中获取信息 $company_id = intval($deviceInfo['company_id']); $dining_hall_id = intval($deviceInfo['dining_hall_id']); $channel_id = intval($deviceInfo['channel_id']); // md5编码签名 $sign2 = md5($post['time'].$post['noncestr'].$deviceInfo['cardpwd']); if($sign2 !== $post['sign']) { //签名错误 $db->query("update tb_pos_device_log set response='sign_err' where id=".$log_id); exit; } // 将api和data数据处理编码后插入到tb_pos_device_log表,并终止脚本运行 // 回应时填充参数、返回的API接口数据、错误提示等。 function response_pos($api, $data) { global $deviceInfo, $post, $log_id, $db; $noncestr = md5(microtime().'_'.rand()); $time = time(); $sign = md5($time.$noncestr.$deviceInfo['cardpwd']); $data['sign'] = $sign; $data['api'] = $api; $data['interval'] = '10000'; $data['transaction_id'] = $post['transaction_id']?$post['transaction_id']:"100"; $json = json_encode($data, JSON_UNESCAPED_UNICODE); $db->query("update tb_pos_device_log set response='".addslashes($json)."' where id=".$log_id); echo $json; exit; } // 向本机地址发送请求,拼接端口号、路径、post参数和header,调用自定义gquery函数 function self_query($path, $post) { return gquery("http://127.0.0.1:".$_SERVER['SERVER_PORT'].$path, $post, array('Host: '.$_SERVER['SERVER_NAME'])); } // 从post参数中获取pay_mode等参数 function pos_check_user() { global $post, $db, $company_id; $pay_mode = $post['params']['pay_mode']; $pay_code = $post['params']['pay_code']; if($pay_mode == '5') { //二维码 $qr_code = trim($post['params']['qr_code']); if(!$qr_code) exit; if(substr($qr_code, 0, 4) != '[st]' || substr($qr_code, -1) != ';') exit; // 截取qr_code,截取第4位到倒数第2位字符 $s = substr($qr_code, 4, -1); // 将字符串分割成数组 $a = explode(",", $s); if(count($a) != 2) exit; $code = $a[0]; $hash = $a[1]; // 关联数组,返回类似json的结构 return array('type' => 'qr', 'code' => $code); } // 搜索父字符串中子字符串的位置,如果pay_code中不以ymzs_开头,则退出 if(strpos($pay_code, 'yzms_') !== 0) exit; $uid = intval(substr($pay_code, 5)); $uInfo = $db->get_one("select * from tb_user where id = '{$uid}' and enabled != '0' and company_id=".$company_id); if(!$uInfo) { $arr = array ( 'result_code' => '2', 'result_msg' => "err", //10寸屏蔽失败时界面显示此内容,result_code=2 'tts' => "用户不存在", //语音播报,为空时不报,TTS语音 'result' => array (), //服务器返回结果数据,查询信息无 'timeout' => '5', //取餐超时时间,秒为单位,注:特殊修改机型才有此功能 'msg' => array (array ('line' => '用户不存在!',)), ); response_pos($post['api'], $arr); } return $uInfo; } // 顶层代码,判断qs中参数 if($qs == 'heartbeat' || $qs == 'addperson' || $qs == 'delperson') { //心跳 // 新增人员 // 设备收到服务器心跳包响应需要执行人员增加之后,向服务器发送设备增加名单完成应答接口 if($qs == 'addperson' && $post['whitelist']) { // 循环遍历人员名单 foreach($post['whitelist'] as $item) { // 将account_id中的yzms_前缀去掉 $uid = intval(str_replace('yzms_', '', $item['account_id'])); // 获取记录 $rec_id = intval($item['rec_id']); // 更新用户设备表中对应记录rec_id和对应用户uid的状态,status=2表示确认增加成功 $db->query("update tb_pos_device_user set status=2,rs='".$item['result_code']."' where id = '{$rec_id}' and uid = '{$uid}'"); } } // 删除人员 if($qs == 'delperson' && $post['whitelist']) { // 循环遍历人员名单 foreach($post['whitelist'] as $item) { // 从account_id中去掉yzms_前缀 $uid = intval(str_replace('yzms_', '', $item['account_id'])); // 获取记录id $rec_id = intval($item['rec_id']); $db->query("delete from tb_pos_device_user where id = '{$rec_id}' and uid = '{$uid}'"); } } // 如果当前查询并非心跳,或者当前时间和设备的synctime差超过1分钟,则执行 if($qs != 'heartbeat' || (time() - strtotime($deviceInfo['synctime']) > 60)) { //1分钟检测一次用户同步 // 更新posdevice表的同步时间 $db->query("update tb_pos_device set synctime=now() where id = ".$device_id); // 从tb_user表中获取所有启用且属于公司公司的用户列表, $userList_s = $db->get_all("select id, username, cellphone, deptname from tb_user where enabled = '1' and company_id = '{$company_id}'"); // 从tb_pos_device_user表中获取对应用户设备信息 // 新版Android取餐机,宇航用于下发人脸、用户信息到Android取餐机用的记录表,方便知道每台机现在的同步情况 $userList_d = $db->get_all("select * from tb_pos_device_user where device_id = '{$device_id}'"); // tb_user_face和tb_user表关联,获取对应信息 $data = $db->get_all("select a.* from tb_user_face a, tb_user b where a.user_id=b.id and b.company_id = '{$company_id}'"); $faceInfo = array(); // 提取上一步得到的关联信息并添加到faceInfo数组中,判断此文件是否存在于服务器后台目录,存在则将item信息存进dict数据中,key是user_id,value是item foreach($data as $item) { if(is_file("../backstage/".$item['path'])) { $faceInfo[$item['user_id']] = $item; } } // 关联用户表和用户id卡表,获取信息 $data = $db->get_all("select a.* from tb_user_idcard a, tb_user b where a.user_id=b.id and b.company_id = '{$company_id}' and a.state=1 order by a.id"); $cardInfo = array(); // 提取上一步获取的信息并将数据循环加入到卡信息数组中,类似List[Dict],key是user_id,value是item foreach($data as $item) { $cardInfo[$item['user_id']] = $item; } // 从获取的该公司user_list表信息中循环遍历,获取对应用户id的facecode,path,cardno信息,添加到userList_s数组中 // 最终facecode,path和cardno作为用户设备需要下发的信息添加到userList_s中 foreach($userList_s as $key => $item) { $uid = $item['id']; $info1 = $faceInfo[$uid]; $userList_s[$key]['facecode'] = $info1['facecode'].''; $userList_s[$key]['facepath'] = $info1['path'].''; $info1 = $cardInfo[$uid]; $userList_s[$key]['cardno'] = $info1['cardno'].''; } $sUserInfo = array(); // 遍历更新完成的userList_s中数据,存入sUserInfo数组中,key是用户id,value是对应的item信息 // ["id1" => {xx}, "id2" => {xx}, "id3" => {xx}] foreach($userList_s as $item) { $sUserInfo[$item['id']] = $item; } $dUserInfo = array(); // 遍历获取的tb_pos_device_user表,即用户设备表,将其数据通过循环变为uid => item数据的数组 // 最终得到uid => item信息的数组 foreach($userList_d as $item) { $dUserInfo[$item['uid']] = $item; } // 从全局配置表中获取域名,需要注意新版表中将class字段更改成了name字段 $row = $db->get_one("select * from tb_config where class = 'HOST'"); // 设置域名 $host = $row['value']; $addList = array(); //增加列表 $maxcount = 100; // 遍历用户表 foreach($userList_s as $item) { $uid = $item['id']; // 设备用户表不存在对应用户的信息 if(!$dUserInfo[$uid]) { // 往数组末尾添加一个item $addList[] = $item; if(count($addList) >= $maxcount) break; //限制一次20个 continue; } // 设备表的用户信息 $item2 = $dUserInfo[$uid]; // 如果设备用户表的记录和用户表的记录不一致 if( ($item2['facecode'].'') !== ($item['facecode'].'') || ($item2['cardno'].'') !== ($item['cardno'].'') || ($item2['username'].'') !== ($item['name'].'') || ($item2['cellphone'].'') !== ($item['cellphone'].'') || ($item2['deptname'].'') !== ($item['deptname'].'') ) { // 则将item加到addList末尾 $addList[] = $item; if(count($addList) >= $maxcount) break; continue; } // 设备用户表的status为1,表示已下发增加,但还未收到确认,并且距上次下发5分钟后还没收到确认 if($item2['status'] == 1 && time()-strtotime($item2['sendtime']) > 300) { //距上次下发5分钟后还没收到确认 // 增加item到addList $addList[] = $item; if(count($addList) >= $maxcount) break; continue; } } // 增加列表 if($addList) { $whitelist = array(); foreach($addList as $item) { $uid = $item['id']; $face_url = ''; // 拼接人脸路径 if($item['facepath']) { $face_url = 'https://'.$host.'/backstage/'.$item['facepath']; } $row1 = $db->get_one("select * from tb_pos_device_user where device_id = '".addslashes($device_id)."' and uid = '".addslashes($uid)."'"); // sql拓展字符串 $sqlext = " name = '".addslashes($item['username'])."', cellphone = '".addslashes($item['cellphone'])."', deptname = '".addslashes($item['deptname'])."', cardno = '".addslashes($item['cardno'])."', facecode = '".addslashes($item['facecode'])."', sendtime = now(), status = 1, rs = '' "; // 如果用户设备表中没有对应设备id和用户id的数据 if(!$row1) { // 插入记录 $db->query("insert into tb_pos_device_user set device_id = '".addslashes($device_id)."', uid = '".addslashes($uid)."', {$sqlext}, addtime = now() ", 'SILENT'); $log_id1 = $db->insert_id(); } else { // 有则更新 $db->query("update tb_pos_device_user set {$sqlext} where id=".$row1['id']); $log_id1 = $row1['id']; } // 人员名单 $whitelist[] = array ( 'rec_id' => $log_id1.'', //可为数据库中的唯一id,可以是字符串,可以是整数,可为空字符 'account_id' => 'yzms_'.$uid, //帐号,唯一,50个字符,必填 'emp_id' => 'emp_'.$uid, //工号,唯一,50个字符,必填 'emp_fname' => $item['username'], //姓名,50个字符,必填 'depart_name' => $item['deptname'], //部门,50个字符,必填 'job_name' => '', //职务,仅10.1寸去向牌门禁机有效 'tel' => $item['cellphone'], //电话,仅10.1寸去向牌门禁机有效 'sex' => '', //性别,可为空字符 'birth_date' => '', //出生日期,可为空字符 'valid_date' => '2099-01-01', //有效日期,必填 'level_id' => '1', //级别,整数,必须是整数,必填,用不到的话填0 'card_sn' => $item['cardno'], //卡序列号,整数,没有时,用空字符,注意:卡号时前面不能有0 'door_right' => '', //门权限, 预留,没有时用空字符 'url' => $face_url, //个人相片的url地址,可为空字符,表示无相片 'groups' => '0', //组别为第3组,长整型,二进制时从最低位开始为1组,0表示无,1表示有,例如第3组的二进制表示0000100,转换为十进制则是4,第3组的groups为4,必填,不需要时填0 'access_pwd' => '1234', //个人密码,整数,4位,不足补0,必填 'state' => '0', //整数,状态:0正常 2挂失(只有刷卡时才会判断,人脸不判断,另离线时才判断,在线由平台判断),为整数,不能非整数,必填 'twins' => '', //注意固定为空!,必填 'retain_photo' => '0', //整数,下载个人相片处理方式, 仅url为空时才有效, = 0默认删除相片 、 =1保留相片还要识别,必填,无相片时统一此值用0,当url有值时按url的值处理,可以固定为0 'begin_date' => '2022-04-01 00:00:00',//开始时间 'end_date' => '2099-01-01 23:59:59', //结束时间 'time' => array (), ); } $arr = array ( 'whitelist' => $whitelist, ); // 响应增加人员的api,向设备回送数据,往设备增加人员信息 response_pos('addperson', $arr); exit; } $delList = array(); //删除列表 $maxcount = 100; foreach($userList_d as $item) { $uid = $item['uid']; // status为4,表示已下发删除 if($item['status'] == 4) { if(time()-strtotime($item['sendtime']) > 300) { //距上次下发删除5分钟后还没收到确认 // 错误次数小于3 if($item['err_num'] < 3) { // 更新用户设备表错误次数 $db->query("update tb_pos_device_user set sendtime = now(), status = 4, rs = '',err_num=err_num+1 where id=".$item['id']); // 追加删除列表 $delList[] = $item; if(count($delList) >= $maxcount) break; //限制一次20个 } else { // 错误次数大于等于3,直接删除记录 $db->query("delete from tb_pos_device_user where id=".$item['id']); } } continue; } // 不在用户表中,添加item到删除列表 if(!$sUserInfo[$uid]) { $delList[] = $item; if(count($delList) >= $maxcount) break; //限制一次20个 continue; } } // 数据更新完成,开始删除列表 if($delList) { $whitelist = array(); foreach($delList as $item) { $log_id1 = $item['id']; $uid = $item['uid']; $whitelist[] = array ( 'rec_id' => $log_id1.'', //服务器端数据库的唯一id 'account_id' => 'yzms_'.$uid, //帐号 'emp_id' => 'emp_'.$uid, //工号 'only_del_photo' => '0', //=0删除名单 =1名单不删除只删除个人相片 ); } $arr = array ( 'whitelist' => $whitelist, ); // 删除人员 response_pos('delperson', $arr); exit; } } // 记录 $records = $post['records']; $arr = array ( 'setmname' => '', ); // 如果有记录 if($records) { $records2 = array(); // 遍历记录并将record的记录变成"id" => realid,加入到record2中 // like: [[id => 1], [id => 2], [id => 3]...] foreach($records as $item) { $records2[] = array('id' => $item['id']); } $arr['records'] = $records2; } // 服务器向设备回送心跳包,包括记录 response_pos('heartbeat', $arr); } // 在线消费,无消费确认,无查询,直接消费 else if($qs == 'real') { $api = $post['api']; if($api == 'takemeal' || $api == 'takemealok') { //取餐 $uInfo = pos_check_user(); $uid = $uInfo['id']; function get_today_dates($dining_hall_id) { global $db; $data = $db->get_all("select * from tb_date where dining_hall_id={$dining_hall_id} and dc_date = '".date("Y-m-d")."'"); $nowtime = date('H:i'); $all = array(); foreach($data as $item) { $dc_type = $item['dc_type']; $meal_info = $db->get_one("select * from tb_meal_type where status=1 and id=".intval($dc_type)); if( $nowtime >= $meal_info['start_time'] && $nowtime < $meal_info['end_time']) { $all[] = $item; } } return $all; } $dates = get_today_dates($dining_hall_id); $order = ''; for($i = 1; $i <= 2; $i++) { foreach($dates as $date) { $sqlext = " uid = {$uid} "; if($uInfo['type'] == 'qr') { //取餐码 $sqlext = " take_food_code = '".addslashes($uInfo['code'])."' "; } $row = $db->get_one("select * from tb_order where {$sqlext} and date_id=".$date['id']." and state_id =".($i==1?6:3)); if($row) { $order = $row; if($uInfo['type'] == 'qr') { //取餐码 $uInfo = $db->get_one("select * from tb_user where id = '".$order['uid']."'"); if(!$uInfo) exit; $uid = $uInfo['id']; } break; } } if($order) break; } if(!$order) { if($uInfo['type'] == 'qr') { //取餐码 $arr = array ( 'result_code' => '2', 'result_msg' => "err", //10寸屏蔽失败时界面显示此内容,result_code=2 'tts' => "取餐码错误", //语音播报,为空时不报,TTS语音 'result' => array (), //服务器返回结果数据,查询信息无 'msg' => array ( array ( 'line' => '取餐码错误', ), ), 'timeout' => '5', //取餐超时时间,秒为单位,注:特殊修改机型才有此功能 ); } else { $arr = array ( 'result_code' => '2', 'result_msg' => "err", //10寸屏蔽失败时界面显示此内容,result_code=2 'tts' => "没有该时段的订餐记录", //语音播报,为空时不报,TTS语音 'result' => array (), //服务器返回结果数据,查询信息无 'msg' => array ( array ( 'line' => '没有该时段的订餐记录', ), array ( 'line' => '姓名: '.$uInfo['username'], ), array ( 'line' => '手机号: '.$uInfo['cellphone'], ), ), 'timeout' => '5', //取餐超时时间,秒为单位,注:特殊修改机型才有此功能 ); } response_pos($api, $arr); } $s = self_query("/api/order/take", array('take_code' => $order['take_food_code'], 'dining_hall_id' => $dining_hall_id, 'channelid' => $channel_id)); $rs = json_decode($s, true); if($rs['status'] != 1) { $message = '取餐失败'; if($rs['message']) $message = $rs['message']; $arr = array ( 'result_code' => '2', 'result_msg' => "err", //10寸屏蔽失败时界面显示此内容,result_code=2 'tts' => $message, //语音播报,为空时不报,TTS语音 'result' => array (), //服务器返回结果数据,查询信息无 'timeout' => '5', //取餐超时时间,秒为单位,注:特殊修改机型才有此功能 'msg' => array (array ('line' => $message,)), ); response_pos($api, $arr); } $takeInfo = $rs['data']['list'][0]; $detail = array(); foreach($takeInfo['order_detail'] as $item) { $detail[] = array ( 'menu' => $item['dish_name'], 'amount' => $item['dish_amount'], ); } $take_status = $takeInfo['take_status']; $tts = ''; $result_msg = ''; $timeout = '5'; if($take_status == 1) { $tts = ($api == 'takemealok'?($uInfo['username']."取餐成功"):""); $result_msg = ($api == 'takemealok'?"取餐成功":"查询成功"); $timeout = ($api == 'takemealok'?"1":"10"); } else { $tts = '您已取过餐'; $result_msg = '您已取过餐'; $timeout = '5'; } $arr = array ( 'result_code' => ($take_status==1?'0':'1'), //0成功 非0失败,1表示已取餐,也要显示菜品信息,2其它失败 'result_msg' => $result_msg, //10寸屏蔽失败时界面显示此内容,result_code=2 'tts' => $tts, //语音播报,为空时不报,TTS语音 'result' => array (), //服务器返回结果数据,查询信息无 'msg' => array ( array ( 'line' => '订单号:'.$order['code'], ), array ( 'line' => '姓名: '.$uInfo['username'], ), array ( 'line' => '手机号: '.$uInfo['cellphone'], ), ), 'detail' => $detail, 'timeout' => $timeout, //取餐超时时间,秒为单位,注:特殊修改机型才有此功能 ); response_pos($api, $arr); } else if($api == 'infoquery') { //消费前查询用户 $uInfo = pos_check_user(); $uid = $uInfo['id']; $row = $db->get_one("select sum( account ) as c from tb_account where user_id = '{$uid}'"); $arr = array ( 'tts' => '', 'msg' => array (), 'balance' => '', //余额显示测试无效 'query_code' => '0', //注意取餐机时应答为1,消费机时为0 'result_code' => '0', 'result' => array ( array ( 'account_id' => '', 'consume' => '', 'balance' => round($row['c']*100).'', 'sign' => '', 'Corrections' => '', ) ), 'title' => '云中美食', 'emp_fname' => $uInfo['username'], 'result_msg' => '', 'detail' => array (), ); response_pos('infoquery', $arr); } else if($api == 'posonline') { //消费 $uInfo = pos_check_user(); $uid = $uInfo['id']; $amount = intval($post['params']['amount']); if($amount <= 0) exit; $amount = $amount/100; $pay_mode = $post['params']['pay_mode']; // 0人脸 1云卡 2卡序列号 3取餐码 5二维码 $post = array('ftid' => $dining_hall_id, 'channelid' => $channel_id, 'fee' => $amount); if($pay_mode == 0) { //0人脸 $row1 = $db->get_one("select * from tb_user_face where user_id = '{$uid}'"); if(!$row1) exit; $post['type'] = 'face'; $post['code'] = $row1['card']; } else if($pay_mode == 2) { //2卡序列号 $row1 = $db->get_one("select * from tb_user_idcard where user_id = '{$uid}' and state = '1'"); if(!$row1) exit; $post['type'] = 'ic'; $post['code'] = $row1['cardno']; } else if($pay_mode == 5) { //5付款码 if($uInfo['type'] != 'qr') exit; $post['type'] = 'qr'; $post['code'] = $uInfo['code']; } else { exit; } // 服务器向本机/api/usr/qrpay接口发送数据 $s = self_query("/api/user/qrpay", $post); $rs = json_decode($s, true); if($rs['status'] != 1) { $message = '付款失败'; if($rs['message']) $message = $rs['message']; $arr = array ( 'result_code' => '2', 'result_msg' => "err", //10寸屏蔽失败时界面显示此内容,result_code=2 'tts' => $message, //语音播报,为空时不报,TTS语音 'result' => array (), //服务器返回结果数据,查询信息无 'timeout' => '5', //取餐超时时间,秒为单位,注:特殊修改机型才有此功能 'msg' => array (array ('line' => $message,)), ); response_pos($api, $arr); } if($uInfo['type'] == 'qr') { $row1 = $db->get_one("select * from tb_payqr where code = '".addslashes($uInfo['code'])."'"); $uid = intval($row1['uid']); $uInfo = $db->get_one("select * from tb_user where id = '".$uid."'"); } $arr = array ( 'result_code' => '0', //0成功,7需要密码消费,消费机出现输入密码的界面,密码输入正确后,消费机再次调用接口,psd_state=1,服务器收到为1时,则消费成功,其它失败 'result_msg' => '', 'result' => array (), 'tts' => $uInfo['username'].' 消费'.$amount.'元成功', 'timeout' => '10', 'msg' => array ( array ( 'line' => '姓名: '.$uInfo['username'], ), array ( 'line' => '消费: '.$amount.'元', ), array ( 'line' => '余额: '.$rs['data']['balance'].'元', ), ), ); response_pos('posonline', $arr); } } else if($qs == 'takephoto') { $arr = array ( 'result_code' => '0', //0成功 'result_msg' => '', 'result' => array (), ); response_pos('takephoto', $arr); }