修改bug

main
yanzai 2 years ago
parent df201574d9
commit 8903efd2dc

Binary file not shown.

@ -12,10 +12,11 @@ class UserController extends Controller
$status =request('status'); $status =request('status');
$cname =request('cname'); $cname =request('cname');
$departmentid =request('departmentid'); $departmentid =request('departmentid');
$ward =request('ward');
$page =request('page'); $page =request('page');
$pagesize =request('pageSize'); $pagesize =request('pageSize');
$s=app()->make(UserService::class); $s=app()->make(UserService::class);
return $s->GetInfoList(['cname'=>$cname,'status'=>$status,'departmentid'=>$departmentid,'page'=>$page,'pagesize'=>$pagesize]); return $s->GetInfoList(['cname'=>$cname,'status'=>$status,'departmentid'=>$departmentid,'ward'=>$ward,'page'=>$page,'pagesize'=>$pagesize]);
} }
public function Save(){ public function Save(){
$userInfo =request('userInfo'); $userInfo =request('userInfo');

@ -48,33 +48,48 @@ class CheckItemController extends Controller
public function SetHuChi(Request $request) public function SetHuChi(Request $request)
{ {
$userid = $request->get('userid');//中间件产生的参数 $userid = $request->get('userid');//中间件产生的参数
$id = request('id');
$code1 = request('code1'); $code1 = request('code1');
$code2 = request('code2'); $code2 = request('code2');
$time = request('time'); $time = request('time');
if(!isset($time)) return \Yz::echoError1("时间不能为空"); if(!isset($time)) return \Yz::echoError1("时间不能为空");
$i=null;
$u=null;
if($id==0){
$q = DB::table('s_huchi')
->where('is_del', 0)
->where(function ($query) use ($code1, $code2) {
$query->where('code1', $code1)->where('code2', $code2);
})
->orWhere(function ($query) use ($code1, $code2) {
$query->where('code1', $code2)->where('code2', $code1);
})
->get();
if (count($q) > 0) return \Yz::echoError1("此条互斥已存在,请删除后再添加!");
$i = DB::table('s_huchi')->insertGetId([
'code1' => $code1,
'code2' => $code2,
'time' => $time,
'is_del' => 0,
'add_user' => $userid,
]);
}else{
$u=DB::table('s_huchi')->where(['id'=>$id,'is_del'=>0])->update([
'time'=>$time
]);
if($u){
$i= $id;
}
}
$q = DB::table('s_huchi')
->where('is_del', 0)
->where(function ($query) use ($code1, $code2) {
$query->where('code1', $code1)->where('code2', $code2);
})
->orWhere(function ($query) use ($code1, $code2) {
$query->where('code1', $code2)->where('code2', $code1);
})
->get();
if (count($q) > 0) return \Yz::echoError1("此条互斥已存在,请删除后再添加!");
$i = DB::table('s_huchi')->insert([
'code1' => $code1,
'code2' => $code2,
'time' => $time,
'is_del' => 0,
'add_user' => $userid,
]);
if ($i) { if ($i) {
return \Yz::Return(true, '设置成功', []); return \Yz::Return(true, '设置成功', ['code2'=>$code2,'id'=>$i]);
} else { } else {
return \Yz::echoError1("添加失败"); return \Yz::echoError1("操作失败");
} }
} }
@ -117,7 +132,22 @@ ON
WHERE WHERE
s_huchi.is_del = 0 AND s_huchi.is_del = 0 AND
(s_huchi.code1 = ? OR s_huchi.code2 = ?)",[$code,$code]); (s_huchi.code1 = ? OR s_huchi.code2 = ?)",[$code,$code]);
foreach ($q as $key=> $row) {
$r=[];
if ($row->code1 == $code) {
$r=$row;
} else if ($row->code2 == $code) {
$jsonString = json_encode($row);
$array = json_decode($jsonString, true);
$r=$array;
$r['code1_item_name'] = $array['code2_item_name'];
$r['code2_item_name'] = $array['code1_item_name'];
$r['code1'] = $array['code2'];
$r['code2'] = $array['code1'];
}
$q[$key]=$r;
}
return \Yz::Return(true, '操作成功', $q); return \Yz::Return(true, '操作成功', $q);
} }

@ -0,0 +1,62 @@
<?php
namespace App\Http\Controllers\API\Admin\YeWu;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class InpatientWardController extends Controller
{
//获取病区列表
public function GetList()
{
$page = request('page');
$pageSize = request('pageSize');
$searchInfo = request('searchInfo');
$list=DB::table('s_inpatient_ward')->where(['is_del'=>0]);
if(!empty($searchInfo['name'])){
$list=$list->where(['name','like','%'.$searchInfo['name'].'%']);
}
$count= $list->count();
$list= $list->skip(($page-1)*$pageSize) // 跳过前9999条记录
->take($pageSize)->get();
foreach ($list as $k=>$v){
$list[$k]->user_list=DB::table('users')
->whereRaw("FIND_IN_SET(?, ward)", [$v->name])
->where(['status'=>1])->get();
}
return \Yz::Return(true,"查询完成",['list'=>$list,'count'=>$count]);
}
public function Save(Request $request)
{
$userid = $request->get('userid');//中间件产生的参数
$data = request('Info');
if(empty($data['id'])){
$id=DB::table('s_inpatient_ward')->insertGetId($data);
if($id){
return \Yz::Return(true, '添加成功', $id);
}else{
return \Yz::Return(false, '添加失败');
}
}else{
$res=DB::table('s_inpatient_ward')->where('id',$data['id'])->update($data);
if($res){
return \Yz::Return(true, '修改成功');
}else{
return \Yz::Return(false, '修改失败');
}
}
}
public function Del()
{
$id = request('id');
$res=DB::table('s_inpatient_ward')->where('id',$id)->update(['is_del'=>1]);
if($res){
return \Yz::Return(true, '删除成功');
}else{
return \Yz::Return(false, '删除失败');
}
}
}

@ -3,6 +3,7 @@
namespace App\Http\Controllers\API\Admin\YeWu; namespace App\Http\Controllers\API\Admin\YeWu;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Services\Admin\YeWu\PlanListService;
use DateTime; use DateTime;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
@ -47,20 +48,21 @@ class WorkMainController extends Controller
if (!in_array($appointment_type, $qudaos)){ if (!in_array($appointment_type, $qudaos)){
$msg=$msg.',该项目不支持在此渠道预约'; $msg=$msg.',该项目不支持在此渠道预约';
} }
if(isset($ii[0]->check_begin_time)){ //判断开医嘱后多久能预约 // if(isset($ii[0]->check_begin_time)){ //判断开医嘱后多久能预约
$entrust_time = $value->entrust_date . ' ' . $value->entrust_time; //医嘱时间 // $entrust_time = $value->entrust_date . ' ' . $value->entrust_time; //医嘱时间
$date = new DateTime($entrust_time); // $date = new DateTime($entrust_time);
$date->modify("+" . $ii[0]->check_begin_time . " minutes"); // $date->modify("+" . $ii[0]->check_begin_time . " minutes");
$enableCheckTime = $date;//到此时间后可进行预约 // $enableCheckTime = $date;//到此时间后可进行预约
$current_time = new DateTime(); // $current_time = new DateTime();
if ($current_time < $enableCheckTime){ // if ($current_time < $enableCheckTime){
$msg=$msg.",请于" . $enableCheckTime->format("Y-m-d H:i:s") . "后进行预约"; // $msg=$msg.",请于" . $enableCheckTime->format("Y-m-d H:i:s") . "后进行预约";
} // }
} // }
} }
$itemInfo[]=[ $itemInfo[]=[
'maininfo'=>$value, 'maininfo'=>$value,
'iteminfo'=>$ii,
'msg'=>$msg 'msg'=>$msg
]; ];
} }
@ -164,18 +166,21 @@ class WorkMainController extends Controller
->where(['a.item_name'=>$val['name'],'a.is_del'=>0,'a.status'=>1]) ->where(['a.item_name'=>$val['name'],'a.is_del'=>0,'a.status'=>1])
->whereNotNull('b.device_id') ->whereNotNull('b.device_id')
->pluck('b.device_id')->toArray(); ->pluck('b.device_id')->toArray();
$FirstItem= DB::table('s_check_item as a')
->where(['a.item_name'=>$val['name'],'a.is_del'=>0,'a.status'=>1])->first();
if(!$FirstItem) return \Yz::echoError1('此医嘱项目无效,请检查项目设置');
} }
$itemNames[]=$val['name']; $itemNames[]=$val['name'];
} }
//获取所有传过来的检查项目关联的设备 //获取所有传过来的检查项目关联的设备,并且只有设置相同的医嘱后等待时间才能同时勾选
$ItemsDevices= DB::table('s_check_item as a') $ItemsDevices= DB::table('s_check_item as a')
->leftJoin('s_check_item_device as b','a.id','=','b.item_id') ->leftJoin('s_check_item_device as b','a.id','=','b.item_id')
->leftJoin('s_devices as c','b.device_id','=','c.id') ->leftJoin('s_devices as c','b.device_id','=','c.id')
->whereIn('a.item_name',$itemNames)->where(['a.is_del'=>0,'a.status'=>1])->get(); ->whereIn('a.item_name',$itemNames)->where(['a.is_del'=>0,'a.status'=>1,'check_begin_time'=>$FirstItem->check_begin_time])->get();
//按照第一个勾选的检查项目关联的设备进行分组,能在一个设备上进行检查的分在一组 //按照第一个勾选的检查项目关联的设备进行分组,能在一个设备上进行检查的分在一组
if(!empty($FirstItemDevices)){ if(!empty($FirstItemDevices)){
$group=[]; $group=[];
foreach ($FirstItemDevices as $value){ foreach ($FirstItemDevices as $f_key=> $value){
$lg=[]; $lg=[];
foreach ($ItemsDevices as $v){ foreach ($ItemsDevices as $v){
if($v->device_id==$value){ if($v->device_id==$value){
@ -189,11 +194,15 @@ class WorkMainController extends Controller
$lg[]=$v; $lg[]=$v;
} }
} }
$group[]=$lg; if(count($lg)>1 or count($group)==0){
$group[]=$lg;
}
} }
return \Yz::Return(true,'查询完成',['group'=>$group]); return \Yz::Return(true,'查询完成',['group'=>$group]);
}else{ }else{
return \Yz::echoError1('当前勾选检查项目不可用'); return \Yz::echoError1('当前勾选检查项目不可进行此操作');
} }
}else{ }else{
return \Yz::echoError1('检查项目不能为空'); return \Yz::echoError1('检查项目不能为空');
@ -272,4 +281,12 @@ class WorkMainController extends Controller
} }
} }
//医生取消预约
public function DoctorCancelYuYue()
{
$MainListId = request('MainListId');
$reg_num = request('reg_num');
$service = new PlanListService();
return $service->CancelYuYue($MainListId,$reg_num);
}
} }

@ -41,7 +41,7 @@ class Kernel extends HttpKernel
'api' => [ 'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api', 'throttle:120,1',
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,
], ],
]; ];

@ -19,6 +19,9 @@ class UserService
if(isset($arr['departmentid'])){ if(isset($arr['departmentid'])){
$list=$list->where('a.department_id', $arr['departmentid']); $list=$list->where('a.department_id', $arr['departmentid']);
} }
if(isset($arr['ward'])){
$list=$list->whereRaw("FIND_IN_SET(?, a.ward)", [$arr['ward']]);
}
$count=$list->count(); $count=$list->count();
$list=$list $list=$list
->skip(($arr['page']-1)*$arr['pagesize']) // 跳过前9999条记录 ->skip(($arr['page']-1)*$arr['pagesize']) // 跳过前9999条记录
@ -35,7 +38,8 @@ class UserService
'cn_name' => $arr['info']['cname'], 'cn_name' => $arr['info']['cname'],
'username' => $arr['info']['uname'], 'username' => $arr['info']['uname'],
'status'=>$arr['info']['status'], 'status'=>$arr['info']['status'],
'department_id'=> isset($arr['info']['department_id']) ?$arr['info']['department_id']: 0 'department_id'=> isset($arr['info']['department_id']) ?$arr['info']['department_id']: 0,
'ward'=> isset($arr['info']['ward']) ?$arr['info']['ward']: null
]); ]);
if($query){ if($query){
$result['status']='ok'; $result['status']='ok';
@ -60,7 +64,8 @@ class UserService
'username' => $arr['info']['uname'], 'username' => $arr['info']['uname'],
'pwd' => $hash, 'pwd' => $hash,
'status'=>1, 'status'=>1,
'department_id'=> isset($arr['info']['department_id']) ?$arr['info']['department_id']: 0 'department_id'=> isset($arr['info']['department_id']) ?$arr['info']['department_id']: 0,
'ward'=> isset($arr['info']['ward']) ?$arr['info']['ward']: null
]); ]);
DB::commit(); // 手动提交事务 DB::commit(); // 手动提交事务

