yzms/show/api_pos.php

658 lines
24 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?
// echo "1";
// exit;
require_once(dirname(__FILE__)."/common.php");
// 接收查询参数即URL路径参数后的查询参数params
$qs = $_SERVER['QUERY_STRING'];
// 读取发送到脚本的数据
$json = file_get_contents("php://input");
$post = json_decode($json, true, 512 , JSON_BIGINT_AS_STRING);
$serialno = $post['serialno'];
if(!$serialno) exit;
// 获取数据库表数据POS机设备信息
$deviceInfo = $db->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机日志记录
// device_id serialno ip path request response addtime
$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() ");
// 获取最近一次成功插入的记录的自增id
$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等参数实时消费接口中使用包括infoquery takemeal takemealok posonline
function pos_check_user() {
global $post, $db, $company_id;
$pay_mode = $post['params']['pay_mode'];
$pay_code = $post['params']['pay_code'];
// 如果是二维码则检查二维码的字符串是否符合规则并从中截取获得code和哈希
// 返回code值和type
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表关联获取对应信息
// user user_face
$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_idvalue是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_idvalue是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是用户idvalue是对应的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);
}