@ -18,9 +18,12 @@ class PlanListService
// $episodeid = request('episodeid'); // $episodeid = request('episodeid');
// $appointment_type = request('appointment_type'); //预约类型 // $appointment_type = request('appointment_type'); //预约类型
// $appointment_date = request('date'); //预约日期 // $appointment_date = request('date'); //预约日期
$allDevice=[];
foreach ($entrustids as $key=>$entrustid){
$info = DB::table('s_list')->where(['reg_num' => $regnum, 'entrust_id' => $entrustid, 'episodeid' => $episodeid,'is_nullify'=>0])->first(); $allDevice = [];//所有医嘱检查项目绑定的设备id
$commPatientType = [];//所有医嘱共同的病人类型
foreach ($entrustids as $key => $entrustid) {
$info = DB::table('s_list')->where(['reg_num' => $regnum, 'entrust_id' => $entrustid, 'episodeid' => $episodeid, 'is_nullify' => 0])->first();
if (!$info) return \Yz::echoError1('没有找到对应医嘱信息'); if (!$info) return \Yz::echoError1('没有找到对应医嘱信息');
$itemInfo = DB::table('s_check_item')->where(['item_name' => $info->entrust, 'status' => 1, "is_del" => 0])->get(); $itemInfo = DB::table('s_check_item')->where(['item_name' => $info->entrust, 'status' => 1, "is_del" => 0])->get();
if (count($itemInfo) == 0) return \Yz::echoError1('没有找到可用的检查项目信息'); if (count($itemInfo) == 0) return \Yz::echoError1('没有找到可用的检查项目信息');
@ -29,24 +32,29 @@ class PlanListService
$qudaos = explode(',', $itemInfo->reservation_method); $qudaos = explode(',', $itemInfo->reservation_method);
if (!in_array($appointment_type, $qudaos)) return \Yz::echoError1('此检查项目不支持在当前渠道预约'); if (!in_array($appointment_type, $qudaos)) return \Yz::echoError1('此检查项目不支持在当前渠道预约');
$entrust_time = $info->entrust_date . ' ' . $info->entrust_time; //医嘱时间 // $entrust_time = $info->entrust_date . ' ' . $info->entrust_time; //医嘱时间
$date = new DateTime($entrust_time); // $date = new DateTime($entrust_time);
$date->modify("+" . $itemInfo->check_begin_time . " minutes"); // $date->modify("+" . $itemInfo->check_begin_time . " minutes");
$enableCheckTime = $date;//到此时间后可进行预约 // $enableCheckTime = $date;//到此时间后可进行预约
$current_time = new DateTime(); // $current_time = new DateTime();
if ($current_time < $enableCheckTime) return \Yz::echoError1("请于" . $enableCheckTime->format("Y-m-d H:i:s") . "后进行预约"); // if ($current_time < $enableCheckTime) return \Yz::echoError1("请于" . $enableCheckTime->format("Y-m-d H:i:s") . "后进行预约");
//获取检查项目绑定的服务组(设备),判断状态正常的 //获取检查项目绑定的服务组(设备),判断状态正常的
$devices = DB::table('s_check_item_device') $devices = DB::table('s_check_item_device')
->leftJoin("s_devices", "s_check_item_device.device_id", "=", "s_devices.id") ->leftJoin("s_devices", "s_check_item_device.device_id", "=", "s_devices.id")
->where(['s_check_item_device.item_id' => $itemInfo->id]) ->where(['s_check_item_device.item_id' => $itemInfo->id])
->where(['s_devices.status' => 1, 'is_del' => 0])->pluck('s_devices.id')->toArray(); ->where(['s_devices.status' => 1, 'is_del' => 0])->pluck('s_devices.id')->toArray();
$allDevice[]=$devices; $allDevice[] = $devices;
if (!in_array($info->patient_type, $commPatientType)) {
// 如果不在数组中,则添加它
array_push($commPatientType, $info->patient_type);
}
} }
$commonDevice=[]; //多个检查项目共同的设备id $commonDevice = []; //多个检查项目共同的设备id
foreach($allDevice as $set) { foreach ($allDevice as $set) {
if(count($commonDevice) == 0) { if (count($commonDevice) == 0) {
// 如果$intersection为空直接将第一个子数组的元素放入 // 如果$intersection为空直接将第一个子数组的元素放入
$commonDevice = $set; $commonDevice = $set;
} else { } else {
@ -54,7 +62,7 @@ class PlanListService
$commonDevice = array_intersect($commonDevice, $set); $commonDevice = array_intersect($commonDevice, $set);
} }
} }
// dd($commonDevice); // dd($commonDevice);
//获取主表检查项目绑定的科室id //获取主表检查项目绑定的科室id
$department_id = DB::table('s_department')->where(['department_number' => $info->RISRAcceptDeptCode])->first(); $department_id = DB::table('s_department')->where(['department_number' => $info->RISRAcceptDeptCode])->first();
@ -87,8 +95,8 @@ class PlanListService
// ->get(); // ->get();
$placeholders = implode(',', array_fill(0, count($commonDevice), '?')); $placeholders = implode(',', array_fill(0, count($commonDevice), '?'));
$canshu=array_merge($commonDevice,[$department_id->id,$appointment_date,$appointment_type]); $canshu = array_merge($commonDevice, [$department_id->id, $appointment_date, $appointment_type]);
$plan=DB::select("SELECT $plan = DB::select("SELECT
a.*, a.*,
dd.devices, dd.devices,
b.department_resources_name, b.department_resources_name,
@ -117,11 +125,29 @@ WHERE
AND a.STATUS = 1 AND a.STATUS = 1
AND a.is_del = 0 AND a.is_del = 0
AND b.is_del = 0 AND b.is_del = 0
AND c.appointment_type_id =?",$canshu); AND c.appointment_type_id =?", $canshu);
//遍历列表 把超过当前时间的放在后面
$pl1 = [];
$pl2 = [];
$pp = [];
$nowtime = date('Y-m-d H:i:s');
foreach ($plan as $key => $p) {
// //病人类型不符合的过滤掉
$planPatientType = explode(",", $p->patient_type);
if (!empty(array_diff($commPatientType, $planPatientType))) {
continue;
}
//过期的排在后面
$time = $p->date . ' ' . $p->end_time;
if ($time > $nowtime) {
$pl1[] = $p;
} else {
$pl2[] = $p;
}
}
$pp = array_merge($pl1, $pl2);
return \Yz::Return(true, '查询完成', ['today_date' => date("Y-m-d"), 'appointment_date' => $appointment_date, 'weekname' => Tools::GetWeekName($appointment_date), 'mainInfo' => $info, 'plan_list' => $plan]); return \Yz::Return(true, '查询完成', ['today_date' => date("Y-m-d"), 'appointment_date' => $appointment_date, 'weekname' => Tools::GetWeekName($appointment_date), 'mainInfo' => $info, 'plan_list' => $pp]);
} }
//开始预约占用名额 //开始预约占用名额
@ -141,39 +167,55 @@ WHERE
if ($nowdatetime > $planInfo->date . ' ' . $planInfo->end_reservation_time) return \Yz::echoError1('已经超过预约截止时间'); if ($nowdatetime > $planInfo->date . ' ' . $planInfo->end_reservation_time) return \Yz::echoError1('已经超过预约截止时间');
$planCount = DB::table('s_source_roster_detail_count')->where(['roster_detail_id' => $planid, $planCount = DB::table('s_source_roster_detail_count')->where(['roster_detail_id' => $planid,
'appointment_type_id' => $appointment_type])->first(); 'appointment_type_id' => $appointment_type])->first();
if ($planCount->count <= ($planCount->used_count+count($mainlistids))) return \Yz::echoError1('当前预约时间名额不足'); if ($planCount->count <= ($planCount->used_count + count($mainlistids))) return \Yz::echoError1('当前预约时间名额不足');
$oldMainInfos=[];//临时存储原来的主表信息,用于改约 $oldMainInfos = [];//临时存储原来的主表信息,用于改约
//遍历多个s_list表id,前端多选,一次预约多个检查项目 //遍历多个s_list表id,前端多选,一次预约多个检查项目
foreach ($mainlistids as $key_m=>$mainlistid ){ foreach ($mainlistids as $key_m => $mainlistid) {
$mainInfo = DB::table('s_list as a') $mainInfo = DB::table('s_list as a')
->select('a.*','b.period_begin_time','b.period_end_time') ->select('a.*', 'b.period_begin_time', 'b.period_end_time')
->leftJoin('s_period as b','a.reservation_time','=','b.id') ->leftJoin('s_period as b', 'a.reservation_time', '=', 'b.id')
->where(['a.id' => $mainlistid])->first(); ->where(['a.id' => $mainlistid])->first();
$oldMainInfos[]=$mainInfo; $oldMainInfos[] = $mainInfo;
if (!$mainInfo) return \Yz::echoError1('医嘱不存在'); if (!$mainInfo) return \Yz::echoError1('医嘱不存在');
//判断状态 //判断状态
$msg_t=""; $msg_t = "";
if($mainInfo->list_status==0){ if ($mainInfo->list_status == 0) {
$msg_t="当前状态为待预约"; $msg_t = "当前状态为待预约";
} }
if($mainInfo->list_status==1){ if ($mainInfo->list_status == 1) {
$msg_t="已经在".$mainInfo->reservation_date.'的'.$mainInfo->period_begin_time.'-'.$mainInfo->period_end_time.'预约成功,不能再次预约'; $msg_t = "已经在" . $mainInfo->reservation_date . '的' . $mainInfo->period_begin_time . '-' . $mainInfo->period_end_time . '预约成功,不能再次预约';
} }
if($mainInfo->list_status==2){ if ($mainInfo->list_status == 2) {
$msg_t="当前状态为已登记"; $msg_t = "当前状态为已登记";
} }
if($mainInfo->list_status==3){ if ($mainInfo->list_status == 3) {
$msg_t="当前状态为已完成"; $msg_t = "当前状态为已完成";
} }
if ($do_type == 1 && $mainInfo->list_status <> 0) return \Yz::echoError1($mainInfo->entrust.' '.$msg_t.',禁止预约'); if ($do_type == 1 && $mainInfo->list_status <> 0) return \Yz::echoError1($mainInfo->entrust . ' ' . $msg_t . ',禁止预约');
if ($do_type == 2 && $mainInfo->list_status <> 1) return \Yz::echoError1($mainInfo->entrust.' '.$msg_t.',不允许改约操作'); if ($do_type == 2 && $mainInfo->list_status <> 1) return \Yz::echoError1($mainInfo->entrust . ' ' . $msg_t . ',不允许改约操作');
//判断病人类型
$plan_patient_type=explode(",", $planInfo->patient_type);
if(!in_array($mainInfo->patient_type, $plan_patient_type)) return \Yz::echoError1('当前计划不支持此病人类型');
//判断互斥暂时根据reg_num判断身份 //判断互斥暂时根据reg_num判断身份
//查询想要预约的项目 其自身code //查询想要预约的项目 其自身code
$item = DB::table('s_check_item')->where(['item_name' => $mainInfo->entrust, 'status' => 1, 'is_del' => 0])->first(); $item = DB::table('s_check_item')->where(['item_name' => $mainInfo->entrust, 'status' => 1, 'is_del' => 0])->first();
if (!$item) return \Yz::echoError1('此检查项目不可用'); if (!$item) return \Yz::echoError1('此检查项目不可用');
//医嘱开具后,预约时间需在设定的等待期之后,单位分钟
$entrust_time = $mainInfo->entrust_date . ' ' . $mainInfo->entrust_time; //医嘱时间
$date = new DateTime($entrust_time);
$date->modify("+" . $item->check_begin_time . " minutes");
$enableCheckTime = $date;//到此时间后可进行预约
$plan_dateTime = $planInfo->date . ' ' . $planInfo->begin_time;
$plan_dateTime = new DateTime($plan_dateTime);
if ($plan_dateTime < $enableCheckTime) return \Yz::echoError1($item->item_name . " 已设置只能预约医嘱开具后" . $item->check_begin_time . "分钟后的时间,请预约" . $enableCheckTime->format("Y-m-d H:i:s") . "之后的时间");
//检测是否空腹
$configs = DB::table('configs')->where('label', '开启空腹')->first();
if ($item->limosis == 1 and $planInfo->end_reservation_time > '12:00:00' and $configs->value == 1) return \Yz::echoError1($item->item_name . ' 项目必须空腹,只能预约上午,请重新选择时间');
//查询当前检查项目是否存在互斥 //查询当前检查项目是否存在互斥
$cha_hc = DB::table('s_huchi')->where('is_del', 0) $cha_hc = DB::table('s_huchi')->where('is_del', 0)
->where(function ($q) use ($item) { ->where(function ($q) use ($item) {
@ -184,7 +226,10 @@ WHERE
$status_1 = DB::table('s_list') $status_1 = DB::table('s_list')
->select('s_check_item.item_code', 's_list.entrust', 's_list.reservation_date', 's_list.reservation_time') ->select('s_check_item.item_code', 's_list.entrust', 's_list.reservation_date', 's_list.reservation_time')
->leftJoin('s_check_item', 's_list.entrust', '=', 's_check_item.item_name') ->leftJoin('s_check_item', 's_list.entrust', '=', 's_check_item.item_name')
->where(['s_list.reg_num' => $mainInfo->reg_num, 's_list.list_status' => 1, 's_list.is_del' => 0, 's_list.is_nullify' => 0])->get(); ->where(['s_list.reg_num' => $mainInfo->reg_num, 's_list.list_status' => 1, 's_list.is_del' => 0, 's_list.is_nullify' => 0])
//排除自身,自己不能互斥自己
->whereNotIn('item_name', [$mainInfo->entrust])
->get();
if (count($status_1) > 0) { if (count($status_1) > 0) {
foreach ($status_1 as $key => $value) { foreach ($status_1 as $key => $value) {
foreach ($cha_hc as $k => $v) { foreach ($cha_hc as $k => $v) {
@ -221,50 +266,50 @@ WHERE
DB::beginTransaction(); DB::beginTransaction();
try { try {
//更新计划明细表使用数量 //更新计划明细表使用数量
$u = DB::table('s_source_roster_detail_count')->where(['id' => $planCount->id]) ->whereRaw('count > (used_count + ?)', [count($mainlistids)]) $u = DB::table('s_source_roster_detail_count')->where(['id' => $planCount->id])->whereRaw('count > (used_count + ?)', [count($mainlistids)])
->increment('used_count',count($mainlistids)); ->increment('used_count', count($mainlistids));
if ($u) { if ($u) {
$cha = DB::table('s_source_roster_detail_count')->where(['id' => $planCount->id])->first(); $cha = DB::table('s_source_roster_detail_count')->where(['id' => $planCount->id])->first();
if ($cha->count >= ($cha->used_count+count($mainlistids))) { if ($cha->count >= ($cha->used_count + count($mainlistids))) {
//更新主表信息 //更新主表信息
$u_data=[ $u_data = [
'list_status' => 1, 'list_status' => 1,
'reservation_date' => $planInfo->date, 'reservation_date' => $planInfo->date,
'reservation_time' => $planInfo->period_id, 'reservation_time' => $planInfo->period_id,
'reservation_sources' => $planInfo->resources_id, 'reservation_sources' => $planInfo->resources_id,
'services_group' => $planInfo->device_id, 'services_group' => $planInfo->device_id,
'roster_id' => $planInfo->id, 'roster_id' => $planInfo->id,
'department_id'=>$planInfo->department_id, 'department_id' => $planInfo->department_id,
'xuhao' => 0, 'xuhao' => 0,
'appointment_type_id' => $appointment_type, 'appointment_type_id' => $appointment_type,
]; ];
$u_mainList = DB::table('s_list')->whereIn('id' , $mainlistids)->update($u_data); $u_mainList = DB::table('s_list')->whereIn('id', $mainlistids)->update($u_data);
$note="预约"; $note = "预约";
foreach ($oldMainInfos as $key =>$oldMainInfo){ foreach ($oldMainInfos as $key => $oldMainInfo) {
if ($do_type == 2) { if ($do_type == 2) {
// if(count($mainlistids)>1) return \Yz::echoError1('请选择1条医嘱改约暂不支持批量'); // if(count($mainlistids)>1) return \Yz::echoError1('请选择1条医嘱改约暂不支持批量');
$note="改约"; $note = "改约";
//如果是改约,则恢复原来的数量 //如果是改约,则恢复原来的数量
$u_old = DB::table('s_source_roster_detail_count')->where(['roster_detail_id' => $oldMainInfo->roster_id, 'appointment_type_id' => $oldMainInfo->appointment_type_id,['used_count','>',0]])->decrement('used_count'); $u_old = DB::table('s_source_roster_detail_count')->where(['roster_detail_id' => $oldMainInfo->roster_id, 'appointment_type_id' => $oldMainInfo->appointment_type_id, ['used_count', '>', 0]])->decrement('used_count');
} }
$i_log=DB::table('s_list_log')->insert([ $i_log = DB::table('s_list_log')->insert([
'list_id'=>$oldMainInfo->id, 'list_id' => $oldMainInfo->id,
'reg_num'=>$oldMainInfo->reg_num, 'reg_num' => $oldMainInfo->reg_num,
'old_status'=>$oldMainInfo->list_status, 'old_status' => $oldMainInfo->list_status,
'new_status'=>1, 'new_status' => 1,
'create_user'=>null, 'create_user' => null,
'note'=>$note, 'note' => $note,
'data'=>json_encode($u_data) 'data' => json_encode($u_data)
]); ]);
} }
if ($u_mainList) { if ($u_mainList) {
DB::commit(); DB::commit();
return \Yz::Return(true, '预约成功', []); return \Yz::Return(true, '预约成功', ['planid' => $planid, 'mainlistids' => $mainlistids]);
} else { } else {
DB::rollBack(); DB::rollBack();
return \Yz::echoError1('预约失败'); return \Yz::echoError1('预约失败');
@ -281,7 +326,7 @@ WHERE
} catch (\Exception $e) { } catch (\Exception $e) {
DB::rollBack(); DB::rollBack();
return \Yz::echoError1('预约异常'.$e->getMessage()); return \Yz::echoError1('预约异常' . $e->getMessage());
} }
@ -292,13 +337,13 @@ WHERE
date_default_timezone_set('PRC'); date_default_timezone_set('PRC');
$nowdatetime = date("Y-m-d H:i:s"); $nowdatetime = date("Y-m-d H:i:s");
$mainInfo = DB::table('s_list')->where(['id' => $MainListId,'reg_num'=>$reg_num])->first(); $mainInfo = DB::table('s_list')->where(['id' => $MainListId, 'reg_num' => $reg_num])->first();
if (!$mainInfo) return \Yz::echoError1('医嘱不存在'); if (!$mainInfo) return \Yz::echoError1('医嘱不存在');
//判断状态 //判断状态
if ($mainInfo->list_status <> 1) return \Yz::echoError1('该记录无法取消,当前状态:' . $mainInfo->list_status); if ($mainInfo->list_status <> 1) return \Yz::echoError1('该记录无法取消,当前状态:' . $mainInfo->list_status);
DB::beginTransaction(); DB::beginTransaction();
try { try {
$u_data=[ $u_data = [
'list_status' => 0, 'list_status' => 0,
'reservation_date' => null, 'reservation_date' => null,
'reservation_time' => null, 'reservation_time' => null,
@ -306,19 +351,19 @@ WHERE
'services_group' => null, 'services_group' => null,
'roster_id' => null, 'roster_id' => null,
'xuhao' => null, 'xuhao' => null,
'department_id'=>null, 'department_id' => null,
'appointment_type_id' => null, 'appointment_type_id' => null,
'canel_time' => $nowdatetime, 'canel_time' => $nowdatetime,
]; ];
$u_mainList = DB::table('s_list')->where(['id' => $MainListId])->update($u_data); $u_mainList = DB::table('s_list')->where(['id' => $MainListId])->update($u_data);
$i_log=DB::table('s_list_log')->insert([ $i_log = DB::table('s_list_log')->insert([
'list_id'=>$mainInfo->id, 'list_id' => $mainInfo->id,
'reg_num'=>$mainInfo->reg_num, 'reg_num' => $mainInfo->reg_num,
'old_status'=>$mainInfo->list_status, 'old_status' => $mainInfo->list_status,
'new_status'=>0, 'new_status' => 0,
'create_user'=>null, 'create_user' => null,
'note'=>'取消预约', 'note' => '取消预约',
'data'=>json_encode($u_data) 'data' => json_encode($u_data)
]); ]);
$u_count = DB::table('s_source_roster_detail_count')->where(['roster_detail_id' => $mainInfo->roster_id, 'appointment_type_id' => $mainInfo->appointment_type_id])->decrement('used_count'); $u_count = DB::table('s_source_roster_detail_count')->where(['roster_detail_id' => $mainInfo->roster_id, 'appointment_type_id' => $mainInfo->appointment_type_id])->decrement('used_count');
if ($u_mainList && $u_count) { if ($u_mainList && $u_count) {

@ -21,6 +21,8 @@
let tomorrowDate='' let tomorrowDate=''
let MainInfo_ids=[];//选中的检查项目对应的mainid let MainInfo_ids=[];//选中的检查项目对应的mainid
let PrintInfo=null;//打印信息 let PrintInfo=null;//打印信息
let YuYueSuccessInfo='';//预约成功后返回的信息
let MainInfo_period_ids=[];//选中的检查项目对应的reservation_time
//获取url参数 //获取url参数
function getParameterByName(name, url) { function getParameterByName(name, url) {
if (!url) url = decodeURIComponent(window.location.href) if (!url) url = decodeURIComponent(window.location.href)
@ -79,10 +81,16 @@
let msg=item.msg==''?"":'<div style="color:#fff">不可预约:<span>'+item.msg+'</span></div>' let msg=item.msg==''?"":'<div style="color:#fff">不可预约:<span>'+item.msg+'</span></div>'
let yuyueshijian=item.maininfo.list_status==1?'<div>预约时间:<span>'+item.maininfo.reservation_date+' '+item.maininfo.period_begin_time.substring(0,5) +'-'+ item.maininfo.period_end_time.substring(0,5) +'</span></div>' :'' let yuyueshijian=item.maininfo.list_status==1?'<div>预约时间:<span>'+item.maininfo.reservation_date+' '+item.maininfo.period_begin_time.substring(0,5) +'-'+ item.maininfo.period_end_time.substring(0,5) +'</span></div>' :''
let bj_color=item.msg==''?"":"hongse_bg" let bj_color=item.msg==''?"":"hongse_bg"
let kongfu=(item.iteminfo[0] && item.iteminfo[0].limosis==1)?'是':'否'
if(YuYueSuccessInfo && YuYueSuccessInfo.mainlistids.includes(item.maininfo.id+'')){
console.log(666666666);
bj_color="lanse_bg"
}
$('#yizhulist').append( $('#yizhulist').append(
'<div class="yiyuInfo '+ bj_color+' "data-entrustid='+ item.maininfo.entrust_id +' data-id='+ key +' data-mainid='+ item.maininfo.id +' data-name='+ item.maininfo.entrust +'>' + '<div class="yiyuInfo '+ bj_color+' "data-entrustid='+ item.maininfo.entrust_id +' data-id='+ key +' data-mainid='+ item.maininfo.id +' data-status='+ item.maininfo.list_status +' data-name='+ item.maininfo.entrust+ ' data-periodid='+ item.maininfo.reservation_time +'>' +
'<div>医嘱项:<span >'+item.maininfo.entrust +'</span></div>' + '<div>医嘱项:<span >'+item.maininfo.entrust +'</span></div>' +
'<div>当前状态:<span >'+zhuangtai+'</span></div>' +msg+ '<div>当前状态:<span >'+zhuangtai+'</span></div>' +msg+
'<div>医生:<span >'+ item.maininfo.docotr+' </span> 是否空腹:<span >'+ kongfu+'</span></div>' +
yuyueshijian+ yuyueshijian+
'</div>' '</div>'
); );
@ -99,13 +107,24 @@
} }
}); });
$('.b_tel').html(MainInfoList[0].maininfo.user_phone); $('.b_tel').html(MainInfoList[0].maininfo.user_phone);
$('.b_yun').html(MainInfoList[0].maininfo.RISRPatDiag); // $('.b_yun').html(MainInfoList[0].maininfo.RISRPatDiag);
today_date=data.data.today_date today_date=data.data.today_date
let add=1;
if(getParameterByName('dotype')==2 && YuYueSuccessInfo==''){//如果是改约,默认定位到预约的日期
console.log(data.data.info[0].maininfo.reservation_date);
today_date=data.data.info[0].maininfo.reservation_date
add=0;
}
var currentDate = new Date(data.data.today_date) var currentDate = new Date(data.data.today_date)
currentDate.setDate(currentDate.getDate() + 1)
currentDate.setDate(currentDate.getDate() + add)
tomorrowDate = currentDate.toISOString().split('T')[0] tomorrowDate = currentDate.toISOString().split('T')[0]
$('.yiyuInfo:first').trigger('click'); if(YuYueSuccessInfo==''){
$('.yiyuInfo:first').trigger('click');
}
} else { } else {
MsgAlert(data.msg) MsgAlert(data.msg)
@ -131,16 +150,40 @@
$('#xingqi1').html(data.data.weekname); $('#xingqi1').html(data.data.weekname);
$('#table1 tbody').html('') $('#table1 tbody').html('')
if(data.data.plan_list.length>0){ if(data.data.plan_list.length>0){
//判断是否是预约成功后重新加载计划列表,自动选中成功后的日期
let zhuangtai="class='table_row '";
data.data.plan_list.forEach(function(v, i) { data.data.plan_list.forEach(function(v, i) {
$('#table1 tbody').append('<tr data-resource='+v.department_resources_name+' data-timerange='+v.begin_time+'-'+v.end_time +' data-date='+v.date +' data-id=' + v.id + ' class=\'table_row\'>\n' + if(YuYueSuccessInfo && YuYueSuccessInfo.planid==v.id){
zhuangtai="class='table_row row_selected'"
}else{
zhuangtai="class='table_row '"
}
if(getParameterByName('dotype')==2 && YuYueSuccessInfo=='') {//如果是改约,默认定位到预约的日期
if(MainInfo_period_ids[0]==v.period_id){
zhuangtai="class='table_row row_selected'"
}else{
zhuangtai="class='table_row '"
}
}
$('#table1 tbody').append('<tr data-resource='+v.department_resources_name+' data-timerange='+v.begin_time+'-'+v.end_time +' data-date='+v.date +' data-id=' + v.id + ' '+zhuangtai+'>\n' +
' <td>' + v.date + '</td>\n' + ' <td>' + v.date + '</td>\n' +
' <td>' + v.weekname + '</td>\n' + ' <td>' + v.weekname + '</td>\n' +
' <td>' + v.department_resources_name + '</td>\n' + ' <td>' + v.department_resources_name + '</td>\n' +
' <td>' + v.devices + '</td>\n' + ' <td>' + v.devices + '</td>\n' +
' <td>' + v.begin_time + '-' + v.end_time + '</td>\n' + ' <td>' + v.begin_time.substring(0,5) + '-' + v.end_time.substring(0,5) + '</td>\n' +
' <td class=\'usecount\' onclick=\'test('+v.id+')\'>' + v.used_count + '/' + v.count + '</td>\n' + ' <td class=\'usecount\' onclick=\'test('+v.id+')\'>' + v.used_count + '/' + v.count + '</td>\n' +
' </tr>') ' </tr>')
}) })
if(getParameterByName('dotype')==2 && YuYueSuccessInfo=='') {//如果是改约,默认定位到预约的日期
console.log($('.row_selected').position())
$('.table_k').animate({
scrollTop: $('.row_selected').position().top-200
}, 200); // 动画持续时间为1000毫秒
}
}else{ }else{
$('#table1 tbody').append('暂无可用计划') $('#table1 tbody').append('暂无可用计划')
} }
@ -151,13 +194,20 @@
$('#xingqi2').html(data.data.weekname); $('#xingqi2').html(data.data.weekname);
$('#table2 tbody').html('') $('#table2 tbody').html('')
if(data.data.plan_list.length>0){ if(data.data.plan_list.length>0){
//判断是否是预约成功后重新加载计划列表,自动选中成功后的日期
let zhuangtai="class='table_row '";
data.data.plan_list.forEach(function(v, i) { data.data.plan_list.forEach(function(v, i) {
$('#table2 tbody').append('<tr data-resource='+v.department_resources_name+' data-timerange='+v.begin_time+'-'+v.end_time +' data-date='+v.date +' data-id=' + v.id + ' class=\'table_row\'>\n' + if(YuYueSuccessInfo && YuYueSuccessInfo.planid==v.id){
zhuangtai="class='table_row row_selected'"
}else{
zhuangtai="class='table_row '"
}
$('#table2 tbody').append('<tr data-resource='+v.department_resources_name+' data-timerange='+v.begin_time+'-'+v.end_time +' data-date='+v.date +' data-id=' + v.id + ' '+zhuangtai+'>\n' +
' <td>' + v.date + '</td>\n' + ' <td>' + v.date + '</td>\n' +
' <td>' + v.weekname + '</td>\n' + ' <td>' + v.weekname + '</td>\n' +
' <td>' + v.department_resources_name + '</td>\n' + ' <td>' + v.department_resources_name + '</td>\n' +
' <td>' + v.devices + '</td>\n' + ' <td>' + v.devices + '</td>\n' +
' <td>' + v.begin_time + '-' + v.end_time + '</td>\n' + ' <td>' + v.begin_time.substring(0,5) + '-' + v.end_time.substring(0,5) + '</td>\n' +
' <td class=\'usecount\' onclick=\'test('+v.id+')\'>' + v.used_count + '/' + v.count + '</td>\n' + ' <td class=\'usecount\' onclick=\'test('+v.id+')\'>' + v.used_count + '/' + v.count + '</td>\n' +
' </tr>') ' </tr>')
}) })
@ -187,10 +237,11 @@
} }
}) })
} else { } else {
MsgAlert(data.msg) MsgAlert(data.msg)
} }
// $("#table2").freezeHeader({ 'height': '100px' });
}, 'json') }, 'json')
@ -225,6 +276,7 @@
}, function(data) { }, function(data) {
$('#loadingModal').modal('hide'); $('#loadingModal').modal('hide');
if (data.status) { if (data.status) {
YuYueSuccessInfo=data.data
MsgAlert("操作成功") MsgAlert("操作成功")
if($('#enable_print').is(':checked')){ if($('#enable_print').is(':checked')){
PrintFunc(); PrintFunc();
@ -296,6 +348,12 @@
//点击左侧的检查项目,调用接口查询可同时预约的项目 //点击左侧的检查项目,调用接口查询可同时预约的项目
$("#yizhulist").on('click','.yiyuInfo:not(.yiyuInfo.hongse_bg)',function(){ $("#yizhulist").on('click','.yiyuInfo:not(.yiyuInfo.hongse_bg)',function(){
//如果有预约状态的被选中则启用取消按钮,否则取消按钮关闭
if($(this).data('status')==1){
$('#quxiao_button').prop('disabled', false);
}else{
$('#quxiao_button').prop('disabled', true);
}
//如果点击的项目本身已经被选中,再次点击则取消 //如果点击的项目本身已经被选中,再次点击则取消
if( $(this).hasClass('lanse_bg')){ if( $(this).hasClass('lanse_bg')){
$(this).removeClass('lanse_bg'); $(this).removeClass('lanse_bg');
@ -305,9 +363,11 @@
//如果点击的项目还没被选中,则清空所有已经选中的 //如果点击的项目还没被选中,则清空所有已经选中的
if(!$(this).hasClass('lanse_bg')){ if(!$(this).hasClass('lanse_bg')){
$(".lanse_bg").removeClass('lanse_bg'); $(".lanse_bg").removeClass('lanse_bg');
insertInto_appointmenText() $(this).addClass('lanse_bg');
insertInto_appointmenText()
} }
let first=$(this).data('id') let first=$(this).data('id')
let items=[] let items=[]
$('.yiyuInfo:not(.yiyuInfo.hongse_bg)').each(function(){ $('.yiyuInfo:not(.yiyuInfo.hongse_bg)').each(function(){
@ -315,32 +375,38 @@
if($(this).data('id')==first){ if($(this).data('id')==first){
isfirst=1 isfirst=1
} }
items.push({name:$(this).data('name'),rowid:$(this).data('id'),first:isfirst}) if($(this).data('status')==0){
items.push({name:$(this).data('name'),rowid:$(this).data('id'),first:isfirst})
}
}) })
$('#loadingModal').modal('show'); if(items.length>1){
$.post(BaseUrl+'/api/admin/CheckEntrstItemGroup', { $('#loadingModal').modal('show');
items:items $.post(BaseUrl+'/api/admin/CheckEntrstItemGroup', {
}, function(data) { items:items
$('#loadingModal').modal('hide'); }, function(data) {
if (data.status) { $('#loadingModal').modal('hide');
data.data.group[0].forEach(function(v,i){ if (data.status) {
//遍历所有 不是红色的检查项目。匹配接口返回的可同时预约的项目 ,勾选 data.data.group[0].forEach(function(v,i){
$('.yiyuInfo:not(.yiyuInfo.hongse_bg)').each(function(){ //遍历所有 不是红色的检查项目。匹配接口返回的可同时预约的项目 ,勾选
if($(this).data('id')==v.rowid){ $('.yiyuInfo:not(.yiyuInfo.hongse_bg)').each(function(){
$(this).addClass('lanse_bg'); if($(this).data('id')==v.rowid){
} $(this).addClass('lanse_bg');
}
})
}) })
insertInto_appointmenText()
})
insertInto_appointmenText()
}else{
MsgAlert(data.msg)
}
})
}
}else{
MsgAlert(data.msg)
}
})
}) })
function insertInto_appointmenText(){ //向检查项目输入框 填写内容 function insertInto_appointmenText(){ //向检查项目输入框 填写内容
@ -351,6 +417,7 @@
input_itemname=input_itemname+" "+$(this).data('name') input_itemname=input_itemname+" "+$(this).data('name')
entrust_ids.push($(this).data('entrustid')) entrust_ids.push($(this).data('entrustid'))
MainInfo_ids.push($(this).data('mainid')) MainInfo_ids.push($(this).data('mainid'))
MainInfo_period_ids.push($(this).data('periodid'))
}) })
$('#input_appointment_item').val(input_itemname); $('#input_appointment_item').val(input_itemname);
console.log(entrust_ids); console.log(entrust_ids);
@ -406,6 +473,35 @@
getPlanList(t2) getPlanList(t2)
} }
}) })
//点击取消
$('#quxiao_button').on('click',function(){
if (MainInfo_ids.length !== 1) {
MsgAlert("请勾选1条 预约中 的医嘱,进行取消")
return false
}
let result = confirm("你确定要取消此预约吗?");
if (result !== true) {
return false;
}
$('#loadingModal').modal('show');
$.post(BaseUrl+'/api/admin/DoctorCancelYuYue', {
MainListId:MainInfo_ids[0],
reg_num: getParameterByName('regnum'),
}, function(data) {
$('#loadingModal').modal('hide');
if (data.status) {
MsgAlert("操作成功")
getPlanList($('#datetimepicker1 input').val())
getPlanList($('#datetimepicker2 input').val())
GetMainInfo()
}else{
MsgAlert(data.msg)
}
})
})
$('#tablelist').load("tablelist.html"); $('#tablelist').load("tablelist.html");
}) })
@ -501,7 +597,7 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-3 "> <div class="col-md-2 ">
<div style="margin-top: 8px;"> 医嘱列表</div> <div style="margin-top: 8px;"> 医嘱列表</div>
<div id="yizhulist"></div> <div id="yizhulist"></div>
<!-- <div class="yiyuInfo"> <!-- <div class="yiyuInfo">
@ -514,9 +610,11 @@
</div> --> </div> -->
</div> </div>
<div class="col-md-9"> <div class="col-md-10">
<div class="b_info">姓名: <span class="b_name"></span> 登记号: <span class="b_id"></span> 性别: <span class="b_sex"></span> <div class="b_info">姓名: <span class="b_name"></span> 登记号: <span class="b_id"></span> 性别: <span class="b_sex"></span>
年龄: <span class="b_age"></span> 电话: <span class="b_tel"></span> 孕周: <span class="b_yun"></span></div> 年龄: <span class="b_age"></span> 电话: <span class="b_tel"></span>
<!-- 孕周: <span class="b_yun"></span>-->
</div>
<div class="row" style="margin-top: 16px"> <div class="row" style="margin-top: 16px">
<div class="col-md-3"> <div class="col-md-3">
<div class="input-group"> <div class="input-group">
@ -550,6 +648,7 @@
<div class="row" style="margin-top: 20px"> <div class="row" style="margin-top: 20px">
<div class="col-md-12"> <div class="col-md-12">
<button type="button" id="yuyue_button" class="btn btn-info" style="width: 130px">预 约</button> <button type="button" id="yuyue_button" class="btn btn-info" style="width: 130px">预 约</button>
<button type="button" disabled id="quxiao_button" class="btn btn-danger" style="width: 130px;margin-left: 8px">取消预约</button>
<button type="button" id="shuaxin_button" class="btn btn-info" style="width: 130px;margin-left: 8px;">刷 新</button> <button type="button" id="shuaxin_button" class="btn btn-info" style="width: 130px;margin-left: 8px;">刷 新</button>
<button type="button" id="shangyiye_button" class="btn " style="width: 130px;margin-left: 8px;">上一页</button> <button type="button" id="shangyiye_button" class="btn " style="width: 130px;margin-left: 8px;">上一页</button>
<button type="button" id="xiayiye_button" class="btn " style="width: 130px;margin-left: 8px;">下一页</button> <button type="button" id="xiayiye_button" class="btn " style="width: 130px;margin-left: 8px;">下一页</button>
@ -682,10 +781,50 @@
white-space: nowrap; white-space: nowrap;
} }
td ,th{ td ,th{
width: 100px !important; width: 100px ;
white-space: normal; /* 保留空白符序列,但是正常换行 */ white-space: normal; /* 保留空白符序列,但是正常换行 */
word-break: break-all; /* 允许在单词内换行 */ word-break: break-all; /* 允许在单词内换行 */
} }
tr th:first-child{
width: 70px ;
}
tr td:first-child{
width: 70px ;
}
tr th:nth-child(2){
width: 50px ;
}
tr td:nth-child(2){
width: 50px ;
}
tr th:nth-child(3){
width: 80px ;
}
tr td:nth-child(3){
width: 80px ;
}
tr th:nth-child(5){
width: 70px ;
}
tr td:nth-child(5){
width: 70px ;
}
tr th:nth-child(6){
width: 60px ;
}
tr td:nth-child(6){
width: 60px ;
}
tbody tr td { tbody tr td {
padding: 8px; padding: 8px;
@ -734,7 +873,10 @@
height:100vh; height:100vh;
overflow-y: auto; overflow-y: auto;
} }
.usecount{
color: #31b0d5;
font-weight: 700;
}
</style> </style>
</body> </body>

@ -4,6 +4,7 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-body text-center"> <div class="modal-body text-center">
<div class="spinner-border text-primary" role="status"> <div class="spinner-border text-primary" role="status">
<div style="width: 100%;text-align: right;padding-bottom: 8px; cursor: pointer;" id="close_button">X</div>
<table id="usedlist_table" > <table id="usedlist_table" >
<thead> <thead>
<tr> <tr>
@ -37,7 +38,8 @@
}, function(data) { }, function(data) {
$('#loadingModal').modal('hide'); $('#loadingModal').modal('hide');
if (data.status) { if (data.status) {
data.data.forEach(function(v, i) { if(data.data.length>0){
data.data.forEach(function(v, i) {
$('#usedlist_table tbody').append('<tr >\n' + $('#usedlist_table tbody').append('<tr >\n' +
' <td>' + v.user_name + '</td>\n' + ' <td>' + v.user_name + '</td>\n' +
' <td>' + v.user_phone + '</td>\n' + ' <td>' + v.user_phone + '</td>\n' +
@ -47,6 +49,11 @@
' <td>' + v.qudao_name + '</td>\n' + ' <td>' + v.qudao_name + '</td>\n' +
' </tr>') ' </tr>')
}) })
}else{
$('#usedlist_table tbody').append('<tr >\n' +
' <td colspan=6>' + '暂无数据' + '</td>\n'+
' </tr>')
}
$('#usedList').modal('show'); $('#usedList').modal('show');
}else{ }else{
@ -55,7 +62,9 @@
}) })
} }
$(function () { $(function () {
$('#close_button').click(function(){
$('#usedList').modal('hide');
})
}) })
</script> </script>

@ -94,6 +94,9 @@ Route::group(['middleware'=>['checktoken','log'],'prefix'=>'v1'],function () {
Route::post('admin/DelHuChi','App\Http\Controllers\API\Admin\YeWu\CheckItemController@DelHuChi');//删除互斥 Route::post('admin/DelHuChi','App\Http\Controllers\API\Admin\YeWu\CheckItemController@DelHuChi');//删除互斥
Route::post('admin/CheckEntrstItemGroup','App\Http\Controllers\API\Admin\YeWu\WorkMainController@CheckEntrstItemGroup');//批量检查医嘱检查项目是否可以同时预约 Route::post('admin/CheckEntrstItemGroup','App\Http\Controllers\API\Admin\YeWu\WorkMainController@CheckEntrstItemGroup');//批量检查医嘱检查项目是否可以同时预约
Route::post('admin/GetHuChiList','App\Http\Controllers\API\Admin\YeWu\CheckItemController@GetHuChiList');//获取某个项目已经设置的互斥列表 Route::post('admin/GetHuChiList','App\Http\Controllers\API\Admin\YeWu\CheckItemController@GetHuChiList');//获取某个项目已经设置的互斥列表
Route::post('admin/InpatientWardGetList','App\Http\Controllers\API\Admin\YeWu\InpatientWardController@GetList');//获取病区列表
Route::post('admin/InpatientWardSave','App\Http\Controllers\API\Admin\YeWu\InpatientWardController@Save');//保存病区
Route::post('admin/InpatientWardDel','App\Http\Controllers\API\Admin\YeWu\InpatientWardController@Del');//删除病区
}); });
//暂时不加权限 //暂时不加权限
@ -104,7 +107,8 @@ Route::group(['middleware'=>['log']],function () {
Route::post('admin/CheckEntrstItemGroup','App\Http\Controllers\API\Admin\YeWu\WorkMainController@CheckEntrstItemGroup');//批量检查医嘱检查项目是否可以同时预约 Route::post('admin/CheckEntrstItemGroup','App\Http\Controllers\API\Admin\YeWu\WorkMainController@CheckEntrstItemGroup');//批量检查医嘱检查项目是否可以同时预约
Route::post('admin/CheckIsDaiJian','App\Http\Controllers\API\Admin\YeWu\WorkMainController@CheckIsDaiJian');//检查当前时段是否有存在已经预约的待检查项目 Route::post('admin/CheckIsDaiJian','App\Http\Controllers\API\Admin\YeWu\WorkMainController@CheckIsDaiJian');//检查当前时段是否有存在已经预约的待检查项目
Route::post('admin/GetPlanUsedList','App\Http\Controllers\API\Admin\YeWu\PlanListController@GetUsedList');//计划占用详情列表 Route::post('admin/GetPlanUsedList','App\Http\Controllers\API\Admin\YeWu\PlanListController@GetUsedList');//计划占用详情列表
Route::post('admin/NoPayCancel','App\Http\Controllers\API\Admin\YeWu\WorkMainController@NoPayCancel');//检查是否有超时未支付的门诊预约记录,如果有则给其取消,并恢复名额 Route::get('admin/NoPayCancel','App\Http\Controllers\API\Admin\YeWu\WorkMainController@NoPayCancel');//检查是否有超时未支付的门诊预约记录,如果有则给其取消,并恢复名额
Route::post('admin/DoctorCancelYuYue','App\Http\Controllers\API\Admin\YeWu\WorkMainController@DoctorCancelYuYue');//医生取消预约
}); });
//H5端接口 //H5端接口

@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>深圳市南山区妇幼保健院-医技预约</title> <title>深圳市南山区妇幼保健院-医技预约</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

@ -302,3 +302,15 @@ export const CheckEntrstItemGroup = (data = {}) => {
export const getMainDetail = (data = {}) => { export const getMainDetail = (data = {}) => {
return axios({ url: import.meta.env.VITE_APP_API + 'admin/getMainDetail', data: data }) return axios({ url: import.meta.env.VITE_APP_API + 'admin/getMainDetail', data: data })
} }
//获取病区列表
export const InpatientWardGetList = (data = {}) => {
return axios({ url: import.meta.env.VITE_APP_API + 'v1/admin/InpatientWardGetList', data: data })
}
//保存病区
export const InpatientWardSave = (data = {}) => {
return axios({ url: import.meta.env.VITE_APP_API + 'v1/admin/InpatientWardSave', data: data })
}
//删除病区
export const InpatientWardDel = (data = {}) => {
return axios({ url: import.meta.env.VITE_APP_API + 'v1/admin/InpatientWardDel', data: data })
}

@ -156,6 +156,13 @@ const router = createRouter({
meta: { meta: {
title: '各渠道比例设置' title: '各渠道比例设置'
} }
},{
path: '/yewu/inpatientwardconfig',
name: 'InpatientWardConfig',
component: () => import('../views/YeWu/InpatientWardConfig.vue'),
meta: {
title: '病区管理'
}
}] }]
}, },

@ -3,13 +3,12 @@
<el-container class="content"> <el-container class="content">
<el-header> <el-header>
<el-row> <el-row>
<el-col :span="4" class="logo"> <el-col :span="3" class="logo" style="min-width: 250px;">
<div><img :src="configInfo.站点图片"/></div> <div><img :src="configInfo.站点图片"/></div>
<div> <div>
<div style="font-size: 16px;">深圳市南山区妇幼保健院</div> <div style="font-size: 16px;">深圳市南山区妇幼保健院</div>
<div>{{configInfo.站点名称}}</div> <div>{{configInfo.站点名称}}</div>
</div> </div>
</el-col> </el-col>
<el-col :span="3"> <el-col :span="3">
<el-icon size="30" color="#5f5f5f" class="offOn" v-if="isCollapse==true" <el-icon size="30" color="#5f5f5f" class="offOn" v-if="isCollapse==true"
@ -197,6 +196,9 @@ watch(()=>router.currentRoute.value,(newVal,oldVal)=>{
BaseUserInfo.value = res.info[0] BaseUserInfo.value = res.info[0]
sessionStorage.setItem('LoginUserInfo',JSON.stringify(res.info[0])) sessionStorage.setItem('LoginUserInfo',JSON.stringify(res.info[0]))
BaseUserInfo.value.img=import.meta.env.VITE_APP_FILE+BaseUserInfo.value.img BaseUserInfo.value.img=import.meta.env.VITE_APP_FILE+BaseUserInfo.value.img
if(res.info[0].group !=1 && res.info[0].group !=5){
window.location.href = "./#/yewu/mainList"
}
}) })
} }
let configInfo=ref(''); let configInfo=ref('');

@ -234,6 +234,19 @@
immediate: true immediate: true
} // immediate: true } // immediate: true
); );
watch(
() => route.query.ward,
(newward, oldward) => {
if (route.query.ward) {
searchInfo.value.ward = route.query.ward
} else {
searchInfo.value.ward = null
}
GetUserList() // departmentId
}, {
immediate: true
} // immediate: true
);
let resetPwdDialogShow = ref(false); let resetPwdDialogShow = ref(false);
let Password = ref(''); let Password = ref('');
const resetPwdClick = () => { const resetPwdClick = () => {

@ -98,8 +98,8 @@
<div class="row"><span class="title">检查时间</span><el-input v-model="selectedItemInfo.check_time" <div class="row"><span class="title">检查时间</span><el-input v-model="selectedItemInfo.check_time"
placeholder="请输入此项检查的时长" style="width: 200px;" /></div> placeholder="请输入此项检查的时长" style="width: 200px;" /></div>
<div class="row"><span class="title">开始预约时间</span><el-input v-model="selectedItemInfo.check_begin_time" <div class="row"><span class="title">等待时间</span><el-input v-model="selectedItemInfo.check_begin_time"
placeholder="开医嘱后多久能开始预约" style="width: 200px;" /><div style="margin-left: 4px;">开医嘱后多久能开始预约,单位分钟</div></div> placeholder="开医嘱后,等待时间" style="width: 200px;" /><div style="margin-left: 4px;">医嘱开具后预约时间需在设定的等待期之后,单位分钟</div></div>
<div class="row"><span class="title">检查须知</span><el-input v-model="selectedItemInfo.check_notice" <div class="row"><span class="title">检查须知</span><el-input v-model="selectedItemInfo.check_notice"
@ -116,20 +116,55 @@
</el-dialog> </el-dialog>
<el-dialog v-model="HuChiDialogVisible" :title="'为 '+SelectedHuChiItemInfo.name+' 设置互斥'" width="80%" > <el-dialog v-model="HuChiDialogVisible" :title="'为 '+SelectedHuChiItemInfo.name+' 设置互斥'" width="80%" >
请输入想要与 <span style="font-weight: 900;font-size: 16px;">{{SelectedHuChiItemInfo.name}}</span> 互斥的项目名称 请输入想要与 <span style="font-weight: 900;font-size: 16px;">{{SelectedHuChiItemInfo.name}}</span> 互斥的项目名称
<div class="row" v-loading="HuChiLoading" style="border-bottom: 1px solid #ccc;padding-bottom: 12px;margin-bottom: 20px;"> <el-input v-model="HuChi_InputItem" placeholder="项目搜索" style="width: 200px;" />
<el-button type="success" style="margin-left: 8px;" @click="HuChi_SearchItem()"></el-button> <!-- <div v-if="itemHuChiList.length>0">
</div>
<div v-if="itemHuChiList.length>0">
<div class="row" v-for="(item,index) in itemHuChiList" :key="index"> <div class="row" v-for="(item,index) in itemHuChiList" :key="index">
<div class="col">{{item.code1_item_name}}</div> <div class="col">{{item.code1_item_name}}</div>
<div class="col">{{item.code2_item_name}}</div> <div class="col">{{item.code2_item_name}}</div>
<div class="col" style="width: 150px;"><span v-if="item.time>0">{{item.time}}</span><span v-if="item.time==0"></span></div> <div class="col" style="width: 150px;"><span v-if="item.time>0">{{item.time}}</span><span v-if="item.time==0"></span></div>
<el-button type="danger" style="margin-left: 8px;" @click="HuChi_Del(item.id)"></el-button> <el-button type="danger" style="margin-left: 8px;" @click="HuChi_Del(item.id)"></el-button>
</div> </div>
</div> -->
<div style="display: flex;margin-top: 20px;">
<div style="width: 30%;border-right: 1px solid #ccc;padding:4px 20px;">
<div class="row" v-loading="HuChiLoading" style="border-bottom: 1px solid #ccc;padding-bottom: 12px;margin-bottom: 20px;">
<el-input v-model="HuChi_InputItem" placeholder="项目搜索" style="width: 200px;" />
<el-button type="success" style="margin-left: 8px;" @click="HuChi_SearchItem()"></el-button>
</div>
<div style="padding-left: 40px;max-height: 400px;overflow-y:scroll;">
<el-checkbox-group v-model="HuChiCheckedItems" style="display: flex;flex-direction: column;">
<el-checkbox v-for="(item1,index1) in HuChiSelectItemList" :key="index1" :label="item1.item_code" >{{item1.item_name}}</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div style="width: 100px;border-right: 1px solid #ccc;text-align: center;height:400px;line-height: 400px;">
<el-button type="primary" :icon="ArrowRight" circle @click="AddHuChiItem()" />
</div>
<div style="width: 60%;padding-left: 20px;max-height: 450px;overflow-y:scroll;">
<div style="color: #999;margin-bottom: 4px;font-size: 12px;">已经存在互斥项{{itemHuChiList.length}}</div>
<table id="huchi_table" v-if="itemHuChiList.length>0">
<tr v-for="(item,index) in itemHuChiList" :key="index">
<td >{{item.code2_item_name}}</td>
<td ><el-input v-model="itemHuChiList[index].time" placeholder="时间,0为永久互斥" style="width: 150px;" /><span style="font-size: 12px;color:#bbb;"> </span></td>
<td >
<span v-if="item.id" style="display: flex;">
<el-button type="danger" style="margin-left: 8px;" @click="HuChi_Del(item.id)"></el-button>
<el-button type="primary" style="margin-left: 8px;" @click="HuChi_Save(SelectedHuChiItemInfo.code,item.code2,itemHuChiList[index].time,item.id)"></el-button>
</span>
<span v-else style="display: flex;">
<el-button type="success" style="margin-left: 8px;" @click="HuChi_Save(SelectedHuChiItemInfo.code,item.code2,itemHuChiList[index].time)"></el-button>
<el-button style="margin-left: 8px;" @click="HuChi_YiChu(item)"></el-button>
</span>
</td>
</tr>
</table>
<div v-else style="text-align: center;margin-top: 20px;color:#bbb;">
-- 暂未设置互斥项 --
</div>
</div>
</div> </div>
<div v-else style="text-align: center;margin-top: 20px;color:#bbb;">
-- 暂未设置互斥项 --
</div>
</el-dialog> </el-dialog>
<el-dialog v-model="HuChiSelectDialogVisible" :title="'为 '+SelectedHuChiItemInfo.name+' 设置互斥'" width="50%"> <el-dialog v-model="HuChiSelectDialogVisible" :title="'为 '+SelectedHuChiItemInfo.name+' 设置互斥'" width="50%">
<div v-loading="HuChiLoading"> <div v-loading="HuChiLoading">
@ -166,6 +201,9 @@
import { import {
ElMessage,ElMessageBox ElMessage,ElMessageBox
} from 'element-plus' } from 'element-plus'
import {
ArrowRight
} from '@element-plus/icons-vue'
let loading = ref(false); let loading = ref(false);
let searchInfo = ref({ let searchInfo = ref({
@ -331,6 +369,7 @@
let HuChiSelectDialogVisible=ref(false); let HuChiSelectDialogVisible=ref(false);
let HuChi_InputTimeList=ref([]);// let HuChi_InputTimeList=ref([]);//
let HuChiLoading=ref(false); let HuChiLoading=ref(false);
let HuChiCheckedItems=ref([]);//
let SelectedHuChiItemInfo=ref({ let SelectedHuChiItemInfo=ref({
code:'', code:'',
name:'' name:''
@ -339,6 +378,8 @@
HuChiDialogVisible.value = true HuChiDialogVisible.value = true
SelectedHuChiItemInfo.value.name = row.item_name SelectedHuChiItemInfo.value.name = row.item_name
SelectedHuChiItemInfo.value.code = row.item_code SelectedHuChiItemInfo.value.code = row.item_code
HuChiSelectItemList.value=[];
HuChi_InputItem.value='';
HuChiList() HuChiList()
} }
const HuChi_SearchItem = () => { const HuChi_SearchItem = () => {
@ -353,7 +394,6 @@
HuChiLoading.value=false HuChiLoading.value=false
if (res.status) { if (res.status) {
if(res.data.list.length>0){ if(res.data.list.length>0){
HuChiSelectDialogVisible.value=true
HuChiSelectItemList.value = res.data.list HuChiSelectItemList.value = res.data.list
}else{ }else{
ElMessage.error('未找到相关项目') ElMessage.error('未找到相关项目')
@ -365,10 +405,49 @@
}) })
} }
//
const AddHuChiItem=()=>{
console.log(HuChiCheckedItems.value);
//HuChiSelectItemList.value
let newArr = [];
let removedArr = [];
//
let enable=true
itemHuChiList.value.forEach(function(v,i){
HuChiCheckedItems.value.forEach(function(v2,i2){
if(v.code2==v2){
ElMessage.error(v.code2_item_name+" 已经存在于右侧,不能重复添加")
enable=false
}
})
})
if(!enable) return false;
HuChiSelectItemList.value.forEach(item => {
if (HuChiCheckedItems.value.includes(item.item_code)) {
itemHuChiList.value.push({code2:item.item_code,code2_item_name:item.item_name}); // ididremovedArr
} else {
newArr.push(item); // newArr
}
});
HuChiSelectItemList.value=newArr;
console.log(removedArr);
console.log(itemHuChiList.value);
HuChiCheckedItems.value=[]
}
//
const HuChi_YiChu=(y_item)=>{
itemHuChiList.value=itemHuChiList.value.filter(item => item.code2 != y_item.code2);
if(!HuChiSelectItemList.value.find(item => item.item_code==y_item.code2)){
HuChiSelectItemList.value.push({item_code:y_item.code2,item_name:y_item.code2_item_name});
}
}
// //
const HuChi_Save=(code1,code2,time)=>{ const HuChi_Save=(code1,code2,time,id=0)=>{
HuChiLoading.value=true HuChiLoading.value=true
SetHuChi({ SetHuChi({
id:id,
code1: code1, code1: code1,
code2: code2, code2: code2,
time: time time: time
@ -379,8 +458,12 @@
message: res.msg, message: res.msg,
type: 'success', type: 'success',
}) })
HuChiSelectDialogVisible.value = false itemHuChiList.value.forEach(function(v,i){
HuChiList(); if(v.code2==res.data.code2){
console.log(333);
itemHuChiList.value[i].id=res.data.id
}
})
} else { } else {
ElMessage.error(res.msg) ElMessage.error(res.msg)
} }
@ -470,4 +553,18 @@
padding: 4px; padding: 4px;
width: 400px; width: 400px;
} }
#huchi_table{
width: 100%;border: 1px solid #ccc;
border-collapse: collapse;
}
#huchi_table tr{
border: 1px solid #ccc;
}
#huchi_table td{
border: 1px solid #ccc;
padding: 4px 8px;
}
</style> </style>

@ -0,0 +1,254 @@
<template>
<div class="head">
<el-row>
<el-form-item>
<el-input v-model="searchInfo.name" placeholder="请输入病区名称" style="margin-left: 10px;"/>
</el-form-item>
<el-button @click="InpatientWardGetListAction()" style="margin-left: 10px;">搜索</el-button>
<el-button type="primary" @click="Add()" style="margin-left: 10px;">添加</el-button>
</el-row>
</div>
<el-table :data="tableData" style="width: 100%;" row-key="id" v-loading="loading">
<el-table-column prop="id" label="Id" width="100" />
<el-table-column prop="name" label="病区名称" />
<el-table-column prop="status" label="状态" >
<template #default="scope">
<el-tag v-if="scope.row.status==1" class="ml-2" type="success"></el-tag>
<el-tag v-if="scope.row.status==0" class="ml-2" type="danger"></el-tag>
</template>
</el-table-column>
<el-table-column prop="" label="关联账号" >
<template #default="scope">
<el-button v-if="scope.row.status==1" @click="addUser(scope.row)" size="small"></el-button>
<div style="color: #00aaff; font-size: 12px; cursor: pointer;" @click="gotoUserList(scope.row)">
已关联{{scope.row.user_list.length}}</div>
</template>
</el-table-column>
<el-table-column prop="" label="操作">
<template #default="scope">
<el-button type="primary" @click="Edit(scope.row)" size="small" style="margin-left: 10px;">修改</el-button>
<el-button type="danger" @click="Del(scope.row.id)" size="small" style="margin-left: 10px;">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="page">
<el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize"
:page-sizes="[15, 50, 100, 200]" layout="total,sizes, prev, pager, next" :total="total"
@size-change="PageSizeChange" @current-change="PageCurrentChange" />
</div>
<el-dialog v-model="dialogVisible" title="病区信息" width="30%">
<el-form :model="Info" label-width="100px" v-loading="loading" style="padding-right: 40px;">
<el-form-item label="病区名称:">
<el-input v-model="Info.name" />
</el-form-item>
<el-form-item label="病区状态:">
<el-switch v-model="Info.status" size="large" active-text="" inactive-text=""
:active-value="1" :inactive-value="0" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="Save">
确定
</el-button>
</span>
</template>
</el-dialog>
<el-dialog v-model="addUserdialogVisible" title="添加用户" width="30%">
<el-form :model="addUserInfo" label-width="100px" v-loading="loading" style="padding-right: 40px;">
<el-form-item label="姓名:">
<el-input v-model="addUserInfo.cname" />
</el-form-item>
<el-form-item label="账号:">
<el-input v-model="addUserInfo.uname" />
</el-form-item>
<el-form-item label="角色:">
<el-select :filterable="true" clearable v-model="addUserInfo.groupId" placeholder="选择角色"
style="margin-left: 10px;">
<el-option v-for="(item ,index) in groupList_new" :label="item.group_name" :value="item.id" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="addUserdialogVisible = false">取消</el-button>
<el-button type="primary" @click="addUserSave()">
确定
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import {
ref,
onMounted,computed
} from 'vue'
import {
InpatientWardGetList,InpatientWardSave,InpatientWardDel,
SaveSystemUserInfo,
getGroupList
} from '@/api/api.js'
import { ElMessage, ElMessageBox } from 'element-plus'
const groupList_new = computed(() => {
return groupList.value.filter(item => (item.id ==2));
});
let loading=ref(false);
let searchInfo=ref({
name:''
})
let Info=ref({
id:null,
name:'',
status:1
})
let dialogVisible=ref(false);
//list
let tableData = ref([])
let currentPage = ref(1) //
let pageSize = ref(15) //
let total = 0 //
const InpatientWardGetListAction=()=>{
loading.value = true
InpatientWardGetList({searchInfo:searchInfo.value,page:currentPage.value,pageSize:pageSize.value}).then(res => {
loading.value = false
if (res.status) {
tableData.value=res.data.list
total=res.data.count
} else {
ElMessage.error(res.msg)
}
})
}
const PageSizeChange = (e) => { //
pageSize.value = e
InpatientWardGetListAction()
}
const PageCurrentChange = (e) => { //
currentPage.value = e
InpatientWardGetListAction()
}
//
let addUserdialogVisible =ref(false);
let addUserInfo=ref({
id:0,
department_id:null,
ward:null,
cname:'',
uname:'',
groupId:'',
status:1
})
const addUser=(row)=>{
addUserdialogVisible.value=true
addUserInfo.value.ward=row.name
}
const addUserSave=()=>{
if(addUserInfo.value.cname=='' || addUserInfo.value.uname=='' ||addUserInfo.value.groupId==''){
ElMessage.error("请填写全部信息")
return false
}
SaveSystemUserInfo({
userInfo: addUserInfo.value,
}).then(res => {
loading.value = false
if (res.status=='ok') {
addUserdialogVisible.value=false
ElMessage({
message: '添加成功',
type: 'success',
})
InpatientWardGetListAction()
} else {
ElMessage.error(res.msg)
}
})
}
const Add=()=>{
dialogVisible.value=true
Info.id=null
searchInfo.name=''
}
const Edit=(row)=>{
Info.value={
id:row.id,
name:row.name,
status:row.status
}
dialogVisible.value=true
}
const Del=(id)=>{
ElMessageBox.confirm(
'确定删除吗?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
InpatientWardDel({id:id}).then(res => {
loading.value = false
if (res.status) {
InpatientWardGetListAction()
} else {
ElMessage.error(res.msg)
}
})
})
}
//
const Save=()=>{
loading.value = true
InpatientWardSave({Info:Info.value}).then(res => {
loading.value = false
if (res.status) {
dialogVisible.value=false
InpatientWardGetListAction()
} else {
ElMessage.error(res.msg)
}
})
}
let groupList=ref('');
const getGroup=()=>{
getGroupList().then(res => {
loading.value = false
if (res.status=='ok') {
groupList.value=res.list
} else {
ElMessage.error(res.msg)
}
})
}
const gotoUserList=(row)=>{
window.location.href = "./#/adminUserList?ward=" + row.name
}
onMounted(()=>{
getGroup()
InpatientWardGetListAction()
})
</script>
<style scoped>
.page {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
</style>

@ -1,7 +1,7 @@
<template> <template>
<div v-loading="loading"> <div v-loading="loading">
<el-dialog title="预约列表" class="iframeDialog" v-model="iframe_show" @opened="dialogopened"> <el-dialog title="预约列表" class="iframeDialog" v-model="iframe_show" @opened="dialogopened" @closed="closeIfram()">
<iframe id="bdIframe" :src="selectedUrl" class="iframe" :key="iframeKey" scrolling="auto"></iframe> <iframe id="bdIframe" :src="selectedUrl" class="iframe" :key="iframeKey" scrolling="auto"></iframe>
</el-dialog> </el-dialog>
<div class="head"> <div class="head">
@ -226,7 +226,7 @@
ElMessage, ElMessage,
ElMessageBox ElMessageBox
} from 'element-plus' } from 'element-plus'
let AutoGroup=ref(true);//
let shenqingdan_show=ref(false); let shenqingdan_show=ref(false);
let do_type = ref(0) //12 let do_type = ref(0) //12
let loading = ref(false) let loading = ref(false)
@ -377,14 +377,25 @@
console.log(tableSelected.value) console.log(tableSelected.value)
let entrustids=[]//entrustid let entrustids=[]//entrustid
do_type.value = type do_type.value = type
console.log(tableSelected.value);
if (tableSelected.value.length === 0) { if (tableSelected.value.length === 0) {
ElMessage.error('请勾选1条记录') ElMessage.error('请勾选1条记录')
return false return false
} }
//tableSelected.value[0].entrust_id //tableSelected.value[0].entrust_id
let next=true
tableSelected.value.forEach((v,i)=>{ tableSelected.value.forEach((v,i)=>{
if(type==1 && v.list_status!=0){
ElMessage.error(v.entrust+" 不可进行预约操作")
next=false
}
if(type==2 && v.list_status!=1){
ElMessage.error(v.entrust+" 不可进行改约操作")
next=false
}
entrustids.push(v.entrust_id) entrustids.push(v.entrust_id)
}) })
if(next==false) return false
iframeKey.value++ iframeKey.value++
iframe_show.value = true iframe_show.value = true
@ -411,6 +422,14 @@
ElMessage.error('请勾选1条记录') ElMessage.error('请勾选1条记录')
return false return false
} }
let next=true
tableSelected.value.forEach((v,i)=>{
if(v.list_status!=1){
ElMessage.error(v.entrust+" 无需取消")
next=false
}
})
if(next==false) return false
ElMessageBox.prompt('请输入登录密码后再操作', '提示', { ElMessageBox.prompt('请输入登录密码后再操作', '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
@ -544,7 +563,9 @@
} }
const closeIfram=()=>{
GetList()
}
onMounted(() => { onMounted(() => {
if(loginUserinfo.value.group!=2){ if(loginUserinfo.value.group!=2){
getEnableResource() getEnableResource()

3
bot/.gitignore vendored

@ -0,0 +1,3 @@
.idea
.DS_Store
config.ini

@ -0,0 +1,9 @@
MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,3 @@
# 鹿和开发套件 PHP 机器人
LuCodePhpBot

@ -0,0 +1,41 @@
<?php
namespace Workerman\Lib;
use Workerman\Worker;
$bot_loop = new Worker();
$bot_loop->count = 1;
$bot_loop->name = "OrderCancel";
function OrderCancelFunc()
{
$res= post("http://yijiyuyue/api/admin/NoPayCancel","");
var_dump(json_encode(json_decode($res),JSON_UNESCAPED_UNICODE));
}
$bot_loop->onWorkerStart = function () {
Timer::add(3, function () {
OrderCancelFunc();
});
};
function post($url, $data_string)
{
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
// curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl, CURLOPT_HTTPHEADER, [
'Content-Type: application/json; charset=utf-8',
'Content-Length: ' . strlen($data_string)
]);
// curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
$r = curl_exec($curl);
curl_close($curl);
return $r;
}

@ -0,0 +1,6 @@
AppName = 鹿和开发套件
MainDbName = example
MainDbUser = example
MainDbPassword = example
MainDbHost = example.com:3306

File diff suppressed because it is too large Load Diff

@ -0,0 +1,24 @@
<?php
/**
* //TODO
* DateTime: 2019/8/28 13:52
* File: start.php
*/
use Workerman\Worker;
date_default_timezone_set('Asia/Shanghai');
require_once "workerman/Autoloader.php";
\Workerman\Autoloader::setRootPath(__DIR__);
//if (!extension_loaded('pcntl')) exit("Please install pcntl extension. See http://doc3.workerman.net/install/install.html\n");
//if (!extension_loaded('posix')) exit("Please install posix extension. See http://doc3.workerman.net/install/install.html\n");
$config = [];
$config['workerman']['pidFile'] = __DIR__ . '/logs/workerman.pid';
$config['workerman']['logFile'] = __DIR__ . '/logs/workerman.log';
Worker::$pidFile = $config['workerman']['pidFile'];
Worker::$logFile = $config['workerman']['logFile'];
foreach (glob(__DIR__ . '/bot/bot_*.php') as $start_file) {
require_once $start_file;
}
Worker::runAll();

@ -0,0 +1,5 @@
.buildpath
.project
.settings
.idea
.DS_Store

@ -0,0 +1,69 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman;
/**
* Autoload.
*/
class Autoloader
{
/**
* Autoload root path.
*
* @var string
*/
protected static $_autoloadRootPath = '';
/**
* Set autoload root path.
*
* @param string $root_path
* @return void
*/
public static function setRootPath($root_path)
{
self::$_autoloadRootPath = $root_path;
}
/**
* Load files by namespace.
*
* @param string $name
* @return boolean
*/
public static function loadByNamespace($name)
{
$class_path = str_replace('\\', DIRECTORY_SEPARATOR, $name);
if (strpos($name, 'Workerman\\') === 0) {
$class_file = __DIR__ . substr($class_path, strlen('Workerman')) . '.php';
} else {
if (self::$_autoloadRootPath) {
$class_file = self::$_autoloadRootPath . DIRECTORY_SEPARATOR . $class_path . '.php';
}
if (empty($class_file) || !is_file($class_file)) {
$class_file = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . "$class_path.php";
}
}
if (is_file($class_file)) {
require_once($class_file);
if (class_exists($name, false)) {
return true;
}
}
return false;
}
}
spl_autoload_register('\Workerman\Autoloader::loadByNamespace');

@ -0,0 +1,372 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Connection;
use Workerman\Events\EventInterface;
use Workerman\Lib\Timer;
use Workerman\Worker;
use Exception;
/**
* AsyncTcpConnection.
*/
class AsyncTcpConnection extends TcpConnection
{
/**
* Emitted when socket connection is successfully established.
*
* @var callback
*/
public $onConnect = null;
/**
* Transport layer protocol.
*
* @var string
*/
public $transport = 'tcp';
/**
* Status.
*
* @var int
*/
protected $_status = self::STATUS_INITIAL;
/**
* Remote host.
*
* @var string
*/
protected $_remoteHost = '';
/**
* Remote port.
*
* @var int
*/
protected $_remotePort = 80;
/**
* Connect start time.
*
* @var string
*/
protected $_connectStartTime = 0;
/**
* Remote URI.
*
* @var string
*/
protected $_remoteURI = '';
/**
* Context option.
*
* @var array
*/
protected $_contextOption = null;
/**
* Reconnect timer.
*
* @var int
*/
protected $_reconnectTimer = null;
/**
* PHP built-in protocols.
*
* @var array
*/
protected static $_builtinTransports = array(
'tcp' => 'tcp',
'udp' => 'udp',
'unix' => 'unix',
'ssl' => 'ssl',
'sslv2' => 'sslv2',
'sslv3' => 'sslv3',
'tls' => 'tls'
);
/**
* Construct.
*
* @param string $remote_address
* @param array $context_option
* @throws Exception
*/
public function __construct($remote_address, $context_option = null)
{
$address_info = parse_url($remote_address);
if (!$address_info) {
list($scheme, $this->_remoteAddress) = explode(':', $remote_address, 2);
if (!$this->_remoteAddress) {
Worker::safeEcho(new \Exception('bad remote_address'));
}
} else {
if (!isset($address_info['port'])) {
$address_info['port'] = 80;
}
if (!isset($address_info['path'])) {
$address_info['path'] = '/';
}
if (!isset($address_info['query'])) {
$address_info['query'] = '';
} else {
$address_info['query'] = '?' . $address_info['query'];
}
$this->_remoteAddress = "{$address_info['host']}:{$address_info['port']}";
$this->_remoteHost = $address_info['host'];
$this->_remotePort = $address_info['port'];
$this->_remoteURI = "{$address_info['path']}{$address_info['query']}";
$scheme = isset($address_info['scheme']) ? $address_info['scheme'] : 'tcp';
}
$this->id = $this->_id = self::$_idRecorder++;
if(PHP_INT_MAX === self::$_idRecorder){
self::$_idRecorder = 0;
}
// Check application layer protocol class.
if (!isset(self::$_builtinTransports[$scheme])) {
$scheme = ucfirst($scheme);
$this->protocol = '\\Protocols\\' . $scheme;
if (!class_exists($this->protocol)) {
$this->protocol = "\\Workerman\\Protocols\\$scheme";
if (!class_exists($this->protocol)) {
throw new Exception("class \\Protocols\\$scheme not exist");
}
}
} else {
$this->transport = self::$_builtinTransports[$scheme];
}
// For statistics.
self::$statistics['connection_count']++;
$this->maxSendBufferSize = self::$defaultMaxSendBufferSize;
$this->_contextOption = $context_option;
static::$connections[$this->_id] = $this;
}
/**
* Do connect.
*
* @return void
*/
public function connect()
{
if ($this->_status !== self::STATUS_INITIAL && $this->_status !== self::STATUS_CLOSING &&
$this->_status !== self::STATUS_CLOSED) {
return;
}
$this->_status = self::STATUS_CONNECTING;
$this->_connectStartTime = microtime(true);
if ($this->transport !== 'unix') {
// Open socket connection asynchronously.
if ($this->_contextOption) {
$context = stream_context_create($this->_contextOption);
$this->_socket = stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}",
$errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT, $context);
} else {
$this->_socket = stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}",
$errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT);
}
} else {
$this->_socket = stream_socket_client("{$this->transport}://{$this->_remoteAddress}", $errno, $errstr, 0,
STREAM_CLIENT_ASYNC_CONNECT);
}
// If failed attempt to emit onError callback.
if (!$this->_socket || !is_resource($this->_socket)) {
$this->emitError(WORKERMAN_CONNECT_FAIL, $errstr);
if ($this->_status === self::STATUS_CLOSING) {
$this->destroy();
}
if ($this->_status === self::STATUS_CLOSED) {
$this->onConnect = null;
}
return;
}
// Add socket to global event loop waiting connection is successfully established or faild.
Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection'));
// For windows.
if(DIRECTORY_SEPARATOR === '\\') {
Worker::$globalEvent->add($this->_socket, EventInterface::EV_EXCEPT, array($this, 'checkConnection'));
}
}
/**
* Reconnect.
*
* @param int $after
* @return void
*/
public function reconnect($after = 0)
{
$this->_status = self::STATUS_INITIAL;
static::$connections[$this->_id] = $this;
if ($this->_reconnectTimer) {
Timer::del($this->_reconnectTimer);
}
if ($after > 0) {
$this->_reconnectTimer = Timer::add($after, array($this, 'connect'), null, false);
return;
}
$this->connect();
}
/**
* CancelReconnect.
*/
public function cancelReconnect()
{
if ($this->_reconnectTimer) {
Timer::del($this->_reconnectTimer);
}
}
/**
* Get remote address.
*
* @return string
*/
public function getRemoteHost()
{
return $this->_remoteHost;
}
/**
* Get remote URI.
*
* @return string
*/
public function getRemoteURI()
{
return $this->_remoteURI;
}
/**
* Try to emit onError callback.
*
* @param int $code
* @param string $msg
* @return void
*/
protected function emitError($code, $msg)
{
$this->_status = self::STATUS_CLOSING;
if ($this->onError) {
try {
call_user_func($this->onError, $this, $code, $msg);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
}
/**
* Check connection is successfully established or faild.
*
* @param resource $socket
* @return void
*/
public function checkConnection()
{
// Remove EV_EXPECT for windows.
if(DIRECTORY_SEPARATOR === '\\') {
Worker::$globalEvent->del($this->_socket, EventInterface::EV_EXCEPT);
}
// Remove write listener.
Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE);
if ($this->_status != self::STATUS_CONNECTING) {
return;
}
// Check socket state.
if ($address = stream_socket_get_name($this->_socket, true)) {
// Nonblocking.
stream_set_blocking($this->_socket, 0);
// Compatible with hhvm
if (function_exists('stream_set_read_buffer')) {
stream_set_read_buffer($this->_socket, 0);
}
// Try to open keepalive for tcp and disable Nagle algorithm.
if (function_exists('socket_import_stream') && $this->transport === 'tcp') {
$raw_socket = socket_import_stream($this->_socket);
socket_set_option($raw_socket, SOL_SOCKET, SO_KEEPALIVE, 1);
socket_set_option($raw_socket, SOL_TCP, TCP_NODELAY, 1);
}
// SSL handshake.
if ($this->transport === 'ssl') {
$this->_sslHandshakeCompleted = $this->doSslHandshake($this->_socket);
if ($this->_sslHandshakeCompleted === false) {
return;
}
} else {
// There are some data waiting to send.
if ($this->_sendBuffer) {
Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite'));
}
}
// Register a listener waiting read event.
Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead'));
$this->_status = self::STATUS_ESTABLISHED;
$this->_remoteAddress = $address;
// Try to emit onConnect callback.
if ($this->onConnect) {
try {
call_user_func($this->onConnect, $this);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
// Try to emit protocol::onConnect
if (method_exists($this->protocol, 'onConnect')) {
try {
call_user_func(array($this->protocol, 'onConnect'), $this);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
} else {
// Connection failed.
$this->emitError(WORKERMAN_CONNECT_FAIL, 'connect ' . $this->_remoteAddress . ' fail after ' . round(microtime(true) - $this->_connectStartTime, 4) . ' seconds');
if ($this->_status === self::STATUS_CLOSING) {
$this->destroy();
}
if ($this->_status === self::STATUS_CLOSED) {
$this->onConnect = null;
}
}
}
}

@ -0,0 +1,209 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Connection;
use Workerman\Events\EventInterface;
use Workerman\Worker;
use Exception;
/**
* AsyncTcpConnection.
*/
class AsyncUdpConnection extends UdpConnection
{
/**
* Emitted when socket connection is successfully established.
*
* @var callback
*/
public $onConnect = null;
/**
* Emitted when socket connection closed.
*
* @var callback
*/
public $onClose = null;
/**
* Connected or not.
*
* @var bool
*/
protected $connected = false;
/**
* Context option.
*
* @var array
*/
protected $_contextOption = null;
/**
* Construct.
*
* @param string $remote_address
* @throws Exception
*/
public function __construct($remote_address, $context_option = null)
{
// Get the application layer communication protocol and listening address.
list($scheme, $address) = explode(':', $remote_address, 2);
// Check application layer protocol class.
if ($scheme !== 'udp') {
$scheme = ucfirst($scheme);
$this->protocol = '\\Protocols\\' . $scheme;
if (!class_exists($this->protocol)) {
$this->protocol = "\\Workerman\\Protocols\\$scheme";
if (!class_exists($this->protocol)) {
throw new Exception("class \\Protocols\\$scheme not exist");
}
}
}
$this->_remoteAddress = substr($address, 2);
$this->_contextOption = $context_option;
}
/**
* For udp package.
*
* @param resource $socket
* @return bool
*/
public function baseRead($socket)
{
$recv_buffer = stream_socket_recvfrom($socket, Worker::MAX_UDP_PACKAGE_SIZE, 0, $remote_address);
if (false === $recv_buffer || empty($remote_address)) {
return false;
}
if ($this->onMessage) {
if ($this->protocol) {
$parser = $this->protocol;
$recv_buffer = $parser::decode($recv_buffer, $this);
}
ConnectionInterface::$statistics['total_request']++;
try {
call_user_func($this->onMessage, $this, $recv_buffer);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
return true;
}
/**
* Sends data on the connection.
*
* @param string $send_buffer
* @param bool $raw
* @return void|boolean
*/
public function send($send_buffer, $raw = false)
{
if (false === $raw && $this->protocol) {
$parser = $this->protocol;
$send_buffer = $parser::encode($send_buffer, $this);
if ($send_buffer === '') {
return null;
}
}
if ($this->connected === false) {
$this->connect();
}
return strlen($send_buffer) === stream_socket_sendto($this->_socket, $send_buffer, 0);
}
/**
* Close connection.
*
* @param mixed $data
* @param bool $raw
*
* @return bool
*/
public function close($data = null, $raw = false)
{
if ($data !== null) {
$this->send($data, $raw);
}
Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ);
fclose($this->_socket);
$this->connected = false;
// Try to emit onClose callback.
if ($this->onClose) {
try {
call_user_func($this->onClose, $this);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
$this->onConnect = $this->onMessage = $this->onClose = null;
return true;
}
/**
* Connect.
*
* @return void
*/
public function connect()
{
if ($this->connected === true) {
return;
}
if ($this->_contextOption) {
$context = stream_context_create($this->_contextOption);
$this->_socket = stream_socket_client("udp://{$this->_remoteAddress}", $errno, $errmsg,
30, STREAM_CLIENT_CONNECT, $context);
} else {
$this->_socket = stream_socket_client("udp://{$this->_remoteAddress}", $errno, $errmsg);
}
if (!$this->_socket) {
Worker::safeEcho(new \Exception($errmsg));
return;
}
stream_set_blocking($this->_socket, false);
if ($this->onMessage) {
Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead'));
}
$this->connected = true;
// Try to emit onConnect callback.
if ($this->onConnect) {
try {
call_user_func($this->onConnect, $this);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
}
}

@ -0,0 +1,125 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Connection;
/**
* ConnectionInterface.
*/
abstract class ConnectionInterface
{
/**
* Statistics for status command.
*
* @var array
*/
public static $statistics = array(
'connection_count' => 0,
'total_request' => 0,
'throw_exception' => 0,
'send_fail' => 0,
);
/**
* Emitted when data is received.
*
* @var callback
*/
public $onMessage = null;
/**
* Emitted when the other end of the socket sends a FIN packet.
*
* @var callback
*/
public $onClose = null;
/**
* Emitted when an error occurs with connection.
*
* @var callback
*/
public $onError = null;
/**
* Sends data on the connection.
*
* @param mixed $send_buffer
* @return void|boolean
*/
abstract public function send($send_buffer);
/**
* Get remote IP.
*
* @return string
*/
abstract public function getRemoteIp();
/**
* Get remote port.
*
* @return int
*/
abstract public function getRemotePort();
/**
* Get remote address.
*
* @return string
*/
abstract public function getRemoteAddress();
/**
* Get local IP.
*
* @return string
*/
abstract public function getLocalIp();
/**
* Get local port.
*
* @return int
*/
abstract public function getLocalPort();
/**
* Get local address.
*
* @return string
*/
abstract public function getLocalAddress();
/**
* Is ipv4.
*
* @return bool
*/
abstract public function isIPv4();
/**
* Is ipv6.
*
* @return bool
*/
abstract public function isIPv6();
/**
* Close connection.
*
* @param $data
* @return void
*/
abstract public function close($data = null);
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,191 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Connection;
/**
* UdpConnection.
*/
class UdpConnection extends ConnectionInterface
{
/**
* Application layer protocol.
* The format is like this Workerman\\Protocols\\Http.
*
* @var \Workerman\Protocols\ProtocolInterface
*/
public $protocol = null;
/**
* Udp socket.
*
* @var resource
*/
protected $_socket = null;
/**
* Remote address.
*
* @var string
*/
protected $_remoteAddress = '';
/**
* Construct.
*
* @param resource $socket
* @param string $remote_address
*/
public function __construct($socket, $remote_address)
{
$this->_socket = $socket;
$this->_remoteAddress = $remote_address;
}
/**
* Sends data on the connection.
*
* @param string $send_buffer
* @param bool $raw
* @return void|boolean
*/
public function send($send_buffer, $raw = false)
{
if (false === $raw && $this->protocol) {
$parser = $this->protocol;
$send_buffer = $parser::encode($send_buffer, $this);
if ($send_buffer === '') {
return null;
}
}
return strlen($send_buffer) === stream_socket_sendto($this->_socket, $send_buffer, 0, $this->_remoteAddress);
}
/**
* Get remote IP.
*
* @return string
*/
public function getRemoteIp()
{
$pos = strrpos($this->_remoteAddress, ':');
if ($pos) {
return trim(substr($this->_remoteAddress, 0, $pos), '[]');
}
return '';
}
/**
* Get remote port.
*
* @return int
*/
public function getRemotePort()
{
if ($this->_remoteAddress) {
return (int)substr(strrchr($this->_remoteAddress, ':'), 1);
}
return 0;
}
/**
* Get remote address.
*
* @return string
*/
public function getRemoteAddress()
{
return $this->_remoteAddress;
}
/**
* Get local IP.
*
* @return string
*/
public function getLocalIp()
{
$address = $this->getLocalAddress();
$pos = strrpos($address, ':');
if (!$pos) {
return '';
}
return substr($address, 0, $pos);
}
/**
* Get local port.
*
* @return int
*/
public function getLocalPort()
{
$address = $this->getLocalAddress();
$pos = strrpos($address, ':');
if (!$pos) {
return 0;
}
return (int)substr(strrchr($address, ':'), 1);
}
/**
* Get local address.
*
* @return string
*/
public function getLocalAddress()
{
return (string)@stream_socket_get_name($this->_socket, false);
}
/**
* Is ipv4.
*
* return bool.
*/
public function isIpV4()
{
if ($this->transport === 'unix') {
return false;
}
return strpos($this->getRemoteIp(), ':') === false;
}
/**
* Is ipv6.
*
* return bool.
*/
public function isIpV6()
{
if ($this->transport === 'unix') {
return false;
}
return strpos($this->getRemoteIp(), ':') !== false;
}
/**
* Close connection.
*
* @param mixed $data
* @param bool $raw
* @return bool
*/
public function close($data = null, $raw = false)
{
if ($data !== null) {
$this->send($data, $raw);
}
return true;
}
}

@ -0,0 +1,194 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author 有个鬼<42765633@qq.com>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Events;
use Workerman\Worker;
/**
* ev eventloop
*/
class Ev implements EventInterface
{
/**
* All listeners for read/write event.
*
* @var array
*/
protected $_allEvents = array();
/**
* Event listeners of signal.
*
* @var array
*/
protected $_eventSignal = array();
/**
* All timer event listeners.
* [func, args, event, flag, time_interval]
*
* @var array
*/
protected $_eventTimer = array();
/**
* Timer id.
*
* @var int
*/
protected static $_timerId = 1;
/**
* Add a timer.
* {@inheritdoc}
*/
public function add($fd, $flag, $func, $args = null)
{
$callback = function ($event, $socket) use ($fd, $func) {
try {
call_user_func($func, $fd);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
};
switch ($flag) {
case self::EV_SIGNAL:
$event = new \EvSignal($fd, $callback);
$this->_eventSignal[$fd] = $event;
return true;
case self::EV_TIMER:
case self::EV_TIMER_ONCE:
$repeat = $flag == self::EV_TIMER_ONCE ? 0 : $fd;
$param = array($func, (array)$args, $flag, $fd, self::$_timerId);
$event = new \EvTimer($fd, $repeat, array($this, 'timerCallback'), $param);
$this->_eventTimer[self::$_timerId] = $event;
return self::$_timerId++;
default :
$fd_key = (int)$fd;
$real_flag = $flag === self::EV_READ ? \Ev::READ : \Ev::WRITE;
$event = new \EvIo($fd, $real_flag, $callback);
$this->_allEvents[$fd_key][$flag] = $event;
return true;
}
}
/**
* Remove a timer.
* {@inheritdoc}
*/
public function del($fd, $flag)
{
switch ($flag) {
case self::EV_READ:
case self::EV_WRITE:
$fd_key = (int)$fd;
if (isset($this->_allEvents[$fd_key][$flag])) {
$this->_allEvents[$fd_key][$flag]->stop();
unset($this->_allEvents[$fd_key][$flag]);
}
if (empty($this->_allEvents[$fd_key])) {
unset($this->_allEvents[$fd_key]);
}
break;
case self::EV_SIGNAL:
$fd_key = (int)$fd;
if (isset($this->_eventSignal[$fd_key])) {
$this->_eventSignal[$fd_key]->stop();
unset($this->_eventSignal[$fd_key]);
}
break;
case self::EV_TIMER:
case self::EV_TIMER_ONCE:
if (isset($this->_eventTimer[$fd])) {
$this->_eventTimer[$fd]->stop();
unset($this->_eventTimer[$fd]);
}
break;
}
return true;
}
/**
* Timer callback.
*
* @param \EvWatcher $event
*/
public function timerCallback($event)
{
$param = $event->data;
$timer_id = $param[4];
if ($param[2] === self::EV_TIMER_ONCE) {
$this->_eventTimer[$timer_id]->stop();
unset($this->_eventTimer[$timer_id]);
}
try {
call_user_func_array($param[0], $param[1]);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
/**
* Remove all timers.
*
* @return void
*/
public function clearAllTimer()
{
foreach ($this->_eventTimer as $event) {
$event->stop();
}
$this->_eventTimer = array();
}
/**
* Main loop.
*
* @see EventInterface::loop()
*/
public function loop()
{
\Ev::run();
}
/**
* Destroy loop.
*
* @return void
*/
public function destroy()
{
foreach ($this->_allEvents as $event) {
$event->stop();
}
}
/**
* Get timer count.
*
* @return integer
*/
public function getTimerCount()
{
return count($this->_eventTimer);
}
}

@ -0,0 +1,219 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author 有个鬼<42765633@qq.com>
* @copyright 有个鬼<42765633@qq.com>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Events;
use Workerman\Worker;
/**
* libevent eventloop
*/
class Event implements EventInterface
{
/**
* Event base.
* @var object
*/
protected $_eventBase = null;
/**
* All listeners for read/write event.
* @var array
*/
protected $_allEvents = array();
/**
* Event listeners of signal.
* @var array
*/
protected $_eventSignal = array();
/**
* All timer event listeners.
* [func, args, event, flag, time_interval]
* @var array
*/
protected $_eventTimer = array();
/**
* Timer id.
* @var int
*/
protected static $_timerId = 1;
/**
* construct
* @return void
*/
public function __construct()
{
if (class_exists('\\\\EventBase', false)) {
$class_name = '\\\\EventBase';
} else {
$class_name = '\EventBase';
}
$this->_eventBase = new $class_name();
}
/**
* @see EventInterface::add()
*/
public function add($fd, $flag, $func, $args=array())
{
if (class_exists('\\\\Event', false)) {
$class_name = '\\\\Event';
} else {
$class_name = '\Event';
}
switch ($flag) {
case self::EV_SIGNAL:
$fd_key = (int)$fd;
$event = $class_name::signal($this->_eventBase, $fd, $func);
if (!$event||!$event->add()) {
return false;
}
$this->_eventSignal[$fd_key] = $event;
return true;
case self::EV_TIMER:
case self::EV_TIMER_ONCE:
$param = array($func, (array)$args, $flag, $fd, self::$_timerId);
$event = new $class_name($this->_eventBase, -1, $class_name::TIMEOUT|$class_name::PERSIST, array($this, "timerCallback"), $param);
if (!$event||!$event->addTimer($fd)) {
return false;
}
$this->_eventTimer[self::$_timerId] = $event;
return self::$_timerId++;
default :
$fd_key = (int)$fd;
$real_flag = $flag === self::EV_READ ? $class_name::READ | $class_name::PERSIST : $class_name::WRITE | $class_name::PERSIST;
$event = new $class_name($this->_eventBase, $fd, $real_flag, $func, $fd);
if (!$event||!$event->add()) {
return false;
}
$this->_allEvents[$fd_key][$flag] = $event;
return true;
}
}
/**
* @see Events\EventInterface::del()
*/
public function del($fd, $flag)
{
switch ($flag) {
case self::EV_READ:
case self::EV_WRITE:
$fd_key = (int)$fd;
if (isset($this->_allEvents[$fd_key][$flag])) {
$this->_allEvents[$fd_key][$flag]->del();
unset($this->_allEvents[$fd_key][$flag]);
}
if (empty($this->_allEvents[$fd_key])) {
unset($this->_allEvents[$fd_key]);
}
break;
case self::EV_SIGNAL:
$fd_key = (int)$fd;
if (isset($this->_eventSignal[$fd_key])) {
$this->_eventSignal[$fd_key]->del();
unset($this->_eventSignal[$fd_key]);
}
break;
case self::EV_TIMER:
case self::EV_TIMER_ONCE:
if (isset($this->_eventTimer[$fd])) {
$this->_eventTimer[$fd]->del();
unset($this->_eventTimer[$fd]);
}
break;
}
return true;
}
/**
* Timer callback.
* @param null $fd
* @param int $what
* @param int $timer_id
*/
public function timerCallback($fd, $what, $param)
{
$timer_id = $param[4];
if ($param[2] === self::EV_TIMER_ONCE) {
$this->_eventTimer[$timer_id]->del();
unset($this->_eventTimer[$timer_id]);
}
try {
call_user_func_array($param[0], $param[1]);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
/**
* @see Events\EventInterface::clearAllTimer()
* @return void
*/
public function clearAllTimer()
{
foreach ($this->_eventTimer as $event) {
$event->del();
}
$this->_eventTimer = array();
}
/**
* @see EventInterface::loop()
*/
public function loop()
{
$this->_eventBase->loop();
}
/**
* Destroy loop.
*
* @return void
*/
public function destroy()
{
foreach ($this->_eventSignal as $event) {
$event->del();
}
}
/**
* Get timer count.
*
* @return integer
*/
public function getTimerCount()
{
return count($this->_eventTimer);
}
}

@ -0,0 +1,107 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Events;
interface EventInterface
{
/**
* Read event.
*
* @var int
*/
const EV_READ = 1;
/**
* Write event.
*
* @var int
*/
const EV_WRITE = 2;
/**
* Except event
*
* @var int
*/
const EV_EXCEPT = 3;
/**
* Signal event.
*
* @var int
*/
const EV_SIGNAL = 4;
/**
* Timer event.
*
* @var int
*/
const EV_TIMER = 8;
/**
* Timer once event.
*
* @var int
*/
const EV_TIMER_ONCE = 16;
/**
* Add event listener to event loop.
*
* @param mixed $fd
* @param int $flag
* @param callable $func
* @param mixed $args
* @return bool
*/
public function add($fd, $flag, $func, $args = null);
/**
* Remove event listener from event loop.
*
* @param mixed $fd
* @param int $flag
* @return bool
*/
public function del($fd, $flag);
/**
* Remove all timers.
*
* @return void
*/
public function clearAllTimer();
/**
* Main loop.
*
* @return void
*/
public function loop();
/**
* Destroy loop.
*
* @return mixed
*/
public function destroy();
/**
* Get Timer count.
*
* @return mixed
*/
public function getTimerCount();
}

@ -0,0 +1,227 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Events;
use Workerman\Worker;
/**
* libevent eventloop
*/
class Libevent implements EventInterface
{
/**
* Event base.
*
* @var resource
*/
protected $_eventBase = null;
/**
* All listeners for read/write event.
*
* @var array
*/
protected $_allEvents = array();
/**
* Event listeners of signal.
*
* @var array
*/
protected $_eventSignal = array();
/**
* All timer event listeners.
* [func, args, event, flag, time_interval]
*
* @var array
*/
protected $_eventTimer = array();
/**
* construct
*/
public function __construct()
{
$this->_eventBase = event_base_new();
}
/**
* {@inheritdoc}
*/
public function add($fd, $flag, $func, $args = array())
{
switch ($flag) {
case self::EV_SIGNAL:
$fd_key = (int)$fd;
$real_flag = EV_SIGNAL | EV_PERSIST;
$this->_eventSignal[$fd_key] = event_new();
if (!event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null)) {
return false;
}
if (!event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) {
return false;
}
if (!event_add($this->_eventSignal[$fd_key])) {
return false;
}
return true;
case self::EV_TIMER:
case self::EV_TIMER_ONCE:
$event = event_new();
$timer_id = (int)$event;
if (!event_set($event, 0, EV_TIMEOUT, array($this, 'timerCallback'), $timer_id)) {
return false;
}
if (!event_base_set($event, $this->_eventBase)) {
return false;
}
$time_interval = $fd * 1000000;
if (!event_add($event, $time_interval)) {
return false;
}
$this->_eventTimer[$timer_id] = array($func, (array)$args, $event, $flag, $time_interval);
return $timer_id;
default :
$fd_key = (int)$fd;
$real_flag = $flag === self::EV_READ ? EV_READ | EV_PERSIST : EV_WRITE | EV_PERSIST;
$event = event_new();
if (!event_set($event, $fd, $real_flag, $func, null)) {
return false;
}
if (!event_base_set($event, $this->_eventBase)) {
return false;
}
if (!event_add($event)) {
return false;
}
$this->_allEvents[$fd_key][$flag] = $event;
return true;
}
}
/**
* {@inheritdoc}
*/
public function del($fd, $flag)
{
switch ($flag) {
case self::EV_READ:
case self::EV_WRITE:
$fd_key = (int)$fd;
if (isset($this->_allEvents[$fd_key][$flag])) {
event_del($this->_allEvents[$fd_key][$flag]);
unset($this->_allEvents[$fd_key][$flag]);
}
if (empty($this->_allEvents[$fd_key])) {
unset($this->_allEvents[$fd_key]);
}
break;
case self::EV_SIGNAL:
$fd_key = (int)$fd;
if (isset($this->_eventSignal[$fd_key])) {
event_del($this->_eventSignal[$fd_key]);
unset($this->_eventSignal[$fd_key]);
}
break;
case self::EV_TIMER:
case self::EV_TIMER_ONCE:
// 这里 fd 为timerid
if (isset($this->_eventTimer[$fd])) {
event_del($this->_eventTimer[$fd][2]);
unset($this->_eventTimer[$fd]);
}
break;
}
return true;
}
/**
* Timer callback.
*
* @param mixed $_null1
* @param int $_null2
* @param mixed $timer_id
*/
protected function timerCallback($_null1, $_null2, $timer_id)
{
if ($this->_eventTimer[$timer_id][3] === self::EV_TIMER) {
event_add($this->_eventTimer[$timer_id][2], $this->_eventTimer[$timer_id][4]);
}
try {
call_user_func_array($this->_eventTimer[$timer_id][0], $this->_eventTimer[$timer_id][1]);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
if (isset($this->_eventTimer[$timer_id]) && $this->_eventTimer[$timer_id][3] === self::EV_TIMER_ONCE) {
$this->del($timer_id, self::EV_TIMER_ONCE);
}
}
/**
* {@inheritdoc}
*/
public function clearAllTimer()
{
foreach ($this->_eventTimer as $task_data) {
event_del($task_data[2]);
}
$this->_eventTimer = array();
}
/**
* {@inheritdoc}
*/
public function loop()
{
event_base_loop($this->_eventBase);
}
/**
* Destroy loop.
*
* @return void
*/
public function destroy()
{
foreach ($this->_eventSignal as $event) {
event_del($event);
}
}
/**
* Get timer count.
*
* @return integer
*/
public function getTimerCount()
{
return count($this->_eventTimer);
}
}

@ -0,0 +1,262 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Events\React;
use Workerman\Events\EventInterface;
use React\EventLoop\TimerInterface;
/**
* Class StreamSelectLoop
* @package Workerman\Events\React
*/
class Base implements \React\EventLoop\LoopInterface
{
/**
* @var array
*/
protected $_timerIdMap = array();
/**
* @var int
*/
protected $_timerIdIndex = 0;
/**
* @var array
*/
protected $_signalHandlerMap = array();
/**
* @var \React\EventLoop\LoopInterface
*/
protected $_eventLoop = null;
/**
* Base constructor.
*/
public function __construct()
{
$this->_eventLoop = new \React\EventLoop\StreamSelectLoop();
}
/**
* Add event listener to event loop.
*
* @param $fd
* @param $flag
* @param $func
* @param array $args
* @return bool
*/
public function add($fd, $flag, $func, $args = array())
{
$args = (array)$args;
switch ($flag) {
case EventInterface::EV_READ:
return $this->addReadStream($fd, $func);
case EventInterface::EV_WRITE:
return $this->addWriteStream($fd, $func);
case EventInterface::EV_SIGNAL:
if (isset($this->_signalHandlerMap[$fd])) {
$this->removeSignal($fd, $this->_signalHandlerMap[$fd]);
}
$this->_signalHandlerMap[$fd] = $func;
return $this->addSignal($fd, $func);
case EventInterface::EV_TIMER:
$timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) {
call_user_func_array($func, $args);
});
$this->_timerIdMap[++$this->_timerIdIndex] = $timer_obj;
return $this->_timerIdIndex;
case EventInterface::EV_TIMER_ONCE:
$index = ++$this->_timerIdIndex;
$timer_obj = $this->addTimer($fd, function() use ($func, $args, $index) {
$this->del($index,EventInterface::EV_TIMER_ONCE);
call_user_func_array($func, $args);
});
$this->_timerIdMap[$index] = $timer_obj;
return $this->_timerIdIndex;
}
return false;
}
/**
* Remove event listener from event loop.
*
* @param mixed $fd
* @param int $flag
* @return bool
*/
public function del($fd, $flag)
{
switch ($flag) {
case EventInterface::EV_READ:
return $this->removeReadStream($fd);
case EventInterface::EV_WRITE:
return $this->removeWriteStream($fd);
case EventInterface::EV_SIGNAL:
if (!isset($this->_eventLoop[$fd])) {
return false;
}
$func = $this->_eventLoop[$fd];
unset($this->_eventLoop[$fd]);
return $this->removeSignal($fd, $func);
case EventInterface::EV_TIMER:
case EventInterface::EV_TIMER_ONCE:
if (isset($this->_timerIdMap[$fd])){
$timer_obj = $this->_timerIdMap[$fd];
unset($this->_timerIdMap[$fd]);
$this->cancelTimer($timer_obj);
return true;
}
}
return false;
}
/**
* Main loop.
*
* @return void
*/
public function loop()
{
$this->run();
}
/**
* Destroy loop.
*
* @return void
*/
public function destroy()
{
}
/**
* Get timer count.
*
* @return integer
*/
public function getTimerCount()
{
return count($this->_timerIdMap);
}
/**
* @param resource $stream
* @param callable $listener
*/
public function addReadStream($stream, $listener)
{
return $this->_eventLoop->addReadStream($stream, $listener);
}
/**
* @param resource $stream
* @param callable $listener
*/
public function addWriteStream($stream, $listener)
{
return $this->_eventLoop->addWriteStream($stream, $listener);
}
/**
* @param resource $stream
*/
public function removeReadStream($stream)
{
return $this->_eventLoop->removeReadStream($stream);
}
/**
* @param resource $stream
*/
public function removeWriteStream($stream)
{
return $this->_eventLoop->removeWriteStream($stream);
}
/**
* @param float|int $interval
* @param callable $callback
* @return \React\EventLoop\Timer\Timer|TimerInterface
*/
public function addTimer($interval, $callback)
{
return $this->_eventLoop->addTimer($interval, $callback);
}
/**
* @param float|int $interval
* @param callable $callback
* @return \React\EventLoop\Timer\Timer|TimerInterface
*/
public function addPeriodicTimer($interval, $callback)
{
return $this->_eventLoop->addPeriodicTimer($interval, $callback);
}
/**
* @param TimerInterface $timer
*/
public function cancelTimer(TimerInterface $timer)
{
return $this->_eventLoop->cancelTimer($timer);
}
/**
* @param callable $listener
*/
public function futureTick($listener)
{
return $this->_eventLoop->futureTick($listener);
}
/**
* @param int $signal
* @param callable $listener
*/
public function addSignal($signal, $listener)
{
return $this->_eventLoop->addSignal($signal, $listener);
}
/**
* @param int $signal
* @param callable $listener
*/
public function removeSignal($signal, $listener)
{
return $this->_eventLoop->removeSignal($signal, $listener);
}
/**
* Run.
*/
public function run()
{
return $this->_eventLoop->run();
}
/**
* Stop.
*/
public function stop()
{
return $this->_eventLoop->stop();
}
}

@ -0,0 +1,27 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Events\React;
/**
* Class ExtEventLoop
* @package Workerman\Events\React
*/
class ExtEventLoop extends Base
{
public function __construct()
{
$this->_eventLoop = new \React\EventLoop\ExtEventLoop();
}
}

@ -0,0 +1,27 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Events\React;
use Workerman\Events\EventInterface;
/**
* Class ExtLibEventLoop
* @package Workerman\Events\React
*/
class ExtLibEventLoop extends Base
{
public function __construct()
{
$this->_eventLoop = new \React\EventLoop\ExtLibeventLoop();
}
}

@ -0,0 +1,26 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Events\React;
/**
* Class StreamSelectLoop
* @package Workerman\Events\React
*/
class StreamSelectLoop extends Base
{
public function __construct()
{
$this->_eventLoop = new \React\EventLoop\StreamSelectLoop();
}
}

@ -0,0 +1,340 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Events;
/**
* select eventloop
*/
class Select implements EventInterface
{
/**
* All listeners for read/write event.
*
* @var array
*/
public $_allEvents = array();
/**
* Event listeners of signal.
*
* @var array
*/
public $_signalEvents = array();
/**
* Fds waiting for read event.
*
* @var array
*/
protected $_readFds = array();
/**
* Fds waiting for write event.
*
* @var array
*/
protected $_writeFds = array();
/**
* Fds waiting for except event.
*
* @var array
*/
protected $_exceptFds = array();
/**
* Timer scheduler.
* {['data':timer_id, 'priority':run_timestamp], ..}
*
* @var \SplPriorityQueue
*/
protected $_scheduler = null;
/**
* All timer event listeners.
* [[func, args, flag, timer_interval], ..]
*
* @var array
*/
protected $_eventTimer = array();
/**
* Timer id.
*
* @var int
*/
protected $_timerId = 1;
/**
* Select timeout.
*
* @var int
*/
protected $_selectTimeout = 100000000;
/**
* Paired socket channels
*
* @var array
*/
protected $channel = array();
/**
* Construct.
*/
public function __construct()
{
// Create a pipeline and put into the collection of the read to read the descriptor to avoid empty polling.
$this->channel = stream_socket_pair(DIRECTORY_SEPARATOR === '/' ? STREAM_PF_UNIX : STREAM_PF_INET,
STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
if($this->channel) {
stream_set_blocking($this->channel[0], 0);
$this->_readFds[0] = $this->channel[0];
}
// Init SplPriorityQueue.
$this->_scheduler = new \SplPriorityQueue();
$this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH);
}
/**
* {@inheritdoc}
*/
public function add($fd, $flag, $func, $args = array())
{
switch ($flag) {
case self::EV_READ:
case self::EV_WRITE:
$count = $flag === self::EV_READ ? count($this->_readFds) : count($this->_writeFds);
if ($count >= 1024) {
echo "Warning: system call select exceeded the maximum number of connections 1024, please install event/libevent extension for more connections.\n";
} else if (DIRECTORY_SEPARATOR !== '/' && $count >= 256) {
echo "Warning: system call select exceeded the maximum number of connections 256.\n";
}
$fd_key = (int)$fd;
$this->_allEvents[$fd_key][$flag] = array($func, $fd);
if ($flag === self::EV_READ) {
$this->_readFds[$fd_key] = $fd;
} else {
$this->_writeFds[$fd_key] = $fd;
}
break;
case self::EV_EXCEPT:
$fd_key = (int)$fd;
$this->_allEvents[$fd_key][$flag] = array($func, $fd);
$this->_exceptFds[$fd_key] = $fd;
break;
case self::EV_SIGNAL:
// Windows not support signal.
if(DIRECTORY_SEPARATOR !== '/') {
return false;
}
$fd_key = (int)$fd;
$this->_signalEvents[$fd_key][$flag] = array($func, $fd);
pcntl_signal($fd, array($this, 'signalHandler'));
break;
case self::EV_TIMER:
case self::EV_TIMER_ONCE:
$timer_id = $this->_timerId++;
$run_time = microtime(true) + $fd;
$this->_scheduler->insert($timer_id, -$run_time);
$this->_eventTimer[$timer_id] = array($func, (array)$args, $flag, $fd);
$select_timeout = ($run_time - microtime(true)) * 1000000;
if( $this->_selectTimeout > $select_timeout ){
$this->_selectTimeout = $select_timeout;
}
return $timer_id;
}
return true;
}
/**
* Signal handler.
*
* @param int $signal
*/
public function signalHandler($signal)
{
call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal));
}
/**
* {@inheritdoc}
*/
public function del($fd, $flag)
{
$fd_key = (int)$fd;
switch ($flag) {
case self::EV_READ:
unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]);
if (empty($this->_allEvents[$fd_key])) {
unset($this->_allEvents[$fd_key]);
}
return true;
case self::EV_WRITE:
unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]);
if (empty($this->_allEvents[$fd_key])) {
unset($this->_allEvents[$fd_key]);
}
return true;
case self::EV_EXCEPT:
unset($this->_allEvents[$fd_key][$flag], $this->_exceptFds[$fd_key]);
if(empty($this->_allEvents[$fd_key]))
{
unset($this->_allEvents[$fd_key]);
}
return true;
case self::EV_SIGNAL:
if(DIRECTORY_SEPARATOR !== '/') {
return false;
}
unset($this->_signalEvents[$fd_key]);
pcntl_signal($fd, SIG_IGN);
break;
case self::EV_TIMER:
case self::EV_TIMER_ONCE;
unset($this->_eventTimer[$fd_key]);
return true;
}
return false;
}
/**
* Tick for timer.
*
* @return void
*/
protected function tick()
{
while (!$this->_scheduler->isEmpty()) {
$scheduler_data = $this->_scheduler->top();
$timer_id = $scheduler_data['data'];
$next_run_time = -$scheduler_data['priority'];
$time_now = microtime(true);
$this->_selectTimeout = ($next_run_time - $time_now) * 1000000;
if ($this->_selectTimeout <= 0) {
$this->_scheduler->extract();
if (!isset($this->_eventTimer[$timer_id])) {
continue;
}
// [func, args, flag, timer_interval]
$task_data = $this->_eventTimer[$timer_id];
if ($task_data[2] === self::EV_TIMER) {
$next_run_time = $time_now + $task_data[3];
$this->_scheduler->insert($timer_id, -$next_run_time);
}
call_user_func_array($task_data[0], $task_data[1]);
if (isset($this->_eventTimer[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) {
$this->del($timer_id, self::EV_TIMER_ONCE);
}
continue;
}
return;
}
$this->_selectTimeout = 100000000;
}
/**
* {@inheritdoc}
*/
public function clearAllTimer()
{
$this->_scheduler = new \SplPriorityQueue();
$this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH);
$this->_eventTimer = array();
}
/**
* {@inheritdoc}
*/
public function loop()
{
while (1) {
if(DIRECTORY_SEPARATOR === '/') {
// Calls signal handlers for pending signals
pcntl_signal_dispatch();
}
$read = $this->_readFds;
$write = $this->_writeFds;
$except = $this->_exceptFds;
// Waiting read/write/signal/timeout events.
set_error_handler(function(){});
$ret = stream_select($read, $write, $except, 0, $this->_selectTimeout);
restore_error_handler();
if (!$this->_scheduler->isEmpty()) {
$this->tick();
}
if (!$ret) {
continue;
}
if ($read) {
foreach ($read as $fd) {
$fd_key = (int)$fd;
if (isset($this->_allEvents[$fd_key][self::EV_READ])) {
call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0],
array($this->_allEvents[$fd_key][self::EV_READ][1]));
}
}
}
if ($write) {
foreach ($write as $fd) {
$fd_key = (int)$fd;
if (isset($this->_allEvents[$fd_key][self::EV_WRITE])) {
call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0],
array($this->_allEvents[$fd_key][self::EV_WRITE][1]));
}
}
}
if($except) {
foreach($except as $fd) {
$fd_key = (int) $fd;
if(isset($this->_allEvents[$fd_key][self::EV_EXCEPT])) {
call_user_func_array($this->_allEvents[$fd_key][self::EV_EXCEPT][0],
array($this->_allEvents[$fd_key][self::EV_EXCEPT][1]));
}
}
}
}
}
/**
* Destroy loop.
*
* @return void
*/
public function destroy()
{
}
/**
* Get timer count.
*
* @return integer
*/
public function getTimerCount()
{
return count($this->_eventTimer);
}
}

@ -0,0 +1,221 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author Ares<aresrr#qq.com>
* @link http://www.workerman.net/
* @link https://github.com/ares333/Workerman
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Events;
use Swoole\Event;
use Swoole\Timer;
class Swoole implements EventInterface
{
protected $_timer = array();
protected $_timerOnceMap = array();
protected $mapId = 0;
protected $_fd = array();
// milisecond
public static $signalDispatchInterval = 200;
protected $_hasSignal = false;
/**
*
* {@inheritdoc}
*
* @see \Workerman\Events\EventInterface::add()
*/
public function add($fd, $flag, $func, $args = null)
{
if (! isset($args)) {
$args = array();
}
switch ($flag) {
case self::EV_SIGNAL:
$res = pcntl_signal($fd, $func, false);
if (! $this->_hasSignal && $res) {
Timer::tick(static::$signalDispatchInterval,
function () {
pcntl_signal_dispatch();
});
$this->_hasSignal = true;
}
return $res;
case self::EV_TIMER:
case self::EV_TIMER_ONCE:
$method = self::EV_TIMER == $flag ? 'tick' : 'after';
if ($this->mapId > PHP_INT_MAX) {
$this->mapId = 0;
}
$mapId = $this->mapId++;
$timer_id = Timer::$method($fd * 1000,
function ($timer_id = null) use ($func, $args, $mapId) {
call_user_func_array($func, $args);
// EV_TIMER_ONCE
if (! isset($timer_id)) {
// may be deleted in $func
if (array_key_exists($mapId, $this->_timerOnceMap)) {
$timer_id = $this->_timerOnceMap[$mapId];
unset($this->_timer[$timer_id],
$this->_timerOnceMap[$mapId]);
}
}
});
if ($flag == self::EV_TIMER_ONCE) {
$this->_timerOnceMap[$mapId] = $timer_id;
$this->_timer[$timer_id] = $mapId;
} else {
$this->_timer[$timer_id] = null;
}
return $timer_id;
case self::EV_READ:
case self::EV_WRITE:
$fd_key = (int) $fd;
if (! isset($this->_fd[$fd_key])) {
if ($flag == self::EV_READ) {
$res = Event::add($fd, $func, null, SWOOLE_EVENT_READ);
$fd_type = SWOOLE_EVENT_READ;
} else {
$res = Event::add($fd, null, $func, SWOOLE_EVENT_WRITE);
$fd_type = SWOOLE_EVENT_WRITE;
}
if ($res) {
$this->_fd[$fd_key] = $fd_type;
}
} else {
$fd_val = $this->_fd[$fd_key];
$res = true;
if ($flag == self::EV_READ) {
if (($fd_val & SWOOLE_EVENT_READ) != SWOOLE_EVENT_READ) {
$res = Event::set($fd, $func, null,
SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE);
$this->_fd[$fd_key] |= SWOOLE_EVENT_READ;
}
} else {
if (($fd_val & SWOOLE_EVENT_WRITE) != SWOOLE_EVENT_WRITE) {
$res = Event::set($fd, null, $func,
SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE);
$this->_fd[$fd_key] |= SWOOLE_EVENT_WRITE;
}
}
}
return $res;
}
}
/**
*
* {@inheritdoc}
*
* @see \Workerman\Events\EventInterface::del()
*/
public function del($fd, $flag)
{
switch ($flag) {
case self::EV_SIGNAL:
return pcntl_signal($fd, SIG_IGN, false);
case self::EV_TIMER:
case self::EV_TIMER_ONCE:
// already remove in EV_TIMER_ONCE callback.
if (! array_key_exists($fd, $this->_timer)) {
return true;
}
$res = Timer::clear($fd);
if ($res) {
$mapId = $this->_timer[$fd];
if (isset($mapId)) {
unset($this->_timerOnceMap[$mapId]);
}
unset($this->_timer[$fd]);
}
return $res;
case self::EV_READ:
case self::EV_WRITE:
$fd_key = (int) $fd;
if (isset($this->_fd[$fd_key])) {
$fd_val = $this->_fd[$fd_key];
if ($flag == self::EV_READ) {
$flag_remove = ~ SWOOLE_EVENT_READ;
} else {
$flag_remove = ~ SWOOLE_EVENT_WRITE;
}
$fd_val &= $flag_remove;
if (0 === $fd_val) {
$res = Event::del($fd);
if ($res) {
unset($this->_fd[$fd_key]);
}
} else {
$res = Event::set($fd, null, null, $fd_val);
if ($res) {
$this->_fd[$fd_key] = $fd_val;
}
}
} else {
$res = true;
}
return $res;
}
}
/**
*
* {@inheritdoc}
*
* @see \Workerman\Events\EventInterface::clearAllTimer()
*/
public function clearAllTimer()
{
foreach (array_keys($this->_timer) as $v) {
Timer::clear($v);
}
$this->_timer = array();
$this->_timerOnceMap = array();
}
/**
*
* {@inheritdoc}
*
* @see \Workerman\Events\EventInterface::loop()
*/
public function loop()
{
Event::wait();
}
/**
*
* {@inheritdoc}
*
* @see \Workerman\Events\EventInterface::destroy()
*/
public function destroy()
{
//Event::exit();
}
/**
*
* {@inheritdoc}
*
* @see \Workerman\Events\EventInterface::getTimerCount()
*/
public function getTimerCount()
{
return count($this->_timer);
}
}

@ -0,0 +1,40 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
// Display errors.
ini_set('display_errors', 'on');
// Reporting all.
error_reporting(E_ALL);
// Reset opcache.
if (function_exists('opcache_reset')) {
opcache_reset();
}
// For onError callback.
define('WORKERMAN_CONNECT_FAIL', 1);
// For onError callback.
define('WORKERMAN_SEND_FAIL', 2);
// Define OS Type
define('OS_TYPE_LINUX', 'linux');
define('OS_TYPE_WINDOWS', 'windows');
// Compatible with php7
if(!class_exists('Error'))
{
class Error extends Exception
{
}
}

@ -0,0 +1,140 @@
<?php
namespace Workerman\Lib;
class Db
{
public $db;
private static $instance = array();
public function __construct($data)
{
$dsn = "mysql:dbname=" . $data['dbname'] . ";host=" . $data['dbhost'];
$this->db = new \PDO($dsn, $data['dbuser'], $data['dbpassword']);
$this->db->query('set character set utf8mb4;');
$this->db->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
}
public static function get($key = 'Main'): Db
{
$database['dbname'] = Tool::ini($key . 'DbName');
$database['dbuser'] = Tool::ini($key . 'DbUser');
$database['dbpassword'] = Tool::ini($key . 'DbPassword');
$database['dbhost'] = Tool::ini($key . 'DbHost');
if (!isset(self::$instance[$database['dbname']]) || !self::$instance[$database['dbname']] instanceof self) {
self::$instance[$database['dbname']] = new Db($database);
} else {
try {
//断线重连
self::$instance[$database['dbname']]->db->getAttribute(\PDO::ATTR_SERVER_INFO);
} catch (\PDOException $e) {
if (strpos($e->getMessage(), 'MySQL server has gone away') !== false) {
self::$instance[$database['dbname']] = new Db($database);
}
}
}
return self::$instance[$database['dbname']];
}
/*********PDO**********/
public function count($sql, $parameters = null): int
{
return $this->exeupdate($sql, $parameters);
}
public function querysql($sql, $parameters = null)
{
return $this->exeupdate($sql, $parameters);
}
public function querysqlinsertid($sql, $parameters = null)
{
if ($this->exeupdate($sql, $parameters)) {
return $this->db->lastInsertId();
} else {
return 0;
}
}
public function getRow($sql, $parameters = null)
{
$res = $this->exequery($sql, $parameters);
if (count($res) > 0) {
return $res[0];
} else {
return array();
}
}
public function getAll($sql, $parameters = null)
{
$res = $this->exequery($sql, $parameters);
if (count($res) > 0) {
return $res;
} else {
return array();
}
}
public function beginTransaction()
{
$this->db->beginTransaction();
}
public function rollback()
{
$this->db->rollback();
}
public function commit()
{
$this->db->commit();
}
public function exequery($sql, $parameters = null)
{
$conn = $this->db;
$stmt = $conn->prepare($sql);
$stmt->execute($parameters);
$rs = $stmt->fetchall(\PDO::FETCH_ASSOC);
$stmt = null;
$conn = null;
return $rs;
}
public function exeupdate($sql, $parameters = null)
{
$stmt = $this->db->prepare($sql);
$stmt->execute($parameters);
$affectedrows = $stmt->rowcount();
$stmt = null;
$conn = null;
return $affectedrows;
}
public function checklink()
{
$res = $this->db->getAttribute(\PDO::ATTR_SERVER_INFO);
if (strpos($res, 'server has gone away') !== false) {
$this->db = null;
return false;
} else {
return true;
}
}
public function getinsertid()
{
return $this->db->lastInsertId();
}
public function close()
{
return $this->db = null;
}
}

@ -0,0 +1,43 @@
<?php
namespace Workerman\Lib;
class Db2
{
public static function i($db, $table, $array)
{
$insertArr = array();
$sql = "insert into `{$table}`(";
$sql1 = 'values(';
foreach ($array as $key => $value) {
$sql .= "`{$key}`,";
$sql1 .= "?,";
$insertArr[] = $value;
}
$sql = trim($sql, ',');
$sql1 = trim($sql1, ',');
$sql .= ")" . $sql1 . ")";
$db->querysql($sql, $insertArr);
return $db->getinsertid();
}
public static function u($db, $table, $array, $where, $where_a = [])
{
$updateArr = array();
$sql = "update `{$table}` set ";
foreach ($array as $key => $value) {
$sql .= "`{$key}`=?,";
$updateArr[] = $value;
}
$sql = trim($sql, ',');
$where = ' ' . $where;
$sql .= $where;
$updateArr = array_merge($updateArr, $where_a);
return $db->querysql($sql, $updateArr);
}
public static function d($db, $table, $where, $where_a = [])
{
$sql = "delete from `{$table}` " . $where;
$db->querysql($sql, $where_a);
}
}

@ -0,0 +1,179 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Lib;
use Workerman\Events\EventInterface;
use Workerman\Worker;
use Exception;
/**
* Timer.
*
* example:
* Workerman\Lib\Timer::add($time_interval, callback, array($arg1, $arg2..));
*/
class Timer
{
/**
* Tasks that based on ALARM signal.
* [
* run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]],
* run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]],
* ..
* ]
*
* @var array
*/
protected static $_tasks = array();
/**
* event
*
* @var \Workerman\Events\EventInterface
*/
protected static $_event = null;
/**
* Init.
*
* @param \Workerman\Events\EventInterface $event
* @return void
*/
public static function init($event = null)
{
if ($event) {
self::$_event = $event;
} else {
if (function_exists('pcntl_signal')) {
pcntl_signal(SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false);
}
}
}
/**
* ALARM signal handler.
*
* @return void
*/
public static function signalHandle()
{
if (!self::$_event) {
pcntl_alarm(1);
self::tick();
}
}
/**
* Add a timer.
*
* @param float $time_interval
* @param callable $func
* @param mixed $args
* @param bool $persistent
* @return int/false
*/
public static function add($time_interval, $func, $args = array(), $persistent = true)
{
if ($time_interval <= 0) {
Worker::safeEcho(new Exception("bad time_interval"));
return false;
}
if (self::$_event) {
return self::$_event->add($time_interval,
$persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE, $func, $args);
}
if (!is_callable($func)) {
Worker::safeEcho(new Exception("not callable"));
return false;
}
if (empty(self::$_tasks)) {
pcntl_alarm(1);
}
$time_now = time();
$run_time = $time_now + $time_interval;
if (!isset(self::$_tasks[$run_time])) {
self::$_tasks[$run_time] = array();
}
self::$_tasks[$run_time][] = array($func, (array)$args, $persistent, $time_interval);
return 1;
}
/**
* Tick.
*
* @return void
*/
public static function tick()
{
if (empty(self::$_tasks)) {
pcntl_alarm(0);
return;
}
$time_now = time();
foreach (self::$_tasks as $run_time => $task_data) {
if ($time_now >= $run_time) {
foreach ($task_data as $index => $one_task) {
$task_func = $one_task[0];
$task_args = $one_task[1];
$persistent = $one_task[2];
$time_interval = $one_task[3];
try {
call_user_func_array($task_func, $task_args);
} catch (\Exception $e) {
Worker::safeEcho($e);
}
if ($persistent) {
self::add($time_interval, $task_func, $task_args);
}
}
unset(self::$_tasks[$run_time]);
}
}
}
/**
* Remove a timer.
*
* @param mixed $timer_id
* @return bool
*/
public static function del($timer_id)
{
if (self::$_event) {
return self::$_event->del($timer_id, EventInterface::EV_TIMER);
}
return false;
}
/**
* Remove all timers.
*
* @return void
*/
public static function delAll()
{
self::$_tasks = array();
pcntl_alarm(0);
if (self::$_event) {
self::$_event->clearAllTimer();
}
}
}

@ -0,0 +1,83 @@
<?php
namespace Workerman\Lib;
class Tool
{
// region 获取 UUID
public static function uuid($break = '-'): string
{
$chars = md5(uniqid(mt_rand(), true));
$chars_arr = [
substr($chars, 0, 8),
substr($chars, 8, 4),
substr($chars, 12, 4),
substr($chars, 16, 4),
substr($chars, 20, 12),
];
return implode($break, $chars_arr);
}
// endregion
// region 发送POST请求
public static function post($url, $data = [], $decode = true, $type = 'json')
{
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
if ($type === 'data') {
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
}
if ($type === 'json') {
$data_string = json_encode($data, JSON_UNESCAPED_UNICODE);
curl_setopt($curl, CURLOPT_HTTPHEADER, [
'Content-Type: application/json; charset=utf-8',
'Content-Length: ' . strlen($data_string)
]);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
}
$r = curl_exec($curl);
curl_close($curl);
if ($decode) {
return json_decode($r, true);
} else {
return $r;
}
}
// endregion
// region 读取 Config ini
public static function ini($key, $default = false)
{
if (!$key) return $default;
$config_file_path = dirname(__DIR__, 2) . '/config.ini';
$config_info = parse_ini_file($config_file_path);
return (isset($config_info[$key])) ? $config_info[$key] : $default;
}
// endregion
// region 10位时间戳 格式化
public static function date($time = false, $format = "Y-m-d H:i:s")
{
if (!$time) $time = time();
return date($format, $time);
}
// endregion
// region 去除空格
public static function ge($str)
{
return preg_replace("/\s+/", ' ', $str);
}
// endregion
// region 毫秒时间戳
public static function time()
{
return floor(microtime(true) * 1000);
}
// endregion
}

@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2009-2015 walkor<walkor@workerman.net> and contributors (see https://github.com/walkor/workerman/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

@ -0,0 +1,61 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Protocols;
use Workerman\Connection\TcpConnection;
/**
* Frame Protocol.
*/
class Frame
{
/**
* Check the integrity of the package.
*
* @param string $buffer
* @param TcpConnection $connection
* @return int
*/
public static function input($buffer, TcpConnection $connection)
{
if (strlen($buffer) < 4) {
return 0;
}
$unpack_data = unpack('Ntotal_length', $buffer);
return $unpack_data['total_length'];
}
/**
* Decode.
*
* @param string $buffer
* @return string
*/
public static function decode($buffer)
{
return substr($buffer, 4);
}
/**
* Encode.
*
* @param string $buffer
* @return string
*/
public static function encode($buffer)
{
$total_length = 4 + strlen($buffer);
return pack('N', $total_length) . $buffer;
}
}

@ -0,0 +1,701 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Protocols;
use Workerman\Connection\TcpConnection;
use Workerman\Worker;
/**
* http protocol
*/
class Http
{
/**
* The supported HTTP methods
* @var array
*/
public static $methods = array('GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS');
/**
* Check the integrity of the package.
*
* @param string $recv_buffer
* @param TcpConnection $connection
* @return int
*/
public static function input($recv_buffer, TcpConnection $connection)
{
if (!strpos($recv_buffer, "\r\n\r\n")) {
// Judge whether the package length exceeds the limit.
if (strlen($recv_buffer) >= $connection->maxPackageSize) {
$connection->close();
return 0;
}
return 0;
}
list($header,) = explode("\r\n\r\n", $recv_buffer, 2);
$method = substr($header, 0, strpos($header, ' '));
if(in_array($method, static::$methods)) {
return static::getRequestSize($header, $method);
}else{
$connection->send("HTTP/1.1 400 Bad Request\r\n\r\n", true);
return 0;
}
}
/**
* Get whole size of the request
* includes the request headers and request body.
* @param string $header The request headers
* @param string $method The request method
* @return integer
*/
protected static function getRequestSize($header, $method)
{
if($method === 'GET' || $method === 'OPTIONS' || $method === 'HEAD') {
return strlen($header) + 4;
}
$match = array();
if (preg_match("/\r\nContent-Length: ?(\d+)/i", $header, $match)) {
$content_length = isset($match[1]) ? $match[1] : 0;
return $content_length + strlen($header) + 4;
}
return $method === 'DELETE' ? strlen($header) + 4 : 0;
}
/**
* Parse $_POST、$_GET、$_COOKIE.
*
* @param string $recv_buffer
* @param TcpConnection $connection
* @return array
*/
public static function decode($recv_buffer, TcpConnection $connection)
{
// Init.
$_POST = $_GET = $_COOKIE = $_REQUEST = $_SESSION = $_FILES = array();
$GLOBALS['HTTP_RAW_POST_DATA'] = '';
// Clear cache.
HttpCache::$header = array('Connection' => 'Connection: keep-alive');
HttpCache::$instance = new HttpCache();
// $_SERVER
$_SERVER = array(
'QUERY_STRING' => '',
'REQUEST_METHOD' => '',
'REQUEST_URI' => '',
'SERVER_PROTOCOL' => '',
'SERVER_SOFTWARE' => 'workerman/'.Worker::VERSION,
'SERVER_NAME' => '',
'HTTP_HOST' => '',
'HTTP_USER_AGENT' => '',
'HTTP_ACCEPT' => '',
'HTTP_ACCEPT_LANGUAGE' => '',
'HTTP_ACCEPT_ENCODING' => '',
'HTTP_COOKIE' => '',
'HTTP_CONNECTION' => '',
'CONTENT_TYPE' => '',
'REMOTE_ADDR' => '',
'REMOTE_PORT' => '0',
'REQUEST_TIME' => time()
);
// Parse headers.
list($http_header, $http_body) = explode("\r\n\r\n", $recv_buffer, 2);
$header_data = explode("\r\n", $http_header);
list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ',
$header_data[0]);
$http_post_boundary = '';
unset($header_data[0]);
foreach ($header_data as $content) {
// \r\n\r\n
if (empty($content)) {
continue;
}
list($key, $value) = explode(':', $content, 2);
$key = str_replace('-', '_', strtoupper($key));
$value = trim($value);
$_SERVER['HTTP_' . $key] = $value;
switch ($key) {
// HTTP_HOST
case 'HOST':
$tmp = explode(':', $value);
$_SERVER['SERVER_NAME'] = $tmp[0];
if (isset($tmp[1])) {
$_SERVER['SERVER_PORT'] = $tmp[1];
}
break;
// cookie
case 'COOKIE':
parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE);
break;
// content-type
case 'CONTENT_TYPE':
if (!preg_match('/boundary="?(\S+)"?/', $value, $match)) {
if ($pos = strpos($value, ';')) {
$_SERVER['CONTENT_TYPE'] = substr($value, 0, $pos);
} else {
$_SERVER['CONTENT_TYPE'] = $value;
}
} else {
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
$http_post_boundary = '--' . $match[1];
}
break;
case 'CONTENT_LENGTH':
$_SERVER['CONTENT_LENGTH'] = $value;
break;
case 'UPGRADE':
if($value=='websocket'){
$connection->protocol = "\\Workerman\\Protocols\\Websocket";
return \Workerman\Protocols\Websocket::input($recv_buffer,$connection);
}
break;
}
}
if(isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE){
HttpCache::$gzip = true;
}
// Parse $_POST.
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_SERVER['CONTENT_TYPE'])) {
switch ($_SERVER['CONTENT_TYPE']) {
case 'multipart/form-data':
self::parseUploadFiles($http_body, $http_post_boundary);
break;
case 'application/json':
$_POST = json_decode($http_body, true);
break;
case 'application/x-www-form-urlencoded':
parse_str($http_body, $_POST);
break;
}
}
}
// Parse other HTTP action parameters
if ($_SERVER['REQUEST_METHOD'] != 'GET' && $_SERVER['REQUEST_METHOD'] != "POST") {
$data = array();
if ($_SERVER['CONTENT_TYPE'] === "application/x-www-form-urlencoded") {
parse_str($http_body, $data);
} elseif ($_SERVER['CONTENT_TYPE'] === "application/json") {
$data = json_decode($http_body, true);
}
$_REQUEST = array_merge($_REQUEST, $data);
}
// HTTP_RAW_REQUEST_DATA HTTP_RAW_POST_DATA
$GLOBALS['HTTP_RAW_REQUEST_DATA'] = $GLOBALS['HTTP_RAW_POST_DATA'] = $http_body;
// QUERY_STRING
$_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
if ($_SERVER['QUERY_STRING']) {
// $GET
parse_str($_SERVER['QUERY_STRING'], $_GET);
} else {
$_SERVER['QUERY_STRING'] = '';
}
if (is_array($_POST)) {
// REQUEST
$_REQUEST = array_merge($_GET, $_POST, $_REQUEST);
} else {
// REQUEST
$_REQUEST = array_merge($_GET, $_REQUEST);
}
// REMOTE_ADDR REMOTE_PORT
$_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp();
$_SERVER['REMOTE_PORT'] = $connection->getRemotePort();
return array('get' => $_GET, 'post' => $_POST, 'cookie' => $_COOKIE, 'server' => $_SERVER, 'files' => $_FILES);
}
/**
* Http encode.
*
* @param string $content
* @param TcpConnection $connection
* @return string
*/
public static function encode($content, TcpConnection $connection)
{
// Default http-code.
if (!isset(HttpCache::$header['Http-Code'])) {
$header = "HTTP/1.1 200 OK\r\n";
} else {
$header = HttpCache::$header['Http-Code'] . "\r\n";
unset(HttpCache::$header['Http-Code']);
}
// Content-Type
if (!isset(HttpCache::$header['Content-Type'])) {
$header .= "Content-Type: text/html;charset=utf-8\r\n";
}
// other headers
foreach (HttpCache::$header as $key => $item) {
if ('Set-Cookie' === $key && is_array($item)) {
foreach ($item as $it) {
$header .= $it . "\r\n";
}
} else {
$header .= $item . "\r\n";
}
}
if(HttpCache::$gzip && isset($connection->gzip) && $connection->gzip){
$header .= "Content-Encoding: gzip\r\n";
$content = gzencode($content,$connection->gzip);
}
// header
$header .= "Server: workerman/" . Worker::VERSION . "\r\nContent-Length: " . strlen($content) . "\r\n\r\n";
// save session
self::sessionWriteClose();
// the whole http package
return $header . $content;
}
/**
* 设置http头
*
* @return bool|void
*/
public static function header($content, $replace = true, $http_response_code = 0)
{
if (PHP_SAPI != 'cli') {
return $http_response_code ? header($content, $replace, $http_response_code) : header($content, $replace);
}
if (strpos($content, 'HTTP') === 0) {
$key = 'Http-Code';
} else {
$key = strstr($content, ":", true);
if (empty($key)) {
return false;
}
}
if ('location' === strtolower($key) && !$http_response_code) {
return self::header($content, true, 302);
}
if (isset(HttpCache::$codes[$http_response_code])) {
HttpCache::$header['Http-Code'] = "HTTP/1.1 $http_response_code " . HttpCache::$codes[$http_response_code];
if ($key === 'Http-Code') {
return true;
}
}
if ($key === 'Set-Cookie') {
HttpCache::$header[$key][] = $content;
} else {
HttpCache::$header[$key] = $content;
}
return true;
}
/**
* Remove header.
*
* @param string $name
* @return void
*/
public static function headerRemove($name)
{
if (PHP_SAPI != 'cli') {
header_remove($name);
return;
}
unset(HttpCache::$header[$name]);
}
/**
* Set cookie.
*
* @param string $name
* @param string $value
* @param integer $maxage
* @param string $path
* @param string $domain
* @param bool $secure
* @param bool $HTTPOnly
* @return bool|void
*/
public static function setcookie(
$name,
$value = '',
$maxage = 0,
$path = '',
$domain = '',
$secure = false,
$HTTPOnly = false
) {
if (PHP_SAPI != 'cli') {
return setcookie($name, $value, $maxage, $path, $domain, $secure, $HTTPOnly);
}
return self::header(
'Set-Cookie: ' . $name . '=' . rawurlencode($value)
. (empty($domain) ? '' : '; Domain=' . $domain)
. (empty($maxage) ? '' : '; Max-Age=' . $maxage)
. (empty($path) ? '' : '; Path=' . $path)
. (!$secure ? '' : '; Secure')
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
}
/**
* sessionCreateId
*
* @return string
*/
public static function sessionCreateId()
{
mt_srand();
return bin2hex(pack('d', microtime(true)) . pack('N',mt_rand(0, 2147483647)));
}
/**
* sessionId
*
* @param string $id
*
* @return string|null
*/
public static function sessionId($id = null)
{
if (PHP_SAPI != 'cli') {
return $id ? session_id($id) : session_id();
}
if (static::sessionStarted() && HttpCache::$instance->sessionFile) {
return str_replace('ses_', '', basename(HttpCache::$instance->sessionFile));
}
return '';
}
/**
* sessionName
*
* @param string $name
*
* @return string
*/
public static function sessionName($name = null)
{
if (PHP_SAPI != 'cli') {
return $name ? session_name($name) : session_name();
}
$session_name = HttpCache::$sessionName;
if ($name && ! static::sessionStarted()) {
HttpCache::$sessionName = $name;
}
return $session_name;
}
/**
* sessionSavePath
*
* @param string $path
*
* @return void
*/
public static function sessionSavePath($path = null)
{
if (PHP_SAPI != 'cli') {
return $path ? session_save_path($path) : session_save_path();
}
if ($path && is_dir($path) && is_writable($path) && !static::sessionStarted()) {
HttpCache::$sessionPath = $path;
}
return HttpCache::$sessionPath;
}
/**
* sessionStarted
*
* @return bool
*/
public static function sessionStarted()
{
if (!HttpCache::$instance) return false;
return HttpCache::$instance->sessionStarted;
}
/**
* sessionStart
*
* @return bool
*/
public static function sessionStart()
{
if (PHP_SAPI != 'cli') {
return session_start();
}
self::tryGcSessions();
if (HttpCache::$instance->sessionStarted) {
Worker::safeEcho("already sessionStarted\n");
return true;
}
HttpCache::$instance->sessionStarted = true;
// Generate a SID.
if (!isset($_COOKIE[HttpCache::$sessionName]) || !is_file(HttpCache::$sessionPath . '/ses_' . $_COOKIE[HttpCache::$sessionName])) {
// Create a unique session_id and the associated file name.
while (true) {
$session_id = static::sessionCreateId();
if (!is_file($file_name = HttpCache::$sessionPath . '/ses_' . $session_id)) break;
}
HttpCache::$instance->sessionFile = $file_name;
return self::setcookie(
HttpCache::$sessionName
, $session_id
, ini_get('session.cookie_lifetime')
, ini_get('session.cookie_path')
, ini_get('session.cookie_domain')
, ini_get('session.cookie_secure')
, ini_get('session.cookie_httponly')
);
}
if (!HttpCache::$instance->sessionFile) {
HttpCache::$instance->sessionFile = HttpCache::$sessionPath . '/ses_' . $_COOKIE[HttpCache::$sessionName];
}
// Read session from session file.
if (HttpCache::$instance->sessionFile) {
$raw = file_get_contents(HttpCache::$instance->sessionFile);
if ($raw) {
$_SESSION = unserialize($raw);
}
}
return true;
}
/**
* Save session.
*
* @return bool
*/
public static function sessionWriteClose()
{
if (PHP_SAPI != 'cli') {
return session_write_close();
}
if (!empty(HttpCache::$instance->sessionStarted) && !empty($_SESSION)) {
$session_str = serialize($_SESSION);
if ($session_str && HttpCache::$instance->sessionFile) {
return file_put_contents(HttpCache::$instance->sessionFile, $session_str);
}
}
return empty($_SESSION);
}
/**
* End, like call exit in php-fpm.
*
* @param string $msg
* @throws \Exception
*/
public static function end($msg = '')
{
if (PHP_SAPI != 'cli') {
exit($msg);
}
if ($msg) {
echo $msg;
}
throw new \Exception('jump_exit');
}
/**
* Get mime types.
*
* @return string
*/
public static function getMimeTypesFile()
{
return __DIR__ . '/Http/mime.types';
}
/**
* Parse $_FILES.
*
* @param string $http_body
* @param string $http_post_boundary
* @return void
*/
protected static function parseUploadFiles($http_body, $http_post_boundary)
{
$http_body = substr($http_body, 0, strlen($http_body) - (strlen($http_post_boundary) + 4));
$boundary_data_array = explode($http_post_boundary . "\r\n", $http_body);
if ($boundary_data_array[0] === '') {
unset($boundary_data_array[0]);
}
$key = -1;
foreach ($boundary_data_array as $boundary_data_buffer) {
list($boundary_header_buffer, $boundary_value) = explode("\r\n\r\n", $boundary_data_buffer, 2);
// Remove \r\n from the end of buffer.
$boundary_value = substr($boundary_value, 0, -2);
$key ++;
foreach (explode("\r\n", $boundary_header_buffer) as $item) {
list($header_key, $header_value) = explode(": ", $item);
$header_key = strtolower($header_key);
switch ($header_key) {
case "content-disposition":
// Is file data.
if (preg_match('/name="(.*?)"; filename="(.*?)"$/', $header_value, $match)) {
// Parse $_FILES.
$_FILES[$key] = array(
'name' => $match[1],
'file_name' => $match[2],
'file_data' => $boundary_value,
'file_size' => strlen($boundary_value),
);
break;
} // Is post field.
else {
// Parse $_POST.
if (preg_match('/name="(.*?)"$/', $header_value, $match)) {
$_POST[$match[1]] = $boundary_value;
}
}
break;
case "content-type":
// add file_type
$_FILES[$key]['file_type'] = trim($header_value);
break;
}
}
}
}
/**
* Try GC sessions.
*
* @return void
*/
public static function tryGcSessions()
{
if (HttpCache::$sessionGcProbability <= 0 ||
HttpCache::$sessionGcDivisor <= 0 ||
rand(1, HttpCache::$sessionGcDivisor) > HttpCache::$sessionGcProbability) {
return;
}
$time_now = time();
foreach(glob(HttpCache::$sessionPath.'/ses*') as $file) {
if(is_file($file) && $time_now - filemtime($file) > HttpCache::$sessionGcMaxLifeTime) {
unlink($file);
}
}
}
}
/**
* Http cache for the current http response.
*/
class HttpCache
{
public static $codes = array(
100 => 'Continue',
101 => 'Switching Protocols',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
306 => '(Unused)',
307 => 'Temporary Redirect',
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
422 => 'Unprocessable Entity',
423 => 'Locked',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
);
/**
* @var HttpCache
*/
public static $instance = null;
public static $header = array();
public static $gzip = false;
public static $sessionPath = '';
public static $sessionName = '';
public static $sessionGcProbability = 1;
public static $sessionGcDivisor = 1000;
public static $sessionGcMaxLifeTime = 1440;
public $sessionStarted = false;
public $sessionFile = '';
public static function init()
{
if (!self::$sessionName) {
self::$sessionName = ini_get('session.name');
}
if (!self::$sessionPath) {
self::$sessionPath = @session_save_path();
}
if (!self::$sessionPath || strpos(self::$sessionPath, 'tcp://') === 0) {
self::$sessionPath = sys_get_temp_dir();
}
if ($gc_probability = ini_get('session.gc_probability')) {
self::$sessionGcProbability = $gc_probability;
}
if ($gc_divisor = ini_get('session.gc_divisor')) {
self::$sessionGcDivisor = $gc_divisor;
}
if ($gc_max_life_time = ini_get('session.gc_maxlifetime')) {
self::$sessionGcMaxLifeTime = $gc_max_life_time;
}
}
}
HttpCache::init();

@ -0,0 +1,80 @@
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
application/x-javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/png png;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
image/svg+xml svg svgz;
image/webp webp;
application/java-archive jar war ear;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.ms-excel xls;
application/vnd.ms-powerpoint ppt;
application/vnd.wap.wmlc wmlc;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream eot;
application/octet-stream iso img;
application/octet-stream msi msp msm;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}

@ -0,0 +1,52 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Protocols;
use Workerman\Connection\ConnectionInterface;
/**
* Protocol interface
*/
interface ProtocolInterface
{
/**
* Check the integrity of the package.
* Please return the length of package.
* If length is unknow please return 0 that mean wating more data.
* If the package has something wrong please return false the connection will be closed.
*
* @param ConnectionInterface $connection
* @param string $recv_buffer
* @return int|false
*/
public static function input($recv_buffer, ConnectionInterface $connection);
/**
* Decode package and emit onMessage($message) callback, $message is the result that decode returned.
*
* @param ConnectionInterface $connection
* @param string $recv_buffer
* @return mixed
*/
public static function decode($recv_buffer, ConnectionInterface $connection);
/**
* Encode package brefore sending to client.
*
* @param ConnectionInterface $connection
* @param mixed $data
* @return string
*/
public static function encode($data, ConnectionInterface $connection);
}

@ -0,0 +1,70 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Protocols;
use Workerman\Connection\TcpConnection;
/**
* Text Protocol.
*/
class Text
{
/**
* Check the integrity of the package.
*
* @param string $buffer
* @param TcpConnection $connection
* @return int
*/
public static function input($buffer, TcpConnection $connection)
{
// Judge whether the package length exceeds the limit.
if (strlen($buffer) >= $connection->maxPackageSize) {
$connection->close();
return 0;
}
// Find the position of "\n".
$pos = strpos($buffer, "\n");
// No "\n", packet length is unknown, continue to wait for the data so return 0.
if ($pos === false) {
return 0;
}
// Return the current package length.
return $pos + 1;
}
/**
* Encode.
*
* @param string $buffer
* @return string
*/
public static function encode($buffer)
{
// Add "\n"
return $buffer . "\n";
}
/**
* Decode.
*
* @param string $buffer
* @return string
*/
public static function decode($buffer)
{
// Remove "\n"
return rtrim($buffer, "\r\n");
}
}

@ -0,0 +1,503 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Protocols;
use Workerman\Connection\ConnectionInterface;
use Workerman\Connection\TcpConnection;
use Workerman\Worker;
/**
* WebSocket protocol.
*/
class Websocket implements \Workerman\Protocols\ProtocolInterface
{
/**
* Websocket blob type.
*
* @var string
*/
const BINARY_TYPE_BLOB = "\x81";
/**
* Websocket arraybuffer type.
*
* @var string
*/
const BINARY_TYPE_ARRAYBUFFER = "\x82";
/**
* Check the integrity of the package.
*
* @param string $buffer
* @param ConnectionInterface $connection
* @return int
*/
public static function input($buffer, ConnectionInterface $connection)
{
// Receive length.
$recv_len = strlen($buffer);
// We need more data.
if ($recv_len < 6) {
return 0;
}
// Has not yet completed the handshake.
if (empty($connection->websocketHandshake)) {
return static::dealHandshake($buffer, $connection);
}
// Buffer websocket frame data.
if ($connection->websocketCurrentFrameLength) {
// We need more frame data.
if ($connection->websocketCurrentFrameLength > $recv_len) {
// Return 0, because it is not clear the full packet length, waiting for the frame of fin=1.
return 0;
}
} else {
$firstbyte = ord($buffer[0]);
$secondbyte = ord($buffer[1]);
$data_len = $secondbyte & 127;
$is_fin_frame = $firstbyte >> 7;
$masked = $secondbyte >> 7;
if (!$masked) {
Worker::safeEcho("frame not masked so close the connection\n");
$connection->close();
return 0;
}
$opcode = $firstbyte & 0xf;
switch ($opcode) {
case 0x0:
break;
// Blob type.
case 0x1:
break;
// Arraybuffer type.
case 0x2:
break;
// Close package.
case 0x8:
// Try to emit onWebSocketClose callback.
if (isset($connection->onWebSocketClose) || isset($connection->worker->onWebSocketClose)) {
try {
call_user_func(isset($connection->onWebSocketClose)?$connection->onWebSocketClose:$connection->worker->onWebSocketClose, $connection);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
} // Close connection.
else {
$connection->close("\x88\x02\x27\x10", true);
}
return 0;
// Ping package.
case 0x9:
break;
// Pong package.
case 0xa:
break;
// Wrong opcode.
default :
Worker::safeEcho("error opcode $opcode and close websocket connection. Buffer:" . bin2hex($buffer) . "\n");
$connection->close();
return 0;
}
// Calculate packet length.
$head_len = 6;
if ($data_len === 126) {
$head_len = 8;
if ($head_len > $recv_len) {
return 0;
}
$pack = unpack('nn/ntotal_len', $buffer);
$data_len = $pack['total_len'];
} else {
if ($data_len === 127) {
$head_len = 14;
if ($head_len > $recv_len) {
return 0;
}
$arr = unpack('n/N2c', $buffer);
$data_len = $arr['c1']*4294967296 + $arr['c2'];
}
}
$current_frame_length = $head_len + $data_len;
$total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length;
if ($total_package_size > $connection->maxPackageSize) {
Worker::safeEcho("error package. package_length=$total_package_size\n");
$connection->close();
return 0;
}
if ($is_fin_frame) {
if ($opcode === 0x9) {
if ($recv_len >= $current_frame_length) {
$ping_data = static::decode(substr($buffer, 0, $current_frame_length), $connection);
$connection->consumeRecvBuffer($current_frame_length);
$tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB;
$connection->websocketType = "\x8a";
if (isset($connection->onWebSocketPing) || isset($connection->worker->onWebSocketPing)) {
try {
call_user_func(isset($connection->onWebSocketPing)?$connection->onWebSocketPing:$connection->worker->onWebSocketPing, $connection, $ping_data);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
} else {
$connection->send($ping_data);
}
$connection->websocketType = $tmp_connection_type;
if ($recv_len > $current_frame_length) {
return static::input(substr($buffer, $current_frame_length), $connection);
}
}
return 0;
} else if ($opcode === 0xa) {
if ($recv_len >= $current_frame_length) {
$pong_data = static::decode(substr($buffer, 0, $current_frame_length), $connection);
$connection->consumeRecvBuffer($current_frame_length);
$tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB;
$connection->websocketType = "\x8a";
// Try to emit onWebSocketPong callback.
if (isset($connection->onWebSocketPong) || isset($connection->worker->onWebSocketPong)) {
try {
call_user_func(isset($connection->onWebSocketPong)?$connection->onWebSocketPong:$connection->worker->onWebSocketPong, $connection, $pong_data);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
$connection->websocketType = $tmp_connection_type;
if ($recv_len > $current_frame_length) {
return static::input(substr($buffer, $current_frame_length), $connection);
}
}
return 0;
}
return $current_frame_length;
} else {
$connection->websocketCurrentFrameLength = $current_frame_length;
}
}
// Received just a frame length data.
if ($connection->websocketCurrentFrameLength === $recv_len) {
static::decode($buffer, $connection);
$connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
$connection->websocketCurrentFrameLength = 0;
return 0;
} // The length of the received data is greater than the length of a frame.
elseif ($connection->websocketCurrentFrameLength < $recv_len) {
static::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection);
$connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
$current_frame_length = $connection->websocketCurrentFrameLength;
$connection->websocketCurrentFrameLength = 0;
// Continue to read next frame.
return static::input(substr($buffer, $current_frame_length), $connection);
} // The length of the received data is less than the length of a frame.
else {
return 0;
}
}
/**
* Websocket encode.
*
* @param string $buffer
* @param ConnectionInterface $connection
* @return string
*/
public static function encode($buffer, ConnectionInterface $connection)
{
if (!is_scalar($buffer)) {
throw new \Exception("You can't send(" . gettype($buffer) . ") to client, you need to convert it to a string. ");
}
$len = strlen($buffer);
if (empty($connection->websocketType)) {
$connection->websocketType = static::BINARY_TYPE_BLOB;
}
$first_byte = $connection->websocketType;
if ($len <= 125) {
$encode_buffer = $first_byte . chr($len) . $buffer;
} else {
if ($len <= 65535) {
$encode_buffer = $first_byte . chr(126) . pack("n", $len) . $buffer;
} else {
$encode_buffer = $first_byte . chr(127) . pack("xxxxN", $len) . $buffer;
}
}
// Handshake not completed so temporary buffer websocket data waiting for send.
if (empty($connection->websocketHandshake)) {
if (empty($connection->tmpWebsocketData)) {
$connection->tmpWebsocketData = '';
}
// If buffer has already full then discard the current package.
if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) {
if ($connection->onError) {
try {
call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package');
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
return '';
}
$connection->tmpWebsocketData .= $encode_buffer;
// Check buffer is full.
if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) {
if ($connection->onBufferFull) {
try {
call_user_func($connection->onBufferFull, $connection);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
}
// Return empty string.
return '';
}
return $encode_buffer;
}
/**
* Websocket decode.
*
* @param string $buffer
* @param ConnectionInterface $connection
* @return string
*/
public static function decode($buffer, ConnectionInterface $connection)
{
$len = ord($buffer[1]) & 127;
if ($len === 126) {
$masks = substr($buffer, 4, 4);
$data = substr($buffer, 8);
} else {
if ($len === 127) {
$masks = substr($buffer, 10, 4);
$data = substr($buffer, 14);
} else {
$masks = substr($buffer, 2, 4);
$data = substr($buffer, 6);
}
}
$dataLength = strlen($data);
$masks = str_repeat($masks, floor($dataLength / 4)) . substr($masks, 0, $dataLength % 4);
$decoded = $data ^ $masks;
if ($connection->websocketCurrentFrameLength) {
$connection->websocketDataBuffer .= $decoded;
return $connection->websocketDataBuffer;
} else {
if ($connection->websocketDataBuffer !== '') {
$decoded = $connection->websocketDataBuffer . $decoded;
$connection->websocketDataBuffer = '';
}
return $decoded;
}
}
/**
* Websocket handshake.
*
* @param string $buffer
* @param \Workerman\Connection\TcpConnection $connection
* @return int
*/
protected static function dealHandshake($buffer, $connection)
{
// HTTP protocol.
if (0 === strpos($buffer, 'GET')) {
// Find \r\n\r\n.
$heder_end_pos = strpos($buffer, "\r\n\r\n");
if (!$heder_end_pos) {
return 0;
}
$header_length = $heder_end_pos + 4;
// Get Sec-WebSocket-Key.
$Sec_WebSocket_Key = '';
if (preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) {
$Sec_WebSocket_Key = $match[1];
} else {
$connection->send("HTTP/1.1 200 Websocket\r\nServer: workerman/".Worker::VERSION."\r\n\r\n<div style=\"text-align:center\"><h1>Websocket</h1><hr>powerd by <a href=\"https://www.workerman.net\">workerman ".Worker::VERSION."</a></div>",
true);
$connection->close();
return 0;
}
// Calculation websocket key.
$new_key = base64_encode(sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true));
// Handshake response data.
$handshake_message = "HTTP/1.1 101 Switching Protocols\r\n";
$handshake_message .= "Upgrade: websocket\r\n";
$handshake_message .= "Sec-WebSocket-Version: 13\r\n";
$handshake_message .= "Connection: Upgrade\r\n";
$handshake_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n";
// Websocket data buffer.
$connection->websocketDataBuffer = '';
// Current websocket frame length.
$connection->websocketCurrentFrameLength = 0;
// Current websocket frame data.
$connection->websocketCurrentFrameBuffer = '';
// Consume handshake data.
$connection->consumeRecvBuffer($header_length);
// blob or arraybuffer
if (empty($connection->websocketType)) {
$connection->websocketType = static::BINARY_TYPE_BLOB;
}
$has_server_header = false;
// Try to emit onWebSocketConnect callback.
if (isset($connection->onWebSocketConnect) || isset($connection->worker->onWebSocketConnect)) {
static::parseHttpHeader($buffer);
try {
call_user_func(isset($connection->onWebSocketConnect)?$connection->onWebSocketConnect:$connection->worker->onWebSocketConnect, $connection, $buffer);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
if (!empty($_SESSION) && class_exists('\GatewayWorker\Lib\Context')) {
$connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION);
}
$_GET = $_SERVER = $_SESSION = $_COOKIE = array();
if (isset($connection->headers)) {
if (is_array($connection->headers)) {
foreach ($connection->headers as $header) {
if (strpos($header, 'Server:') === 0) {
$has_server_header = true;
}
$handshake_message .= "$header\r\n";
}
} else {
$handshake_message .= "$connection->headers\r\n";
}
}
}
if (!$has_server_header) {
$handshake_message .= "Server: workerman/".Worker::VERSION."\r\n";
}
$handshake_message .= "\r\n";
// Send handshake response.
$connection->send($handshake_message, true);
// Mark handshake complete..
$connection->websocketHandshake = true;
// There are data waiting to be sent.
if (!empty($connection->tmpWebsocketData)) {
$connection->send($connection->tmpWebsocketData, true);
$connection->tmpWebsocketData = '';
}
if (strlen($buffer) > $header_length) {
return static::input(substr($buffer, $header_length), $connection);
}
return 0;
} // Is flash policy-file-request.
elseif (0 === strpos($buffer, '<polic')) {
$policy_xml = '<?xml version="1.0"?><cross-domain-policy><site-control permitted-cross-domain-policies="all"/><allow-access-from domain="*" to-ports="*"/></cross-domain-policy>' . "\0";
$connection->send($policy_xml, true);
$connection->consumeRecvBuffer(strlen($buffer));
return 0;
}
// Bad websocket handshake request.
$connection->send("HTTP/1.1 200 Websocket\r\nServer: workerman/".Worker::VERSION."\r\n\r\n<div style=\"text-align:center\"><h1>Websocket</h1><hr>powerd by <a href=\"https://www.workerman.net\">workerman ".Worker::VERSION."</a></div>",
true);
$connection->close();
return 0;
}
/**
* Parse http header.
*
* @param string $buffer
* @return void
*/
protected static function parseHttpHeader($buffer)
{
// Parse headers.
list($http_header, ) = explode("\r\n\r\n", $buffer, 2);
$header_data = explode("\r\n", $http_header);
if ($_SERVER) {
$_SERVER = array();
}
list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ',
$header_data[0]);
unset($header_data[0]);
foreach ($header_data as $content) {
// \r\n\r\n
if (empty($content)) {
continue;
}
list($key, $value) = explode(':', $content, 2);
$key = str_replace('-', '_', strtoupper($key));
$value = trim($value);
$_SERVER['HTTP_' . $key] = $value;
switch ($key) {
// HTTP_HOST
case 'HOST':
$tmp = explode(':', $value);
$_SERVER['SERVER_NAME'] = $tmp[0];
if (isset($tmp[1])) {
$_SERVER['SERVER_PORT'] = $tmp[1];
}
break;
// cookie
case 'COOKIE':
parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE);
break;
}
}
// QUERY_STRING
$_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
if ($_SERVER['QUERY_STRING']) {
// $GET
parse_str($_SERVER['QUERY_STRING'], $_GET);
} else {
$_SERVER['QUERY_STRING'] = '';
}
}
}

@ -0,0 +1,471 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Protocols;
use Workerman\Worker;
use Workerman\Lib\Timer;
use Workerman\Connection\TcpConnection;
/**
* Websocket protocol for client.
*/
class Ws
{
/**
* Websocket blob type.
*
* @var string
*/
const BINARY_TYPE_BLOB = "\x81";
/**
* Websocket arraybuffer type.
*
* @var string
*/
const BINARY_TYPE_ARRAYBUFFER = "\x82";
/**
* Check the integrity of the package.
*
* @param string $buffer
* @param ConnectionInterface $connection
* @return int
*/
public static function input($buffer, $connection)
{
if (empty($connection->handshakeStep)) {
Worker::safeEcho("recv data before handshake. Buffer:" . bin2hex($buffer) . "\n");
return false;
}
// Recv handshake response
if ($connection->handshakeStep === 1) {
return self::dealHandshake($buffer, $connection);
}
$recv_len = strlen($buffer);
if ($recv_len < 2) {
return 0;
}
// Buffer websocket frame data.
if ($connection->websocketCurrentFrameLength) {
// We need more frame data.
if ($connection->websocketCurrentFrameLength > $recv_len) {
// Return 0, because it is not clear the full packet length, waiting for the frame of fin=1.
return 0;
}
} else {
$firstbyte = ord($buffer[0]);
$secondbyte = ord($buffer[1]);
$data_len = $secondbyte & 127;
$is_fin_frame = $firstbyte >> 7;
$masked = $secondbyte >> 7;
if ($masked) {
Worker::safeEcho("frame masked so close the connection\n");
$connection->close();
return 0;
}
$opcode = $firstbyte & 0xf;
switch ($opcode) {
case 0x0:
break;
// Blob type.
case 0x1:
break;
// Arraybuffer type.
case 0x2:
break;
// Close package.
case 0x8:
// Try to emit onWebSocketClose callback.
if (isset($connection->onWebSocketClose)) {
try {
call_user_func($connection->onWebSocketClose, $connection);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
} // Close connection.
else {
$connection->close();
}
return 0;
// Ping package.
case 0x9:
break;
// Pong package.
case 0xa:
break;
// Wrong opcode.
default :
Worker::safeEcho("error opcode $opcode and close websocket connection. Buffer:" . $buffer . "\n");
$connection->close();
return 0;
}
// Calculate packet length.
if ($data_len === 126) {
if (strlen($buffer) < 4) {
return 0;
}
$pack = unpack('nn/ntotal_len', $buffer);
$current_frame_length = $pack['total_len'] + 4;
} else if ($data_len === 127) {
if (strlen($buffer) < 10) {
return 0;
}
$arr = unpack('n/N2c', $buffer);
$current_frame_length = $arr['c1']*4294967296 + $arr['c2'] + 10;
} else {
$current_frame_length = $data_len + 2;
}
$total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length;
if ($total_package_size > $connection->maxPackageSize) {
Worker::safeEcho("error package. package_length=$total_package_size\n");
$connection->close();
return 0;
}
if ($is_fin_frame) {
if ($opcode === 0x9) {
if ($recv_len >= $current_frame_length) {
$ping_data = static::decode(substr($buffer, 0, $current_frame_length), $connection);
$connection->consumeRecvBuffer($current_frame_length);
$tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB;
$connection->websocketType = "\x8a";
if (isset($connection->onWebSocketPing)) {
try {
call_user_func($connection->onWebSocketPing, $connection, $ping_data);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
} else {
$connection->send($ping_data);
}
$connection->websocketType = $tmp_connection_type;
if ($recv_len > $current_frame_length) {
return static::input(substr($buffer, $current_frame_length), $connection);
}
}
return 0;
} else if ($opcode === 0xa) {
if ($recv_len >= $current_frame_length) {
$pong_data = static::decode(substr($buffer, 0, $current_frame_length), $connection);
$connection->consumeRecvBuffer($current_frame_length);
$tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB;
$connection->websocketType = "\x8a";
// Try to emit onWebSocketPong callback.
if (isset($connection->onWebSocketPong)) {
try {
call_user_func($connection->onWebSocketPong, $connection, $pong_data);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
$connection->websocketType = $tmp_connection_type;
if ($recv_len > $current_frame_length) {
return static::input(substr($buffer, $current_frame_length), $connection);
}
}
return 0;
}
return $current_frame_length;
} else {
$connection->websocketCurrentFrameLength = $current_frame_length;
}
}
// Received just a frame length data.
if ($connection->websocketCurrentFrameLength === $recv_len) {
self::decode($buffer, $connection);
$connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
$connection->websocketCurrentFrameLength = 0;
return 0;
} // The length of the received data is greater than the length of a frame.
elseif ($connection->websocketCurrentFrameLength < $recv_len) {
self::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection);
$connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
$current_frame_length = $connection->websocketCurrentFrameLength;
$connection->websocketCurrentFrameLength = 0;
// Continue to read next frame.
return self::input(substr($buffer, $current_frame_length), $connection);
} // The length of the received data is less than the length of a frame.
else {
return 0;
}
}
/**
* Websocket encode.
*
* @param string $buffer
* @param ConnectionInterface $connection
* @return string
*/
public static function encode($payload, $connection)
{
if (empty($connection->websocketType)) {
$connection->websocketType = self::BINARY_TYPE_BLOB;
}
$payload = (string)$payload;
if (empty($connection->handshakeStep)) {
static::sendHandshake($connection);
}
$mask = 1;
$mask_key = "\x00\x00\x00\x00";
$pack = '';
$length = $length_flag = strlen($payload);
if (65535 < $length) {
$pack = pack('NN', ($length & 0xFFFFFFFF00000000) >> 32, $length & 0x00000000FFFFFFFF);
$length_flag = 127;
} else if (125 < $length) {
$pack = pack('n*', $length);
$length_flag = 126;
}
$head = ($mask << 7) | $length_flag;
$head = $connection->websocketType . chr($head) . $pack;
$frame = $head . $mask_key;
// append payload to frame:
$mask_key = str_repeat($mask_key, floor($length / 4)) . substr($mask_key, 0, $length % 4);
$frame .= $payload ^ $mask_key;
if ($connection->handshakeStep === 1) {
// If buffer has already full then discard the current package.
if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) {
if ($connection->onError) {
try {
call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package');
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
return '';
}
$connection->tmpWebsocketData = $connection->tmpWebsocketData . $frame;
// Check buffer is full.
if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) {
if ($connection->onBufferFull) {
try {
call_user_func($connection->onBufferFull, $connection);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
}
return '';
}
return $frame;
}
/**
* Websocket decode.
*
* @param string $buffer
* @param ConnectionInterface $connection
* @return string
*/
public static function decode($bytes, $connection)
{
$data_length = ord($bytes[1]);
if ($data_length === 126) {
$decoded_data = substr($bytes, 4);
} else if ($data_length === 127) {
$decoded_data = substr($bytes, 10);
} else {
$decoded_data = substr($bytes, 2);
}
if ($connection->websocketCurrentFrameLength) {
$connection->websocketDataBuffer .= $decoded_data;
return $connection->websocketDataBuffer;
} else {
if ($connection->websocketDataBuffer !== '') {
$decoded_data = $connection->websocketDataBuffer . $decoded_data;
$connection->websocketDataBuffer = '';
}
return $decoded_data;
}
}
/**
* Send websocket handshake data.
*
* @return void
*/
public static function onConnect($connection)
{
static::sendHandshake($connection);
}
/**
* Clean
*
* @param $connection
*/
public static function onClose($connection)
{
$connection->handshakeStep = null;
$connection->websocketCurrentFrameLength = 0;
$connection->tmpWebsocketData = '';
$connection->websocketDataBuffer = '';
if (!empty($connection->websocketPingTimer)) {
Timer::del($connection->websocketPingTimer);
$connection->websocketPingTimer = null;
}
}
/**
* Send websocket handshake.
*
* @param \Workerman\Connection\TcpConnection $connection
* @return void
*/
public static function sendHandshake($connection)
{
if (!empty($connection->handshakeStep)) {
return;
}
// Get Host.
$port = $connection->getRemotePort();
$host = $port === 80 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port;
// Handshake header.
$connection->websocketSecKey = base64_encode(md5(mt_rand(), true));
$user_header = isset($connection->headers) ? $connection->headers :
(isset($connection->wsHttpHeader) ? $connection->wsHttpHeader : null);
$user_header_str = '';
if (!empty($user_header)) {
if (is_array($user_header)){
foreach($user_header as $k=>$v){
$user_header_str .= "$k: $v\r\n";
}
} else {
$user_header_str .= $user_header;
}
$user_header_str = "\r\n".trim($user_header_str);
}
$header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n".
(!preg_match("/\nHost:/i", $user_header_str) ? "Host: $host\r\n" : '').
"Connection: Upgrade\r\n".
"Upgrade: websocket\r\n".
(isset($connection->websocketOrigin) ? "Origin: ".$connection->websocketOrigin."\r\n":'').
(isset($connection->WSClientProtocol)?"Sec-WebSocket-Protocol: ".$connection->WSClientProtocol."\r\n":'').
"Sec-WebSocket-Version: 13\r\n".
"Sec-WebSocket-Key: " . $connection->websocketSecKey . $user_header_str . "\r\n\r\n";
$connection->send($header, true);
$connection->handshakeStep = 1;
$connection->websocketCurrentFrameLength = 0;
$connection->websocketDataBuffer = '';
$connection->tmpWebsocketData = '';
}
/**
* Websocket handshake.
*
* @param string $buffer
* @param \Workerman\Connection\TcpConnection $connection
* @return int
*/
public static function dealHandshake($buffer, $connection)
{
$pos = strpos($buffer, "\r\n\r\n");
if ($pos) {
//checking Sec-WebSocket-Accept
if (preg_match("/Sec-WebSocket-Accept: *(.*?)\r\n/i", $buffer, $match)) {
if ($match[1] !== base64_encode(sha1($connection->websocketSecKey . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true))) {
Worker::safeEcho("Sec-WebSocket-Accept not match. Header:\n" . substr($buffer, 0, $pos) . "\n");
$connection->close();
return 0;
}
} else {
Worker::safeEcho("Sec-WebSocket-Accept not found. Header:\n" . substr($buffer, 0, $pos) . "\n");
$connection->close();
return 0;
}
// handshake complete
// Get WebSocket subprotocol (if specified by server)
if (preg_match("/Sec-WebSocket-Protocol: *(.*?)\r\n/i", $buffer, $match)) {
$connection->WSServerProtocol = trim($match[1]);
}
$connection->handshakeStep = 2;
$handshake_response_length = $pos + 4;
// Try to emit onWebSocketConnect callback.
if (isset($connection->onWebSocketConnect)) {
try {
call_user_func($connection->onWebSocketConnect, $connection, substr($buffer, 0, $handshake_response_length));
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
// Headbeat.
if (!empty($connection->websocketPingInterval)) {
$connection->websocketPingTimer = Timer::add($connection->websocketPingInterval, function() use ($connection){
if (false === $connection->send(pack('H*', '898000000000'), true)) {
Timer::del($connection->websocketPingTimer);
$connection->websocketPingTimer = null;
}
});
}
$connection->consumeRecvBuffer($handshake_response_length);
if (!empty($connection->tmpWebsocketData)) {
$connection->send($connection->tmpWebsocketData, true);
$connection->tmpWebsocketData = '';
}
if (strlen($buffer) > $handshake_response_length) {
return self::input(substr($buffer, $handshake_response_length), $connection);
}
}
return 0;
}
public static function WSSetProtocol($connection, $params) {
$connection->WSClientProtocol = $params[0];
}
public static function WSGetServerProtocol($connection) {
return (property_exists($connection, 'WSServerProtocol')?$connection->WSServerProtocol:null);
}
}

@ -0,0 +1,604 @@
# Workerman
[![Gitter](https://badges.gitter.im/walkor/Workerman.svg)](https://gitter.im/walkor/Workerman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge)
[![Latest Stable Version](https://poser.pugx.org/workerman/workerman/v/stable)](https://packagist.org/packages/workerman/workerman)
[![Total Downloads](https://poser.pugx.org/workerman/workerman/downloads)](https://packagist.org/packages/workerman/workerman)
[![Monthly Downloads](https://poser.pugx.org/workerman/workerman/d/monthly)](https://packagist.org/packages/workerman/workerman)
[![Daily Downloads](https://poser.pugx.org/workerman/workerman/d/daily)](https://packagist.org/packages/workerman/workerman)
[![License](https://poser.pugx.org/workerman/workerman/license)](https://packagist.org/packages/workerman/workerman)
## What is it
Workerman is an asynchronous event driven PHP framework with high performance for easily building fast, scalable network applications. Supports HTTP, Websocket, SSL and other custom protocols. Supports libevent/event extension, [HHVM](https://github.com/facebook/hhvm) , [ReactPHP](https://github.com/reactphp/react).
## Requires
PHP 5.3 or Higher
A POSIX compatible operating system (Linux, OSX, BSD)
POSIX and PCNTL extensions required
Event extension recommended for better performance
## Installation
```
composer require workerman/workerman
```
## Basic Usage
### A websocket server
```php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
// Create a Websocket server
$ws_worker = new Worker("websocket://0.0.0.0:2346");
// 4 processes
$ws_worker->count = 4;
// Emitted when new connection come
$ws_worker->onConnect = function($connection)
{
echo "New connection\n";
};
// Emitted when data received
$ws_worker->onMessage = function($connection, $data)
{
// Send hello $data
$connection->send('hello ' . $data);
};
// Emitted when connection closed
$ws_worker->onClose = function($connection)
{
echo "Connection closed\n";
};
// Run worker
Worker::runAll();
```
### An http server
```php
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
// #### http worker ####
$http_worker = new Worker("http://0.0.0.0:2345");
// 4 processes
$http_worker->count = 4;
// Emitted when data received
$http_worker->onMessage = function($connection, $data)
{
// $_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER, $_FILES are available
var_dump($_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER, $_FILES);
// send data to client
$connection->send("hello world \n");
};
// run all workers
Worker::runAll();
```
### A WebServer
```php
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\WebServer;
use Workerman\Worker;
// WebServer
$web = new WebServer("http://0.0.0.0:80");
// 4 processes
$web->count = 4;
// Set the root of domains
$web->addRoot('www.your_domain.com', '/your/path/Web');
$web->addRoot('www.another_domain.com', '/another/path/Web');
// run all workers
Worker::runAll();
```
### A tcp server
```php
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
// #### create socket and listen 1234 port ####
$tcp_worker = new Worker("tcp://0.0.0.0:1234");
// 4 processes
$tcp_worker->count = 4;
// Emitted when new connection come
$tcp_worker->onConnect = function($connection)
{
echo "New Connection\n";
};
// Emitted when data received
$tcp_worker->onMessage = function($connection, $data)
{
// send data to client
$connection->send("hello $data \n");
};
// Emitted when new connection come
$tcp_worker->onClose = function($connection)
{
echo "Connection closed\n";
};
Worker::runAll();
```
### Enable SSL
```php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
// SSL context.
$context = array(
'ssl' => array(
'local_cert' => '/your/path/of/server.pem',
'local_pk' => '/your/path/of/server.key',
'verify_peer' => false,
)
);
// Create a Websocket server with ssl context.
$ws_worker = new Worker("websocket://0.0.0.0:2346", $context);
// Enable SSL. WebSocket+SSL means that Secure WebSocket (wss://).
// The similar approaches for Https etc.
$ws_worker->transport = 'ssl';
$ws_worker->onMessage = function($connection, $data)
{
// Send hello $data
$connection->send('hello ' . $data);
};
Worker::runAll();
```
### Custom protocol
Protocols/MyTextProtocol.php
```php
namespace Protocols;
/**
* User defined protocol
* Format Text+"\n"
*/
class MyTextProtocol
{
public static function input($recv_buffer)
{
// Find the position of the first occurrence of "\n"
$pos = strpos($recv_buffer, "\n");
// Not a complete package. Return 0 because the length of package can not be calculated
if($pos === false)
{
return 0;
}
// Return length of the package
return $pos+1;
}
public static function decode($recv_buffer)
{
return trim($recv_buffer);
}
public static function encode($data)
{
return $data."\n";
}
}
```
```php
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
// #### MyTextProtocol worker ####
$text_worker = new Worker("MyTextProtocol://0.0.0.0:5678");
$text_worker->onConnect = function($connection)
{
echo "New connection\n";
};
$text_worker->onMessage = function($connection, $data)
{
// send data to client
$connection->send("hello world \n");
};
$text_worker->onClose = function($connection)
{
echo "Connection closed\n";
};
// run all workers
Worker::runAll();
```
### Timer
```php
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
use Workerman\Lib\Timer;
$task = new Worker();
$task->onWorkerStart = function($task)
{
// 2.5 seconds
$time_interval = 2.5;
$timer_id = Timer::add($time_interval,
function()
{
echo "Timer run\n";
}
);
};
// run all workers
Worker::runAll();
```
### AsyncTcpConnection (tcp/ws/text/frame etc...)
```php
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
use Workerman\Connection\AsyncTcpConnection;
$worker = new Worker();
$worker->onWorkerStart = function()
{
// Websocket protocol for client.
$ws_connection = new AsyncTcpConnection("ws://echo.websocket.org:80");
$ws_connection->onConnect = function($connection){
$connection->send('hello');
};
$ws_connection->onMessage = function($connection, $data){
echo "recv: $data\n";
};
$ws_connection->onError = function($connection, $code, $msg){
echo "error: $msg\n";
};
$ws_connection->onClose = function($connection){
echo "connection closed\n";
};
$ws_connection->connect();
};
Worker::runAll();
```
### Async Mysql of ReactPHP
```
composer require react/mysql
```
```php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
$worker = new Worker('tcp://0.0.0.0:6161');
$worker->onWorkerStart = function() {
global $mysql;
$loop = Worker::getEventLoop();
$mysql = new React\MySQL\Connection($loop, array(
'host' => '127.0.0.1',
'dbname' => 'dbname',
'user' => 'user',
'passwd' => 'passwd',
));
$mysql->on('error', function($e){
echo $e;
});
$mysql->connect(function ($e) {
if($e) {
echo $e;
} else {
echo "connect success\n";
}
});
};
$worker->onMessage = function($connection, $data) {
global $mysql;
$mysql->query('show databases' /*trim($data)*/, function ($command, $mysql) use ($connection) {
if ($command->hasError()) {
$error = $command->getError();
} else {
$results = $command->resultRows;
$fields = $command->resultFields;
$connection->send(json_encode($results));
}
});
};
Worker::runAll();
```
### Async Redis of ReactPHP
```
composer require clue/redis-react
```
```php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Clue\React\Redis\Factory;
use Clue\React\Redis\Client;
use Workerman\Worker;
$worker = new Worker('tcp://0.0.0.0:6161');
$worker->onWorkerStart = function() {
global $factory;
$loop = Worker::getEventLoop();
$factory = new Factory($loop);
};
$worker->onMessage = function($connection, $data) {
global $factory;
$factory->createClient('localhost:6379')->then(function (Client $client) use ($connection) {
$client->set('greeting', 'Hello world');
$client->append('greeting', '!');
$client->get('greeting')->then(function ($greeting) use ($connection){
// Hello world!
echo $greeting . PHP_EOL;
$connection->send($greeting);
});
$client->incr('invocation')->then(function ($n) use ($connection){
echo 'This is invocation #' . $n . PHP_EOL;
$connection->send($n);
});
});
};
Worker::runAll();
```
### Aysnc dns of ReactPHP
```
composer require react/dns
```
```php
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
$worker = new Worker('tcp://0.0.0.0:6161');
$worker->onWorkerStart = function() {
global $dns;
// Get event-loop.
$loop = Worker::getEventLoop();
$factory = new React\Dns\Resolver\Factory();
$dns = $factory->create('8.8.8.8', $loop);
};
$worker->onMessage = function($connection, $host) {
global $dns;
$host = trim($host);
$dns->resolve($host)->then(function($ip) use($host, $connection) {
$connection->send("$host: $ip");
},function($e) use($host, $connection){
$connection->send("$host: {$e->getMessage()}");
});
};
Worker::runAll();
```
### Http client of ReactPHP
```
composer require react/http-client
```
```php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
$worker = new Worker('tcp://0.0.0.0:6161');
$worker->onMessage = function($connection, $host) {
$loop = Worker::getEventLoop();
$client = new \React\HttpClient\Client($loop);
$request = $client->request('GET', trim($host));
$request->on('error', function(Exception $e) use ($connection) {
$connection->send($e);
});
$request->on('response', function ($response) use ($connection) {
$response->on('data', function ($data) use ($connection) {
$connection->send($data);
});
});
$request->end();
};
Worker::runAll();
```
### ZMQ of ReactPHP
```
composer require react/zmq
```
```php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
$worker = new Worker('text://0.0.0.0:6161');
$worker->onWorkerStart = function() {
global $pull;
$loop = Worker::getEventLoop();
$context = new React\ZMQ\Context($loop);
$pull = $context->getSocket(ZMQ::SOCKET_PULL);
$pull->bind('tcp://127.0.0.1:5555');
$pull->on('error', function ($e) {
var_dump($e->getMessage());
});
$pull->on('message', function ($msg) {
echo "Received: $msg\n";
});
};
Worker::runAll();
```
### STOMP of ReactPHP
```
composer require react/stomp
```
```php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
$worker = new Worker('text://0.0.0.0:6161');
$worker->onWorkerStart = function() {
global $client;
$loop = Worker::getEventLoop();
$factory = new React\Stomp\Factory($loop);
$client = $factory->createClient(array('vhost' => '/', 'login' => 'guest', 'passcode' => 'guest'));
$client
->connect()
->then(function ($client) use ($loop) {
$client->subscribe('/topic/foo', function ($frame) {
echo "Message received: {$frame->body}\n";
});
});
};
Worker::runAll();
```
## Available commands
```php start.php start ```
```php start.php start -d ```
![workerman start](http://www.workerman.net/img/workerman-start.png)
```php start.php status ```
![workerman satus](http://www.workerman.net/img/workerman-status.png?a=123)
```php start.php connections```
```php start.php stop ```
```php start.php restart ```
```php start.php reload ```
## Documentation
中文主页:[http://www.workerman.net](http://www.workerman.net)
中文文档: [http://doc.workerman.net](http://doc.workerman.net)
Documentation:[https://github.com/walkor/workerman-manual](https://github.com/walkor/workerman-manual/blob/master/english/src/SUMMARY.md)
# Benchmarks
```
CPU: Intel(R) Core(TM) i3-3220 CPU @ 3.30GHz and 4 processors totally
Memory: 8G
OS: Ubuntu 14.04 LTS
Software: ab
PHP: 5.5.9
```
**Codes**
```php
<?php
use Workerman\Worker;
$worker = new Worker('tcp://0.0.0.0:1234');
$worker->count=3;
$worker->onMessage = function($connection, $data)
{
$connection->send("HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nServer: workerman\r\nContent-Length: 5\r\n\r\nhello");
};
Worker::runAll();
```
**Result**
```shell
ab -n1000000 -c100 -k http://127.0.0.1:1234/
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 127.0.0.1 (be patient)
Completed 100000 requests
Completed 200000 requests
Completed 300000 requests
Completed 400000 requests
Completed 500000 requests
Completed 600000 requests
Completed 700000 requests
Completed 800000 requests
Completed 900000 requests
Completed 1000000 requests
Finished 1000000 requests
Server Software: workerman/3.1.4
Server Hostname: 127.0.0.1
Server Port: 1234
Document Path: /
Document Length: 5 bytes
Concurrency Level: 100
Time taken for tests: 7.240 seconds
Complete requests: 1000000
Failed requests: 0
Keep-Alive requests: 1000000
Total transferred: 73000000 bytes
HTML transferred: 5000000 bytes
Requests per second: 138124.14 [#/sec] (mean)
Time per request: 0.724 [ms] (mean)
Time per request: 0.007 [ms] (mean, across all concurrent requests)
Transfer rate: 9846.74 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 5
Processing: 0 1 0.2 1 9
Waiting: 0 1 0.2 1 9
Total: 0 1 0.2 1 9
Percentage of the requests served within a certain time (ms)
50% 1
66% 1
75% 1
80% 1
90% 1
95% 1
98% 1
99% 1
100% 9 (longest request)
```
## Other links with workerman
[PHPSocket.IO](https://github.com/walkor/phpsocket.io)
[php-socks5](https://github.com/walkor/php-socks5)
[php-http-proxy](https://github.com/walkor/php-http-proxy)
## Donate
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=UQGGS9UB35WWG"><img src="http://donate.workerman.net/img/donate.png"></a>
## LICENSE
Workerman is released under the [MIT license](https://github.com/walkor/workerman/blob/master/MIT-LICENSE.txt).

@ -0,0 +1,327 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman;
use Workerman\Protocols\Http;
use Workerman\Protocols\HttpCache;
/**
* WebServer.
*/
class WebServer extends Worker
{
/**
* Virtual host to path mapping.
*
* @var array ['workerman.net'=>'/home', 'www.workerman.net'=>'home/www']
*/
protected $serverRoot = array();
/**
* Mime mapping.
*
* @var array
*/
protected static $mimeTypeMap = array();
/**
* Used to save user OnWorkerStart callback settings.
*
* @var callback
*/
protected $_onWorkerStart = null;
/**
* Add virtual host.
*
* @param string $domain
* @param string $config
* @return void
*/
public function addRoot($domain, $config)
{
if (is_string($config)) {
$config = array('root' => $config);
}
$this->serverRoot[$domain] = $config;
}
/**
* Construct.
*
* @param string $socket_name
* @param array $context_option
*/
public function __construct($socket_name, $context_option = array())
{
list(, $address) = explode(':', $socket_name, 2);
parent::__construct('http:' . $address, $context_option);
$this->name = 'WebServer';
}
/**
* Run webserver instance.
*
* @see Workerman.Worker::run()
*/
public function run()
{
$this->_onWorkerStart = $this->onWorkerStart;
$this->onWorkerStart = array($this, 'onWorkerStart');
$this->onMessage = array($this, 'onMessage');
parent::run();
}
/**
* Emit when process start.
*
* @throws \Exception
*/
public function onWorkerStart()
{
if (empty($this->serverRoot)) {
Worker::safeEcho(new \Exception('server root not set, please use WebServer::addRoot($domain, $root_path) to set server root path'));
exit(250);
}
// Init mimeMap.
$this->initMimeTypeMap();
// Try to emit onWorkerStart callback.
if ($this->_onWorkerStart) {
try {
call_user_func($this->_onWorkerStart, $this);
} catch (\Exception $e) {
self::log($e);
exit(250);
} catch (\Error $e) {
self::log($e);
exit(250);
}
}
}
/**
* Init mime map.
*
* @return void
*/
public function initMimeTypeMap()
{
$mime_file = Http::getMimeTypesFile();
if (!is_file($mime_file)) {
$this->log("$mime_file mime.type file not fond");
return;
}
$items = file($mime_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if (!is_array($items)) {
$this->log("get $mime_file mime.type content fail");
return;
}
foreach ($items as $content) {
if (preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) {
$mime_type = $match[1];
$workerman_file_extension_var = $match[2];
$workerman_file_extension_array = explode(' ', substr($workerman_file_extension_var, 0, -1));
foreach ($workerman_file_extension_array as $workerman_file_extension) {
self::$mimeTypeMap[$workerman_file_extension] = $mime_type;
}
}
}
}
/**
* Emit when http message coming.
*
* @param Connection\TcpConnection $connection
* @return void
*/
public function onMessage($connection)
{
// REQUEST_URI.
$workerman_url_info = parse_url('http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
if (!$workerman_url_info) {
Http::header('HTTP/1.1 400 Bad Request');
if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") {
$connection->send('<h1>400 Bad Request</h1>');
} else {
$connection->close('<h1>400 Bad Request</h1>');
}
return;
}
$workerman_path = isset($workerman_url_info['path']) ? $workerman_url_info['path'] : '/';
$workerman_path_info = pathinfo($workerman_path);
$workerman_file_extension = isset($workerman_path_info['extension']) ? $workerman_path_info['extension'] : '';
if ($workerman_file_extension === '') {
$workerman_path = ($len = strlen($workerman_path)) && $workerman_path[$len - 1] === '/' ? $workerman_path . 'index.php' : $workerman_path . '/index.php';
$workerman_file_extension = 'php';
}
$workerman_siteConfig = isset($this->serverRoot[$_SERVER['SERVER_NAME']]) ? $this->serverRoot[$_SERVER['SERVER_NAME']] : current($this->serverRoot);
$workerman_root_dir = $workerman_siteConfig['root'];
$workerman_file = "$workerman_root_dir/$workerman_path";
if(isset($workerman_siteConfig['additionHeader'])){
Http::header($workerman_siteConfig['additionHeader']);
}
if ($workerman_file_extension === 'php' && !is_file($workerman_file)) {
$workerman_file = "$workerman_root_dir/index.php";
if (!is_file($workerman_file)) {
$workerman_file = "$workerman_root_dir/index.html";
$workerman_file_extension = 'html';
}
}
// File exsits.
if (is_file($workerman_file)) {
// Security check.
if ((!($workerman_request_realpath = realpath($workerman_file)) || !($workerman_root_dir_realpath = realpath($workerman_root_dir))) || 0 !== strpos($workerman_request_realpath,
$workerman_root_dir_realpath)
) {
Http::header('HTTP/1.1 400 Bad Request');
if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") {
$connection->send('<h1>400 Bad Request</h1>');
} else {
$connection->close('<h1>400 Bad Request</h1>');
}
return;
}
$workerman_file = realpath($workerman_file);
// Request php file.
if ($workerman_file_extension === 'php') {
$workerman_cwd = getcwd();
chdir($workerman_root_dir);
ini_set('display_errors', 'off');
ob_start();
// Try to include php file.
try {
// $_SERVER.
$_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp();
$_SERVER['REMOTE_PORT'] = $connection->getRemotePort();
include $workerman_file;
} catch (\Exception $e) {
// Jump_exit?
if ($e->getMessage() != 'jump_exit') {
Worker::safeEcho($e);
}
}
$content = ob_get_clean();
ini_set('display_errors', 'on');
if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") {
$connection->send($content);
} else {
$connection->close($content);
}
chdir($workerman_cwd);
return;
}
// Send file to client.
return self::sendFile($connection, $workerman_file);
} else {
// 404
Http::header("HTTP/1.1 404 Not Found");
if(isset($workerman_siteConfig['custom404']) && file_exists($workerman_siteConfig['custom404'])){
$html404 = file_get_contents($workerman_siteConfig['custom404']);
}else{
$html404 = '<html><head><title>404 File not found</title></head><body><center><h3>404 Not Found</h3></center></body></html>';
}
if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") {
$connection->send($html404);
} else {
$connection->close($html404);
}
return;
}
}
public static function sendFile($connection, $file_path)
{
// Check 304.
$info = stat($file_path);
$modified_time = $info ? date('D, d M Y H:i:s', $info['mtime']) . ' ' . date_default_timezone_get() : '';
if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $info) {
// Http 304.
if ($modified_time === $_SERVER['HTTP_IF_MODIFIED_SINCE']) {
// 304
Http::header('HTTP/1.1 304 Not Modified');
// Send nothing but http headers..
if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") {
$connection->send('');
} else {
$connection->close('');
}
return;
}
}
// Http header.
if ($modified_time) {
$modified_time = "Last-Modified: $modified_time\r\n";
}
$file_size = filesize($file_path);
$file_info = pathinfo($file_path);
$extension = isset($file_info['extension']) ? $file_info['extension'] : '';
$file_name = isset($file_info['filename']) ? $file_info['filename'] : '';
$header = "HTTP/1.1 200 OK\r\n";
if (isset(self::$mimeTypeMap[$extension])) {
$header .= "Content-Type: " . self::$mimeTypeMap[$extension] . "\r\n";
} else {
$header .= "Content-Type: application/octet-stream\r\n";
$header .= "Content-Disposition: attachment; filename=\"$file_name\"\r\n";
}
$header .= "Connection: keep-alive\r\n";
$header .= $modified_time;
$header .= "Content-Length: $file_size\r\n\r\n";
$trunk_limit_size = 1024*1024;
if ($file_size < $trunk_limit_size) {
return $connection->send($header.file_get_contents($file_path), true);
}
$connection->send($header, true);
// Read file content from disk piece by piece and send to client.
$connection->fileHandler = fopen($file_path, 'r');
$do_write = function()use($connection)
{
// Send buffer not full.
while(empty($connection->bufferFull))
{
// Read from disk.
$buffer = fread($connection->fileHandler, 8192);
// Read eof.
if($buffer === '' || $buffer === false)
{
return;
}
$connection->send($buffer, true);
}
};
// Send buffer full.
$connection->onBufferFull = function($connection)
{
$connection->bufferFull = true;
};
// Send buffer drain.
$connection->onBufferDrain = function($connection)use($do_write)
{
$connection->bufferFull = false;
$do_write();
};
$do_write();
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,38 @@
{
"name": "workerman/workerman",
"type": "library",
"keywords": [
"event-loop",
"asynchronous"
],
"homepage": "http://www.workerman.net",
"license": "MIT",
"description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.",
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "http://www.workerman.net",
"role": "Developer"
}
],
"support": {
"email": "walkor@workerman.net",
"issues": "https://github.com/walkor/workerman/issues",
"forum": "http://wenda.workerman.net/",
"wiki": "http://doc.workerman.net/",
"source": "https://github.com/walkor/workerman"
},
"require": {
"php": ">=5.3"
},
"suggest": {
"ext-event": "For better performance. "
},
"autoload": {
"psr-4": {
"Workerman\\": "./"
}
},
"minimum-stability": "dev"
}
Loading…
Cancel
Save