diff --git a/superadmin/vendor/wanghua/general-utility-tools-php b/superadmin/vendor/wanghua/general-utility-tools-php deleted file mode 160000 index a64ab59..0000000 --- a/superadmin/vendor/wanghua/general-utility-tools-php +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a64ab59e2d4cbc1bacfb0baf18f8d3d6f21f28ac diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/README.en.md b/superadmin/vendor/wanghua/general-utility-tools-php/README.en.md new file mode 100644 index 0000000..4c9c21a --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/README.en.md @@ -0,0 +1,37 @@ +# general_utility_tools_php + +#### Description +php常用工具箱 +general utility tool for php + +#### Software Architecture +Software architecture description + +#### Installation + +1. xxxx +2. xxxx +3. xxxx + +#### Instructions + +1. xxxx +2. xxxx +3. xxxx + +#### Contribution + +1. Fork the repository +2. Create Feat_xxx branch +3. Commit your code +4. Create Pull Request + + +#### Gitee Feature + +1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md +2. Gitee blog [blog.gitee.com](https://blog.gitee.com) +3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) +4. The most valuable open source project [GVP](https://gitee.com/gvp) +5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) +6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/README.md b/superadmin/vendor/wanghua/general-utility-tools-php/README.md new file mode 100644 index 0000000..f1bee76 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/README.md @@ -0,0 +1,142 @@ +# general_utility_tools_php + +#### 介绍 + +[PHP常用通用工具库] + +一、常用验证类库 +1. 验证手机号是否正确 +2. 验证邮箱 +3. 验证身份证号码 +4. 是否字母或数字 +5. 是否是小数格式 +6. 更多功能请参考代码 + +二、PHP日期处理工具 +1. 日期加上N年、月、日、时、分、秒,得到计算后的时间 +2. 两个日期相减得到天、时、分、秒(没有两个日期相加一说) +3. 更多更完善功能敬请期待...... + +三、更多类库参考本类库目录 +1. 每个目录都有使用说明. + +#### 软件架构 + +每一种工具独立为一个功能类库。 +#### 软件要求 + thinkphp5.0+,PHP7.1+ + + +#### 安装教程 + composer require wanghua/general-utility-tools-php dev-master + +###### 注意:如果总是安装失败,可切换国内或者国外源;如果是找不到版本则加上dev-master尝试 +###### 注意:如果总是安装失败,可卸载此包重新安装,卸载指令:composer remove wanghua/general-utility-tools-php +###### 注意:如果总是不能提交 vendor下面的一个文件夹(或文件),请参考这篇文章解决:https://blog.csdn.net/qq_15941409/article/details/113184021 + +### 一、常用验证类库使用说明 +##### 初始化 + + //静态方法无需实例化直接调用 + +##### 验证参数是否是小数格式 + $str = '1.223'; + var_dump(Validate::is_float_number($str)); + +##### 验证参数是否字母或数字 + $str = 'qwer#199'; + var_dump(Validate::is_letter_or_number($str)); + +###### 注: 更多功能请参考源码 + +### 二、PHP日期处理工具使用说明 + +##### 初始化 + $date = (new Date()); + $date->date_format = 'Y-m-d H:i:s';//设置格式,默认Y-m-d H:i:s + +##### 日期类型参数(第2个参数)可选项: + //分、时、天、周、月、年 + protected $data_type = [ + 'm' =>'minute',//分钟 + 'minute' =>'minute',//分钟 + 'h' =>'hour',//小时 + 'hour' =>'hour',//小时 + 'd' =>'day',//天 + 'day' =>'day',//天 + 'w' =>'week',//周 + 'week' =>'week',//周 + 'M' =>'month',//月 + 'month' =>'month',//月 + 'y' =>'year',//年 + 'year' =>'year',//年 + ]; + + +##### 在当前时间基础上加3天(第三个参数不传默认使用当前时间) + $res = $date->addTime(3, 'd');//支持单词和字母,例如:d表示天,day也表示天 + dump($res); + +##### 在当前时间基础上加1小时(第三个参数不传默认使用当前时间) + $res = $date->addTime(1, 'h');//支持单词和字母,例如:h表示小时,hour也表示小时 + dump($res); + +##### 在指定时间基础上加1小时 + $res = $date->addTime(1, 'h', strtotime('2010-10-01 12:00:10')); + dump($res); + +##### 在指定时间基础上减1个月 (注意:月是大写M字母,分钟是小写m字母) + $res = $date->reduceTime(1, 'M', strtotime('2010-10-01 12:00:10')); + dump($res); + +##### 在指定时间基础上减20分钟 (注意:分钟是小写m字母) + $res = $date->reduceTime(20, 'm', strtotime('2010-10-01 12:00:10')); + dump($res); + +##### 在指定时间基础上减1年 + $res = $date->reduceTime(1, 'y', strtotime('2010-10-01 12:00:10')); + dump($res); + +### 日期时间相减 + + //时间相减返回的时间类型 秒、分、时、天 默认返回秒 + protected $time_type = [ + 's' =>1,//秒 + 'second' =>1,//秒 + 'm' =>60,//分钟 + 'minute' =>60,//分钟 + 'h' =>3600,//小时 + 'hour' =>3600,//小时 + 'd' =>86400,//天 + 'day' =>86400,//天 + ]; + + $start_time = '2010-05-01 12:30:00'; + $end_time = '2010-10-28 12:30:00'; + + //结束时间减去开始时间得到秒数 (返回类型参考上方配置) + $res = $date->timeReduceTime($end_time, $start_time); + dump($res); + + //结束时间减去开始时间得到天数 (返回类型参考上方配置) + $res = $date->timeReduceTime($end_time, $start_time, 'day'); + dump($res); + +###### 注: 更多功能请参考源码 + +#### 参与贡献 + +1. Fork 本仓库 +2. 新建 Feat_xxx 分支 +3. 提交代码 +4. 新建 Pull Request + + +#### 特技 + +1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md +2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) +3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 +4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 +5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) +6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/composer.json b/superadmin/vendor/wanghua/general-utility-tools-php/composer.json new file mode 100644 index 0000000..510c7cb --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/composer.json @@ -0,0 +1,25 @@ +{ + "type": "library", + "name": "wanghua/general-utility-tools-php", + "homepage": "https://gitee.com/drop_drop/general_utility_tools_php.git", + "description": "general utility tools for thinkPHP", + "license": "Apache-2.0", + "version":"1.0.1", + "keywords": ["general-utility-tools","php tool","common tool"], + "authors": [ + { + "name": "wanghua", + "email": "wanghua@qq.com", + "homepage": "https://blog.csdn.net/qq_15941409" + } + ], + "minimum-stability": "stable", + "require": { + "ext-json": "*" + }, + "autoload": { + "psr-4": { + "wanghua\\general_utility_tools_php\\": "src/" + } + } +} diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/Date.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/Date.php new file mode 100644 index 0000000..072cfb9 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/Date.php @@ -0,0 +1,396 @@ +'minute',//分钟 + 'minute' =>'minute',//分钟 + 'h' =>'hour',//小时 + 'hour' =>'hour',//小时 + 'd' =>'day',//天 + 'day' =>'day',//天 + 'w' =>'week',//周 + 'week' =>'week',//周 + 'M' =>'month',//月 + 'month' =>'month',//月 + 'y' =>'year',//年 + 'year' =>'year',//年 + ]; + //秒、分、时、天 + protected $time_type = [ + 's' =>1,//秒 + 'second' =>1,//秒 + 'm' =>60,//分钟 + 'minute' =>60,//分钟 + 'h' =>3600,//小时 + 'hour' =>3600,//小时 + 'd' =>86400,//天 + 'day' =>86400,//天 + ]; + /** + * desc:日期时间加N + * + * 【注意】 + * 请在计算月时使用$date->date_format = 'Y-m'防止跳月份 + * 例如:2020-03-31月基础上加一个月,在默认$date->date_format='Y-m-d H:i:s'时,会跳到2020-05-01 + * + * author:wh + * @param int $times 相加的时间数量 整型 + * @param string $date_type 要相加的时间类型 可选值:m 分钟;h 小时;d 天;w 周;M 月;y 年 + * @param int $default_time 时间戳,默认当前时间 + * @return false|string 返回$this->date_format格式,可根据需要设定格式 + */ + function addTime(int $times, string $date_type, int $default_time=0){ + return date($this->date_format, strtotime("+{$times} {$this->data_type[$date_type]}", $default_time?$default_time:time())); + } + + /** + * desc:日期时间减N + * + * 【注意】 + * 请在计算月时使用$date->date_format = 'Y-m'防止跳月份 + * 例如:2020-03-31月基础上加一个月,在默认$date->date_format='Y-m-d H:i:s'时,会跳到2020-05-01 + * + * author:wh + * @param int $times 减去的时间数量 整型 + * @param string $date_type 要相减的时间类型 可选值:m 分钟;h 小时;d 天;w 周;M 月;y 年 + * @param int $default_time 时间戳,默认当前时间 + * @return false|string 返回$this->date_format格式,可根据需要设定格式 + */ + function reduceTime(int $times, string $date_type, int $default_time=0){ + return date($this->date_format, strtotime("-{$times} {$this->data_type[$date_type]}", $default_time?$default_time:time())); + } + + /** + * desc:日期时间相减,通常结束时间大于开始时间 + * author:wh + * @param string $end_time 结束时间 + * @param string $start_time 开始时间 + * @param string $return_type 日期时间相减后得到的时间类型,可能是小数。可选值:s 秒;m 分钟;h 小时;d;天 + * @return float|int 返回计算后的天、时、分、秒数 + */ + function timeReduceTime(string $end_time, string $start_time, string $return_type='s'){ + return (strtotime($end_time) - strtotime($start_time)) / $this->time_type[$return_type]; + } + + /** + * desc:日期减日期, 返回月数或年数 + * + * author:wh + * @param string $start_time 开始时间 + * @param string $end_time 结束时间 + * @param string $return_type M 返回一共有多少个月,y 返回有多少个年 + * @return float|int + */ + function dateCutDate(string $start_time, string $end_time, string $return_type='M'){ + $e = date_create($end_time); + + $s = date_create($start_time); + + $diff = date_diff($e, $s); + + //计算月份 + if ($diff->y > 0) { + $m = $diff->y * 12 + $diff->m; + }else{ + $m = $diff->m; + } + return $return_type=='M'?$m:$diff->y; + } + + /** + * desc:日期相减得到月数 + * 注意:不是计算的时间戳,而是计算的月份差值 + * author:wh + * @param string $start_time + * @param string $end_time + * @return false|float|int|string + */ + function dateCutMonth(string $start_time, string $end_time){ + $start_y = date('Y', strtotime($start_time)); + $end_y = date('Y', strtotime($end_time)); + $start_m = date('m', strtotime($start_time)); + $end_m = date('m', strtotime($end_time)); + $ym = ($end_y-$start_y) * 12; + return $ym - $start_m + $end_m; + } + /** + * desc:日期相减得到年数 + * 注意:不是计算的时间戳,而是计算的年份差值 + * author:wh + * @param string $start_time + * @param string $end_time + * @return false|float|int|string + */ + function dateCutYear(string $start_time, string $end_time){ + $start_y = date('Y', strtotime($start_time)); + $end_y = date('Y', strtotime($end_time)); + return ($end_y-$start_y) * 12; + } + /** + * desc:返回年中第几天 + * author:wh + * @param string $month 指定月份 + * @param string $day 指定日 + * @param string $year 指定年份 + * @return false|string + */ + function get_day_Year(string $month, string $day, string $year){ + return date('z',mktime(0,0,0,$month,$day,$year)); + } + + /** + * desc:今天的开始时间 + * author:wh + * @return false|string + */ + function beginToday(){ + return date('Y-m-d').' 00:00:00'; + } + /** + * desc:今天的结束时间 + * author:wh + * @return false|string + */ + function endToday(){ + return date('Y-m-d').' 23:59:59'; + } + + /** + * desc:昨天的开始时间 + * author:wh + * @return string + */ + function beginYesterday(){ + return date('Y-m-d', strtotime('-1 day')).' 00:00:00'; + } + + /** + * desc:昨天的结束时间 + * author:wh + * @return string + */ + function endYesterday(){ + return date('Y-m-d', strtotime('-1 day')).' 23:59:59'; + } + + /** + * desc:前天的开始时间 + * author:wh + * @return string + */ + function beginBeforeYesterday(){ + return date('Y-m-d', strtotime('-2 day')).' 00:00:00'; + } + + /** + * desc:前天的结束时间 + * author:wh + * @return string + */ + function endBeforeYesterday(){ + return date('Y-m-d', strtotime('-2 day')).' 23:59:59'; + } + + /** + * 本周的开始日期 + * + * @param bool $His 是否展示时分秒 默认true + * + * @return false|string + */ + function beginWeek($His = true) + { + //此代码会跳周、月、年 + //$timestamp = mktime(0, 0, 0, date('m'), date('d') - date('w') + 1, date('Y')); + //return $His ? date('Y-m-d H:i:s', $timestamp) : date('Y-m-d', $timestamp); + + //改进 + //$first =1 表示每周星期一为开始日期 0表示每周日为开始日期 + $first=1; + $sdefaultDate = date("Y-m-d"); + //获取当前周的第几天 周日是 0 周一到周六是 1 - 6 + $w=date('w',strtotime($sdefaultDate)); + //是否展示时分秒 + $week_start = date('Y-m-d',strtotime("$sdefaultDate -".($w ? $w - $first : 6).' days')); + //是否展示时分秒 + if($His){ + //$week_start + return $week_start.' 00:00:00'; + } + //$week_start + return $week_start; + } + + /** + * 本周的结束日期 + * + * @param bool $His 是否展示时分秒 默认true + * + * @return false|string + */ + function endWeek($His = true) + { + //此代码会跳周、月、年 + //$timestamp = mktime(23, 59, 59, date('m'), date('d') - date('w') + 7, date('Y')); + //return $His ? date('Y-m-d H:i:s', $timestamp) : date('Y-m-d', $timestamp); + + //改进 + //$first =1 表示每周星期一为开始日期 0表示每周日为开始日期 + $first=1; + $sdefaultDate = date("Y-m-d"); + //获取当前周的第几天 周日是 0 周一到周六是 1 - 6 + $w=date('w',strtotime($sdefaultDate)); + //是否展示时分秒 + $week_start = date('Y-m-d',strtotime("$sdefaultDate -".($w ? $w - $first : 6).' days')); + $week_end=date('Y-m-d',strtotime("$week_start +6 days")); + //是否展示时分秒 + return $His?$week_end.' 23:59:59':$week_end; + } + + /** + * desc:上周开始 + * author:wh + * @return false|string + */ + function beginBeforeWeek(){ + $week = $this->beginWeek(); + $this->date_format = 'Y-m-d'; + return $this->reduceTime(7,'d',strtotime($week)) . ' 00:00:00'; + + //此代码会跳周、月、年 + //return date('Y-m-d', strtotime('-1 monday', time())).' 00:00:00';//上周一,无论今天几号,-1 monday为上一个有效周未 + } + + /** + * desc:上周结束 + * author:wh + * @return false|string + */ + function endBeforeWeek(){ + + $week = $this->beginWeek(); + $this->date_format = 'Y-m-d'; + return $this->reduceTime(1,'d',strtotime($week)) . ' 23:59:59'; + + //此代码会跳周、月、年 + //return date('Y-m-d', strtotime('-1 sunday', time())).' 23:59:59'; //上一个有效周日,同样适用于其它星期 + } + + /** + * 本月的开始日期 + * + * @param bool $His 是否展示时分秒 默认true + * + * @return false|string + */ + function beginMonth($His = true) + { + return $His ? date('Y-m-').'01 00:00:00' : date('Y-m-').'01'; + } + /** + * 本月的结束日期 + * + * @param bool $His 是否展示时分秒 默认true + * + * @return false|string + */ + function endMonth($His = true) + { + $timestamp = mktime(23, 59, 59, date('m'), date('t'), date('Y')); + return $His ? date('Y-m-d H:i:s', $timestamp) : date('Y-m-d', $timestamp); + } + + /** + * desc:上月开始时间(上月一日) + * author:wh + * @return false|string + */ + function beginBeforeMonth(){ + return date('Y-m-d', strtotime('-1 month', strtotime(date('Y-m', time()) . '-01 00:00:00'))).' 00:00:00'; //本月一日直接strtotime上减一个月 + } + /** + * desc:上月结束时间(上月最后一日) + * author:wh + * @return false|string + */ + function endBeforeMonth(){ + return date('Y-m-d', strtotime(date('Y-m', time()) . '-01 00:00:00') - 86400).' 23:59:59'; //本月一日减一天即是上月最后一日 + } + + /** + * desc:七天(一周)以内 + * author:wh + * @return false|string + */ + function innerWeekDay(){ + $time = time(); + //当前时间减去30天 + $second = $time - 7 * 86400; + return date('Y-m-d', $second); + } + /** + * desc:一月以内 + * author:wh + * @return false|string + */ + function innerMonth(){ + $time = time(); + //当前时间减去30天 + $second = $time - 30 * 86400; + return date('Y-m-d', $second); + } + /** + * desc:一年以内 + * author:wh + * @return false|string + */ + function innerYear(){ + $time = time(); + //当前时间减去30天 + $second = $time - 365 * 86400; + return date('Y-m-d', $second); + } + + /** + * 几年的开始日期 + * + * @param bool $His 是否展示时分秒 默认true + * + * @return false|string + */ + function beginYear($His = true) + { + $timestamp = date('Y'); + return $His ? $timestamp.'-01-01 00:00:00' : $timestamp.'-01-01'; + } + + /** + * 几年的结束日期 + * + * @param bool $His 是否展示时分秒 默认true + * + * @return false|string + */ + function endYear($His = true) + { + $timestamp = date('Y'); + return $His ? $timestamp.'-12-31 23:59:59' : $timestamp.'-12-31'; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/Mmodel.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/Mmodel.php new file mode 100644 index 0000000..fd27413 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/Mmodel.php @@ -0,0 +1,134 @@ +insertGetId($data); + } + $res = Db::table($table) + ->where($where) + ->find(); + if($res){ + //不修改条件数据 + $data = array_diff_key($data,$where);//从data中删除where数组中的键值对 + + Db::table($table) + ->where($where) + ->data($data) + ->update(); + return $res['id']; + } + return Db::table($table)->insertGetId($data); + } + + + /** + * desc:捕获式函数块 + * + * 无事务处理 + * + * author:wh + * @param $fn + */ + static function catch($fn){ + try{ + //不输出日志,此代码块可能用于内部逻辑 + //Tools::log_to_write_txt(['input'=>input()]); + $res = $fn(); + //Tools::log_to_write_txt(['output'=>$res]); + return $res; + }catch (\Exception $e){ + Tools::error_txt_log($e); + return Tools::set_fail('操作失败.',$e->getMessage()); + } + } + /** + * desc:捕获式函数块 + * + * 无事务处理 + * + * author:wh + * @param $fn + */ + static function catchJson($fn, $is_whrite_log=true){ + try{ + !$is_whrite_log?:Tools::log_to_write_txt(['input'=>input()]); + $res = $fn(); + !$is_whrite_log?:Tools::log_to_write_txt(['output'=>$res]); + return json($res); + }catch (\Exception $e){ + Tools::error_txt_log($e); + return json(Tools::set_fail('操作失败.',$e->getMessage())); + } + } + /** + * desc:捕获式函数块 + * + * 自动事务处理 + * + * author:wh + * @param $fn + */ + static function catchTrans($fn, $is_whrite_log=true){ + Db::startTrans(); + try{ + !$is_whrite_log?:Tools::log_to_write_txt(['input'=>input()]); + $res = $fn(); + !$is_whrite_log?:Tools::log_to_write_txt(['output'=>$res]); + Db::commit(); + return $res; + }catch (\Exception $e){ + Db::rollback(); + Tools::error_txt_log($e); + return Tools::set_fail('操作失败.',$e->getMessage()); + } + } + /** + * desc:捕获式函数块 + * + * 自动事务处理 + * + * author:wh + * @param $fn + */ + static function catchTransJson($fn, $is_whrite_log=true){ + Db::startTrans(); + try{ + !$is_whrite_log?:Tools::log_to_write_txt(['input'=>input()]); + $res = $fn(); + !$is_whrite_log?:Tools::log_to_write_txt(['output'=>$res]); + Db::commit(); + return json($res); + }catch (\Exception $e){ + Db::rollback(); + Tools::error_txt_log($e); + return json(Tools::set_fail('操作失败.',$e->getMessage())); + } + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/PinYin.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/PinYin.php new file mode 100644 index 0000000..91b032a --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/PinYin.php @@ -0,0 +1,94 @@ +where('key',$key); + $obj->data(['val'=>$val]); + return $obj->update(); + } + $obj = Db::table($tabname); + return $obj->where(['key'=>$key]) + ->cache($tabname.'_'.$key,0,$tabname) + ->value('val'); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/Validate.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/Validate.php new file mode 100644 index 0000000..14e9507 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/Validate.php @@ -0,0 +1,360 @@ +2){ + //返回false,表示非严格的2位数金额 + return false; + } + + + return $r; + } + + /** + * desc:是否是小数格式 + * author:wh + * @param string $num + * @return bool + */ + static function is_float_number(string $num){ + return preg_match('/^[0-9]+.[0-9]+$/', $num)?true:false; + } + + /** + * desc:是否是数字 + * author:wh + * @param string $num + * @return bool + */ + static function is_number(string $num){ + return preg_match('/^\d+$/', $num)?true:false; + } + + /** + * desc:是否是字母 + * author: wh + * @param string $str + * @return bool + */ + static function is_letter(string $str){ + return preg_match('/^[a-zA-Z]+$/', $str) ? true : false; + } + + /** + * desc:是否字母或数字 + * author:wh + * @param string $str + * @return bool + */ + static function is_letter_or_number(string $str){ + return preg_match('/^[a-zA-Z|0-9]+$/', $str) ? true : false; + } + + /** + * desc:验证数组是否存在空值 + * 仅针对基本数据类型 + * 仅针对一维数组 + * author:wh + * @param $array + * @return bool + */ + static function check_array_val_empty($array){ + $is_empty = false; + foreach ($array as $value){ + if(($value!==0 && $value !=='0') && empty($value)){ + $is_empty = true; + break; + } + if(is_int($value) && empty(1*$value)){ + $is_empty = true; + break; + } + } + return $is_empty; + } + + /** + * desc:是否是url + * author:wh + * @param $v + * @return bool + */ + static function is_url($v){ + $pattern="#(http|https)://(.*\.)?.*\..*#i"; + return preg_match($pattern,$v)?true:false; + } + + + /** + * [是否全部大写] + * + * @param $str + * @return bool + * @example + * @see + * @link + */ + static function is_upper($str){ + return preg_match('/^[A-Z]+$/', $str)?true:false; + } + + /** + * [是否全部小写] + * + * @param $str + * @return bool + * @example + * @see + * @link + */ + static function is_lower($str){ + return preg_match('/^[a-z]+$/', $str)?true:false; + } + + + /** + * desc:验证是微信内还是微信外 + * + * 是否在微信内,是否在微信中 + * + * author:wh + * @return bool + */ + static function is_weixin(){ + return strpos($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger') !== false; + } + + /** + * desc:是否包含特殊字符 + * author:wh + * @param string $str + * @return bool + */ + static function is_special_character(string $str){ + $res = preg_match ( '/[\Q~!@#$%^&*()+-_=.:?<>\E]/', $str ); + return $res ? true : false; + } + + /** + * desc:验证字符串是否全部是中文 + * + * 返回 true表示全部是中文,false表示部分是中文或没有中文 + * + * author:wh + * @param string $str + * @return bool + */ + static function is_all_chinese(string $str){ + return preg_match("/^[\x{4e00}-\x{9fa5}]+$/u",$str)?true:false; + } + + /** + * desc:验证两个浮点数值是否相等 + * + * 例如: + * $num1 = 0.1; + $num2 = 0.7; + + $res = $num1 + $num2; + + var_dump($res); + + var_dump($res == 0.8);//false 不相等 + + var_dump(intval(strval($res)) == intval(strval(0.8)));//true 相等 + * + * author:wh + * @param $float_num1 + * @param $float_num2 + * @return bool + */ + static function is_equal_num_val($float_num1, $float_num2){ + + return intval(strval($float_num1)) == intval(strval($float_num2)); + } + + /** + * desc:是否包含给定的主域名 + * author:wh + * @param $domain + * @param $main_domain + * @return bool + */ + static function is_main_domain($domain,$main_domain){ + $exp_arr = explode('.',$domain); + $str = $exp_arr[count($exp_arr)-2] .'.'. $exp_arr[count($exp_arr)-1]; + return $str==$main_domain; + } + + /** + * desc:国内国外IP校验,校验IP来源 + * + * author:wh + * @param $ip + * @return bool + */ + static function validate_ip($ip) { + $chinaStart = ip2long('1.0.1.0'); // 中国大陆起始IP + $chinaEnd = ip2long('254.254.254.254'); // 中国大陆结束IP + + $hongKongStart = ip2long('8.36.0.0'); // 香港起始IP + $hongKongEnd = ip2long('8.37.255.255'); // 香港结束IP + + $taiwanStart = ip2long('192.168.0.0'); // 台湾起始IP + $taiwanEnd = ip2long('192.168.255.255'); // 台湾结束IP + + $ipLong = ip2long($ip); + + if ($ipLong >= $chinaStart && $ipLong <= $chinaEnd || + $ipLong >= $hongKongStart && $ipLong <= $hongKongEnd || + $ipLong >= $taiwanStart && $ipLong <= $taiwanEnd) { + return true; // IP属于国内 + } else { + return false; // IP不属于国内 + } + } + + + /** + * 是否移动端访问访问 + * + * @return bool + */ + static function is_mobile_client() + { + // 如果有HTTP_X_WAP_PROFILE则一定是移动设备 + if (isset($_SERVER['HTTP_X_WAP_PROFILE'])) { + return true; + } + +//如果via信息含有wap则一定是移动设备,部分服务商会屏蔽该信息 + if (isset($_SERVER['HTTP_VIA'])) { + //找不到为flase,否则为true + return stristr($_SERVER['HTTP_VIA'], "wap") ? true : false; + } + +//判断手机发送的客户端标志 + if (isset($_SERVER['HTTP_USER_AGENT'])) { + $clientkeywords = [ + 'nokia', 'sony', 'ericsson', 'mot', 'samsung', 'htc', 'sgh', 'lg', 'sharp', + 'sie-', 'philips', 'panasonic', 'alcatel', 'lenovo', 'iphone', 'ipod', 'blackberry', 'meizu', + 'android', 'netfront', 'symbian', 'ucweb', 'windowsce', 'palm', 'operamini', 'operamobi', + 'openwave', 'nexusone', 'cldc', 'midp', 'wap', 'mobile','alipay' + ]; + +// 从HTTP_USER_AGENT中查找手机浏览器的关键字 + if (preg_match("/(".implode('|', $clientkeywords).")/i", strtolower($_SERVER['HTTP_USER_AGENT']))) { + return true; + } + } + +//协议法,因为有可能不准确,放到最后判断 + if (isset($_SERVER['HTTP_ACCEPT'])) { + // 如果只支持wml并且不支持html那一定是移动设备 + // 如果支持wml和html但是wml在html之前则是移动设备 + if ((strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') !== false) && (strpos($_SERVER['HTTP_ACCEPT'], 'text/html') === false || (strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') < strpos($_SERVER['HTTP_ACCEPT'], 'text/html')))) { + return true; + } + } + + return false; + } + + /** + * 判断是否微信内置浏览器访问 + * @return bool + */ + static function is_wx_client() + { + return strpos($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger') !== false; + } + + /** + * 判断是否支付宝内置浏览器访问 + * @return bool + */ + static function is_ali_client() + { + return strpos($_SERVER['HTTP_USER_AGENT'], 'Alipay') !== false; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/algorithm/Algorithm.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/algorithm/Algorithm.php new file mode 100644 index 0000000..493b70a --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/algorithm/Algorithm.php @@ -0,0 +1,121 @@ + $weight) { + $cumulativeWeight += $weight; // 累加权重 + if ($random <= $cumulativeWeight) { + $selected = $index; // 根据权重选择索引 + break; + } + } + //输出选择的索引 + return $selected; + } + + /** + * desc:测试权重随机算法概率 + * author:wh + * @param array $weights 权重数组 eg:[1, 9, 30, 70] + */ + static function testWeightRandom(array $weights){ + echo json_encode($weights)."\n"; + + $trials = 1000; // 试验次数 + $selectionCounts = array_fill(0, count($weights), 0); // 用于存储每个索引的选择次数 + + for ($i = 0; $i < $trials; $i++) { + + $index = self::weightRandom($weights); + $selectionCounts[$index]++; // 记录选择次数 + } + + // 计算每个索引的选择概率 + $probabilities = []; + foreach ($selectionCounts as $index => $count) { + $probabilities[$index] = ($count / $trials) * 100; // 转换为百分比 + } + + // 输出结果 + echo "After {$trials} trials, the probabilities of selecting each index are:\n"; + foreach ($probabilities as $index => $probability) { + echo "Index {$index}: {$probability}%\n"; + } + } + + /** + * desc:洗牌算法 + * + * 对数组进行随机排序,实现洗牌算法 + * author:wh + * @param array $items eg: [1, 2, 3, 4, 5] + * @return array 返回计算后的数组 + */ + static function shuffle(array $items) { + //$items = [1, 2, 3, 4, 5]; + shuffle($items); + return $items; + } + + /** + * 随机密码生成算法 + * + * 作用:创建安全的随机密码,常用于用户注册或密码重置 + * 使用场景:用户账户创建、密码重置链接生成 + */ + static function randomPassword($length = 8) { + $password = ''; + $possibleChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + for ($i = 0; $i < $length; $i++) { + $password .= substr($possibleChars, mt_rand(0, strlen($possibleChars) - 1), 1); + } + return $password; + } + + /** + * 随机颜色生成算法 + * + * 作用:生成随机RGB颜色值,可用于网页设计中动态改变元素颜色 + * 使用场景:动态网页背景、随机颜色显示的元素 + */ + static function randomColor() { + $red = mt_rand(0, 255); + $green = mt_rand(0, 255); + $blue = mt_rand(0, 255); + return [$red, $green, $blue]; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/algorithm/AlgorithmTest.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/algorithm/AlgorithmTest.php new file mode 100644 index 0000000..9c61338 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/algorithm/AlgorithmTest.php @@ -0,0 +1,52 @@ + $count) { + $probabilities[$index] = ($count / $trials) * 100; // 转换为百分比 + } + + // 输出结果 + echo "After {$trials} trials, the probabilities of selecting each index are:\n"; + foreach ($probabilities as $index => $probability) { + echo "Index {$index}: {$probability}%\n"; + } + } + + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/algorithm/README.MD b/superadmin/vendor/wanghua/general-utility-tools-php/src/algorithm/README.MD new file mode 100644 index 0000000..2411941 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/algorithm/README.MD @@ -0,0 +1 @@ +## 常见实用算法 \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/AlibabaAuth.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/AlibabaAuth.php new file mode 100644 index 0000000..67f760a --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/AlibabaAuth.php @@ -0,0 +1,162 @@ +config = $config; + } + + function trusteeshipAuth(){ + //待开发 + } + + /** + * desc:授权分为托管式授权和WEB端授权,这里是WEB端授权(后去code临时凭证) + * + * author:wh + * @param string $appKey app注册时,分配给app的唯一标识 + * @param string $redirect_uri app的入口地址,授权临时令牌会以queryString的形式跟在该url后返回。注意参数中回调地址的域名必须与app注册时填写的回调地址的域名匹配。 + * @param string $site site参数标识当前授权的站点,直接填写1688 + * @param string $state 可选,app自定义参数,回跳到redirect_uri时,会原样返回 + * return 用户输入用户名密码,并确认授权,返回临时授权码code给app,具体返回方式,参照redirect_uri说明 + */ + function webAppAuth($redirect_uri,$site='1688',$state=''){ + $appKey = $this->config['appkey']; + + $url = "https://auth.1688.com/oauth/authorize?client_id={$appKey}&site={$site}&redirect_uri={$redirect_uri}&state={$state}"; + + + //$res = Curl::curl_post($url,[]); + return ""; + } + + /** + * desc:使用code获取令牌【code有效期(2分钟)】 + * + * 注:此接口必须使用POST方法提交;必须使用https + getToken接口参数说明: + a) grant_type为授权类型,使用authorization_code即可 + b) need_refresh_token为是否需要返回refresh_token,如果返回了refresh_token,原来获取的refresh_token也不会失效,除非超过半年有效期 + c) client_id为app唯一标识,即appKey + d) client_secret为app密钥 + e) redirect_uri为app入口地址 + f) code为授权完成后返回的一次性令牌 + g) 调用getToken接口不需要签名 + 注:如果超过code有效期(2分钟)或者已经使用code获取了一次令牌,code都将失效,需要返回第二步重新获取code + * author:wh + */ + function getTokenByCode($code,$redirect_uri){ + $appkey = $this->config['appkey']; + $secret = $this->config['appsecret']; + $url = "https://gw.open.1688.com/openapi/"; + $urlPath = "http/1/system.oauth2/getToken/".$appkey; + $paramstr = "?grant_type=authorization_code&need_refresh_token=true&client_id={$appkey}&client_secret={$secret}&redirect_uri={$redirect_uri}&code={$code}"; + + $url = $url.$urlPath.$paramstr; + return Curl::curl_post($url,[]); + } + + /** + * 生成签名 + * + 1、 构造urlPath。urlPath是url 中的一部分,从协议开始截取,到“?”为止,包含了协议、namespace、apiName和apiVersion,如urlPath=http/1/system/currentTime/1688 + + 2、 拼装参数。首先将所有参数按照key+value拼装得到一个字符串数组(如[b1,a2]),然后对数组进行排序([a2,b1]),最后将排序后的数组合并为字符串(a2b1) + + 3、 合并。把前两步的字符串拼接(http/1/system/currentTime/1688a2b1) + + 4、 执行hmac_sha1算法。 Signature=uppercase (hex (hmac_sha1 (concatString, secretKey)),假设secretKey=test123,那么得到的签名为2F1E96587451DE0E171978F4AAE1A90FF9B2F94B + + a) concatString为合并后的字符串 + + b) secretKey为签名密钥,与urlPath中的appKey(1000000)对应 + + c) hmac_sha1为通用的hmac_sha1算法,各编程语言一般都对应类库 + + d) hex为转为十六进制 + + e) uppercase为转为大写字符 + * author:wh + * @param string $urlPath eg: param2/1/com.alibaba.trade/alibaba.trade.fastCreateOrder/7571561 + * @param array $params 请求参数 + * @return string + */ + public function signature($urlPath,$params) + { + $accessToken = $this->config['accessToken']; + $params['access_token'] = $accessToken; + //$url = 'http://gw.open.1688.com/openapi';//1688开放平台使用gw.open.1688.com域名 + //$appKey = $this->config['appkey']; + $appSecret = $this->config['appsecret']; + + //dump('$urlPath: '.$urlPath); + //$apiInfo = 'param2/1/system/currentTime/' . $appKey;//此处请用具体api进行替换 + //$urlPath = $urlPath . $appKey;//此处请用具体api进行替换 + + //配置参数,请用apiInfo对应的api参数进行替换 + $aliParams = array(); + foreach ($params as $key => $val) { + $aliParams[] = $key . $val; + } + sort($aliParams); + $sign_str = join('', $aliParams); + $sign_str = $urlPath . $sign_str; + $code_sign = strtoupper(bin2hex(hash_hmac("sha1", $sign_str, $appSecret, true))); + + return $code_sign; + } + + /** + * desc:发起请求,自动完成签名并返回结果 + * author:wh + * @param string $urlPath eg: param2/1/com.alibaba.trade/alibaba.trade.fastCreateOrder/23222222 + * @param array $request_data 接口请求数据 + */ + function request($urlPath, $request_data){ + set_time_limit(0); + $request_params_str = ''; + foreach ($request_data as $k=>$v){ + if($request_params_str == ''){ + $request_params_str .= $k.'='.$v; + }else{ + $request_params_str .= '&'.$k.'='.$v; + } + } + $accessToken = $this->config['accessToken']; + //dump($request_params_str); + $_aop_signature = $this->signature($urlPath,$request_data); + //dump($_aop_signature); + //$request_data['_aop_signature'] = $_aop_signature; + + $url = $this->base_url.$urlPath.'?'.$request_params_str.'&access_token='.$accessToken.'&_aop_signature='.$_aop_signature; + //dump($url); + $this->request_body = $url; + $res = Curl::curl_post($url,[]); + return $res; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/BaseStrict.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/BaseStrict.php new file mode 100644 index 0000000..1c921cb --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/BaseStrict.php @@ -0,0 +1,23 @@ +authObj = $authObj; + //初始化并设置当前模块的基础url + $this->authObj->base_url = $this->base_url; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/README.MD b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/README.MD new file mode 100644 index 0000000..d187b94 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/README.MD @@ -0,0 +1,3 @@ +## distributes 分销模块 + +### strict 分销中严选模块(商家自营) \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/Testexample.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/Testexample.php new file mode 100644 index 0000000..6eddd6a --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/Testexample.php @@ -0,0 +1,158 @@ +queryOrderIsNoPassPay(); + } + function getLogisticsTemplate(){ + $config = config('meebo_supply_config'); + $authObj = new AlibabaAuth($config); + $obj = new StrictLogistics($authObj); + return $obj->getLogisticsTemplate(); + } + //物流模板详情 + function getLogisticsTemplateDetail($template_id){ + $config = config('meebo_supply_config'); + $authObj = new AlibabaAuth($config); + $obj = new StrictLogistics($authObj); + return $obj->getLogisticsTemplateDetail($template_id); + } + function getGoodsList($where){ + set_time_limit(0); + $config = config('meebo_supply_config'); + $authObj = new AlibabaAuth($config); + $obj = new StrictGoods($authObj); + return $obj->getGoodsList($where); + } + function getGoodsListsPft($where){ + $config = config('meebo_supply_config'); + $authObj = new AlibabaAuth($config); + $obj = new StrictGoods($authObj); + return $obj->getGoodsListsPft($where); + } + function getGoodsDetail(array $itemIds){ + $config = config('meebo_supply_config'); + $authObj = new AlibabaAuth($config); + $obj = new StrictGoods($authObj); + return $obj->getGoodsDetail($itemIds); + } + function getCategoryByCID($category_id){ + + $config = config('meebo_supply_config'); + $authObj = new AlibabaAuth($config); + $obj = new StrictCategory($authObj); + return $obj->getCategoryByCID($category_id); + } + function testGetRootCategory(){ + $config = config('meebo_supply_config'); + $authObj = new AlibabaAuth($config); + $obj = new StrictCategory($authObj); + return $obj->getRootCategory(); + } + + function testWebAuth($redirect_uri){ + $config = config('meebo_supply_config'); + $res = (new AlibabaAuth($config))->webAppAuth($redirect_uri); + return $res; + } + function getTokenByCode($code,$redirect_uri){ + $config = config('meebo_supply_config'); + $res = (new AlibabaAuth($config))->getTokenByCode($code,$redirect_uri); + return $res; + } + //测试创建订单 + function createOrder(){ + $order_info = Db::table(TabConf::$fa_order) + ->where('orderid','mm70wgex1719586377768') + ->find(); + $address = Db::table(TabConf::$fa_useraddress) + ->where('id',$order_info['useraddress_id']) + ->where('users_id',$order_info['users_id']) + ->find(); + $goods = Db::table(TabConf::$fa_goods) + ->where('id',$order_info['goods_id']) + ->find(); + + $config = config('meebo_supply_config'); + $authObj = new AlibabaAuth($config); + $order = new StrictOrder($authObj); + $order->setAddress($address); + $order->setGoods($goods,$order_info['buy_num']); + return $order->createOrder($order_info); + } + + function previewOrder(){ + $order_info = Db::table(TabConf::$fa_order) + ->where('orderid','mm70wgex1719586377768') + ->find(); + $address = Db::table(TabConf::$fa_useraddress) + ->where('id',$order_info['useraddress_id']) + ->where('users_id',$order_info['users_id']) + ->find(); + $goods = Db::table(TabConf::$fa_goods) + ->where('id',$order_info['goods_id']) + ->find(); + + $config = config('meebo_supply_config'); + $authObj = new AlibabaAuth($config); + $order = new StrictOrder($authObj); + $order->setAddress($address); + $order->setGoods($goods,$order_info['buy_num']); + return $order->previewOrder(); + } + //查询订单可以支持的支付渠道 + function queryOrderPayChannel($order_id){ + $config = config('meebo_supply_config'); + $authObj = new AlibabaAuth($config); + $order = new StrictPay($authObj); + return $order->queryOrderPayChannel($order_id); + } + //组合收银台url获取 + function getPayUrl(array $orderIds){ + $config = config('meebo_supply_config'); + $authObj = new AlibabaAuth($config); + $order = new StrictPay($authObj); + return $order->getPayUrl($orderIds,'PC'); + } + + //获取唤起旺旺聊天的链接 + function getCustomerServiceLink($toOpenUid){ + $config = config('meebo_supply_config'); + $authObj = new AlibabaAuth($config); + $order = new CustomerService($authObj); + return $order->getCustomerServiceLink($toOpenUid); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/alibabanotify/Alibaba.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/alibabanotify/Alibaba.php new file mode 100644 index 0000000..dadda46 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/alibabanotify/Alibaba.php @@ -0,0 +1,127 @@ +$message_data]); + if(empty($message_data['type'])){ + return Tools::set_fail('type为空'); + } + //$key = strtolower(explode('_',$message_data['type'])[0]); + //获取消息类型 + $fn = strtolower($message_data['type']);//类型即为方法名 + //消息类型前缀即为类名 + $class = '\\app\\index\\logic\\alibabanotify\\'.ucfirst(explode('_',$fn)[0]).'Notify'; + //实例化类 + return (new $class())->{$fn}($message_data);//调用方法 + }); + } + + /** + * desc:ali采购获取临时授权code + * + * index/alibaba/webAuthCode + * + * author:wh + * @return string + */ + function webAuthCode(){ + + $Testexample = new Testexample(); + + $res = $Testexample->testWebAuth(request()->domain().'/index/alibaba/webUserAuth'); + return $res; + } + + /** + * desc:网页授权,code换取token + * + * index/alibaba/webUserAuth + * + * author:wh + */ + function webUserAuth(){ + $code = input('code'); + if(empty($code)){ + return $this->error('授权失败'); + } + + $redirect_uri = url('test/test2'); + $Testexample = new Testexample(); + $res = $Testexample->getTokenByCode($code,$redirect_uri); + if($res['code'] != 200){ + return $this->error('授权失败.'.$res['msg']); + } + $data = json_decode($res['data']); + $auth_data = [ + 'access_token'=>$data['access_token'], + 'aliid'=>$data['aliId'], + 'expires_in'=>$data['expires_in'], + 'memberid'=>$data['memberId'], + 'refresh_token'=>$data['refresh_token'], + 'refresh_token_timeout'=>$data['refresh_token_timeout'], + 'resource_owner'=>$data['resource_owner'], + ]; + //写入数据库 + Mmodel::existsUpdateInsert(TabConf::$fa_alibaba_user_auth,['access_token'=>$data['access_token']],$auth_data); + return '授权成功'; + } + + //function getCode(){ + // return Mmodel::catchJson(function (){ + // //保存code + // $code = input('code'); + // + // }); + //} +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/alibabanotify/BaseAlibabaLogic.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/alibabanotify/BaseAlibabaLogic.php new file mode 100644 index 0000000..78b33b8 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/alibabanotify/BaseAlibabaLogic.php @@ -0,0 +1,18 @@ +$message_data]); + + }); + } + /** + * 物流单号修改消息 + */ + function logistics_mail_no_change($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/alibabanotify/OrderNotify.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/alibabanotify/OrderNotify.php new file mode 100644 index 0000000..efb03e6 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/alibabanotify/OrderNotify.php @@ -0,0 +1,137 @@ +$message_data]); + + }); + } + + /** + * desc:1688交易成功(卖家视角) + * author:wh + */ + function order_buyer_view_order_success($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } + /** + * 1688订单批量支付状态同步消息 + */ + function order_batch_pay($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } + /** + * 1688交易付款(买家视角)/1688 transaction payment (buyer view) + */ + function order_buyer_view_order_pay($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } + /** + * 1688卖家关闭订单(买家视角)/seller closing order (buyer view) + */ + function order_buyer_view_order_seller_close($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } + /** + * 1688修改订单价格(买家视角)/order price modification (buyer view) + */ + function order_buyer_view_order_price_modify($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } + /** + * 1688订单发货(买家视角)/1688 order delivery (buyer view) + */ + function order_buyer_view_announce_sendgoods($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } + /** + * 1688订单部分发货(买家视角)/Partial delivery of 1688 order (buyer view) + */ + function order_buyer_view_part_part_sendgoods($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } + /** + * 商家修改订单地址(买家视角) + */ + function order_buyer_view_order_seller_modify_adress($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } + /** + * 1688订单售中退款(买家视角) + */ + function order_buyer_view_order_buyer_refund_in_sales($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } + /** + * 1688订单售后退款(买家视角) + */ + function order_buyer_view_order_refund_after_sales($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } + /** + * 1688买家关闭订单(买家视角)/buyer closing order (buyer view) + */ + function order_buyer_view_order_buyer_close($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } + /** + * 1688订单确认收货(买家视角)/order receipt confirmation (buyer view) + */ + function order_buyer_view_order_comfirm_receivegoods($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/alibabanotify/ProductNotify.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/alibabanotify/ProductNotify.php new file mode 100644 index 0000000..8524216 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/alibabanotify/ProductNotify.php @@ -0,0 +1,89 @@ +$message_data]); + + }); + } + /** + * 1688产品删除(关系用户视角) + */ + function product_relation_view_product_delete($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } + /** + * 1688产品新增或修改(关系用户视角) + */ + function product_relation_view_product_new_or_modify($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } + /** + * 1688产品上架(关系用户视角) + */ + function product_relation_view_product_repost($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } + /** + * 1688商品库存变更消息(关系用户视角) + */ + function product_product_inventory_change($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } + /** + * 精选货源商品下架消息 + */ + function product_pft_offer_quit($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } + /** + * 精选货源商品价格变动消息 + */ + function product_pft_offer_price_modify($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } + /** + * 1688产品下架(关系用户视角) + */ + function product_relation_view_product_expire($message_data){ + return Mmodel::catch(function ()use($message_data){ + Tools::log_to_write_txt([__FUNCTION__.'消息入参:'=>$message_data]); + + }); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/alibabanotify/README.MD b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/alibabanotify/README.MD new file mode 100644 index 0000000..ef4338e --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/alibabanotify/README.MD @@ -0,0 +1,18 @@ +## 阿里巴巴开放平台消息通知模板 + + +### 特别说明 +#### Alibaba.php是控制器类(复制可用),是消息通道httpCallback的回调地址类文件。 +##### eg:http://www.xxx.com/index/alibaba/notify +##### 回调地址配置入口:https://open.1688.com/develop/app/detail?spm=a260s.develop-app-list.0.0.4d875bfaS5YzoD&appkey=7571564&appKey=7571564 +进入页面后找到“日常消息测试”按钮,点击后在弹出的界面中进行配置。 +### 文件说明 +#### 按照消息类型前缀分类(复制可用) +1. 订单相关 OrderNotify.php +2. 产品相关 ProductNotify.php +3. 物流相关 LogisticsNotify.php +4. 售后相关 + +......以此类推,消息类型的小写就是类的方法名。 + +这里只是打个样,这几个模板可以直接复制,补充上自己的业务逻辑即可。 \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/notify/BaseStrictNotify.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/notify/BaseStrictNotify.php new file mode 100644 index 0000000..4b28e83 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/notify/BaseStrictNotify.php @@ -0,0 +1,55 @@ +$message_data]); + if(empty($message_data['type'])){ + return Tools::set_fail('type为空'); + } + //获取消息类型 + $fn = strtolower($message_data['type']);//类型即为方法名 + //消息类型前缀即为类名 + $class = '\\app\\index\\logic\\alibaba\\distributes\\notify\\'.ucfirst(explode('_',$fn)[0]).'Notify'; + $this->object = (new $class());//实例化类 + return $this->object->{$fn}($callback);//调用方法 + }); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/notify/ProductNotify.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/notify/ProductNotify.php new file mode 100644 index 0000000..a2115c2 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/notify/ProductNotify.php @@ -0,0 +1,48 @@ +authObj->config['appkey']; + + return $this->authObj->request($urlPath, ['toOpenUid'=>$toOpenUid]); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/README.MD b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/README.MD new file mode 100644 index 0000000..34ded96 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/README.MD @@ -0,0 +1,4 @@ +## 分销严选模块 +只能使用分销严选方案里的接口 +DOC: +https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.product:alibaba.category.get-1&aopApiCategory=category_new \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/StrictCategory.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/StrictCategory.php new file mode 100644 index 0000000..502717c --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/StrictCategory.php @@ -0,0 +1,63 @@ +authObj->config['appkey']; + + $request_data = []; + $request_data['categoryID'] = '0'; + return $this->authObj->request($urlPath,$request_data); + } + + /** + * desc:根据类目id获取类目信息 + * author:wh + * @param $category_id + * @return mixed + */ + function getCategoryByCID($category_id){ + $urlPath = "param2/1/com.alibaba.product/alibaba.category.get/".$this->authObj->config['appkey']; + $request_data = []; + $request_data['categoryID'] = $category_id; + return $this->authObj->request($urlPath,$request_data); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/StrictGoods.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/StrictGoods.php new file mode 100644 index 0000000..10b262e --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/StrictGoods.php @@ -0,0 +1,164 @@ +authObj->config['appkey']; + + //$request_data = []; + //foreach ($where as $k=>$v){ + // $request_data[$k] = $v; + //} + + return $this->authObj->request($urlPath, $request_data); + } + + + /** + *@deprecated 【官方说是老接口,推荐上面的新接口】 + * desc:[批发团]分销严选商品列表查询(itemId就是offerId) + * + * 精选货源商品列表查询,查询到的商品铺货至下游产生订单后,请采用ttpft的交易flow下单,精选货源商品下单目前仅支持单商品下单。 + * + * https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.fenxiao:alibaba.pifatuan.product.list-1 + * + * author:wh + * @param array $request_data 查询条件,根据接口文档传值 + * @return mixed + */ + function getGoodsListsPft($request_data = []){ + //$base_url = "https://gw.open.1688.com/openapi/"; + $urlPath = "param2/1/com.alibaba.fenxiao/alibaba.pifatuan.product.list/".$this->authObj->config['appkey']; + + //$request_data = []; + //foreach ($where as $k=>$v){ + // $request_data[$k] = $v; + //} + + return $this->authObj->request($urlPath, $request_data); + } + + /** + * 商品sku信息查询(itemId就是offerId) + * + * sku信息查询,用于页面选品后,查询商品的sku,采用接口下单 + * doc: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.product:product.skuinfo.get-1 + */ + function getGoodsSkuInfo($offerId){ + $urlPath = "param2/1/com.alibaba.product/product.skuinfo.get/".$this->authObj->config['appkey']; + + $request_data = ['offerId'=>$offerId]; + + return $this->authObj->request($urlPath, $request_data); + } + + /** + * desc:[推荐]分销严选商品详情批量查询 + * + * @param string $itemId 商品列表返回的itemId就是offerId + * + * doc:https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.fenxiao:alibaba.pifatuan.product.detail.list-2 + * + */ + function getGoodsDetail(array $itemIds){ + + $urlPath = "param2/2/com.alibaba.fenxiao/alibaba.pifatuan.product.detail.list/".$this->authObj->config['appkey']; + + //精选货源offerIds 示例值[682162000966] + $request_data = ['offerIds'=>json_encode($itemIds)]; + + return $this->authObj->request($urlPath, $request_data); + } + + /** + * desc:[不推荐]分销严选商品详情批量查询 + * + * @param string $itemId 商品列表返回的itemId就是offerId + * + * doc:https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.fenxiao:alibaba.pifatuan.product.detail.list-1 + * + */ + function getGoodsDetailV1(array $itemIds){ + + $urlPath = "param2/1/com.alibaba.fenxiao/alibaba.pifatuan.product.detail.list/".$this->authObj->config['appkey']; + + //精选货源offerIds 示例值[682162000966] + $request_data = ['offerIds'=>json_encode($itemIds)]; + + return $this->authObj->request($urlPath, $request_data); + } + + /** + * desc:关注商品 + * author:wh + * @param string $productId 商品id 3412111233445 + * + * { + "code": 0, + "message": "success" + } + * doc: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.product:alibaba.product.follow-1 + */ + function followGoods($productId){ + $urlPath = "param2/1/com.alibaba.fenxiao/alibaba.product.follow/".$this->authObj->config['appkey']; + + return $this->authObj->request($urlPath, ['productId'=>$productId]); + } + + /** + * 精选货源商品列表筛选条件枚举值查询 + * + * doc: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.fenxiao:jxhy.productFilter.get-1 + */ + function getGoodsFilter(){ + $urlPath = "param2/1/com.alibaba.fenxiao/jxhy.productFilter.get/".$this->authObj->config['appkey']; + + return $this->authObj->request($urlPath, []); + } + + /** + * 解除关注 + * + * { + "code": 0, + "message": "success" + } + * doc: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.product:alibaba.product.unfollow.crossborder-1 + */ + function unfollowGoods($productId){ + $urlPath = "param2/1/com.alibaba.product/alibaba.product.unfollow.crossborder/".$this->authObj->config['appkey']; + return $this->authObj->request($urlPath, ['productId'=>$productId]); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/StrictLogistics.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/StrictLogistics.php new file mode 100644 index 0000000..7e1f145 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/StrictLogistics.php @@ -0,0 +1,52 @@ +authObj->config['appkey']; + + return $this->authObj->request($urlPath, ['templateId'=>$template_id]); + } + function getLogisticsTemplate(){ + $urlPath = "param2/1/com.alibaba.logistics/alibaba.logistics.myFreightTemplate.list.get/".$this->authObj->config['appkey']; + + return $this->authObj->request($urlPath, []); + } + + /** + * desc: 获取物流公司列表 + * + * 物流公司列表-自联物流 + * + * doc:https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.logistics:alibaba.logistics.OpQueryLogisticCompanyList.offline-1&aopApiCategory=Logistics_NEW + * author:wh + */ + function getLogisticsCompanyList(){ + $urlPath = "param2/1/com.alibaba.logistics/alibaba.logistics.OpQueryLogisticCompanyList.offline/".$this->authObj->config['appkey']; + + return $this->authObj->request($urlPath, []); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/StrictOrder.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/StrictOrder.php new file mode 100644 index 0000000..96c2afd --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/StrictOrder.php @@ -0,0 +1,437 @@ +address = [ + //'addressId'=>$address['id'], + 'address'=>$address['address'],//街道地 网商路699号 + 'phone'=>$address['mobile'],//电话 0517-88990077 + 'mobile'=>$address['mobile'],//手机 + 'fullName'=>$address['name'], + 'postCode'=>'000000',//邮编 000000 + 'areaText'=>$address['area'],//区文本 + 'townText'=>$address['town'],//镇文本 + 'cityText'=>$address['city'],//市文本 + 'provinceText'=>$address['provice'],//省份文本 + //'districtCode'=>'000000',//地址编码 310107 + ]; + + } + function setGoods($goods,$buy_num){ + $this->goods = [ + 'offerId'=>$goods['offerid'], + 'quantity'=>$buy_num,//$goods['quantity'],//商品数量(计算金额用) + ]; + if(!empty($goods['specid'])){ + $this->goods['specId'] = $goods['specid'];//商品规格id + } + } + + /** + * desc:设置发票信息 + * author:wh + */ + function setInvoice(array $invoice){ + $this->invoice = $invoice; + } + + /** + * desc:创建分销订单 下单 + * + * 获取订单运费,请调用预览订单接口 alibaba.createOrder.preview + * + * 【优先推荐】 + * 优先推荐(适用于大部分场景):如果渠道服务的用户在采购过程中,价格是核心决策点,推荐直接使用接口中的"最优价下单“能力,即在订单预览、下单过程中选择flow为空,1688会在所有买家可下单的流程中选择最优价进行下单; + * 如果渠道服务的对象以代发群体为主(例如淘卖),需要依赖包邮服务,在分销严选商品下单时,fow选择【boutiquefenxiao】下单,这样即使在采购多件时依然能够保证包邮、48小时发货、7天无理由以及极速退款服务; + * 如果渠道服务对象以批发群体为主(例如跨境),对干是否包,邮的服务不是特别敏感,在分销严选商品下单时,fow选择[boutiquepifa】进行下单,使用该接口时,如果只单笔订单购买一件,依然以包邮价进行下单,在单笔多件时会自动切换为批发价+邮费的模式下单, + * + * @param $order + * @param string $flow 下单时必填 boutiquefenxiao(新分销严选包邮) boutiquepifa(分销批发) + * author:wh + */ + function createOrder($order,$flow){ + if(empty($this->authObj)){ + throw new Exception('未初始化授权对象'); + } + if(empty($this->address)){ + throw new Exception('未设置收货地址'); + } + if(empty($this->goods)){ + throw new Exception('未设置商品信息'); + } + //$base_url = "https://gw.open.1688.com/openapi/"; + $urlPath = "param2/1/com.alibaba.trade/alibaba.trade.fastCreateOrder/".$this->authObj->config['appkey']; + + if(empty($this->tradeType)){ + throw new Exception('交易类型必须'); + } + //由于不同的商品支持的交易方式不同,没有一种交易方式是全局通用的, + //所以当前下单可使用的交易方式必须通过下单预览接口的tradeModeNameList获取。 + $request_data = ['tradeType'=>$this->tradeType]; + + //boutiquefenxiao(新分销严选) + $request_data['flow'] = $flow;//'boutiquefenxiao'; + $request_data['isvBizTypeStr'] = 'fenxiaoMedia'; + + $request_data['message'] = $order['remark'];//买家留言 + //地址 + $request_data['addressParam'] = json_encode($this->address,JSON_UNESCAPED_UNICODE); + //商品信息 + $request_data['cargoParamList'] = json_encode($this->goods,JSON_UNESCAPED_UNICODE); + //发票信息 + $request_data['invoiceParam'] = json_encode($this->invoice,JSON_UNESCAPED_UNICODE); + + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + + /** + * 创建订单前预览数据接口 + * + * 【下单的时候flow是必填的。只有在预览的时候不填写flow会返回最优的下单方式】 + * 【调用预览接口,这个是最准确的。】 + * + * 订单创建只允许购买同一个供应商的商品。 + * 本接口返回创建订单相关的优惠等信息。 + * 1、校验商品数据是否允许订购。 2、校验代销关系 3、校验库存、起批量、是否满足混批条件 + * + * doc: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.createOrder.preview-1&aopApiCategory=trade_new + */ + function previewOrder($flow=''){ + $urlPath = "param2/1/com.alibaba.trade/alibaba.createOrder.preview/".$this->authObj->config['appkey']; + $request_data = []; + //boutiquefenxiao(新分销严选) + $request_data['flow'] = $flow;//'boutiquefenxiao'; + //$request_data['isvBizTypeStr'] = 'fenxiaoMedia'; + + //$request_data['message'] = $order['remark'];//买家留言 + //地址 + $request_data['addressParam'] = json_encode($this->address,JSON_UNESCAPED_UNICODE); + //商品信息 + $request_data['cargoParamList'] = json_encode($this->goods,JSON_UNESCAPED_UNICODE); + //发票信息 + $request_data['invoiceParam'] = json_encode($this->invoice,JSON_UNESCAPED_UNICODE); + + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + + /** + * 取消交易 + * + * 买家或者卖家取消交易,注意只有特定状态的交易才能取消,1688可用于取消未付款的订单。 + * + * doc: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.trade.cancel-1&aopApiCategory=trade_new + * + * author:wh + * @param string $order_id 订单号 + * @param string $cancelReason 原因描述;buyerCancel:买家取消订单;sellerGoodsLack:卖家库存不足;other:其它 + * + * 返回:{ + "success": true + } + */ + function cancelOrder($order_id, $cancelReason, $remark=''){ + $urlPath = "param2/1/com.alibaba.trade/alibaba.trade.cancel/".$this->authObj->config['appkey']; + $request_data = []; + $request_data['tradeID'] = $order_id; + $request_data['webSite'] = 1688; + //原因描述;buyerCancel:买家取消订单;sellerGoodsLack:卖家库存不足;other:其它 + $request_data['cancelReason'] = $cancelReason; + if($remark){ + $request_data['remark'] = $remark; + } + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + + /** + * 修改订单备忘 + * + * 授权用户为卖家修改卖家备忘,授权用户为买家修改买家备忘 注意:该接口可重复调用,备注内容将覆盖前一次调用 + * + * doc: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.order.memoAdd-1&aopApiCategory=trade_new + * + */ + function updateOrderRemark($order_id,$remark){ + $urlPath = "param2/1/com.alibaba.trade/alibaba.order.memoAdd/".$this->authObj->config['appkey']; + $request_data = []; + $request_data['orderId'] = $order_id; + $request_data['memo'] = $remark; + //备忘图标,目前仅支持数字。1位红色图标,2为蓝色图标,3为绿色图标,4为黄色图标 + $request_data['remarkIcon'] = 2; + + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + + /** + * 根据地址解析地区码 + * + * 根据地址信息,解析地区码 + * + * doc:https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.trade.addresscode.parse-1&aopApiCategory=trade_new + * + *返回:{ + "result": { + "address": "网商路699号", + "addressCode": "330108", + "isDefault": false, + "latest": false, + "postCode": "310051" + } + } + */ + function getAreaCode($address){ + $urlPath = "param2/1/com.alibaba.trade/alibaba.trade.addresscode.parse/".$this->authObj->config['appkey']; + $request_data = []; + //地址信息 eg: 浙江省 杭州市 滨江区网商路699号 + $request_data['addressInfo'] = $address; + + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + + /** + * 获取交易地址代码表详情 + * + * 获取交易地址代码表,该API会返回输入code的详情和该code的下一级地区code. + * + * doc: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.trade.addresscode.get-1&aopApiCategory=trade_new + * + * 返回:{ + "result": { + "code": "330108", + "name": "滨江区", + "parentCode": "330100", + "post": "310051", + "children": ["330108001", + "330108002", + "330108003"] + } + } + */ + function getAreaCodeDetail($areaCode){ + $urlPath = "param2/1/com.alibaba.trade/alibaba.trade.addresscode.get/".$this->authObj->config['appkey']; + $request_data = []; + //地址code码 eg: 330108 + $request_data['areaCode'] = $areaCode; + $request_data['webSite'] = 1688; + + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + + /** + * 订单列表查看(买家视角) + * https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.trade.getBuyerOrderList-1&aopApiCategory=trade_new + * + */ + function getOrderList($queryParams=[],$page=1,$pageSize=20){ + $urlPath = "param2/1/com.alibaba.trade/alibaba.trade.getSellerOrderList/".$this->authObj->config['appkey']; + $request_data = []; + $request_data['page'] = $page; + $request_data['pageSize'] = $pageSize; + + //业务类型,支持: "cn"(普通订单类型), "ws"(大额批发订单类型), "yp"(普通拿样订单类型), "yf"(一分钱拿样订单类型), + // "fs"(倒批(限时折扣)订单类型), "cz"(加工定制订单类型), "ag"(协议采购订单类型), "hp"(伙拼订单类型), + // "gc"(国采订单类型), "supply"(供销订单类型), "nyg"(nyg订单类型), "factory"(淘工厂订单类型), + // "quick"(快订下单), "xiangpin"(享拼订单), "nest"(采购商城-鸟巢), "f2f"(当面付), "cyfw"(存样服务), + // "sp"(代销订单标记), "wg"(微供订单), "factorysamp"(淘工厂打样订单), "factorybig"(淘工厂大货订单) + if(isset($queryParams['bizTypes'])){ + $request_data['bizTypes'] = $queryParams['bizTypes']; + } + //下单开始时间 20180802211113000+0800 + if(isset($queryParams['createStartTime'])){ + $request_data['createStartTime'] = $queryParams['createStartTime']; + } + //下单结束时间 20180802211113000+0800 + if(isset($queryParams['createEndTime'])){ + $request_data['createEndTime'] = $queryParams['createEndTime']; + } + //是否查询历史订单表,默认查询当前表,即默认值为false + if(isset($queryParams['isHis'])){ + $request_data['isHis'] = $queryParams['isHis']; + } + //查询修改时间结束 20180802211113000+0800 + if(isset($queryParams['modifyStartTime'])){ + $request_data['modifyStartTime'] = $queryParams['modifyStartTime']; + } + //查询修改时间结束 20180802211113000+0800 + if(isset($queryParams['modifyEndTime'])){ + $request_data['modifyEndTime'] = $queryParams['modifyEndTime']; + } + //订单状态,值有 success, cancel(交易取消,违约金等交割完毕), waitbuyerpay(等待卖家付款), + // waitsellersend(等待卖家发货),waitbuyerreceive(等待买家收货 ) + if(isset($queryParams['orderStatus'])){ + $request_data['orderStatus'] = $queryParams['orderStatus']; + } + //查询分页页码,从1开始 + //if(isset($queryParams['page'])){ + // $request_data['page'] = $queryParams['page']; + //} + ////查询的每页的数量 20 + //if(isset($queryParams['pageSize'])){ + // $request_data['pageSize'] = $queryParams['pageSize']; + //} + //退款状态,支持: "waitselleragree"(等待卖家同意), "refundsuccess"(退款成功), "refundclose"(退款关闭), + // "waitbuyermodify"(待买家修改), "waitbuyersend"(等待买家退货), "waitsellerreceive"(等待卖家确认收货) + if(isset($queryParams['refundStatus'])){ + $request_data['refundStatus'] = $queryParams['refundStatus']; + } + //卖家memberId b2b-1624961198 + if(isset($queryParams['sellerMemberId'])){ + $request_data['sellerMemberId'] = $queryParams['sellerMemberId']; + } + //卖家loginId alitestforisv02 + if(isset($queryParams['sellerLoginId'])){ + $request_data['sellerLoginId'] = $queryParams['sellerLoginId']; + } + //卖家评价状态 (4:已评价,5:未评价,6;不需要评价) + if(isset($queryParams['sellerRateStatus'])){ + $request_data['sellerRateStatus'] = $queryParams['sellerRateStatus']; + } + //交易类型: 担保交易(1), 预存款交易(2), ETC境外收单交易(3), 即时到帐交易(4), 保障金安全交易(5), 统一交易流程(6), + // 分阶段交易(7), 货到付款交易(8), 信用凭证支付交易(9), 账期支付交易(10), 1688交易4.0,新分阶段交易(50060), + // 当面付的交易流程(50070), 服务类的交易流程(50080) + if(isset($queryParams['tradeType'])){ + $request_data['tradeType'] = $queryParams['tradeType']; + } + //商品名称 + if(isset($queryParams['productName'])){ + $request_data['productName'] = $queryParams['productName']; + } + //是否需要查询买家的详细地址信息和电话 false + if(isset($queryParams['needBuyerAddressAndPhone'])){ + $request_data['needBuyerAddressAndPhone'] = $queryParams['needBuyerAddressAndPhone']; + } + //是否需要查询备注信息 false + if(isset($queryParams['needMemoInfo'])){ + $request_data['needMemoInfo'] = $queryParams['needMemoInfo']; + } + //外部订单号,可用于控制幂等 90187872898371 + if(isset($queryParams['outOrderId'])){ + $request_data['outOrderId'] = $queryParams['outOrderId']; + } + + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + /** + * 订单详情查看(买家视角) + * https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.trade.get.buyerView-1&aopApiCategory=trade_new + */ + function getBuyerView(int $orderId,$webSite='1688',$includeFields='',$attributeKeys='',$outOrderId=''){ + $urlPath = "param2/1/com.alibaba.trade/alibaba.trade.get.buyerView/".$this->authObj->config['appkey']; + $request_data = []; + $request_data['orderId'] = $orderId;//交易的订单id 1234345 + $request_data['webSite'] = $webSite;//站点信息,指定调用的API是属于国际站(alibaba)还是1688网站(1688) + $request_data['includeFields'] = $includeFields;//查询结果中包含的域,GuaranteesTerms:保障条款,NativeLogistics:物流信息,RateDetail:评价详情,OrderInvoice:发票信息。默认返回GuaranteesTerms、NativeLogistics、OrderInvoice。 + $request_data['attributeKeys'] = $attributeKeys;//垂直表中的attributeKeys + $request_data['outOrderId'] = $outOrderId;//外部订单id,控制幂等 + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + + /** + * 获取交易订单的物流信息(买家视角) + * + * 该接口需要获得订单买家的授权,获取买家的订单的物流详情,在采购或者分销场景中,作为买家也有获取物流详情的需求。 + * 该接口能查能根据订单号查看物流详情,包括发件人,收件人,所发货物明细等。由于物流单录入的原因, + * 可能跟踪信息的API查询会有延迟。该API需要向开放平台申请权限才能访问。 + * + * https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.logistics:alibaba.trade.getLogisticsInfos.buyerView-1&aopApiCategory=Logistics_NEW + */ + function getLogisticsInfosBuyerView(int $orderId,$webSite='1688',$fields=''){ + $urlPath = "param2/1/com.alibaba.logistics/alibaba.trade.getLogisticsInfos.buyerView/".$this->authObj->config['appkey']; + $request_data = []; + $request_data['orderId'] = $orderId;//交易的订单id 1234345 + $request_data['webSite'] = $webSite;//站点信息,指定调用的API是属于国际站是1688业务还是icbu业务 (1688或者alibaba) + $request_data['fields'] = $fields;//需要返回的字段,目前有:company.name,sender,receiver,sendgood。返回的字段要用英文逗号分隔开 + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + /** + * 获取交易订单的物流跟踪信息(买家视角) + * + * 该接口需要获取订单买家的授权,获取买家的订单的物流跟踪信息,在采购或者分销场景中,作为买家也有获取物流详情的需求。 + * 该接口能查能根据物流单号查看物流单跟踪信息。由于物流单录入的原因,可能跟踪信息的API查询会有延迟。 + * 该API需要向开放平台申请权限才能访问。 + * + * https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.logistics:alibaba.trade.getLogisticsTraceInfo.buyerView-1&aopApiCategory=Logistics_NEW + */ + function getLogisticsTraceInfoBuyerView(int $orderId,$webSite='1688',$logisticsId=''){ + $urlPath = "param2/1/com.alibaba.logistics/alibaba.trade.getLogisticsTraceInfo.buyerView/".$this->authObj->config['appkey']; + $request_data = []; + $request_data['orderId'] = $orderId;//交易的订单id 1234345 + $request_data['webSite'] = $webSite;//站点信息,指定调用的API是属于国际站是1688业务还是ic + $request_data['logisticsId'] = $logisticsId;//该订单下的物流编号 AL8234243 + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + + /** + * 买家确认收货 + * + * doc:https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:trade.receivegoods.confirm-1 + */ + function receiveGoodsConfirm(int $orderId,$orderEntryIds=[]){ + $urlPath = "param2/1/com.alibaba.trade/trade.receivegoods.confirm/".$this->authObj->config['appkey']; + $request_data = []; + $request_data['orderId'] = $orderId;//交易的订单id 1234345 + if($orderEntryIds){ + $request_data['orderEntryIds'] = json_encode($orderEntryIds,JSON_UNESCAPED_UNICODE); + } + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/StrictPay.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/StrictPay.php new file mode 100644 index 0000000..69f6742 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/StrictPay.php @@ -0,0 +1,63 @@ +authObj->config['appkey']; + $request_data = []; + + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + + + /** + * desc:查询订单可以支持的支付渠道 + * author:wh + * @param string $order_id + */ + function queryOrderPayChannel(string $order_id){ + $urlPath = "param2/1/com.alibaba.trade/alibaba.trade.payWay.query/".$this->authObj->config['appkey']; + $request_data = []; + $request_data['orderId'] = $order_id; + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + + /** + * desc:组合收银台url获取 + * author:wh + * @param string $orderIds 订单列表 格式参考 字符串格式的数组:["123456789","123456789"] + * @param string $payPlatformType PC或WIRELESS + * @return mixed + */ + function getPayUrl(string $orderIds,string $payPlatformType){ + $urlPath = "param2/1/com.alibaba.trade/alibaba.trade.grouppay.url.get/".$this->authObj->config['appkey']; + $request_data = []; + $request_data['orderIds'] = $orderIds;//json_encode($orderIds); + $request_data['payPlatformType'] = $payPlatformType;//PC或WIRELESS + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/StrictRefund.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/StrictRefund.php new file mode 100644 index 0000000..05d847b --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alibaba/distributes/strict/StrictRefund.php @@ -0,0 +1,257 @@ +authObj->config['appkey']; + $request_data = []; + + //主订单 + if(empty($params['orderId'])){ + return Tools::set_fail('orderId不能为空'); + } + $request_data['orderId'] = $params['orderId']; + //子订单 订单接口 productItems- subItemID 就是子订单ID, + // 卖家拒绝退款后无法重新申请,需要到1688后台退款单页面 修改退款协议 + if(empty($params['orderEntryIds'])){ + return Tools::set_fail('orderEntryIds不能为空'); + } + $request_data['orderEntryIds'] = $params['orderEntryIds']; + //退款/退款退货。只有已收到货,才可以选择退款退货 退款:"refund"; 退款退货:"returnRefund" + if(empty($params['disputeRequest'])){ + return Tools::set_fail('disputeRequest不能为空'); + } + $request_data['disputeRequest'] = $params['disputeRequest']; + //退款金额(单位:分)。不大于实际付款金额;等待卖家发货时,必须为商品的实际付款金额。 + if(empty($params['applyPayment'])){ + return Tools::set_fail('applyPayment不能为空'); + } + $request_data['applyPayment'] = $params['applyPayment']; + //退运费金额(单位:分)。 + if(!isset($params['applyCarriage'])){ + return Tools::set_fail('applyCarriage不能为空'); + } + $request_data['applyCarriage'] = $params['applyCarriage']; + //退款原因id(从API getRefundReasonList获取) + if(empty($params['applyReasonId'])){ + return Tools::set_fail('applyReasonId不能为空'); + } + $request_data['applyReasonId'] = $params['applyReasonId']; + //退款申请理由,2-150字 + if(empty($params['description'])){ + return Tools::set_fail('description不能为空'); + } + $request_data['description'] = $params['description']; + //货物状态 售中等待卖家发货:"refundWaitSellerSend"; 售中等待买家收货:"refundWaitBuyerReceive"; + // 售中已收货(未确认完成交易):"refundBuyerReceived" 售后未收货:"aftersaleBuyerNotReceived"; + // 售后已收到货:"aftersaleBuyerReceived" + if(empty($params['goodsStatus'])){ + return Tools::set_fail('goodsStatus不能为空'); + } + $request_data['goodsStatus'] = $params['goodsStatus']; + //凭证图片URLs。1-5张,必须使用API uploadRefundVoucher返回的“图片域名/相对路径” + if(isset($params['vouchers'])){ + $request_data['vouchers'] = $params['vouchers']; + } + //子订单退款数量。仅在售中买家已收货(退款退货)时,可指定退货数量;默认,全部退货。[{"id":586683458996743215,"count":1}] + if(isset($params['orderEntryCountList'])){ + $request_data['orderEntryCountList'] = $params['orderEntryCountList']; + } + + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + + /** + * 查询退款退货原因(用于创建退款退货) + * https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.trade.getRefundReasonList-1 + */ + function getRefundReasonList(int $orderId,array $orderEntryIds,$goodsStatus){ + $urlPath = "param2/1/com.alibaba.trade/alibaba.trade.getRefundReasonList/".$this->authObj->config['appkey']; + + $request_data = []; + $request_data['orderId'] = $orderId;//主订单id 1234345 + $request_data['orderEntryIds'] = json_encode($orderEntryIds);//子订单id Long[] + //售中等待买家发货:”refundWaitSellerSend"; 售中等待买家收货:"refundWaitBuyerReceive"; + // 售中已收货(未确认完成交易):"refundBuyerReceived" 售后未收货:"aftersaleBuyerNotReceived"; + // 售后已收到货:"aftersaleBuyerReceived" + $request_data['goodsStatus'] = $goodsStatus;//货物状态 + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + + /** + * @deprecated 【可在前端通过表单提交】 + * + * 上传退款退货凭证 + * + * https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.trade.uploadRefundVoucher-1 + */ + function uploadRefundVoucher($imageData){ + //$urlPath = "param2/1/com.alibaba.trade/alibaba.trade.uploadRefundVoucher/".$this->authObj->config['appkey']; + //$request_data = []; + //$request_data['imageData'] = $imageData;//凭证图片数据。小于1M,jpg格式。 + //$res = $this->authObj->request($urlPath, $request_data); + //return $res; + } + + /** + * 查询退款单列表(买家视角) + * https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.trade.refund.buyer.queryOrderRefundList-1&aopApiCategory=trade_new + * 买家查看退款单列表,该接口不支持子账号查询,请使用主账号授权后查询 + */ + function queryOrderRefundList($params){ + $urlPath = "param2/1/com.alibaba.trade/alibaba.trade.refund.buyer.queryOrderRefundList/".$this->authObj->config['appkey']; + $request_data = []; + //订单Id + if(isset($params['orderId'])){ + $request_data['orderId'] = $params['orderId']; + } + //退款申请时间(起始) 20220926114526000+0800 + if(isset($params['applyStartTime'])){ + $request_data['applyStartTime'] = $params['applyStartTime']; + } + //退款申请时间(截止) 20220926114526000+0800 + if(isset($params['applyEndTime'])){ + $request_data['applyEndTime'] = $params['applyEndTime']; + } + //退款状态列表 等待卖家同意 waitselleragree;退款成功 refundsuccess;退款关闭 refundclose; + //待买家修改 waitbuyermodify;等待买家退货 waitbuyersend;等待卖家确认收货 waitsellerreceive + if(isset($params['refundStatusSet'])){ + $request_data['refundStatusSet'] = $params['refundStatusSet']; + } + //卖家memberId + if(isset($params['sellerMemberId'])){ + $request_data['sellerMemberId'] = $params['sellerMemberId']; + } + //当前页码 + if(isset($params['currentPageNum'])){ + $request_data['currentPageNum'] = $params['currentPageNum']; + } + //每页条数 + if(isset($params['pageSize'])){ + $request_data['pageSize'] = $params['pageSize']; + } + //退货物流单号(传此字段查询时,需同时传入sellerMemberId) + if(isset($params['logisticsNo'])){ + $request_data['logisticsNo'] = $params['logisticsNo']; + if(empty($params['sellerMemberId'])){ + return Tools::set_fail('logisticsNo必须与sellerMemberId同时传入'); + } + } + //退款修改时间(起始) 20220926114526000+0800 + if(isset($params['modifyStartTime'])){ + $request_data['modifyStartTime'] = $params['modifyStartTime']; + } + //退款修改时间(截止) 20220926114526000+0800 + if(isset($params['modifyEndTime'])){ + $request_data['modifyEndTime'] = $params['modifyEndTime']; + } + //1:售中退款,2:售后退款;0:所有退款单 + if(isset($params['dipsuteType'])){ + $request_data['dipsuteType'] = $params['dipsuteType']; + } + + + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + + /** + * 查询退款单详情-根据订单ID(买家视角) + * https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.trade.refund.OpQueryBatchRefundByOrderIdAndStatus-1&aopApiCategory=trade_new + */ + function queryBatchRefundByOrderIdAndStatus(int $orderId,string $queryType){ + $urlPath = "param2/1/com.alibaba.trade/alibaba.trade.refund.OpQueryBatchRefundByOrderIdAndStatus/".$this->authObj->config['appkey']; + $request_data = []; + //订单Id + $request_data['orderId'] = $orderId; + //查询类型 1:活动;3:退款成功(只支持退款中和退款成功) + $request_data['queryType'] = $queryType; + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + + /** + * 退款单操作记录列表(买家视角) + * https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.trade.refund.OpQueryOrderRefundOperationList-1 + */ + function queryOrderRefundOperationList(string $refundId,$pageNo=1,$pageSize=100){ + $urlPath = "param2/1/com.alibaba.trade/alibaba.trade.refund.OpQueryOrderRefundOperationList/".$this->authObj->config['appkey']; + $request_data = []; + $request_data['refundId'] = $refundId; + $request_data['pageNo'] = $pageNo; + $request_data['pageSize'] = $pageSize; + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + /** + * 查询退款单详情-根据退款单ID(买家视角) + * https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.trade.refund.OpQueryOrderRefund-1&aopApiCategory=trade_new + */ + function opQueryOrderRefund(string $refundId,$needTimeOutInfo=false,$needOrderRefundOperation=false){ + $urlPath = "param2/1/com.alibaba.trade/alibaba.trade.refund.OpQueryOrderRefund/".$this->authObj->config['appkey']; + $request_data = []; + $request_data['refundId'] = $refundId; + if(isset($needTimeOutInfo)){ + $request_data['needTimeOutInfo'] = $needTimeOutInfo; + } + if(isset($needOrderRefundOperation)){ + $request_data['needOrderRefundOperation'] = $needOrderRefundOperation; + } + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + /** + * 买家提交退款货信息 + * https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.trade.refund.returnGoods-1 + */ + function buyerSubmitRefundGoodsInfo(string $refundId,string $logisticsCompanyNo,string $freightBill,string $description='',string $vouchers=''){ + $urlPath = "param2/1/com.alibaba.trade/alibaba.trade.refund.returnGoods/".$this->authObj->config['appkey']; + $request_data = []; + $request_data['refundId'] = $refundId; + $request_data['logisticsCompanyNo'] = $logisticsCompanyNo; + $request_data['freightBill'] = $freightBill; + if($description){ + $request_data['description'] = $description;//发货说明,内容在2-200个字之间 + } + //凭证图片URLs,必须使用API alibaba.trade.uploadRefundVoucher返回的“图片域名/相对路径”, + //最多可上传 10 张图片 ;单张大小不超过1M;支持jpg、gif、jpeg、png、和bmp格式。 + // 请上传凭证,以便以后续赔所需(不上传将无法理赔) + if($vouchers){ + $request_data['vouchers'] = $vouchers;//[https://cbu01.alicdn.com/img/ibank/2019/901/930/11848039109.jpg] + } + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } + + + /** + * 取消退款退货申请 + * https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.trade.cancelRefund-1 + */ + function cancelRefund(string $refundId){ + $urlPath = "param2/1/com.alibaba.trade/alibaba.trade.cancelRefund/".$this->authObj->config['appkey']; + $request_data = []; + $request_data['refundId'] = $refundId; + $res = $this->authObj->request($urlPath, $request_data); + return $res; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/AlipayAuthSDK.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/AlipayAuthSDK.php new file mode 100644 index 0000000..8dd8de2 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/AlipayAuthSDK.php @@ -0,0 +1,145 @@ +authExchangeOpenid($auth_code,$grantType); + return $alipay_system_oauth_token_response; + }else{ + return session('session_alipay_system_oauth_token_response'); + } + }else{ + //实时获取 + $alipay_system_oauth_token_response = $this->authExchangeOpenid($auth_code); + return $alipay_system_oauth_token_response; + } + } + // 第一步:构建授权URL (引导用户跳转) + function buildAuthUrl($auth_url) + { + //Tools::log_to_write_txt(['构建授权URL (引导用户跳转),$url'=>$auth_url]); + $redirect_uri = urlencode($auth_url); // 回调地址需在支付宝后台配置 + $auth_url = "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm?" . + "app_id={$this->alipayConfig['app_id']}&" . + "scope=auth_user&" . + "redirect_uri={$redirect_uri}&" . + "state=meebo";//自定义防CSRF参数 + //Tools::log_to_write_txt(['构建授权URL (引导用户跳转),$auth_url:'=>$auth_url]); + return $auth_url; + } + + // 第二步:获取授权码 + function authExchangeOpenid($auth_code,$grantType='authorization_code'){ + $root_path = Tools::get_root_path(); + require_once($root_path.'library/alipay-sdk-php-all/aop/AopCertClient.php'); // 支付宝官方SDK, 需从开放平台下载 + //AlipaySystemOauthTokenRequest + require_once($root_path.'library/alipay-sdk-php-all/aop/request/AlipaySystemOauthTokenRequest.php'); + + /** 初始化 **/ + $aop = new \AopCertClient; + + /** 支付宝网关 **/ + $aop->gatewayUrl = "https://openapi.alipay.com/gateway.do"; + + /** 应用id,如何获取请参考:https://opensupport.alipay.com/support/helpcenter/190/201602493024 **/ + $aop->appId = $this->alipayConfig['app_id']; + + /** 密钥格式为pkcs1,如何获取私钥请参考:https://opensupport.alipay.com/support/helpcenter/207/201602469554 **/ + $aop->rsaPrivateKey = $this->alipayConfig['alipay_app_private_key']; + + /** 应用公钥证书路径,下载后保存位置的绝对路径 **/ + $appCertPath = $root_path.$this->alipayConfig['appCertPublicKey']; + + /** 支付宝公钥证书路径,下载后保存位置的绝对路径 **/ + $alipayCertPath = $root_path.$this->alipayConfig['cert_path_alipayCertPublicKey_RSA2']; + + /** 支付宝公钥证书路径,下载后保存位置的绝对路径 **/ + $rootCertPath = $root_path.$this->alipayConfig['cert_path_alipayRootCert']; + + /** 设置签名类型 **/ + $aop->signType= "RSA2"; + + /** 设置请求格式,固定值json **/ + $aop->format = "json"; + + /** 设置编码格式 **/ + $aop->charset= "utf-8"; + + /** 调用getPublicKey从支付宝公钥证书中提取公钥 **/ + $aop->alipayrsaPublicKey = $aop->getPublicKey($alipayCertPath); + + /** 是否校验自动下载的支付宝公钥证书,如果开启校验要保证支付宝根证书在有效期内 **/ + $aop->isCheckAlipayPublicCert = true; + + /** 调用getCertSN获取证书序列号 **/ + $aop->appCertSN = $aop->getCertSN($appCertPath); + + /** 调用getRootCertSN获取支付宝根证书序列号 **/ + $aop->alipayRootCertSN = $aop->getRootCertSN($rootCertPath); + + /** 实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称alipay.system.oauth.token(换取授权访问令牌) **/ + $request = new \AlipaySystemOauthTokenRequest (); + + /** 值为authorization_code时,代表用code换取;值为refresh_token时,代表用refresh_token换取 **/ + $request->setGrantType($grantType); + + /** 授权码,用户对应用授权后得到,可参考文档:https://opendocs.alipay.com/open/284/web **/ + $request->setCode($auth_code);//"4b203fe6c11548bcabd8da5bb087a83b" + + /** 刷新令牌,上次换取访问令牌时得到。见出参的refresh_token字段 **/ + //$request->setRefreshToken("201208134b203fe6c11548bcabd8da5bb087a83b"); + + + $result = $aop->execute ($request); + + /**第三方调用(服务商模式),传值app_auth_token后,会收款至授权token对应商家账号,如何获传值app_auth_token请参考文档:https://opensupport.alipay.com/support/helpcenter/79/201602494631 **/ + //$result = $aop->execute ($request,null,$app_auth_token); + + $json = json_encode($result,JSON_UNESCAPED_UNICODE); + //dump($json);; + //array(3) { + // ["alipay_system_oauth_token_response"] => array(6) { + // ["access_token"] => string(40) "authusrB61082f01b2064753b039210cec7ddB44" + // ["auth_start"] => string(19) "2025-03-14 14:59:02" + // ["expires_in"] => int(1296000) + // ["re_expires_in"] => int(2592000) + // ["refresh_token"] => string(40) "authusrB85949807a38d4bffbd5c86ae7e797B44" + // ["open_id"] => string(47) "0442qO88pZKcduCIdYq7Z4N8y29dGoocWheNow1jt_QW1k4" + // } + // ["alipay_cert_sn"] => string(32) "9cbb86296783f52af85119eb59308f9a" + // ["sign"] => string(344) "qTFAlIaq3DO3L+9cjoXVJVhSipib8v2ot0Vb1cFEDdtVUH99eZsd33vDBDaKobf00XhVUBqx9WbL67GfEbUF94s6cPfGn9kHuYSMDKh5VRCUFF9TI+melq6GAXih9FbsgqUTY/OuAxz5GDfYL974CppSi7+uBodl0KCmt9kC6VgoC0tUe0s8MDC7LRWBEXheAwrfzMn8NzCpbJRSWRfmewRnBzNXC3SVV6KrMlQPaNcj4Wjl9JQxxCOFXRGqeCMF7pDLYPrChFy7TOCVE/ONUbBQcR1p3qqek3MO/QhpeumTB33VsfKTMQOKjWhEEfSYJa7vGxkQBnZ5QVVngzGVHg==" + //} + $json_arr = json_decode($json,true); + $alipay_system_oauth_token_response = $json_arr['alipay_system_oauth_token_response']; + session('session_alipay_system_oauth_token_response',$alipay_system_oauth_token_response); + //$open_id = $alipay_system_oauth_token_response['open_id']; + return $alipay_system_oauth_token_response; + } + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/AlipayLogic.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/AlipayLogic.php new file mode 100644 index 0000000..4669e48 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/AlipayLogic.php @@ -0,0 +1,82 @@ +setBody($order['goods_name']); + //$payRequestBuilder->setSubject($order['goods_name']); + //$payRequestBuilder->setOutTradeNo($out_trade_no); + //$payRequestBuilder->setTotalAmount($order['goods_price']); + //$payRequestBuilder->setTimeExpress($timeout_express); + // + //$payResponse = new \AlipayTradeService($config); + + + //$result = $payResponse->wapPay($payRequestBuilder,$return_url,$notify_url); + //return $result; + //} +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/BaseAlipay.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/BaseAlipay.php new file mode 100644 index 0000000..049547a --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/BaseAlipay.php @@ -0,0 +1,172 @@ +alipayConfig = $alipayConfig; + } + + /** + * 查询支付宝账户余额 + */ + function queryBalance(){ + //$transferConfig = new TransferConfig(); + //$transferConfig->cert_path_alipayCertPublicKey_RSA2 = Tools::get_root_path().$alipayConfig['cert_path_alipayCertPublicKey_RSA2'];//'<-- 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt -->'; + //$transferConfig->cert_path_alipayRootCert = Tools::get_root_path().$alipayConfig['cert_path_alipayRootCert'];//'<-- 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt" -->'; + //$transferConfig->appCertPublicKey = Tools::get_root_path().$alipayConfig['appCertPublicKey'];//'<-- 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt -->' + + //$trans_obj = new AlipayTransfer(); + Factory::setOptions($this->getAlipayOptions()); + + $method = 'alipay.fund.account.query'; + //公共参数 + $textParams = [ + 'app_id'=>$this->alipayConfig['app_id'], + 'charset'=>'utf-8', + 'sign_type'=>'RSA2', + 'timestamp'=>Tools::get_now_date(), + 'version'=>'1.0', + ]; + + //请求参数 + //订单号前缀 + $biz_content = [ + 'account_type'=>'ACCTRANS_ACCOUNT', + ]; + $pay_res = Factory::util()->generic()->execute($method,$textParams,$biz_content); + + return $pay_res; + } + + /** + * desc:证书模式参数(非证书模式参数需单独获取) + * author:wh + * @param array $this->alipayConfig 支付宝转账配置(非支付配置) + * @return Config + */ + function getAlipayOptions($notifyUrl='') + { + $root_path = Tools::get_root_path(); + $options = new Config(); + $options->protocol = 'https'; + $options->gatewayHost = 'openapi.alipay.com'; + $options->signType = 'RSA2'; + + //'<-- 请填写您的AppId,例如:2019022663440152 -->' + $options->appId = $this->alipayConfig['app_id'];//'2021002103639985'; + + // 为避免私钥随源码泄露,推荐从文件中读取私钥字符串而不是写入源码中 + $options->merchantPrivateKey = $this->alipayConfig['alipay_app_private_key'];//'<-- 请填写您的应用私钥,例如:MIIEvQIBADANB ... ... -->'; + + $options->alipayCertPath = $root_path.$this->alipayConfig['cert_path_alipayCertPublicKey_RSA2'];//'<-- 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt -->'; + $options->alipayRootCertPath = $root_path.$this->alipayConfig['cert_path_alipayRootCert'];//'<-- 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt" -->'; + $options->merchantCertPath = $root_path.$this->alipayConfig['appCertPublicKey'];//'<-- 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt -->'; + + //注:如果采用非证书模式,则无需赋值上面的三个证书路径,改为赋值如下的支付宝公钥字符串即可 + // $options->alipayPublicKey = $this->alipayConfig['alipay_public_key'];//'<-- 请填写您的支付宝公钥,例如:MIIBIjANBg... -->'; + + //可设置异步通知接收服务地址(可选) + $options->notifyUrl = $notifyUrl;//"<-- 请填写您的支付类接口异步通知接收服务地址,例如:https://www.test.com/callback -->"; + + //可设置AES密钥,调用AES加解密相关接口时需要(可选) + //$options->encryptKey = "<-- 请填写您的AES密钥,例如:aa4BtZ4tspm2wnXLb1ThQA== -->"; + + return $options; + } + + /** + * 支付宝退款 + * + * 注意:需要在正式服测,本地会报错 + * + * doc: + * https://opendocs.alipay.com/open/3aea9b48_alipay.trade.refund?pathHash=4de421df&scene=common + * + * 测试结果: + * object(Alipay\EasySDK\Util\Generic\Models\AlipayOpenApiGenericResponse)#93 (7) { + ["_name":protected] => array(5) { + ["httpBody"] => string(9) "http_body" + ["code"] => string(4) "code" + ["msg"] => string(3) "msg" + ["subCode"] => string(8) "sub_code" + ["subMsg"] => string(7) "sub_msg" + } + ["httpBody"] => string(826) "{"alipay_trade_refund_response":{"code":"10000","msg":"Success","buyer_logon_id":"150******25","fund_change":"Y","gmt_refund_pay":"2025-02-24 10:06:26","out_trade_no":"h5cqa31h1740360008389","refund_detail_item_list":[{"amount":"0.11","fund_channel":"ALIPAYACCOUNT"}],"refund_fee":"0.11","send_back_fee":"0.11","trade_no":"2025022422001457441416271979","buyer_open_id":"0442qO88pZKcduCIdYq7Z4N8y29dGoocWheNow1jt_QW1k4"},"alipay_cert_sn":"9cbb86296783f52af85119eb59308f9a","sign":"fcmu9CwYw0uziZTNImfGJvaYE9EdO4OoeKjktl/jxn+EGKvybDijKxTsn+jRM99jm9gcvmQmqweTd6OJMkep6cut1Tl5jsktYGy+ri1lLTGc9eNg+ctyO6qylnVaKQstV0QNZkuRo6Y36W3ZXolZk81ppNM1chtupsALHfwoWSirupqJ6Y52iBRG67kN0AxexL8mbOhwGS9vAMbMXoyRKJ7ajLGSmpnhb1sMvHVhFkvXaCBrzn6Nt1TMw6cyelqNHsAsmJ+OSnndVcRaqt9Q+IAwn98vhBhl1J5Vi7cWbsIYeki+TViQZ2ArW09IdBrP+A9qIydeM0BQdElRFWntvg=="}" + ["code"] => string(5) "10000" + ["msg"] => string(7) "Success" + ["subCode"] => NULL + ["subMsg"] => NULL + ["_required":protected] => array(0) { + } + } + */ + function aliRefund($order_info){ + $alipayConfig = $this->alipayConfig; + //$transferConfig = new TransferConfig(); + //$transferConfig->cert_path_alipayCertPublicKey_RSA2 = Tools::get_root_path().$alipayConfig['cert_path_alipayCertPublicKey_RSA2'];//'<-- 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt -->'; + //$transferConfig->cert_path_alipayRootCert = Tools::get_root_path().$alipayConfig['cert_path_alipayRootCert'];//'<-- 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt" -->'; + //$transferConfig->appCertPublicKey = Tools::get_root_path().$alipayConfig['appCertPublicKey'];//'<-- 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt -->' + + + Factory::setOptions($this->getAlipayOptions()); + + $method = 'alipay.trade.refund'; + //公共参数 + $textParams = [ + 'app_id'=>$alipayConfig['app_id'], + 'charset'=>'utf-8', + 'sign_type'=>'RSA2', + 'timestamp'=>Tools::get_now_date(), + 'version'=>'1.0', + ]; + + //请求参数 wap/pc/app支付 + + //【描述】退分账明细信息。 + // 注: 1.当面付且非直付通模式无需传入退分账明细,系统自动按退款金额与订单金额的比率, + //从收款方和分账收入方退款,不支持指定退款金额与退款方。 + // 2.直付通模式,电脑网站支付,手机 APP 支付,手机网站支付产品,须在退款请求中明确是否退分账, + //从哪个分账收入方退,退多少分账金额; + //如不明确,默认从收款方退款,收款方余额不足退款失败。不支持系统按比率退款。 + $refund_royalty_parameters = [];//如果有分账,此项必填 + //订单号前缀 + $biz_content = [ + //'account_type'=>'ACCTRANS_ACCOUNT', + 'out_trade_no'=>$order_info['orderid'], + 'refund_amount'=>$order_info['real_amount'], + ]; + if($refund_royalty_parameters){ + $biz_content['refund_royalty_parameters'] = $refund_royalty_parameters; + } + $pay_res = Factory::util()->generic()->execute($method,$textParams,$biz_content); + + return $pay_res; + + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/alipay-sdk-php-all.zip b/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/alipay-sdk-php-all.zip new file mode 100644 index 0000000..211bdcb Binary files /dev/null and b/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/alipay-sdk-php-all.zip differ diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/pay/AlipayAppPay.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/pay/AlipayAppPay.php new file mode 100644 index 0000000..e39bfc5 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/pay/AlipayAppPay.php @@ -0,0 +1,140 @@ +getAlipayConfig($this->pay_config)); + // 构造请求参数以调用接口 + $request = new \AlipayTradeAppPayRequest(); + $model = array(); + // 设置商户订单号 + $model['out_trade_no'] = $order['orderid']; + + // 设置订单总金额 + $model['total_amount'] = $order['real_amount'];//实付金额 + + // 设置订单标题 + $model['subject'] = $order['remark']; + + //// 设置产品码 + //$model['product_code'] = "QUICK_MSECURITY_PAY"; + // + ////设置订单包含的商品列表信息 + //$goodsDetail = array(); + //$goodsDetail0 = array(); + //$goodsDetail0['goods_name'] = "ipad"; + //$goodsDetail0['alipay_goods_id'] = "20010001"; + //$goodsDetail0['quantity'] = 1; + //$goodsDetail0['price'] = "2000"; + //$goodsDetail0['goods_id'] = "apple-01"; + //$goodsDetail0['goods_category'] = "34543238"; + //$goodsDetail0['categories_tree'] = "124868003|126232002|126252004"; + //$goodsDetail0['show_url'] = "http://www.alipay.com/xxx.jpg"; + //$goodsDetail[] = $goodsDetail0; + //$model['goods_detail'] = $goodsDetail; + // + ////设置订单绝对超时时间 + //$model['time_expire'] = "2016-12-31 10:05:00"; + // + ////设置业务扩展参数 + //$extendParams = array(); + //$extendParams['sys_service_provider_id'] = "2088511833207846"; + //$extendParams['hb_fq_seller_percent'] = "100"; + //$extendParams['hb_fq_num'] = "3"; + //$extendParams['industry_reflux_info'] = "{\"scene_code\":\"metro_tradeorder\",\"channel\":\"xxxx\",\"scene_data\":{\"asset_name\":\"ALIPAY\"}}"; + //$extendParams['specified_seller_name'] = "XXX的跨境小铺"; + //$extendParams['royalty_freeze'] = "true"; + //$extendParams['card_type'] = "S0JP0000"; + //$model['extend_params'] = $extendParams; + // + ////设置公用回传参数 + //$model['passback_params'] = "merchantBizType%3d3C%26merchantBizNo%3d2016010101111"; + // + ////设置商户的原始订单号 + //$model['merchant_order_no'] = "20161008001"; + // + ////设置外部指定买家 + //$extUserInfo = array(); + //$extUserInfo['cert_type'] = "IDENTITY_CARD"; + //$extUserInfo['cert_no'] = "362334768769238881"; + //$extUserInfo['name'] = "李明"; + //$extUserInfo['mobile'] = "16587658765"; + //$extUserInfo['min_age'] = "18"; + //$extUserInfo['need_check_info'] = "F"; + //$extUserInfo['identity_hash'] = "27bfcd1dee4f22c8fe8a2374af9b660419d1361b1c207e9b41a754a113f38fcc"; + //$model['ext_user_info'] = $extUserInfo; + // + ////设置通知参数选项 + //$queryOptions = array(); + //$queryOptions[] = "hyb_amount"; + //$queryOptions[] = "enterprise_pay_info"; + //$model['query_options'] = $queryOptions; + + //dump($pay_config); + $request->setBizContent(json_encode($model, JSON_UNESCAPED_UNICODE)); + $request->setNotifyUrl($this->notifyUrl); + // 如果是第三方代调用模式,请设置app_auth_token(应用授权令牌) + $authToken = $this->pay_config['app_auth_token'];//"<-- 请填写应用授权令牌 -->" + $orderStr = $alipayClient->sdkExecute($request, $authToken); + + return Tools::set_ok('ok',$orderStr); + } + + private function getAlipayConfig($pay_config) + { + $privateKey = $pay_config['alipay_app_private_key'];//'<-- 请填写您的应用私钥,例如:MIIEvQIBADANB ... ... -->'; + $alipayPublicKey = $pay_config['alipay_app_public_key'];//'<-- 请填写您的支付宝公钥,例如:MIIBIjANBg... -->'; + $alipayConfig = new \AlipayConfig(); + $alipayConfig->setServerUrl('https://openapi.alipay.com/gateway.do'); + $alipayConfig->setAppId($pay_config['app_id']);//'<-- 请填写您的AppId,例如:2019091767145019 -->' + $alipayConfig->setPrivateKey($privateKey); + $alipayConfig->setFormat('json'); + $alipayConfig->setAlipayPublicKey($alipayPublicKey); + $alipayConfig->setCharset('UTF-8'); + $alipayConfig->setSignType('RSA2'); + return $alipayConfig; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/pay/AlipayScanPay.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/pay/AlipayScanPay.php new file mode 100644 index 0000000..5a72962 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/pay/AlipayScanPay.php @@ -0,0 +1,184 @@ +getOptions()); + //2. 发起API调用(以支付能力下的统一收单交易创建接口为例) + $result = Factory::payment()->faceToFace() + ->optional('scene','bar_code') + ->pay($order['title'], $order['orderid'], $order['real_amount'], $auth_code); + + if($result->code == 10003){ + return Tools::set_res(230,'交易进行中(提示用户输入密码)'.$result->msg); + } + //必须解析json + $resp = json_decode($result->httpBody, true); + //支付宝面对面扫码支付后续流程 + return Tools::set_ok('ok',$resp['alipay_trade_pay_response']);//返回支付结果(一维数组) + } catch (\Exception $e) { + $err_data = [ + 'error'=>'支付宝付款码支付错误'.$e->getMessage(), + 'error_info'=>$e->getTraceAsString() + ]; + Tools::log_to_write_txt($err_data); + return Tools::set_fail('错误.'.$e->getMessage()); + } + } + + + /** + * desc:获取支付配置 + * author:wh + * @throws Exception + */ + private function getOptions() + { + if(empty($this->pay_config)){ + throw new Exception('支付宝配置错误'); + } + if(empty($this->pay_config['notifyUrl'])){ + throw new Exception('支付宝配置错误:notifyUrl通知地址字段必须配置(免密支付是同步返回支付结果,输入密码支付是异步通知支付结果)'); + } + $options = new Config(); + $options->protocol = 'https'; + $options->gatewayHost = 'openapi.alipay.com'; + $options->signType = $this->pay_config['sign_type']; + + $options->appId = $this->pay_config['app_id'];//'<-- 请填写您的AppId,例如:2019022663440152 -->'; + + // 为避免私钥随源码泄露,推荐从文件中读取私钥字符串而不是写入源码中 + $options->merchantPrivateKey = $this->pay_config['alipay_app_private_key'];//'<-- 请填写您的应用私钥,例如:MIIEvQIBADANB ... ... -->'; + + $options->alipayCertPath = Tools::get_root_path().$this->pay_config['cert_path_alipayCertPublicKey_RSA2'];//'<-- 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt -->'; + $options->alipayRootCertPath = Tools::get_root_path().$this->pay_config['cert_path_alipayRootCert'];//'<-- 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt" -->'; + $options->merchantCertPath = Tools::get_root_path().$this->pay_config['appCertPublicKey'];//'<-- 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt -->'; + + //注:如果采用非证书模式,则无需赋值上面的三个证书路径,改为赋值如下的支付宝公钥字符串即可 + // $options->alipayPublicKey = '<-- 请填写您的支付宝公钥,例如:MIIBIjANBg... -->'; + + //可设置异步通知接收服务地址(可选) + $options->notifyUrl = $this->pay_config['notifyUrl'];//request()->domain().'/index/alipay/notify';//"<-- 请填写您的支付类接口异步通知接收服务地址,例如:https://www.test.com/callback -->"; + + return $options; + } + + + /** + * desc:支付宝扫码异步通知 + * + * 【通知结果是一维数组】 + * + * {"gmt_create":"2024-06-10 11:53:37","charset":"UTF-8","seller_email":"15023017325", + * "subject":"购物消费", + * "sign":"oCR3X0ViRrydigLMeOCTrMBNx7O8bSZnEflT0qZYo0gXk2jTI2OVB9mG2UPCkGuSND3wq/pMRFN + * ljuz+2r3KMha9a6ArD/ey8w1am3yCKI+FMqUMQSv9TJqfpWdAsc8B45egtr+txBBOA1NMfN41hPnTxM3QzQ82cm + * 7VYw6pnYpxJVj/3AsqYxY+wcyMq5+v25eC4TgBo+YJ9pfb3rqaAV91cJBoHRq1Cwh0XEnOzm9zpv7b5cu3r+V0oeW + * wTCCi8S1TXW2dfE4H8o63xgASYfJO2fgGIu19YICQdUONkDhu9Tfbf3fFf9SHw3RIw8zrQRqkwAXC32NwfUv4Ru5NqQ==", + * "buyer_id":"2088612757425274","invoice_amount":"3258.60","notify_id":"2024061001222115350025271439748729", + * "fund_bill_list":"[{\"amount\":\"3258.60\",\"fundChannel\":\"ALIPAYACCOUNT\"}]", + * "notify_type":"trade_status_sync","trade_status":"TRADE_SUCCESS","receipt_amount":"3258.60", + * "app_id":"2021004136614333","buyer_pay_amount":"3258.60","sign_type":"RSA2", + * "seller_id":"2088642975857443","gmt_payment":"2024-06-10 11:53:50", + * "notify_time":"2024-06-10 12:07:40","version":"1.0","out_trade_no":"cs25obpv1717991615660", + * "total_amount":"3258.60","trade_no":"2024061022001425271426453509","auth_app_id":"2021004136614333", + * "buyer_logon_id":"146***@qq.com","point_amount":"0.00"} + * + * author:wh + * param function $fn 传入订单处理逻辑函数 要求返回success、fail + * @return string + */ + function scanPayNotify($fn){ + Tools::log_to_write_txt([ + '支付宝异步通知', + 'input'=>input() + ]); + + $out_trade_no = input('out_trade_no'); + if(empty($out_trade_no)){ + Tools::log_to_write_txt(['error'=>'out_trade_no为空,文件:'.__FILE__.',line:'.__LINE__]); + return ''; + } + //$order = Db::table(TabConf::$fa_order) + // ->where('orderid',$out_trade_no) + // ->find(); + //if(empty($order)){ + // Tools::log_to_write_txt(['error'=>'订单不存在,文件:'.__FILE__.',line:'.__LINE__]); + // return ''; + //} + //该字段用于判断成功、失败 + $trade_status = input('trade_status'); + if(empty($trade_status)){ + Tools::log_to_write_txt(['error'=>'trade_status为空,文件:'.__FILE__.',line:'.__LINE__]); + return ''; + } + if(empty($this->pay_config['seller_id'])){ + Tools::log_to_write_txt(['error'=>'卖家seller_id为空,文件:'.__FILE__.',line:'.__LINE__]); + return ''; + } + + //判断来源 + $seller_id = input('seller_id'); + if($seller_id != $this->pay_config['seller_id']){ + Tools::log_to_write_txt(['error'=>'支付通知来源错误','seller_id'=>$seller_id,'config'=>$this->pay_config['seller_id']]); + return 'fail'; + } + //支付宝面对面扫码支付后续流程 + $result = input(); + //异步通知 + //$res = CheckstandLogic::alipayScanSuccessFlow($result,$order); + //if($res['code'] == 200 || $res['code'] == 230){//成功或进行中 + // return 'success'; + //} + return $fn($result);//要求返回success、fail + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/pay/AlipayWapPay.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/pay/AlipayWapPay.php new file mode 100644 index 0000000..c8bbb74 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/pay/AlipayWapPay.php @@ -0,0 +1,271 @@ +alipayConfig; + + /** 初始化 **/ + $aop = new \AopCertClient(); + + /** 支付宝网关 **/ + $aop->gatewayUrl = "https://openapi.alipay.com/gateway.do"; + + /** 应用id,如何获取请参考:https://opensupport.alipay.com/support/helpcenter/190/201602493024 **/ + $aop->appId = $alipayConfig['app_id']; + + /** 密钥格式为pkcs1,如何获取私钥请参考:https://opensupport.alipay.com/support/helpcenter/207/201602469554 **/ + $aop->rsaPrivateKey = $alipayConfig['alipay_app_private_key']; + + /** 应用公钥证书路径,下载后保存位置的绝对路径 **/ + $appCertPath = $root_path.$alipayConfig['appCertPublicKey']; + + /** 支付宝公钥证书路径,下载后保存位置的绝对路径 **/ + $alipayCertPath = $root_path.$alipayConfig['cert_path_alipayCertPublicKey_RSA2']; + + /** 支付宝公钥证书路径,下载后保存位置的绝对路径 **/ + $rootCertPath = $root_path.$alipayConfig['cert_path_alipayRootCert']; + + /** 设置签名类型 **/ + $aop->signType= "RSA2"; + + /** 设置请求格式,固定值json **/ + $aop->format = "json"; + + /** 设置编码格式 **/ + $aop->charset= "utf-8"; + + /** 调用getPublicKey从支付宝公钥证书中提取公钥 **/ + $aop->alipayrsaPublicKey = $aop->getPublicKey($alipayCertPath); + + /** 是否校验自动下载的支付宝公钥证书,如果开启校验要保证支付宝根证书在有效期内 **/ + $aop->isCheckAlipayPublicCert = true; + + /** 调用getCertSN获取证书序列号 **/ + $aop->appCertSN = $aop->getCertSN($appCertPath); + + /** 调用getRootCertSN获取支付宝根证书序列号 **/ + $aop->alipayRootCertSN = $aop->getRootCertSN($rootCertPath); + + /** 实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.wap.pay **/ + $request = new \AlipayTradeWapPayRequest(); + + /** 设置业务参数 **/ + $request->setBizContent("{" . + + /** 商户订单号,商户自定义,需保证在商户端不重复,如:20150320010101001 **/ + "'out_trade_no':'$orderid'," . + + /** 销售产品码,固定值:QUICK_WAP_WAY **/ + "'product_code':'QUICK_WAP_WAY'," . + + /** 订单金额,精确到小数点后两位 **/ + "'total_amount':'$total_amount'," . + + /** 订单标题 **/ + "'subject':'$subject'," . + + /** 业务扩展参数 **/ + // "'extend_params':{" . + /** 系统商编号,填写服务商的PID用于获取返佣,返佣参数传值前提:传值账号需要签约返佣协议,用于isv商户。 **/ + //"'sys_service_provider_id':'2088511833207846'," . + + /** 花呗分期参数传值前提:必须有该接口花呗收款准入条件,且需签约花呗分期 **/ + /** 指定可选期数,只支持3/6/12期,还款期数越长手续费越高 **/ + // "'hb_fq_num':'3'," . + + /** 指定花呗分期手续费承担方式,手续费可以由用户全承担(该值为0),也可以商户全承担(该值为100),但不可以共同承担,即不可取0和100外的其他值。 **/ + //"'hb_fq_seller_percent':'100'" . + // "}," . + + /** 订单描述 **/ + "'body':'$subject'" . + "}"); + + /**注:支付结果以异步通知为准,不能以同步返回为准,因为如果实际支付成功,但因为外力因素,如断网、断电等导致页面没有跳转,则无法接收到同步通知;**/ + /** 支付完成的跳转地址,用于用户视觉感知支付已成功,传值外网可以访问的地址,如果同步未跳转可参考该文档进行确认:https://opensupport.alipay.com/support/helpcenter/193/201602474937 **/ + $request->setReturnUrl($pay_end_url); + + /** 异步通知地址,以http或者https开头的,商户外网可以post访问的异步地址,用于接收支付宝返回的支付结果,如果未收到该通知可参考该文档进行确认:https://opensupport.alipay.com/support/helpcenter/193/201602475759 **/ + $request->setNotifyUrl($notify_url); + + /** 调用SDK生成支付链接,可在浏览器打开链接进入支付页面 **/ + $result = $aop->pageExecute ($request,'get'); + + /**第三方调用(服务商模式),传值app_auth_token后,会收款至授权token对应商家账号,如何获传值app_auth_token请参考文档:https://opensupport.alipay.com/support/helpcenter/79/201602494631 **/ + //$result = $aop->pageExecute($request,'get',"传入获取到的app_auth_token值"); + + /** 获取接口调用结果,如果调用失败,可根据返回错误信息到该文档寻找排查方案:https://opensupport.alipay.com/support/helpcenter/93 **/ + //print_r(htmlspecialchars($result)); + $html = << +EOF; + return $html; + } + function alipayWapH5payCertReturnUrl($order,$notify_url,$pay_end_url){ + $root_path = Tools::get_root_path(); + require $root_path.'library/alipay-sdk-php-all/aop/AopCertClient.php'; + require $root_path.'library/alipay-sdk-php-all/aop/request/AlipayTradeWapPayRequest.php'; + + Tools::log_to_write_txt(['支付宝支付(证书模式)-提交支付,参数(出参在回调中):', $order,$notify_url,$pay_end_url]); + + + //商户订单号,商户网站订单系统中唯一订单号,必填 + $orderid = $order['orderid']; + $total_amount = $order['real_amount'];//交易金额 + $subject = $order['goods_name']?:"购物消费¥{$total_amount}元";//订单标题 + + //$game_admin_domain = any_sys_env_domain_conf('store_outer_api_domain'); + //这里可以把支付结果通知到其它系统 + //$notify_url = $game_admin_domain.'/paytool/Alipay/alipayNotify'; + + //跳转到支付结果并显示商品推荐 + //$pay_end_url = url('paytool/recommend/payend','',false,true);//支付结果展示 + + //$alipayConfig = aliPayConfPublicAcc(); + $alipayConfig = $this->alipayConfig; + + /** 初始化 **/ + $aop = new \AopCertClient(); + + /** 支付宝网关 **/ + $aop->gatewayUrl = "https://openapi.alipay.com/gateway.do"; + + /** 应用id,如何获取请参考:https://opensupport.alipay.com/support/helpcenter/190/201602493024 **/ + $aop->appId = $alipayConfig['app_id']; + + /** 密钥格式为pkcs1,如何获取私钥请参考:https://opensupport.alipay.com/support/helpcenter/207/201602469554 **/ + $aop->rsaPrivateKey = $alipayConfig['alipay_app_private_key']; + + /** 应用公钥证书路径,下载后保存位置的绝对路径 **/ + $appCertPath = $root_path.$alipayConfig['appCertPublicKey']; + + /** 支付宝公钥证书路径,下载后保存位置的绝对路径 **/ + $alipayCertPath = $root_path.$alipayConfig['cert_path_alipayCertPublicKey_RSA2']; + + /** 支付宝公钥证书路径,下载后保存位置的绝对路径 **/ + $rootCertPath = $root_path.$alipayConfig['cert_path_alipayRootCert']; + + /** 设置签名类型 **/ + $aop->signType= "RSA2"; + + /** 设置请求格式,固定值json **/ + $aop->format = "json"; + + /** 设置编码格式 **/ + $aop->charset= "utf-8"; + + /** 调用getPublicKey从支付宝公钥证书中提取公钥 **/ + $aop->alipayrsaPublicKey = $aop->getPublicKey($alipayCertPath); + + /** 是否校验自动下载的支付宝公钥证书,如果开启校验要保证支付宝根证书在有效期内 **/ + $aop->isCheckAlipayPublicCert = true; + + /** 调用getCertSN获取证书序列号 **/ + $aop->appCertSN = $aop->getCertSN($appCertPath); + + /** 调用getRootCertSN获取支付宝根证书序列号 **/ + $aop->alipayRootCertSN = $aop->getRootCertSN($rootCertPath); + + /** 实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.wap.pay **/ + $request = new \AlipayTradeWapPayRequest(); + + /** 设置业务参数 **/ + $request->setBizContent("{" . + + /** 商户订单号,商户自定义,需保证在商户端不重复,如:20150320010101001 **/ + "'out_trade_no':'$orderid'," . + + /** 销售产品码,固定值:QUICK_WAP_WAY **/ + "'product_code':'QUICK_WAP_WAY'," . + + /** 订单金额,精确到小数点后两位 **/ + "'total_amount':'$total_amount'," . + + /** 订单标题 **/ + "'subject':'$subject'," . + + /** 业务扩展参数 **/ + // "'extend_params':{" . + /** 系统商编号,填写服务商的PID用于获取返佣,返佣参数传值前提:传值账号需要签约返佣协议,用于isv商户。 **/ + //"'sys_service_provider_id':'2088511833207846'," . + + /** 花呗分期参数传值前提:必须有该接口花呗收款准入条件,且需签约花呗分期 **/ + /** 指定可选期数,只支持3/6/12期,还款期数越长手续费越高 **/ + // "'hb_fq_num':'3'," . + + /** 指定花呗分期手续费承担方式,手续费可以由用户全承担(该值为0),也可以商户全承担(该值为100),但不可以共同承担,即不可取0和100外的其他值。 **/ + //"'hb_fq_seller_percent':'100'" . + // "}," . + + /** 订单描述 **/ + "'body':'$subject'" . + "}"); + + /**注:支付结果以异步通知为准,不能以同步返回为准,因为如果实际支付成功,但因为外力因素,如断网、断电等导致页面没有跳转,则无法接收到同步通知;**/ + /** 支付完成的跳转地址,用于用户视觉感知支付已成功,传值外网可以访问的地址,如果同步未跳转可参考该文档进行确认:https://opensupport.alipay.com/support/helpcenter/193/201602474937 **/ + $request->setReturnUrl($pay_end_url); + + /** 异步通知地址,以http或者https开头的,商户外网可以post访问的异步地址,用于接收支付宝返回的支付结果,如果未收到该通知可参考该文档进行确认:https://opensupport.alipay.com/support/helpcenter/193/201602475759 **/ + $request->setNotifyUrl($notify_url); + + /** 调用SDK生成支付链接,可在浏览器打开链接进入支付页面 **/ + $result = $aop->pageExecute ($request,'get'); + + /**第三方调用(服务商模式),传值app_auth_token后,会收款至授权token对应商家账号,如何获传值app_auth_token请参考文档:https://opensupport.alipay.com/support/helpcenter/79/201602494631 **/ + //$result = $aop->pageExecute($request,'get',"传入获取到的app_auth_token值"); + + /** 获取接口调用结果,如果调用失败,可根据返回错误信息到该文档寻找排查方案:https://opensupport.alipay.com/support/helpcenter/93 **/ + //print_r(htmlspecialchars($result)); +// $html = << +//EOF; + return $result; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/transfer/AlipayTransfer.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/transfer/AlipayTransfer.php new file mode 100644 index 0000000..c880277 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/transfer/AlipayTransfer.php @@ -0,0 +1,108 @@ +cert_path_alipayCertPublicKey_RSA2 = Tools::get_root_path().$alipayConfig['cert_path_alipayCertPublicKey_RSA2'];//'<-- 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt -->'; + $trans_config->cert_path_alipayRootCert = Tools::get_root_path().$alipayConfig['cert_path_alipayRootCert'];//'<-- 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt" -->'; + $trans_config->appCertPublicKey = Tools::get_root_path().$alipayConfig['appCertPublicKey'];//'<-- 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt -->' + + + $order_prefix = 'trs'; + $order_id = Tools::to_create_order_no($order_prefix); + $trans_config->order_id = $order_id;//订单号 必须 + $trans_config->timestamp = Tools::get_now_date();//请求时间 eg:2020-01-08 10:12:50 + $trans_config->trans_amount = $trans_data['trans_amount'];//转账金额 必须 且为字符串 最低0.1元 取值范围[0.1,100000000] + $trans_config->order_title = $trans_data['order_title'];//订单标题 可选 + $trans_config->phone = $trans_data['phone'];//支付宝登录手机号 必须 + $trans_config->name = $trans_data['name'];//支付宝真实姓名 必须 + $trans_config->remark = $trans_data['remark'];//支付备注 可选 + + + $trans = new AlipayTransfer($alipayConfig); + Tools::log_to_write_txt(['转账到支付宝,入参:'=>input(), 'trans_config'=>$trans_config]); + $pay_res = $trans->alitransfer($trans_config); + Tools::log_to_write_txt(['转账到支付宝,出参:'=>$pay_res]); + //直接返回转账结果,自主验证成功失败的status。code=10000表示操作成功,status=SUCCESS表示转账成功 + //由于是同步响应,所以不需要对结果验签 + return $pay_res; + } + * + */ + function alitransfer($transferData){ + Factory::setOptions($this->getAlipayOptions()); + + $method = 'alipay.fund.trans.uni.transfer'; + //公共参数 + $textParams = [ + 'app_id'=>$this->alipayConfig['app_id'], + 'charset'=>'utf-8', + 'sign_type'=>'RSA2', + 'timestamp'=>$transferData->timestamp, + 'version'=>'1.0', + ]; + + //请求参数 + //订单号前缀 + $biz_content = [ + 'out_biz_no'=>$transferData->order_id,//商家支付订单号 + 'trans_amount'=>$transferData->trans_amount, + 'product_code'=>'TRANS_ACCOUNT_NO_PWD', + 'biz_scene'=>'DIRECT_TRANSFER', + 'order_title'=>$transferData->order_title, + 'payee_info'=>[ + 'identity'=>$transferData->phone,//手机号 + 'identity_type'=>'ALIPAY_LOGON_ID', + 'name'=>$transferData->name,//参与方真实姓名,如果非空,将校验收款支付宝账号姓名一致性。当identity_type=ALIPAY_LOGON_ID时,本字段必填。 + ], + 'remark'=>$transferData->remark, + ]; + $pay_res = Factory::util()->generic()->execute($method,$textParams,$biz_content); + + return $pay_res; + } + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/transfer/TransferConfig.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/transfer/TransferConfig.php new file mode 100644 index 0000000..e10a4b3 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/alipay/transfer/TransferConfig.php @@ -0,0 +1,63 @@ +cert_path_alipayCertPublicKey_RSA2 = Tools::get_root_path().$alipayConfig['cert_path_alipayCertPublicKey_RSA2'];//'<-- 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt -->'; + $trans_config->cert_path_alipayRootCert = Tools::get_root_path().$alipayConfig['cert_path_alipayRootCert'];//'<-- 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt" -->'; + $trans_config->appCertPublicKey = Tools::get_root_path().$alipayConfig['appCertPublicKey'];//'<-- 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt -->' + + + $order_prefix = 'trs'; + $order_id = Tools::to_create_order_no($order_prefix); + $trans_config->order_id = $order_id;//订单号 必须 + $trans_config->timestamp = Tools::get_now_date();//请求时间 eg:2020-01-08 10:12:50 + $trans_config->trans_amount = '0.21';//转账金额 必须 且为字符串 最低0.1元 取值范围[0.1,100000000] + $trans_config->order_title = '代理提现';//订单标题 可选 + $trans_config->phone = '18290416033';//支付宝登录手机号 必须 + $trans_config->name = '王华';//支付宝真实姓名 必须 + $trans_config->remark = '代理提现到支付宝';//支付备注 可选 + + $trans = new AlipayTransfer(); + $pay_res = $trans->alitransfer($alipayConfig, $trans_config); + + dump($pay_res); + * + */ +class TransferConfig +{ + //'<-- 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt -->'; + public $cert_path_alipayCertPublicKey_RSA2 = '';//必须 + //'<-- 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt" -->'; + public $cert_path_alipayRootCert = '';//必须 + //'<-- 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt -->' + public $appCertPublicKey = '';//必须 + + public $notifyUrl = '';//接收异步通知(可选) + + + //业务参数 + public $order_id = '';//订单号 必须 + public $timestamp = '';//请求时间 必须 eg:2020-01-08 10:12:50 + public $trans_amount = '';//转账金额 必须 且为字符串 最低0.1元 取值范围[0.1,100000000] + public $order_title = '';//订单标题 可选 + public $phone = '';//支付宝登录手机号 必须 + public $name = '';//支付宝真实姓名 必须 + public $remark = '';//支付备注 可选 + + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/api/Api.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/api/Api.php new file mode 100644 index 0000000..7f48139 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/api/Api.php @@ -0,0 +1,425 @@ +apidir = Tools::get_root_path().'application/api/controller/'; + if(!$this->table_obj) + $this->table_obj = new Table(); + } + + /** + * desc:api目录 + * author:wh + * @param string $dir api创建目录,格式必须按照例子填写 eg: application/api/controller/ + */ + function setapidir(string $dir){ + $this->apidir = $dir; + $nsp = str_replace('application','app',substr($dir,0,strlen($dir)-2)); + $this->namespace = str_replace('/','\\',$nsp); + } + + /** + * desc:替换表前缀 + * author:wh + * @param $tablename + * @return false|string + */ + function replaceTablePrefix($tablename){ + $prefix = config('database.prefix'); + if($prefix){ + return substr($tablename,strlen($prefix)); + } + $tmp_prefix = 't_'; + if(false !== strpos($tablename,$tmp_prefix)){ + return substr($tablename,strlen($tmp_prefix)); + } + return $tablename; + } + + /** + * desc:设置方法名称 + * + * author:wh + * @param array $functions_arr + */ + function setApiFunctionsName(array $functions_arr){ + $this->functions_arr = array_merge($functions_arr,$this->functions_arr); + } + + /** + * desc:动态创建方法 + * + * author:wh + */ + private function syncCreateFunction(array $func_name_arr){ + $code = ''; + foreach ($func_name_arr as $func_name=>$func_remark){ //接口方法公共代码模块 + $commonFunctionCodeModule = $this->commonFunctionCodeModule($func_remark); + $code .= <<, <, >=, <=, != + * between + * in + * + * author:wh + * @param array $select_in_params eg: + * [ + * 'gameid'=>['and'], + * 'nickname'=>['like%'], + * 'reg_time'=>['between'],//时间区间查询 + * 'audit_time'=>['2015-01-3','eq'],//时间区间查询 + * ] + * + */ + function setQueryCodeBody(array $select_in_params){ + $code = ''; + + return $code; + } + + /** + * desc:写入查询接口的代码体 + * + * author:wh + * @param array $insert_params + * @return string + */ + function setInsertCodeBody(array $insert_params){ + $code = ''; + + return $code; + } + + /** + * desc:快速构建api代码 + * + * 本方法不验证接口名称是否重复 + * + * author:wh + * @param string $tablename 数据表名称 + * @param array $func_name_arr 方法名称数组 eg:['getData'=>'获取数据','function_aaa'=>'function_注释aaa'] + */ + function buildApi(string $tablename, array $func_name_arr){ + //deal table name + $tablename = $this->replaceTablePrefix($tablename); + + //基础 start + //create base controller + $this->buildBaseApiPublicController();//开放控制器 + $this->buildBaseApiAuthController();//权限控制器 + $this->errController();//错误控制器 + //基础 end + + //接口类名 + $classname = ucfirst(Tools::convertUnderLine($tablename)); + //接口注释 + $comment = $this->table_obj->getTableComment($tablename); + + //批量构建方法代码 + $build_functions_code = $this->syncCreateFunction($func_name_arr); + + //PHP文件名,不含物理路径 + $file_name = $classname.$this->ext(); + + //PHP文件路径和文件名 + $php_file = $this->apidir.$file_name; + + if(file_exists($php_file)){ + //追加代码 + //读取并删除最后的“}” + $php_code_str = file_get_contents($php_file); + $sub_php_code_str = mb_substr($php_code_str,0,mb_strlen($php_code_str)-2); + //追加新的代码 + $sub_php_code_str .= $build_functions_code ."\n }"; + //清空代码 + file_put_contents($php_file,''); + //追加 + $this->createFileExists($php_file,$sub_php_code_str); + }else{ + //创建代码文件 + $use_tpl = $this->useTpl(); + $php = <<namespace}; +{$use_tpl} + +/** + * {$comment} + * Class {$classname} + * @package {$this->namespace} + */ +class {$classname} extends BaseApiPublicController +{ + + + + {$build_functions_code} + +} +EOF; + + $this->createDir($this->apidir); + + + $this->createFile($php_file,$php); + } + } + + /** + * desc:公共方法模块 + * + * author:wh + * @param $comment + * @return string + */ + private function commonFunctionCodeModule($comment){ + $php = <<'{$comment},__IN:','INPUT'=>input()]); + //region 校验区 start + + //endregion 校验区 end + + + + + //region 业务逻辑区 start + + //endregion 业务逻辑区 end + + + + + //响应结果处理 + Tools::log_to_write_txt(['title'=>'{$comment},__OUT:','OUTPUT'=>[]]); + return json(Tools::set_res(200,'ok')); + }catch (\Exception \$e){ + Tools::log_to_write_txt([ + 'error'=>'{$comment},API异常。'.\$e->getMessage(), + '入参'=>input(), + 'error_info'=>\$e->getTraceAsString() + ]); + return json(Tools::set_res(500,'系统繁忙')); + } +EOF; + + return $php; + } + + /** + * desc:api基础开放控制器 + * + * 如果存在则不创建 + * + * author:wh + */ + private function buildBaseApiPublicController(){ + $file_name = 'BaseApiPublicController'; + $php = <<namespace}; + +use think\Controller; + + +/** + * desc:api基础开放架构 + * + * 所有开放接口继承此父类 + * + * author: + */ +class {$file_name} extends Controller +{ + +} +EOF; + + $php_file = $this->apidir.$file_name.$this->ext(); + + $this->createDir($this->apidir); + + $this->createFile($php_file,$php); + } + + /** + * desc:api基础权限控制器 + * + * 如果存在则不创建 + * + * author:wh + */ + private function buildBaseApiAuthController(){ + $file_name = 'BaseApiAuthController'; + $php = <<namespace}; + +use think\Controller; + + +/** + * desc:api基础权限架构 + * + * 所有权限接口继承此父类 + * + * author: + */ +class {$file_name} extends Controller +{ + +} +EOF; + + $php_file = $this->apidir.$file_name.$this->ext(); + + $this->createDir($this->apidir); + + $this->createFile($php_file,$php); + } + + /** + * desc:api底层基础控制器架构 + * + * [按需创建][可单独调用] + * + * 如果存在则不创建 + * + * author:wh + */ + function buildBaseApiController(){ + $file_name = 'BaseApiController'; + $php = <<namespace}; + +use think\Controller; + + +/** + * desc:api底层基础控制器架构 + * + * author: + */ +class {$file_name} extends Controller +{ + + +} +EOF; + + $php_file = $this->apidir.$file_name.$this->ext(); + + $this->createDir($this->apidir); + + $this->createFile($php_file,$php); + } + + /** + * desc:use模块 + * + * author:wh + * @return string + */ + private function useTpl(){ + $php = <<namespace}; + + +use wanghua\general_utility_tools_php\\framework\\base\PublicController; +use wanghua\general_utility_tools_php\\tool\Tools; + + +/** + * + * 错误(异常)中转架构 + * + * Class Err + * @package app\index\controller + */ +class {$file_name} extends BaseApiPublicController +{ + /** + * desc:校验失败中转 + * + * 场景:内网端口验证未通过时跳转; + * + * author:wh + * @return \\think\\response\\Json + */ + function checkfailed(){ + + return json(Tools::set_res(500,'Permission denied',input())); + } +} +EOF; + + $php_file = $this->apidir.$file_name.$this->ext(); + + $this->createDir($this->apidir); + + $this->createFile($php_file,$php); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/api/ApiDocument.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/api/ApiDocument.php new file mode 100644 index 0000000..458c7df --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/api/ApiDocument.php @@ -0,0 +1,317 @@ +buildDoc(); + * Class ApiDocument + */ +class ApiDocument +{ + public $app_name = 'api'; + private $api_cache_arr = [];//缓存所有接口 + public $api_domain = 'http://127.0.0.1:8080/';//接口域名/ip + /** + * @var string 接口控制器命名空间 + */ + public $namespace = 'app\\api\\controller'; + /** + * @var string 接口控制器基类,精确到类名,如:app\\common\\controller\\Api + * 注意:如果同级目录存在多个基类,则设置直接基类,如果同级目录没有基类,则设置底层基类 + */ + public $extends_base_class = ''; + //控制器目录物理路径 + public $controllerDirectory = ''; + /** + * @var string 接口文档保存路径 + */ + public $api_docs_save_dir = 'public/api_docs/'; + //设置过滤类 + private $filterClassArr = []; + //设置过滤方法 + private $filterFunctionArr = []; + + public function __construct($api_domain='',$controllerDirectory='') + { + //默认,如果是tp6,那application就要改为app了,自行传参吧 + $this->controllerDirectory = Tools::get_root_path()."application/{$this->app_name}/controller"; + if($api_domain){ + $this->api_domain = $api_domain; + } + if($controllerDirectory){ + $this->controllerDirectory = $controllerDirectory; + } + } + + /** + * desc:设置过滤类(类名) + * 过滤场景: + * 1、基类 + * 2、测试类 + * 3、定时任务类 + * 4、其它非必要类 + * author:wh + * @param array $filterClassArr + */ + function setFilterClass(array $filterClassArr=[]){ + $this->filterClassArr = $filterClassArr; + } + function setFilterFunction(array $filterArr=[]){ + $this->filterFunctionArr = $filterArr; + } + /** + * desc:构建接口文档,支持同步到在线文档 + * author:wh + */ + function buildDoc(){ + if(empty($this->extends_base_class)){ + throw new Exception('请设置接口控制器基类,精确到类名,如:app\\common\\controller\\Api'); + } + $out_path = Tools::get_root_path().$this->api_docs_save_dir; + if(!file_exists($out_path)){ + mkdir($out_path,0777,true); + } + $outputFile = $out_path.'api_list.md'; + $controllerClasses = []; + + // 搜索控制器目录下的所有PHP文件 + foreach (glob($this->controllerDirectory . '/*.php') as $filename) { + // 获取文件中的类名 + $class = basename($filename, '.php'); + if(in_array($class,$this->filterClassArr)){ + continue; + } + // 构建完整的命名空间类名 + $fullClassName = $this->namespace . '\\' . $class; + foreach (explode(',',$this->extends_base_class) as $base_class){ + // 检查类是否有效并且是think\Controller的子类 + if (class_exists($fullClassName) && is_subclass_of($fullClassName, $base_class)) { + $controllerClasses[] = $fullClassName; + } + } + } + + // 创建Markdown文件 + $file = fopen($outputFile, 'w') or die('无法创建文件'); + + $head_text = <<api_domain} +##### 请求方式:POST(默认) + +EOF; + // 写入Markdown文件头部 + fwrite($file, $head_text); + + foreach ($controllerClasses as $controllerClass) { + $reflector = new \ReflectionClass($controllerClass); + // 遍历控制器中的公共方法 + $methods = $reflector->getMethods(\ReflectionMethod::IS_PUBLIC); + foreach ($methods as $method) { + //过滤方法 + if(in_array($method->name, $this->filterFunctionArr)){ + continue; + } + $exp_class = explode('\\',$controllerClass); + //过滤类 + if(in_array($exp_class[count($exp_class)-1],$this->filterClassArr)){ + continue; + } + $comments = $method->getDocComment(); + if ($comments) { + $this->processMethodComment($comments, $controllerClass, $method->name, $outputFile); + } + } + } + fclose($file); + + //缓存所有接口 + cache('api_doc_cache_arr',$this->api_cache_arr); + } + + + /** + * desc:解析方法注释并写入Markdown文件 + * author:wh + * @param $comments + * @param $className + * @param $methodName + * @param $savepath + */ + private function processMethodComment($comments, $className, $methodName, $savepath) { + if($methodName == '__construct'){ + return ''; + } + $api_url = "/{$this->app_name}/{$className}/{$methodName}"; + $js_api_func_name = "api_{$className}_{$methodName}"; + $str = <<camelCaseToUnderscore($className); + + + $api_name = "{$this->app_name}/{$className}/{$methodName}"; + $doc_txt = <<api_cache_arr[$className][] = ['api_name'=>$api_name,'doc_txt'=>$doc_txt,'class_name'=>$className]; + file_put_contents($savepath,$doc_txt, FILE_APPEND); + } + + /** + * desc:驼峰转下划线 + * author:wh + * @param $string + * @return string + */ + private function camelCaseToUnderscore($string) { + $str = strtolower(preg_replace('/(? +.txt-lf{text-align: left} + +
+
文档说明:
+
1、如果没有明确说明,提交请求均使用post
+
2、此接口文档不包含websocket接口
+
3、接口参数之间使用“/”符号隔开
+
4、此文档接口测试功能只针对普通post、get接口,不能测试文件上传或文件流
+
5、功能模块按照颜色分组
+
清理缓存: + + 点击清理 + (由于接口数据可能会被缓存,发现数据没变化或需要时,可清理缓存) +
+ + + +
+ +EOF; + + $script_str = ""; + foreach ($api_doc_cache_arr as $class_name_key=>$func_arr){ + //随机取一个颜色 + $color_code = $color_code_arr[array_rand($color_code_arr,1)]; + //是否显示上边距 + $is_show_margin_top = 'margin-top: 50px;'; + foreach ($func_arr as $k => $item){ + $api_name = $item['api_name']; + $doc_txt = $item['doc_txt']; + + if($k > 0){ + + $is_show_margin_top = ''; + } + + + $function_name = str_replace('/','_',$api_name); + $htm_str .= << +
{$doc_txt}
+
+ 按需填写其它接口参数: + + 测试 +
+
+ + +EOF; + $script_str.=<< + + + + 接口文档 + + +
+ {$htm_str} +
+ + + + + + +EOF; + return $html; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/api/BaseApi.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/api/BaseApi.php new file mode 100644 index 0000000..ad763cb --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/api/BaseApi.php @@ -0,0 +1,230 @@ +post_url = $url; + if(empty($this->post_url)) { + Tools::log_to_write_txt(['error'=>'API OPERATE: '.ApiException::EMPTY_URL_ERROR], $this->getLogFileName()); + throw new \Exception(ApiException::EMPTY_URL_ERROR); + } + } + + /** + * desc:设置api请求日志文件名称 + * + * 日志保存在runtime中 + * + * author:wh + * @param string $log_file_name + */ + function setLogFileName(string $log_file_name=''){ + if($log_file_name)$this->log_file_name = $log_file_name; + } + + /** + * desc 获取记录日志的文件名 + * author:wh + * @return string + */ + function getLogFileName(){ + return $this->prefix.$this->log_file_name; + } + + /** + * desc:设置调用位置 + * author:wh + * @param $__file__ + * @param $__line__ + */ + function setFileDir($__file__, $__line__){ + $this->__sit__ = $__file__.' > '.$__line__; + } + + /** + * desc:错误位置 + * author:wh + * @return string + */ + function getErrorSit(){ + return $this->__sit__; + } + + /** + * desc: + * + * author:wh + * @param array $params + */ + private function mergeAuthParams(array &$params){ + $params['nonce'] = Tools::rand_str(10); + $params['timestamp'] = time(); + $token = config('service_framework_config.sign_token'); + $params['sign'] = Tools::signature($params, $token); + } + /** + * desc:执行post请求, 带权限参数 + * + * 同框架、内部系统推荐 + * + * 返回结果由code 错误码和msg 错误信息组成 + * + * author:wh + * @param $param 以表单格式提交数据 + * @return array|bool|int|mixed|string + * @throws ApiException + * @throws \think\Exception + */ + function apiPost($param){ + $this->mergeAuthParams($param); + + Tools::log_to_write_txt(['API OPERATE, IN',$this->post_url,$param], $this->getLogFileName()); + $res = Tools::curl_post($this->post_url, $param); + Tools::log_to_write_txt(['API OPERATE, OUT',$this->post_url, $res], $this->getLogFileName()); + + if(empty($res['data'])) return $res; + + $data = json_decode($res['data'], true); + if(empty($data['code'])) { + Tools::log_to_write_txt([ + 'error: api操作,错误', + 'post_url'=>$this->post_url, + 'error_info'=>ApiException::API_RESPONSE_FORMAT_ERROR, + 'error_sit'=>$this->getErrorSit(), + 'result'=>$res, + ], $this->getLogFileName()); + throw new \Exception(ApiException::API_RESPONSE_FORMAT_ERROR); + } + return $data; + } + + /** + * + * desc:执行post请求 + * + * 返回结果由code 错误码和msg 错误信息组成 + * + * author:wh + * @param $param 以json格式提交数据 + * @return array|bool|int|mixed|string + * @throws ApiException + * @throws \think\Exception + */ + function do($param){ + + Tools::log_to_write_txt(['API OPERATE, IN',$this->post_url,$param], $this->getLogFileName()); + $res = Tools::curl_post($this->post_url, json_encode($param)); + Tools::log_to_write_txt(['API OPERATE, OUT',$this->post_url, $res], $this->getLogFileName()); + + if(empty($res['data'])) return $res; + + $data = json_decode($res['data'], true); + if(empty($data['code'])) { + Tools::log_to_write_txt([ + 'error: api操作,错误', + 'post_url'=>$this->post_url, + 'error_info'=>ApiException::API_RESPONSE_FORMAT_ERROR, + 'error_sit'=>$this->getErrorSit(), + 'result'=>$res, + ], $this->getLogFileName()); + throw new \Exception(ApiException::API_RESPONSE_FORMAT_ERROR.$this->post_url); + } + return $data; + } + + /** + * + * desc:执行请求 + * + * do 方法的改版,返回结果由state 错误码和err 错误信息组成 + * + * author:wh + * @param $param 以json格式提交数据 + * @return array|bool|int|mixed|string + * @throws ApiException + * @throws \think\Exception + */ + function apiDo($param){ + + Tools::log_to_write_txt(['API OPERATE, IN',$this->post_url,$param], $this->getLogFileName()); + $res = Tools::curl_post($this->post_url, json_encode($param)); + Tools::log_to_write_txt(['API OPERATE, OUT',$this->post_url, $res], $this->getLogFileName()); + if(empty($res['data'])) return $res; + + + $data = json_decode($res['data'], true); + + if(is_null($data['state'])) { + Tools::log_to_write_txt([ + 'error: api操作,错误', + 'post_url'=>$this->post_url, + 'error_info'=>ApiException::API_RESPONSE_FORMAT_ERROR, + 'error_sit'=>$this->getErrorSit(), + 'result'=>$res, + ], $this->getLogFileName()); + throw new \Exception(ApiException::API_RESPONSE_FORMAT_ERROR); + } + $data['code'] = isset($data['state'])&&$data['state']==0?200:$data['state']; + $data['msg'] = isset($data['err'])?$data['err']:'api接口错误'; + return $data; + } + + /** + * + * desc:执行请求 + * + * 同框架、内部系统推荐 + * + * author:wh + * @param $param 以表单方式提交数据 + * @return array|bool|int|mixed|string + * @throws ApiException + * @throws \think\Exception + */ + function doPost($param){ + + Tools::log_to_write_txt(['API OPERATE, IN',$this->post_url,$param], $this->getLogFileName()); + $res = Tools::curl_post($this->post_url, $param); + Tools::log_to_write_txt(['API OPERATE, OUT',$this->post_url, $res], $this->getLogFileName()); + + if(empty($res['data'])) return $res; + + $data = json_decode($res['data'], true); + if(empty($data['code'])) { + Tools::log_to_write_txt([ + 'error: api操作,错误', + 'post_url'=>$this->post_url, + 'error_info'=>ApiException::API_RESPONSE_FORMAT_ERROR, + 'error_sit'=>$this->getErrorSit(), + 'result'=>$res, + ], $this->getLogFileName()); + throw new \Exception(ApiException::API_RESPONSE_FORMAT_ERROR); + } + return $data; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/api/BaseLibApi.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/api/BaseLibApi.php new file mode 100644 index 0000000..739b4b3 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/api/BaseLibApi.php @@ -0,0 +1,54 @@ +isPost()){ + return Tools::set_fail('错误'); + } + Tools::log_to_write_txt(['登录入参:'=>input()]); + $input_data = input(); + if(empty($input_data)){ + return Tools::set_fail('请求参数错误'); + } + if(empty($input_data['username'])){ + return Tools::set_fail('用户名或密码错误'); + } + if(empty($input_data['password'])){ + return Tools::set_fail('用户名或密码错误.'); + } + // start 这段代码可放在请求合法性里面校验 Tools::signature + if(empty($input_data['timestamp'])){ + return Tools::set_fail('请求错误..'); + } + if(empty($input_data['noncestr'])){ + return Tools::set_fail('请求错误...'); + } + if(empty($input_data['sign'])){ + return Tools::set_fail('请求错误。'); + } + + $sign = $input_data['sign'];//md5后的字符串 + unset($input_data['sign']); + //验签 + $signstr = Tools::signature($input_data); + //Tools::log_to_write_txt([ + // '签名日志:', + // $signstr, + // $sign, + // 'input'=>input(), + //]); + if($signstr != $sign){ + return Tools::set_fail('非法请求'); + } + + //请求时间超过有效期 N分钟内有效 + if(time()-5*60 > strtotime($input_data['timestamp'])){ + return Tools::set_fail('请求失效'); + } + // end 这段代码可放在请求合法性里面校验 Tools::signature + + $username = $input_data['username']; + $user = Db::table($this->user_table)->where('username',$username)->find(); + if(empty($user)){ + return Tools::set_fail('账号密码错误!'); + } + //校验密码 + if($user['password'] != $input_data['password']){ + return Tools::set_fail('账号密码错误!!'); + } + + $expires = time()+12*60*60; + + + + //返回票据 + $ticketstr = md5($user['username'].$user['expires']); + //保存ticket + $user[$ticketstr] = $expires;//N秒后过期 + //修改有效期 + Db::table($this->user_table) + ->data([ + 'ticket'=>$ticketstr, + 'expires'=>7*86400+time(),//7天 + ]) + ->where('username',$username) + ->update(); + session('api_user',$user);//备用(跨环境情况下session不生效) + + return Tools::set_ok('登录成功',['ticket'=>$ticketstr]); + } + + /** + * desc:验证登录,根据提交的ticket来校验是否登录,适用于跨环境登录 + * + * author:wh + * @return bool + */ + function isLogin(){ + + $ticket = input('ticket'); + if(empty($ticket)){ + Tools::log_to_write_txt(['title'=>'业务ticket字段不存在','input'=>input()]); + return false; + } + $user = Db::table(TabConf::$fa_users) + ->where('ticket',$ticket) + ->find(); + if(empty($user)){ + Tools::log_to_write_txt(['title'=>'用户未登录']); + return false; + } + //无效票据 + //if(empty($user[$ticket])){ + // Tools::log_to_write_txt(['title'=>'未获取到用户ticket',$ticket=>$user]); + // + // return false; + //} + //已过期 + if(time() > $user['expires']){ + Tools::log_to_write_txt(['title'=>'ticket已过期',$ticket=>$user]); + return false; + } + return true; + } + /** + * desc:用户注册 + * + * 此模块通用性不高,按需使用 + * + * author:wh + */ + function reg($input_data){ + try { + if(empty($input_data['username'])){ + return Tools::set_fail('用户名错误'); + } + if(empty($input_data['password'])){ + return Tools::set_fail('密码错误.'); + } + if(empty($input_data['password2'])){ + return Tools::set_fail('密码错误!'); + } + if($input_data['password'] != $input_data['password2']){ + return Tools::set_fail('密码不一致!'); + } + $insert_data = [ + 'username'=>$input_data['username'], + 'password'=>$input_data['password'], + ]; + if(!empty($input_data['phone'])){ + $insert_data['phone'] = $input_data['phone']; + } + Db::table($this->user_table) + ->data($insert_data) + ->insert(); + return Tools::set_ok(); + }catch (\Exception $e){ + Tools::error_txt_log($e); + return Tools::set_fail(); + } + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/apk/Apk.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/apk/Apk.php new file mode 100644 index 0000000..5da7d81 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/apk/Apk.php @@ -0,0 +1,79 @@ +'',// 应用名称 + 'package'=>'',// 应用包名 + 'version_name'=>'',// 版本名称 + 'version_code'=>'',// 版本代码 + ]; + + dump($targetFile);die; + if(!is_file($targetFile)){ + return $result; + } + $appObj->open($targetFile);//$res + + $result = [ + 'app_name'=>$appObj->getAppName(),// 应用名称 + 'package'=>$appObj->getPackage(),// 应用包名 + 'version_name'=>$appObj->getVersionName(),// 版本名称 + 'version_code'=>$appObj->getVersionCode(),// 版本代码 + ]; + return $result; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/apk/ApkParser.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/apk/ApkParser.php new file mode 100644 index 0000000..a3f0cfa --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/apk/ApkParser.php @@ -0,0 +1,421 @@ +open($apk_file) === TRUE) { + $xml = $zip->getFromName($xml_file); + $zip->close(); + if ($xml){ + try { + return $this->parseString($xml); + }catch (Exception $e){ + } + } + } + return false; + } + + public function parseString($xml){ + $this->xml = $xml; + $this->length = strlen($xml); + + $this->root = $this->parseBlock(self::AXML_FILE); + return true; + } + + public function getXML($node=NULL, $lv=-1){ + if ($lv == -1) $node = $this->root; + if (!$node) return ''; + + if ($node['type'] == self::END_TAG) $lv--; + $xml = @($node['line'] == 0 || $node['line'] == $this->line) ? '' : "\n".str_repeat(' ', $lv); + $xml .= $node['tag']; + $this->line = @$node['line']; + foreach ($node['child'] as $c){ + $xml .= $this->getXML($c, $lv+1); + } + return $xml; + } + + public function getPackage(){ + return $this->getAttribute('manifest', 'package'); + } + + public function getVersionName(){ + return $this->getAttribute('manifest', 'android:versionName'); + } + + public function getVersionCode(){ + return $this->getAttribute('manifest', 'android:versionCode'); + } + + public function getAppName(){ + return $this->getAttribute('manifest/application', 'android:name'); + } + + public function getMainActivity(){ + for ($id=0; true; $id++){ + $act = $this->getAttribute("manifest/application/activity[{$id}]/intent-filter/action", 'android:name'); + if (!$act) break; + if ($act == 'android.intent.action.MAIN') return $this->getActivity($id); + } + return NULL; + } + + public function getActivity($idx=0){ + $idx = intval($idx); + return $this->getAttribute("manifest/application/activity[{$idx}]", 'android:name'); + } + + public function getAttribute($path, $name){ + $r = $this->getElement($path); + if (is_null($r)) return NULL; + + if (isset($r['attrs'])){ + foreach ($r['attrs'] as $a){ + if ($a['ns_name'] == $name) return $this->getAttributeValue($a); + } + } + return NULL; + } + + //---------------------- + // 类型常量定义 + //---------------------- + const AXML_FILE = 0x00080003; + const STRING_BLOCK = 0x001C0001; + const RESOURCEIDS = 0x00080180; + const START_NAMESPACE = 0x00100100; + const END_NAMESPACE = 0x00100101; + const START_TAG = 0x00100102; + const END_TAG = 0x00100103; + const TEXT = 0x00100104; + + const TYPE_NULL =0; + const TYPE_REFERENCE =1; + const TYPE_ATTRIBUTE =2; + const TYPE_STRING =3; + const TYPE_FLOAT =4; + const TYPE_DIMENSION =5; + const TYPE_FRACTION =6; + const TYPE_INT_DEC =16; + const TYPE_INT_HEX =17; + const TYPE_INT_BOOLEAN =18; + const TYPE_INT_COLOR_ARGB8 =28; + const TYPE_INT_COLOR_RGB8 =29; + const TYPE_INT_COLOR_ARGB4 =30; + const TYPE_INT_COLOR_RGB4 =31; + + const UNIT_MASK = 15; + private static $RADIX_MULTS = array(0.00390625, 3.051758E-005, 1.192093E-007, 4.656613E-010); + private static $DIMENSION_UNITS = array("px","dip","sp","pt","in","mm","",""); + private static $FRACTION_UNITS = array("%","%p","","","","","",""); + + private $xml=''; + private $length = 0; + private $stringCount = 0; + private $styleCount = 0; + private $stringTab = array(); + private $styleTab = array(); + private $resourceIDs = array(); + private $ns = array(); + private $cur_ns = NULL; + private $root = NULL; + private $line = 0; + + //---------------------- + // 内部私有函数 + //---------------------- + private function getElement($path){ + if (!$this->root) return NULL; + $ps = explode('/', $path); + $r = $this->root; + foreach ($ps as $v){ + if (preg_match('/([^\[]+)\[([0-9]+)\]$/', $v, $ms)){ + $v = $ms[1]; + $off = $ms[2]; + }else { + $off = 0; + } + foreach ($r['child'] as $c){ + if ($c['type'] == self::START_TAG && $c['ns_name'] == $v){ + if ($off == 0){ + $r = $c; continue 2; + }else { + $off--; + } + } + } + // 没有找到节点 + return NULL; + } + return $r; + } + + private function parseBlock($need = 0){ + $o = 0; + $type = $this->get32($o); + if ($need && $type != $need) throw new Exception('Block Type Error', 1); + $size = $this->get32($o); + if ($size < 8 || $size > $this->length) throw new Exception('Block Size Error', 2); + $left = $this->length - $size; + + $props = false; + switch ($type){ + case self::AXML_FILE: + $props = array( + 'line' => 0, + 'tag' => '' + ); + break; + case self::STRING_BLOCK: + $this->stringCount = $this->get32($o); + $this->styleCount = $this->get32($o); + $o += 4; + $strOffset = $this->get32($o); + $styOffset = $this->get32($o); + $strListOffset = $this->get32array($o, $this->stringCount); + $styListOffset = $this->get32array($o, $this->styleCount); + $this->stringTab = $this->stringCount > 0 ? $this->getStringTab($strOffset, $strListOffset) : array(); + $this->styleTab = $this->styleCount > 0 ? $this->getStringTab($styOffset, $styListOffset) : array(); + $o = $size; + break; + case self::RESOURCEIDS: + $count = $size / 4 - 2; + $this->resourceIDs = $this->get32array($o, $count); + break; + case self::START_NAMESPACE: + $o += 8; + $prefix = $this->get32($o); + $uri = $this->get32($o); + + if (empty($this->cur_ns)){ + $this->cur_ns = array(); + $this->ns[] = &$this->cur_ns; + } + $this->cur_ns[$uri] = $prefix; + break; + case self::END_NAMESPACE: + $o += 8; + $prefix = $this->get32($o); + $uri = $this->get32($o); + + if (empty($this->cur_ns)) break; + unset($this->cur_ns[$uri]); + break; + case self::START_TAG: + $line = $this->get32($o); + + $o += 4; + $attrs = array(); + $props = array( + 'line' => $line, + 'ns' => $this->getNameSpace($this->get32($o)), + 'name' => $this->getString($this->get32($o)), + 'flag' => $this->get32($o), + 'count' => $this->get16($o), + 'id' => $this->get16($o)-1, + 'class' => $this->get16($o)-1, + 'style' => $this->get16($o)-1, + 'attrs' => &$attrs + ); + $props['ns_name'] = $props['ns'].$props['name']; + for ($i=0; $i < $props['count']; $i++){ + $a = array( + 'ns' => $this->getNameSpace($this->get32($o)), + 'name' => $this->getString($this->get32($o)), + 'val_str' => $this->get32($o), + 'val_type' => $this->get32($o), + 'val_data' => $this->get32($o) + ); + $a['ns_name'] = $a['ns'].$a['name']; + $a['val_type'] >>= 24; + $attrs[] = $a; + } + // 处理TAG字符串 + $tag = "<{$props['ns_name']}"; + foreach ($this->cur_ns as $uri => $prefix){ + $uri = $this->getString($uri); + $prefix = $this->getString($prefix); + $tag .= " xmlns:{$prefix}=\"{$uri}\""; + } + foreach ($props['attrs'] as $a){ + $tag .= " {$a['ns_name']}=\"". + $this->getAttributeValue($a). + '"'; + } + $tag .= '>'; + $props['tag'] = $tag; + + unset($this->cur_ns); + $this->cur_ns = array(); + $this->ns[] = &$this->cur_ns; + $left = -1; + break; + case self::END_TAG: + $line = $this->get32($o); + $o += 4; + $props = array( + 'line' => $line, + 'ns' => $this->getNameSpace($this->get32($o)), + 'name' => $this->getString($this->get32($o)) + ); + $props['ns_name'] = $props['ns'].$props['name']; + $props['tag'] = ""; + if (count($this->ns) > 1){ + array_pop($this->ns); + unset($this->cur_ns); + $this->cur_ns = array_pop($this->ns); + $this->ns[] = &$this->cur_ns; + } + break; + case self::TEXT: + $o += 8; + $props = array( + 'tag' => $this->getString($this->get32($o)) + ); + $o += 8; + break; + default: + throw new Exception('Block Type Error', 3); + break; + } + + $this->skip($o); + $child = array(); + while ($this->length > $left){ + $c = $this->parseBlock(); + if ($props && $c) $child[] = $c; + if ($left == -1 && $c['type'] == self::END_TAG){ + $left = $this->length; + break; + } + } + if ($this->length != $left) throw new Exception('Block Overflow Error', 4); + if ($props){ + $props['type'] = $type; + $props['size'] = $size; + $props['child'] = $child; + return $props; + }else { + return false; + } + } + + private function getAttributeValue($a){ + $type = &$a['val_type']; + $data = &$a['val_data']; + switch ($type){ + case self::TYPE_STRING: + return $this->getString($a['val_str']); + case self::TYPE_ATTRIBUTE: + return sprintf('?%s%08X', self::_getPackage($data), $data); + case self::TYPE_REFERENCE: + return sprintf('@%s%08X', self::_getPackage($data), $data); + case self::TYPE_INT_HEX: + return sprintf('0x%08X', $data); + case self::TYPE_INT_BOOLEAN: + return ($data != 0 ? 'true' : 'false'); + case self::TYPE_INT_COLOR_ARGB8: + case self::TYPE_INT_COLOR_RGB8: + case self::TYPE_INT_COLOR_ARGB4: + case self::TYPE_INT_COLOR_RGB4: + return sprintf('#%08X', $data); + case self::TYPE_DIMENSION: + return $this->_complexToFloat($data).self::$DIMENSION_UNITS[$data & self::UNIT_MASK]; + case self::TYPE_FRACTION: + return $this->_complexToFloat($data).self::$FRACTION_UNITS[$data & self::UNIT_MASK]; + case self::TYPE_FLOAT: + return $this->_int2float($data); + } + if ($type >=self::TYPE_INT_DEC && $type < self::TYPE_INT_COLOR_ARGB8){ + return (string)$data; + } + return sprintf('<0x%X, type 0x%02X>', $data, $type); + } + + private function _complexToFloat($data){ + return (float)($data & 0xFFFFFF00) * self::$RADIX_MULTS[($data>>4) & 3]; + } + private function _int2float($v) { + $x = ($v & ((1 << 23) - 1)) + (1 << 23) * ($v >> 31 | 1); + $exp = ($v >> 23 & 0xFF) - 127; + return $x * pow(2, $exp - 23); + } + private static function _getPackage($data){ + return ($data >> 24 == 1) ? 'android:' : ''; + } + + private function getStringTab($base, $list){ + $tab = array(); + foreach ($list as $off){ + $off += $base; + $len = $this->get16($off); + $mask = ($len >> 0x8) & 0xFF; + $len = $len & 0xFF; + if ($len == $mask){ + if ($off + $len > $this->length) throw new Exception('String Table Overflow', 11); + $tab[] = substr($this->xml, $off, $len); + }else { + if ($off + $len * 2 > $this->length) throw new Exception('String Table Overflow', 11); + $str = substr($this->xml, $off, $len * 2); + $tab[] = mb_convert_encoding($str, 'UTF-8', 'UCS-2LE'); + } + } + return $tab; + } + private function getString($id){ + if ($id > -1 && $id < $this->stringCount){ + return $this->stringTab[$id]; + }else { + return ''; + } + } + private function getNameSpace($uri){ + for ($i=count($this->ns); $i > 0; ){ + $ns = $this->ns[--$i]; + if (isset($ns[$uri])){ + $ns = $this->getString($ns[$uri]); + if (!empty($ns)) $ns .= ':'; + return $ns; + } + } + return ''; + } + private function get32(&$off){ + $int = unpack('V', substr($this->xml, $off, 4)); + $off += 4; + return array_shift($int); + } + private function get32array(&$off, $size){ + if ($size <= 0) return NULL; + $arr = unpack('V*', substr($this->xml, $off, 4 * $size)); + if (count($arr) != $size) throw new Exception('Array Size Error', 10); + $off += 4 * $size; + return $arr; + } + private function get16(&$off){ + $int = unpack('v', substr($this->xml, $off, 2)); + $off += 2; + return array_shift($int); + } + private function skip($size){ + $this->xml = substr($this->xml, $size); + $this->length -= $size; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/coin/Coin.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/coin/Coin.php new file mode 100644 index 0000000..0c8cf2b --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/coin/Coin.php @@ -0,0 +1,103 @@ +'1000', + 'sort'=>'market_cap', + 'sort_dir'=>'desc', + * @param array $params + * @return array|bool|int|string + */ + function getAll(array $params){ + $params_str = http_build_query($params); + $url = $this->url.'?'.$params_str; + + $header = [ + 'X-CMC_PRO_API_KEY:'.$this->apiKey, + 'accept: application/json', + ]; + + return Curl::curl_get($url,10,$header); + } + + + /** + * desc: + * + *https://coinmarketcap.com/api/documentation/v1/#section/Introduction + * 'https://pro-api.coinmarketcap.com/v2/cryptocurrency/ohlcv/historical' + * author:wh + */ + function k(){ + $url = $this->url; + $time_start = input('time_start'); + $time_end = input('time_end'); + $type = input('type'); + + $header = [ + 'X-CMC_PRO_API_KEY:'.$this->apiKey, + ]; + + $arr = explode('=',$type); + $params = [ + $arr[0]=>$arr[1],//币种 + 'time_start'=>$time_start, + 'time_end'=>$time_end, + 'count'=>7, + ]; + + $url = $url.'?'.http_build_query($params); + + return Curl::curl_get($url,10,$header); + } + + private function curl_request($url, $method = 'GET',$data=null,$header=array(),$call_back=null) + { + set_time_limit(30); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + if($header){ + curl_setopt($ch, CURLOPT_HTTPHEADER, $header); + } + if($method == 'POST'){ + if($data) curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + } + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + if($call_back){ + curl_setopt($ch, CURLOPT_WRITEFUNCTION, $call_back); + } + $result = curl_exec($ch); + if (curl_errno($ch)) { + return [ + 'status' => 'error', + 'message' => 'curl 错误信息: ' . curl_error($ch) + ]; + } + curl_close($ch); + return $result; + } + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/db/DbCacheUtility.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/db/DbCacheUtility.php new file mode 100644 index 0000000..34d4c5f --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/db/DbCacheUtility.php @@ -0,0 +1,390 @@ +table)->whereIn("id",$ids)->where(["is_deleted"=>0]); + return DbCacheUtility::getAll($obj); + } + * Class DbCacheUtility + * @package libraries + */ +class DbCacheUtility +{ + const NoCacheTime = 0; + const LowCacheTime = 60 * 1; + const NormalCacheTime = 60 * 10; + const HighCacheTime = 60 * 60; + const LongCacheTime = 60 * 60 * 24; + + /** + * desc: + * author:wh + * @param $object 查询对象 + * @param int $cacheDuration + * @param bool $is_log + * @return mixed + */ + public static function getOne($object, $cacheDuration = self::NoCacheTime, $is_log = true) + { + $begin_time = Tools::getMillisecond(); + + $is_cache = 0; + + $last_sql = $object->fetchSql(true)->find(); + + $md5_name = md5($last_sql); + + $key = 'get_one:' . $md5_name; + + if ($cacheDuration == 0) { + if(cache($key)){ + cache($key, null); + } + } + + if (!(cache($key))) { + $result = $object->fetchSql(false)->find(); + if ($cacheDuration != 0) { + cache($key, $result, $cacheDuration); + } + } else { + $is_cache = 1; + $result = cache($key); + } + + $end_time = Tools::getMillisecond(); + + if($is_log){ + SqlOperateLog::add($last_sql, $end_time, $begin_time, $md5_name, 'get_one', $is_cache); + } + + return $result; + } + + /** + * desc: + * author:wh + * @param $object 查询对象 + * @param int $cacheDuration + * @param bool $is_log + * @return mixed + */ + public static function getAll($object, $cacheDuration = self::NoCacheTime, $is_log = true) + { + + $begin_time = Tools::getMillisecond(); + + $is_cache = 0; + + $last_sql = $object->fetchSql(true)->select(); + + $md5_name = md5($last_sql); + + $key = 'get_all:' . $md5_name; + + if ($cacheDuration == 0) { + if(cache($key)){ + cache($key, null); + } + } + + if (!(cache($key))) { + $result = $object->fetchSql(false)->select(); + if ($cacheDuration != 0) { + cache($key, $result, $cacheDuration); + } + } else { + $is_cache = 1; + $result = cache($key); + } + + $end_time = Tools::getMillisecond(); + + if($is_log){ + SqlOperateLog::add($last_sql, $end_time, $begin_time, $md5_name, 'get_all', $is_cache); + } + return $result; + } + + /** + * desc: + * author:wh + * @param $object + * @param int $page + * @param int $listRows + * @param bool $simple + * @param array $config + * @param int $cacheDuration + * @param bool $is_log + * @return mixed + */ + static function paginates($object, $page=1, $listRows = null, $simple = false, $config = [], $cacheDuration = self::NoCacheTime, $is_log = true){ + + $begin_time = Tools::getMillisecond(); + + $is_cache = 0; + + $last_sql = $object->limit(abs(1*$page-1) * $listRows, $listRows)->fetchSql(true)->select(); + + $md5_name = md5($last_sql); + + $key = 'paginates:' . $md5_name; + + if ($cacheDuration == 0) { + if(cache($key)){ + cache($key, null); + } + } + + if (!(cache($key))) { + $result = $object->fetchSql(false)->paginate($listRows, $simple, $config); + if ($cacheDuration != 0) { + cache($key, $result, $cacheDuration); + } + } else { + $is_cache = 1; + $result = cache($key); + } + + $end_time = Tools::getMillisecond(); + + if($is_log){ + SqlOperateLog::add($last_sql, $end_time, $begin_time, $md5_name, 'paginates', $is_cache); + } + return $result; + } + + /** + * desc: + * author:wh + * @param $sql + * @param array $bind + * @return mixed + * @throws \think\db\exception\BindParamException + * @throws \think\exception\PDOException + */ + static function queryScalar($sql, $bind = [], $field, $cacheDuration = self::NoCacheTime, $is_log = true) + { + $begin_time = Tools::getMillisecond(); + + $is_cache = 0; + + $last_sql = self::presetBind($sql, $bind); + + $md5_name = md5($last_sql); + + $key = 'query_scalar:' . $md5_name; + + if ($cacheDuration == 0) { + if(cache($key)){ + cache($key, null); + } + } + + if (!(cache($key))) { + $result = Db::query($sql, $bind); + if ($cacheDuration != 0) { + cache($key, $result, $cacheDuration); + } + } else { + $is_cache = 1; + $result = cache($key); + } + + $end_time = Tools::getMillisecond(); + + if($is_log){ + SqlOperateLog::add($last_sql, $end_time, $begin_time, $md5_name, 'query_scalar', $is_cache); + } + + return $result ? $result[0][$field] : []; + } + + /** + * desc: + * author:wh + * @param $sql + * @param array $bind + * @return mixed + * @throws \think\db\exception\BindParamException + * @throws \think\exception\PDOException + */ + static function queryOne($sql, $bind = [], $cacheDuration = self::NoCacheTime, $is_log = true) + { + $begin_time = Tools::getMillisecond(); + + $is_cache = 0; + + $last_sql = self::presetBind($sql, $bind); + + $md5_name = md5($last_sql); + + $key = 'query_one:' . $md5_name; + + if ($cacheDuration == 0) { + if(cache($key)){ + cache($key, null); + } + } + + if (!(cache($key))) { + $result = Db::query($sql, $bind); + if ($cacheDuration != 0) { + cache($key, $result, $cacheDuration); + } + } else { + $is_cache = 1; + $result = cache($key); + } + + $end_time = Tools::getMillisecond(); + + if($is_log){ + SqlOperateLog::add($last_sql, $end_time, $begin_time, $md5_name, 'query_one', $is_cache); + } + + return $result ? $result[0] : []; + } + /** + * desc: + * author:wh + * @param $sql + * @param array $bind + * @return mixed + * @throws \think\db\exception\BindParamException + * @throws \think\exception\PDOException + */ + static function queryAll($sql, $bind = [], $cacheDuration = self::NoCacheTime, $is_log = true) + { + $begin_time = Tools::getMillisecond(); + + $is_cache = 0; + + $last_sql = self::presetBind($sql, $bind); +//echo $last_sql;die; + $md5_name = md5($last_sql); + + $key = 'query_all:' . $md5_name; + + if ($cacheDuration == 0) { + if(cache($key)){ + cache($key, null); + } + } + + if (!(cache($key))) { + $result = Db::query($sql, $bind); + if ($cacheDuration != 0) { + cache($key, $result, $cacheDuration); + } + } else { + $is_cache = 1; + $result = cache($key); + } + + $end_time = Tools::getMillisecond(); + + if($is_log){ + SqlOperateLog::add($last_sql, $end_time, $begin_time, $md5_name, 'query_all', $is_cache); + } + + return $result; + } + + /** + * desc: + * author:wh + * @param $sql + * @param array $bind + * @return int + * @throws \think\db\exception\BindParamException + * @throws \think\exception\PDOException + */ + static function execute($sql, $bind = []){ + return Db::execute($sql, $bind); + } + + /** + * desc: + * author:wh + * @param $sql + * @param array $bind + * @return mixed + */ + static function presetBind($sql, $bind = []){ + if($bind){ + foreach ($bind as $key=>$val){ + $sql = str_replace(':'.$key, $val, $sql); + } + } + return $sql; + } + + static function paginate($sql, $bind, $total, $listRows = null, $simple = false, $config = []) + { + //if (is_int($simple)) { + // $total = $simple; + // $simple = false; + //} + + $paginate = Container::get('config')->pull('paginate'); + + if (is_array($listRows)) { + $config = array_merge($paginate, $listRows); + $listRows = $config['list_rows']; + } else { + $config = array_merge($paginate, $config); + $listRows = $listRows ?: $config['list_rows']; + } + + /** @var Paginator $class */ + $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']); + $page = isset($config['page']) ? (int) $config['page'] : call_user_func([ + $class, + 'getCurrentPage', + ], $config['var_page']); + + $page = $page < 1 ? 1 : $page; + + $config['path'] = isset($config['path']) ? $config['path'] : call_user_func([$class, 'getCurrentPath']); + + //if (!isset($total) && !$simple) { + // + // + // unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']); + // + // $bind = $this->bind; + // $total = $this->count(); + // $results = $this->options($options)->bind($bind)->page($page, $listRows)->select(); + //} elseif ($simple) { + // $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select(); + // $total = null; + //} else { + // $results = $this->page($page, $listRows)->select(); + //} + + $results = DbCacheUtility::queryAll($sql, $bind); + + return $class::make($results, $listRows, $page, $total, $simple, $config); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/db/RedisUtility.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/db/RedisUtility.php new file mode 100644 index 0000000..b8f4125 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/db/RedisUtility.php @@ -0,0 +1,109 @@ +handler(); + } + return self::$redisHandle; + } + /** + * desc:设置 0 永久有效 + * author:wh + * @param $key_name 键名 + * @param $value 值 json数据 + * @return bool + */ + static function set($key_name, $value){ + return Cache::store('redis')->set($key_name, json_encode($value, JSON_UNESCAPED_UNICODE)); + } + + /** + * desc:重置 + * author:wh + * @param $key_name + * @return bool + */ + static function reset($key_name){ + return Cache::store('redis')->set($key_name, null); + } + + /** + * desc:设置过期时间 + * author:wh + * @param $key_name 键名 + * @param $value 值 json数据 + * @param int $expire 过期时间 0 永久有效 + * @return bool + */ + static function setExpire($key_name, $value, $expire=0){ + return Cache::store('redis')->set($key_name, json_encode($value), $expire); + } + + /** + * desc:存储hash类型 + * author:wh + * @param $key + * @param $field + * @param $value + */ + static function hSet($key, $field, $value){ + return self::redisObject()->hSet($key, $field, json_encode($value, JSON_UNESCAPED_UNICODE)); + } + + /** + * desc:删除指定的键。 + * author:wh + * @param $key + * @return mixed + */ + static function delete($key){ + return self::redisObject()->delete($key); + } + + /** + * desc:从存储在键上的哈希中移除一个值。 + * 如果哈希表不存在,或者键不存在,则返回 FALSE 。 + * author:wh + * @param $key + * @param $hashKey1 + * @return mixed + */ + static function hDel($key, $hashKey1){ + return self::redisObject()->hDel($key, $hashKey1); + } + + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/db/SqlOperateLog.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/db/SqlOperateLog.php new file mode 100644 index 0000000..8bce713 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/db/SqlOperateLog.php @@ -0,0 +1,55 @@ +request()->baseUrl(), + 'duration_time'=>$end_time - $begin_time, + 'type'=>$type, + 'is_cache'=>$is_cache?1:0, + 'sql'=>$sql, + 'md5_name'=>$md5_name, + ]; + try{ + if(config('app.is_sql_slow_log') && ($end_time - $begin_time) >= config('app.is_sql_slow_log')){ + Db::table('log_sql_operate')->insert($data); + } + }catch (\Exception $e){ + //DATABASE BOOM + //tp6 + //Log::error('========[数据库异常:DATABASE BOOM]========'.$e->getTraceAsString()); + //Log::close(); + //tp5.1 + Log::write('========[DATABASE ERROR:DATABASE BOOM]========【'.$e->getMessage().'】'.$e->getTraceAsString()); + } + } + + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/db/es/Elasticsearch.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/db/es/Elasticsearch.php new file mode 100644 index 0000000..592c5ea --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/db/es/Elasticsearch.php @@ -0,0 +1,262 @@ +ip = false===strpos($ip_port,'http')?'http://'.$ip_port:$ip_port; + $this->index_sign = $index_sign; + } + + /** + * desc:设置查询方法,默认_search + * author:wh + * @param $method_name + */ + function setQueryMethod($method_name){ + $this->query_method = $method_name; + } + + + /** + * desc:根据日期索引查询文档 + * 注:默认一个月 + * + * eg:'qa-stat-2021.01'; + * + * 注:如果es设置的索引没有规律也不是日期作为索引,请设置普通索引setIndexNormal,且请在初始化时,设置索引前缀index_sign为""字符串 + * author:wh + */ + function setIndexDefault(){ + $this->setRequestIndex(date('Y'.$this->doc_date_sign.'m')); + } + + /** + * desc:设置要查询的日期文档索引 + * + * 1、按年检索文档[建议5年左右] + * + * eg: qa-stat-2018.*,qa-stat-2019.*,qa-stat-2020.*,qa-stat-2021.* + * + * 注意:当索引过长,es会抛出索引太长异常 + * + * 注:如果es设置的索引没有规律也不是日期作为索引,请设置普通索引setIndexNormal,且请在初始化时,设置索引前缀index_sign为""字符串 + * + * author:wh + * @param string $start_time 开始时间 eg:2010-01-01 08:05:55 + * @param string $end_time 结束时间 eg:2021-12-31 12:05:00 + * @return string + */ + function setIndexYearDate(string $start_time, string $end_time){ + + //默认查询索引 + $index = date('Y'.$this->doc_date_sign.'*', strtotime($start_time)); + + $sign = ',';//分隔符 + + $m = date('Y', strtotime($end_time)) - date('Y', strtotime($start_time)); + + //拼装查询索引 + for ($i=0; $i<$m; $i++){ + + $index .= $sign.($this->index_sign.(date('Y', strtotime($start_time))+$i).$this->doc_date_sign.'*'); + } + + $this->setRequestIndex($index); + } + + /** + * desc:设置日期索引 + * + * 检索全部日期索引文档 + * + * 例如es设置的索引为: + * qa-stat-2021.01 + qa-stat-2021.02 + qa-stat-2021.03 + qa-stat-2021.04 + * + * 实际查询自动设置为:qa-stat-* + * + * 注:如果es设置的索引没有规律也不是日期作为索引,请设置普通索引setIndexNormal,且请在初始化时,设置索引前缀index_sign为""字符串 + * + * author:wh + * @return string + */ + function setIndexAllDate(){ + + $this->setRequestIndex('*'); + } + + /** + * desc:设置普通索引 + * + * 调用此方法,请在初始化时,设置索引前缀index_sign为""空字符串 + * + * author:wh + * @param string $index + */ + function setIndexNormal(string $index){ + $this->setRequestIndex($index); + } + + /** + * desc:设置跨月份索引 + * + * eg: qa-stat-2021.01,qa-stat-2021.02 + * + * 注:如果es设置的索引没有规律也不是日期作为索引,请设置普通索引setIndexNormal,且请在初始化时,设置索引前缀index_sign为""字符串 + * + * author:wh + * @param string $start_time eg:'2020-11' + * @param string $end_time eg:'2021-01' + * @return string eg: qa-stat-2020.11,qa-stat-2020.12,qa-stat-2021.01 + */ + function setIndexDate(string $start_time, string $end_time){ + $date = new Date(); + + //计算月份 + $m = $date->dateCutMonth($start_time, $end_time); + ////解决跨年且不足1个月时,索引设置错误问题 + //if($m == 0 && (date('Y', strtotime($end_time)) > date('Y', strtotime($start_time)))){ + // $m = 1; + //} + //格式 + $date->date_format = 'Y-m'; + + //默认查询索引 + $index = date('Y'.$this->doc_date_sign.'m', strtotime($start_time)); + + $tmp_time = $start_time; + $sign = ',';//分隔符 + + //拼装查询索引 + for ($i=0; $i<$m; $i++){ + $tmp_time = $date->addTime(1, 'M', strtotime($tmp_time)); + $index .= $sign.$this->index_sign.(date('Y'.$this->doc_date_sign.'m', strtotime($tmp_time))); + } + + $this->setRequestIndex($index); + } + + + /** + * desc:设置查询参数 + * author:wh + * @param string $query_param_json + */ + function setQueryParam(string $query_param_json){ + $this->query_param = $query_param_json; + $this->post_url = $this->ip.'/'.$this->index.'/'.$this->query_method; + } + + /** + * desc:获取查询参数,用于调试 + * author:wh + * @return array + */ + function getQueryParam(){ + return [ + 'ip'=>$this->ip, + 'index_sign'=>$this->index_sign, + 'index'=>$this->index, + 'query_method'=>$this->query_method, + 'post_url'=>$this->post_url, + 'query_param'=>$this->query_param, + ]; + } + + /** + * desc:执行es查询 + * author:wh + * @return array|bool|int|string + * @throws \Exception + */ + function execute(){ + if(empty($this->ip)) throw new \Exception('请设置ip'); + //if(empty($this->index_sign)) throw new \Exception('请设置索引前缀'); + if(empty($this->index)) throw new \Exception('请设置索引'); + if(empty($this->query_method)) throw new \Exception('请设置查询方法'); + if(empty($this->query_param)) throw new \Exception('请设置查询参数'); + + + + + Tools::log_to_write_txt(['exe_func'=>$this->exe_func,'request_url'=>$this->post_url,'IN'=>" | IN: ",'query_params'=>$this->query_param, 'input'=>input()], $this->es_log_file); + $result = Tools::curl_post($this->post_url, $this->query_param); + Tools::log_to_write_txt(['exe_func'=>$this->exe_func,'OUT'=>" | OUT: ", 'result'=>$result], $this->es_log_file); + + return $result; + } + + /** + * desc:设置是哪个方法调用本es库,用于日志记录 + * author:wh + * @param string $function + */ + function setExecuteFunction(string $function){ + $this->exe_func = $function; + } + + /** + * desc:设置请求文档索引 + * author:wh + * @param string $index + */ + protected function setRequestIndex(string $index){ + $this->index = $this->index_sign.$index; + } + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/db/es/README.md b/superadmin/vendor/wanghua/general-utility-tools-php/src/db/es/README.md new file mode 100644 index 0000000..ea56d61 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/db/es/README.md @@ -0,0 +1,1340 @@ +##### Elasticsearch数据库操作类库使用教程 + +#### 一、Elasticsearch原生cUrl查询库 +##### 说明:该库只提供查询请求,开发者只需要关注查询参数怎么写,请求之后会把结果按原样返回,不会有任何修改。 + +**** + +###### 【查询参数案例:查询一条数据】 +###### 只需要简单的查询条件即可;from=0,size=1表示从第0条数据查询1条,from可省略,等于mysql的limit 1。 +###### 这里unixtime和statFunc为数据结构(表)中的字段名称,gt大于,lt小于,gte大于等于,lte小于等于 +###### 这里register是字段statFunc的值,而unixtime存的是毫秒时间戳 +###### 注:数据字段尽量不存小数,小数太小的时候,在内存中计算时会出现误差,比如0.001+0.001=0 + +#### 代码使用示例: +``` + +//======【有规律的日期索引】【特殊】======= +$query_params = "{ + "query": { + "bool": { + "must": [ + { + "range": { + "unixtime": { + "gt": 1623254400000, + "lt": 2623340800000 + } + } + }, + { + "terms": { + "statFunc": [ + "signup", + "returncard" + ] + } + }, + { + "term": { + "matchTypeNum": 3 + } + } + ] + } + }, + "size": 1, + "aggs": { + "group_by_day": { + "date_histogram": { + "field": "@timestamp", + "interval": "day", + "min_doc_count": 0, + "order": { + "_key": "asc" + }, + "time_zone": "+08:00", + "format": "yyyy-MM-dd" + }, + "aggs": { + "item1000_sum": { + "sum": { + "field": "item1000" + } + } + } + } + } + }"; +$es = new Elasticsearch('http://49.4.3.4:3201','qa-item-'); +$es->setIndexAllDate(); +$es->setQueryParam($query_params); +dump($es->execute()); + +dump('=================================================================='); + +//======【无规律的普通索引】【推荐】======= +$query_params = "{ + 'query': { + 'bool': { + 'must': [ + { + 'range': { + 'unixtime': { + 'gt': 1617206400000, + 'lt': 1619712000000 + } + } + }, + { + 'term': { + 'statFunc': 'register' + } + } + ] + } + }, + 'size': 1 + }"; +dump($query_params); +$es = new Elasticsearch('http://49.4.3.4:3201',''); +$es->setIndexNormal('qa-item-2021.06.*'); +$es->setQueryParam($query_params); +dump($es->execute()); + + +//以上都经过测试 + +``` + +#### 查询参数示例 +###### 示例1: 这里展示了must查询 +``` +{ + "query": { + "bool": { + "must": [ + { + "range": { + "unixtime": { + "gt": 1617206400000, + "lt": 1619712000000 + } + } + }, + { + "term": { + "statFunc": "register" + } + } + ] + } + }, + "size": 1 +} +``` +###### 示例2: 这里展示了混合查询 +###### must表示必须,等于mysql的and; +###### should表示应该,等于mysql的or; +###### 模糊查询用missing,等于mysql的like,但是不用%%通配符; +###### must_not表示必须不是,等于mysql的!=; +###### 它们可以一起使用。 +###### 仔细观察发现,query,bool基本是固定的,而里面的参数写法都差不多。 + +``` +{ + "query": { + "bool": { + "must": [ + { + "match": { + "statFunc": "signup" + } + } + ], + "must_not": [], + "should": [ + { + "term": { + "gameTagNum": "210" + } + }, + { + "term": { + "gameTagNum": "300" + } + } + ] + } + }, + "size": 1 +} +``` + +**** + +###### 【查询参数案例:查询多条数据】 +###### 只需要简单的查询条件即可,这里只改变了size字段值。 +###### eg: +``` +{ + "query": { + "bool": { + "must": [ + { + "range": { + "unixtime": { + "gt": 1617206400000, + "lt": 1619712000000 + } + } + }, + { + "term": { + "statFunc": "register" + } + } + ] + } + }, + "size": 10 +} +``` + +**** + +###### 【查询参数案例:简单聚合】 +###### 根据时间和某个字段查询数据,并且基于查询结果再次按时间分组,分组段按天分段,同时基于查询结果再次聚合某个字段 +###### 为了便于演示,这里查询条件改为容易理解的字段:"统计张三一段时间内已下单的订单总金额" +###### order_money_sum是自定义名称,等于mysql的别名 +###### eg: +``` +{ + "query": { + "bool": { + "must": [ + { + "range": { + "order_time": { + "gt": 1617206400000, + "lt": 1619712000000 + } + } + }, + { + "term": { + "name": "张三" + } + } + ] + } + }, + "size": 0, + "aggs": { + "order_money_sum": { + "sum": { + "field": "order_money" + } + } + } +} +``` + +###### 【查询参数案例:复杂聚合】 +###### aggs可以嵌套,嵌套时的含义表示基于上一个聚合结果再次聚合 +###### 这里表示先按day(天)分组,再统计字段名为item1000的字段,day可以改为月(month)年(year) +###### eg: +``` +{ + "query": { + "bool": { + "must": [ + { + "range": { + "unixtime": { + "gt": 1617206400000, + "lt": 1619712000000 + } + } + }, + { + "term": { + "statFunc": "register" + } + } + ] + } + }, + "size": 0, + "aggs": { + "group_by_day": { + "date_histogram": { + "field": "@timestamp", + "interval": "day", + "min_doc_count": 0, + "order": { + "_key": "desc" + }, + "time_zone": "+08:00", + "format": "yyyy-MM-dd" + }, + "aggs": { + "item1000_sum": { + "sum": { + "field": "item1000" + } + } + } + } + } +} +``` + + +**** + +#### 二、Elasticsearch助手库(不推荐,因为不灵活。掌握了一的查询参数,其它都是小问题) + +##### 说明: + +##### 通用es查询多条数据 +* (这一种方式可实现大部分列表查询功能) +* (where条件跟随业务需要修改) +* (多条件组合查询满足不同查询业务) + +#### 查询列表: +``` + //获取分页数据 + $offset = input('offset', 5); + $limit = input('limit', 5); + + + //获取数据 + $input_data = $_POST; + 初始化es + $es = new BaseElasticsearch(); + + + //(必须)判断时间查询设置索引 + if(!empty($input_data['sign_time'])){ + $start_time = explode(' - ', $input_data['sign_time'])[0]; + $end_time = explode(' - ', $input_data['sign_time'])[1]; + //这里动态生成索引 + $es->index = $es->setIndexDate($start_time, $end_time); + }else{ + //设置默认索引 + $es->index = $es->setDefaultDate();//默认一个月 + } + + + //设置查询的文档类型 + $es->type = 'logs';//本例是查询日志类型的数据 + + //分页计算得到当前页码(可根据自身情况设置) + $es->current_page = $offset/$limit; + $es->limit = $limit; + + + //关键步骤 + //设置查询条件 + $where = [ + [ + 'term' => [ + 'statType' => 'draw' + ] + ] + ]; + //可选 + if(!empty($input_data['gameid'])){ + $where[] = [ + 'term' => [ + 'uid' => $input_data['gameid'] + ] + ]; + } + //时间 + if(!empty($input_data['sign_time'])){ + //这里将字符串日期转换为unix时间戳(精确到毫秒) + $date_arr = $es->strtounixtime($input_data['sign_time']); + $where[] = [ + 'range' => [ + 'unixtime' => [ + 'gte' => $date_arr['start_time'], + 'lte' => $date_arr['end_time'], + ] + ] + ]; + } + //排序 + $es->order('unixtime', 'desc'); + + //设置bool查询模式 + $es->setQueryBool(); + //必须[setQueryBool & must、must_not、should方法必须在一起使用] + $es->must($where); + + + //执行查询 + $res = $es->query(); + dump($res); + +``` + +#### 查询一条数据(完全可以用查询列表功能,组合多条件筛选出一条数据): + +``` + //根据id查询一条数据 + //初始化es + $es = new BaseElasticsearch(); + + $es->index_sign = 'qa-item-';//设置index标识 + + $es->index = $es->setDefaultDate();//设置文档索引 + + $es->type = 'logs';//设置文档类型 + $res = $es->getById('AXbaw_CGbqvMMYZl-lOz'); + dump($res); +``` + + +#### 查询案例 + +``` +index = $es->setIndexDate($start_time, $end_time); + }else{ + $es->index = $es->setDefaultDate();//默认一个月 + } + $es->type = 'logs'; + $es->current_page = $limit ? intval($offset / $limit) + 1 : 1; + $es->limit = $limit; + //关键步骤 + //设置查询条件 + + //帐变类型:4=后台赠送,6首充奖励,7=报名,8=充值 + $change_type_arr = ['backsend'=>4,'firstprize'=>6,'signup'=>7,'recharge'=>8]; + //必须 + $where = [ + [ + 'range' => [ + $amount_field => ['gt' => -999999] + ] + ] + ]; + + //可选 + if(!empty($input_data['gameid'])){ + $where[] = [ + 'term' => [ + 'uid' => $input_data['gameid'] + ] + ]; + } + if(!empty($input_data['nickname'])){ + $where[] = [ + 'term' => [ + 'nickname' => $input_data['nickname'] + ] + ]; + } + //帐变类型 + if(!empty($input_data['change_type'])){ + $where[] = [ + 'term' => [ + 'statType' => array_flip($change_type_arr)[$input_data['change_type']] + ] + ]; + } + //帐变时间 + if(!empty($input_data['change_time'])){ + $date_arr = $es->strtounixtime($input_data['change_time']); + + $where[] = [ + 'range' => [ + 'unixtime' => [ + 'gte' => $date_arr['start_time'], + 'lte' => $date_arr['end_time'], + ] + ] + ]; + } + + //排序 + $es->order('unixtime', 'desc'); + + //昵称查询要先查uid 再去es查昵称 + + //$es->queryRange('unixtime', [ + // 'gt' => 1609750662180, + // 'lt' => 1609750662184 + //]); + //$es->queryFieldExist($amount_field); + $es->setQueryBool(); + //必须[setQueryBool & must,must_not, should必须在一起使用] + $es->must($where); + + //执行查询 + Tools::log_to_write_txt(['执行查询,入参:',$es->params], 'es_player_ticket_change_record'); + $res = $es->query(); + Tools::log_to_write_txt(['执行查询,出参:',$res], 'es_player_ticket_change_record'); + + //删除本地临时数据 + Db::execute('truncate table fa_zc_player_ticket_change_record'); + + if(empty($res['hits']['hits'])) return set_result(500, '未查询到相关数据.'); + + $lists = $res['hits']['hits']; + $this->total = $res['hits']['total']; + + //组装入库字段 + $items = []; + foreach ($lists as $list){ + $item = []; + $item['gameid'] = empty($list['_source']['uid'])?'':$list['_source']['uid']; + $item['nickname'] = empty($list['_source']['nickname'])?'':$list['_source']['nickname']; + $item['change_type'] = empty($change_type_arr[$list['_source']['statType']])?'0':$change_type_arr[$list['_source']['statType']].''; + + $item['amount'] = empty($list['_source'][$amount_field])?0:$list['_source'][$amount_field];//帐变额 + + $after_amount = empty($list['_source'][$after_amount_field])?0:$list['_source'][$after_amount_field]; + $item['after_amount'] = $after_amount; + $item['before_amount'] = $item['after_amount']-$item['amount'];//帐变前=帐变后-帐变额 + $item['change_time'] = floor($list['_source']['unixtime']/1000); + $item['api_key'] = $list['_id']; + $items[] = $item; + } + + //记录入库数据 + Tools::log_to_write_txt('记录入库数据 | '.json_encode($items, JSON_UNESCAPED_UNICODE), 'es_player_ticket_change_record'); + + //入库 + Db::table('fa_zc_player_ticket_change_record')->insertAll($items); + return set_result(200, 'ok'); + }catch (Exception $exception){ + Tools::log_to_write_txt('未查询到相关数据. error: '.$exception->getMessage().' | '.$exception->getTraceAsString(), 'es_player_ticket_change_record'); + return set_result(500, '未查询到相关数据.'.$exception->getMessage()); + } + } + + //现金 + function money(){ + try{ + //帐变前数额字段 + $amount_field = 'item1002';//现金帐变 只能是字符 + //帐变后数额字段 + $after_amount_field = 'package1002';//现金帐变 只能是字符 + + $input_data = json_decode(input('filter'), true); + $offset = input('offset', 5); + $limit = input('limit', 5); + $es = new BaseElasticsearch(); + //判断时间查询,动态生成索引 + if(!empty($input_data['change_time'])){ + $start_time = explode(' - ', $input_data['change_time'])[0]; + $end_time = explode(' - ', $input_data['change_time'])[1]; + $es->index = $es->setIndexDate($start_time, $end_time); + }else{ + $es->index = $es->setDefaultDate();//默认一个月 + } + $es->type = 'logs'; + $es->current_page = $limit ? intval($offset / $limit) + 1 : 1; + $es->limit = $limit; + //关键步骤 + //设置查询条件 + //帐变类型:3=比赛奖励,4=后台赠送 99暂定提现 + $change_type_arr = [ + //账变来源大分类 (draw:抽奖,exchange:兑换,winprize:比赛奖励,backsend:后台赠送,sign:签到,firstprize:首充奖励,signup:报名,recharge:充值,useprop:使用道具,cashout:提现,regsend:注册赠送) + 'winprize'=>3, + 'backsend'=>4, + 'cashout'=>99 + ]; + //必须 + $where = [ + [ + 'range' => [ + $amount_field => ['gt' => -999999] + ] + ], + ]; + + //可选 + if(!empty($input_data['gameid'])){ + $where[] = [ + 'term' => [ + 'uid' => $input_data['gameid'] + ] + ]; + } + if(!empty($input_data['nickname'])){ + $where[] = [ + 'term' => [ + 'nickname' => $input_data['nickname'] + ] + ]; + } + //帐变类型 + if(!empty($input_data['change_type'])){ + $where[] = [ + 'term' => [ + 'statType' => array_flip($change_type_arr)[$input_data['change_type']] + ] + ]; + } + //帐变时间 + if(!empty($input_data['change_time'])){ + $date_arr = $es->strtounixtime($input_data['change_time']); + + $where[] = [ + 'range' => [ + 'unixtime' => [ + 'gte' => $date_arr['start_time'], + 'lte' => $date_arr['end_time'], + ] + ] + ]; + } + + //排序 + $es->order('unixtime', 'desc'); + + //$es->queryRange('unixtime', [ + // 'gt' => 1609750662180, + // 'lt' => 1609750662184 + //]); + //$es->queryFieldExist($amount_field); + $es->setQueryBool(); + //必须[setQueryBool & must,must_not, should必须在一起使用] + $es->must($where); + + //执行查询 + Tools::log_to_write_txt(['执行查询,入参:',$es->params], 'es_player_cash_change_record'); + $res = $es->query(); + Tools::log_to_write_txt(['执行查询,出参:',$res], 'es_player_cash_change_record'); + + //删除本地临时数据 + Db::execute('truncate table fa_zc_player_cash_change_record'); + + if(empty($res['hits']['hits'])) return set_result(500, '未查询到相关数据.'); + + $lists = $res['hits']['hits']; + $this->total = $res['hits']['total']; + //组装入库字段 + $items = []; + foreach ($lists as $list){ + $item = []; + $item['gameid'] = empty($list['_source']['uid'])?'':$list['_source']['uid']; + $item['nickname'] = empty($list['_source']['nickname'])?'':$list['_source']['nickname']; + $item['change_type'] = empty($change_type_arr[$list['_source']['statType']])?'0':$change_type_arr[$list['_source']['statType']].''; + + $item['amount'] = empty($list['_source'][$amount_field])?'0':$list['_source'][$amount_field].'';//帐变额 + + $after_amount = empty($list['_source'][$after_amount_field])?0:$list['_source'][$after_amount_field]; + $item['after_amount'] = $after_amount; + $item['before_amount'] = $item['after_amount']-$item['amount'];//帐变前=帐变后-帐变额 + $item['change_time'] = floor($list['_source']['unixtime']/1000); + $item['api_key'] = $list['_id']; + $items[] = $item; + } + //记录入库数据 + Tools::log_to_write_txt('记录入库数据 | '.json_encode($items, JSON_UNESCAPED_UNICODE), 'es_player_cash_change_record'); + + //入库 + Db::table('fa_zc_player_cash_change_record')->insertAll($items); + return set_result(200, 'ok'); + }catch (Exception $exception){ + Tools::log_to_write_txt('未查询到相关数据. error: '.$exception->getMessage().' | '.$exception->getTraceAsString(), 'es_player_cash_change_record'); + return set_result(500, '未查询到相关数据.'.$exception->getMessage()); + } + } + + //礼券 + function gift(){ + try{ + //帐变前数额字段 + $amount_field = 'item1001';//只能是字符 + //帐变后数额字段 + $after_amount_field = 'package1001';//只能是字符 + + $input_data = json_decode(input('filter'), true); + $offset = input('offset', 5); + + $limit = input('limit', 5); + $es = new BaseElasticsearch(); + //判断时间查询,动态生成索引 + if(!empty($input_data['change_time'])){ + $start_time = explode(' - ', $input_data['change_time'])[0]; + $end_time = explode(' - ', $input_data['change_time'])[1]; + $es->index = $es->setIndexDate($start_time, $end_time); + }else{ + $es->index = $es->setDefaultDate();//默认一个月 + } + $es->type = 'logs'; + $es->current_page = $limit ? intval($offset / $limit) + 1 : 1; + $es->limit = $limit; + //关键步骤 + //设置查询条件 + //帐变类型:1=抽奖,2=兑换,3=比赛奖励,4=后台赠送,5=签到 + $change_type_arr = ['draw'=>1,'exchange'=>2,'winprize'=>3,'backsend'=>4,'sign'=>5]; + //必须 + $where = [ + [ + 'range' => [ + $amount_field => ['gt' => -999999] + ] + ], + ]; + + //可选 + if(!empty($input_data['gameid'])){ + $where[] = [ + 'term' => [ + 'uid' => $input_data['gameid'] + ] + ]; + } + if(!empty($input_data['nickname'])){ + $where[] = [ + 'term' => [ + 'nickname' => $input_data['nickname'] + ] + ]; + } + //帐变类型 + if(!empty($input_data['change_type'])){ + $where[] = [ + 'term' => [ + 'statType' => array_flip($change_type_arr)[$input_data['change_type']] + ] + ]; + } + //帐变时间 + if(!empty($input_data['change_time'])){ + $date_arr = $es->strtounixtime($input_data['change_time']); + + $where[] = [ + 'range' => [ + 'unixtime' => [ + 'gte' => $date_arr['start_time'], + 'lte' => $date_arr['end_time'], + ] + ] + ]; + } + + //排序 + $es->order('unixtime', 'desc'); + + //$es->queryRange('unixtime', [ + // 'gt' => 1609750662180, + // 'lt' => 1609750662184 + //]); + //$es->queryFieldExist($amount_field); + $es->setQueryBool(); + //必须[setQueryBool & must,must_not, should必须在一起使用] + $es->must($where); + + //执行查询 + Tools::log_to_write_txt(['执行查询,入参:'], 'es_player_gift_change_record'); + $res = $es->query(); + + Tools::log_to_write_txt(['执行查询,出参:', $es->params, $res], 'es_player_gift_change_record'); + + //删除本地临时数据 + Db::execute('truncate table fa_zc_player_gift_change_record'); + + if(empty($res['hits']['hits'])) return set_result(500, '未查询到相关数据.'); + + $lists = $res['hits']['hits']; + + $this->total = $res['hits']['total']; + //组装入库字段 + $items = []; + foreach ($lists as $list){ + $item = []; + $item['gameid'] = empty($list['_source']['uid'])?'':$list['_source']['uid']; + $item['nickname'] = empty($list['_source']['nickname'])?'':$list['_source']['nickname']; + $item['change_type'] = empty($change_type_arr[$list['_source']['statType']])?'0':$change_type_arr[$list['_source']['statType']].''; + + $item['amount'] = empty($list['_source'][$amount_field])?'0':$list['_source'][$amount_field].'';//帐变额 + + $after_amount = empty($list['_source'][$after_amount_field])?0:$list['_source'][$after_amount_field]; + $item['after_amount'] = $after_amount; + $item['before_amount'] = $item['after_amount']-$item['amount'];//帐变前=帐变后-帐变额 + $item['change_time'] = floor($list['_source']['unixtime']/1000); + $item['api_key'] = $list['_id']; + $items[] = $item; + } + //记录入库数据 + Tools::log_to_write_txt(['记录入库数据',$items], 'es_player_gift_change_record'); + + //入库 + Db::table('fa_zc_player_gift_change_record')->insertAll($items); + return set_result(200, 'ok', $res); + }catch (Exception $exception){ + Tools::log_to_write_txt('未查询到相关数据. error: '.$exception->getMessage().' | '.$exception->getTraceAsString(), 'es_player_gift_change_record'); + return set_result(500, '未查询到相关数据.'.$exception->getMessage()); + } + + } + +} +``` + +#### 聚合案例 + +##### sum求和 +``` +index = $es->setIndexDate($start_time, $end_time); + }else{ + $es->index = $es->setDefaultDate();//默认一个月 + } + + $es->type = 'logs'; + $es->current_page = 1; + $es->limit = 99999999; + //关键步骤 + //设置查询条件 + $where = [ + [ + 'term' => [ + 'statType' => 'recharge'//充值类型 + ] + ], + [ + 'range' => [ + //参赛券 + 'item1000' => [ + 'gte' => 0,//不可能是负数 + ] + ] + ], + [ + 'range' => [ + //参赛券 + 'item1002' => [ + 'lt' => 0,//item1002(奖励金)<0 + ] + ] + ] + ]; + //可选 + if(!empty($input_data['gameid'])){ + $where[] = [ + 'term' => [ + 'uid' => $input_data['gameid'] + ] + ]; + } + //时间 + if(!empty($input_data['order_itme'])){ + $date_arr = $es->strtounixtime($input_data['order_itme']); + $where[] = [ + 'range' => [ + 'unixtime' => [ + 'gte' => $date_arr['start_time'], + 'lte' => $date_arr['end_time'], + ] + ] + ]; + } + if(!empty($input_data['order_status'])){ + $where[] = [ + 'term' => [ + 'status' => $input_data['order_status'] + ] + ]; + } + + //排序 + //$es->order('unixtime', 'desc'); + //设置bool查询模式 + $es->setQueryBool(); + //必须[setQueryBool & must,must_not, should必须在一起使用] + $es->must($where); + $es->setAggs('item1000', 0); + //执行查询 + $res = $es->sum(); + + + return $res['aggregations']['sum']['value']; + } + + + /** + * desc:查询本月总充值 + * author:wh + * @return array|\think\response\Json + */ + function month(){ + + $input_data = json_decode(input('filter'), true); + + $es = new BaseElasticsearch(); + //判断时间查询,动态生成索引 + if(!empty($input_data['order_itme'])){ + $start_time = explode(' - ', $input_data['order_itme'])[0]; + $end_time = explode(' - ', $input_data['order_itme'])[1]; + $es->index = $es->setIndexDate($start_time, $end_time); + }else{ + $es->index = $es->setDefaultDate();//默认一个月 + } + + $es->type = 'logs'; + $es->current_page = 1; + $es->limit = 99999999; + //关键步骤 + //设置查询条件 + $where = [ + [ + 'term' => [ + 'statType' => 'recharge'//充值类型 + ] + ], + [ + 'range' => [ + //参赛券 + 'item1000' => [ + 'gte' => 0,//不可能是负数 + ] + ] + ], + [ + 'range' => [ + //参赛券 + 'item1002' => [ + 'lt' => 0,//item1002(奖励金)<0 + ] + ] + ], + [ + 'range' => [ + //参赛券 + 'unixtime' => [ + 'gt' => strtotime(date('Y-m').'-01').'000', + ] + ] + ] + ]; + //可选 + if(!empty($input_data['gameid'])){ + $where[] = [ + 'term' => [ + 'uid' => $input_data['gameid'] + ] + ]; + } + //时间 + if(!empty($input_data['order_itme'])){ + $date_arr = $es->strtounixtime($input_data['order_itme']); + $where[] = [ + 'range' => [ + 'unixtime' => [ + 'gte' => $date_arr['start_time'], + 'lte' => $date_arr['end_time'], + ] + ] + ]; + } + //订单状态(es的充值记录都是充值成功的数据) + //if(!empty($input_data['order_status'])){ + // $where[] = [ + // 'term' => [ + // 'status' => $input_data['order_status'] + // ] + // ]; + //} + + //排序 + //$es->order('unixtime', 'desc'); + //设置bool查询模式 + $es->setQueryBool(); + //必须[setQueryBool & must,must_not, should必须在一起使用] + $es->must($where); + $es->setAggs('item1000', 0); + //执行查询 + $res = $es->sum(); + + + return $res['aggregations']['sum']['value']; + } + /** + * desc:查询本周总充值 + * author:wh + * @return array|\think\response\Json + */ + function week(){ + + $input_data = json_decode(input('filter'), true); + + $es = new BaseElasticsearch(); + //判断时间查询,动态生成索引 + if(!empty($input_data['order_itme'])){ + $start_time = explode(' - ', $input_data['order_itme'])[0]; + $end_time = explode(' - ', $input_data['order_itme'])[1]; + $es->index = $es->setIndexDate($start_time, $end_time); + }else{ + $es->index = $es->setDefaultDate();//默认一个月 + } + + $es->type = 'logs'; + $es->current_page = 1; + $es->limit = 99999999; + //关键步骤 + //设置查询条件 + $where = [ + [ + 'term' => [ + 'statType' => 'recharge'//充值类型 + ] + ], + [ + 'range' => [ + //参赛券 + 'item1000' => [ + 'gte' => 0,//不可能是负数 + ] + ] + ], + [ + 'range' => [ + //参赛券 + 'item1002' => [ + 'lt' => 0,//item1002(奖励金)<0 + ] + ] + ], + [ + 'range' => [ + //本周 + 'unixtime' => [ + 'gte' => strtotime((new Date())->beginWeek()).'000', + 'lte' => strtotime((new Date())->endWeek()).'000', + ] + ] + ] + ]; + //可选 + if(!empty($input_data['gameid'])){ + $where[] = [ + 'term' => [ + 'uid' => $input_data['gameid'] + ] + ]; + } + //时间 + if(!empty($input_data['order_itme'])){ + $date_arr = $es->strtounixtime($input_data['order_itme']); + $where[] = [ + 'range' => [ + 'unixtime' => [ + 'gte' => $date_arr['start_time'], + 'lte' => $date_arr['end_time'], + ] + ] + ]; + } + //订单状态(es的充值记录都是充值成功的数据) + //if(!empty($input_data['order_status'])){ + // $where[] = [ + // 'term' => [ + // 'status' => $input_data['order_status'] + // ] + // ]; + //} + + //排序 + //$es->order('unixtime', 'desc'); + //设置bool查询模式 + $es->setQueryBool(); + //必须[setQueryBool & must,must_not, should必须在一起使用] + $es->must($where); + $es->setAggs('item1000', 0); + //执行查询 + $res = $es->sum(); + + + return $res['aggregations']['sum']['value']; + } + /** + * desc:查询今日总充值 + * author:wh + * @return array|\think\response\Json + */ + function today(){ + + $input_data = json_decode(input('filter'), true); + + $es = new BaseElasticsearch(); + //判断时间查询,动态生成索引 + if(!empty($input_data['order_itme'])){ + $start_time = explode(' - ', $input_data['order_itme'])[0]; + $end_time = explode(' - ', $input_data['order_itme'])[1]; + $es->index = $es->setIndexDate($start_time, $end_time); + }else{ + $es->index = $es->setDefaultDate();//默认一个月 + } + + $es->type = 'logs'; + $es->current_page = 1; + $es->limit = 99999999; + //关键步骤 + //设置查询条件 + $where = [ + [ + 'term' => [ + 'statType' => 'recharge'//充值类型 + ] + ], + [ + 'range' => [ + //参赛券 + 'item1000' => [ + 'gte' => 0,//不可能是负数 + ] + ] + ], + [ + 'range' => [ + //参赛券 + 'item1002' => [ + 'lt' => 0,//item1002(奖励金)<0 + ] + ] + ], + [ + 'range' => [ + //今日 + 'unixtime' => [ + 'gt' => strtotime(date('Y-m-d')).'000', + ] + ] + ] + ]; + //可选 + if(!empty($input_data['gameid'])){ + $where[] = [ + 'term' => [ + 'uid' => $input_data['gameid'] + ] + ]; + } + //时间 + if(!empty($input_data['order_itme'])){ + $date_arr = $es->strtounixtime($input_data['order_itme']); + $where[] = [ + 'range' => [ + 'unixtime' => [ + 'gte' => $date_arr['start_time'], + 'lte' => $date_arr['end_time'], + ] + ] + ]; + } + //订单状态(es的充值记录都是充值成功的数据) + //if(!empty($input_data['order_status'])){ + // $where[] = [ + // 'term' => [ + // 'status' => $input_data['order_status'] + // ] + // ]; + //} + + //排序 + //$es->order('unixtime', 'desc'); + //设置bool查询模式 + $es->setQueryBool(); + //必须[setQueryBool & must,must_not, should必须在一起使用] + $es->must($where); + $es->setAggs('item1000', 0); + //执行查询 + $res = $es->sum(); + + + return $res['aggregations']['sum']['value']; + } + + + + + +} +``` + +#### 或查询(相当于mysql or查询) +``` +/** + * desc:或查询(相当于mysql or查询) + * author:wh + * @return array|\think\response\Json + */ + function total(){ + + $input_data = json_decode(input('filter'), true); + + $es = new BaseElasticsearch(); + //判断时间查询,动态生成索引 + if(!empty($input_data['conversion_time'])){ + $start_time = explode(' - ', $input_data['conversion_time'])[0]; + $end_time = explode(' - ', $input_data['conversion_time'])[1]; + $es->index = $es->setIndexDate($start_time, $end_time); + }else{ + //$es->index = $es->setDefaultDate();//默认一个月 + //默认查询所有历史记录 + $es->index = $es->setIndexAllDate(); + } + + $es->type = 'logs'; + $es->current_page = 1; + $es->limit = 999999999; + //关键步骤 + //设置查询条件 + // + $where = [ + [ + 'term' => [ + 'statType' => 'exchange'//兑换类型 + ] + ], + + ]; + //可选 + if(!empty($input_data['gameid'])){ + $where[] = [ + 'term' => [ + 'uid' => $input_data['gameid'] + ] + ]; + } + //昵称 + if(!empty($input_data['nickname'])){ + $where[] = [ + 'term' => [ + 'nickname' => $input_data['nickname'] + ] + ]; + } + //时间 + if(!empty($input_data['conversion_time'])){ + $date_arr = $es->strtounixtime($input_data['conversion_time']); + $where[] = [ + 'range' => [ + 'unixtime' => [ + 'gte' => $date_arr['start_time'], + 'lte' => $date_arr['end_time'], + ] + ] + ]; + } + + $should_where = [ + [ + "range"=> [ + "item2001"=> [ + "gte"=> -9999999 + ] + ] + ], + [ + "range"=> [ + "item2002"=> [ + "gte"=> -9999999 + ] + ] + ], + [ + "range"=> [ + "item2005"=> [ + "gte"=> -9999999 + ] + ] + ], + [ + "range"=> [ + "item2010"=> [ + "gte"=> -9999999 + ] + ] + ] + ]; + + + //排序 + //$es->order('unixtime', 'desc'); + //设置bool查询模式 + $es->setQueryBool(); + //必须[setQueryBool & must,must_not, should必须在一起使用] + $es->must($where); + $es->should($should_where); + $es->setAggs('item1001', 1); + //执行查询 + $res = $es->sum(); + + return $res['aggregations']['sum']['value']; + } +``` \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/db/mysql/README.MD b/superadmin/vendor/wanghua/general-utility-tools-php/src/db/mysql/README.MD new file mode 100644 index 0000000..1960d4c --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/db/mysql/README.MD @@ -0,0 +1,6 @@ +## 仅适用于tp5+, php7+ + + +## 表管理 + +## 字段管理 \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/db/mysql/lib/Field.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/db/mysql/lib/Field.php new file mode 100644 index 0000000..2bab4c9 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/db/mysql/lib/Field.php @@ -0,0 +1,304 @@ +getTableFields(); + if(in_array($table['fields_name'], $f)){ + //枚举类型 + if ($table['type'] == 'enum'){ + $enum_str = ''; + $enum_default = ''; + //组合枚举值 + if($table['default']){ + $enum_str.='ENUM('.$table['default'].')'; + $enum_default = explode(',', $table['default'])[0]; + } + $sql2 = "ALTER TABLE {$table_name} MODIFY {$table['fields_name']} {$enum_str} NOT NULL DEFAULT {$enum_default} COMMENT '{$table['title']}';"; + }else{ + $sql2 = "ALTER TABLE {$table_name} MODIFY COLUMN {$table['fields_name']} {$table['type']}(".$table['size'].$dec_num.") {$is_unsigned} DEFAULT {$table['default']} COMMENT '{$table['title']}';"; + } + }else{ + + if(in_array($table['type'], ['text','longtext'])){ + $sql2 = "ALTER TABLE {$table_name} ADD COLUMN {$table['fields_name']} {$table['type']} COMMENT '{$table['title']}' AFTER id;"; + }elseif ($table['type'] == 'enum'){//枚举类型 + $enum_str = ''; + $enum_default = ''; + //组合枚举值 + if($table['default']){ + $enum_str.='ENUM('.$table['default'].')'; + $enum_default = explode(',', $table['default'])[0]; + } + $sql2 = "ALTER TABLE {$table_name} ADD {$table['fields_name']} {$enum_str} NOT NULL DEFAULT {$enum_default} COMMENT '{$table['title']}' AFTER id;"; + }else{ + $sql2 = "ALTER TABLE {$table_name} ADD COLUMN {$table['fields_name']} {$table['type']}(".$table['size'].$dec_num.") {$is_unsigned} DEFAULT {$table['default']} COMMENT '{$table['title']}' AFTER id;"; + } + } + + DB::execute($sql2); + } + + /** + * desc:删除字段 + * author:wh + * @param $tablename + * @param $fieldname + */ + function dropFieldName($tablename, $fieldname){ + $sql = "ALTER TABLE {$tablename} DROP COLUMN {$fieldname};"; + DB::execute($sql); + } + + /** + * desc:改注释 + * author:wh + * @param $tablename + * @param $comment + * @return bool + */ + function updateComment($tablename,$field, $comment){ + + $sql = "ALTER TABLE {$tablename} MODIFY COLUMN {$field} INT COMMENT '{$comment}';"; + Db::execute($sql); + } + + /** + * desc:获取数据表中所有字段的数据类型(含其它属性) + * + * 查询结果字段名说明 + * + * F_FIELD:字段名称 + * F_DATATYPE:数据类型 + * F_DATALENGTH:数据长度(int类型默认为null,业务处理时默认为10即可) + * F_PRECISION:精度 + * F_DECIMAL_DIGITS:小数位数 + * F_ALLOWNULL:是否允许为null值(1是,0否) + * F_FIELDNAME:字段名称 + * F_PRIMARYKEY:是否主键(1是,0否) + * F_DEFAULTS:字段默认值 + * + * author:wh + * @param string $dbname + * @param string $tablename + * @param string $fieldname + * @return mixed + */ + function getFieldsDataType(string $dbname,string $tablename){ + $sql = "SELECT + COLUMN_NAME F_FIELD, + data_type F_DATATYPE, + CHARACTER_MAXIMUM_LENGTH F_DATALENGTH, + NUMERIC_PRECISION F_PRECISION, + NUMERIC_SCALE F_DECIMAL_DIGITS, +IF + ( IS_NULLABLE = 'YES', '1', '0' ) F_ALLOWNULL, + COLUMN_COMMENT F_FIELDNAME, +IF + ( COLUMN_KEY = 'PRI', '1', '0' ) F_PRIMARYKEY, + column_default F_DEFAULTS, + CONCAT( upper( COLUMN_NAME ), '(', COLUMN_COMMENT, ')' ) AS 'F_DESCRIPTION' +FROM + INFORMATION_SCHEMA.COLUMNS +WHERE + TABLE_NAME = '$tablename' + AND TABLE_SCHEMA = '$dbname'"; + return Db::query($sql); + } + + + /** + * desc:获取数据表中某个字段的数据类型(含这个字段的其它属性) + * + * 查询结果字段名说明 + * + * F_FIELD:字段名称 + * F_DATATYPE:数据类型 + * F_DATALENGTH:数据长度(int类型默认为null,业务处理时默认为10即可) + * F_PRECISION:精度 + * F_DECIMAL_DIGITS:小数位数 + * F_ALLOWNULL:是否允许为null值(1是,0否) + * F_FIELDNAME:字段名称 + * F_PRIMARYKEY:是否主键(1是,0否) + * F_DEFAULTS:字段默认值 + * + * author:wh + * @param string $dbname + * @param string $tablename + * @param string $fieldname + * @return mixed + */ + function getFieldDataType(string $dbname,string $tablename,string $fieldname){ + $sql = "SELECT + COLUMN_NAME F_FIELD, + data_type F_DATATYPE, + CHARACTER_MAXIMUM_LENGTH F_DATALENGTH, + NUMERIC_PRECISION F_PRECISION, + NUMERIC_SCALE F_DECIMAL_DIGITS, +IF + ( IS_NULLABLE = 'YES', '1', '0' ) F_ALLOWNULL, + COLUMN_COMMENT F_FIELDNAME, +IF + ( COLUMN_KEY = 'PRI', '1', '0' ) F_PRIMARYKEY, + column_default F_DEFAULTS, + CONCAT( upper( COLUMN_NAME ), '(', COLUMN_COMMENT, ')' ) AS 'F_DESCRIPTION' +FROM + INFORMATION_SCHEMA.COLUMNS +WHERE + TABLE_NAME = '$tablename' + AND TABLE_SCHEMA = '$dbname' + AND COLUMN_NAME='$fieldname' + "; + $ar = Db::query($sql); + return $ar[0]; + } + + + /** + * desc:获取数据表中某个字段的数据类型 + * + * 不含这个字段的其它属性 + * + * + * 查询结果字段名说明 + * + * F_FIELD:字段名称 + * F_DATATYPE:数据类型 + * F_DATALENGTH:数据长度(int类型默认为null,业务处理时默认为10即可) + * F_PRECISION:精度 + * F_DECIMAL_DIGITS:小数位数 + * F_ALLOWNULL:是否允许为null值(1是,0否) + * F_FIELDNAME:字段名称 + * F_PRIMARYKEY:是否主键(1是,0否) + * F_DEFAULTS:字段默认值 + * + * author:wh + * @param string $dbname + * @param string $tablename + * @param string $fieldname + * @return mixed + */ + function getFieldDataTypeVal(string $dbname,string $tablename,string $fieldname){ + $sql = "SELECT + COLUMN_NAME F_FIELD, + data_type F_DATATYPE, + CHARACTER_MAXIMUM_LENGTH F_DATALENGTH, + NUMERIC_PRECISION F_PRECISION, + NUMERIC_SCALE F_DECIMAL_DIGITS, +IF + ( IS_NULLABLE = 'YES', '1', '0' ) F_ALLOWNULL, + COLUMN_COMMENT F_FIELDNAME, +IF + ( COLUMN_KEY = 'PRI', '1', '0' ) F_PRIMARYKEY, + column_default F_DEFAULTS, + CONCAT( upper( COLUMN_NAME ), '(', COLUMN_COMMENT, ')' ) AS 'F_DESCRIPTION' +FROM + INFORMATION_SCHEMA.COLUMNS +WHERE + TABLE_NAME = '$tablename' + AND TABLE_SCHEMA = '$dbname' + AND COLUMN_NAME='$fieldname' + "; + $ar = Db::query($sql); + return $ar[0]['F_DATATYPE']; + } + + /** + * desc:获取数据表中某个字段的属性 + * + * 查询结果字段名说明 + * + * F_FIELD:字段名称 + * F_DATATYPE:数据类型 + * F_DATALENGTH:数据长度(int类型默认为null,业务处理时默认为10即可) + * F_PRECISION:精度 + * F_DECIMAL_DIGITS:小数位数 + * F_ALLOWNULL:是否允许为null值(1是,0否) + * F_FIELDNAME:字段名称 + * F_PRIMARYKEY:是否主键(1是,0否) + * F_DEFAULTS:字段默认值 + * + * author:wh + * @param string $dbname + * @param string $tablename + * @param string $fieldname + * @return mixed + */ + function getFieldAttrVal(string $dbname,string $tablename,string $fieldname,string $attr){ + $sql = "SELECT + COLUMN_NAME F_FIELD, + data_type F_DATATYPE, + CHARACTER_MAXIMUM_LENGTH F_DATALENGTH, + NUMERIC_PRECISION F_PRECISION, + NUMERIC_SCALE F_DECIMAL_DIGITS, +IF + ( IS_NULLABLE = 'YES', '1', '0' ) F_ALLOWNULL, + COLUMN_COMMENT F_FIELDNAME, +IF + ( COLUMN_KEY = 'PRI', '1', '0' ) F_PRIMARYKEY, + column_default F_DEFAULTS, + CONCAT( upper( COLUMN_NAME ), '(', COLUMN_COMMENT, ')' ) AS 'F_DESCRIPTION' +FROM + INFORMATION_SCHEMA.COLUMNS +WHERE + TABLE_NAME = '$tablename' + AND TABLE_SCHEMA = '$dbname' + AND COLUMN_NAME='$fieldname' + "; + $ar = Db::query($sql); + return $ar[0][$attr]; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/db/mysql/lib/Table.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/db/mysql/lib/Table.php new file mode 100644 index 0000000..0456999 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/db/mysql/lib/Table.php @@ -0,0 +1,162 @@ +$this->appid, + 'secret'=>$this->secret, + 'grant_type'=>'client_credential', + ]; + $header = [ + 'Accept: application/json', + 'content-type: application/json' + ]; + $res = Curl::curl_request($url,$method,json_encode($params),$header); + + $json_arr = json_decode($res, true); + if($json_arr['err_no']){ + return Tools::set_res($json_arr['err_no'],$json_arr['err_tips']); + } + //保存 + session('ses_dou_yin_access_token',$json_arr['data']); + return Tools::set_ok('ok',$json_arr['data']); + } + + /** + * desc:实时获取token + * author:wh + * @return array + */ + function realTimeGetAccessToken(){ + $json_arr = session('ses_dou_yin_access_token'); + if(empty($json_arr)){ + return $this->getAccessToken(); + } + if(time() >= $json_arr['expiresAt']){ + //过期 + return $this->getAccessToken(); + } + return Tools::set_ok('ok',$json_arr); + } + + /** + * desc:前端tt.login触发调用 + * + * author:wh + */ + function jscode2session($code='',$anonymous_code=''){ + $url = 'https://minigame.zijieapi.com/mgplatform/api/apps/jscode2session'; + //$Scope = 'open.ttgame.mgplatform'; + $method = 'GET'; + + if(empty($code) && empty($anonymous_code)){ + return Tools::set_fail('tt.login 接口返回的匿名登录凭证(code 和 anonymous_code 至少要有一个)'); + } + $params = [ + 'appid'=>$this->appid, + 'secret'=>$this->secret, + ]; + if($code){ + $params['code'] = $code; + } + if($anonymous_code){ + $params['anonymous_code'] = $anonymous_code; + } + $header = [ + 'Accept: application/json', + 'content-type: application/json' + ]; + $url .= '?'.http_build_query($params); + //dump($url); + Tools::log_to_write_txt(['input'=>$url]); + $res = Curl::curl_request($url,$method,[],$header); + Tools::log_to_write_txt(['output'=>$res]); + //dump($res); + $json_arr = json_decode($res, true); + //dump($json_arr);die; + if($json_arr['error']){ + return Tools::set_res($json_arr['errcode'],$json_arr['errmsg']); + } + //保存 + session('ses_dou_yin_js_code_2_session',$json_arr); + return Tools::set_ok('ok',$json_arr); + } + + /** + * desc:创建二维码 + * 接口说明 + * 获取小程序/小游戏的二维码。该二维码可通过任意 app 扫码打开, + * 能跳转到开发者指定的对应字节系 app 内拉起小程序/小游戏,并传入开发者指定的参数。 + * 通过该接口生成的二维码,永久有效,暂无数量限制。 + * + * ⚠ Tip:在使用该功能之前请记得先配置您的默认分享文案和图片,配置方式可参考论坛。 + * ⚠ Tip:小程序的 path 要 encode 一次,如 pages%3fparam%3dtrue,小游戏的 path 为 JSON 字符串, + * 如{"param":true},否则会导致取不到。 + * + * 参数: + * code和anonymous_code二选一 必须 + * appname 可选,目标打开应用名称 默认douyin + * background 可选,背景色,rgb格式,英文逗号隔开,默认透明色 + * path 可选,小程序/小游戏启动参数,小程序则格式为 encode({path}?{query}),小游戏则格式为 JSON 字符串,默认为空 + * width 宽度 可选,二维码宽度,单位 px,最小 280px,最大 1280px,默认为 430px + * line_color 可选,二维码线条颜色,默认为黑色,rgb格式,英文逗号隔开,默认黑色 + * set_icon 可选,是否展示小程序/小游戏 icon,默认不展示,传yes展示,no不展示,默认no + * + * author:wh + */ + function createQRCode($path='',$appname='',$width='',$background='',$line_color='',$set_icon='no'){ + try { + $url = 'https://minigame.zijieapi.com/mgplatform/api/apps/qrcode'; + //$Scope = 'open.ttgame.mgplatform'; + $method = 'POST'; + + $json_arr = $this->realTimeGetAccessToken(); + if(empty($json_arr)){ + return Tools::set_fail('请重新授权'); + } + + $params = [ + 'access_token'=>$json_arr['data']['access_token'], + ]; + if($appname){ + $params['appname'] = $appname; + } + if($width){ + $params['width'] = $width; + } + //if($path){ + //小程序/小游戏启动参数,小程序则格式为 encode({path}?{query}),小游戏则格式为 JSON 字符串,默认为空 + $params['path'] = $path; + //} + //英文逗号隔开,r,g,b格式 + if($background){ + $background_rgb = explode(',',$background); + $params['r'] = $background_rgb[0]; + $params['g'] = $background_rgb[1]; + $params['b'] = $background_rgb[2]; + } + if($line_color){ + $line_color_rgb = explode(',',$line_color); + $params['r'] = $line_color_rgb[0]; + $params['g'] = $line_color_rgb[1]; + $params['b'] = $line_color_rgb[2]; + } + $params['set_icon'] = $set_icon=='yes'; + + $header = [ + 'Accept: application/json', + 'content-type: application/json' + ]; + + Tools::log_to_write_txt(['input'=>input(),'$params'=>$params]); + $res = Curl::curl_request($url,$method,json_encode($params),$header); + Tools::log_to_write_txt(['output'=>$res]); + + //流 + return $res; + }catch (\Exception $e){ + Tools::error_txt_log($e); + return Tools::set_fail(); + } + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/encrypt/TripleDES.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/encrypt/TripleDES.php new file mode 100644 index 0000000..664daa6 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/encrypt/TripleDES.php @@ -0,0 +1,88 @@ + strlen($text)) { + return false; + } + if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) { + return false; + } + return substr($text, 0, -1 * $pad); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/errorcode/AuthError.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/errorcode/AuthError.php new file mode 100644 index 0000000..1ad249e --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/errorcode/AuthError.php @@ -0,0 +1,20 @@ +ip = false===strpos($ip_port,'http')?'http://'.$ip_port:$ip_port; + $this->index_sign = $index_sign; + } + + /** + * desc:设置查询方法,默认_search + * author:wh + * @param $method_name + */ + function setQueryMethod($method_name){ + $this->query_method = $method_name; + } + + + /** + * desc:根据日期索引查询文档 + * 注:默认一个月 + * + * eg:'qa-stat-2021.01'; + * + * 注:如果es设置的索引没有规律也不是日期作为索引,请设置普通索引setIndexNormal,且请在初始化时,设置索引前缀index_sign为""字符串 + * author:wh + */ + function setIndexDefault(){ + $this->setRequestIndex(date('Y'.$this->doc_date_sign.'m')); + } + + /** + * desc:设置要查询的日期文档索引 + * + * 1、按年检索文档[建议5年左右] + * + * eg: qa-stat-2018.*,qa-stat-2019.*,qa-stat-2020.*,qa-stat-2021.* + * + * 注意:当索引过长,es会抛出索引太长异常 + * + * 注:如果es设置的索引没有规律也不是日期作为索引,请设置普通索引setIndexNormal,且请在初始化时,设置索引前缀index_sign为""字符串 + * + * author:wh + * @param string $start_time 开始时间 eg:2010-01-01 08:05:55 + * @param string $end_time 结束时间 eg:2021-12-31 12:05:00 + * @return string + */ + function setIndexYearDate(string $start_time, string $end_time){ + + //默认查询索引 + $index = date('Y'.$this->doc_date_sign.'*', strtotime($start_time)); + + $sign = ',';//分隔符 + + $m = date('Y', strtotime($end_time)) - date('Y', strtotime($start_time)); + + //拼装查询索引 + for ($i=0; $i<$m; $i++){ + + $index .= $sign.($this->index_sign.(date('Y', strtotime($start_time))+$i).$this->doc_date_sign.'*'); + } + + $this->setRequestIndex($index); + } + + /** + * desc:设置日期索引 + * + * 检索全部日期索引文档 + * + * 例如es设置的索引为: + * qa-stat-2021.01 + qa-stat-2021.02 + qa-stat-2021.03 + qa-stat-2021.04 + * + * 实际查询自动设置为:qa-stat-* + * + * 注:如果es设置的索引没有规律也不是日期作为索引,请设置普通索引setIndexNormal,且请在初始化时,设置索引前缀index_sign为""字符串 + * + * author:wh + * @return string + */ + function setIndexAllDate(){ + + $this->setRequestIndex('*'); + } + + /** + * desc:设置普通索引 + * + * 调用此方法,请在初始化时,设置索引前缀index_sign为""空字符串 + * + * author:wh + * @param string $index + */ + function setIndexNormal(string $index){ + $this->setRequestIndex($index); + } + + /** + * desc:设置跨月份索引 + * + * eg: qa-stat-2021.01,qa-stat-2021.02 + * + * 注:如果es设置的索引没有规律也不是日期作为索引,请设置普通索引setIndexNormal,且请在初始化时,设置索引前缀index_sign为""字符串 + * + * author:wh + * @param string $start_time eg:'2020-11' + * @param string $end_time eg:'2021-01' + * @return string eg: qa-stat-2020.11,qa-stat-2020.12,qa-stat-2021.01 + */ + function setIndexDate(string $start_time, string $end_time){ + $date = new Date(); + + //计算月份 + $m = $date->dateCutMonth($start_time, $end_time); + ////解决跨年且不足1个月时,索引设置错误问题 + //if($m == 0 && (date('Y', strtotime($end_time)) > date('Y', strtotime($start_time)))){ + // $m = 1; + //} + //格式 + $date->date_format = 'Y-m'; + + //默认查询索引 + $index = date('Y'.$this->doc_date_sign.'m', strtotime($start_time)); + + $tmp_time = $start_time; + $sign = ',';//分隔符 + + //拼装查询索引 + for ($i=0; $i<$m; $i++){ + $tmp_time = $date->addTime(1, 'M', strtotime($tmp_time)); + $index .= $sign.$this->index_sign.(date('Y'.$this->doc_date_sign.'m', strtotime($tmp_time))); + } + + $this->setRequestIndex($index); + } + + + /** + * desc:设置查询参数 + * author:wh + * @param string $query_param_json + */ + function setQueryParam(string $query_param_json){ + $this->query_param = $query_param_json; + $this->post_url = $this->ip.'/'.$this->index.'/'.$this->query_method; + } + + /** + * desc:获取查询参数,用于调试 + * author:wh + * @return array + */ + function getQueryParam(){ + return [ + 'ip'=>$this->ip, + 'index_sign'=>$this->index_sign, + 'index'=>$this->index, + 'query_method'=>$this->query_method, + 'post_url'=>$this->post_url, + 'query_param'=>$this->query_param, + ]; + } + + /** + * desc:执行es查询 + * author:wh + * @return array|bool|int|string + * @throws \Exception + */ + function execute(){ + if(empty($this->ip)) throw new \Exception('请设置ip'); + //if(empty($this->index_sign)) throw new \Exception('请设置索引前缀'); + if(empty($this->index)) throw new \Exception('请设置索引'); + if(empty($this->query_method)) throw new \Exception('请设置查询方法'); + if(empty($this->query_param)) throw new \Exception('请设置查询参数'); + + + + + Tools::log_to_write_txt(['exe_func'=>$this->exe_func,'request_url'=>$this->post_url,'IN'=>" | IN: ",'query_params'=>$this->query_param, 'input'=>input()], $this->es_log_file); + $result = Tools::curl_post($this->post_url, $this->query_param); + Tools::log_to_write_txt(['exe_func'=>$this->exe_func,'OUT'=>" | OUT: ", 'result'=>$result], $this->es_log_file); + + return $result; + } + + /** + * desc:设置是哪个方法调用本es库,用于日志记录 + * author:wh + * @param string $function + */ + function setExecuteFunction(string $function){ + $this->exe_func = $function; + } + + /** + * desc:设置请求文档索引 + * author:wh + * @param string $index + */ + protected function setRequestIndex(string $index){ + $this->index = $this->index_sign.$index; + } + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/es/README.md b/superadmin/vendor/wanghua/general-utility-tools-php/src/es/README.md new file mode 100644 index 0000000..ea56d61 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/es/README.md @@ -0,0 +1,1340 @@ +##### Elasticsearch数据库操作类库使用教程 + +#### 一、Elasticsearch原生cUrl查询库 +##### 说明:该库只提供查询请求,开发者只需要关注查询参数怎么写,请求之后会把结果按原样返回,不会有任何修改。 + +**** + +###### 【查询参数案例:查询一条数据】 +###### 只需要简单的查询条件即可;from=0,size=1表示从第0条数据查询1条,from可省略,等于mysql的limit 1。 +###### 这里unixtime和statFunc为数据结构(表)中的字段名称,gt大于,lt小于,gte大于等于,lte小于等于 +###### 这里register是字段statFunc的值,而unixtime存的是毫秒时间戳 +###### 注:数据字段尽量不存小数,小数太小的时候,在内存中计算时会出现误差,比如0.001+0.001=0 + +#### 代码使用示例: +``` + +//======【有规律的日期索引】【特殊】======= +$query_params = "{ + "query": { + "bool": { + "must": [ + { + "range": { + "unixtime": { + "gt": 1623254400000, + "lt": 2623340800000 + } + } + }, + { + "terms": { + "statFunc": [ + "signup", + "returncard" + ] + } + }, + { + "term": { + "matchTypeNum": 3 + } + } + ] + } + }, + "size": 1, + "aggs": { + "group_by_day": { + "date_histogram": { + "field": "@timestamp", + "interval": "day", + "min_doc_count": 0, + "order": { + "_key": "asc" + }, + "time_zone": "+08:00", + "format": "yyyy-MM-dd" + }, + "aggs": { + "item1000_sum": { + "sum": { + "field": "item1000" + } + } + } + } + } + }"; +$es = new Elasticsearch('http://49.4.3.4:3201','qa-item-'); +$es->setIndexAllDate(); +$es->setQueryParam($query_params); +dump($es->execute()); + +dump('=================================================================='); + +//======【无规律的普通索引】【推荐】======= +$query_params = "{ + 'query': { + 'bool': { + 'must': [ + { + 'range': { + 'unixtime': { + 'gt': 1617206400000, + 'lt': 1619712000000 + } + } + }, + { + 'term': { + 'statFunc': 'register' + } + } + ] + } + }, + 'size': 1 + }"; +dump($query_params); +$es = new Elasticsearch('http://49.4.3.4:3201',''); +$es->setIndexNormal('qa-item-2021.06.*'); +$es->setQueryParam($query_params); +dump($es->execute()); + + +//以上都经过测试 + +``` + +#### 查询参数示例 +###### 示例1: 这里展示了must查询 +``` +{ + "query": { + "bool": { + "must": [ + { + "range": { + "unixtime": { + "gt": 1617206400000, + "lt": 1619712000000 + } + } + }, + { + "term": { + "statFunc": "register" + } + } + ] + } + }, + "size": 1 +} +``` +###### 示例2: 这里展示了混合查询 +###### must表示必须,等于mysql的and; +###### should表示应该,等于mysql的or; +###### 模糊查询用missing,等于mysql的like,但是不用%%通配符; +###### must_not表示必须不是,等于mysql的!=; +###### 它们可以一起使用。 +###### 仔细观察发现,query,bool基本是固定的,而里面的参数写法都差不多。 + +``` +{ + "query": { + "bool": { + "must": [ + { + "match": { + "statFunc": "signup" + } + } + ], + "must_not": [], + "should": [ + { + "term": { + "gameTagNum": "210" + } + }, + { + "term": { + "gameTagNum": "300" + } + } + ] + } + }, + "size": 1 +} +``` + +**** + +###### 【查询参数案例:查询多条数据】 +###### 只需要简单的查询条件即可,这里只改变了size字段值。 +###### eg: +``` +{ + "query": { + "bool": { + "must": [ + { + "range": { + "unixtime": { + "gt": 1617206400000, + "lt": 1619712000000 + } + } + }, + { + "term": { + "statFunc": "register" + } + } + ] + } + }, + "size": 10 +} +``` + +**** + +###### 【查询参数案例:简单聚合】 +###### 根据时间和某个字段查询数据,并且基于查询结果再次按时间分组,分组段按天分段,同时基于查询结果再次聚合某个字段 +###### 为了便于演示,这里查询条件改为容易理解的字段:"统计张三一段时间内已下单的订单总金额" +###### order_money_sum是自定义名称,等于mysql的别名 +###### eg: +``` +{ + "query": { + "bool": { + "must": [ + { + "range": { + "order_time": { + "gt": 1617206400000, + "lt": 1619712000000 + } + } + }, + { + "term": { + "name": "张三" + } + } + ] + } + }, + "size": 0, + "aggs": { + "order_money_sum": { + "sum": { + "field": "order_money" + } + } + } +} +``` + +###### 【查询参数案例:复杂聚合】 +###### aggs可以嵌套,嵌套时的含义表示基于上一个聚合结果再次聚合 +###### 这里表示先按day(天)分组,再统计字段名为item1000的字段,day可以改为月(month)年(year) +###### eg: +``` +{ + "query": { + "bool": { + "must": [ + { + "range": { + "unixtime": { + "gt": 1617206400000, + "lt": 1619712000000 + } + } + }, + { + "term": { + "statFunc": "register" + } + } + ] + } + }, + "size": 0, + "aggs": { + "group_by_day": { + "date_histogram": { + "field": "@timestamp", + "interval": "day", + "min_doc_count": 0, + "order": { + "_key": "desc" + }, + "time_zone": "+08:00", + "format": "yyyy-MM-dd" + }, + "aggs": { + "item1000_sum": { + "sum": { + "field": "item1000" + } + } + } + } + } +} +``` + + +**** + +#### 二、Elasticsearch助手库(不推荐,因为不灵活。掌握了一的查询参数,其它都是小问题) + +##### 说明: + +##### 通用es查询多条数据 +* (这一种方式可实现大部分列表查询功能) +* (where条件跟随业务需要修改) +* (多条件组合查询满足不同查询业务) + +#### 查询列表: +``` + //获取分页数据 + $offset = input('offset', 5); + $limit = input('limit', 5); + + + //获取数据 + $input_data = $_POST; + 初始化es + $es = new BaseElasticsearch(); + + + //(必须)判断时间查询设置索引 + if(!empty($input_data['sign_time'])){ + $start_time = explode(' - ', $input_data['sign_time'])[0]; + $end_time = explode(' - ', $input_data['sign_time'])[1]; + //这里动态生成索引 + $es->index = $es->setIndexDate($start_time, $end_time); + }else{ + //设置默认索引 + $es->index = $es->setDefaultDate();//默认一个月 + } + + + //设置查询的文档类型 + $es->type = 'logs';//本例是查询日志类型的数据 + + //分页计算得到当前页码(可根据自身情况设置) + $es->current_page = $offset/$limit; + $es->limit = $limit; + + + //关键步骤 + //设置查询条件 + $where = [ + [ + 'term' => [ + 'statType' => 'draw' + ] + ] + ]; + //可选 + if(!empty($input_data['gameid'])){ + $where[] = [ + 'term' => [ + 'uid' => $input_data['gameid'] + ] + ]; + } + //时间 + if(!empty($input_data['sign_time'])){ + //这里将字符串日期转换为unix时间戳(精确到毫秒) + $date_arr = $es->strtounixtime($input_data['sign_time']); + $where[] = [ + 'range' => [ + 'unixtime' => [ + 'gte' => $date_arr['start_time'], + 'lte' => $date_arr['end_time'], + ] + ] + ]; + } + //排序 + $es->order('unixtime', 'desc'); + + //设置bool查询模式 + $es->setQueryBool(); + //必须[setQueryBool & must、must_not、should方法必须在一起使用] + $es->must($where); + + + //执行查询 + $res = $es->query(); + dump($res); + +``` + +#### 查询一条数据(完全可以用查询列表功能,组合多条件筛选出一条数据): + +``` + //根据id查询一条数据 + //初始化es + $es = new BaseElasticsearch(); + + $es->index_sign = 'qa-item-';//设置index标识 + + $es->index = $es->setDefaultDate();//设置文档索引 + + $es->type = 'logs';//设置文档类型 + $res = $es->getById('AXbaw_CGbqvMMYZl-lOz'); + dump($res); +``` + + +#### 查询案例 + +``` +index = $es->setIndexDate($start_time, $end_time); + }else{ + $es->index = $es->setDefaultDate();//默认一个月 + } + $es->type = 'logs'; + $es->current_page = $limit ? intval($offset / $limit) + 1 : 1; + $es->limit = $limit; + //关键步骤 + //设置查询条件 + + //帐变类型:4=后台赠送,6首充奖励,7=报名,8=充值 + $change_type_arr = ['backsend'=>4,'firstprize'=>6,'signup'=>7,'recharge'=>8]; + //必须 + $where = [ + [ + 'range' => [ + $amount_field => ['gt' => -999999] + ] + ] + ]; + + //可选 + if(!empty($input_data['gameid'])){ + $where[] = [ + 'term' => [ + 'uid' => $input_data['gameid'] + ] + ]; + } + if(!empty($input_data['nickname'])){ + $where[] = [ + 'term' => [ + 'nickname' => $input_data['nickname'] + ] + ]; + } + //帐变类型 + if(!empty($input_data['change_type'])){ + $where[] = [ + 'term' => [ + 'statType' => array_flip($change_type_arr)[$input_data['change_type']] + ] + ]; + } + //帐变时间 + if(!empty($input_data['change_time'])){ + $date_arr = $es->strtounixtime($input_data['change_time']); + + $where[] = [ + 'range' => [ + 'unixtime' => [ + 'gte' => $date_arr['start_time'], + 'lte' => $date_arr['end_time'], + ] + ] + ]; + } + + //排序 + $es->order('unixtime', 'desc'); + + //昵称查询要先查uid 再去es查昵称 + + //$es->queryRange('unixtime', [ + // 'gt' => 1609750662180, + // 'lt' => 1609750662184 + //]); + //$es->queryFieldExist($amount_field); + $es->setQueryBool(); + //必须[setQueryBool & must,must_not, should必须在一起使用] + $es->must($where); + + //执行查询 + Tools::log_to_write_txt(['执行查询,入参:',$es->params], 'es_player_ticket_change_record'); + $res = $es->query(); + Tools::log_to_write_txt(['执行查询,出参:',$res], 'es_player_ticket_change_record'); + + //删除本地临时数据 + Db::execute('truncate table fa_zc_player_ticket_change_record'); + + if(empty($res['hits']['hits'])) return set_result(500, '未查询到相关数据.'); + + $lists = $res['hits']['hits']; + $this->total = $res['hits']['total']; + + //组装入库字段 + $items = []; + foreach ($lists as $list){ + $item = []; + $item['gameid'] = empty($list['_source']['uid'])?'':$list['_source']['uid']; + $item['nickname'] = empty($list['_source']['nickname'])?'':$list['_source']['nickname']; + $item['change_type'] = empty($change_type_arr[$list['_source']['statType']])?'0':$change_type_arr[$list['_source']['statType']].''; + + $item['amount'] = empty($list['_source'][$amount_field])?0:$list['_source'][$amount_field];//帐变额 + + $after_amount = empty($list['_source'][$after_amount_field])?0:$list['_source'][$after_amount_field]; + $item['after_amount'] = $after_amount; + $item['before_amount'] = $item['after_amount']-$item['amount'];//帐变前=帐变后-帐变额 + $item['change_time'] = floor($list['_source']['unixtime']/1000); + $item['api_key'] = $list['_id']; + $items[] = $item; + } + + //记录入库数据 + Tools::log_to_write_txt('记录入库数据 | '.json_encode($items, JSON_UNESCAPED_UNICODE), 'es_player_ticket_change_record'); + + //入库 + Db::table('fa_zc_player_ticket_change_record')->insertAll($items); + return set_result(200, 'ok'); + }catch (Exception $exception){ + Tools::log_to_write_txt('未查询到相关数据. error: '.$exception->getMessage().' | '.$exception->getTraceAsString(), 'es_player_ticket_change_record'); + return set_result(500, '未查询到相关数据.'.$exception->getMessage()); + } + } + + //现金 + function money(){ + try{ + //帐变前数额字段 + $amount_field = 'item1002';//现金帐变 只能是字符 + //帐变后数额字段 + $after_amount_field = 'package1002';//现金帐变 只能是字符 + + $input_data = json_decode(input('filter'), true); + $offset = input('offset', 5); + $limit = input('limit', 5); + $es = new BaseElasticsearch(); + //判断时间查询,动态生成索引 + if(!empty($input_data['change_time'])){ + $start_time = explode(' - ', $input_data['change_time'])[0]; + $end_time = explode(' - ', $input_data['change_time'])[1]; + $es->index = $es->setIndexDate($start_time, $end_time); + }else{ + $es->index = $es->setDefaultDate();//默认一个月 + } + $es->type = 'logs'; + $es->current_page = $limit ? intval($offset / $limit) + 1 : 1; + $es->limit = $limit; + //关键步骤 + //设置查询条件 + //帐变类型:3=比赛奖励,4=后台赠送 99暂定提现 + $change_type_arr = [ + //账变来源大分类 (draw:抽奖,exchange:兑换,winprize:比赛奖励,backsend:后台赠送,sign:签到,firstprize:首充奖励,signup:报名,recharge:充值,useprop:使用道具,cashout:提现,regsend:注册赠送) + 'winprize'=>3, + 'backsend'=>4, + 'cashout'=>99 + ]; + //必须 + $where = [ + [ + 'range' => [ + $amount_field => ['gt' => -999999] + ] + ], + ]; + + //可选 + if(!empty($input_data['gameid'])){ + $where[] = [ + 'term' => [ + 'uid' => $input_data['gameid'] + ] + ]; + } + if(!empty($input_data['nickname'])){ + $where[] = [ + 'term' => [ + 'nickname' => $input_data['nickname'] + ] + ]; + } + //帐变类型 + if(!empty($input_data['change_type'])){ + $where[] = [ + 'term' => [ + 'statType' => array_flip($change_type_arr)[$input_data['change_type']] + ] + ]; + } + //帐变时间 + if(!empty($input_data['change_time'])){ + $date_arr = $es->strtounixtime($input_data['change_time']); + + $where[] = [ + 'range' => [ + 'unixtime' => [ + 'gte' => $date_arr['start_time'], + 'lte' => $date_arr['end_time'], + ] + ] + ]; + } + + //排序 + $es->order('unixtime', 'desc'); + + //$es->queryRange('unixtime', [ + // 'gt' => 1609750662180, + // 'lt' => 1609750662184 + //]); + //$es->queryFieldExist($amount_field); + $es->setQueryBool(); + //必须[setQueryBool & must,must_not, should必须在一起使用] + $es->must($where); + + //执行查询 + Tools::log_to_write_txt(['执行查询,入参:',$es->params], 'es_player_cash_change_record'); + $res = $es->query(); + Tools::log_to_write_txt(['执行查询,出参:',$res], 'es_player_cash_change_record'); + + //删除本地临时数据 + Db::execute('truncate table fa_zc_player_cash_change_record'); + + if(empty($res['hits']['hits'])) return set_result(500, '未查询到相关数据.'); + + $lists = $res['hits']['hits']; + $this->total = $res['hits']['total']; + //组装入库字段 + $items = []; + foreach ($lists as $list){ + $item = []; + $item['gameid'] = empty($list['_source']['uid'])?'':$list['_source']['uid']; + $item['nickname'] = empty($list['_source']['nickname'])?'':$list['_source']['nickname']; + $item['change_type'] = empty($change_type_arr[$list['_source']['statType']])?'0':$change_type_arr[$list['_source']['statType']].''; + + $item['amount'] = empty($list['_source'][$amount_field])?'0':$list['_source'][$amount_field].'';//帐变额 + + $after_amount = empty($list['_source'][$after_amount_field])?0:$list['_source'][$after_amount_field]; + $item['after_amount'] = $after_amount; + $item['before_amount'] = $item['after_amount']-$item['amount'];//帐变前=帐变后-帐变额 + $item['change_time'] = floor($list['_source']['unixtime']/1000); + $item['api_key'] = $list['_id']; + $items[] = $item; + } + //记录入库数据 + Tools::log_to_write_txt('记录入库数据 | '.json_encode($items, JSON_UNESCAPED_UNICODE), 'es_player_cash_change_record'); + + //入库 + Db::table('fa_zc_player_cash_change_record')->insertAll($items); + return set_result(200, 'ok'); + }catch (Exception $exception){ + Tools::log_to_write_txt('未查询到相关数据. error: '.$exception->getMessage().' | '.$exception->getTraceAsString(), 'es_player_cash_change_record'); + return set_result(500, '未查询到相关数据.'.$exception->getMessage()); + } + } + + //礼券 + function gift(){ + try{ + //帐变前数额字段 + $amount_field = 'item1001';//只能是字符 + //帐变后数额字段 + $after_amount_field = 'package1001';//只能是字符 + + $input_data = json_decode(input('filter'), true); + $offset = input('offset', 5); + + $limit = input('limit', 5); + $es = new BaseElasticsearch(); + //判断时间查询,动态生成索引 + if(!empty($input_data['change_time'])){ + $start_time = explode(' - ', $input_data['change_time'])[0]; + $end_time = explode(' - ', $input_data['change_time'])[1]; + $es->index = $es->setIndexDate($start_time, $end_time); + }else{ + $es->index = $es->setDefaultDate();//默认一个月 + } + $es->type = 'logs'; + $es->current_page = $limit ? intval($offset / $limit) + 1 : 1; + $es->limit = $limit; + //关键步骤 + //设置查询条件 + //帐变类型:1=抽奖,2=兑换,3=比赛奖励,4=后台赠送,5=签到 + $change_type_arr = ['draw'=>1,'exchange'=>2,'winprize'=>3,'backsend'=>4,'sign'=>5]; + //必须 + $where = [ + [ + 'range' => [ + $amount_field => ['gt' => -999999] + ] + ], + ]; + + //可选 + if(!empty($input_data['gameid'])){ + $where[] = [ + 'term' => [ + 'uid' => $input_data['gameid'] + ] + ]; + } + if(!empty($input_data['nickname'])){ + $where[] = [ + 'term' => [ + 'nickname' => $input_data['nickname'] + ] + ]; + } + //帐变类型 + if(!empty($input_data['change_type'])){ + $where[] = [ + 'term' => [ + 'statType' => array_flip($change_type_arr)[$input_data['change_type']] + ] + ]; + } + //帐变时间 + if(!empty($input_data['change_time'])){ + $date_arr = $es->strtounixtime($input_data['change_time']); + + $where[] = [ + 'range' => [ + 'unixtime' => [ + 'gte' => $date_arr['start_time'], + 'lte' => $date_arr['end_time'], + ] + ] + ]; + } + + //排序 + $es->order('unixtime', 'desc'); + + //$es->queryRange('unixtime', [ + // 'gt' => 1609750662180, + // 'lt' => 1609750662184 + //]); + //$es->queryFieldExist($amount_field); + $es->setQueryBool(); + //必须[setQueryBool & must,must_not, should必须在一起使用] + $es->must($where); + + //执行查询 + Tools::log_to_write_txt(['执行查询,入参:'], 'es_player_gift_change_record'); + $res = $es->query(); + + Tools::log_to_write_txt(['执行查询,出参:', $es->params, $res], 'es_player_gift_change_record'); + + //删除本地临时数据 + Db::execute('truncate table fa_zc_player_gift_change_record'); + + if(empty($res['hits']['hits'])) return set_result(500, '未查询到相关数据.'); + + $lists = $res['hits']['hits']; + + $this->total = $res['hits']['total']; + //组装入库字段 + $items = []; + foreach ($lists as $list){ + $item = []; + $item['gameid'] = empty($list['_source']['uid'])?'':$list['_source']['uid']; + $item['nickname'] = empty($list['_source']['nickname'])?'':$list['_source']['nickname']; + $item['change_type'] = empty($change_type_arr[$list['_source']['statType']])?'0':$change_type_arr[$list['_source']['statType']].''; + + $item['amount'] = empty($list['_source'][$amount_field])?'0':$list['_source'][$amount_field].'';//帐变额 + + $after_amount = empty($list['_source'][$after_amount_field])?0:$list['_source'][$after_amount_field]; + $item['after_amount'] = $after_amount; + $item['before_amount'] = $item['after_amount']-$item['amount'];//帐变前=帐变后-帐变额 + $item['change_time'] = floor($list['_source']['unixtime']/1000); + $item['api_key'] = $list['_id']; + $items[] = $item; + } + //记录入库数据 + Tools::log_to_write_txt(['记录入库数据',$items], 'es_player_gift_change_record'); + + //入库 + Db::table('fa_zc_player_gift_change_record')->insertAll($items); + return set_result(200, 'ok', $res); + }catch (Exception $exception){ + Tools::log_to_write_txt('未查询到相关数据. error: '.$exception->getMessage().' | '.$exception->getTraceAsString(), 'es_player_gift_change_record'); + return set_result(500, '未查询到相关数据.'.$exception->getMessage()); + } + + } + +} +``` + +#### 聚合案例 + +##### sum求和 +``` +index = $es->setIndexDate($start_time, $end_time); + }else{ + $es->index = $es->setDefaultDate();//默认一个月 + } + + $es->type = 'logs'; + $es->current_page = 1; + $es->limit = 99999999; + //关键步骤 + //设置查询条件 + $where = [ + [ + 'term' => [ + 'statType' => 'recharge'//充值类型 + ] + ], + [ + 'range' => [ + //参赛券 + 'item1000' => [ + 'gte' => 0,//不可能是负数 + ] + ] + ], + [ + 'range' => [ + //参赛券 + 'item1002' => [ + 'lt' => 0,//item1002(奖励金)<0 + ] + ] + ] + ]; + //可选 + if(!empty($input_data['gameid'])){ + $where[] = [ + 'term' => [ + 'uid' => $input_data['gameid'] + ] + ]; + } + //时间 + if(!empty($input_data['order_itme'])){ + $date_arr = $es->strtounixtime($input_data['order_itme']); + $where[] = [ + 'range' => [ + 'unixtime' => [ + 'gte' => $date_arr['start_time'], + 'lte' => $date_arr['end_time'], + ] + ] + ]; + } + if(!empty($input_data['order_status'])){ + $where[] = [ + 'term' => [ + 'status' => $input_data['order_status'] + ] + ]; + } + + //排序 + //$es->order('unixtime', 'desc'); + //设置bool查询模式 + $es->setQueryBool(); + //必须[setQueryBool & must,must_not, should必须在一起使用] + $es->must($where); + $es->setAggs('item1000', 0); + //执行查询 + $res = $es->sum(); + + + return $res['aggregations']['sum']['value']; + } + + + /** + * desc:查询本月总充值 + * author:wh + * @return array|\think\response\Json + */ + function month(){ + + $input_data = json_decode(input('filter'), true); + + $es = new BaseElasticsearch(); + //判断时间查询,动态生成索引 + if(!empty($input_data['order_itme'])){ + $start_time = explode(' - ', $input_data['order_itme'])[0]; + $end_time = explode(' - ', $input_data['order_itme'])[1]; + $es->index = $es->setIndexDate($start_time, $end_time); + }else{ + $es->index = $es->setDefaultDate();//默认一个月 + } + + $es->type = 'logs'; + $es->current_page = 1; + $es->limit = 99999999; + //关键步骤 + //设置查询条件 + $where = [ + [ + 'term' => [ + 'statType' => 'recharge'//充值类型 + ] + ], + [ + 'range' => [ + //参赛券 + 'item1000' => [ + 'gte' => 0,//不可能是负数 + ] + ] + ], + [ + 'range' => [ + //参赛券 + 'item1002' => [ + 'lt' => 0,//item1002(奖励金)<0 + ] + ] + ], + [ + 'range' => [ + //参赛券 + 'unixtime' => [ + 'gt' => strtotime(date('Y-m').'-01').'000', + ] + ] + ] + ]; + //可选 + if(!empty($input_data['gameid'])){ + $where[] = [ + 'term' => [ + 'uid' => $input_data['gameid'] + ] + ]; + } + //时间 + if(!empty($input_data['order_itme'])){ + $date_arr = $es->strtounixtime($input_data['order_itme']); + $where[] = [ + 'range' => [ + 'unixtime' => [ + 'gte' => $date_arr['start_time'], + 'lte' => $date_arr['end_time'], + ] + ] + ]; + } + //订单状态(es的充值记录都是充值成功的数据) + //if(!empty($input_data['order_status'])){ + // $where[] = [ + // 'term' => [ + // 'status' => $input_data['order_status'] + // ] + // ]; + //} + + //排序 + //$es->order('unixtime', 'desc'); + //设置bool查询模式 + $es->setQueryBool(); + //必须[setQueryBool & must,must_not, should必须在一起使用] + $es->must($where); + $es->setAggs('item1000', 0); + //执行查询 + $res = $es->sum(); + + + return $res['aggregations']['sum']['value']; + } + /** + * desc:查询本周总充值 + * author:wh + * @return array|\think\response\Json + */ + function week(){ + + $input_data = json_decode(input('filter'), true); + + $es = new BaseElasticsearch(); + //判断时间查询,动态生成索引 + if(!empty($input_data['order_itme'])){ + $start_time = explode(' - ', $input_data['order_itme'])[0]; + $end_time = explode(' - ', $input_data['order_itme'])[1]; + $es->index = $es->setIndexDate($start_time, $end_time); + }else{ + $es->index = $es->setDefaultDate();//默认一个月 + } + + $es->type = 'logs'; + $es->current_page = 1; + $es->limit = 99999999; + //关键步骤 + //设置查询条件 + $where = [ + [ + 'term' => [ + 'statType' => 'recharge'//充值类型 + ] + ], + [ + 'range' => [ + //参赛券 + 'item1000' => [ + 'gte' => 0,//不可能是负数 + ] + ] + ], + [ + 'range' => [ + //参赛券 + 'item1002' => [ + 'lt' => 0,//item1002(奖励金)<0 + ] + ] + ], + [ + 'range' => [ + //本周 + 'unixtime' => [ + 'gte' => strtotime((new Date())->beginWeek()).'000', + 'lte' => strtotime((new Date())->endWeek()).'000', + ] + ] + ] + ]; + //可选 + if(!empty($input_data['gameid'])){ + $where[] = [ + 'term' => [ + 'uid' => $input_data['gameid'] + ] + ]; + } + //时间 + if(!empty($input_data['order_itme'])){ + $date_arr = $es->strtounixtime($input_data['order_itme']); + $where[] = [ + 'range' => [ + 'unixtime' => [ + 'gte' => $date_arr['start_time'], + 'lte' => $date_arr['end_time'], + ] + ] + ]; + } + //订单状态(es的充值记录都是充值成功的数据) + //if(!empty($input_data['order_status'])){ + // $where[] = [ + // 'term' => [ + // 'status' => $input_data['order_status'] + // ] + // ]; + //} + + //排序 + //$es->order('unixtime', 'desc'); + //设置bool查询模式 + $es->setQueryBool(); + //必须[setQueryBool & must,must_not, should必须在一起使用] + $es->must($where); + $es->setAggs('item1000', 0); + //执行查询 + $res = $es->sum(); + + + return $res['aggregations']['sum']['value']; + } + /** + * desc:查询今日总充值 + * author:wh + * @return array|\think\response\Json + */ + function today(){ + + $input_data = json_decode(input('filter'), true); + + $es = new BaseElasticsearch(); + //判断时间查询,动态生成索引 + if(!empty($input_data['order_itme'])){ + $start_time = explode(' - ', $input_data['order_itme'])[0]; + $end_time = explode(' - ', $input_data['order_itme'])[1]; + $es->index = $es->setIndexDate($start_time, $end_time); + }else{ + $es->index = $es->setDefaultDate();//默认一个月 + } + + $es->type = 'logs'; + $es->current_page = 1; + $es->limit = 99999999; + //关键步骤 + //设置查询条件 + $where = [ + [ + 'term' => [ + 'statType' => 'recharge'//充值类型 + ] + ], + [ + 'range' => [ + //参赛券 + 'item1000' => [ + 'gte' => 0,//不可能是负数 + ] + ] + ], + [ + 'range' => [ + //参赛券 + 'item1002' => [ + 'lt' => 0,//item1002(奖励金)<0 + ] + ] + ], + [ + 'range' => [ + //今日 + 'unixtime' => [ + 'gt' => strtotime(date('Y-m-d')).'000', + ] + ] + ] + ]; + //可选 + if(!empty($input_data['gameid'])){ + $where[] = [ + 'term' => [ + 'uid' => $input_data['gameid'] + ] + ]; + } + //时间 + if(!empty($input_data['order_itme'])){ + $date_arr = $es->strtounixtime($input_data['order_itme']); + $where[] = [ + 'range' => [ + 'unixtime' => [ + 'gte' => $date_arr['start_time'], + 'lte' => $date_arr['end_time'], + ] + ] + ]; + } + //订单状态(es的充值记录都是充值成功的数据) + //if(!empty($input_data['order_status'])){ + // $where[] = [ + // 'term' => [ + // 'status' => $input_data['order_status'] + // ] + // ]; + //} + + //排序 + //$es->order('unixtime', 'desc'); + //设置bool查询模式 + $es->setQueryBool(); + //必须[setQueryBool & must,must_not, should必须在一起使用] + $es->must($where); + $es->setAggs('item1000', 0); + //执行查询 + $res = $es->sum(); + + + return $res['aggregations']['sum']['value']; + } + + + + + +} +``` + +#### 或查询(相当于mysql or查询) +``` +/** + * desc:或查询(相当于mysql or查询) + * author:wh + * @return array|\think\response\Json + */ + function total(){ + + $input_data = json_decode(input('filter'), true); + + $es = new BaseElasticsearch(); + //判断时间查询,动态生成索引 + if(!empty($input_data['conversion_time'])){ + $start_time = explode(' - ', $input_data['conversion_time'])[0]; + $end_time = explode(' - ', $input_data['conversion_time'])[1]; + $es->index = $es->setIndexDate($start_time, $end_time); + }else{ + //$es->index = $es->setDefaultDate();//默认一个月 + //默认查询所有历史记录 + $es->index = $es->setIndexAllDate(); + } + + $es->type = 'logs'; + $es->current_page = 1; + $es->limit = 999999999; + //关键步骤 + //设置查询条件 + // + $where = [ + [ + 'term' => [ + 'statType' => 'exchange'//兑换类型 + ] + ], + + ]; + //可选 + if(!empty($input_data['gameid'])){ + $where[] = [ + 'term' => [ + 'uid' => $input_data['gameid'] + ] + ]; + } + //昵称 + if(!empty($input_data['nickname'])){ + $where[] = [ + 'term' => [ + 'nickname' => $input_data['nickname'] + ] + ]; + } + //时间 + if(!empty($input_data['conversion_time'])){ + $date_arr = $es->strtounixtime($input_data['conversion_time']); + $where[] = [ + 'range' => [ + 'unixtime' => [ + 'gte' => $date_arr['start_time'], + 'lte' => $date_arr['end_time'], + ] + ] + ]; + } + + $should_where = [ + [ + "range"=> [ + "item2001"=> [ + "gte"=> -9999999 + ] + ] + ], + [ + "range"=> [ + "item2002"=> [ + "gte"=> -9999999 + ] + ] + ], + [ + "range"=> [ + "item2005"=> [ + "gte"=> -9999999 + ] + ] + ], + [ + "range"=> [ + "item2010"=> [ + "gte"=> -9999999 + ] + ] + ] + ]; + + + //排序 + //$es->order('unixtime', 'desc'); + //设置bool查询模式 + $es->setQueryBool(); + //必须[setQueryBool & must,must_not, should必须在一起使用] + $es->must($where); + $es->should($should_where); + $es->setAggs('item1001', 1); + //执行查询 + $res = $es->sum(); + + return $res['aggregations']['sum']['value']; + } +``` \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/exception/BaseException.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/exception/BaseException.php new file mode 100644 index 0000000..6d73165 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/exception/BaseException.php @@ -0,0 +1,16 @@ +200, 'msg'=>'操作成功', 'data'=>[]] + if(is_null($result['code'])){ + //无code字段,抛出响应格式错误 + throw new ResponseException(ResponseException::RESPONSE_FORMAT_ERROR); + } \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/exception/SystemException.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/exception/SystemException.php new file mode 100644 index 0000000..1613281 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/exception/SystemException.php @@ -0,0 +1,60 @@ +getError(), 422); + //} + + // 请求异常 + //if ($e instanceof HttpException && request()->isAjax()) { + // return response($e->getMessage(), $e->getStatusCode()); + //} + + Tools::log_to_write_txt([ + 'error'=>'系统错误.'.$e->getMessage(), + 'input'=>input(), + 'error_info'=>$e->getTraceAsString() + ]); + + $title = '新的【系统异常】,请立即处理'; + //邮件通知管理员 + $mail_content = '

error:'.$e->getMessage().'

'; + $mail_content .= '

url:'.request()->url(true).'

'; + $mail_content .= '

ip:'.request()->ip().'

'; + $mail_content .= '

详情请登录宝塔查看日志

'; + + $emails = SundryConfig::val('admin_error_log_email'); + if(config('sys_env') == 'PROD'){//线上环境才发错误邮件 + EmailTool::email_to_person($title,$mail_content,$emails); + } + + if(request()->LogObj){ + request()->LogObj->flush(); + } + if(request()->ServeLogObj){ + request()->ServeLogObj->flush(); + } + + //可以在此交由系统处理 + return parent::render($e); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/exception/api/ApiException.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/exception/api/ApiException.php new file mode 100644 index 0000000..f2b795c --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/exception/api/ApiException.php @@ -0,0 +1,21 @@ +where('name',$menu_name)->data(['status'=>'normal'])->update(); + }else{ + Db::table('fa_auth_rule')->where('name',$menu_name)->data(['status'=>'hidden'])->update(); + } + } + return true; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/file/File.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/file/File.php new file mode 100644 index 0000000..3e45098 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/file/File.php @@ -0,0 +1,236 @@ + array(1) { + // ["path"] => string(25) "D:\wanghua\test_files/111" + // } + // [1] => array(1) { + // ["path"] => string(25) "D:\wanghua\test_files/222" + // } + //} + * + * + * + * + * @param $file_name_arr 存储文件路径、文件名称 + * + //array(64) { + // [0] => array(3) { + // ["path"] => string(25) "D:\wanghua\test_files/111" + // ["path_file"] => string(33) "D:\wanghua\test_files/111/111.txt" + // ["filename"] => string(7) "111.txt" + // } + // [1] => array(3) { + // ["path"] => string(25) "D:\wanghua\test_files/111" + // ["path_file"] => string(37) "D:\wanghua\test_files/111/1111111.txt" + // ["filename"] => string(11) "1111111.txt" + // } + //} + */ + function read_folder($folderPath,&$dirs,&$file_name_arr) { + // 获取指定目录下所有文件和文件夹 + $files = scandir($folderPath); + foreach ($files as $file) { + // 如果当前项为文件夹且不是"."或者".." + if (is_dir("$folderPath/$file") && !in_array($file, array('.', '..'))) { + $dirs[] = ['path'=>$folderPath.'/'.$file];//此时的$file是文件夹名称 + // 调用自身进行递归操作 + $this->read_folder("$folderPath/$file",$dirs,$file_name_arr); + } elseif (!in_array($file, array('.', '..'))){ // 如果当前项为文件而非"."或者".." + $file_name_arr[] = ['path'=>$folderPath,'path_file'=>"$folderPath/$file",'filename'=>$file]; + } + } + } + + /** + * desc:复制文件 + * + * author:wh + * @param array $sourceFileArr 源文件路径数组 path,path_file,filename + * + * @param string $destination_path 目标存放路径 D:\wanghua\test_files\new_files + * + * @param array $cp_type_arr 允许复制的文件后缀 eg:['jpg','png,'spine'] + * + * @param bool $is_cover 是否覆盖 默认false,不覆盖会以源文件名+微秒时间戳格式作为新的文件名 + * + * @param bool $is_continue 相同文件是否跳过复制 默认 true + * + * 实战案例: + * + function testcopy(){ + $source_dir = 'D:\wanghua\test_files';//源文件路径 + $destination_path = 'D:\wanghua\test_files\new_files';//目标文件路径 + + $dirs = [];//存储文件路径 + $file_name_arr = [];//含文件路径、名称 + $file_obj = new File(); + $file_obj->read_folder($source_dir,$dirs,$file_name_arr); + //dump($dirs); + //dump($file_name_arr);die; + $file_obj->copy($file_name_arr,$destination_path,['jpg','png,'spine']); + } + * + */ + function copy(array $sourceFileArr, string $destination_path, $cp_type_arr=['jpg','png'], $is_cover=false, $is_continue=false){ + //设置不超时 + set_time_limit(0); + //源文件路径 => 目标文件路径 + foreach ($sourceFileArr as $key=>$file_arr){ + if(!file_exists($file_arr['path_file'])){ + continue; + } + //验证目标文件夹路径 + if(!file_exists($destination_path)){ + mkdir($destination_path,0777,true); + } + $name_arr = explode('.',$file_arr['filename']); + //验证符合条件的文件类型 + if(!in_array($name_arr[1],$cp_type_arr)){ + continue; + } + if(file_exists($destination_path.'/'.$file_arr['filename'])){ + if($is_continue){ + continue;//相同文件跳过复制 + } + //覆盖 + if($is_cover){ + copy($file_arr['path_file'], $destination_path.'/'.$file_arr['filename']); + }else{ + //不覆盖,名称区分 + $microsecond = Tools::getMicrosecond(); + copy($file_arr['path_file'], $destination_path.'/'.$name_arr[0].'('.$microsecond.').'.$name_arr[1]); + } + }else{ + copy($file_arr['path_file'], $destination_path.'/'.$file_arr['filename']); + } + } + } + + /** + * desc:递归删除目录 + * + * $dir 物理路径 + * + * author:wh + */ + function recursive_delete($dir) { + if (!is_dir($dir)) { + return unlink($dir); + } + + $handle = opendir($dir); + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $path = $dir . DIRECTORY_SEPARATOR . $file; + if (is_dir($path)) { + $this->recursive_delete($path); + } else { + unlink($path); + } + } + closedir($handle); + rmdir($dir); + } + + + /** + * desc:解压ZIP文件并存储在本地 + * author:wh + * @param resource $file_stream 文件流 + * 例如:$response = $client->request('POST', $url, [ + 'headers' => $headers, + 'body' => $body, + ]); + // 处理响应 + //$file_stream = $response->getBody(); + * @param string $save_path 存储路径 + * @return array + * @throws \Exception + */ + function unzip($file_stream,$save_path){ + $zpi_file_name = 'zip_tmp_file_'.Tools::rand_str(20).'.zip'; + //这个目录将会在结束时删除 + $zipTempPath = $save_path.'/tmp_zip_files/'; // 临时存放ZIP文件的路径 + //压缩文件保存目录 + if(!file_exists($zipTempPath)){ + mkdir($zipTempPath,0777,true); + } + $zpi_file = $zipTempPath.$zpi_file_name; + // 保存ZIP文件到临时路径 + file_put_contents($zpi_file, $file_stream); + + $this->unzip_path = $zipTempPath.'unzip_files/'; + //解压目录 + if(!file_exists( $this->unzip_path)){ + mkdir( $this->unzip_path,0777,true); + } + // 解压ZIP文件 + $zip = new \ZipArchive(); + if ($zip->open($zpi_file) === TRUE) { + $zip->extractTo( $this->unzip_path);//移动到临时解压目录 + $zip->close(); + //遍历所有文件(file_ext后缀自行指定),遍历部分文件,如:*.json,*.zip,*.mp4,*.mp3等 + $wavFiles = $this->glob_by_ext( $this->unzip_path.$this->file_ext); + + //$urls = []; + //// 遍历解压后的文件,生成URL + //foreach ($wavFiles as $wavFilePath) { + // // 生成外部访问的URL + // $downloadUrl = request()->domain() . explode('public',$wavFilePath)[1]; + // $urls[] = $downloadUrl; + //} + //// 返回所有WAV文件的URL + //return ['wav_urls' => $urls]; + return $wavFiles; + } else { + throw new \Exception('Failed to open ZIP file.'); + } + } + + /** + * desc:获取指定扩展类型的文件列表 + * + * author:wh + * @param string $file_ext 如: wav,pm4,mp3,json,zip + * @return array|false + */ + function glob_by_ext($file_ext='*'){ + return glob( $this->unzip_path.'*.'.$file_ext); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/file/README.MD b/superadmin/vendor/wanghua/general-utility-tools-php/src/file/README.MD new file mode 100644 index 0000000..8459022 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/file/README.MD @@ -0,0 +1,5 @@ +## 文件处理 + +#### 文件夹读取,文件复制 + +#### 文件上传 \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/file/upload/FileUpload.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/file/upload/FileUpload.php new file mode 100644 index 0000000..961c665 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/file/upload/FileUpload.php @@ -0,0 +1,767 @@ +file_dir = $file_dir; + } + /** + * desc:单图 + * + * 【!!!必须确认php.ini中upload_max_filesize的配置足够大,否则上传失败但不会报错!!!】 + * + * author:wh + * @param string $file_upload_name 表单文件元素name值 + * @param int $size 大小,默认2M + * @param string $ext 允许的扩展 + * @return array + */ + function image($file_upload_name = 'file_upload', $size=10, $ext=''){ + if(!$this->uploadMaxFilesizeCheck($size)){ + return Tools::set_res(1, '上传文件受限'); + } + // 获取表单上传文件 例如上传了001.jpg + $file = request()->file($file_upload_name); + if(empty($file)){ + return Tools::set_res(1, '请上传文件'); + } + // 移动到框架应用根目录/uploads/ 目录下 + $outer_req_url = $this->file_dir.'/image'; + $physics_path = 'public'.$outer_req_url; + $root_path = explode('vendor',__DIR__)[0]; + $upload_dir = $root_path.$physics_path; + if(!is_dir($upload_dir)){ + mkdir($upload_dir, 0777, true); + } + if(empty($file)){ + return Tools::set_res(1, '上传文件不存在'); + } + + if(empty($ext)) $ext = $this->img_ext; + + $info = $file->validate(['size'=>$size * 1024 * 1024, 'ext'=>$ext])->move($upload_dir); + if($info){ + //原文件名 + $source_file_name = $info->getInfo('name'); + + //文件类型 + $file_type = $info->getInfo('type'); + + // 输出扩展名 jpg + $extension = $info->getExtension(); + + // 输出 20160820/42a79759f284b767dfcb2a0197904287.jpg + $save_name = $info->getSaveName(); + + // 输出 42a79759f284b767dfcb2a0197904287.jpg + $new_filename = $info->getFilename(); + + $relative_url = $outer_req_url.'/'.str_replace('\\', '/', $save_name); + $result = [ + 'prefix'=>request()->domain().$outer_req_url,//访问前缀 + 'source_file_name'=>$source_file_name, + 'file_type'=>$file_type, + 'extension'=>$extension, + 'save_name'=>str_replace('\\', '/', $save_name), + 'new_filename'=>$new_filename, + 'real_path'=>$this->dealPath($root_path.'public'.$relative_url),//物理路径 + 'outer_req_url'=>request()->domain().$relative_url, + 'outer_req_relative_url'=>$relative_url, + 'size'=>$info->getSize(), + ]; + return Tools::set_res(0, '上传成功', $result); + }else{ + // 上传失败获取错误信息 + return Tools::set_res(1, $file->getError()); + } + } + + /** + * desc:多图 + * + * 【!!!必须确认php.ini中upload_max_filesize的配置足够大,否则上传失败但不会报错!!!】 + * + * author:wh + * @param string $file_upload_name 表单文件元素name值 + * @param int $size 大小,默认2M + * @param string $ext 允许的扩展 + * @return array + */ + function images($file_upload_name = 'file_upload', $size=10, $ext=''){ + if(!$this->uploadMaxFilesizeCheck($size)){ + return Tools::set_res(1, '上传文件受限'); + } + // 获取表单上传文件 + $files = request()->file($file_upload_name); + if(empty($files)){ + return Tools::set_res(1, '请上传文件'); + } + $outer_req_url = $this->file_dir.'/image'; + $physics_path = 'public'.$outer_req_url; + $root_path = explode('vendor',__DIR__)[0]; + $upload_dir = $root_path.$physics_path; + if(!is_dir($upload_dir)){ + mkdir($upload_dir, 0777, true); + } + + if(empty($ext)) $ext = $this->img_ext; + + $result = []; + $error = []; + foreach($files as $file){ + if(!is_object($file)){ + return Tools::set_res(1, '请上传正确的文件'); + } + // 移动到框架应用根目录/uploads/ 目录下 + $info = $file->validate(['size'=>$size * 1024 * 1024, 'ext'=>$ext])->move($upload_dir); + if($info){ + //原文件名 + $source_file_name = $info->getInfo('name'); + + //文件类型 + $file_type = $info->getInfo('type'); + + // 输出扩展名 jpg + $extension = $info->getExtension(); + + // 输出 20160820/42a79759f284b767dfcb2a0197904287.jpg + $save_name = $info->getSaveName(); + + // 输出 42a79759f284b767dfcb2a0197904287.jpg + $new_filename = $info->getFilename(); + + $relative_url = $outer_req_url.'/'.str_replace('\\', '/', $save_name); + $result[] = [ + 'prefix'=>request()->domain().$outer_req_url,//访问前缀 + 'source_file_name'=>$source_file_name, + 'file_type'=>$file_type, + 'extension'=>$extension, + 'save_name'=>str_replace('\\', '/', $save_name), + 'new_filename'=>$new_filename, + 'real_path'=>$this->dealPath($root_path.'public'.$relative_url),//物理路径 + 'outer_req_url'=>request()->domain().$relative_url, + 'outer_req_relative_url'=>$relative_url, + 'size'=>$info->getSize(), + ]; + }else{ + //忽略错误 + // 上传失败获取错误信息 + $error[] = $file->getError(); + } + } + + $msg = '上传成功'; + $code = 0; + if(count($error) > 0){ + $msg = '上传失败;提示:'.implode(',', $error);//顺带返回错误信息 + $code = 1; + } + return Tools::set_res($code, $msg, $result); + } + + + /** + * desc:单文件 + * + * 【!!!必须确认php.ini中upload_max_filesize的配置足够大,否则上传失败但不会报错!!!】 + * + * author:wh + * @param string $file_upload_name 表单文件元素name值 + * @param int $size 大小,默认10M + * @param string $ext 允许的扩展 + * @return array + */ + function file($file_upload_name = 'file_upload', $size=10, $ext=''){ + if(!$this->uploadMaxFilesizeCheck($size)){ + return Tools::set_res(1, '上传文件受限'); + } + // 获取表单上传文件 例如上传了001.jpg + $file = request()->file($file_upload_name); + if(empty($file)){ + return Tools::set_res(1, '请上传文件'); + } + // 移动到框架应用根目录/uploads/ 目录下 + $outer_req_url = $this->file_dir.'/file'; + $physics_path = 'public'.$outer_req_url; + + $root_path = explode('vendor',__DIR__)[0]; + + $upload_dir = $root_path.$physics_path; + if(!is_dir($upload_dir)){ + mkdir($upload_dir, 0777, true); + } + if(empty($file)){ + return Tools::set_res(1, '上传文件不存在'); + } + + if(empty($ext)) $ext = $this->file_ext; + + $info = $file->validate(['size'=>$size * 1024 * 1024, 'ext'=>$ext])->move($upload_dir); + if($info){ + //原文件名 + $source_file_name = $info->getInfo('name'); + + //文件类型 + $file_type = $info->getInfo('type'); + + // 输出扩展名 jpg + $extension = $info->getExtension(); + + // 输出 20160820/42a79759f284b767dfcb2a0197904287.jpg + $save_name = $info->getSaveName(); + + // 输出 42a79759f284b767dfcb2a0197904287.jpg + $new_filename = $info->getFilename(); + + $relative_url = $outer_req_url.'/'.str_replace('\\', '/', $save_name); + $result = [ + 'prefix'=>request()->domain().$outer_req_url,//访问前缀 + 'source_file_name'=>$source_file_name, + 'file_type'=>$file_type, + 'extension'=>$extension, + 'save_name'=>str_replace('\\', '/', $save_name), + 'new_filename'=>$new_filename, + 'real_path'=>$this->dealPath($root_path.'public'.$relative_url),//物理路径 + 'outer_req_url'=>request()->domain().$relative_url, + 'outer_req_relative_url'=>$relative_url, + 'size'=>$info->getSize(), + ]; + return Tools::set_res(0, '上传成功', $result); + }else{ + // 上传失败获取错误信息 + return Tools::set_res(1, $file->getError()); + } + } + + /** + * desc:多文件 + * + * 【!!!必须确认php.ini中upload_max_filesize的配置足够大,否则上传失败但不会报错!!!】 + * + * author:wh + * @param string $file_upload_name 表单文件元素name值 + * @param int $size 单文件大小,默认10M + * @param string $ext 允许的扩展 + * @return array + */ + function files($file_upload_name = 'file_upload', $size=10, $ext=''){ + if(!$this->uploadMaxFilesizeCheck($size)){ + return Tools::set_res(1, '上传文件受限'); + } + + $root_path = explode('vendor',__DIR__)[0]; + // 获取表单上传文件 + $files = request()->file($file_upload_name); + if(empty($files)){ + return Tools::set_res(1, '请上传文件'); + } + $outer_req_url = $this->file_dir.'/file'; + $physics_path = 'public'.$outer_req_url; + $upload_dir = $root_path.$physics_path; + if(!is_dir($upload_dir)){ + mkdir($upload_dir, 0777, true); + } + + if(empty($ext)) $ext = $this->file_ext; + + $result = []; + $error = []; + foreach($files as $file){ + // 移动到框架应用根目录/uploads/ 目录下 + $info = $file->validate(['size'=>$size * 1024 * 1024, 'ext'=>$ext])->move($upload_dir); + if($info){ + //原文件名 + $source_file_name = $info->getInfo('name'); + + //文件类型 + $file_type = $info->getInfo('type'); + + // 输出扩展名 jpg + $extension = $info->getExtension(); + + // 输出 20160820/42a79759f284b767dfcb2a0197904287.jpg + $save_name = $info->getSaveName(); + + // 输出 42a79759f284b767dfcb2a0197904287.jpg + $new_filename = $info->getFilename(); + + $relative_url = $outer_req_url.'/'.str_replace('\\', '/', $save_name); + $result[] = [ + 'prefix'=>request()->domain().$outer_req_url,//访问前缀 + 'source_file_name'=>$source_file_name, + 'file_type'=>$file_type, + 'extension'=>$extension, + 'save_name'=>str_replace('\\', '/', $save_name), + 'new_filename'=>$new_filename, + 'real_path'=>$this->dealPath($root_path.'public'.$relative_url),//物理路径 + 'outer_req_url'=>request()->domain().$relative_url, + 'outer_req_relative_url'=>$relative_url, + 'size'=>$info->getSize(), + ]; + }else{ + //忽略错误 + // 上传失败获取错误信息 + $error[] = $file->getError(); + } + } + $msg = '上传成功'; + $code = 0; + if(count($error) > 0){ + $msg = '上传失败;提示:'.implode(',', $error);//顺带返回错误信息 + $code = 1; + } + return Tools::set_res($code, $msg, $result); + } + + + /** + * desc:单文件或多文件上传至阿里云OSS服务 + * + * author:wh + * @param array $config 配置 + * //阿里云oss配置 + 'aliyun_oss_config' => [ + //项目应用名称 + 'bucket'=>'wanlliuyinli-adm',//每创建一个bucket必须标明前缀,代表这个bucket属于哪个项目 + 'UserPrincipalName'=>'wanghua@1113242774600735.onaliyun.com', + 'Password'=>'0osE4cGo%tllnP1|uOQADPhM}Y?obR4U', + 'AccessKeyId'=>'LTAI5tPqn1n7jugviVoGqFfa', + 'AccessKeySecret'=>'BRoB5TdcUAFEuIR11BbN3R47Cm4Yep', + //https://help.aliyun.com/zh/oss/user-guide/regions-and-endpoints?spm=a2c4g.11186623.0.0.41ae2effA6oZar + 'region_id'=>'cn-chengdu',//这里配置不能包含oss-前缀,否则提示:Invalid signing region in Authorization header + //西南1(成都) + //oss-cn-chengdu + //oss-cn-chengdu.aliyuncs.com + //oss-cn-chengdu-internal.aliyuncs.com + 'endpoint'=>'oss-cn-chengdu.aliyuncs.com',//成都节点 + 'sts_endpoint'=>'sts.cn-chengdu.aliyuncs.com',//sts服务节点 + ] + * @param string $unique_id 唯一id,可以是用户id,可以是其它用于区分的标识,默认存放在(项目名称/控制器/方法/(唯一标识,可为空)/年月日/*.jpg) + * @param string $file_upload_name 文件上传控件的name值,必须带[],例如:file_upload[] + * @param string $oss_type oss类型,可选值:ali_cloud 阿里云,hua_wei 华为云(待扩展) + * @param int $size 单文件大小,默认10M + * @param string $ext 允许的文件扩展名 + * @return array + */ + function filesUploadToAliCloudOss($config,$unique_id='',$file_upload_name = 'file_upload',$size=10, $ext=''){ + return Mmodel::catch(function ()use ($config,$unique_id,$file_upload_name,$size,$ext){ + $ini_upload_max_filesize = str_replace('M','',ini_get('upload_max_filesize')); + if(($size > $ini_upload_max_filesize)){ + return Tools::set_res(1, '上传文件size受限,不能超过upload_max_filesize='.ini_get('upload_max_filesize').'M'); + } + // 获取表单上传文件 + $files = request()->file($file_upload_name); + if(empty($files)){ + return Tools::set_res(1, '请上传文件'); + } + //扩展 + if(empty($ext)) $ext = $this->file_ext; + //初始化阿里云OSS客户端 + $oss_obj= new Objects($config); + //默认存储地址 + $file_save_dir = $this->setFileSaveDir($unique_id); + $urls = []; + foreach($files as $file){ + if(!$file->isValid()){ + return Tools::set_res(1,'文件不合法'); + } + $check_size = $file->checkSize($size*1024*1024); + if(!$check_size){ + return Tools::set_res(1,'文件大小超限'); + } + $check_res = $file->checkExt($ext); + if(!$check_res){ + return Tools::set_res(1,'请上传合法文件'); + } + $extension = explode('.',$file->getInfo('name'))[1]; + $new_filename = 'file_'.Tools::rand_str(18).'.'.explode('.',$file->getInfo('name'))[1]; + //必须加后缀 + $filename = $file_save_dir.$new_filename; + + // 从文件流上传 + $res = $oss_obj->fileUpload($filename, $file->getInfo('tmp_name'));//上传之前的临时文件物理地址 + $urls[] = [ + 'prefix'=>explode('.com',$res['info']['url'])[0].'.com',//访问前缀 + 'source_file_name'=>$file->getInfo('name'),//源文件名 + 'size'=>$file->getInfo('size'),//大小(b) + 'extension'=>$extension,//扩展名 + 'file_type'=>$file->getInfo('type'),//文件后缀 + 'save_name'=>$filename,//保存文件名 + 'new_filename'=>$new_filename,//新文件名 + 'real_path'=>$res['info']['url'],//实际路径 + 'outer_req_url'=>$res['info']['url'],//外部访问地址 + 'outer_req_relative_url'=>$res['info']['url'],//外部访问相对地址 + ]; + } + return Tools::set_ok('ok', $urls); + }); + } + + /** + * desc:单文件上传至阿里云OSS + * + * author:wh + * @param array $config 配置 + * //阿里云oss配置 + 'aliyun_oss_config' => [ + //项目应用名称 + 'bucket'=>'wanlliuyinli-adm',//每创建一个bucket必须标明前缀,代表这个bucket属于哪个项目 + 'UserPrincipalName'=>'wanghua@1113242774600735.onaliyun.com', + 'Password'=>'0osE4cGo%tllnP1|uOQADPhM}Y?obR4U', + 'AccessKeyId'=>'LTAI5tPqn1n7jugviVoGqFfa', + 'AccessKeySecret'=>'BRoB5TdcUAFEuIR11BbN3R47Cm4Yep', + //https://help.aliyun.com/zh/oss/user-guide/regions-and-endpoints?spm=a2c4g.11186623.0.0.41ae2effA6oZar + 'region_id'=>'cn-chengdu',//这里配置不能包含oss-前缀,否则提示:Invalid signing region in Authorization header + //西南1(成都) + //oss-cn-chengdu + //oss-cn-chengdu.aliyuncs.com + //oss-cn-chengdu-internal.aliyuncs.com + 'endpoint'=>'oss-cn-chengdu.aliyuncs.com',//成都节点 + 'sts_endpoint'=>'sts.cn-chengdu.aliyuncs.com',//sts服务节点 + ] + * @param string $unique_id 唯一id,可以是用户id,可以是其它用于区分的标识,默认存放在(项目名称/控制器/方法/(唯一标识,可为空)/年月日/*.jpg) + * @param string $file_upload_name 文件上传控件的name值,例如:file_upload + * @param string $oss_type oss类型,可选值:ali_cloud 阿里云,hua_wei 华为云(待扩展) + * @param int $size 单文件大小,默认10M + * @param string $ext 允许的文件扩展名 + * @return array + */ + function fileUploadToAliCloudOss($config,$unique_id='',$file_upload_name = 'file_upload',$size=10, $ext=''){ + return Mmodel::catch(function ()use ($config,$unique_id,$file_upload_name,$size,$ext){ + $ini_upload_max_filesize = str_replace('M','',ini_get('upload_max_filesize')); + if(($size > $ini_upload_max_filesize)){ + return Tools::set_res(1, '上传文件size受限,不能超过upload_max_filesize='.ini_get('upload_max_filesize').'M'); + } + // 获取表单上传文件 + $file = request()->file($file_upload_name); + if(empty($file)){ + return Tools::set_res(1, '请上传文件'); + } + //扩展 + if(empty($ext)) $ext = $this->file_ext; + //初始化阿里云OSS客户端 + $oss_obj= new Objects($config); + //默认存储地址 + $file_save_dir = $this->setFileSaveDir($unique_id); + $urls = []; + if(!$file->isValid()){ + return Tools::set_res(1,'文件不合法'); + } + $check_size = $file->checkSize($size*1024*1024); + if(!$check_size){ + return Tools::set_res(1,'文件大小超限'); + } + $check_res = $file->checkExt($ext); + if(!$check_res){ + return Tools::set_res(1,'请上传合法文件'); + } + $extension = explode('.',$file->getInfo('name'))[1]; + $new_filename = 'file_'.Tools::rand_str(18).'.'.explode('.',$file->getInfo('name'))[1]; + //必须加后缀 + $filename = $file_save_dir.$new_filename; + + // 从文件流上传 + $res = $oss_obj->fileUpload($filename, $file->getInfo('tmp_name'));//上传之前的临时文件物理地址 + $urls[] = [ + 'prefix'=>explode('.com',$res['info']['url'])[0].'.com',//访问前缀 + 'source_file_name'=>$file->getInfo('name'),//源文件名 + 'size'=>$file->getInfo('size'),//大小(b) + 'extension'=>$extension,//扩展名 + 'file_type'=>$file->getInfo('type'),//文件后缀 + 'save_name'=>$filename,//保存文件名 + 'new_filename'=>$new_filename,//新文件名 + 'real_path'=>$res['info']['url'],//实际路径 + 'outer_req_url'=>$res['info']['url'],//外部访问地址 + 'outer_req_relative_url'=>$res['info']['url'],//外部访问相对地址 + ]; + return Tools::set_ok('ok', $urls); + }); + } + + /** + * desc:设置文件保存目录 + * author:wh + * @param string $unique_id 唯一id,可以是用户id,可以是其它用于区分的标识 + * @return string + */ + function setFileSaveDir($unique_id=''){ + if($unique_id){ + $unique_id = $unique_id.'/'; + } + //项目名称 + $project_name = Tools::get_project_name(); + //控制器方法路径 + $ctl = strtolower(request()->controller().'/'.request()->action()); + return $project_name.'/'.$ctl.'/'.$unique_id.date('Ymd').'/';//日期 + } + /** + * desc:检测size是否在ini配置范围 + * + * author:wh + * @param $size + * @return bool + */ + private function uploadMaxFilesizeCheck($size){ + $ini_upload_max_filesize = str_replace('M','',ini_get('upload_max_filesize')); + return $size<=$ini_upload_max_filesize; + } + + + /** + * desc:上传base64格式图片【文件不用base64传输】 + * author:wh + * @param string $file_upload_name + * @param int $size + * @param array $ext + * @return array + * @throws \Exception + */ + function uploadBase64Img($file_upload_name = 'file_upload', $size=10, $ext=''){ + $source = input($file_upload_name, 'file_upload'); + $file_size = strlen($source); + if($file_size > $size * 1024 * 1024){ + return Tools::set_res(1, '上传失败,大小限制'); + } + if(preg_match('/^(data:\s*image\/(\w+);base64,)/', $source, $result) < 1){ + return Tools::set_res(1, '上传文件不合法'); + } + $type = $result[2]; + + if(empty($ext)) $ext = $this->file_ext; + + if(!in_array($type, explode($ext))){ + return Tools::set_res(1, '上传失败,类型限制'); + } + $result = base64_image_content($source); + if(false === $result){ + return Tools::set_res(1, '上传失败'); + } + return Tools::set_res(0, '上传成功', $result); + } + + /** + * desc:批量上传base64格式图片【文件不用base64传输】 + * author:wh + * @param string $file_upload_name + * @param int $size + * @param array $ext + * @return array + * @throws \Exception + */ + function uploadBase64Imgs($file_upload_name = 'file_upload', $size=10, $ext=''){ + $source_list = input($file_upload_name, 'file_upload'); + if(!is_array($source_list)){ + return Tools::set_res(1, '参数错误'); + } + + if(empty($ext)) $ext = $this->file_ext; + + $result = []; + $error = []; + foreach ($source_list as $source){ + $file_size = strlen($source); + if($file_size > $size * 1024 * 1024){ + $error[] = '上传失败,大小限制'; + continue; + } + if(preg_match('/^(data:\s*image\/(\w+);base64,)/', $source, $temp) < 1){ + $error[] = '上传文件不合法'; + continue; + } + $type = $temp[2]; + if(!in_array($type, explode(',', $ext))){ + $error[] = '类型限制'; + continue; + } + $up_result = base64_image_content($source); + if(false === $up_result){ + $error[] = '上传失败'; + continue; + } + $result[] = $up_result; + } + + $msg = '上传成功'; + $code = 0; + if(count($error) > 0){ + $msg = '上传失败;提示:'.implode(',', $error);//顺带返回错误信息 + $code = 1; + } + return Tools::set_res($code, $msg, $result); + } + + /** + * desc:优化路径格式 + * author:wh + */ + private function dealPath(string $path){ + $str = str_replace('\\','/',$path); + + return str_replace('//','/',$str); + } + + + /** + * desc:curl上传文件,php上传文件到远程服务器地址 + * + * [php curl模拟文件上传] + * + * 依赖库:"guzzlehttp/guzzle": "^7.8", + * + * author:wh + * @param $url + * @param $params + * @param false[] $config + */ + function uploadCurlFiles($url,$params, $config=['verify' => false]){ + if(empty($this->unique_key)){ + throw new \Exception('UNIQUE_KEY 不能为空'); + } + if(empty($this->file_lists_urls)){ + throw new \Exception('文件地址列表不能为空'); + } + // 创建 Guzzle HTTP 客户端实例 + $client = new Client($config); + + // 定义要上传的文件列表 + $files = [ + //[ + // 'name' => 'file1', // 服务器接收的文件字段名 + // 'contents' => Utils::tryFopen('D:\wanghua\projects\big_world_projects\universal_gravitation\public\uploads\20240506\dc1c441d81d48565ac6817b89d0f8bef.mp4', 'r'), // 文件路径 + // 'filename' => 'dc1c441d81d48565ac6817b89d0f8bef.mp4', // 文件名,可自定义 + //], + //[ + // 'name' => 'files', + // 'contents' => Utils::tryFopen('D:\wanghua\projects\big_world_projects\universal_gravitation\public\uploads\20240506\f80179b23b60619fba8033bfd64f8817.mp4', 'r'), + // 'filename' => 'f80179b23b60619fba8033bfd64f8817.mp4', + //], + //['name'=>'prompt','contents'=>$prompt], + //['name'=>'tts_url','contents'=>$tts_url] + // 可以继续添加更多文件... + ]; + //print_r($params); + foreach ($params as $key=>$val){ + $files[] = ['name'=>$key,'contents'=>$val]; + } + $controller = strtolower(request()->controller()); + $action = strtolower(request()->action()); + $save_path = Tools::get_root_path() . "public/uploads/{$controller}/{$action}/".$this->unique_key; + if(!file_exists($save_path)){ + mkdir($save_path,0777,true); + } + + //文件远程可访问地址列表 + foreach ($this->file_lists_urls as $name=>$video_url) { + //处理可用地址(可能存储在本地或oss) + $video_url = $this->get_usable_url($video_url); + $files[] = [ + 'name'=>"files",//服务器接收的文件字段名 + //读取地址文件流(本地地址或远程地址均可,本地真实物理地址也行) + 'contents'=>Utils::tryFopen($video_url, 'r'), + 'filename'=>$name,//可自定义 文件名 + ]; + } + + // 构建多部分表单数据 + $multipartStream = new MultipartStream($files); + $boundary = $multipartStream->getBoundary(); + $headers = [ + 'Content-Type' => "multipart/form-data; boundary={$boundary}", + ]; + + // 准备请求体 + $body = $multipartStream->getContents(); + + // 发起 POST 请求 + try { + $postinfo = [ + 'headers' => $headers, + 'body' => $body, + ]; + //上传 + $response = $client->request('POST', $url, $postinfo); + // 处理响应 + //$responseBody = (string) $response->getBody(); + // 检查状态码 + //if ($response->getStatusCode() !== 200) { + // return false; + //} + return $response;//返回上传结果 + } catch (\GuzzleHttp\Exception\RequestException $e) { + // 错误处理 + //echo "Error: " . $e->getMessage(); + Tools::error_txt_log($e); + return false; + } + } + + /** + * desc:获取视频可用地址(兼容远程地址或本地地址) + * author:wh + * @param $video_url + * @return string|string[] + */ + private function get_usable_url($video_url){ + if(strpos($video_url,'http')!==false){ + return $video_url;//远程地址 + } + return Tools::get_root_path().$video_url;//本地地址 + } + + + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/file/upload/README.MD b/superadmin/vendor/wanghua/general-utility-tools-php/src/file/upload/README.MD new file mode 100644 index 0000000..df6ecdf --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/file/upload/README.MD @@ -0,0 +1 @@ +## 文件上传类库 \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/framework/BaseController.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/framework/BaseController.php new file mode 100644 index 0000000..1ae2a73 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/framework/BaseController.php @@ -0,0 +1,219 @@ +checkMaintain(); + if ($chm['is_maintain']) { + if ($chm['users_ids']) { + //解析openid + if (!in_array(api_user_info('id'), explode(',', $chm['users_ids']))) { + //白名单之外维护中 + //Tools::log_to_write_txt([ + // '维护测试'=>$chm['users_ids'], + // 'my'=>index_user_openid() + //]); + return $this->error($chm['msg']); + } + } else { + //不存在,直接维护中 + return $this->error($chm['msg'] . '!'); + } + } + //校验系统维护状态 end + } + + /** + * desc:空控操作 + * author:wh + */ + function _empty($info='') + { + + } + /** + * desc:检查路径维护状态 + * + * “===”完全匹配,不能使用like + * + * author:wh + */ + protected function checkMaintain(){ + $configs = Db::table(TabConf::$fa_sys_maintain_config) + ->where('status','1') + ->cache() + ->select(); + + //模块 + $strmodule = request()->module(); + foreach ($configs as $config){ + if($strmodule == $config['url']){ + //模块维护中 + return ['is_maintain'=>true,'msg'=>$config['msg'],'users_ids'=>$config['users_ids']]; + } + } + + //模块/控制器 + $strcontroller = strtolower(request()->module().'/'.request()->controller()); + foreach ($configs as $config){ + if($strcontroller == $config['url']){ + //模块维护中 + return ['is_maintain'=>true,'msg'=>$config['msg'],'users_ids'=>$config['users_ids']]; + } + } + + //模块/控制器/方法 + $straction = strtolower(request()->module().'/'.request()->controller().'/'.request()->action()); + foreach ($configs as $config){ + if($straction == $config['url']){ + //模块维护中 + return ['is_maintain'=>true,'msg'=>$config['msg'],'users_ids'=>$config['users_ids']]; + } + } + + //未维护 + return ['is_maintain'=>false,'msg'=>'服务运行中']; + } + /** + * desc:清空系统缓存 + * + /index/Test/clearCache + * + * author:wh + */ + function clearCache(){ + + Tools::clear_cache(); + } + /** + * eg:/index/test/buildTablesConf + * + * desc:构建统一的表名配置 + * + * author:wh + */ + function buildTablesConf(){ + + (new MySqlTools())->buildTablesConf(); + + } + + /** + * desc:创建&更新接口文档 + * + * 默认存放在/public/api_docs/api_list.md + * + * author:wh + */ + function buildApiDoc() + { + $obj = new ApiDocument(); + //根据自己的实际情况设置直接继承类(仅供参考) + $obj->extends_base_class = 'app\\api\\controller\\BaseHttpApi,wanghua\\general_utility_tools_php\\framework\\base\\BaseWechatAuthController'; + //设置过滤的类(仅供参考) + $obj->setFilterClass([ + 'BaseCommonController', + 'BaseHttpApi', + 'BaseWssApi', + 'Wsspush', + 'BaseController', + 'BaseAuthController', + //'BaseWechatAuthController', + 'BasePublicController' + ]); + $obj->setFilterFunction([ + 'buildApiDoc', + 'buildTablesConf', + 'checkfailed', + 'clearCache', + 'defaultAuth', + 'operateLog', + 'checkMaintain', + '_empty' + ]); + //构建接口文档 + $obj->buildDoc(); + //生成html + $html = $obj->buildApiDocHtml(); + $path = Tools::get_root_path().'/public/api_docs/'; + if(!file_exists($path)){ + mkdir($path,0777,true); + } + file_put_contents($path.'api_list.html',$html); + echo "api_list.html"; + + } + + /** + * desc:用户操作日志 + * author:wh + */ + protected function operateLog($msg,$users_id=0,$username='',$name=''){ + $tbname = 'fa_users_operate_log'; + $sql = " + CREATE TABLE IF NOT EXISTS `{$tbname}` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', + `users_id` int(10) unsigned DEFAULT '0' COMMENT '用户ID', + `username` varchar(15) DEFAULT '' COMMENT '用户名', + `name` varchar(20) DEFAULT '' COMMENT '名称', + `url` varchar(60) DEFAULT '' COMMENT 'URL', + `msg` varchar(90) DEFAULT '' COMMENT '做了什么', + `ip` varchar(30) DEFAULT '' COMMENT '登录ip', + `input` text COMMENT '提交参数', + `create_time` timestamp NULL DEFAULT NULL COMMENT '登陆时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=54 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='用户操作日志';"; + + if(cache('cache_db_tables_now_project')){ + $table_arr = cache('cache_db_tables_now_project'); + }else{ + $table_arr = Tools::get_tables(); + } + //表是否存在 + if(!in_array($tbname,$table_arr)){ + cache('cache_db_tables_now_project',$table_arr); + Db::execute($sql); + } + + $data = [ + 'users_id'=>$users_id, + 'username'=>$username, + 'name'=>$name, + 'url'=>request()->url(), + 'msg'=>$msg, + 'ip'=>request()->ip(), + 'input'=>json_encode(input(),JSON_UNESCAPED_UNICODE), + ]; + Db::table($tbname)->data($data)->insert(); + + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/framework/README.MD b/superadmin/vendor/wanghua/general-utility-tools-php/src/framework/README.MD new file mode 100644 index 0000000..789b3a0 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/framework/README.MD @@ -0,0 +1 @@ +### this classes only to thinkphp5+ \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/framework/base/BaseAuthController.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/framework/base/BaseAuthController.php new file mode 100644 index 0000000..a73e32d --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/framework/base/BaseAuthController.php @@ -0,0 +1,80 @@ +auth_err_redirect_url = $err_redirect_url; + } + + } + + /** + * desc:默认鉴权 + * author:wh + * @return bool + */ + function defaultAuth(){ + $params = input(); + if(empty($params['nonce'])){ + Tools::log_to_write_txt(['服务被拒绝,鉴权参数缺失:nonce。params'=>input()]); + //跳转至错误中转控制器 + return $this->response($this->auth_err_redirect_url,['title'=>'服务被拒绝. permission denied']); + } + if(empty($params['timestamp'])){ + Tools::log_to_write_txt(['服务被拒绝,鉴权参数缺失:timestamp。params'=>input()]); + //跳转至错误中转控制器 + return $this->response($this->auth_err_redirect_url,['title'=>'服务被拒绝. permission denied.']); + } + if(empty($params['sign'])){ + Tools::log_to_write_txt(['服务被拒绝,鉴权参数缺失:sign。params'=>input()]); + //跳转至错误中转控制器 + return $this->response($this->auth_err_redirect_url,['title'=>'服务被拒绝. permission denied。']); + } + $sign = $params['sign']; + unset($params['sign']); + if(Tools::signature($params) != $sign){ + + Tools::log_to_write_txt(['签名失败,服务被拒绝.'=>input()]); + + //跳转至错误中转控制器 + return $this->response($this->auth_err_redirect_url,['title'=>'服务被拒绝. permission denied!']); + } + return true; + } + protected function response($url,$params){ + + if(Request::instance()->isAjax() || Request::instance()->isPost()){ + return json(Tools::set_res(500,$params['title'],['url'=>$url])); + } + //跳转至错误中转控制器 + return $this->redirect(url($url,$params)); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/framework/base/BasePublicController.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/framework/base/BasePublicController.php new file mode 100644 index 0000000..e2f9fa6 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/framework/base/BasePublicController.php @@ -0,0 +1,29 @@ +assign('index_msg', cache('index_msg_alert_cache_time')); + + //线上环境加载微信授权 + if (config('sys_env') == 'PROD') { + $wx_user_info = session('wx_user_info'); + if (empty($wx_user_info['openid'])) { + //重定向之前,保存当前url, 在获取授权信息之后,回跳到授权之前的网页地址 + session('redirect_before_url_session', request()->url(true)); + $ver = Tools::get_thinkphp_version(); + if($ver == '5.0'){ + $url = url('index/Wexinauth/usrAuth','',false,true); + }else{ + $url = request()->domain().url('index/Wexinauth/usrAuth'); + } + //没有则重定向去授权 + return $this->redirect($url); + } + + $this->saveWechatUser($wx_user_info); + } + + + + + } + + + /** + * desc:周期更新当前用户信息 + * author:wh + * @param $wx_user_info + */ + private function saveWechatUser($wx_user_info) + { + try { + $wechat_user = $this->getWxUserByOpenid($wx_user_info['openid']); + if (empty($wechat_user)) { + return $this->insertInfo($wx_user_info); + } + //扩展,按周期更新,而不是不更新 + if (empty($wechat_user['update_time']) || time() - strtotime($wechat_user['update_time']) > 5 * 3600) { + + return $this->updateUser($wx_user_info); + } + + + } catch (\Exception $e) { + Tools::log_to_write_txt([ + 'error' => '存储异常.' . $e->getMessage(), + 'wx_user_info' => $wx_user_info, + 'error_info' => $e->getTraceAsString() + ]); + } + } + + /** + * desc:新增微信用户信息 + * author:wh + * @param $wx_user_info \app\index\model\微信用户 + * + * { + * "openid":"or9D2vs863Ky5Py2ovkAiu9XFLO4", + * "nickname":"起源果蔬副食大华", + * "sex":0, + * "language":"", + * "city":"", + * "province":"", + * "country":"", + * "headimgurl":"https://thirdwx.qlogo.cn/mmopen/vi_32/joiaA475nx3fJiaqx0ibdnWo4A7Q3uCgu2hsribI0ATLItORjuUgCSP8mCaBkqL61ibGojib4pQYX1djUhZpF5zoqpSg/132", + * "privilege":[] + * } + */ + private function insertInfo(array $wx_user_info) + { + + if (isset($wx_user_info['privilege'])) { + $wx_user_info['privilege'] = json_encode($wx_user_info['privilege']); + } + $data = [ + 'nickname' => $wx_user_info['nickname'], + 'country' => $wx_user_info['country'], + 'province' => $wx_user_info['province'], + 'city' => $wx_user_info['city'], + 'headimage' => $wx_user_info['headimgurl'], + 'language' => $wx_user_info['language'], + 'openid' => $wx_user_info['openid'], + 'unionid' => isset($wx_user_info['unionid']) ? $wx_user_info['unionid'] : '', + 'privilege' => $wx_user_info['privilege'], + 'sex' => $wx_user_info['sex'], + //'arm_group' => '', + //'score' => 0,//积分 + //'group_buy_earnings' => 0,//拼团收益 + //'water_drop_balance' => 0,//水滴 + ]; + Db::table($this->wechat_user_table_name) + ->data($data) + ->insert(); + } + + /** + * desc:更新当前用户信息 + * + * author:wh + */ + private function updateUser($wx_user_info) + { + $data = [ + 'nickname' => $wx_user_info['nickname'], + 'headimage' => $wx_user_info['headimgurl'], + 'unionid' => isset($wx_user_info['unionid']) ? $wx_user_info['unionid'] : '', + ]; + Db::table($this->wechat_user_table_name) + ->data($data) + ->where('openid', index_user_openid()) + ->update(); + } + + /** + * desc:根据unionid获取用户信息 + * author:wh + * @param string $unionid + */ + private function getWxUserByUnionid(string $unionid) + { + return Db::table($this->wechat_user_table_name) + ->field('id') + ->where('unionid', $unionid) + ->find(); + } + + /** + * desc:获取微信用户信息 + * author:wh + * @param string $openid + */ + private function getWxUserByOpenid(string $openid) + { + return Db::table($this->wechat_user_table_name) + //->field('id') + ->where('openid', $openid) + ->find(); + } + + /** + * desc:根据openid获取昵称 + * author:wh + * @param string $openid + */ + private function getNicknameByOpenid(string $openid) + { + return Db::table($this->wechat_user_table_name) + ->where('openid', $openid) + ->value('nickname'); + } + + + + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/ftp/Ftp.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/ftp/Ftp.php new file mode 100644 index 0000000..e8c8806 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/ftp/Ftp.php @@ -0,0 +1,213 @@ +conn_id = ftp_connect($host, $port); + if(!$this->conn_id) throw new \Exception("FTP服务器连接失败"); + $login_res = ftp_login($this->conn_id, $user, $password); + if(!$login_res) throw new \Exception("FTP服务器登陆失败"); + ftp_pasv($this->conn_id, false); // true打开被动模拟,默认关闭,如果总是失败,可试试true + } + + /** + * desc:打开被动模拟,登录后使用 + * author:wh + */ + function set_ftp_pasv(){ + ftp_pasv($this->conn_id, true); // true打开被动模拟 + } + + /** + * 上传文件 + * param $path 本地文件路径[包含文件名] + * param $newPath 目标目录[包含文件名][默认存放在ftp用户被授权的根路径] + * param bool $type 若目标目录不存在则新建 + */ + function up_file($path, $newPath, $type = true) + { + if ($type) $this->dir_mkdirs($newPath); + $this->off = ftp_put($this->conn_id, $newPath, $path, FTP_BINARY); + if (!$this->off) + return ['code'=>500, 'msg'=>'文件上传失败,请检查权限及路径是否正确!']; + return ['code'=>200, 'msg'=>'文件移动成功']; + } + + /** + * 移动文件 + * 特别注意: + * $path 如果是当前目录,则必须以 “/”开始 + * 正确:/test2.zip + * 错误:test2.zip + * param $path 原路径(含文件名) + * param $newPath 新路径(含文件名) + * param bool $type 若目标目录不存在则新建 + */ + function move_file($path, $newPath, $ftpbasedir, $type = true) + { + if ($type) $this->ftp_mksubdirs($ftpbasedir,$newPath); + $this->off = ftp_rename($this->conn_id, $path, $newPath); + if (!$this->off) + return ['code'=>500, 'msg'=>'文件移动失败,请检查权限及原路径是否正确!']; + return ['code'=>200, 'msg'=>'文件移动成功']; + } + + /** + * 文件改名 + * param $path 原路径 + * param $newPath 新路径 + */ + function rename_file($path, $newPath) + { + $this->off = ftp_rename($this->conn_id, $path, $newPath); + if (!$this->off) + return ['code'=>500, 'msg'=>'文件改名失败,请检查权限及原路径是否正确!']; + return ['code'=>200, 'msg'=>'文件改名成功']; + } + + /** + * 复制文件 + * 说明:由于FTP无复制命令,本方法变通操作为:下载后再上传到新的路径 + * param $path 原路径 + * param $newPath 新路径 + * param bool $type 若目标目录不存在则新建 + */ + function copy_file($path, $newPath, $type = true) + { + $downPath = "c:/tmp.dat"; + $this->off = ftp_get($this->conn_id, $downPath, $path, FTP_BINARY);// 下载 + if (!$this->off) + return ['code'=>500, 'msg'=>'文件复制失败,请检查权限及原路径是否正确!']; + + $this->up_file($downPath, $newPath, $type); + return ['code'=>200, 'msg'=>'文件复制成功']; + } + + /** + * 删除文件 + * param $path 路径 + */ + function del_file($path) + { + $this->off = ftp_delete($this->conn_id, $path); + if (!$this->off) + return ['code'=>500, 'msg'=>'文件删除失败,请检查权限及路径是否正确!']; + + return ['code'=>200, 'msg'=>'文件删除成功']; + } + + /** + * desc:删除给定目录 + * 注: + * 删除多个目录需重复调用 + * author:wh + * param $dir + * return bool + */ + function del_dir($dir){ + + + $children = ftp_nlist ($this->conn_id, $dir); + if(!$children) { + ftp_rmdir($this->conn_id, $dir); + + return true; + } + + throw new \Exception('请确保目录中无内容'); + + } + + + /** + * desc:生成目录 + * author:wh + * param $ftpcon + * param $ftpbasedir + * param $ftpath + */ + function ftp_mksubdirs($ftpbasedir,$ftpath){ + ftp_chdir($this->conn_id, $ftpbasedir); // /var/www/uploads + $parts = explode('/',$ftpath); // 2013/06/11/username + + if(strpos($parts[count($parts)-1], '.')){ + unset($parts[count($parts)-1]); + } + + foreach($parts as $part){ + if(!ftp_chdir($this->conn_id, $part)){ + ftp_mkdir($this->conn_id, $part); + ftp_chdir($this->conn_id, $part); + //ftp_chmod($this->conn_id, 0777, $part); + } + } + } + + /** + * desc:使用ftp连接方式下载文件 + * author:wh + * param string $local_file 文件本地的路径(如果文件已经存在,则会被覆盖)。 + * param string $remote_file 文件的远程路径。[默认查找ftp用户被授权的根路径] + * return array + */ + function download(string $local_file, string $remote_file){ + // 进行ftp下载[默认保存在本地根目录] + if (!ftp_get($this->conn_id, $local_file, $remote_file, FTP_BINARY)) { + return ['code'=>500, 'msg'=>'ftp download fail']; + } + return ['code'=>200, 'msg'=>'ftp download success']; + } + + /** + * desc:判断ftp文件大小, 如果>-1, 说明文件存在;否则不存在. + * author:wh + * param $remote_file + * return int + */ + function ftp_size($remote_file){ + return ftp_size($this->conn_id, $remote_file); + } + + /** + * desc:返回当前目录 + * author:wh + * return false|string + */ + function ftp_pwd(){ + return ftp_pwd($this->conn_id); + } + /** + * 关闭FTP连接 + */ + function close() + { + ftp_close($this->conn_id); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/ftp/README.md b/superadmin/vendor/wanghua/general-utility-tools-php/src/ftp/README.md new file mode 100644 index 0000000..29b9a72 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/ftp/README.md @@ -0,0 +1,21 @@ +# PHP ftp类库 +#### 实现用ftp方式上传、复制、移动、重命名、删除、下载文件 + +##### 案例代码: +``` + $ftp = new Ftp();//初始化 + //$ftp->up_file('D:\whua\test.zip','test.zip'); // 上传文件 + //$ftp->rename_file('test.zip','test-rename.zip'); // 移动文件 + //$ftp->move_file('/test2.zip','/testdir2/test2.zip', '/www/wwwroot/testftp/'); // 移动文件2 + //$ftp->copy_file('test.zip','test-copy.zip'); // 复制文件 + //$ftp->del_file('/www/aaa.txt'); // 删除具体文件 + $ftp->del_dir('/www'); // 删除目录(错误示范),必须精确到具体文件夹删除 + $ftp->del_dir('/ftp/test/copy-test/a-dir'); // 删除目录(正确示范),必须精确到具体文件夹删除,批量删除请多次调用 + + //下载文件 + //$ftp->download('D:\whua\projects\zc_game_admin3.0\data\local.zip', 'test.zip'); + //$ftp->close(); // 关闭FTP连接 + + //dump($ftp->ftp_pwd());die; + //dump($ftp->ftp_size('test2.zip'));die; +``` \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/gpt/chat/AliCloudChatGPT.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/gpt/chat/AliCloudChatGPT.php new file mode 100644 index 0000000..5f0cebb --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/gpt/chat/AliCloudChatGPT.php @@ -0,0 +1,188 @@ +messages[] = $item; + } + } + } + /** + * desc:定制个性(多模态) + * author:wh + * @param array $customize + */ + function setCustomizeMulti($customize = []) + { + if ($customize) { + foreach ($customize as $item) { + $this->messages[] = $item; + } + } + } + + function setBefore($describe = []) + { + if ($describe) { + foreach ($describe as $item) { + $this->messages[] = $item; + } + } + } + + function setAfter($describe = []) + { + if ($describe) { + foreach ($describe as $item) { + $this->messages[] = $item; + } + } + } + + private function check() + { + if (empty($this->url) || empty($this->apiKey) || empty($this->model)) { + throw new Exception('PARAMS ERROR'); + } + } + + function chat($question = '', $config = [], &$answer_json_arr) + { + $answer = ''; + $this->curlPostChat($question, $config, function ($ch, $data) use ($question, &$answer, &$answer_json_arr) { + $answer_json_arr[] = $data; + $answer .= $data; + echo $data; + ob_flush(); + flush(); + return strlen($data); + }); + return $answer; + } + + function returnAnswer($question = '', $config = [], &$answer_json_arr) + { + $answer = ''; + $this->curlPostChat($question, $config, function ($ch, $data) use ($question, &$answer, &$answer_json_arr) { + $answer_json_arr[] = $data; + $answer .= $data; + return strlen($data); + }); + return $answer; + } + + private function curlPostChat($question = '', $config = [], $callback) + { + $url = $this->url; + $apiKey = $this->apiKey; + $model = $this->model; + $headers = ["Authorization: Bearer $apiKey", 'Accept: application/json', 'Content-Type: application/json',]; + $post_msg_body = ["model" => $model, 'stream' => true, 'chatId'=>$this->chatId]; + if ($config) { + foreach ($config as $key => $val) { + $post_msg_body[$key] = $val; + } + } + if ($question) { + $this->messages[] = ["role" => "user", "content" => $question]; + } + $post_msg_body['messages'] = $this->messages; + $this->post_msg_body = $post_msg_body; + $postData = json_encode($post_msg_body); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); + curl_setopt($ch, CURLOPT_WRITEFUNCTION, $callback); + curl_exec($ch); + } + + + + /** + * desc:多模态场景chat(如多图多轮对话) + * author:wh + * @param $images + * @param $text + * @return mixed + * @throws \Exception + */ + function generateMultimodalContent($images, $text) + { + $url = $this->url; + $apiKey = $this->apiKey; + $model = $this->model; + $messages = [ + [ + 'role' => 'user', + 'content' => array_map(function ($image) { + return ['image' => $image]; + }, $images), + ], + ]; + + if (!empty($text)) { + $messages[0]['content'][] = ['text' => $text]; + } + + $data = [ + 'model' => $model, + 'input' => [ + 'messages' => $messages, + ], + ]; + + $options = [ + 'http' => [ + 'header' => "Authorization: Bearer $apiKey\r\n" . + "Content-Type: application/json\r\n", + 'method' => 'POST', + 'content' => json_encode($data), + ], + ]; + + $context = stream_context_create($options); + $result = file_get_contents($url, false, $context); + + if ($result === FALSE) { + throw new \Exception('Failed to fetch data from API'); + } + + return json_decode($result, true); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/gpt/chat/BaseChat.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/gpt/chat/BaseChat.php new file mode 100644 index 0000000..9992d9c --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/gpt/chat/BaseChat.php @@ -0,0 +1,42 @@ +chatId = Tools::getMillisecond(); + } + + /** + * desc:将字符串里含有json_encode后的一维数组提取出来 + * @param $str + * @return string + */ + function dealstr($str){ + $rep_str = str_replace("\n",'',$str); + $rep_str = str_replace("\r",'',$rep_str); + $rep_str = str_replace(" ",'',$rep_str); + + $start_sit = strpos($rep_str,"[{"); + $end_sit = strpos($rep_str,"}]"); + + $last_json_str = substr($rep_str,$start_sit,$end_sit-$start_sit); + + $last_json_str.="}]"; + + return $last_json_str; + } + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/gpt/chat/C62239E6C0F995AE1FDC00D18B7905EAC.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/gpt/chat/C62239E6C0F995AE1FDC00D18B7905EAC.php new file mode 100644 index 0000000..038292c --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/gpt/chat/C62239E6C0F995AE1FDC00D18B7905EAC.php @@ -0,0 +1 @@ +messages[] = $item; } } } function sB3250714FE96BE2F5E2D7798123A1B94($describe = []) { if ($describe) { foreach ($describe as $item) { $this->messages[] = $item; } } } function s326723D163240094B8C736D3A1A5CAD3($describe = []) { if ($describe) { foreach ($describe as $item) { $this->messages[] = $item; } } } private function c2111E682402A3955B616FA5795DEDE4F() { if (empty($this->url) || empty($this->apiKey) || empty($this->model)) { throw new Exception('PARAMS ERROR'); } } function cE0323A9039ADD2978BF5B49550572C7C($question = '', $config = [], &$answer_json_arr) { $answer = ''; $this->c8ECA4C82960B4611B076D51EF8ADF979($question, $config, function ($ch, $data) use ($question, &$answer, &$answer_json_arr) { $answer_json_arr[] = $data; $answer .= $data; echo $data; ob_flush(); flush(); return strlen($data); }); return $answer; } function crtque($question = '', $config = [], &$answer_json_arr) { $answer = ''; $this->c8ECA4C82960B4611B076D51EF8ADF979($question, $config, function ($ch, $data) use ($question, &$answer, &$answer_json_arr) { $answer_json_arr[] = $data; $answer .= $data; return strlen($data); }); return $answer; } private function c8ECA4C82960B4611B076D51EF8ADF979($question = '', $config = [], $callback) { $url = $this->url; $apiKey = $this->apiKey; $model = $this->model; $headers = ["Authorization: Bearer $apiKey", 'Accept: application/json', 'Content-Type: application/json',]; $post_msg_body = ["model" => $model, 'stream' => true,]; if ($config) { foreach ($config as $key => $val) { $post_msg_body[$key] = $val; } } if ($question) { $this->messages[] = ["role" => "user", "content" => $question]; } $post_msg_body['messages'] = $this->messages; $postData = json_encode($post_msg_body); $ch = curl_init(); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); curl_setopt($ch, CURLOPT_WRITEFUNCTION, $callback); curl_exec($ch); } function getchatgptresponse($question = '', $config = []) { $url = $this->url; $apiKey = $this->apiKey; $model = $this->model; $headers = ["Authorization: Bearer $apiKey", "Content-Type: application/json"]; $post_msg_body = ["model" => $model, 'stream' => false]; if ($config) { foreach ($config as $key => $val) { $post_msg_body[$key] = $val; } } if ($question) { $this->messages[] = ["role" => "user", "content" => $question]; } $post_msg_body['messages'] = $this->messages; $post_msg_body = json_encode($post_msg_body); $ch = curl_init($url); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $post_msg_body); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); if (curl_error($ch)) { return ['code' => curl_errno($ch), 'msg' => curl_error($ch)]; } else { curl_close($ch); return ['code' => 200, 'msg' => 'cURL ok', 'data' => $response]; } } private function pCD736318A127E12426EED8700FF2BF28($data) { if (@json_decode($data)->choices[0]->message->content) { return json_decode($data)->choices[0]->message->content; } $data = str_replace('data: {', '{', $data); $data = rtrim($data, "\n\n"); if (strpos($data, 'data: [DONE]') !== false) { return 'data: [DONE]'; } else { if (false !== strpos($data, "\n\n")) { $exp_arr = explode("\n\n", $data); $str = ''; try { for ($i = 0; $i < count($exp_arr) - 1; $i++) { $jsondecode_arr = json_decode($exp_arr[$i], true); $str .= $jsondecode_arr['choices'][0]['delta']['content']; } return $str; } catch (\Exception $e) { return $str; } } $data = @json_decode($data, true); if (!is_array($data)) { return ''; } if ($data['choices'][0]['finish_reason'] == 'stop') { return 'data: [DONE]'; } elseif ($data['choices'][0]['finish_reason'] == 'length') { return 'data: [CONTINUE]'; } return $data['choices'][0]['delta']['content'] ?? ''; } } } \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/gpt/chat/ChatGPT.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/gpt/chat/ChatGPT.php new file mode 100644 index 0000000..5723f04 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/gpt/chat/ChatGPT.php @@ -0,0 +1,215 @@ +messages[] = $item; + } + } + } + + function setBefore($describe = []) + { + if ($describe) { + foreach ($describe as $item) { + $this->messages[] = $item; + } + } + } + + function setAfter($describe = []) + { + if ($describe) { + foreach ($describe as $item) { + $this->messages[] = $item; + } + } + } + + private function check() + { + if (empty($this->url) || empty($this->apiKey) || empty($this->model)) { + throw new Exception('PARAMS ERROR'); + } + } + + /** + * desc:对话,仅流式输出时调用该方法 + * author:wh + * @param string $question + * @param array $config + * @param $answer_json_arr + * @return string + */ + function chat($question = '', $config = [], &$answer_json_arr) + { + $answer = ''; + $this->curlPostChat($question, $config, function ($ch, $data) use ($question, &$answer, &$answer_json_arr) { + $answer_json_arr[] = $data; + $answer .= $data; + echo $data; + ob_flush(); + flush(); + return strlen($data); + }); + return $answer; + } + + /** + * @deprecated 废弃,因其不兼容故废弃 + * desc:非流式请求对话,一次返回结果 + * author:wh + * @param string $question + * @param array $config + * @param $answer_json_arr + * @return string + */ + function returnAnswer($question = '', $config = [], &$answer_json_arr) + { + $answer = ''; + $this->curlPostChat($question, $config, function ($ch, $data) use ($question, &$answer, &$answer_json_arr) { + $answer_json_arr[] = $data; + $answer .= $data; + return strlen($data); + }); + return $answer; + } + + /** + * @deprecated 废弃 + * desc:一般未使用 + * author:wh + * @param string $question + * @param array $config + * @param $callback + */ + private function curlPostChat($question = '', $config = [], $callback) + { + $url = $this->url; + $apiKey = $this->apiKey; + $model = $this->model; + $headers = ["Authorization: Bearer $apiKey", 'Accept: application/json', 'Content-Type: application/json',]; + $post_msg_body = ["model" => $model, 'stream' => true, 'chatId'=>$this->chatId]; + if ($config) { + foreach ($config as $key => $val) { + $post_msg_body[$key] = $val; + } + } + if ($question) { + $this->messages[] = ["role" => "user", "content" => $question]; + } + $post_msg_body['messages'] = $this->messages; + $this->post_msg_body = $post_msg_body; + $postData = json_encode($post_msg_body); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); + curl_setopt($ch, CURLOPT_WRITEFUNCTION, $callback); + curl_exec($ch); + } + + /** + * desc: 获取对话结果,非流式输出时调用该方法 + * author:wh + * @param string $question + * @param array $config + * @return array + */ + function getchatgptresponse($question = '', $config = []) + { + $url = $this->url; + $apiKey = $this->apiKey; + $model = $this->model; + $headers = ["Authorization: Bearer $apiKey", "Content-Type: application/json"]; + $post_msg_body = ["model" => $model, 'stream' => false]; + if ($config) { + foreach ($config as $key => $val) { + $post_msg_body[$key] = $val; + } + } + if ($question) { + $this->messages[] = ["role" => "user", "content" => $question]; + } + $post_msg_body['messages'] = $this->messages; + $this->post_msg_body = $post_msg_body; + $post_msg_body = json_encode($post_msg_body); + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $post_msg_body); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + // 忽略 SSL 证书验证 + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + $response = curl_exec($ch); + if (curl_error($ch)) { + return ['code' => curl_errno($ch), 'msg' => curl_error($ch)]; + } else { + curl_close($ch); + return ['code' => 200, 'msg' => 'cURL ok', 'data' => $response]; + } + } + + private function parseData($data) + { + if (@json_decode($data)->choices[0]->message->content) { + return json_decode($data)->choices[0]->message->content; + } + $data = str_replace('data: {', '{', $data); + $data = rtrim($data, "\n\n"); + if (strpos($data, 'data: [DONE]') !== false) { + return 'data: [DONE]'; + } else { + if (false !== strpos($data, "\n\n")) { + $exp_arr = explode("\n\n", $data); + $str = ''; + try { + for ($i = 0; $i < count($exp_arr) - 1; $i++) { + $jsondecode_arr = json_decode($exp_arr[$i], true); + $str .= $jsondecode_arr['choices'][0]['delta']['content']; + } + return $str; + } catch (\Exception $e) { + return $str; + } + } + $data = @json_decode($data, true); + if (!is_array($data)) { + return ''; + } + if ($data['choices'][0]['finish_reason'] == 'stop') { + return 'data: [DONE]'; + } elseif ($data['choices'][0]['finish_reason'] == 'length') { + return 'data: [CONTINUE]'; + } + return $data['choices'][0]['delta']['content'] ?? ''; + } + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/gpt/chat/F37A49CDED4199801B05D5FFFAF78E010.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/gpt/chat/F37A49CDED4199801B05D5FFFAF78E010.php new file mode 100644 index 0000000..e373715 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/gpt/chat/F37A49CDED4199801B05D5FFFAF78E010.php @@ -0,0 +1,141 @@ +messages[] = $item; + } + } + } + + function s3ASCD163240094B8C736D3A1A5CSD($describe = []) + { + if ($describe) { + foreach ($describe as $item) { + $this->messages[] = $item; + } + } + } + + function s326723D163240094B8C736D3A1A5CAD3($describe = []) + { + if ($describe) { + foreach ($describe as $item) { + $this->messages[] = $item; + } + } + } + + private function c2111E682402A3955B616FA5795DEDE4F() + { + if (empty($this->url) || empty($this->apiKey)) { + throw new Exception('PARAMS ERROR'); + } + } + + function cAA8AF3EBE14831A7CD1B6D1383A03755($question = '', $config = [], &$answer_json_arr) + { + $answer = ''; + $this->c8ECA4C82960B4611B076D51EF8ADF979($question, $config, function ($ch, $data) use ($question, &$answer, &$answer_json_arr) { + $answer_json_arr[] = $data; + echo $data; + ob_flush(); + flush(); + return strlen($data); + }); + } + + private function c8ECA4C82960B4611B076D51EF8ADF979($question = '', $config = [], $callback) + { + $this->c2111E682402A3955B616FA5795DEDE4F(); + $url = $this->url;; + $apiKey = $this->apiKey; + $model = $this->model; + $chatId = $this->chatId; + $detail = $this->detail; + $headers = ["Authorization: Bearer $apiKey", 'Accept: application/json', 'Content-Type: application/json',]; + $post_msg_body = ['detail' => $detail, 'stream' => true,]; + if ($detail) { + $post_msg_body['detail'] = $detail; + } + if ($chatId) { + $post_msg_body['chatId'] = $chatId; + } + if ($model) { + $post_msg_body['model'] = $model; + } + if ($config) { + foreach ($config as $key => $val) { + $post_msg_body[$key] = $val; + } + } + if ($question) { + $this->messages[] = ["role" => "user", "content" => $question]; + } + $post_msg_body['messages'] = $this->messages; + $postData = json_encode($post_msg_body); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); + curl_setopt($ch, CURLOPT_WRITEFUNCTION, $callback); + curl_exec($ch); + } + + private function pCD736318A127E12426EED8700FF2BF28($data) + { + if (@json_decode($data)->choices[0]->message->content) { + return json_decode($data)->choices[0]->message->content; + } + $data = str_replace('data: {', '{', $data); + $data = rtrim($data, "\n\n"); + if (strpos($data, 'data: [DONE]') !== false) { + return 'data: [DONE]'; + } else { + if (false !== strpos($data, "\n\n")) { + $exp_arr = explode("\n\n", $data); + $str = ''; + try { + for ($i = 0; $i < count($exp_arr) - 1; $i++) { + $jsondecode_arr = json_decode($exp_arr[$i], true); + $str .= $jsondecode_arr['choices'][0]['delta']['content']; + } + return $str; + } catch (\Exception $e) { + return $str; + } + } + $data = @json_decode($data, true); + if (!is_array($data)) { + return ''; + } + if ($data['choices'][0]['finish_reason'] == 'stop') { + return 'data: [DONE]'; + } elseif ($data['choices'][0]['finish_reason'] == 'length') { + return 'data: [CONTINUE]'; + } + return $data['choices'][0]['delta']['content'] ?? ''; + } + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/gpt/chat/FastGPT.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/gpt/chat/FastGPT.php new file mode 100644 index 0000000..737c7de --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/gpt/chat/FastGPT.php @@ -0,0 +1,148 @@ +messages[] = $item; + } + } + } + + function setBefore($describe = []) + { + if ($describe) { + foreach ($describe as $item) { + $this->messages[] = $item; + } + } + } + + function setAfter($describe = []) + { + if ($describe) { + foreach ($describe as $item) { + $this->messages[] = $item; + } + } + } + + private function check() + { + if (empty($this->url) || empty($this->apiKey)) { + throw new Exception('PARAMS ERROR'); + } + } + + function chat($question = '', $config = [], &$answer_json_arr) + { + $answer = ''; + $this->curlPostChat($question, $config, function ($ch, $data) use ($question, &$answer, &$answer_json_arr) { + $answer_json_arr[] = $data; + echo $data; + ob_flush(); + flush(); + return strlen($data); + }); + } + + private function curlPostChat($question = '', $config = [], $callback) + { + $this->check(); + $url = $this->url;; + $apiKey = $this->apiKey; + $model = $this->model; + $chatId = $this->chatId; + $detail = $this->detail; + $headers = ["Authorization: Bearer $apiKey", 'Accept: application/json', 'Content-Type: application/json',]; + $post_msg_body = ['detail' => $detail, 'stream' => true, 'chatId'=>$this->chatId]; + if ($detail) { + $post_msg_body['detail'] = $detail; + } + if ($chatId) { + $post_msg_body['chatId'] = $chatId; + } + if ($model) { + $post_msg_body['model'] = $model; + } + if ($config) { + foreach ($config as $key => $val) { + $post_msg_body[$key] = $val; + } + } + if ($question) { + $this->messages[] = ["role" => "user", "content" => $question]; + } + $post_msg_body['messages'] = $this->messages; + $postData = json_encode($post_msg_body); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); + curl_setopt($ch, CURLOPT_WRITEFUNCTION, $callback); + curl_exec($ch); + } + + private function parseData($data) + { + if (@json_decode($data)->choices[0]->message->content) { + return json_decode($data)->choices[0]->message->content; + } + $data = str_replace('data: {', '{', $data); + $data = rtrim($data, "\n\n"); + if (strpos($data, 'data: [DONE]') !== false) { + return 'data: [DONE]'; + } else { + if (false !== strpos($data, "\n\n")) { + $exp_arr = explode("\n\n", $data); + $str = ''; + try { + for ($i = 0; $i < count($exp_arr) - 1; $i++) { + $jsondecode_arr = json_decode($exp_arr[$i], true); + $str .= $jsondecode_arr['choices'][0]['delta']['content']; + } + return $str; + } catch (\Exception $e) { + return $str; + } + } + $data = @json_decode($data, true); + if (!is_array($data)) { + return ''; + } + if ($data['choices'][0]['finish_reason'] == 'stop') { + return 'data: [DONE]'; + } elseif ($data['choices'][0]['finish_reason'] == 'length') { + return 'data: [CONTINUE]'; + } + return $data['choices'][0]['delta']['content'] ?? ''; + } + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/html/Html.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/html/Html.php new file mode 100644 index 0000000..d0ccce0 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/html/Html.php @@ -0,0 +1,224 @@ +.*?<\/a>/"; + preg_match_all($reg,$html,$a_array); + return $a_array; + } + + /** + * desc:提取div标签 + * author:wh + * @param string $html + * @return null + */ + function getDiv(string $html){ + $a_array = null; + $reg="/
.*?<\/div>/"; + preg_match_all($reg,$html,$a_array); + return $a_array; + } + /** + * desc:提取img标签 + * author:wh + * @param string $html + * @return null + */ + function getImg(string $html){ + $a_array = null; + $reg="/.*?<\/img>/"; + preg_match_all($reg,$html,$a_array); + return $a_array; + } + /** + * desc:提取span标签 + * author:wh + * @param string $html + * @return null + */ + function getSpan(string $html){ + $a_array = null; + $reg="/.*?<\/span>/"; + preg_match_all($reg,$html,$a_array); + return $a_array; + } + /** + * desc:提取ul标签 + * author:wh + * @param string $html + * @return null + */ + function getUl(string $html){ + $a_array = null; + $reg="/
    .*?<\/ul>/"; + preg_match_all($reg,$html,$a_array); + return $a_array; + } + /** + * desc:提取li标签 + * author:wh + * @param string $html + * @return null + */ + function getLi(string $html){ + $a_array = null; + $reg="/
  • .*?<\/li>/"; + preg_match_all($reg,$html,$a_array); + return $a_array; + } + /** + * desc:提取table标签 + * author:wh + * @param string $html + * @return null + */ + function getTable(string $html){ + $a_array = null; + $reg="/.*?<\/table>/"; + preg_match_all($reg,$html,$a_array); + return $a_array; + } + /** + * desc:提取table tr标签 + * author:wh + * @param string $html + * @return null + */ + function getTableTr(string $html){ + $a_array = null; + $reg="/.*?<\/tr>/"; + preg_match_all($reg,$html,$a_array); + return $a_array; + } + /** + * desc:提取table td标签 + * author:wh + * @param string $html + * @return null + */ + function getTableTd(string $html){ + $a_array = null; + $reg="/
    .*?<\/td>/"; + preg_match_all($reg,$html,$a_array); + return $a_array; + } + + + /** + * desc:提取a标签属性 + * author:wh + * @param $html + * @return array + */ + function getAAttribute($html){ + $aarray = null; + $reg1="/.*?<\/a>/"; + preg_match_all($reg1,$html,$aarray); + + $hrefarray=null;//这个存放的是匹配出来的href的链接地址 + $acontent=null;//存放匹配出来的a标签的内容 + $reg2="/href=\"([^\"]+)/"; + $a_arr = []; + for($i=0;$iget_tag_data($html,"div","class","num"); + * + * author:wh + * @param $html + * @param $tag + * @param $class + * @param $value + * @return mixed + */ + function getTagData($html,$tag,$class,$value){ + //$value 为空,则获取class=$class的所有内容 + $regex = $value ? "/<$tag.*?$class=\"$value\".*?>(.*?)<\/$tag>/is" : "/<$tag.*?$class=\".*?$value.*?\".*?>(.*?)<\/$tag>/is"; + preg_match_all($regex,$html,$matches,PREG_PATTERN_ORDER); + //返回数组 ,指定标签内容 + return $matches[1]; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/html/Htmlcontent.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/html/Htmlcontent.php new file mode 100644 index 0000000..5f84cff --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/html/Htmlcontent.php @@ -0,0 +1,126 @@ + "{$url}", + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => "", + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 30, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => "GET", + CURLOPT_HTTPHEADER => array( + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "Accept-Encoding: gzip, deflate, br", + "Accept-Language: zh-CN,zh;q=0.9", + "Cache-Control: no-cache", + "Connection: keep-alive", + "Pragma: no-cache", + "Upgrade-Insecure-Requests: 1", + "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "cache-control: no-cache" + ), + )); + + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); + $response = curl_exec($curl); + $err = curl_error($curl); + curl_close($curl); + if ($err) return false; + else return $response; + } + + /** + * [get_tag_data 使用正则获取html内容中指定的内容] + * @param [type] $html [爬取的页面内容] + * @param [type] $tag [要查找的标签] + * @param [type] $attr [要查找的属性名] + * @param [type] $value [属性名对应的值] + * @return [type] [description] + */ + function get_tag_data($html, $tag, $attr, $value) + { + + // var_dump($tag, $attr, $value); + $regex = "/<$tag.*?$attr=\".*?$value.*?\".*?>(.*?)<\/$tag>/is"; + // var_dump($regex); + preg_match_all($regex, $html, $matches, PREG_PATTERN_ORDER); + //var_dump($matches);die; + $data = isset($matches[1]) ? $matches[1] : ''; + // var_dump($data);die; + return $data; + } + /** + * [get_html_data 使用xpath对获取到的html内容进行处理] + * @param [type] $html [爬取的页面内容] + * @param [type] $path [Xpath语句] + * @param integer $tag [类型 0内容 1标签内容 自定义标签] + * @param boolean $type [单个 还是多个(默认单个时输出单个)] + * @return [type] [description] + */ + + function get_html_data($html, $path, $tag = 1, $type = true) + { + $dom = new \DOMDocument(); + @$dom->loadHTML("" . $html); // 从一个字符串加载HTML并设置UTF8编码 + $dom->normalize(); // 使该HTML规范化 + $xpath = new \DOMXPath($dom); //用DOMXpath加载DOM,用于查询 + $contents = $xpath->query($path); // 获取所有内容 + $data = []; + foreach ($contents as $value) { + if ($tag == 1) { + $data[] = $value->nodeValue; // 获取不带标签内容 + } elseif ($tag == 2) { + $data[] = $dom->saveHtml($value); // 获取带标签内容 + } else { + $data[] = $value->attributes->getNamedItem($tag)->nodeValue; // 获取attr内容 + } + } + if (count($data) == 1) { + $data = $data[0]; + } + return $data; + } + //调用 + public function get_content($url,$tag,$attr='',$value='') + { + //$url = input('url') ?? ''; + //$tag = g('tag') ?? ''; + //$attr = g('attr') ?? ''; + //$value = g('value') ?? ''; + if (empty($url) || empty($tag)) { + return Tools::set_res(4, '缺少关键参数!'); + } + + $html = $this->curlHtml($url); + //使用正则获取 + $data = $this->get_tag_data($html, $tag, $attr, $value); + //Xpath方法,暂未搞明白 + // $data = $this->get_html_data($html, $path, $tag = 1, $type = true); + return Tools::set_res(1, 'ok!', $data); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/http/Curl.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/http/Curl.php new file mode 100644 index 0000000..5e09892 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/http/Curl.php @@ -0,0 +1,611 @@ +getBody()->getContents(); + * + * author:wh + * @param $url + * @param string $method + * @param false[] $config + * @param array $headers + * @return mixed + */ + static function request($url,$method='POST',$config=['verify' => false],$headers=[]){ + $client = new Client($config); + //$headers = [ + // //'User-Agent' => 'Apifox/1.0.0 (https://apifox.com)', + // //'Accept' => '*/*', + // //'Host' => 'vits_simple.excn.top', + // //'Connection' => 'keep-alive' + //]; + $request = new Request($method, $url, $headers); + $res = $client->sendAsync($request)->wait(); + //$res->getBody()->getContents();//如果是文件流,则需要用getContents()方法获取 + return $res->getBody(); + } + + /** + * desc:php curl远程上传文件,提交到远程服务器,适用于上传多个文件(文件类型不限) + * + * 应用场景: + * 前端->后端A->后端B + * 说明:前端发起文件上传,后端A接收到文件后,将文件使用guzzlehttp/guzzle上传到后端B,后端B将文件处理 + * + * 当前版本依赖:"guzzlehttp/guzzle": "^7.8" + * + * author:wh + * @param $url 远程地址 + * @param $params 提交参数 + * @param array $config 配置项 + */ + function curlRemoteUploadFiles($url,$params, $config=['verify' => false]){ + // 创建 Guzzle HTTP 客户端实例 + $client = new Client($config); + + // 定义要上传的文件列表 + $files = [ + //[ + // 'name' => 'file1', // 服务器接收的文件字段名 + // 'contents' => Utils::tryFopen('D:\wanghua\projects\big_world_projects\universal_gravitation\public\uploads\20240506\dc1c441d81d48565ac6817b89d0f8bef.mp4', 'r'), // 文件路径 + // 'filename' => 'dc1c441d81d48565ac6817b89d0f8bef.mp4', // 文件名,可自定义 + //], + //[ + // 'name' => 'files', + // 'contents' => Utils::tryFopen('D:\wanghua\projects\big_world_projects\universal_gravitation\public\uploads\20240506\f80179b23b60619fba8033bfd64f8817.mp4', 'r'), + // 'filename' => 'f80179b23b60619fba8033bfd64f8817.mp4', + //], + //['name'=>'prompt','contents'=>$prompt], + //['name'=>'tts_url','contents'=>$tts_url] + // 可以继续添加更多文件... + ]; + foreach ($params as $key=>$val){ + $files[] = ['name'=>$key,'contents'=>$val]; + } + $controller = \request()->controller(); + $action = \request()->action(); + $save_path = Tools::get_root_path() . "public/uploads/{$controller}/{$action}/"; + $files_obj = \request()->file('files'); + foreach ($files_obj as $k=>$file) { + if ($file->check()) { + $save_res = $file->move($save_path); + $fileinfo = $file->getInfo(); + // 使用CURLFile包装文件路径以供上传 + $filename = $save_path.date('Ymd').'/'.$save_res->getFilename(); + $files[] = [ + 'name'=>"files",//服务器接收的文件字段名 + 'contents'=>Utils::tryFopen($filename, 'r'), + 'filename'=>$fileinfo['name'],//可自定义 + ]; + } + //else { + // return json(['code' => -1, 'msg' => '文件上传失败: ' . $file->getError()]); + //} + } + + // 构建多部分表单数据 + $multipartStream = new MultipartStream($files); + $boundary = $multipartStream->getBoundary(); + $headers = [ + 'Content-Type' => "multipart/form-data; boundary={$boundary}", + ]; + + // 准备请求体 + $body = $multipartStream->getContents(); + + // 发起 POST 请求 + try { + $response = $client->request('POST', $url, [ + 'headers' => $headers, + 'body' => $body, + ]); + + // 处理响应 + $responseBody = (string) $response->getBody(); + //echo "Server response: " . $responseBody; + return Tools::set_ok('ok',$responseBody); + } catch (\GuzzleHttp\Exception\RequestException $e) { + // 错误处理 + //echo "Error: " . $e->getMessage(); + return Tools::set_fail("Error: " . $e->getMessage()); + } + } + + /** + * GET + * + * author:wh + * @param string $url + * @param int $timeout + * @return bool|int|string + */ + static function curl_get(string $url, int $timeout = 10, $header=[]) + { + + $header = $header?:array( + 'Accept: application/json', + ); + $curl = curl_init(); + //curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE); + //curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); + //curl_setopt($curl, CURLOPT_SSLVERSION, 3); + //设置抓取的url + curl_setopt($curl, CURLOPT_URL, $url); + //设置头文件的信息作为数据流输出 + curl_setopt($curl, CURLOPT_HEADER, 0); + // 超时设置,以秒为单位 + curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); + + // 超时设置,以毫秒为单位 + // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500); + + // 设置请求头 + curl_setopt($curl, CURLOPT_HTTPHEADER, $header); + //设置获取的信息以文件流的形式返回,而不是直接输出。 + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); + //执行命令 + $data = curl_exec($curl); + + // 显示错误信息 + if (curl_error($curl)) { + //print "Error: ".curl_errno($curl).'-' . curl_error($curl); + //返回错误码 + return ['code' => curl_errno($curl), 'msg' => curl_error($curl)]; + } else { + //关闭句柄 + curl_close($curl); + // 返回的内容 + return ['code' => 200, 'msg' => 'cURL ok', 'data' => $data]; + } + } + /** + * POST 表单 + * + * author:wh + * @param string $url 是请求的链接 + * @param $postdata 传输的数据,数组格式 (跟$header的参数相关) + * @return bool|int|string + */ + static function curl_post(string $url, $postdata, $header=[]) { + $timeout = 4; + $connect_timeout = 1; + $set_time_limit = 5; + if($timeout + $connect_timeout < $set_time_limit) throw new \Exception('脚本超时值必须大于等于连接超时与请求处理超时之和'); + set_time_limit($set_time_limit); + $header = $header?:array( + 'Accept: application/json', + ); + + //初始化 + $curl = curl_init(); + //设置抓取的url + curl_setopt($curl, CURLOPT_URL, $url); + //设置头文件的信息作为数据流输出 + curl_setopt($curl, CURLOPT_HEADER, 0); + //设置获取的信息以文件流的形式返回,而不是直接输出。 + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + // 超时设置 + curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); + //发起连接前等待的时间,如果设置为0,则无限等待。 + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $connect_timeout); + + // 超时设置,以毫秒为单位 + // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500); + + // 设置请求头 + curl_setopt($curl, CURLOPT_HTTPHEADER, $header); + + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE ); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE ); + + //设置post方式提交 + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $postdata); + //执行命令 + $data = curl_exec($curl); + + // 显示错误信息 + if (curl_error($curl)) { + //返回错误码 + return ['code'=>curl_errno($curl), 'msg'=>curl_error($curl)]; + } else { + //关闭句柄 + curl_close($curl); + // 返回的内容 + return ['code'=>200, 'msg'=>'cURL ok', 'data'=>$data]; + } + } + + /** + * 触发即忘的异步HTTP请求(无需等待响应) + * + * 环境:[linux] + * + * @param string $url 目标URL + * @param array $data POST数据(可选) + */ + static function asyncPostWithPopen(string $url, array $data = []) + { + $escapedUrl = escapeshellarg($url); + $postData = !empty($data) ? ' -d ' . escapeshellarg(http_build_query($data)) : ''; + + // 构建命令(nohup 确保脱离终端) + $cmd = sprintf( + 'nohup curl -s -X POST %s %s > /dev/null 2>&1 ['file', '/dev/null', 'r'], + 1 => ['file', '/dev/null', 'w'], + 2 => ['file', '/dev/null', 'w'], + ]; + + $proc = proc_open($cmd, $descriptors, $pipes, null, null, ['bypass_shell' => false]); + + if (!is_resource($proc)) { + Tools::log_to_write_txt('异步请求启动失败: ' . $cmd); + return false; + } + + // 非阻塞关闭 + proc_get_status($proc); + proc_close($proc); + return true; + } + /** + * @deprecated 弃用 + * POST 查询参数 + * + * author:wh + * @param string $url 是请求的链接 + * @param array $postdata 传输的数据,http_build_query查询参数格式 + * @return bool|int|string + */ + static function curl_post_build_query(string $url, $postdata, $header=[]) { + $timeout = 4; + $connect_timeout = 1; + $set_time_limit = 5; + if($timeout + $connect_timeout < $set_time_limit) throw new \Exception('脚本超时值必须大于等于连接超时与请求处理超时之和'); + set_time_limit($set_time_limit); + $header = $header?:array( + 'Accept: application/json', + ); + + //初始化 + $curl = curl_init(); + //设置抓取的url + curl_setopt($curl, CURLOPT_URL, $url); + //设置头文件的信息作为数据流输出 + curl_setopt($curl, CURLOPT_HEADER, 0); + //设置获取的信息以文件流的形式返回,而不是直接输出。 + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + // 超时设置 + curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); + //发起连接前等待的时间,如果设置为0,则无限等待。 + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $connect_timeout); + + // 超时设置,以毫秒为单位 + // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500); + + // 设置请求头 + curl_setopt($curl, CURLOPT_HTTPHEADER, $header); + + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE ); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE ); + + //设置post方式提交 + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($postdata)); + //执行命令 + $data = curl_exec($curl); + + // 显示错误信息 + if (curl_error($curl)) { + //返回错误码 + return ['code'=>curl_errno($curl), 'msg'=>curl_error($curl)]; + } else { + //关闭句柄 + curl_close($curl); + // 返回的内容 + return ['code'=>200, 'msg'=>'ok', 'data'=>$data]; + } + } + + /** + * @deprecated 弃用 + * POST json + * + * author:wh + * @param string $url 是请求的链接 + * @param array $postdata 传输的数据,json格式 + * @return 返回数组格式 + */ + static function curl_post_json(string $url, $postdata, $header=[]) { + $timeout = 4; + $connect_timeout = 1; + $set_time_limit = 5; + if($timeout + $connect_timeout < $set_time_limit) throw new \Exception('脚本超时值必须大于等于连接超时与请求处理超时之和'); + set_time_limit($set_time_limit); + $header = $header?:array( + 'Accept: application/json', + ); + + //初始化 + $curl = curl_init(); + //设置抓取的url + curl_setopt($curl, CURLOPT_URL, $url); + //设置头文件的信息作为数据流输出 + curl_setopt($curl, CURLOPT_HEADER, 0); + //设置获取的信息以文件流的形式返回,而不是直接输出。 + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + // 超时设置 + curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); + //发起连接前等待的时间,如果设置为0,则无限等待。 + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $connect_timeout); + + // 超时设置,以毫秒为单位 + // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500); + + // 设置请求头 + curl_setopt($curl, CURLOPT_HTTPHEADER, $header); + + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE ); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE ); + + //设置post方式提交 + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($postdata,JSON_UNESCAPED_UNICODE)); + //执行命令 + $data = curl_exec($curl); + + // 显示错误信息 + if (curl_error($curl)) { + //返回错误码 + return ['code'=>curl_errno($curl), 'msg'=>curl_error($curl)]; + } else { + //关闭句柄 + curl_close($curl); + // 返回的内容 + return ['code'=>200, 'msg'=>'cURL ok', 'data'=>$data]; + } + } + + /** + * @deprecated 弃用 + * POST json ,成功直接返回请求结果,错误返回数组 + * + * 注:此方法不是表单提交 + * + * author:wh + * @param string $url 是请求的链接 + * @param array $postdata 传输的数据,最终会转换为json格式请求 + * @return 直接返回请求数据,适合请求第三方返回BUFFER数据流的接口 + */ + static function curl_post_json_return_buffer(string $url, array $postdata, $header=[]) { + $timeout = 4; + $connect_timeout = 1; + $set_time_limit = 5; + if($timeout + $connect_timeout < $set_time_limit) throw new \Exception('脚本超时值必须大于等于连接超时与请求处理超时之和'); + set_time_limit($set_time_limit); + $header = $header?:array( + 'Accept: application/json', + ); + + //初始化 + $curl = curl_init(); + //设置抓取的url + curl_setopt($curl, CURLOPT_URL, $url); + //设置头文件的信息作为数据流输出 + curl_setopt($curl, CURLOPT_HEADER, 0); + //设置获取的信息以文件流的形式返回,而不是直接输出。 + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + // 超时设置 + curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); + //发起连接前等待的时间,如果设置为0,则无限等待。 + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $connect_timeout); + + // 超时设置,以毫秒为单位 + // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500); + + // 设置请求头 + curl_setopt($curl, CURLOPT_HTTPHEADER, $header); + + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE ); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE ); + + //设置post方式提交 + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($postdata,JSON_UNESCAPED_UNICODE)); + //执行命令 + $data = curl_exec($curl); + + // 显示错误信息 + if (curl_error($curl)) { + //返回错误码 + return ['code'=>curl_errno($curl), 'msg'=>curl_error($curl)]; + } else { + //关闭句柄 + curl_close($curl); + // 返回的内容 + return $data; + } + } + + /** + * @deprecated 弃用 + * POST [流] + * + * [请求Java接口] + * + * 思考:1、Java端如果用文件流的方式去获取数据,调用此方法 + * + * @param $url + * @param $postdata + * @param int $timeout + * @return array + * @link + * @example + * @see + */ + static function java_curl_post_file_request($url, $postdata, $timeout = 10, $header=[]) + { + + $header = $header?:array( + 'Accept: application/json', + ); + + //初始化 + $curl = curl_init(); + //设置抓取的url + curl_setopt($curl, CURLOPT_URL, $url); + //设置头文件的信息作为数据流输出 + curl_setopt($curl, CURLOPT_HEADER, 0); + //设置获取的信息以文件流的形式返回,而不是直接输出。 + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + // 超时设置 + curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); + + // 超时设置,以毫秒为单位 + // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500); + + // 设置请求头 + curl_setopt($curl, CURLOPT_HTTPHEADER, $header); + + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE); + + //设置post方式提交 + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $postdata); + //执行命令 + $data = curl_exec($curl); + + // 显示错误信息 + if (curl_error($curl)) { + //返回错误码 + return ['code' => curl_errno($curl), 'msg' => curl_error($curl)]; + } else { + //关闭句柄 + curl_close($curl); + // 返回的内容 + return ['code' => 200, 'msg' => 'ok', 'data' => $data]; + } + } + + /** + * @deprecated 弃用 + * 统一请求 GET/POST请求 + * + * 注:此设置允许header重定向,适合部分请求头中携带参数的接口,如请求头携带token + * + * @param String $url 接口地址 + */ + static function curl_request($url, $method = 'GET',$data=null,$header=array(),$call_back=null) + { + set_time_limit(30); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + if($header){ + curl_setopt($ch, CURLOPT_HTTPHEADER, $header); + } + if($method == 'POST'){ + if($data) curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + } + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + if($call_back){ + //使用此特性前除非你清楚理解回调函数,否则不推荐 + curl_setopt($ch, CURLOPT_WRITEFUNCTION, $call_back); + } + $result = curl_exec($ch); + if (curl_errno($ch)) { + return [ + 'status' => 'error', + 'message' => 'curl 错误信息: ' . curl_error($ch) + ]; + } + curl_close($ch); + return $result; + } + + + /** + * @deprecated 弃用 + * desc:php curl模拟文件上传 + * post + * author:wh + * @param $url 提交地址 + * @param $params 表单参数 + * @param $files 上传文件 + * @param $header 请求头 + * @return array + */ + static function curlFileUpload($url,$params,$files,$header) + { + // 初始化cURL会话 + $ch = curl_init(); + $header = $header ?: array( + 'Accept: application/json', + 'Content-Type: multipart/form-data' + ); + // 合并文件和其他字段数据 + $data = array_merge($params, $files); + + // 设置cURL选项 + curl_setopt($ch, CURLOPT_URL, $url); + // 设置请求头 + curl_setopt($ch, CURLOPT_HTTPHEADER, $header); + + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); + + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + + // 执行cURL请求 + $result = curl_exec($ch); + if (curl_errno($ch)) { + return ['code' => curl_errno($ch), 'msg' => curl_error($ch)]; + } + //关闭句柄 + curl_close($ch); + // 返回的内容 + return ['code' => 200, 'msg' => 'cURL ok', 'data' => $result]; + } + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/image/Image.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/image/Image.php new file mode 100644 index 0000000..ecac61e --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/image/Image.php @@ -0,0 +1,76 @@ +这是一张图片:点击查看

    +//

    还有一张:Another Photo

    +//

    图片链接也可以是:

    +//HTML; + // 使用正则表达式匹配图片链接 + $pattern = '/(https?:\/\/[^\s]+\.(?:jpg|jpeg|png|gif))/i'; + $images = []; + if (preg_match_all($pattern, $html, $matches)) { + //echo "找到的图片链接如下:\n"; + foreach ($matches[0] as $match) { + $images[] = $match; + } + } else { + return $images; + } + return $images; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/log/BaseLog.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/BaseLog.php new file mode 100644 index 0000000..a1eedf4 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/BaseLog.php @@ -0,0 +1,82 @@ +write($title, $content); + self::$log_type != 'db'?:(new Mysql())->write($title, $content); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/log/Driver.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/Driver.php new file mode 100644 index 0000000..7d3e829 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/Driver.php @@ -0,0 +1,134 @@ +setDriver('file'); + $dr->write(['start'=>11]); + + $dr->write(['end'=>222]); + + $dr->write(['result'=>333]); + $dr->flush();//must + * + * Class Driver + * @package wanghua\general_utility_tools_php\log + */ +class Driver +{ + private $driver; + + private $lib_path = '';//驱动路径 + + protected $static = null; + + public function __construct(string $driver='') + { + //driver + $sys_log_type = $driver?$driver:\think\Config::get('sys_log_type'); + //default driver + $this->driver = isset($sys_log_type)&&$sys_log_type!=''?$sys_log_type:'file'; + //init driver + $this->initDriver(); + } + + /** + * desc:choose driver + * + * author:wh + * @param string $driver + */ + function setDriver(string $driver){ + //重新设置驱动 + $this->driver = $driver; + //init driver + $this->initDriver(); + } + + /** + * desc:get driver name + * + * author:wh + * @return string + */ + function getDriverName(){ + return $this->driver; + } + /** + * desc: + * author:wh + */ + private function initDriver(){ + $this->driver = $this->driverPath().ucfirst($this->driver); + $this->static = new $this->driver; + } + + /** + * desc:设置日志路径 + * author:wh + * @param string $logDir + */ + function setLogDir(string $logDir){ + $this->static->setLogDir($logDir); + } + + function getLogDir(){ + return $this->static->getLogDir(); + } + + /** + * desc:set driver lib path + * + * author:wh + * @param $lib_path + */ + function setLibPath($lib_path){ + $this->lib_path = $lib_path; + } + + /** + * desc:init + * + * author:wh + * @return string + */ + private function driverPath(){ + if($this->lib_path) return $this->lib_path; + return 'wanghua\general_utility_tools_php\log\driver\\'; + } + + /** + * desc:group data + * + * author:wh + * @param $data + */ + function write($data){ + + $this->static->write($data); + } + + /** + * desc:output data + * + * author:wh + * @return mixed + */ + function flush(){ + + return $this->static->flush(); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/log/File.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/File.php new file mode 100644 index 0000000..db6483a --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/File.php @@ -0,0 +1,41 @@ +log_dir = (substr($vendor_dir,0,$index)).'runtime/toollog/'.date('Ymd'); + file_exists($this->log_dir)?:mkdir($this->log_dir, 0777, true); + $this->log_dir = $this->log_dir.'/'.date('Y-m-d-H').'.log'; + } + + /** + * desc: + * author:wh + * @param $title + * @param string $content + */ + function write(string $title, string $content=''){ + $string = "\r\n".$title.' '. $content; + file_put_contents($this->log_dir, $string, FILE_APPEND); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/log/Log.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/Log.php new file mode 100644 index 0000000..1a65410 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/Log.php @@ -0,0 +1,21 @@ +200,'msg'=>'ok']; + Tools::log_to_write_txt($data,$log_file_name); + return $result;//含 code、msg、data结构 + }catch (\Exception $e){ + //一般不会出错 + return ['code'=>500,'msg'=>'写入普通日志,日志记录失败.','data'=>['error'=>$e->getMessage(),'error_info'=>$e->getTraceAsString()]]; + } + } + /** + * desc:记录普通日志同时记录邮件日志 + * + * author:wh + * @param array $data 如果data里面的一维数组的key包含了被过滤的配置,则当次不发送邮件 + * @param string $log_file_name + * @return array + */ + static function write_text_email(array $data,string $log_file_name){ + $keys = array_keys($data); + try { + Tools::log_to_write_txt($data,$log_file_name); + if(self::$filter){ + $is_send = true;//发送 + foreach ($keys as $key){ + //过滤不发送邮件的key,可以是任意字符串 + if(in_array($key, self::$filter)){ + $is_send = false; + } + } + if($is_send){ + return self::send($data); + } + } + return self::send($data); + }catch (\Exception $e){ + //一般不会出错 + return ['code'=>500,'msg'=>'普通日志邮件日志记录失败.','data'=>['error'=>$e->getMessage(),'error_info'=>$e->getTraceAsString()]]; + } + } + + /** + * desc: + * author:wh + * @param array $data + * @param string $log_file_name + * @return array + */ + protected static function send(array $data = []){ + $body = json_encode($data,JSON_UNESCAPED_UNICODE); + $body = str_replace('\\"','"',$body); + $body = str_replace('\\n"','',$body); + $mail = new Mail(self::$send_server_nickname,self::$send_server_host,self::$send_server_username,self::$send_server_pwd); + $mail->debug = self::$debug; + return $mail->send(self::$receiver_email,self::$receiver_nickname,'EMAIL TITLE',$body); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/log/Mysql.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/Mysql.php new file mode 100644 index 0000000..4a0d5b3 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/Mysql.php @@ -0,0 +1,41 @@ +insert([ + 'title'=>$title, 'content'=>$content, + ]); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/log/driver/Base.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/driver/Base.php new file mode 100644 index 0000000..b352420 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/driver/Base.php @@ -0,0 +1,90 @@ +reqid = $this->initReqID(); + } + + /** + * desc:获取日志目录 + * + * 子类根据情况实现(Email,MySQL这种不需要目录则不实现) + * + * author:wh + * @return string + */ + function getLogDir(){ + return ''; + } + /** + * 获取当前系统时间(精确到毫秒) + * @return float + */ + protected function getMillisecond() + { + list($t1, $t2) = explode(' ', microtime()); + return sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000); + } + /** + * 获取当前系统时间(精确到微秒) + * @return float + */ + protected function getMicrosecond() + { + list($t1, $t2) = explode(' ', microtime()); + return sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000000); + } + + /** + * desc:初始化请求id + * + * author:wh + * @return float + */ + function initReqID(){ + return $this->getMicrosecond(); + } + /** + * desc:获取请求id + * + * author:wh + * @return float + */ + function getReqID(){ + return $this->reqid; + } + + function write($content){ + + } + + function flush(){ + + } + + function getLogData(){ + + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/log/driver/Email.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/driver/Email.php new file mode 100644 index 0000000..eccb1dc --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/driver/Email.php @@ -0,0 +1,206 @@ +debug = SMTP::DEBUG_SERVER; + } + + /** + * desc:关闭调试模式 + * author:wh + */ + function close_debug(){ + $this->debug = SMTP::DEBUG_OFF; + } + /** + * 【必须】 + * desc:设置邮件服务器配置 + * author:wh + * @param $send_server_nickname + * @param $send_server_username + * @param $send_server_pwd + * @param $send_server_host + */ + function set_email_config(string $send_server_nickname,string $send_server_username,string $send_server_pwd,string $send_server_host){ + $this->send_server_nickname = $send_server_nickname; + $this->send_server_username = $send_server_username; + $this->send_server_pwd = $send_server_pwd; + $this->send_server_host = $send_server_host; + } + + /** + * 【必须】 + * desc:设置收件人配置 + * author:wh + * @param $receiver_email + * @param $receiver_nickname + */ + function set_receiver_config($receiver_email,string $receiver_nickname){ + $this->receiver_email = $receiver_email; + $this->receiver_nickname = $receiver_nickname; + } + + /** + * desc:过滤指定日志,不发送至email + * author:wh + */ + function set_filter(array $filter=[]){ + $this->filter = $filter; + } + + /** + * desc:记录普通日志同时记录邮件日志 + * + * author:wh + * @param array $data 如果data里面的一维数组的key包含了被过滤的配置,则当次不发送邮件 + * @param string $log_file_name + * @return array + */ + function write_text_email(array $data,string $log_file_name){ + $keys = array_keys($data); + try { + Tools::log_to_write_txt($data,$log_file_name); + if($this->filter){ + $is_send = true;//发送 + foreach ($keys as $key){ + //过滤不发送邮件的key,可以是任意字符串 + if(in_array($key, $this->filter)){ + $is_send = false; + } + } + if($is_send){ + return $this->send($data); + } + } + return $this->send($data); + }catch (\Exception $e){ + //一般不会出错 + return ['code'=>500,'msg'=>'普通日志邮件日志记录失败.','data'=>['error'=>$e->getMessage(),'error_info'=>$e->getTraceAsString()]]; + } + } + /** + * desc:记录普通日志同时记录邮件日志 + * + * author:wh + * @param array $data 如果data里面的一维数组的key包含了被过滤的配置,则当次不发送邮件 + * @param string $log_file_name + * @return array + */ + function write_text(string $title,string $body){ + return $this->sendText($title, $body); + } + /** + * desc:记录普通日志同时记录邮件日志 + * + * author:wh + * @param array $data 如果data里面的一维数组的key包含了被过滤的配置,则当次不发送邮件 + * @param string $log_file_name + * @return array + */ + function write_text_aws(string $title,string $body){ + return $this->sendTextAWS($title, $body); + } + + /** + * desc: + * author:wh + * @return array|void + */ + function write($content){ + return $this->sendText($content['title'], $content['body']); + } + /** + * desc:数组格式 + * author:wh + * @param array $data + * @param string $log_file_name + * @return array + */ + protected function send(array $data = []){ + $body = json_encode($data,JSON_UNESCAPED_UNICODE); + $body = str_replace('\\"','"',$body); + $body = str_replace('\\n"','',$body); + $mail = new Mail($this->send_server_nickname,$this->send_server_host,$this->send_server_username,$this->send_server_pwd,$this->send_server_username,$this->port,$this->protocol); + $mail->debug = $this->debug; + return $mail->send($this->receiver_email,$this->receiver_nickname,'EMAIL TITLE',$body); + } + /** + * desc:文本格式 + * author:wh + * @param array $data + * @param string $log_file_name + * @return array + */ + protected function sendText(string $title='邮件标题',string $body='邮件正文'){ + $mail = new Mail($this->send_server_nickname,$this->send_server_host,$this->send_server_username,$this->send_server_pwd,$this->send_server_username,$this->port,$this->protocol); + $mail->debug = $this->debug; + return $mail->send($this->receiver_email,$this->receiver_nickname,$title,$body); + } + + /** + * desc:文本格式 + * author:wh + * @param array $data + * @param string $log_file_name + * @return array + */ + protected function sendTextAWS(string $title='邮件标题',string $body='邮件正文'){ + $mail = new Mail($this->send_server_nickname,$this->send_server_host,$this->send_server_username,$this->send_server_pwd,$this->sender_email,$this->port,$this->protocol); + $mail->debug = $this->debug; + return $mail->sendAWS($this->receiver_email,$this->receiver_nickname,$title,$body); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/log/driver/File.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/driver/File.php new file mode 100644 index 0000000..ace8db7 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/driver/File.php @@ -0,0 +1,157 @@ +write('333333'); + * $LogObj->write('444444'); + * $LogObj->write('55555'); + * $LogObj->flush(); + * + * tp框架使用方法: + * 1、注册全局LogObj对象属性(参考example/tags.php) + * 2、使用全局LogObj对象功能 + * eg: + * //写入数据到内存 + * request()->LogObj->write('333333'); + * request()->LogObj->write('测试'); + * request()->LogObj->write('444444'); + * //持久化存储 + * request()->LogObj->flush(); + * + * + * tp框架行为使用方法: + * 1、在应用目录中添加行为定义文件LoggerBehavior.php(参考example/LoggerBehavior.php) + * 2、注册全局LogObj对象属性 + * 3、注册行为(侦听标签位) + * + */ +class File extends Base +{ + //目录 + private $log_dir = 'runtime/log/sys_log';//默认日志目录 + + private $log_data = []; + public function __construct() + { + parent::__construct(); + } + + /** + * desc:设置日志目录 + * + * write之前调用 + * + * author:wh + * @param $log_dir + */ + function setLogDir($log_dir){ + $this->log_dir = 'runtime/log/'.$log_dir; + } + + /** + * desc:获取日志目录 + * + * author:wh + * @return string + */ + function getLogDir(){ + return $this->log_dir; + } + + /** + * desc:写日志 + * + * 先压到内存中,调用flush方法写入磁盘,建议一次不要压太多 + * + * author:wh + * @param $content + */ + function write($content){ + $ip = request()->ip(); + $write_time = $this->getMicrosecond(); + $date = date('Y-m-d H:i:s'); + $tmp = []; + $tmp['all'] = 0; + $tmp['date'] = $date; + $tmp['ip'] = $ip; + $tmp['url'] = request()->baseUrl(); + $tmp['reqid'] = $this->reqid; + + $tmp_data = []; + if($this->log_data){ + $use_time = $write_time-$this->log_data['content'][count($this->log_data['content'])-1]['time']; + $tmp_data['total'] = (array_sum(array_column($this->log_data['content'], 'use')) + $use_time); + $tmp_data['use'] = ($write_time-$this->log_data['content'][count($this->log_data['content'])-1]['time']); + $tmp_data['time'] = $write_time; + $tmp_data['log_info'] = $content; + }else{ + $tmp_data['total'] = 0; + $tmp_data['use'] = 0; + $tmp_data['time'] = $write_time; + $tmp_data['log_info'] = $content; + } + $tmp['input'] = $_POST; + $this->log_data['base'] = $tmp; + $this->log_data['content'][] = $tmp_data; + } + + /** + * desc:刷新日志数据到文件 + * + * 仅调用一次 + * + * author:wh + */ + function flush(){ + //计算单次request消耗 + $this->log_data['base']['all'] = array_sum(array_column($this->log_data['content'], 'use')); + + //dir + $this->log_dir = Tools::get_root_path().$this->log_dir.'/'.date('Ymd'); + + is_dir($this->log_dir)?:mkdir($this->log_dir, 0777, true); + + $log_file = $this->log_dir.'/log'.date('YmdH').'.txt'; + + $base = json_encode($this->log_data['base'], JSON_UNESCAPED_UNICODE); + $content = json_encode($this->log_data['content'], JSON_UNESCAPED_UNICODE); + + $content_str = str_replace('{"total"',"\n{'total'",$content); + $content_str = str_replace('"log_info":',"'log_info':\n",$content_str); + $strdata = "\n".$base ."\n".$content_str; + + file_put_contents($log_file, "\n".$strdata, FILE_APPEND); + } + + /** + * desc:获取当次请求日志数据 + * + * 注:需在write方法之后调用,否则可能不准确 + * + * author:wh + * @return array + */ + function getLogData(){ + return $this->log_data; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/log/driver/Mysql.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/driver/Mysql.php new file mode 100644 index 0000000..4dc2ff6 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/driver/Mysql.php @@ -0,0 +1,158 @@ +initLogTable(); + } + + /** + * desc:初始化日志表 + * + * author:wh + * @return mixed + */ + private function initLogTable(){ + $ddl = "Create Table If Not Exists `t_sys_period_log` ( + `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', + `url` varchar(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'URL(不含查询参数)', + `time` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '当前时间(微秒)', + `use` int unsigned DEFAULT '0' COMMENT '消耗(微秒)', + `all` int unsigned DEFAULT '0' COMMENT '总消耗(微秒)', + `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '当前时间', + `ip` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'IP', + `reqid` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '请求ID', + `user_agent` varchar(3000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '代理', + `input` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '输入', + `log_info` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '日志内容', + PRIMARY KEY (`id`), + KEY `index_create_time` (`create_time`) USING BTREE, + KEY `index_time` (`time`) USING BTREE, + KEY `index_all` (`all`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='系统周期日志';"; + + if(!\think\Cache::get('mysql_init_log_table_cache')){ + \think\Db::execute($ddl); + \think\Cache::set('mysql_init_log_table_cache',1);//1存在 + } + } + + /** + * desc:设置日志表名 + * + * author:wh + * @param $tab + */ + function setTable($tab){ + $this->table = $tab; + } + + /** + * + * desc:获取日志表名 + * + * author:wh + * @return string + */ + function getTable(){ + return $this->table; + } + + /** + * desc:写日志 + * + * 写进内存 + * + * author:wh + * @param $content + */ + function write($content){ + parent::write($content); + $log_len = count($this->log_data); + $time = $this->getMicrosecond(); + $use = $log_len>0?$time-$this->log_data[$log_len-1]['time']:0; + //data + $this->log_data[] = [ + 'url'=>request()->domain().request()->baseUrl(), + 'time'=>$this->getMicrosecond(), + 'ip'=>request()->ip(), + 'reqid'=>$this->getReqID(), + 'use'=>$use,//单次消耗 + 'all'=>array_sum(array_column($this->log_data, 'use'))+$use, + 'user_agent'=>json_encode(request()->header(),JSON_UNESCAPED_UNICODE), + 'input'=>json_encode(input(), JSON_UNESCAPED_UNICODE), + 'log_info'=>json_encode($content, JSON_UNESCAPED_UNICODE), + ]; + } + + /** + * desc:持久化 + * + * author:wh + */ + function flush() + { + parent::flush(); + return \think\Db::table($this->getTable())->insertAll($this->log_data); + } + + /** + * desc:获取日志数据 + * author:wh + * @return array|void + */ + function getLogData() + { + parent::getLogData(); + return $this->log_data; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/log/example/Logger.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/example/Logger.php new file mode 100644 index 0000000..f2c7c21 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/example/Logger.php @@ -0,0 +1,168 @@ +['1111'=>['222'=>['测试'=>'测试内容']]]]; +$res = Logger::log_email(['aaaaaaaa'=>['1111'=>['222']]],'test_log'); +dump($res); +$res = Logger::log_email(['bbbbbbbbbbbbbbb',$rspArray],'test_log'); +dump($res); + * + * Class Logger + * @package app\common\tools + */ +class Logger +{ + + /** + * desc:开启调试模式 + * author:wh + */ + static function open_debug(){ + LoggerObj::open_debug(); + } + + /** + * desc:关闭调试模式 + * author:wh + */ + static function close_debug(){ + LoggerObj::close_debug(); + } + /** + * desc:系统未来通用统一日志记录 + * + * eg:Logger::log(['测试测试测试测试.',$rspArray],$this->wechat_pay_log);//普通日志写入 + * + * author:wh + * @param array $data + * @param string $file_log_name + */ + static function log(array $data=[], string $file_log_name){ + try { + //服务器配置 start + $send_server_nickname = SundryConfigModel::getConfigVal('send_server_username'); + $send_server_username = SundryConfigModel::getConfigVal('send_server_username'); + $send_server_pwd = SundryConfigModel::getConfigVal('send_server_pwd'); + $send_server_host = SundryConfigModel::getConfigVal('send_server_host'); + //服务器配置 end + + //收件人信息 start + $receiver = SundryConfigModel::getConfigVal('admin_error_log_email'); + $receiver_nickname = '日志管理员';//默认收件人昵称 + //收件人信息 end + + //写入日志 + LoggerObj::set_email_config($send_server_nickname,$send_server_username,$send_server_pwd,$send_server_host); + LoggerObj::set_receiver_config($receiver,$receiver_nickname); + return LoggerObj::write_text($data,$file_log_name); + }catch (\Exception $e){ + Tools::log_to_write_txt(['error'=>'系统未来通用统一日志记录,出错.'.$e->getMessage(),'日志入参:'=>$data,'error_info'=>$e->getTraceAsString()],$file_log_name); + return ['code'=>500,'msg'=>'日志写入异常.'.$e->getMessage()]; + } + } + + /** + * desc:记录错误信息并发送邮件 + * + * eg:Logger::log_email(['收银宝统一支付-支付失败.',$rspArray],$this->wechat_pay_log); + * + * 小贴士:即将写入的数据将被添加一个error键名,再调用底层日志写入 + * + * 使用案例: + * + $rspArray = ['asfdasd'=>['1111'=>['222'=>['测试'=>'测试内容']]]]; + $res = Logger::log_email(['aaaaaaaa'=>['1111'=>['222']]],'test_log'); + dump($res); + $res = Logger::log_email(['bbbbbbbbbbbbbbb',$rspArray],'test_log'); + dump($res); + * + * + * 【注】 + * 必须的邮件配置包含: + * + send_server_nickname 邮件发送服务器昵称 + send_server_username 邮件发送服务器账号 + send_server_pwd 邮件发送服务器密码 + send_server_host 邮件发送服务器 + admin_error_log_email 接收异常日志的管理员邮箱 + * + * sql,复制可用: + INSERT INTO `fa_zc_sundry_config` ( `name`, `key`, `val`, `msg`, `group`, `ext_one`, `ext_two`, `ext_three`, `create_time`, `update_time`) VALUES ( '邮件发送服务器昵称', 'send_server_nickname', '掌电竞技', '', 'email', '', '', '', '2021-12-07 11:37:11', NULL); + INSERT INTO `fa_zc_sundry_config` ( `name`, `key`, `val`, `msg`, `group`, `ext_one`, `ext_two`, `ext_three`, `create_time`, `update_time`) VALUES ( '邮件发送服务器账号', 'send_server_username', '1003076950@qq.com', '', 'email', '', '', '', '2021-12-07 11:37:11', NULL); + INSERT INTO `fa_zc_sundry_config` ( `name`, `key`, `val`, `msg`, `group`, `ext_one`, `ext_two`, `ext_three`, `create_time`, `update_time`) VALUES ( '邮件发送服务器密码', 'send_server_pwd', 'pdosdyjuurowbcfc', '', 'email', '', '', '', '2021-12-07 11:37:11', NULL); + INSERT INTO `fa_zc_sundry_config` ( `name`, `key`, `val`, `msg`, `group`, `ext_one`, `ext_two`, `ext_three`, `create_time`, `update_time`) VALUES ( '邮件发送服务器', 'send_server_host', 'smtp.qq.com', '', 'email', '', '', '', '2021-12-07 11:37:11', NULL); + INSERT INTO `fa_zc_sundry_config` ( `name`, `key`, `val`, `msg`, `group`, `ext_one`, `ext_two`, `ext_three`, `create_time`, `update_time`) VALUES ( '接收异常日志的管理员邮箱', 'admin_error_log_email', '1003076950@qq.com', '', 'email', '', '', '', '2021-12-07 13:48:18', '2021-12-07 13:48:27'); + + * + * + * author:wh + * @param array $data + * @param string $file_log_name + */ + static function log_email(array $data=[], string $file_log_name){ + try { + //服务器配置 start + $send_server_nickname = SundryConfigModel::getConfigVal('send_server_username'); + $send_server_username = SundryConfigModel::getConfigVal('send_server_username'); + $send_server_pwd = SundryConfigModel::getConfigVal('send_server_pwd'); + $send_server_host = SundryConfigModel::getConfigVal('send_server_host'); + //服务器配置 end + if(empty($send_server_nickname)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器昵称为空.'],$file_log_name); + return Tools::set_res(500,'服务器配置错误.发件服务器昵称为空'); + } + if(empty($send_server_username)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器邮箱为空.'],$file_log_name); + return Tools::set_res(500,'服务器配置错误.发件服务器邮箱为空'); + } + if(empty($send_server_pwd)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器密码为空.'],$file_log_name); + return Tools::set_res(500,'服务器配置错误.发件服务器密码为空.'); + } + if(empty($send_server_host)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器HOST为空.'],$file_log_name); + return Tools::set_res(500,'服务器配置错误.发件服务器HOST为空..'); + } + + //收件人信息 start + $receiver = SundryConfigModel::getConfigVal('admin_error_log_email'); + $receiver_nickname = '日志管理员';//默认收件人昵称 + //收件人信息 end + if(empty($send_server_host)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器HOST为空.'],$file_log_name); + return Tools::set_res(500,'服务器配置错误.发件服务器HOST为空..'); + } + + LoggerObj::set_email_config($send_server_nickname,$send_server_username,$send_server_pwd,$send_server_host); + LoggerObj::set_receiver_config($receiver,$receiver_nickname); + + //写入日志 + return LoggerObj::write_text_email($data,$file_log_name); + }catch (\Exception $e){ + Tools::log_to_write_txt(['error'=>'系统未来通用统一日志记录,出错.'.$e->getMessage(),'日志入参:'=>$data,'error_info'=>$e->getTraceAsString()],$file_log_name); + return ['code'=>500,'msg'=>'日志写入异常.'.$e->getMessage()]; + } + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/log/example/LoggerBehavior.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/example/LoggerBehavior.php new file mode 100644 index 0000000..701fc94 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/example/LoggerBehavior.php @@ -0,0 +1,54 @@ +LogObj) { + request()->LogObj = new Driver(); + } + request()->LogObj->write(['app start']); + + + + //业务日志对象 + if(!request()->ServeLogObj) { + request()->ServeLogObj = new Driver(); + request()->ServeLogObj->setDriver('file'); + } + request()->ServeLogObj->write(['app serve start']); + } + function appEnd(){ + //框架生命周期日志对象 + request()->LogObj->write(['app end']); + request()->LogObj->flush(); + + + + //业务日志对象 + request()->ServeLogObj->write(['app serve end']); + request()->ServeLogObj->flush(); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/log/example/README.MD b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/example/README.MD new file mode 100644 index 0000000..c71853f --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/example/README.MD @@ -0,0 +1,70 @@ +## 可直接使用的案例 + +### 使用驱动记录日志 + 如需记录系统生命周期日志、临时调试日志(不推荐记录巨量日志数据),参考如下方案(简单修改即可): + + * 在tags.php文件中初始化日志对象并监听应用行为, 复制代码即可使用 + + ```` + + //框架生命周期日志(bind的变量名可以自定义,不一定是LogObj) + \think\Request::instance()->bind('LogObj',new \wanghua\general_utility_tools_php\log\Driver('mysql')); + //业务日志对象 + \think\Request::instance()->bind('ServeLogObj',new \wanghua\general_utility_tools_php\log\Driver('file')); + + ```` + + * (完成上一步即可使用)需要注意的是,LogObj对象的日志记录在变量中(内存中), + 一旦程序异常终止,则日志也将丢失。有一个好办法,那就是捕获系统异常并刷新已经 + 记录的日志到介质(磁盘、数据库)。在框架config.php配置文件中配置异常接管类, + 参考以下案例: + + ```` + + // 异常处理handle类 留空使用 \think\exception\Handle + // 如果想扩展异常类的功能,可以创建一个类来继承此类,然后把新建的类配置在这里 + 'exception_handle' => '\\wanghua\\general_utility_tools_php\\src\\exception\\SystemException', + + ```` + + * 在系统中,你可以随时记录日志,参考代码: + + `````` + + request()->LogObj->write(['你的文本,这里是数据哦!']);//记录一条简单的日志 + + `````` + + 当然,如果你觉得此代码不好记忆和使用的话,你可以自由封装此代码,方便在应用中调用。 + +### 灵活的日志记录方式(可以在系统任意地方使用) +eg1: + ``` + + //一维数组 + //记录一条简单的日志 + Tools::log_to_write_txt(['测试日志标题'=>'测试日志内容'],'test_log_dir'); + //记录错误日志 + Tools::log_to_write_txt([ + 'error'=>'系统错误.'.$e->getMessage(), + 'input'=>input(), + 'error_info'=>$e->getTraceAsString() + ],'test_log_dir'); + + ``` + +eg2: + ``` + + //多维数组 + Tools::log_to_write_txt([ + '数组类型的日志'=>'随便记录点什么', + [ + '可以是多维数组'=> + [ + 'a'=>'test' + ] + ] + ],'test_log_dir');//test_log_dir是你的日志文件路径 + + ``` \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/log/example/SundryConfigModel.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/example/SundryConfigModel.php new file mode 100644 index 0000000..009cf68 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/example/SundryConfigModel.php @@ -0,0 +1,30 @@ +where('key',$key)->value('val'); + } + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/log/example/tags.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/example/tags.php new file mode 100644 index 0000000..fb565cc --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/log/example/tags.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- + +// 应用行为扩展定义文件 + + +// 动态绑定Request对象属性 +//框架生命周期日志(用mysql驱动) +\think\Request::instance()->bind('LogObj',new \wanghua\general_utility_tools_php\log\Driver('mysql')); +//业务日志对象(用文件驱动) +\think\Request::instance()->bind('ServeLogObj',new \wanghua\general_utility_tools_php\log\Driver('file')); + + +// 注册 app\index\behavior\CheckLang行为类到app_init标签位 +\think\Hook::add('app_init', '\\wanghua\\general_utility_tools_php\\log\\example\\LoggerBehavior'); +//注册 app\admin\behavior\CronRun行为类到app_init标签位 +\think\Hook::add('app_end', '\\wanghua\\general_utility_tools_php\\log\\example\\LoggerBehavior'); + + + + +return [ + // 应用初始化 + 'app_init' => [], + // 应用开始 + 'app_begin' => [], + // 应用调度 + 'app_dispatch' => [ + 'app\\common\\behavior\\Common', + ], + // 模块初始化 + 'module_init' => [ + 'app\\common\\behavior\\Common', + ], + // 插件开始 + 'addon_begin' => [ + 'app\\common\\behavior\\Common', + ], + // 操作开始执行 + 'action_begin' => [], + // 视图内容过滤 + 'view_filter' => [], + // 日志写入 + 'log_write' => [], + // 应用结束 + 'app_end' => [], +]; diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/md5rsa/Md5RSA.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/md5rsa/Md5RSA.php new file mode 100644 index 0000000..3c6651e --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/md5rsa/Md5RSA.php @@ -0,0 +1,119 @@ +sign($signMsg='待加密字符串',$mo_bao_cust_privite_key='私钥'); + if(empty($crypted)){ + return Tools::set_res(500, '加密失败'); + } + + $eb64_cry = base64_encode($crypted); + + + * + * 私钥格式参考: + * -----BEGIN PRIVATE KEY----- + MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKaQA2YLzj0g6e1h + BB1KAu8wwabxX+7zfwBMdqhnC/B6hjJ37/tbH5vHlt2S0lvjphupLW/PdVF1ezIk + haGk1ya0qFiKfByXzdou3Ml4qzOemmozIAix5ooh4Cbq1Py6202SvbM7KYv89syk + HJNB4QWXLLWmgsHCuiXQXhRJz6jDAgMBAAECgYAIF5cSriAm+CJlVgFNKvtZg5Tk + 93UhttLEwPJC3D7IQCuk6A7Qt2yhtOCvgyKVNEotrdp3RCz++CY0GXIkmE2bj7i0 + fv5vT3kWvO9nImGhTBH6QlFDxc9+p3ukwsonnCshkSV9gmH5NB/yFoH1m8tck2Gm + BXDj+bBGUoKGWtQ7gQJBANR/jd5ZKf6unLsgpFUS/kNBgUa+EhVg2tfr9OMioWDv + MSqzG/sARQ2AbO00ytpkbAKxxKkObPYsn47MWsf5970CQQDIqRiGmCY5QDAaejW4 + HbOcsSovoxTqu1scGc3Qd6GYvLHujKDoubZdXCVOYQUMEnCD5j7kdNxPbVzdzXll + 9+p/AkEAu/34iXwCbgEWQWp4V5dNAD0kXGxs3SLpmNpztLn/YR1bNvZry5wKew5h + z1zEFX+AGsYgQJu1g/goVJGvwnj/VQJAOe6f9xPsTTEb8jkAU2S323BG1rQFsPNg + jY9hnWM8k2U/FbkiJ66eWPvmhWd7Vo3oUBxkYf7fMEtJuXu+JdNarwJAAwJK0YmO + LxP4U+gTrj7y/j/feArDqBukSngcDFnAKu1hsc68FJ/vT5iOC6S7YpRJkp8egj5o + pCcWaTO3GgC5Kg== + -----END PRIVATE KEY----- + */ + public function sign(string $data, string $private_key) + { + if (empty($data) || empty($private_key)) + { + return false; + } + //$private_key = file_get_contents(dirname(__FILE__).'/rsa_private_key.pem'); + //if (empty($private_key)) + //{ + // echo "Private Key error!"; + // return False; + //} + + $pkeyid = openssl_get_privatekey($private_key); + if (empty($pkeyid)) + { + // "private key resource identifier False!"; + return false; + } + + $verify = openssl_sign($data, $signature, $pkeyid, OPENSSL_ALGO_MD5); + openssl_free_key($pkeyid); + return $signature; + } + + /** + * 利用公钥和数字签名以及约定数据验证合法性 + * @param string $data 待验证数据 + * @param string $signature 数字签名 + * @return -1:error验证错误 1:correct验证成功 0:incorrect验证失败 + */ + public function isValid(string $data, string $signature, string $public_key) + { + if (empty($data) || empty($signature) || empty($public_key)) + { + return false; + } + + //$public_key = file_get_contents(dirname(__FILE__).'/rsa_public_key.pem'); + //if (empty($public_key)) + //{ + // echo "Public Key error!"; + // return False; + //} + + $pkeyid = openssl_get_publickey($public_key); + if (empty($pkeyid)) + { + // "public key resource identifier False!"; + return false; + } + + $ret = openssl_verify($data, $signature, $pkeyid, OPENSSL_ALGO_MD5); + //switch ($ret) + //{ + // case -1: + // echo "error"; + // break; + // default: + // echo $ret==1 ? "correct" : "incorrect";//0:incorrect + // break; + //} + return $ret==1; + } + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/mysql/README.MD b/superadmin/vendor/wanghua/general-utility-tools-php/src/mysql/README.MD new file mode 100644 index 0000000..1960d4c --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/mysql/README.MD @@ -0,0 +1,6 @@ +## 仅适用于tp5+, php7+ + + +## 表管理 + +## 字段管理 \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/mysql/lib/Field.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/mysql/lib/Field.php new file mode 100644 index 0000000..d164e3e --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/mysql/lib/Field.php @@ -0,0 +1,314 @@ +getTableFields(); + if(in_array($table['fields_name'], $f)){ + //枚举类型 + if ($table['type'] == 'enum'){ + $enum_str = ''; + $enum_default = ''; + //组合枚举值 + if($table['default']){ + $enum_str.='ENUM('.$table['default'].')'; + $enum_default = explode(',', $table['default'])[0]; + } + $sql2 = "ALTER TABLE {$table_name} MODIFY {$table['fields_name']} {$enum_str} NOT NULL DEFAULT {$enum_default} COMMENT '{$table['title']}';"; + }else{ + $sql2 = "ALTER TABLE {$table_name} MODIFY COLUMN {$table['fields_name']} {$table['type']}(".$table['size'].$dec_num.") {$is_unsigned} DEFAULT {$table['default']} COMMENT '{$table['title']}';"; + } + }else{ + + if(in_array($table['type'], ['text','longtext'])){ + $sql2 = "ALTER TABLE {$table_name} ADD COLUMN {$table['fields_name']} {$table['type']} COMMENT '{$table['title']}' AFTER id;"; + }elseif ($table['type'] == 'enum'){//枚举类型 + $enum_str = ''; + $enum_default = ''; + //组合枚举值 + if($table['default']){ + $enum_str.='ENUM('.$table['default'].')'; + $enum_default = explode(',', $table['default'])[0]; + } + $sql2 = "ALTER TABLE {$table_name} ADD {$table['fields_name']} {$enum_str} NOT NULL DEFAULT {$enum_default} COMMENT '{$table['title']}' AFTER id;"; + }else{ + $sql2 = "ALTER TABLE {$table_name} ADD COLUMN {$table['fields_name']} {$table['type']}(".$table['size'].$dec_num.") {$is_unsigned} DEFAULT {$table['default']} COMMENT '{$table['title']}' AFTER id;"; + } + } + + DB::execute($sql2); + } + + /** + * desc:删除字段 + * author:wh + * @param $tablename + * @param $fieldname + */ + function dropFieldName($tablename, $fieldname){ + $sql = "ALTER TABLE {$tablename} DROP COLUMN {$fieldname};"; + DB::execute($sql); + } + + /** + * desc:改注释 + * author:wh + * @param $tablename + * @param $comment + * @return bool + */ + function updateComment($tablename,$field, $comment){ + + $sql = "ALTER TABLE {$tablename} MODIFY COLUMN {$field} INT COMMENT '{$comment}';"; + Db::execute($sql); + } + + /** + * desc:获取数据表中所有字段的数据类型(含其它属性) + * + * 查询结果字段名说明 + * + * F_FIELD:字段名称 + * F_DATATYPE:数据类型 + * F_DATALENGTH:数据长度(int类型默认为null,业务处理时默认为10即可) + * F_PRECISION:精度 + * F_DECIMAL_DIGITS:小数位数 + * F_ALLOWNULL:是否允许为null值(1是,0否) + * F_FIELDNAME:字段名称 + * F_PRIMARYKEY:是否主键(1是,0否) + * F_DEFAULTS:字段默认值 + * + * author:wh + * @param string $dbname + * @param string $tablename + * @param string $fieldname + * @return mixed + */ + function getFieldsDataType(string $dbname,string $tablename){ + $sql = "SELECT + COLUMN_NAME F_FIELD, + data_type F_DATATYPE, + CHARACTER_MAXIMUM_LENGTH F_DATALENGTH, + NUMERIC_PRECISION F_PRECISION, + NUMERIC_SCALE F_DECIMAL_DIGITS, +IF + ( IS_NULLABLE = 'YES', '1', '0' ) F_ALLOWNULL, + COLUMN_COMMENT F_FIELDNAME, +IF + ( COLUMN_KEY = 'PRI', '1', '0' ) F_PRIMARYKEY, + column_default F_DEFAULTS, + CONCAT( upper( COLUMN_NAME ), '(', COLUMN_COMMENT, ')' ) AS 'F_DESCRIPTION' +FROM + INFORMATION_SCHEMA.COLUMNS +WHERE + TABLE_NAME = '$tablename' + AND TABLE_SCHEMA = '$dbname'"; + return Db::query($sql); + } + + + /** + * desc:获取数据表中某个字段的数据类型(含这个字段的其它属性) + * + * 查询结果字段名说明 + * + * F_FIELD:字段名称 + * F_DATATYPE:数据类型 + * F_DATALENGTH:数据长度(int类型默认为null,业务处理时默认为10即可) + * F_PRECISION:精度 + * F_DECIMAL_DIGITS:小数位数 + * F_ALLOWNULL:是否允许为null值(1是,0否) + * F_FIELDNAME:字段名称 + * F_PRIMARYKEY:是否主键(1是,0否) + * F_DEFAULTS:字段默认值 + * + * author:wh + * @param string $dbname + * @param string $tablename + * @param string $fieldname + * @return mixed + */ + function getFieldDataType(string $dbname,string $tablename,string $fieldname){ + $sql = "SELECT + COLUMN_NAME F_FIELD, + data_type F_DATATYPE, + CHARACTER_MAXIMUM_LENGTH F_DATALENGTH, + NUMERIC_PRECISION F_PRECISION, + NUMERIC_SCALE F_DECIMAL_DIGITS, +IF + ( IS_NULLABLE = 'YES', '1', '0' ) F_ALLOWNULL, + COLUMN_COMMENT F_FIELDNAME, +IF + ( COLUMN_KEY = 'PRI', '1', '0' ) F_PRIMARYKEY, + column_default F_DEFAULTS, + CONCAT( upper( COLUMN_NAME ), '(', COLUMN_COMMENT, ')' ) AS 'F_DESCRIPTION' +FROM + INFORMATION_SCHEMA.COLUMNS +WHERE + TABLE_NAME = '$tablename' + AND TABLE_SCHEMA = '$dbname' + AND COLUMN_NAME='$fieldname' + "; + $ar = Db::query($sql); + return $ar[0]; + } + + + /** + * desc:获取数据表中某个字段的数据类型 + * + * 不含这个字段的其它属性 + * + * + * 查询结果字段名说明 + * + * F_FIELD:字段名称 + * F_DATATYPE:数据类型 + * F_DATALENGTH:数据长度(int类型默认为null,业务处理时默认为10即可) + * F_PRECISION:精度 + * F_DECIMAL_DIGITS:小数位数 + * F_ALLOWNULL:是否允许为null值(1是,0否) + * F_FIELDNAME:字段名称 + * F_PRIMARYKEY:是否主键(1是,0否) + * F_DEFAULTS:字段默认值 + * + * author:wh + * @param string $dbname + * @param string $tablename + * @param string $fieldname + * @return mixed + */ + function getFieldDataTypeVal(string $dbname,string $tablename,string $fieldname){ + $sql = "SELECT + COLUMN_NAME F_FIELD, + data_type F_DATATYPE, + CHARACTER_MAXIMUM_LENGTH F_DATALENGTH, + NUMERIC_PRECISION F_PRECISION, + NUMERIC_SCALE F_DECIMAL_DIGITS, +IF + ( IS_NULLABLE = 'YES', '1', '0' ) F_ALLOWNULL, + COLUMN_COMMENT F_FIELDNAME, +IF + ( COLUMN_KEY = 'PRI', '1', '0' ) F_PRIMARYKEY, + column_default F_DEFAULTS, + CONCAT( upper( COLUMN_NAME ), '(', COLUMN_COMMENT, ')' ) AS 'F_DESCRIPTION' +FROM + INFORMATION_SCHEMA.COLUMNS +WHERE + TABLE_NAME = '$tablename' + AND TABLE_SCHEMA = '$dbname' + AND COLUMN_NAME='$fieldname' + "; + $ar = Db::query($sql); + return $ar[0]['F_DATATYPE']; + } + + /** + * desc:获取数据表中某个字段的属性 + * + * 查询结果字段名说明 + * + * F_FIELD:字段名称 + * F_DATATYPE:数据类型 + * F_DATALENGTH:数据长度(int类型默认为null,业务处理时默认为10即可) + * F_PRECISION:精度 + * F_DECIMAL_DIGITS:小数位数 + * F_ALLOWNULL:是否允许为null值(1是,0否) + * F_FIELDNAME:字段名称 + * F_PRIMARYKEY:是否主键(1是,0否) + * F_DEFAULTS:字段默认值 + * + * author:wh + * @param string $dbname + * @param string $tablename + * @param string $fieldname + * @return mixed + */ + function getFieldAttrVal(string $dbname,string $tablename,string $fieldname,string $attr){ + $sql = "SELECT + COLUMN_NAME F_FIELD, + data_type F_DATATYPE, + CHARACTER_MAXIMUM_LENGTH F_DATALENGTH, + NUMERIC_PRECISION F_PRECISION, + NUMERIC_SCALE F_DECIMAL_DIGITS, +IF + ( IS_NULLABLE = 'YES', '1', '0' ) F_ALLOWNULL, + COLUMN_COMMENT F_FIELDNAME, +IF + ( COLUMN_KEY = 'PRI', '1', '0' ) F_PRIMARYKEY, + column_default F_DEFAULTS, + CONCAT( upper( COLUMN_NAME ), '(', COLUMN_COMMENT, ')' ) AS 'F_DESCRIPTION' +FROM + INFORMATION_SCHEMA.COLUMNS +WHERE + TABLE_NAME = '$tablename' + AND TABLE_SCHEMA = '$dbname' + AND COLUMN_NAME='$fieldname' + "; + $ar = Db::query($sql); + return $ar[0][$attr]; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/mysql/lib/Table.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/mysql/lib/Table.php new file mode 100644 index 0000000..8f44e9f --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/mysql/lib/Table.php @@ -0,0 +1,181 @@ +getNowDbName(); + return array_column(Db::query('SHOW TABLES;'), 'Tables_in_'.$dbname); + } + + /** + * desc:获取数据表字所有段名 + * author:wh + * @param $tablename + * @return array + */ + function getTableFields($tablename){ + $dbname = $this->getNowDbName(); + $sql = "SELECT COLUMN_NAME column_name,COLUMN_COMMENT column_comment,DATA_TYPE data_type +FROM information_schema.columns WHERE TABLE_NAME='{$tablename}' AND table_schema='{$dbname}'"; + return array_column(Db::query($sql), 'column_name'); + } + + /** + * desc:查询当前数据库名 + * author:wh + * @return mixed + */ + function getNowDbName(){ + $sql = "SELECT DATABASE() AS dbname;"; + return Db::query($sql)['dbname']; + } + /** + * desc:获取表的属性 + * Name: + 表名称 + Engine: + 表的存储引擎 + Version: + 版本 + Row_format: + 行格式。对于MyISAM引擎,这可能是Dynamic,Fixed或Compressed。动态行的行长度可变,例如Varchar或Blob类型字段。固定行是指行长度不变,例如Char和Integer类型字段 + Rows: + 表中的行数。对于MyISAM和其他存储引擎,这个值是精确的,对于innoDB存储引擎,这个值通常是估算的 + Avg_row_length: + 平均每行包括的字节数 + Data_length: + 整个表的数据量(以字节为单位) + Max_data_length: + 表可以容纳的最大数据量,该值和存储引擎相关 + Index_length: + 索引占用磁盘的空间大小(以字节为单位) + Data_free: + 对于MyISAM引擎,表示已经分配,但目前没有使用的空间。这部分空间包含之前被删除的行,以及后续可以被insert利用到的空间 + Auto_increment: + 下一个Auto_increment的值 + Create_time: + 表的创建时间 + Update_time: + 表的最近更新时间 + Check_time: + 使用 check table 或myisamchk工具最后一次检查表的时间 + Collation: + 表的默认字符集和字符排序规则 + Checksum: + 如果启用,保存的是整个表的实时校验和 + Create_options: + 创建表时指定的其他选项 + Comment: + 包含了其他额外信息,对于MyISAM引擎,保存的是表在创建时带的注释。如果表使用的是innodb引擎 ,保存的是InnoDB表空间的剩余空间。如果是一个视图,注释里面包含了VIEW字样。 + * + * @param $tablename 表名(无表名则查询所有表) + * + * author:wh + * @return mixed + */ + function getTableInfo(string $tablename=''){ + if($tablename){ + $sql = "SHOW TABLE STATUS WHERE Name = 'fa_agent';"; + return Db::query($sql); + } + $sql = 'show table status;'; + return Db::query($sql); + } + + /** + * desc:检查某个表是否存在 + * + * author:wh + * @param string $dbname + * @param string $tablename + * @return bool + */ + function isExistTable(string $dbname,string $tablename){ + $sql = "SELECT TABLE_SCHEMA,TABLE_NAME +FROM information_schema.TABLES +WHERE TABLE_SCHEMA ='$dbname' + AND TABLE_NAME = '$tablename';"; + return empty(Db::query($sql)); + } + + /** + * desc:获取表注释 + * + * author:wh + */ + function getTableComment($table){ + $prefix = config('database.prefix'); + $table = $prefix.$table; + $sql = 'show table status;'; + $arr = Db::query($sql); + $tmp = Tools::key_val_arr($arr,'Name','Comment'); + return empty($tmp[$table])?'':$tmp[$table]; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/alicloud/AliyunOSS.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/alicloud/AliyunOSS.php new file mode 100644 index 0000000..3d71888 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/alicloud/AliyunOSS.php @@ -0,0 +1,140 @@ +aliyun_config = $aliyun_config; + if(empty($aliyun_config['AccessKeyId'])||empty($aliyun_config['AccessKeySecret'])||empty($aliyun_config['bucket'])){ + throw new \Exception('OSS 配置错误'); + } + if(empty($this->bucket)){ + $this->bucket = $aliyun_config['bucket']; + } + //默认存储路径 + $this->setSavePath(); + // 从环境变量中获取RAM用户的访问密钥(AccessKey ID和AccessKey Secret)。 + $accessKeyId = $aliyun_config['AccessKeyId'];//config('aliyun_config.AccessKeyId'); + $accessKeySecret = $aliyun_config['AccessKeySecret'];//config('aliyun_config.AccessKeySecret'); + // 使用代码嵌入的RAM用户的访问密钥配置访问凭证。 + $this->provider = new StaticCredentialsProvider($accessKeyId, $accessKeySecret); + // 填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。 + $endpoint = $aliyun_config['endpoint']; + $config = array( + "provider" => $this->provider, + "signatureVersion" => OssClient::OSS_SIGNATURE_VERSION_V4, + // 填写Endpoint对应的Region信息,例如cn-hangzhou。 + "region" => $this->aliyun_config['region_id'], + "endpoint" => $endpoint, + ); + $this->ossClient = new OssClient($config); + } + + + /** + * desc:生成临时访问凭证。 + * + * doc:https://help.aliyun.com/zh/oss/developer-reference/authorize-access-2?spm=a2c4g.11186623.0.i6#section-8rj-9sk-q7r + * author:wh + */ + function getAccessToken(){ + return Mmodel::catch(function (){ + // 运行本代码示例之前,请确保已使用步骤1创建的RAM用户的访问密钥设置环境变量YOUR_ACCESS_KEY_ID和YOUR_ACCESS_KEY_SECRET。 + $config = new Config([ + 'accessKeyId' => $this->aliyun_config['AccessKeyId'],//getenv('YOUR_ACCESS_KEY_ID'), + 'accessKeySecret' => $this->aliyun_config['AccessKeySecret'],//getenv('YOUR_ACCESS_KEY_SECRET'), + ]); + // + $config->endpoint = $this->aliyun_config['sts_endpoint']; + $client = new Sts($config); + + $assumeRoleRequest = new AssumeRoleRequest([ + // roleArn填写步骤2获取的角色ARN,例如acs:ram::175708322470****:role/ramtest。 + "roleArn" => "acs:ram::1113242774600735:role/ramoss", + // roleSessionName用于自定义角色会话名称,用来区分不同的令牌,例如填写为sessiontest。 + "roleSessionName" => "sessiontest", + // durationSeconds用于设置临时访问凭证有效时间单位为秒,最小值为900,最大值以当前角色设定的最大会话时间为准。本示例指定有效时间为3000秒。 + "durationSeconds" => 3000, + // policy填写自定义权限策略,用于进一步限制STS临时访问凭证的权限。如果不指定Policy,则返回的STS临时访问凭证默认拥有指定角色的所有权限。 + // 临时访问凭证最后获得的权限是步骤4设置的角色权限和该Policy设置权限的交集。 + //"policy" => "" + ]); + $runtime = new RuntimeOptions([ + //'http' => [ + // 'verify' => false, // 禁用SSL验证,仅限于调试 + //], + ]); + $result = $client->assumeRoleWithOptions($assumeRoleRequest, $runtime); + //printf("AccessKeyId:" . $result->body->credentials->accessKeyId. PHP_EOL); + //printf("AccessKeySecret:".$result->body->credentials->accessKeySecret.PHP_EOL); + //printf("Expiration:".$result->body->credentials->expiration.PHP_EOL); + //printf("SecurityToken:".$result->body->credentials->securityToken.PHP_EOL); + return Tools::set_ok('ok',[ + 'accessKeyId'=>$result->body->credentials->accessKeyId, + 'accessKeySecret'=>$result->body->credentials->accessKeySecret, + 'expiration'=>$result->body->credentials->expiration, + 'securityToken'=>$result->body->credentials->securityToken, + ]); + }); + } + + /** + * desc:设置文件保存目录 + * author:wh + * @param string $unique_id 唯一id,可以是用户id,可以是其它用于区分的标识 + * @return string + */ + function setSavePath($unique_id=''){ + if($unique_id){ + $unique_id = $unique_id.'/'; + } + //项目名称 + $project_name = Tools::get_project_name(); + //控制器方法路径 + $ctl = strtolower(request()->controller().'/'.request()->action()); + $this->file_save_path = $project_name.'/'.$ctl.'/'.$unique_id.date('Ymd').'/';//日期 + return $this->file_save_path; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/alicloud/Bucket.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/alicloud/Bucket.php new file mode 100644 index 0000000..539c7fc --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/alicloud/Bucket.php @@ -0,0 +1,101 @@ +aliyun_config['bucket'])){ + throw new \Exception('OSS bucket必须'); + } + $bucket_name = !$bucket_name?$this->aliyun_config['bucket']:$this->aliyun_config['bucket'].'-'.$bucket_name; + return Mmodel::catch(function () use ($bucket_name,$opts,$oss_acl_type){ + // 填写Bucket名称,例如examplebucket。 + //$bucket_name = "examplebucket"; + // 设置Bucket的存储类型为标准类型。 + $options = $opts?:array( + OssClient::OSS_STORAGE => OssClient::OSS_STORAGE_STANDARD + ); + return $this->ossClient->createBucket($bucket_name, $oss_acl_type, $options); + }); + } + + + /** + * desc:列出所有bucket + * author:wh + * @return array + */ + function getAllBucket(){ + return Mmodel::catch(function (){ + // 列举当前账号所有地域下的存储空间。 + $bucketListInfo = $this->ossClient->listBuckets(); + + $bucketList = $bucketListInfo->getBucketList(); + $arr = []; + foreach($bucketList as $bucket) { + $arr['name'] = $bucket->getName(); + $arr['location'] = $bucket->getLocation(); + $arr['create_time'] = $bucket->getCreatedate(); + } + return $arr; + }); + } + + /** + * desc:查询指定前缀bucket + * author:wh + * @param $prefix 指定前缀 + * @throws OssException + * @throws \OSS\Http\RequestCore_Exception + */ + function getBucketPrefix($prefix){ + + return Mmodel::catch(function () use ($prefix){ + + $options = array(OssClient::OSS_QUERY_STRING => array(OssClient::OSS_PREFIX => $prefix)); + // 列举当前账号所有地域下指定前缀的存储空间。 + $bucketListInfo = $this->ossClient->listBuckets($options); + + $bucketList = $bucketListInfo->getBucketList(); + $arr = []; + foreach($bucketList as $bucket) { + $arr['name'] = $bucket->getName(); + $arr['location'] = $bucket->getLocation(); + $arr['create_time'] = $bucket->getCreatedate(); + } + return $arr; + }); + } + + /** + * desc:删除bucket + * author:wh + * @param $bucket_name + * doc:https://help.aliyun.com/zh/oss/developer-reference/delete-buckets-3?spm=a2c4g.11186623.0.0.61556dd5SHNYgV + * @return array + */ + function deleteBucket($bucket_name){ + return Mmodel::catch(function () use ($bucket_name){ + // 填写Bucket名称,例如examplebucket。 + return $this->ossClient->deleteBucket($bucket_name); + }); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/alicloud/Objects.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/alicloud/Objects.php new file mode 100644 index 0000000..76d4b46 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/alicloud/Objects.php @@ -0,0 +1,318 @@ + $opts?:[ + 'x-oss-object-acl' => 'public-read',//$permissions='public-read-write' + 'x-oss-meta-info' => $content + ], + ]; + return $this->ossClient->putObject($this->bucket, $this->file_save_path.$object, $content, $options); + }); + } + + + /** + * desc:文件上传 + * + * author:wh + * @param string $object 上传后的文件名,完整路径中不能包含Bucket名称 如:"exampledir/exampleobject.txt"; + * @param string $filePath 本地文件的完整路径,例如:D:\\localpath\\examplefile.txt, $file->getInfo('tmp_name')可获取临时文件路径 + * @param $opts 权限等其它header参数配置 + * 访问权限: + * 文件的访问权限(ACL)有以下四种:default,private,public-read,public-read-write + * 权限doc: https://help.aliyun.com/zh/oss/developer-reference/manage-object-acls-6?spm=a2c4g.11186623.0.0.63286dd53fYcTB + * @return array + */ + function fileUpload($object, $filePath, $opts=[]){ + return Mmodel::catch(function () use ($object, $filePath,$opts){ + $options = [ + OssClient::OSS_HEADERS => $opts?:[ + 'x-oss-object-acl' => 'public-read',//'public-read-write' + ], + ]; + return $this->ossClient->uploadFile($this->bucket, $object, $filePath,$options); + }); + } + + /** + * desc:文件流上传 + * + * author:wh + * @param $object + * @param $stream + * @param array $opts + * @return array + */ + function uploadFileStream($object, $stream, $opts=[]){ + return Mmodel::catch(function () use ($object, $stream,$opts){ + $options = [ + OssClient::OSS_HEADERS => $opts?:[ + 'x-oss-object-acl' => 'public-read',//'public-read-write' + ], + ]; + // 从文件流上传 + $res = $this->ossClient->putObject($this->bucket, $this->file_save_path.$object, $stream, $options); + return $res; + }); + } + /** + * desc:分片上传文件 + * author:wh + * @param $object 上传后的文件名,完整路径中不能包含Bucket名称 如:"exampledir/exampleobject.txt"; + * @param $uploadFile 填写本地文件的完整路径 如:'D:\\localpath\\examplefile.txt' + */ + function multipartUpload($object,$uploadFile){ + $object = $this->file_save_path.$object; + //填写不包含Bucket名称在内的Object完整路径,例如exampledir/exampleobject.txt。 + //$object = 'exampledir/exampleobject.txt'; + // 填写本地文件的完整路径。 + //$uploadFile = 'D:\\localpath\\examplefile.txt'; + $uploadId = ''; + $initOptions = array( + OssClient::OSS_HEADERS => array( + // 指定该Object被下载时的网页缓存行为。 + // 'Cache-Control' => 'no-cache', + // 指定该Object被下载时的名称。 + // 'Content-Disposition' => 'attachment;filename=oss_download.jpg', + // 指定该Object被下载时的内容编码格式。 + // 'Content-Encoding' => 'utf-8', + // 指定过期时间,单位为毫秒。 + 'Expires' => 150, + // 指定初始化分片上传时是否覆盖同名Object。此处设置为true,表示禁止覆盖同名Object。 + 'x-oss-forbid-overwrite' => 'false', + // 指定上传该Object的每个part时使用的服务器端加密方式。 + // 'x-oss-server-side-encryption'=> 'KMS', + // 指定Object的加密算法。 + // 'x-oss-server-side-data-encryption'=>'SM4', + // 指定KMS托管的用户主密钥。 + //'x-oss-server-side-encryption-key-id' => '9468da86-3509-4f8d-a61e-6eab1eac****', + // 指定Object的存储类型。 + 'x-oss-storage-class' => 'Standard', + // 指定Object的对象标签,可同时设置多个标签。 + // 'x-oss-tagging' => 'TagA=A&TagB=B', + ), + OssClient::OSS_ACL => OssClient::OSS_ACL_TYPE_PUBLIC_READ, + ); + + /** + * 步骤1:初始化一个分片上传事件,并获取uploadId。 + */ + try{ + + //返回uploadId。uploadId是分片上传事件的唯一标识,您可以根据uploadId发起相关的操作,如取消分片上传、查询分片上传等。 + $uploadId = $this->ossClient->initiateMultipartUpload($this->bucket, $object, $initOptions); + //print("initiateMultipartUpload OK" . "\n"); + // 根据uploadId执行取消分片上传事件或者列举已上传分片的操作。 + // 如果您需要根据您需要uploadId执行取消分片上传事件的操作,您需要在调用InitiateMultipartUpload完成初始化分片之后获取uploadId。 + // 如果您需要根据您需要uploadId执行列举已上传分片的操作,您需要在调用InitiateMultipartUpload完成初始化分片之后,且在调用CompleteMultipartUpload完成分片上传之前获取uploadId。 + //print("UploadId: " . $uploadId . "\n"); + } catch(OssException $e) { + Tools::error_txt_log($e); + return Tools::set_fail('初始化一个分片上传事件,并获取uploadId ERROR.'.$e->getMessage()); + } + //dump('$uploadId:'.$uploadId); + + /* + * 步骤2:上传分片。 + */ + $partSize = 10 * 1024 * 1024; + $uploadFileSize = sprintf('%u',filesize($uploadFile)); + $pieces = $this->ossClient->generateMultiuploadParts($uploadFileSize, $partSize); + $responseUploadPart = array(); + $uploadPosition = 0; + $isCheckMd5 = true; + foreach ($pieces as $i => $piece) { + $fromPos = $uploadPosition + (integer)$piece[$this->ossClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[$this->ossClient::OSS_LENGTH] + $fromPos - 1; + $upOptions = array( + // 上传文件。 + $this->ossClient::OSS_FILE_UPLOAD => $uploadFile, + // 设置分片号。 + $this->ossClient::OSS_PART_NUM => ($i + 1), + // 指定分片上传起始位置。 + $this->ossClient::OSS_SEEK_TO => $fromPos, + // 指定文件长度。 + $this->ossClient::OSS_LENGTH => $toPos - $fromPos + 1, + // 是否开启MD5校验,true为开启。 + $this->ossClient::OSS_CHECK_MD5 => $isCheckMd5, + ); + // 开启MD5校验。 + if ($isCheckMd5) { + $contentMd5 = OssUtil::getMd5SumForFile($uploadFile, $fromPos, $toPos); + $upOptions[$this->ossClient::OSS_CONTENT_MD5] = $contentMd5; + } + try { + // 上传分片。 + $responseUploadPart[] = $this->ossClient->uploadPart($this->bucket, $object, $uploadId, $upOptions); + //printf("initiateMultipartUpload, uploadPart - part#{$i} OK\n"); + } catch(OssException $e) { + Tools::error_txt_log($e); + //printf("initiateMultipartUpload, uploadPart - part#{$i} FAILED\n"); + //printf($e->getMessage() . "\n"); + return Tools::set_fail('上传分片ERROR.'.$e->getMessage()); + } + } + // $uploadParts是由每个分片的ETag和分片号(PartNumber)组成的数组。 + $uploadParts = array(); + foreach ($responseUploadPart as $i => $eTag) { + $uploadParts[] = array( + 'PartNumber' => ($i + 1), + 'ETag' => $eTag, + ); + } + //dump($uploadParts); + //die; + /** + * 步骤3:完成上传。 + */ + $comOptions['headers'] = array( + // 指定完成分片上传时是否覆盖同名Object。此处设置为true,表示禁止覆盖同名Object。 + 'x-oss-forbid-overwrite' => 'false', + // 如果指定了x-oss-complete-all:yes,则OSS会列举当前uploadId已上传的所有Part,然后按照PartNumber的序号排序并执行CompleteMultipartUpload操作。 + // 'x-oss-complete-all'=> 'yes' + ); + + try { + // 执行completeMultipartUpload操作时,需要提供所有有效的$uploadParts。OSS收到提交的$uploadParts后,会逐一验证每个分片的有效性。当所有的数据分片验证通过后,OSS将把这些分片组合成一个完整的文件。 + $res = $this->ossClient->completeMultipartUpload($this->bucket, $object, $uploadId, $uploadParts,$comOptions); + //dump($res); + //printf( "Complete Multipart Upload OK\n"); + $this->ossClient->putObjectAcl($this->bucket, $object, OssClient::OSS_ACL_TYPE_PUBLIC_READ); + return Tools::xml_to_array(($res['body'])); + } catch(OssException $e) { + Tools::error_txt_log($e); + //printf("Complete Multipart Upload FAILED\n"); + //printf($e->getMessage() . "\n"); + return Tools::set_fail('ERROR:Complete Multipart Upload FAILED.'.$e->getMessage()); + } + } + + /** + * desc:查询文件列表 + * author:wh + * @param array $options + * @return array + */ + function getLists($options=[]){ + return Mmodel::catch(function () use ($options){ + $options = $options?:[ + 'max-keys'=>200,//设置最大个数为200。 + 'delimiter' => '',//分隔符,表示将Object名称按该分隔符进行截断,并返回结果。 + ]; + $listObjectInfo = $this->ossClient->listObjects($this->bucket,$options); + //文件列表 + $objectList = $listObjectInfo->getObjectList(); + //前缀列表 + //$prefixList = $listObjectInfo->getPrefixList(); + $arr = []; + foreach ($objectList as $objectInfo) { + $arr[] = $objectInfo->getKey(); + } + return $arr; + }); + } + + /** + * desc:文件下载到本地 + * + * author:wh + * @param string $object 存储空间内的文件名 eg: testfolder/exampleobject.txt + * + * @param string $save_local_path 本地保存路径 eg: D:\\localpath\\examplefile.txt + * 如果指定的本地文件存在会覆盖,不存在则新建。如果未指定本地路径,则下载后的文件默认保存到示例程序所属项目对应本地路径中。 + * + * @param array $options 可选参数 + */ + function downloadLocal($object,$save_local_path='',$options=[]){ + // 填写不包含Bucket名称在内的Object完整路径,例如testfolder/exampleobject.txt。 + //$object = "testfolder/exampleobject.txt"; + // 下载Object到本地文件examplefile.txt,并保存到指定的本地路径中(D:\\localpath)。如果指定的本地文件存在会覆盖,不存在则新建。 + // 如果未指定本地路径,则下载后的文件默认保存到示例程序所属项目对应本地路径中。 + //$save_local_path = "D:\\localpath\\examplefile.txt"; + $opts = []; + if($save_local_path){ + $opts = array( + OssClient::OSS_FILE_DOWNLOAD => $save_local_path + ); + } + + if($options){ + $opts = array_merge($opts, $options); + } + // 使用try catch捕获异常。如果捕获到异常,则说明下载失败;如果没有捕获到异常,则说明下载成功。 + try{ + $res = $this->ossClient->getObject($this->bucket, $object, $opts); + if($res){ + return $res; + } + //下载成功 + //print(__FUNCTION__ . ": OK, please check localfile: 'examplefile.txt'" . "\n"); + return Tools::set_ok('ok',['save_path'=>$save_local_path]); + } catch(OssException $e) { + Tools::error_txt_log($e); + //printf(__FUNCTION__ . ": FAILED\n"); + //printf($e->getMessage() . "\n"); + return Tools::set_fail('ERROR:'.$e->getMessage()); + } + } + + /** + * desc:下载文件到内存 + * + * author:wh + * @param $object + * @throws \OSS\Http\RequestCore_Exception + */ + function downloadMemory($object){ + try{ + $this->ossClient->getObject($this->bucket, $object); + //dump($content); + return Tools::set_ok('下载文件到内存ok'); + } catch(OssException $e) { + Tools::error_txt_log($e); + return Tools::set_fail('ERROR:'.$e->getMessage()); + } + } + + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/Config.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/Config.php new file mode 100644 index 0000000..c256a8d --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/Config.php @@ -0,0 +1,29 @@ + Config::$key, + 'secret' => Config::$secret, + 'endpoint' => Config::$endpoint, + 'socket_timeout' => $socket_timeout, + 'connect_timeout' => $connect_timeout + ] ); + } + return self::$obsClient; + } + + /** + * desc:关闭obsClient + * author:wh + */ + static function close(){ + // 关闭obsClient + return self::$obsClient->close(); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/README.md b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/README.md new file mode 100644 index 0000000..f44c4e0 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/README.md @@ -0,0 +1,4 @@ +## 华为OBS-对象存储服务 +###支持常规功能,例如文件上传下载。 +####温馨提示:你可能会遇到权限拒绝问题,请确认在华为云控制台已给用户授权 +资料参考地址:https://support.huaweicloud.com/productdesc-obs/zh-cn_topic_0045829060.html diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/BaseObs.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/BaseObs.php new file mode 100644 index 0000000..91cfcb4 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/BaseObs.php @@ -0,0 +1,40 @@ +obsClient = InitObs::getObsClient(); + if($dir) { + $this->dir = (false !== strpos($dir, '/'))?$dir:$dir.'/'; + } + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/BucketManage.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/BucketManage.php new file mode 100644 index 0000000..5c9dc5f --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/BucketManage.php @@ -0,0 +1,288 @@ +obsClient->listBuckets([ + 'QueryLocation' => true + ]); + $data = []; + $data['RequestId'] = $resp['RequestId']; + $data['OwnerID'] = $resp['Owner']['ID']; + $data['OwnerName'] = $resp ['Owner']['DisplayName']; + + $tmp = []; + foreach ($resp['Buckets'] as $index => $bucket){ + array_push($tmp, [ + 'Name'=> $bucket['Name'], + 'CreationDate'=> $bucket['CreationDate'], + 'Location'=> $bucket['Location'], + ]); + } + $data['Buckets'] = $tmp; + return $data; + } + + /** + * desc:创建桶 + * 桶的名字是全局唯一的,所以您需要确保不与已有的桶名称重复。 + * 同一用户在同一区域多次创建同名桶不会报错,创建的桶属性以第一次请求为准。 + * 本示例创建的桶的访问权限默认是私有读写,存储类型默认是标准类型,区域位置是默认区域华北-北京一(cn-north-1)。 + * 须知: + * 创建桶时,如果使用的终端节点归属于默认区域华北-北京一(cn-north-1),则可以不指定区域;如果使用的终端节点归属于其他区域, + * 则必须指定区域,且指定的区域必须与终端节点归属的区域一致。当前有效的区域名称可从这里查询。 + * 您可以使用带参数创建方式,在创建桶时,指定桶的区域位置。 + * 资料参考地址:https://support.huaweicloud.com/sdk-php-devg-obs/obs_28_0301.html + * author:wh + * @return array + */ + function createBucket(){ + // 创建桶 + $resp = $this->obsClient->createBucket([ + 'Bucket' => $this->bucketName + ]); + + return [ + 'RequestId'=>$resp['RequestId'], + ]; + } + + /** + * desc:带参数创建桶 + * 说明: + * 使用ACL参数指定桶的访问权限;使用StorageClass参数指定桶的存储类型;使用LocationConstraint参数指定桶的区域位置。 + * author:wh + * @return array + */ + function createBucketByParam(){ + // 创建桶 + $resp = $this->obsClient->createBucket([ + 'Bucket' => $this->bucketName, + // 设置桶访问权限为公共读写,默认是私有读写 + 'ACL' => ObsClient::AclPublicReadWrite, + // 设置桶的存储类型为归档存储类型 + 'StorageClass' => ObsClient::StorageClassCold, + // 设置桶区域位置 + 'LocationConstraint' => 'bucketlocation' + ]); + + return [ + 'RequestId'=>$resp['RequestId'], + ]; + } + + /** + * desc:删除桶 + * author:wh + * @return array + */ + function deleteBucket(){ + // 删除桶 + $resp = $this->obsClient->deleteBucket([ + 'Bucket' => $this->bucketName + ]); + + return [ + 'RequestId'=>$resp['RequestId'], + ]; + } + + /** + * desc:获取桶存量信息 + * 桶存量信息包括桶已使用的空间大小以及桶包含的对象个数。 + * 您可以通过ObsClient->getBucketStorageInfo获取桶的存量信息。 + * author:wh + * @return array + */ + function getBucketStorageInfo(){ + $resp = $this->obsClient->getBucketStorageInfo([ + 'Bucket' => $this->bucketName + ]); + + return [ + 'RequestId'=>$resp['RequestId'], + 'Size'=>$resp['Size'], + 'ObjectNumber'=>$resp['ObjectNumber'], + ]; + } + + /** + * desc:获取桶配额 + * author:wh + * @return array + */ + function getBucketQuota(){ + $resp = $this->obsClient->getBucketQuota([ + 'Bucket' => $this->bucketName + ]); + + return [ + 'RequestId'=>$resp['RequestId'], + 'StorageQuota'=>$resp['StorageQuota'], + ]; + } + + /** + * desc:设置桶配额 + * 使用StorageQuota参数指定桶的配额大小。 + * 桶配额值必须为非负整数,单位为字节,支持的最大值为2^63 - 1。 + * author:wh + * @param float|int $StorageQuota + * @return array + */ + function setBucketQuota($StorageQuota = 1024 * 1024 * 100){ + $resp = $this->obsClient->setBucketQuota([ + 'Bucket' => $this->bucketName, + 'StorageQuota' => $StorageQuota + ]); + + return [ + 'RequestId'=>$resp['RequestId'], + ]; + } + + /** + * desc:获取桶存储类型 + * author:wh + * @return array + */ + function getBucketStoragePolicy(){ + $resp = $this->obsClient->getBucketStoragePolicy([ + 'Bucket' => $this->bucketName + ]); + + return [ + 'RequestId'=>$resp['RequestId'], + 'StorageClass'=>$resp['StorageClass'], + ]; + } + + /** + * desc:设置桶存储类型 + * OBS允许您对桶配置不同的存储类型,桶中对象的存储类型默认将与桶的存储类型保持一致。 + * 不同的存储类型可以满足客户业务对存储性能、成本的不同诉求。桶的存储类型分为三类 + * 标准存储 + 标准存储拥有低访问时延和较高的吞吐量,适用于有大量热点对象(平均一个月多次)或小对象(<1MB),且需要频繁访问数据的业务场景。 + ObsClient::StorageClassStandard + 低频访问存储 + 低频访问存储适用于不频繁访问(平均一年少于12次)但在需要时也要求能够快速访问数据的业务场景。 + ObsClient::StorageClassWarm + 归档存储 + 归档存储适用于很少访问(平均一年访问一次)数据的业务场景。 + ObsClient::StorageClassCold + * 资料参考地址:https://support.huaweicloud.com/sdk-php-devg-obs/obs_28_0311.html + * author:wh + * @param $StorageClass + * @return array + */ + function setBucketStoragePolicy($StorageClass){ + $resp = $this->obsClient->setBucketStoragePolicy([ + 'Bucket' => $this->bucketName, + 'StorageClass' => $StorageClass + ]); + + return [ + 'RequestId'=>$resp['RequestId'], + ]; + } + + /** + * desc:获取桶区域位置 + * 说明: + * 创建桶时可以指定桶的区域位置,请参见创建桶。 + * author:wh + * @return array + */ + function getBucketLocation(){ + $resp = $this->obsClient->getBucketLocation([ + 'Bucket' => $this->bucketName + ]); + + return [ + 'RequestId'=>$resp['RequestId'], + 'Location'=>$resp['Location'], + ]; + } + + /** + * desc:获取桶策略 + * author:wh + * @return array + */ + function getBucketPolicy(){ + $resp = $this->obsClient->getBucketPolicy([ + 'Bucket' => $this->bucketName + ]); + + return [ + 'RequestId'=>$resp['RequestId'], + 'Policy'=>$resp['Policy'], + ]; + } + + /** + * @deprecated 未完成 + * desc:设置桶策略[此方法待扩展] + * 除了桶访问权限外,桶的拥有者还可以通过桶策略,提供对桶和桶内对象的集中访问控制。 + * 更多关于桶策略的内容请参考桶策略 https://support.huaweicloud.com/usermanual-obs/zh-cn_topic_0045829071.html + * 说明: + * 桶策略内容的具体格式(JSON格式字符串)请参考《对象存储服务API参考》。 + * author:wh + * @return array + */ + function setBucketPolicy(){ + $resp = $this->obsClient->setBucketPolicy([ + 'Bucket' => $this->bucketName, + 'Policy' => 'your policy' + ]); + + return [ + 'RequestId'=>$resp['RequestId'], + ]; + } + + /** + * desc:删除桶策略 + * author:wh + * @return array + */ + function deleteBucketPolicy(){ + $resp = $this->obsClient->deleteBucketPolicy([ + 'Bucket' => $this->bucketName + ]); + + return [ + 'RequestId'=>$resp['RequestId'], + ]; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/Cors.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/Cors.php new file mode 100644 index 0000000..760d5fa --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/Cors.php @@ -0,0 +1,87 @@ +obsClient->setBucketCors ( [ + 'Bucket' => $this->bucketName, + 'CorsRules' => [ + [ + 'ID' => 'rule1', + // 指定允许的跨域请求方法(GET/PUT/DELETE/POST/HEAD) + 'AllowedMethod' => $this->AllowedMethod, + // 指定允许跨域请求的来源 + 'AllowedOrigin' => $this->domains, + // 控制在OPTIONS预取指令中Access-Control-Request-Headers头中指定的header是否被允许使用 + 'AllowedHeader' => [ 'x-obs-header'], + // 指定允许用户从应用程序中访问的header + 'ExposeHeader' => ['x-obs-expose-header'], + // 指定浏览器对特定资源的预取(OPTIONS)请求返回结果的缓存时间,单位为秒 + 'MaxAgeSeconds' => 60 + ] + ] + ] ); + return [ + 'RequestId'=>$resp['RequestId'], + ]; + } + + /** + * desc:查看跨域规则 + * author:wh + * @return array + */ + function getBucketCors(){ + $resp = $this->obsClient->getBucketCors ( [ + 'Bucket' => $this->bucketName, + ] ); + $data = []; + foreach ( $resp ['CorsRules'] as $index => $rule ) { + $data[]['ID'] = $rule ['ID']; + $data[]['MaxAgeSeconds'] = $rule ['MaxAgeSeconds']; + $data[]['AllowedMethod'] = $rule ['AllowedMethod']; + $data[]['AllowedOrigin'] = $rule ['AllowedOrigin']; + $data[]['AllowedHeader'] = $rule ['AllowedHeader']; + $data[]['ExposeHeader'] = $rule ['ExposeHeader']; + } + return $data; + } + + /** + * desc:删除跨域规则 + * author:wh + * @return array + */ + function deleteBucketCors(){ + $resp = $this->obsClient->deleteBucketCors ( [ + 'Bucket' => $this->bucketName, + ] ); + return [ + 'RequestId'=>$resp['RequestId'], + ]; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/Download.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/Download.php new file mode 100644 index 0000000..ba7349a --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/Download.php @@ -0,0 +1,79 @@ +obsClient -> getObject([ + 'Bucket' => $this->bucketName, + 'Key' => $objectname + ]); + $str = ''; + // 获取对象内容 + while(!$resp['Body'] -> eof()){ + $str .= $resp['Body'] -> read(65536); + } + return [ + 'RequestId'=>$resp ['RequestId'], + 'text'=>$str + ]; + } + + /** + * desc:流式下载(向浏览器输出流直接下载文件) + * + * 使用SaveAsStream参数指定使用流式下载。 + * 返回结果中的Body是一个可读的GuzzleHttp\Psr7\StreamInterface对象,可将对象的内容读取到本地文件或者内存中。 + * author:wh + * @param string $objectname + * @param string $filename + */ + function stream(string $objectname, string $filename){ + $resp = $this->obsClient -> getObject([ + 'Bucket' => $this->bucketName, + 'Key' => $objectname, + 'SaveAsStream' => true + ]); + header("Content-Disposition:attachment; filename={$filename}"); + while(!$resp['Body'] -> eof()){ + echo $resp['Body'] -> read(65536);//开始下载 + } + } + + /** + * desc:文件下载(将文件从obs下载到当前应用所在服务器) + * 说明: + * 使用SaveAsFile参数指定文件下载的路径。 + * author:wh + * @param string $objectname + * @param $localfile "将文件从obs下载到当前应用所在服务器" + * @return array + */ + function file(string $objectname,string $localfile){ + $resp = $this->obsClient -> getObject([ + 'Bucket' => $this->bucketName, + 'Key' => $objectname, + 'SaveAsFile' => $localfile, + ]); + + return [ + 'RequestId'=>$resp ['RequestId'], + ]; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/ObjectManage.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/ObjectManage.php new file mode 100644 index 0000000..c8760c5 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/ObjectManage.php @@ -0,0 +1,231 @@ +obsClient->listObjects ( [ + 'Bucket' => $this->bucketName + ] ); + + $data = []; + $data['RequestId'] = $resp ['RequestId']; + $tmp = []; + foreach ( $resp ['Contents'] as $index => $content ) { + array_push($tmp, [ + 'Key'=>$content ['Key'], + 'LastModified'=> $content ['LastModified'], + 'ETag'=> $content ['ETag'], + 'Size'=> $content ['Size'], + 'OwnerID'=> $content ['Owner']['ID'], + 'OwnerName'=> $content ['Owner']['DisplayName'], + 'StorageClass'=> $content ['StorageClass'], + ]); + } + $data['Contents'] = $tmp; + return $data; + } + + /** + * desc:指定数目列举 + * author:wh + * @param int $rowsLimit + * @return array + */ + function listObjectsLimit (int $rowsLimit=100){ + $resp = $this->obsClient->listObjects ( [ + 'Bucket' => $this->bucketName, + // 列举100个对象 + 'MaxKeys' => $rowsLimit + ] ); + + $data['RequestId'] = $resp ['RequestId']; + $tmp = []; + foreach ( $resp ['Contents'] as $index => $content ) { + array_push($tmp, [ + 'Key'=>$content ['Key'], + 'LastModified'=> $content ['LastModified'], + 'ETag'=> $content ['ETag'], + 'Size'=> $content ['Size'], + 'OwnerID'=> $content ['Owner']['ID'], + 'OwnerName'=> $content ['Owner']['DisplayName'], + 'StorageClass'=> $content ['StorageClass'], + ]); + } + $data['Contents'] = $tmp; + return $data; + } + + /** + * desc:指定起始位置列举 + * 模糊匹配 + * author:wh + * @param string $Marker 对象名 + * @param int $MaxKeys 返回数量 + * @return array + */ + function listObjectsFromStart(string $Marker, int $MaxKeys=100){ + $resp = $this->obsClient->listObjects ( [ + 'Bucket' => $this->bucketName, + // 设置列举对象名字典序在"test"之后的100个对象 + 'MaxKeys' => $MaxKeys, + 'Marker' => $Marker + ] ); + + $data = []; + $data['RequestId'] = $resp ['RequestId']; + $tmp = []; + foreach ( $resp ['Contents'] as $index => $content ) { + array_push($tmp, [ + 'Key'=>$content ['Key'], + 'LastModified'=> $content ['LastModified'], + 'ETag'=> $content ['ETag'], + 'Size'=> $content ['Size'], + 'OwnerID'=> $content ['Owner']['ID'], + 'OwnerName'=> $content ['Owner']['DisplayName'], + 'StorageClass'=> $content ['StorageClass'], + ]); + } + $data['Contents'] = $tmp; + return $data; + } + + /** + * desc:列举文件夹中的所有对象 + * author:wh + */ + function listObjectsForDirAll(string $dir, $MaxKeys=1000){ + $marker = null; + $index = 1; + $data = []; + do { + $resp = $this->obsClient->listObjects ( [ + 'Bucket' => $this->bucketName, + 'MaxKeys' => $MaxKeys, + // 设置文件夹对象名"dir/"为前缀 + 'Prefix' => $dir, + 'Marker' => $marker + ] ); + + $items = []; + foreach ( $resp ['Contents'] as $content ) { + $item = [ + 'Key'=>$content ['Key'], + 'LastModified'=>$content ['LastModified'], + 'ETag'=>$content ['ETag'], + 'Size'=>$content ['Size'], + 'KeyID'=>$content ['Owner'] ['ID'], + 'OwnerName'=> $content ['Owner']['DisplayName'], + 'StorageClass'=>$content ['StorageClass'], + ]; + $items[] = $item; + } + $marker = $resp['NextMarker']; + $data[] = $items; + }while($resp['IsTruncated']); + return $data; + } + + /** + * desc:获取对象属性 + * 包括对象长度,对象MIME类型,对象自定义元数据等信息。 + * author:wh + * @param $objectname + * @return array + */ + function getObjectMetadata(string $objectname){ + $resp = $this->obsClient -> getObjectMetadata([ + 'Bucket' => $this->bucketName, + 'Key' => $objectname + ]); + + return [ + 'RequestId'=>$resp['RequestId'], + 'ContentType'=>$resp['ContentType'], + 'ContentLength'=>$resp['ContentLength'], + 'Metadata'=>$resp['Metadata'], + ]; + } + + /** + * desc:删除单个对象 + * 说明: + * 请您谨慎使用删除操作,若对象所在的桶未开启多版本控制功能,该对象一旦删除将无法恢复。 + * author:wh + * @param string $objectname + * @return array + */ + function deleteObject(string $objectname){ + + $resp = $this->obsClient->deleteObject ( [ + 'Bucket' => $this->bucketName, + 'Key' => $objectname + ] ); + return [ + 'RequestId'=>$resp['RequestId'], + ]; + } + + /** + * desc:获取对象访问权限 + * 对象访问权限与桶访问权限类似,也可支持预定义访问策略(参见桶访问权限)或直接设置。 + * 对象访问权限(ACL)可以通过三种方式设置: + * 上传对象时指定预定义访问策略。 + * 调用ObsClient->setObjectAcl指定预定义访问策略。 + * 调用ObsClient->setObjectAcl直接设置。 + * 资料参考:https://support.huaweicloud.com/sdk-php-devg-obs/obs_28_0602.html + * author:wh + * @param string $objectname + * @return array + */ + function getObjectAcl(string $objectname){ + $resp = $this->obsClient->getObjectAcl ( [ + 'Bucket' => $this->bucketName, + 'Key' => $objectname + ] ); + + $data = [ + 'RequestId'=>$resp['RequestId'], + 'OwnerID'=>$resp ['Owner'] ['ID'], + ]; + $tmp = []; + foreach ( $resp ['Grants'] as $index => $grant ) { + $tmp[] = [ + 'GranteeID'=>$grant ['Grantee'] ['ID'], + 'GranteeURI'=>$grant ['Grantee'] ['URI'], + 'Permission'=>$grant['Permission'], + ]; + } + $data['Grants'] = $tmp; + return $data; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/Upload.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/Upload.php new file mode 100644 index 0000000..05a6d71 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/Upload.php @@ -0,0 +1,125 @@ +putObject直接上传字符串到OBS. + * 说明: + * 使用Body参数指定待上传的字符串。 + * 资料参考:https://support.huaweicloud.com/sdk-php-devg-obs/obs_28_0403.html + * author:wh + * @param string $objectname 桶内对象名称 + * @param string $text 要上传的文本 + * @return array + */ + function putObjectText(string $objectname, string $text){ + $resp = $this->obsClient->putObject([ + 'Bucket' => $this->bucketName, + 'Key' => $this->dir.$objectname, + 'Body' => $text //'Hello OBS' + ]); + + return [ + 'RequestId'=>$resp['RequestId'] + ]; + } + + /** + * desc:上传网络流 + * 流式上传使用resource或GuzzleHttp\Psr7\StreamInterface作为对象的数据源 + * 资料参考:https://support.huaweicloud.com/sdk-php-devg-obs/obs_28_0403.html + * author:wh + * @param string $objectname 桶内对象名称 + * @param string $url + * @return array + */ + function putObjectNetStream(string $objectname, string $url){ + $resp = $this->obsClient->putObject([ + 'Bucket' => $this->bucketName, + 'Key' => $this->dir.$objectname, + // 创建网络流 eg: $url='http://www.a.com' + 'Body' => fopen($url,'r') + ]); + + return [ + 'RequestId'=>$resp['RequestId'] + ]; + } + + /** + * desc:上传文件流 + * 使用Body参数指定待上传的流数据时,其值必须是一个resource对象或GuzzleHttp\Psr7\StreamInterface对象。 + * 大文件上传建议使用分段上传。 + * 资料参考:https://support.huaweicloud.com/sdk-php-devg-obs/obs_28_0403.html + * 分段上传资料参考:https://support.huaweicloud.com/sdk-php-devg-obs/obs_28_0407.html + * author:wh + * @param string $objectname 桶内对象名称 + * @param string $localfile 本地文件,必须是一个resource对象或GuzzleHttp\Psr7\StreamInterface对象 + * @return array + */ + function putObjectFileStream(string $objectname, string $localfile){ + $resp = $this->obsClient->putObject([ + 'Bucket' => $this->bucketName, + 'Key' => $this->dir.$objectname, + 'Body' => fopen($localfile, 'r') + ]); + + return [ + 'RequestId'=>$resp['RequestId'] + ]; + } + + /** + * desc:文件上传 + * 使用SourceFile参数指定待上传的文件路径。 + * SourceFile参数和Body参数不能同时使用。 + * 上传内容大小不能超过5GB。 + * 资料参考:https://support.huaweicloud.com/sdk-php-devg-obs/obs_28_0404.html + * author:wh + * @param string $objectname 文件名称 + * @param string $localfile + * @return array + */ + function putObjectLocalFile(string $objectname, string $localfile){ + $resp = $this->obsClient->putObject([ + 'Bucket' => $this->bucketName, + 'Key' => $this->dir.$objectname, + 'SourceFile' => $localfile // localfile为待上传的本地文件路径,需要指定到具体的文件名 + ]); + + return [ + 'RequestId'=>$resp['RequestId'] + ]; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/VersionControl.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/VersionControl.php new file mode 100644 index 0000000..621f272 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/oss/huawei/obs/service/VersionControl.php @@ -0,0 +1,80 @@ +listObjects)时默认列出最新对象列表,可以指定列出桶内所有版本对象列表(ObsClient->listVersions)。 + * 除了删除标记外,每个版本的对象存储均需计费。 + * author:wh + * @return array + */ + function setBucketVersionEnabled(){ + // 启用桶多版本状态 + $resp = $this->obsClient->setBucketVersioning([ + 'Bucket' => $this->bucketName, + 'Status' => 'Enabled' + ]); + + return [ + 'RequestId'=>$resp['RequestId'], + ]; + } + /** + * desc:停用桶多版本状态 + * + * 旧的版本数据继续保留。 + * 上传对象时创建对象的版本号为null,上传同名的对象将覆盖原有同名的版本号为null的对象。 + * 可以指定版本号下载对象,不指定版本号默认下载最新对象。 + * 删除对象时可以指定版本号删除,不带版本号删除对象将产生一个版本号为null的删除标记,并删除版本号为null的对象。 + * 除了删除标记外,每个版本的对象存储均需计费。 + * author:wh + * @return array + */ + function setBucketVersionSuspended(){ + // 停用桶多版本状态 + $resp = $this->obsClient->setBucketVersioning([ + 'Bucket' => $this->bucketName, + 'Status' => 'Suspended' + ]); + + return [ + 'RequestId'=>$resp['RequestId'], + ]; + } + + /** + * desc:查看桶多版本状态 + * author:wh + * @return array + */ + function getBucketVersion(){ + $resp = $this->obsClient->getBucketVersioning([ + 'Bucket' => $this->bucketName, + ]); + + return [ + 'RequestId'=>$resp['RequestId'], + 'Status'=>$resp['Status'], + ]; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/pay/README.md b/superadmin/vendor/wanghua/general-utility-tools-php/src/pay/README.md new file mode 100644 index 0000000..43128d1 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/pay/README.md @@ -0,0 +1,41 @@ +#### 支付类库 +##### 收单宝 +##### 通联 - 收银宝 +##### ...... + + + +##### 1、收单宝 +示例: +``` + $appKey = '123134sdf6sd4s6'; + $shop_no = 's46dfs64f'; + $accountNo = "sd1f2s4g3s4";//付款账户编号 + $run_type = 'sandbox';//运行环境 sandbox 沙箱环境(开发、测试时用), product 正式环境 (上线后用) + + $pay = new ReceiveOrderPay(); + $pay->appKey = $appKey; + + $pay->shop_no = $shop_no; + $pay->accountNo = $accountNo; + $pay->run_type = $run_type; + + $pay->setNonceStr(); + $pay->setPayType('alipay');//wechat_bank 表示微信付款 到银行卡, wechat 表示付款到微信钱包, wechat_readpack 表示付款到微信红包, alipay 表示支付宝付款到支付宝账户 + + $pay->realUserName = '张三'; + $pay->bankNo = '18290416099'; + $pay->setRequestNo();//不使用默认编号可以自写代替 + $pay->amount = 0.01;//元 + + $pay->setSign();//设置签名 + $pay->setData(); + + //记录日志到runtime/log/shoudan_pay_log中 + //Tools::log_to_write_txt(['入参:',$pay->post_params], 'shoudan_pay_log'); + $res = $pay->pay(); + //Tools::log_to_write_txt(['出参:',$res], 'shoudan_pay_log'); + + + dump($res); +``` \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/pay/shoudanpay/BasePay.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/pay/shoudanpay/BasePay.php new file mode 100644 index 0000000..8b4855d --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/pay/shoudanpay/BasePay.php @@ -0,0 +1,78 @@ +nonce_str = Tools::rand_str(16); + } + + /** + * desc:设置支付类型 + * author:wh + * @param $payType + * @throws \Exception + */ + function setPayType($payType){ + if(!in_array($payType,[ + 'wechat_bank', + 'wechat', + 'wechat_readpack', + 'alipay', + ])) throw new \Exception('支付类型错误'); + //wechat_bank 表示微信付款 到银行卡, wechat 表示付 款到微信钱包, wechat_readpack 表示付款到微信红包, alipay 表示支付宝付款到支付宝账户 + $this->payType = $payType; + } + + + /** + * desc:设置订单编号 + * author:wh + */ + function setRequestNo(){ + $this->requestNo = 'sdb'.date('YmdHis').Tools::rand_str(6); + } + + /** + * desc:设置签名 + * author:wh + */ + function setSign(){ + $this->sign = md5($this->appKey.$this->run_type.$this->shop_no.$this->nonce_str); + } + + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/pay/shoudanpay/ReceiveOrderPay.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/pay/shoudanpay/ReceiveOrderPay.php new file mode 100644 index 0000000..5145baa --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/pay/shoudanpay/ReceiveOrderPay.php @@ -0,0 +1,52 @@ +post_params['shop_no'] = $this->shop_no; + $this->post_params['accountNo'] = $this->accountNo; + $this->post_params['run_type'] = $this->run_type; + + $this->post_params['nonce_str'] = $this->nonce_str; + $this->post_params['payType'] = $this->payType; + $this->post_params['realUserName'] = $this->realUserName; + $this->post_params['bankNo'] = $this->bankNo; + $this->post_params['requestNo'] = $this->requestNo; + $this->post_params['amount'] = $this->amount; + $this->post_params['sign'] = $this->sign; + + if($this->bankCode) $this->post_params['bankCode'] = $this->bankCode; + } + /** + * desc:调起支付 + * author:wh + */ + function pay(){ + set_time_limit($this->request_time_out);//脚本超时时间 + + + return Tools::curl_post($this->url, json_encode($this->post_params)); + } + + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/pay/shouyinbaopay/ShouYinBaoUnifyPay.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/pay/shouyinbaopay/ShouYinBaoUnifyPay.php new file mode 100644 index 0000000..2e82a7b --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/pay/shouyinbaopay/ShouYinBaoUnifyPay.php @@ -0,0 +1,258 @@ +price; + $params["reqsn"] = $this->order_id; + $params["paytype"] = 'W02'; + $params["body"] = $this->body; + $params["remark"] = $this->remark; + //$params["validtime"] = 12; + $params["acct"] = $this->openid;//jsapi时,填openid + $params["notify_url"] = $this->notify_url; + //$params["limit_pay"] = ''; + $params["sub_appid"] = $weixin_appid; + //$params["subbranch"] = ''; + //$params["cusip"] = ''; + //$params["idno"] = ''; + //$params["truename"] = ''; + //$params["fqnum"] = ''; + $params["randomstr"] = Tools::rand_str(); + $params["signtype"] ='RSA'; + //支付完成跳转 必须为https协议地址,且不允许带参数 + $params["front_url"] = $this->front_url;// + + $params["sign"] = urlencode($this->Sign($params,$private_key));//签名 + $paramsStr = $this->ToUrlParams($params); + + $url = $pay_domain . "/apiweb/unitorder/pay"; + $rsp = $this->request($url, $paramsStr); + + if($rsp['code'] != 200){ + return Tools::set_res(500,'支付请求错误.'.$rsp['msg']); + } + + $rspArray = json_decode($rsp['data'], true); + if(strtolower($rspArray['retcode']) != 'success'){ + return Tools::set_res(500,'支付失败.'); + } + + if(!$this->validSign($rspArray)){ + return Tools::set_res(500,'验签失败.'); + } + + //验证交易状态 + if($rspArray['trxstatus'] !== '0000'){ + return Tools::set_res(500,'交易未成功.'); + } + + return Tools::set_res(200, 'ok',$rspArray); + } + + + /** + * desc:jsapi支付对应的通知 + * + * {"acct":"ojEmk5lAXuEDVBPTqKM3N7jLnkmc","accttype":"99","appid":"00224060","chnlid":"205299480", + "chnltrxid":"4200001173202111091438864430","cmid":"472485785","cusid":"553361048165HBG","cusorderid":"wx1636446415073835792", + "fee":"0","initamt":"1","outtrxid":"wx1636446415073835792","paytime":"20211109162701", + "sign":"AxjxzsvPo9nGVLHTZ9DtxsZcP9H+n8sdRFTlLjEa1929e/dyFmxd2ee88QuCgUBsUFLNsoNy++n41MrBbiK6Gzi3kLqPGU3qxRe7ZJLMKKLgrSgU + Sc0w/mSq0HvaaF6W81WH2XaOzHACCQ4qd5bn0gMf97GwlByNgblivb0dJnA=", + "signtype":"RSA","termauthno":"OTHERS","termrefnum":"4200001173202111091438864430","termtraceno":"0", + "trxamt":"1","trxcode":"VSP501","trxdate":"20211109","trxid":"211109116606825752", + "trxreserved":"参赛券购买","trxstatus":"0000"} + * + * author:wh + * @return array + */ + function jsapiNotify(){ + $params = array(); + //动态遍历获取所有收到的参数,此步非常关键,因为收银宝以后可能会加字段,动态获取可以兼容由于收银宝加字段而引起的签名异常 + foreach($_POST as $key=>$val) { + $params[$key] = $val; + } + if(count($params)<1){//如果参数为空,则不进行处理 + return Tools::set_res(500, '未获取到参数',$params); + } + if(!$this->ValidSign($params)){//验签成功 + //此处进行业务逻辑处理 + return Tools::set_res(500, '签名校验失败',$params); + } + + $tradeStatus = $params['trxstatus']; + if(strtolower($tradeStatus) != '0000'){ + return Tools::set_res(500, '交易未成功',$params); + } + + return Tools::set_res(200, '交易成功',$params); + } + + + + /** + * RSA签名 + * + * 将参数数组签名 + */ + protected function Sign(array $array,string $private_key){ + ksort($array); + $bufSignSrc = $this->ToUrlParams($array); + $private_key = chunk_split($private_key , 64, "\n"); + $key = "-----BEGIN RSA PRIVATE KEY-----\n".wordwrap($private_key)."-----END RSA PRIVATE KEY-----"; + + if(!openssl_sign($bufSignSrc, $signature, $key )){ + return ''; + } + //加密后的内容通常含有特殊字符,需要编码转换下,在网络间通过url传输时要注意base64编码是否是url安全的 + $sign = base64_encode($signature); + + return $sign; + + } + + /** + * desc: + * author:wh + * @param array $array + * @return string + */ + protected function ToUrlParams(array $array) + { + $buff = ""; + foreach ($array as $k => $v) + { + if($v != "" && !is_array($v)){ + $buff .= $k . "=" . $v . "&"; + } + } + + $buff = trim($buff, "&"); + return $buff; + } + + /** + * 校验签名 + * @param array 参数 + * @param unknown_type appkey + */ + protected function ValidSign(array $array){ + $sign =$array['sign']; + unset($array['sign']); + ksort($array); + $bufSignSrc = $this->ToUrlParams($array); + //通联固定公钥 + $public_key='MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCm9OV6zH5DYH/ZnAVYHscEELdCNfNTHGuBv1nYYEY9FrOzE0/4kLl9f7Y9dkWHlc2ocDwbrFSm0Vqz0q2rJPxXUYBCQl5yW3jzuKSXif7q1yOwkFVtJXvuhf5WRy+1X5FOFoMvS7538No0RpnLzmNi3ktmiqmhpcY/1pmt20FHQQIDAQAB'; + $public_key = chunk_split($public_key , 64, "\n"); + $key = "-----BEGIN PUBLIC KEY-----\n$public_key-----END PUBLIC KEY-----\n"; + $result= openssl_verify($bufSignSrc,base64_decode($sign), $key ); + return $result; + } + + /** + * desc:发送请求操作仅供参考,不为最佳实践 + * author:wh + * @param $url + * @param $params + * @return array + */ + protected function request($url,$params){ + $curl = curl_init(); + $this_header = array("content-type: application/x-www-form-urlencoded;charset=UTF-8"); + curl_setopt($curl,CURLOPT_HTTPHEADER,$this_header); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; MSIE 5.01; Windows NT 5.0)'); + curl_setopt($curl, CURLOPT_TIMEOUT, 3); + + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $params); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);//如果不加验证,就设false,商户自行处理 + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE); + + $output = curl_exec($curl); + + // 显示错误信息 + if (curl_error($curl)) { + //返回错误码 + return ['code' => curl_errno($curl), 'msg' => curl_error($curl)]; + } else { + //关闭句柄 + curl_close($curl); + // 返回的内容 + return ['code' => 200, 'msg' => 'ok', 'data' => $output]; + } + } + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/phpmailer/AliYunEmail.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/phpmailer/AliYunEmail.php new file mode 100644 index 0000000..db96258 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/phpmailer/AliYunEmail.php @@ -0,0 +1,112 @@ +config = $config; + if(empty($this->config['AccessKeyId']) || empty($this->config['AccessKeySecret'])){ + throw new Exception('请配置accessKeyId和accessKeySecret'); + } + if(empty($this->config['accountName'])){ + throw new Exception('请配置发信地址accountName'); + } + if(empty($this->config['fromAlias'])){ + throw new Exception('请配置发信人昵称fromAlias'); + } + } + + + /** + * desc:发送邮件 + * author:wh + * @param string $toAddress 收件人 + * @param string $subject 邮件标题 + * @param string $content 邮件内容(支持Html) + */ + public function sendMail(string $toAddress,string $subject,string $content=''){ + return Mmodel::catchJson(function () use ($toAddress,$subject,$content){ + $client = self::createClient(); + $conf = [ + "accountName" => $this->config['accountName'],//"libin@mail.excn.vip", + "addressType" => isset($this->config['addressType'])?$this->config['addressType']:1,//默认批量发送 + "replyToAddress" => isset($this->config['replyToAddress'])?$this->config['replyToAddress']:false,//默认不接收回复 + "fromAlias" => $this->config['fromAlias'],//"test发信人昵称" + "tagName" => "test_tag", + "toAddress" => $toAddress,//"382379437@qq.com",//多个逗号隔开 + "subject" => $subject,//"test主题", + "htmlBody" => $content,//"test HtmlBody html 邮件正文", + "textBody" => "邮件正文邮件正文邮件正文邮件正文test TextBody txt邮件正文", + ]; + //dump($conf);die; + $singleSendMailRequest = new SingleSendMailRequest($conf); + $runtime = new RuntimeOptions([]); + // 复制代码运行请自行打印 API 的返回值 + $res = $client->singleSendMailWithOptions($singleSendMailRequest, $runtime); + //有可能有延迟,最高的有20分钟延迟 + if($res->statusCode == 200){ + return Tools::set_ok(); + } + return Tools::set_fail('邮件发送失败.'); + }); + } + + /** + * 使用AK&SK初始化账号Client + * @return Dm Client + */ + private function createClient(){ + // 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。 + // 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/311677.html。 + $conf = [ + // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。 + "accessKeyId" => $this->config['AccessKeyId'],//config('aliyun_oss_config.AccessKeyId'), + // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。 + "accessKeySecret" => $this->config['AccessKeySecret'],//config('aliyun_oss_config.AccessKeySecret'), + //关闭证书验证 + "securityToken" => isset($this->config['securityToken'])?$this->config['securityToken']:"false",//默认 + // 访问的 debug 模式 + "debug" => isset($this->config['debug'])?$this->config['debug']:true,//默认为调试模式 + // 默认使用 HTTP 协议 + "protocol" => isset($this->config['protocol'])?$this->config['protocol']:"HTTP"//默认http + ]; + //dump($conf);die; + $config = new Config($conf); + // Endpoint 请参考 https://api.aliyun.com/product/Dm + $config->endpoint = "dm.aliyuncs.com";//默认 + return new Dm($config); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/phpmailer/Exception.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/phpmailer/Exception.php new file mode 100644 index 0000000..a55f95d --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/phpmailer/Exception.php @@ -0,0 +1,39 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2017 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace wanghua\general_utility_tools_php\phpmailer; + +/** + * PHPMailer exception handler. + * + * @author Marcus Bointon + */ +class Exception extends \Exception +{ + /** + * Prettify error message output. + * + * @return string + */ + public function errorMessage() + { + return '' . htmlspecialchars($this->getMessage()) . "
    \n"; + } +} diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/phpmailer/Mail.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/phpmailer/Mail.php new file mode 100644 index 0000000..a420dd8 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/phpmailer/Mail.php @@ -0,0 +1,220 @@ +nickname = $nickname;//发送方昵称 + $this->host = $host;//发送方邮件服务器地址 + $this->username = $username;//发送方邮件服务器账号 + $this->pwd = $pwd;//发送方邮件服务器密码 + $this->sender_email = $sender_email;//发送方邮箱 + $this->port = $port;//发送方邮件服务器端口 有465, 25 2465等 + $this->protocol = $protocol;//发送方邮件服务器要求的协议 有ssl、tls + } + + /** + * @deprecated [老项目-发送国内邮箱] + * + * 注意:发送人和发送人邮箱一致才能用此方法,否则发送失败 + * @Author + * @Date 2019-11-22 + * @param [array|string] $receiver [收件人邮箱] + * @param [array|string] $receiver_nick [收件人名称] + * @param [string] $subject [邮件主题] + * @param [string] $body [邮件正文] + * @return [type] [description] + */ + function send($receiver,$receiver_nick,$subject,$body){ + if(empty($receiver)){ + return ['code'=>500, 'msg'=>'收件人邮箱为空']; + } + + $email_nickname = $this->nickname;//平台邮箱名称 + + $mail = new PHPMailer(); + $mail->isSMTP(); + if($this->debug){ + $mail->SMTPDebug = $this->debug; // 调试模式 + } + $mail->Host = $this->host; // 邮件服务器地址 + $mail->SMTPAuth = true; // 允许 SMTP 认证 + $mail->CharSet = "UTF-8"; // 编码格式 + $mail->Username = $this->username; // SMTP 用户名 即邮箱的用户名 + $mail->Password = $this->pwd; // SMTP 密码 + $mail->SMTPSecure = $this->protocol; // TLS或者ssl协议 + $mail->Port = $this->port; // 服务器端口 25 或者465 具体要看邮箱服务器支持 + $mail->setFrom($this->username, $email_nickname); + //dump($mail);die; + if(is_array($receiver)){ + //多个收件人 + foreach ($receiver as $key => $val){ + $mail->addAddress($val, $receiver_nick[$key]); + } + }else{ + //单个收件人 + $mail->addAddress($receiver, $receiver_nick); + } + + $mail->isHTML(true); //是否以HTML文档格式发送 + $mail->Subject = $subject; + $mail->Body = $body; //发送带有html标签文本 + // $mail->AltBody = ''; //如果邮件客户端不支持HTML则显示此内容 + // $mail->addCC(); // 添加抄送 + // $mail->addBCC(); // 添加密送 + // $mail->addAttachment("2e3f19fd-8e3f-405b-9854-41dd19ddebf8.jpg"); // 添加附件 + + if(!$mail->send()){ + return ['code'=>500, 'msg'=>$mail->ErrorInfo]; + } + return ['code'=>200, 'msg'=>'Email has been sent.']; + } + + + /** + * [通用发送国内邮箱] + * + * 注意:发送人和发送人邮箱一致才能用此方法,否则发送失败 + * @Author + * @Date 2019-11-22 + * @param [array|string] $receiver [收件人邮箱] + * @param [array|string] $receiver_nick [收件人名称] + * @param [string] $subject [邮件主题] + * @param [string] $body [邮件正文] + * @return [type] [description] + */ + function sendInner($receiver,$receiver_nick,$subject,$body){ + if(empty($receiver)){ + return ['code'=>500, 'msg'=>'收件人邮箱为空']; + } + + $email_nickname = $this->nickname;//平台邮箱名称 + + $mail = new PHPMailer(); + $mail->isSMTP(); + if($this->debug){ + $mail->SMTPDebug = $this->debug; // 调试模式 + } + $mail->Host = $this->host; // 邮件服务器地址 + $mail->SMTPAuth = true; // 允许 SMTP 认证 + $mail->CharSet = "UTF-8"; // 编码格式 + $mail->Username = $this->username; // SMTP 用户名 即邮箱的用户名 + $mail->Password = $this->pwd; // SMTP 密码 + $mail->SMTPSecure = $this->protocol; // TLS或者ssl协议 + $mail->Port = $this->port; // 服务器端口 25 或者465 具体要看邮箱服务器支持 + $mail->setFrom($this->sender_email, $email_nickname); + if(is_array($receiver)){ + //多个收件人 + foreach ($receiver as $key => $val){ + $mail->addAddress($val, $receiver_nick[$key]); + } + }else{ + //单个收件人 + $mail->addAddress($receiver, $receiver_nick); + } + + $mail->isHTML(true); //是否以HTML文档格式发送 + $mail->Subject = $subject; + $mail->Body = $body; //发送带有html标签文本 + // $mail->AltBody = ''; //如果邮件客户端不支持HTML则显示此内容 + // $mail->addCC(); // 添加抄送 + // $mail->addBCC(); // 添加密送 + // $mail->addAttachment("2e3f19fd-8e3f-405b-9854-41dd19ddebf8.jpg"); // 添加附件 + + if(!$mail->send()){ + return ['code'=>500, 'msg'=>$mail->ErrorInfo]; + } + return ['code'=>200, 'msg'=>'Email has been sent.']; + } + + /** + * [发送【国外】aws亚马逊邮件] + * + * 邮件服务器用亚马逊服务器,接收者邮箱不限制 + * + * 注意: + * 发送方要在亚马逊控制台,添加发送者邮箱,才可以对外发邮件 + * 端口用25,25测试通过 + * + * @Author + * @Date 2019-11-22 + * @param [array|string] $sender [发件人邮箱] + * @param [array|string] $receiver [收件人邮箱] + * @param [array|string] $receiver_nick [收件人名称] + * @param [string] $subject [邮件主题] + * @param [string] $body [邮件正文] + * @return [type] [description] + */ + function sendAWS($receiver,$receiver_nick,$subject,$body){ + if(empty($receiver)){ + return ['code'=>500, 'msg'=>'收件人邮箱为空']; + } + if(empty($this->sender_email)){ + return ['code'=>500, 'msg'=>'发件人邮箱为空']; + } + + $email_nickname = $this->nickname;//平台邮箱名称 + + $mail = new PHPMailer(); + $mail->isSMTP(); + if($this->debug){ + $mail->SMTPDebug = $this->debug; // 调试模式 + } + //$mail->setFrom($this->sender_email, $email_nickname); + + $mail->Host = $this->host; // 邮件服务器地址 + $mail->SMTPAuth = true; // 允许 SMTP 认证 + $mail->CharSet = "UTF-8"; // 编码格式 + $mail->Username = $this->username; // SMTP 用户名 即邮箱的用户名 + $mail->Password = $this->pwd; // SMTP 密码 + $mail->SMTPSecure = $this->protocol; // TLS或者ssl协议 + $mail->Port = $this->port; // 服务器端口 腾讯465,具体要看邮箱服务器支持 亚马逊25 或 2465 + $mail->setFrom($this->sender_email, $email_nickname); + + if(is_array($receiver)){ + //多个收件人 + foreach ($receiver as $key => $val){ + $mail->addAddress($val, $receiver_nick[$key]); + } + }else{ + //单个收件人 + $mail->addAddress($receiver, $receiver_nick); + } + + $mail->isHTML(true); //是否以HTML文档格式发送 + $mail->Subject = $subject; + $mail->Body = $body; //发送带有html标签文本 + // $mail->AltBody = ''; //如果邮件客户端不支持HTML则显示此内容 + // $mail->addCC(); // 添加抄送 + // $mail->addBCC(); // 添加密送 + // $mail->addAttachment("2e3f19fd-8e3f-405b-9854-41dd19ddebf8.jpg"); // 添加附件 + + if(!$mail->send()){ + return ['code'=>500, 'msg'=>$mail->ErrorInfo]; + } + return ['code'=>200, 'msg'=>'Email has been sent.']; + } + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/phpmailer/PHPMailer.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/phpmailer/PHPMailer.php new file mode 100644 index 0000000..90baa6f --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/phpmailer/PHPMailer.php @@ -0,0 +1,4770 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2019 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace wanghua\general_utility_tools_php\phpmailer; + +/** + * PHPMailer - PHP email creation and transport class. + * + * @author Marcus Bointon (Synchro/coolbru) + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + */ +class PHPMailer +{ + const CHARSET_ASCII = 'us-ascii'; + const CHARSET_ISO88591 = 'iso-8859-1'; + const CHARSET_UTF8 = 'utf-8'; + + const CONTENT_TYPE_PLAINTEXT = 'text/plain'; + const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar'; + const CONTENT_TYPE_TEXT_HTML = 'text/html'; + const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative'; + const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed'; + const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related'; + + const ENCODING_7BIT = '7bit'; + const ENCODING_8BIT = '8bit'; + const ENCODING_BASE64 = 'base64'; + const ENCODING_BINARY = 'binary'; + const ENCODING_QUOTED_PRINTABLE = 'quoted-printable'; + + const ENCRYPTION_STARTTLS = 'tls'; + const ENCRYPTION_SMTPS = 'ssl'; + + const ICAL_METHOD_REQUEST = 'REQUEST'; + const ICAL_METHOD_PUBLISH = 'PUBLISH'; + const ICAL_METHOD_REPLY = 'REPLY'; + const ICAL_METHOD_ADD = 'ADD'; + const ICAL_METHOD_CANCEL = 'CANCEL'; + const ICAL_METHOD_REFRESH = 'REFRESH'; + const ICAL_METHOD_COUNTER = 'COUNTER'; + const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER'; + + /** + * Email priority. + * Options: null (default), 1 = High, 3 = Normal, 5 = low. + * When null, the header is not set at all. + * + * @var int + */ + public $Priority; + + /** + * The character set of the message. + * + * @var string + */ + public $CharSet = self::CHARSET_ISO88591; + + /** + * The MIME Content-type of the message. + * + * @var string + */ + public $ContentType = self::CONTENT_TYPE_PLAINTEXT; + + /** + * The message encoding. + * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". + * + * @var string + */ + public $Encoding = self::ENCODING_8BIT; + + /** + * Holds the most recent mailer error message. + * + * @var string + */ + public $ErrorInfo = ''; + + /** + * The From email address for the message. + * + * @var string + */ + public $From = 'root@localhost'; + + /** + * The From name of the message. + * + * @var string + */ + public $FromName = 'Root User'; + + /** + * The envelope sender of the message. + * This will usually be turned into a Return-Path header by the receiver, + * and is the address that bounces will be sent to. + * If not empty, will be passed via `-f` to sendmail or as the 'MAIL FROM' value over SMTP. + * + * @var string + */ + public $Sender = ''; + + /** + * The Subject of the message. + * + * @var string + */ + public $Subject = ''; + + /** + * An HTML or plain text message body. + * If HTML then call isHTML(true). + * + * @var string + */ + public $Body = ''; + + /** + * The plain-text message body. + * This body can be read by mail clients that do not have HTML email + * capability such as mutt & Eudora. + * Clients that can read HTML will view the normal Body. + * + * @var string + */ + public $AltBody = ''; + + /** + * An iCal message part body. + * Only supported in simple alt or alt_inline message types + * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator. + * + * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ + * @see http://kigkonsult.se/iCalcreator/ + * + * @var string + */ + public $Ical = ''; + + /** + * Value-array of "method" in Contenttype header "text/calendar" + * + * @var string[] + */ + protected static $IcalMethods = [ + self::ICAL_METHOD_REQUEST, + self::ICAL_METHOD_PUBLISH, + self::ICAL_METHOD_REPLY, + self::ICAL_METHOD_ADD, + self::ICAL_METHOD_CANCEL, + self::ICAL_METHOD_REFRESH, + self::ICAL_METHOD_COUNTER, + self::ICAL_METHOD_DECLINECOUNTER, + ]; + + /** + * The complete compiled MIME message body. + * + * @var string + */ + protected $MIMEBody = ''; + + /** + * The complete compiled MIME message headers. + * + * @var string + */ + protected $MIMEHeader = ''; + + /** + * Extra headers that createHeader() doesn't fold in. + * + * @var string + */ + protected $mailHeader = ''; + + /** + * Word-wrap the message body to this number of chars. + * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. + * + * @see static::STD_LINE_LENGTH + * + * @var int + */ + public $WordWrap = 0; + + /** + * Which method to use to send mail. + * Options: "mail", "sendmail", or "smtp". + * + * @var string + */ + public $Mailer = 'mail'; + + /** + * The path to the sendmail program. + * + * @var string + */ + public $Sendmail = '/usr/sbin/sendmail'; + + /** + * Whether mail() uses a fully sendmail-compatible MTA. + * One which supports sendmail's "-oi -f" options. + * + * @var bool + */ + public $UseSendmailOptions = true; + + /** + * The email address that a reading confirmation should be sent to, also known as read receipt. + * + * @var string + */ + public $ConfirmReadingTo = ''; + + /** + * The hostname to use in the Message-ID header and as default HELO string. + * If empty, PHPMailer attempts to find one with, in order, + * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value + * 'localhost.localdomain'. + * + * @see PHPMailer::$Helo + * + * @var string + */ + public $Hostname = ''; + + /** + * An ID to be used in the Message-ID header. + * If empty, a unique id will be generated. + * You can set your own, but it must be in the format "", + * as defined in RFC5322 section 3.6.4 or it will be ignored. + * + * @see https://tools.ietf.org/html/rfc5322#section-3.6.4 + * + * @var string + */ + public $MessageID = ''; + + /** + * The message Date to be used in the Date header. + * If empty, the current date will be added. + * + * @var string + */ + public $MessageDate = ''; + + /** + * SMTP hosts. + * Either a single hostname or multiple semicolon-delimited hostnames. + * You can also specify a different port + * for each host by using this format: [hostname:port] + * (e.g. "smtp1.example.com:25;smtp2.example.com"). + * You can also specify encryption type, for example: + * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). + * Hosts will be tried in order. + * + * @var string + */ + public $Host = 'localhost'; + + /** + * The default SMTP server port. + * + * @var int + */ + public $Port = 25; + + /** + * The SMTP HELO/EHLO name used for the SMTP connection. + * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find + * one with the same method described above for $Hostname. + * + * @see PHPMailer::$Hostname + * + * @var string + */ + public $Helo = ''; + + /** + * What kind of encryption to use on the SMTP connection. + * Options: '', static::ENCRYPTION_STARTTLS, or static::ENCRYPTION_SMTPS. + * + * @var string + */ + public $SMTPSecure = ''; + + /** + * Whether to enable TLS encryption automatically if a server supports it, + * even if `SMTPSecure` is not set to 'tls'. + * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. + * + * @var bool + */ + public $SMTPAutoTLS = true; + + /** + * Whether to use SMTP authentication. + * Uses the Username and Password properties. + * + * @see PHPMailer::$Username + * @see PHPMailer::$Password + * + * @var bool + */ + public $SMTPAuth = false; + + /** + * Options array passed to stream_context_create when connecting via SMTP. + * + * @var array + */ + public $SMTPOptions = []; + + /** + * SMTP username. + * + * @var string + */ + public $Username = ''; + + /** + * SMTP password. + * + * @var string + */ + public $Password = ''; + + /** + * SMTP auth type. + * Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2, attempted in that order if not specified. + * + * @var string + */ + public $AuthType = ''; + + /** + * An instance of the PHPMailer OAuth class. + * + * @var OAuth + */ + protected $oauth; + + /** + * The SMTP server timeout in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. + * + * @var int + */ + public $Timeout = 300; + + /** + * Comma separated list of DSN notifications + * 'NEVER' under no circumstances a DSN must be returned to the sender. + * If you use NEVER all other notifications will be ignored. + * 'SUCCESS' will notify you when your mail has arrived at its destination. + * 'FAILURE' will arrive if an error occurred during delivery. + * 'DELAY' will notify you if there is an unusual delay in delivery, but the actual + * delivery's outcome (success or failure) is not yet decided. + * + * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY + */ + public $dsn = ''; + + /** + * SMTP class debug output mode. + * Debug output level. + * Options: + * * SMTP::DEBUG_OFF: No output + * * SMTP::DEBUG_CLIENT: Client messages + * * SMTP::DEBUG_SERVER: Client and server messages + * * SMTP::DEBUG_CONNECTION: As SERVER plus connection status + * * SMTP::DEBUG_LOWLEVEL: Noisy, low-level data output, rarely needed + * + * @see SMTP::$do_debug + * + * @var int + */ + public $SMTPDebug = 0; + + /** + * How to handle debug output. + * Options: + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
    `, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise. + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * ```php + * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * ``` + * + * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug` + * level output is used: + * + * ```php + * $mail->Debugoutput = new myPsr3Logger; + * ``` + * + * @see SMTP::$Debugoutput + * + * @var string|callable|\Psr\Log\LoggerInterface + */ + public $Debugoutput = 'echo'; + + /** + * Whether to keep SMTP connection open after each message. + * If this is set to true then to close the connection + * requires an explicit call to smtpClose(). + * + * @var bool + */ + public $SMTPKeepAlive = false; + + /** + * Whether to split multiple to addresses into multiple messages + * or send them all in one message. + * Only supported in `mail` and `sendmail` transports, not in SMTP. + * + * @var bool + */ + public $SingleTo = false; + + /** + * Storage for addresses when SingleTo is enabled. + * + * @var array + */ + protected $SingleToArray = []; + + /** + * Whether to generate VERP addresses on send. + * Only applicable when sending via SMTP. + * + * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path + * @see http://www.postfix.org/VERP_README.html Postfix VERP info + * + * @var bool + */ + public $do_verp = false; + + /** + * Whether to allow sending messages with an empty body. + * + * @var bool + */ + public $AllowEmpty = false; + + /** + * DKIM selector. + * + * @var string + */ + public $DKIM_selector = ''; + + /** + * DKIM Identity. + * Usually the email address used as the source of the email. + * + * @var string + */ + public $DKIM_identity = ''; + + /** + * DKIM passphrase. + * Used if your key is encrypted. + * + * @var string + */ + public $DKIM_passphrase = ''; + + /** + * DKIM signing domain name. + * + * @example 'example.com' + * + * @var string + */ + public $DKIM_domain = ''; + + /** + * DKIM Copy header field values for diagnostic use. + * + * @var bool + */ + public $DKIM_copyHeaderFields = true; + + /** + * DKIM Extra signing headers. + * + * @example ['List-Unsubscribe', 'List-Help'] + * + * @var array + */ + public $DKIM_extraHeaders = []; + + /** + * DKIM private key file path. + * + * @var string + */ + public $DKIM_private = ''; + + /** + * DKIM private key string. + * + * If set, takes precedence over `$DKIM_private`. + * + * @var string + */ + public $DKIM_private_string = ''; + + /** + * Callback Action function name. + * + * The function that handles the result of the send email action. + * It is called out by send() for each email sent. + * + * Value can be any php callable: http://www.php.net/is_callable + * + * Parameters: + * bool $result result of the send action + * array $to email addresses of the recipients + * array $cc cc email addresses + * array $bcc bcc email addresses + * string $subject the subject + * string $body the email body + * string $from email address of sender + * string $extra extra information of possible use + * "smtp_transaction_id' => last smtp transaction id + * + * @var string + */ + public $action_function = ''; + + /** + * What to put in the X-Mailer header. + * Options: An empty string for PHPMailer default, whitespace/null for none, or a string to use. + * + * @var string|null + */ + public $XMailer = ''; + + /** + * Which validator to use by default when validating email addresses. + * May be a callable to inject your own validator, but there are several built-in validators. + * The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option. + * + * @see PHPMailer::validateAddress() + * + * @var string|callable + */ + public static $validator = 'php'; + + /** + * An instance of the SMTP sender class. + * + * @var SMTP + */ + protected $smtp; + + /** + * The array of 'to' names and addresses. + * + * @var array + */ + protected $to = []; + + /** + * The array of 'cc' names and addresses. + * + * @var array + */ + protected $cc = []; + + /** + * The array of 'bcc' names and addresses. + * + * @var array + */ + protected $bcc = []; + + /** + * The array of reply-to names and addresses. + * + * @var array + */ + protected $ReplyTo = []; + + /** + * An array of all kinds of addresses. + * Includes all of $to, $cc, $bcc. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * + * @var array + */ + protected $all_recipients = []; + + /** + * An array of names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $all_recipients + * and one of $to, $cc, or $bcc. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * @see PHPMailer::$all_recipients + * + * @var array + */ + protected $RecipientsQueue = []; + + /** + * An array of reply-to names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $ReplyTo. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$ReplyTo + * + * @var array + */ + protected $ReplyToQueue = []; + + /** + * The array of attachments. + * + * @var array + */ + protected $attachment = []; + + /** + * The array of custom headers. + * + * @var array + */ + protected $CustomHeader = []; + + /** + * The most recent Message-ID (including angular brackets). + * + * @var string + */ + protected $lastMessageID = ''; + + /** + * The message's MIME type. + * + * @var string + */ + protected $message_type = ''; + + /** + * The array of MIME boundary strings. + * + * @var array + */ + protected $boundary = []; + + /** + * The array of available languages. + * + * @var array + */ + protected $language = []; + + /** + * The number of errors encountered. + * + * @var int + */ + protected $error_count = 0; + + /** + * The S/MIME certificate file path. + * + * @var string + */ + protected $sign_cert_file = ''; + + /** + * The S/MIME key file path. + * + * @var string + */ + protected $sign_key_file = ''; + + /** + * The optional S/MIME extra certificates ("CA Chain") file path. + * + * @var string + */ + protected $sign_extracerts_file = ''; + + /** + * The S/MIME password for the key. + * Used only if the key is encrypted. + * + * @var string + */ + protected $sign_key_pass = ''; + + /** + * Whether to throw exceptions for errors. + * + * @var bool + */ + protected $exceptions = false; + + /** + * Unique ID used for message ID and boundaries. + * + * @var string + */ + protected $uniqueid = ''; + + /** + * The PHPMailer Version number. + * + * @var string + */ + const VERSION = '6.1.3'; + + /** + * Error severity: message only, continue processing. + * + * @var int + */ + const STOP_MESSAGE = 0; + + /** + * Error severity: message, likely ok to continue processing. + * + * @var int + */ + const STOP_CONTINUE = 1; + + /** + * Error severity: message, plus full stop, critical error reached. + * + * @var int + */ + const STOP_CRITICAL = 2; + + /** + * SMTP RFC standard line ending. + * + * @var string + */ + protected static $LE = "\r\n"; + + /** + * The maximum line length supported by mail(). + * + * Background: mail() will sometimes corrupt messages + * with headers headers longer than 65 chars, see #818. + * + * @var int + */ + const MAIL_MAX_LINE_LENGTH = 63; + + /** + * The maximum line length allowed by RFC 2822 section 2.1.1. + * + * @var int + */ + const MAX_LINE_LENGTH = 998; + + /** + * The lower maximum line length allowed by RFC 2822 section 2.1.1. + * This length does NOT include the line break + * 76 means that lines will be 77 or 78 chars depending on whether + * the line break format is LF or CRLF; both are valid. + * + * @var int + */ + const STD_LINE_LENGTH = 76; + + /** + * Constructor. + * + * @param bool $exceptions Should we throw external exceptions? + */ + public function __construct($exceptions = null) + { + if (null !== $exceptions) { + $this->exceptions = (bool) $exceptions; + } + //Pick an appropriate debug output format automatically + $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html'); + } + + /** + * Destructor. + */ + public function __destruct() + { + //Close any open SMTP connection nicely + $this->smtpClose(); + } + + /** + * Call mail() in a safe_mode-aware fashion. + * Also, unless sendmail_path points to sendmail (or something that + * claims to be sendmail), don't pass params (not a perfect fix, + * but it will do). + * + * @param string $to To + * @param string $subject Subject + * @param string $body Message Body + * @param string $header Additional Header(s) + * @param string|null $params Params + * + * @return bool + */ + private function mailPassthru($to, $subject, $body, $header, $params) + { + //Check overloading of mail function to avoid double-encoding + if (ini_get('mbstring.func_overload') & 1) { + $subject = $this->secureHeader($subject); + } else { + $subject = $this->encodeHeader($this->secureHeader($subject)); + } + //Calling mail() with null params breaks + if (!$this->UseSendmailOptions || null === $params) { + $result = @mail($to, $subject, $body, $header); + } else { + $result = @mail($to, $subject, $body, $header, $params); + } + + return $result; + } + + /** + * Output debugging info via user-defined method. + * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug). + * + * @see PHPMailer::$Debugoutput + * @see PHPMailer::$SMTPDebug + * + * @param string $str + */ + protected function edebug($str) + { + if ($this->SMTPDebug <= 0) { + return; + } + //Is this a PSR-3 logger? + if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { + $this->Debugoutput->debug($str); + + return; + } + //Avoid clash with built-in function names + if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) { + call_user_func($this->Debugoutput, $str, $this->SMTPDebug); + + return; + } + switch ($this->Debugoutput) { + case 'error_log': + //Don't output, just log + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking, HTML-safe output + echo htmlentities( + preg_replace('/[\r\n]+/', '', $str), + ENT_QUOTES, + 'UTF-8' + ), "
    \n"; + break; + case 'echo': + default: + //Normalize line breaks + $str = preg_replace('/\r\n|\r/m', "\n", $str); + echo gmdate('Y-m-d H:i:s'), + "\t", + //Trim trailing space + trim( + //Indent for readability, except for trailing break + str_replace( + "\n", + "\n \t ", + trim($str) + ) + ), + "\n"; + } + } + + /** + * Sets message type to HTML or plain. + * + * @param bool $isHtml True for HTML mode + */ + public function isHTML($isHtml = true) + { + if ($isHtml) { + $this->ContentType = static::CONTENT_TYPE_TEXT_HTML; + } else { + $this->ContentType = static::CONTENT_TYPE_PLAINTEXT; + } + } + + /** + * Send messages using SMTP. + */ + public function isSMTP() + { + $this->Mailer = 'smtp'; + } + + /** + * Send messages using PHP's mail() function. + */ + public function isMail() + { + $this->Mailer = 'mail'; + } + + /** + * Send messages using $Sendmail. + */ + public function isSendmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'sendmail')) { + $this->Sendmail = '/usr/sbin/sendmail'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'sendmail'; + } + + /** + * Send messages using qmail. + */ + public function isQmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'qmail')) { + $this->Sendmail = '/var/qmail/bin/qmail-inject'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'qmail'; + } + + /** + * Add a "To" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addAddress($address, $name = '') + { + return $this->addOrEnqueueAnAddress('to', $address, $name); + } + + /** + * Add a "CC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('cc', $address, $name); + } + + /** + * Add a "BCC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addBCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('bcc', $address, $name); + } + + /** + * Add a "Reply-To" address. + * + * @param string $address The email address to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addReplyTo($address, $name = '') + { + return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer + * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still + * be modified after calling this function), addition of such addresses is delayed until send(). + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addOrEnqueueAnAddress($kind, $address, $name) + { + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + $pos = strrpos($address, '@'); + if (false === $pos) { + // At-sign is missing. + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + $params = [$kind, $address, $name]; + // Enqueue addresses with IDN until we know the PHPMailer::$CharSet. + if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) { + if ('Reply-To' !== $kind) { + if (!array_key_exists($address, $this->RecipientsQueue)) { + $this->RecipientsQueue[$address] = $params; + + return true; + } + } elseif (!array_key_exists($address, $this->ReplyToQueue)) { + $this->ReplyToQueue[$address] = $params; + + return true; + } + + return false; + } + + // Immediately add standard addresses without IDN. + return call_user_func_array([$this, 'addAnAddress'], $params); + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addAnAddress($kind, $address, $name = '') + { + if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) { + $error_message = sprintf( + '%s: %s', + $this->lang('Invalid recipient kind'), + $kind + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if (!static::validateAddress($address)) { + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if ('Reply-To' !== $kind) { + if (!array_key_exists(strtolower($address), $this->all_recipients)) { + $this->{$kind}[] = [$address, $name]; + $this->all_recipients[strtolower($address)] = true; + + return true; + } + } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) { + $this->ReplyTo[strtolower($address)] = [$address, $name]; + + return true; + } + + return false; + } + + /** + * Parse and validate a string containing one or more RFC822-style comma-separated email addresses + * of the form "display name
    " into an array of name/address pairs. + * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. + * Note that quotes in the name part are removed. + * + * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation + * + * @param string $addrstr The address list string + * @param bool $useimap Whether to use the IMAP extension to parse the list + * + * @return array + */ + public static function parseAddresses($addrstr, $useimap = true) + { + $addresses = []; + if ($useimap && function_exists('imap_rfc822_parse_adrlist')) { + //Use this built-in parser if it's available + $list = imap_rfc822_parse_adrlist($addrstr, ''); + foreach ($list as $address) { + if (('.SYNTAX-ERROR.' !== $address->host) && static::validateAddress( + $address->mailbox . '@' . $address->host + )) { + $addresses[] = [ + 'name' => (property_exists($address, 'personal') ? $address->personal : ''), + 'address' => $address->mailbox . '@' . $address->host, + ]; + } + } + } else { + //Use this simpler parser + $list = explode(',', $addrstr); + foreach ($list as $address) { + $address = trim($address); + //Is there a separate name part? + if (strpos($address, '<') === false) { + //No separate name, just use the whole thing + if (static::validateAddress($address)) { + $addresses[] = [ + 'name' => '', + 'address' => $address, + ]; + } + } else { + list($name, $email) = explode('<', $address); + $email = trim(str_replace('>', '', $email)); + if (static::validateAddress($email)) { + $addresses[] = [ + 'name' => trim(str_replace(['"', "'"], '', $name)), + 'address' => $email, + ]; + } + } + } + } + + return $addresses; + } + + /** + * Set the From and FromName properties. + * + * @param string $address + * @param string $name + * @param bool $auto Whether to also set the Sender address, defaults to true + * + * @throws Exception + * + * @return bool + */ + public function setFrom($address, $name = '', $auto = true) + { + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + // Don't validate now addresses with IDN. Will be done in send(). + $pos = strrpos($address, '@'); + if ((false === $pos) + || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported()) + && !static::validateAddress($address)) + ) { + $error_message = sprintf( + '%s (From): %s', + $this->lang('invalid_address'), + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + $this->From = $address; + $this->FromName = $name; + if ($auto && empty($this->Sender)) { + $this->Sender = $address; + } + + return true; + } + + /** + * Return the Message-ID header of the last email. + * Technically this is the value from the last time the headers were created, + * but it's also the message ID of the last sent message except in + * pathological cases. + * + * @return string + */ + public function getLastMessageID() + { + return $this->lastMessageID; + } + + /** + * Check that a string looks like an email address. + * Validation patterns supported: + * * `auto` Pick best pattern automatically; + * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0; + * * `pcre` Use old PCRE implementation; + * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; + * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. + * * `noregex` Don't use a regex: super fast, really dumb. + * Alternatively you may pass in a callable to inject your own validator, for example: + * + * ```php + * PHPMailer::validateAddress('user@example.com', function($address) { + * return (strpos($address, '@') !== false); + * }); + * ``` + * + * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator. + * + * @param string $address The email address to check + * @param string|callable $patternselect Which pattern to use + * + * @return bool + */ + public static function validateAddress($address, $patternselect = null) + { + if (null === $patternselect) { + $patternselect = static::$validator; + } + if (is_callable($patternselect)) { + return $patternselect($address); + } + //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 + if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) { + return false; + } + switch ($patternselect) { + case 'pcre': //Kept for BC + case 'pcre8': + /* + * A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL + * is based. + * In addition to the addresses allowed by filter_var, also permits: + * * dotless domains: `a@b` + * * comments: `1234 @ local(blah) .machine .example` + * * quoted elements: `'"test blah"@example.org'` + * * numeric TLDs: `a@b.123` + * * unbracketed IPv4 literals: `a@192.168.0.1` + * * IPv6 literals: 'first.last@[IPv6:a1::]' + * Not all of these will necessarily work for sending! + * + * @see http://squiloople.com/2009/12/20/email-address-validation/ + * @copyright 2009-2010 Michael Rushton + * Feel free to use and redistribute this code. But please keep this copyright notice. + */ + return (bool) preg_match( + '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . + '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . + '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . + '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . + '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . + '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . + '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . + '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . + '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', + $address + ); + case 'html5': + /* + * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. + * + * @see http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email) + */ + return (bool) preg_match( + '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . + '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', + $address + ); + case 'php': + default: + return (bool) filter_var($address, FILTER_VALIDATE_EMAIL); + } + } + + /** + * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the + * `intl` and `mbstring` PHP extensions. + * + * @return bool `true` if required functions for IDN support are present + */ + public static function idnSupported() + { + return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding'); + } + + /** + * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. + * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. + * This function silently returns unmodified address if: + * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) + * - Conversion to punycode is impossible (e.g. required PHP functions are not available) + * or fails for any reason (e.g. domain contains characters not allowed in an IDN). + * + * @see PHPMailer::$CharSet + * + * @param string $address The email address to convert + * + * @return string The encoded address in ASCII form + */ + public function punyencodeAddress($address) + { + // Verify we have required functions, CharSet, and at-sign. + $pos = strrpos($address, '@'); + if (!empty($this->CharSet) && + false !== $pos && + static::idnSupported() + ) { + $domain = substr($address, ++$pos); + // Verify CharSet string is a valid one, and domain properly encoded in this CharSet. + if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) { + $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet); + //Ignore IDE complaints about this line - method signature changed in PHP 5.4 + $errorcode = 0; + $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_UTS46); + if (false !== $punycode) { + return substr($address, 0, $pos) . $punycode; + } + } + } + + return $address; + } + + /** + * Create a message and send it. + * Uses the sending method specified by $Mailer. + * + * @throws Exception + * + * @return bool false on error - See the ErrorInfo property for details of the error + */ + public function send() + { + try { + if (!$this->preSend()) { + return false; + } + + return $this->postSend(); + } catch (Exception $exc) { + $this->mailHeader = ''; + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Prepare a message for sending. + * + * @throws Exception + * + * @return bool + */ + public function preSend() + { + if ('smtp' === $this->Mailer + || ('mail' === $this->Mailer && stripos(PHP_OS, 'WIN') === 0) + ) { + //SMTP mandates RFC-compliant line endings + //and it's also used with mail() on Windows + static::setLE("\r\n"); + } else { + //Maintain backward compatibility with legacy Linux command line mailers + static::setLE(PHP_EOL); + } + //Check for buggy PHP versions that add a header with an incorrect line break + if ('mail' === $this->Mailer + && ((PHP_VERSION_ID >= 70000 && PHP_VERSION_ID < 70017) + || (PHP_VERSION_ID >= 70100 && PHP_VERSION_ID < 70103)) + && ini_get('mail.add_x_header') === '1' + && stripos(PHP_OS, 'WIN') === 0 + ) { + trigger_error( + 'Your version of PHP is affected by a bug that may result in corrupted messages.' . + ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' . + ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.', + E_USER_WARNING + ); + } + + try { + $this->error_count = 0; // Reset errors + $this->mailHeader = ''; + + // Dequeue recipient and Reply-To addresses with IDN + foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { + $params[1] = $this->punyencodeAddress($params[1]); + call_user_func_array([$this, 'addAnAddress'], $params); + } + if (count($this->to) + count($this->cc) + count($this->bcc) < 1) { + throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL); + } + + // Validate From, Sender, and ConfirmReadingTo addresses + foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) { + $this->$address_kind = trim($this->$address_kind); + if (empty($this->$address_kind)) { + continue; + } + $this->$address_kind = $this->punyencodeAddress($this->$address_kind); + if (!static::validateAddress($this->$address_kind)) { + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $address_kind, + $this->$address_kind + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + } + + // Set whether the message is multipart/alternative + if ($this->alternativeExists()) { + $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE; + } + + $this->setMessageType(); + // Refuse to send an empty message unless we are specifically allowing it + if (!$this->AllowEmpty && empty($this->Body)) { + throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL); + } + + //Trim subject consistently + $this->Subject = trim($this->Subject); + // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) + $this->MIMEHeader = ''; + $this->MIMEBody = $this->createBody(); + // createBody may have added some headers, so retain them + $tempheaders = $this->MIMEHeader; + $this->MIMEHeader = $this->createHeader(); + $this->MIMEHeader .= $tempheaders; + + // To capture the complete message when using mail(), create + // an extra header list which createHeader() doesn't fold in + if ('mail' === $this->Mailer) { + if (count($this->to) > 0) { + $this->mailHeader .= $this->addrAppend('To', $this->to); + } else { + $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); + } + $this->mailHeader .= $this->headerLine( + 'Subject', + $this->encodeHeader($this->secureHeader($this->Subject)) + ); + } + + // Sign with DKIM if enabled + if (!empty($this->DKIM_domain) + && !empty($this->DKIM_selector) + && (!empty($this->DKIM_private_string) + || (!empty($this->DKIM_private) + && static::isPermittedPath($this->DKIM_private) + && file_exists($this->DKIM_private) + ) + ) + ) { + $header_dkim = $this->DKIM_Add( + $this->MIMEHeader . $this->mailHeader, + $this->encodeHeader($this->secureHeader($this->Subject)), + $this->MIMEBody + ); + $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . static::$LE . + static::normalizeBreaks($header_dkim) . static::$LE; + } + + return true; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Actually send a message via the selected mechanism. + * + * @throws Exception + * + * @return bool + */ + public function postSend() + { + try { + // Choose the mailer and send through it + switch ($this->Mailer) { + case 'sendmail': + case 'qmail': + return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); + case 'smtp': + return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); + case 'mail': + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + default: + $sendMethod = $this->Mailer . 'Send'; + if (method_exists($this, $sendMethod)) { + return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody); + } + + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + } + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + } + + return false; + } + + /** + * Send mail using the $Sendmail program. + * + * @see PHPMailer::$Sendmail + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function sendmailSend($header, $body) + { + $header = rtrim($header, "\r\n ") . static::$LE . static::$LE; + + // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + if (!empty($this->Sender) && self::isShellSafe($this->Sender)) { + if ('qmail' === $this->Mailer) { + $sendmailFmt = '%s -f%s'; + } else { + $sendmailFmt = '%s -oi -f%s -t'; + } + } elseif ('qmail' === $this->Mailer) { + $sendmailFmt = '%s'; + } else { + $sendmailFmt = '%s -oi -t'; + } + + $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender); + + if ($this->SingleTo) { + foreach ($this->SingleToArray as $toAddr) { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fwrite($mail, 'To: ' . $toAddr . "\n"); + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $this->doCallback( + ($result === 0), + [$toAddr], + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + } else { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $this->doCallback( + ($result === 0), + $this->to, + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + + return true; + } + + /** + * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters. + * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows. + * + * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report + * + * @param string $string The string to be validated + * + * @return bool + */ + protected static function isShellSafe($string) + { + // Future-proof + if (escapeshellcmd($string) !== $string + || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""]) + ) { + return false; + } + + $length = strlen($string); + + for ($i = 0; $i < $length; ++$i) { + $c = $string[$i]; + + // All other characters have a special meaning in at least one common shell, including = and +. + // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. + // Note that this does permit non-Latin alphanumeric characters based on the current locale. + if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { + return false; + } + } + + return true; + } + + /** + * Check whether a file path is of a permitted type. + * Used to reject URLs and phar files from functions that access local file paths, + * such as addAttachment. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function isPermittedPath($path) + { + return !preg_match('#^[a-z]+://#i', $path); + } + + /** + * Send mail using the PHP mail() function. + * + * @see http://www.php.net/manual/en/book.mail.php + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function mailSend($header, $body) + { + $header = rtrim($header, "\r\n ") . static::$LE . static::$LE; + + $toArr = []; + foreach ($this->to as $toaddr) { + $toArr[] = $this->addrFormat($toaddr); + } + $to = implode(', ', $toArr); + + $params = null; + //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver + //A space after `-f` is optional, but there is a long history of its presence + //causing problems, so we don't use one + //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html + //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Example problem: https://www.drupal.org/node/1057954 + // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) { + $params = sprintf('-f%s', $this->Sender); + } + if (!empty($this->Sender) && static::validateAddress($this->Sender)) { + $old_from = ini_get('sendmail_from'); + ini_set('sendmail_from', $this->Sender); + } + $result = false; + if ($this->SingleTo && count($toArr) > 1) { + foreach ($toArr as $toAddr) { + $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); + $this->doCallback($result, [$toAddr], $this->cc, $this->bcc, $this->Subject, $body, $this->From, []); + } + } else { + $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); + $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []); + } + if (isset($old_from)) { + ini_set('sendmail_from', $old_from); + } + if (!$result) { + throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL); + } + + return true; + } + + /** + * Get an instance to use for SMTP operations. + * Override this function to load your own SMTP implementation, + * or set one with setSMTPInstance. + * + * @return SMTP + */ + public function getSMTPInstance() + { + if (!is_object($this->smtp)) { + $this->smtp = new SMTP(); + } + + return $this->smtp; + } + + /** + * Provide an instance to use for SMTP operations. + * + * @return SMTP + */ + public function setSMTPInstance(SMTP $smtp) + { + $this->smtp = $smtp; + + return $this->smtp; + } + + /** + * Send mail via SMTP. + * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. + * + * @see PHPMailer::setSMTPInstance() to use a different class. + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function smtpSend($header, $body) + { + $header = rtrim($header, "\r\n ") . static::$LE . static::$LE; + $bad_rcpt = []; + if (!$this->smtpConnect($this->SMTPOptions)) { + throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); + } + //Sender already validated in preSend() + if ('' === $this->Sender) { + $smtp_from = $this->From; + } else { + $smtp_from = $this->Sender; + } + if (!$this->smtp->mail($smtp_from)) { + $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); + throw new Exception($this->ErrorInfo, self::STOP_CRITICAL); + } + + $callbacks = []; + // Attempt to send to all recipients + foreach ([$this->to, $this->cc, $this->bcc] as $togroup) { + foreach ($togroup as $to) { + if (!$this->smtp->recipient($to[0], $this->dsn)) { + $error = $this->smtp->getError(); + $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']]; + $isSent = false; + } else { + $isSent = true; + } + + $callbacks[] = ['issent'=>$isSent, 'to'=>$to[0]]; + } + } + + // Only send the DATA command if we have viable recipients + if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) { + throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL); + } + + $smtp_transaction_id = $this->smtp->getLastTransactionID(); + + if ($this->SMTPKeepAlive) { + $this->smtp->reset(); + } else { + $this->smtp->quit(); + $this->smtp->close(); + } + + foreach ($callbacks as $cb) { + $this->doCallback( + $cb['issent'], + [$cb['to']], + [], + [], + $this->Subject, + $body, + $this->From, + ['smtp_transaction_id' => $smtp_transaction_id] + ); + } + + //Create error message for any bad addresses + if (count($bad_rcpt) > 0) { + $errstr = ''; + foreach ($bad_rcpt as $bad) { + $errstr .= $bad['to'] . ': ' . $bad['error']; + } + throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE); + } + + return true; + } + + /** + * Initiate a connection to an SMTP server. + * Returns false if the operation failed. + * + * @param array $options An array of options compatible with stream_context_create() + * + * @throws Exception + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @return bool + */ + public function smtpConnect($options = null) + { + if (null === $this->smtp) { + $this->smtp = $this->getSMTPInstance(); + } + + //If no options are provided, use whatever is set in the instance + if (null === $options) { + $options = $this->SMTPOptions; + } + + // Already connected? + if ($this->smtp->connected()) { + return true; + } + + $this->smtp->setTimeout($this->Timeout); + $this->smtp->setDebugLevel($this->SMTPDebug); + $this->smtp->setDebugOutput($this->Debugoutput); + $this->smtp->setVerp($this->do_verp); + $hosts = explode(';', $this->Host); + $lastexception = null; + + foreach ($hosts as $hostentry) { + $hostinfo = []; + if (!preg_match( + '/^((ssl|tls):\/\/)*([a-zA-Z\d.-]*|\[[a-fA-F\d:]+]):?(\d*)$/', + trim($hostentry), + $hostinfo + )) { + $this->edebug($this->lang('connect_host') . ' ' . $hostentry); + // Not a valid host entry + continue; + } + // $hostinfo[2]: optional ssl or tls prefix + // $hostinfo[3]: the hostname + // $hostinfo[4]: optional port number + // The host string prefix can temporarily override the current setting for SMTPSecure + // If it's not specified, the default value is used + + //Check the host name is a valid name or IP address before trying to use it + if (!static::isValidHost($hostinfo[3])) { + $this->edebug($this->lang('connect_host') . ' ' . $hostentry); + continue; + } + $prefix = ''; + $secure = $this->SMTPSecure; + $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure); + if ('ssl' === $hostinfo[2] || ('' === $hostinfo[2] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) { + $prefix = 'ssl://'; + $tls = false; // Can't have SSL and TLS at the same time + $secure = static::ENCRYPTION_SMTPS; + } elseif ('tls' === $hostinfo[2]) { + $tls = true; + // tls doesn't use a prefix + $secure = static::ENCRYPTION_STARTTLS; + } + //Do we need the OpenSSL extension? + $sslext = defined('OPENSSL_ALGO_SHA256'); + if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) { + //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled + if (!$sslext) { + throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL); + } + } + $host = $hostinfo[3]; + $port = $this->Port; + $tport = (int) $hostinfo[4]; + if ($tport > 0 && $tport < 65536) { + $port = $tport; + } + if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { + try { + if ($this->Helo) { + $hello = $this->Helo; + } else { + $hello = $this->serverHostname(); + } + $this->smtp->hello($hello); + //Automatically enable TLS encryption if: + // * it's not disabled + // * we have openssl extension + // * we are not already using SSL + // * the server offers STARTTLS + if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) { + $tls = true; + } + if ($tls) { + if (!$this->smtp->startTLS()) { + throw new Exception($this->lang('connect_host')); + } + // We must resend EHLO after TLS negotiation + $this->smtp->hello($hello); + } + if ($this->SMTPAuth && !$this->smtp->authenticate( + $this->Username, + $this->Password, + $this->AuthType, + $this->oauth + )) { + throw new Exception($this->lang('authenticate')); + } + + return true; + } catch (Exception $exc) { + $lastexception = $exc; + $this->edebug($exc->getMessage()); + // We must have connected, but then failed TLS or Auth, so close connection nicely + $this->smtp->quit(); + } + } + } + // If we get here, all connection attempts have failed, so close connection hard + $this->smtp->close(); + // As we've caught all exceptions, just report whatever the last one was + if ($this->exceptions && null !== $lastexception) { + throw $lastexception; + } + + return false; + } + + /** + * Close the active SMTP session if one exists. + */ + public function smtpClose() + { + if ((null !== $this->smtp) && $this->smtp->connected()) { + $this->smtp->quit(); + $this->smtp->close(); + } + } + + /** + * Set the language for error messages. + * Returns false if it cannot load the language file. + * The default language is English. + * + * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") + * @param string $lang_path Path to the language file directory, with trailing separator (slash) + * + * @return bool + */ + public function setLanguage($langcode = 'en', $lang_path = '') + { + // Backwards compatibility for renamed language codes + $renamed_langcodes = [ + 'br' => 'pt_br', + 'cz' => 'cs', + 'dk' => 'da', + 'no' => 'nb', + 'se' => 'sv', + 'rs' => 'sr', + 'tg' => 'tl', + ]; + + if (isset($renamed_langcodes[$langcode])) { + $langcode = $renamed_langcodes[$langcode]; + } + + // Define full set of translatable strings in English + $PHPMAILER_LANG = [ + 'authenticate' => 'SMTP Error: Could not authenticate.', + 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', + 'data_not_accepted' => 'SMTP Error: data not accepted.', + 'empty_message' => 'Message body empty', + 'encoding' => 'Unknown encoding: ', + 'execute' => 'Could not execute: ', + 'file_access' => 'Could not access file: ', + 'file_open' => 'File Error: Could not open file: ', + 'from_failed' => 'The following From address failed: ', + 'instantiate' => 'Could not instantiate mail function.', + 'invalid_address' => 'Invalid address: ', + 'mailer_not_supported' => ' mailer is not supported.', + 'provide_address' => 'You must provide at least one recipient email address.', + 'recipients_failed' => 'SMTP Error: The following recipients failed: ', + 'signing' => 'Signing Error: ', + 'smtp_connect_failed' => 'SMTP connect() failed.', + 'smtp_error' => 'SMTP server error: ', + 'variable_set' => 'Cannot set or reset variable: ', + 'extension_missing' => 'Extension missing: ', + ]; + if (empty($lang_path)) { + // Calculate an absolute path so it can work if CWD is not here + $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR; + } + //Validate $langcode + if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) { + $langcode = 'en'; + } + $foundlang = true; + $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php'; + // There is no English translation file + if ('en' !== $langcode) { + // Make sure language file path is readable + if (!static::isPermittedPath($lang_file) || !file_exists($lang_file)) { + $foundlang = false; + } else { + // Overwrite language-specific strings. + // This way we'll never have missing translation keys. + $foundlang = include $lang_file; + } + } + $this->language = $PHPMAILER_LANG; + + return (bool) $foundlang; // Returns false if language not found + } + + /** + * Get the array of strings for the current language. + * + * @return array + */ + public function getTranslations() + { + return $this->language; + } + + /** + * Create recipient headers. + * + * @param string $type + * @param array $addr An array of recipients, + * where each recipient is a 2-element indexed array with element 0 containing an address + * and element 1 containing a name, like: + * [['joe@example.com', 'Joe User'], ['zoe@example.com', 'Zoe User']] + * + * @return string + */ + public function addrAppend($type, $addr) + { + $addresses = []; + foreach ($addr as $address) { + $addresses[] = $this->addrFormat($address); + } + + return $type . ': ' . implode(', ', $addresses) . static::$LE; + } + + /** + * Format an address for use in a message header. + * + * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name like + * ['joe@example.com', 'Joe User'] + * + * @return string + */ + public function addrFormat($addr) + { + if (empty($addr[1])) { // No name provided + return $this->secureHeader($addr[0]); + } + + return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . + ' <' . $this->secureHeader($addr[0]) . '>'; + } + + /** + * Word-wrap message. + * For use with mailers that do not automatically perform wrapping + * and for quoted-printable encoded messages. + * Original written by philippe. + * + * @param string $message The message to wrap + * @param int $length The line length to wrap to + * @param bool $qp_mode Whether to run in Quoted-Printable mode + * + * @return string + */ + public function wrapText($message, $length, $qp_mode = false) + { + if ($qp_mode) { + $soft_break = sprintf(' =%s', static::$LE); + } else { + $soft_break = static::$LE; + } + // If utf-8 encoding is used, we will need to make sure we don't + // split multibyte characters when we wrap + $is_utf8 = static::CHARSET_UTF8 === strtolower($this->CharSet); + $lelen = strlen(static::$LE); + $crlflen = strlen(static::$LE); + + $message = static::normalizeBreaks($message); + //Remove a trailing line break + if (substr($message, -$lelen) === static::$LE) { + $message = substr($message, 0, -$lelen); + } + + //Split message into lines + $lines = explode(static::$LE, $message); + //Message will be rebuilt in here + $message = ''; + foreach ($lines as $line) { + $words = explode(' ', $line); + $buf = ''; + $firstword = true; + foreach ($words as $word) { + if ($qp_mode && (strlen($word) > $length)) { + $space_left = $length - strlen($buf) - $crlflen; + if (!$firstword) { + if ($space_left > 20) { + $len = $space_left; + if ($is_utf8) { + $len = $this->utf8CharBoundary($word, $len); + } elseif ('=' === substr($word, $len - 1, 1)) { + --$len; + } elseif ('=' === substr($word, $len - 2, 1)) { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + $buf .= ' ' . $part; + $message .= $buf . sprintf('=%s', static::$LE); + } else { + $message .= $buf . $soft_break; + } + $buf = ''; + } + while ($word !== '') { + if ($length <= 0) { + break; + } + $len = $length; + if ($is_utf8) { + $len = $this->utf8CharBoundary($word, $len); + } elseif ('=' === substr($word, $len - 1, 1)) { + --$len; + } elseif ('=' === substr($word, $len - 2, 1)) { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = (string) substr($word, $len); + + if ($word !== '') { + $message .= $part . sprintf('=%s', static::$LE); + } else { + $buf = $part; + } + } + } else { + $buf_o = $buf; + if (!$firstword) { + $buf .= ' '; + } + $buf .= $word; + + if ('' !== $buf_o && strlen($buf) > $length) { + $message .= $buf_o . $soft_break; + $buf = $word; + } + } + $firstword = false; + } + $message .= $buf . static::$LE; + } + + return $message; + } + + /** + * Find the last character boundary prior to $maxLength in a utf-8 + * quoted-printable encoded string. + * Original written by Colin Brown. + * + * @param string $encodedText utf-8 QP text + * @param int $maxLength Find the last character boundary prior to this length + * + * @return int + */ + public function utf8CharBoundary($encodedText, $maxLength) + { + $foundSplitPos = false; + $lookBack = 3; + while (!$foundSplitPos) { + $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack); + $encodedCharPos = strpos($lastChunk, '='); + if (false !== $encodedCharPos) { + // Found start of encoded character byte within $lookBack block. + // Check the encoded byte value (the 2 chars after the '=') + $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2); + $dec = hexdec($hex); + if ($dec < 128) { + // Single byte character. + // If the encoded char was found at pos 0, it will fit + // otherwise reduce maxLength to start of the encoded char + if ($encodedCharPos > 0) { + $maxLength -= $lookBack - $encodedCharPos; + } + $foundSplitPos = true; + } elseif ($dec >= 192) { + // First byte of a multi byte character + // Reduce maxLength to split at start of character + $maxLength -= $lookBack - $encodedCharPos; + $foundSplitPos = true; + } elseif ($dec < 192) { + // Middle byte of a multi byte character, look further back + $lookBack += 3; + } + } else { + // No encoded character found + $foundSplitPos = true; + } + } + + return $maxLength; + } + + /** + * Apply word wrapping to the message body. + * Wraps the message body to the number of chars set in the WordWrap property. + * You should only do this to plain-text bodies as wrapping HTML tags may break them. + * This is called automatically by createBody(), so you don't need to call it yourself. + */ + public function setWordWrap() + { + if ($this->WordWrap < 1) { + return; + } + + switch ($this->message_type) { + case 'alt': + case 'alt_inline': + case 'alt_attach': + case 'alt_inline_attach': + $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap); + break; + default: + $this->Body = $this->wrapText($this->Body, $this->WordWrap); + break; + } + } + + /** + * Assemble message headers. + * + * @return string The assembled headers + */ + public function createHeader() + { + $result = ''; + + $result .= $this->headerLine('Date', '' === $this->MessageDate ? self::rfcDate() : $this->MessageDate); + + // To be created automatically by mail() + if ($this->SingleTo) { + if ('mail' !== $this->Mailer) { + foreach ($this->to as $toaddr) { + $this->SingleToArray[] = $this->addrFormat($toaddr); + } + } + } elseif (count($this->to) > 0) { + if ('mail' !== $this->Mailer) { + $result .= $this->addrAppend('To', $this->to); + } + } elseif (count($this->cc) === 0) { + $result .= $this->headerLine('To', 'undisclosed-recipients:;'); + } + + $result .= $this->addrAppend('From', [[trim($this->From), $this->FromName]]); + + // sendmail and mail() extract Cc from the header before sending + if (count($this->cc) > 0) { + $result .= $this->addrAppend('Cc', $this->cc); + } + + // sendmail and mail() extract Bcc from the header before sending + if (( + 'sendmail' === $this->Mailer || 'qmail' === $this->Mailer || 'mail' === $this->Mailer + ) + && count($this->bcc) > 0 + ) { + $result .= $this->addrAppend('Bcc', $this->bcc); + } + + if (count($this->ReplyTo) > 0) { + $result .= $this->addrAppend('Reply-To', $this->ReplyTo); + } + + // mail() sets the subject itself + if ('mail' !== $this->Mailer) { + $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject))); + } + + // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4 + // https://tools.ietf.org/html/rfc5322#section-3.6.4 + if ('' !== $this->MessageID && preg_match('/^<.*@.*>$/', $this->MessageID)) { + $this->lastMessageID = $this->MessageID; + } else { + $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname()); + } + $result .= $this->headerLine('Message-ID', $this->lastMessageID); + if (null !== $this->Priority) { + $result .= $this->headerLine('X-Priority', $this->Priority); + } + if ('' === $this->XMailer) { + $result .= $this->headerLine( + 'X-Mailer', + 'PHPMailer ' . self::VERSION . ' (https://github.com/PHPMailer/PHPMailer)' + ); + } else { + $myXmailer = trim($this->XMailer); + if ($myXmailer) { + $result .= $this->headerLine('X-Mailer', $myXmailer); + } + } + + if ('' !== $this->ConfirmReadingTo) { + $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>'); + } + + // Add custom headers + foreach ($this->CustomHeader as $header) { + $result .= $this->headerLine( + trim($header[0]), + $this->encodeHeader(trim($header[1])) + ); + } + if (!$this->sign_key_file) { + $result .= $this->headerLine('MIME-Version', '1.0'); + $result .= $this->getMailMIME(); + } + + return $result; + } + + /** + * Get the message MIME type headers. + * + * @return string + */ + public function getMailMIME() + { + $result = ''; + $ismultipart = true; + switch ($this->message_type) { + case 'inline': + $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';'); + $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"'); + break; + case 'attach': + case 'inline_attach': + case 'alt_attach': + case 'alt_inline_attach': + $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_MIXED . ';'); + $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"'); + break; + case 'alt': + case 'alt_inline': + $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';'); + $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"'); + break; + default: + // Catches case 'plain': and case '': + $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet); + $ismultipart = false; + break; + } + // RFC1341 part 5 says 7bit is assumed if not specified + if (static::ENCODING_7BIT !== $this->Encoding) { + // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE + if ($ismultipart) { + if (static::ENCODING_8BIT === $this->Encoding) { + $result .= $this->headerLine('Content-Transfer-Encoding', static::ENCODING_8BIT); + } + // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible + } else { + $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding); + } + } + + if ('mail' !== $this->Mailer) { +// $result .= static::$LE; + } + + return $result; + } + + /** + * Returns the whole MIME message. + * Includes complete headers and body. + * Only valid post preSend(). + * + * @see PHPMailer::preSend() + * + * @return string + */ + public function getSentMIMEMessage() + { + return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . static::$LE . static::$LE . $this->MIMEBody; + } + + /** + * Create a unique ID to use for boundaries. + * + * @return string + */ + protected function generateId() + { + $len = 32; //32 bytes = 256 bits + $bytes = ''; + if (function_exists('random_bytes')) { + try { + $bytes = random_bytes($len); + } catch (\Exception $e) { + //Do nothing + } + } elseif (function_exists('openssl_random_pseudo_bytes')) { + /** @noinspection CryptographicallySecureRandomnessInspection */ + $bytes = openssl_random_pseudo_bytes($len); + } + if ($bytes === '') { + //We failed to produce a proper random string, so make do. + //Use a hash to force the length to the same as the other methods + $bytes = hash('sha256', uniqid((string) mt_rand(), true), true); + } + + //We don't care about messing up base64 format here, just want a random string + return str_replace(['=', '+', '/'], '', base64_encode(hash('sha256', $bytes, true))); + } + + /** + * Assemble the message body. + * Returns an empty string on failure. + * + * @throws Exception + * + * @return string The assembled message body + */ + public function createBody() + { + $body = ''; + //Create unique IDs and preset boundaries + $this->uniqueid = $this->generateId(); + $this->boundary[1] = 'b1_' . $this->uniqueid; + $this->boundary[2] = 'b2_' . $this->uniqueid; + $this->boundary[3] = 'b3_' . $this->uniqueid; + + if ($this->sign_key_file) { + $body .= $this->getMailMIME() . static::$LE; + } + + $this->setWordWrap(); + + $bodyEncoding = $this->Encoding; + $bodyCharSet = $this->CharSet; + //Can we do a 7-bit downgrade? + if (static::ENCODING_8BIT === $bodyEncoding && !$this->has8bitChars($this->Body)) { + $bodyEncoding = static::ENCODING_7BIT; + //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit + $bodyCharSet = static::CHARSET_ASCII; + } + //If lines are too long, and we're not already using an encoding that will shorten them, + //change to quoted-printable transfer encoding for the body part only + if (static::ENCODING_BASE64 !== $this->Encoding && static::hasLineLongerThanMax($this->Body)) { + $bodyEncoding = static::ENCODING_QUOTED_PRINTABLE; + } + + $altBodyEncoding = $this->Encoding; + $altBodyCharSet = $this->CharSet; + //Can we do a 7-bit downgrade? + if (static::ENCODING_8BIT === $altBodyEncoding && !$this->has8bitChars($this->AltBody)) { + $altBodyEncoding = static::ENCODING_7BIT; + //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit + $altBodyCharSet = static::CHARSET_ASCII; + } + //If lines are too long, and we're not already using an encoding that will shorten them, + //change to quoted-printable transfer encoding for the alt body part only + if (static::ENCODING_BASE64 !== $altBodyEncoding && static::hasLineLongerThanMax($this->AltBody)) { + $altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE; + } + //Use this as a preamble in all multipart message types + $mimepre = 'This is a multi-part message in MIME format.' . static::$LE; + switch ($this->message_type) { + case 'inline': + $body .= $mimepre; + $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('inline', $this->boundary[1]); + break; + case 'attach': + $body .= $mimepre; + $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + case 'inline_attach': + $body .= $mimepre; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';'); + $body .= $this->textLine(' boundary="' . $this->boundary[2] . '";'); + $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"'); + $body .= static::$LE; + $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('inline', $this->boundary[2]); + $body .= static::$LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + case 'alt': + $body .= $mimepre; + $body .= $this->getBoundary( + $this->boundary[1], + $altBodyCharSet, + static::CONTENT_TYPE_PLAINTEXT, + $altBodyEncoding + ); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= static::$LE; + $body .= $this->getBoundary( + $this->boundary[1], + $bodyCharSet, + static::CONTENT_TYPE_TEXT_HTML, + $bodyEncoding + ); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + if (!empty($this->Ical)) { + $method = static::ICAL_METHOD_REQUEST; + foreach (static::$IcalMethods as $imethod) { + if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) { + $method = $imethod; + break; + } + } + $body .= $this->getBoundary( + $this->boundary[1], + '', + static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method, + '' + ); + $body .= $this->encodeString($this->Ical, $this->Encoding); + $body .= static::$LE; + } + $body .= $this->endBoundary($this->boundary[1]); + break; + case 'alt_inline': + $body .= $mimepre; + $body .= $this->getBoundary( + $this->boundary[1], + $altBodyCharSet, + static::CONTENT_TYPE_PLAINTEXT, + $altBodyEncoding + ); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= static::$LE; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';'); + $body .= $this->textLine(' boundary="' . $this->boundary[2] . '";'); + $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"'); + $body .= static::$LE; + $body .= $this->getBoundary( + $this->boundary[2], + $bodyCharSet, + static::CONTENT_TYPE_TEXT_HTML, + $bodyEncoding + ); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('inline', $this->boundary[2]); + $body .= static::$LE; + $body .= $this->endBoundary($this->boundary[1]); + break; + case 'alt_attach': + $body .= $mimepre; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';'); + $body .= $this->textLine(' boundary="' . $this->boundary[2] . '"'); + $body .= static::$LE; + $body .= $this->getBoundary( + $this->boundary[2], + $altBodyCharSet, + static::CONTENT_TYPE_PLAINTEXT, + $altBodyEncoding + ); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= static::$LE; + $body .= $this->getBoundary( + $this->boundary[2], + $bodyCharSet, + static::CONTENT_TYPE_TEXT_HTML, + $bodyEncoding + ); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + if (!empty($this->Ical)) { + $method = static::ICAL_METHOD_REQUEST; + foreach (static::$IcalMethods as $imethod) { + if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) { + $method = $imethod; + break; + } + } + $body .= $this->getBoundary( + $this->boundary[2], + '', + static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method, + '' + ); + $body .= $this->encodeString($this->Ical, $this->Encoding); + } + $body .= $this->endBoundary($this->boundary[2]); + $body .= static::$LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + case 'alt_inline_attach': + $body .= $mimepre; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';'); + $body .= $this->textLine(' boundary="' . $this->boundary[2] . '"'); + $body .= static::$LE; + $body .= $this->getBoundary( + $this->boundary[2], + $altBodyCharSet, + static::CONTENT_TYPE_PLAINTEXT, + $altBodyEncoding + ); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= static::$LE; + $body .= $this->textLine('--' . $this->boundary[2]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';'); + $body .= $this->textLine(' boundary="' . $this->boundary[3] . '";'); + $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"'); + $body .= static::$LE; + $body .= $this->getBoundary( + $this->boundary[3], + $bodyCharSet, + static::CONTENT_TYPE_TEXT_HTML, + $bodyEncoding + ); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('inline', $this->boundary[3]); + $body .= static::$LE; + $body .= $this->endBoundary($this->boundary[2]); + $body .= static::$LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + default: + // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types + //Reset the `Encoding` property in case we changed it for line length reasons + $this->Encoding = $bodyEncoding; + $body .= $this->encodeString($this->Body, $this->Encoding); + break; + } + + if ($this->isError()) { + $body = ''; + if ($this->exceptions) { + throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL); + } + } elseif ($this->sign_key_file) { + try { + if (!defined('PKCS7_TEXT')) { + throw new Exception($this->lang('extension_missing') . 'openssl'); + } + + $file = tempnam(sys_get_temp_dir(), 'srcsign'); + $signed = tempnam(sys_get_temp_dir(), 'mailsign'); + file_put_contents($file, $body); + + //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197 + if (empty($this->sign_extracerts_file)) { + $sign = @openssl_pkcs7_sign( + $file, + $signed, + 'file://' . realpath($this->sign_cert_file), + ['file://' . realpath($this->sign_key_file), $this->sign_key_pass], + [] + ); + } else { + $sign = @openssl_pkcs7_sign( + $file, + $signed, + 'file://' . realpath($this->sign_cert_file), + ['file://' . realpath($this->sign_key_file), $this->sign_key_pass], + [], + PKCS7_DETACHED, + $this->sign_extracerts_file + ); + } + + @unlink($file); + if ($sign) { + $body = file_get_contents($signed); + @unlink($signed); + //The message returned by openssl contains both headers and body, so need to split them up + $parts = explode("\n\n", $body, 2); + $this->MIMEHeader .= $parts[0] . static::$LE . static::$LE; + $body = $parts[1]; + } else { + @unlink($signed); + throw new Exception($this->lang('signing') . openssl_error_string()); + } + } catch (Exception $exc) { + $body = ''; + if ($this->exceptions) { + throw $exc; + } + } + } + + return $body; + } + + /** + * Return the start of a message boundary. + * + * @param string $boundary + * @param string $charSet + * @param string $contentType + * @param string $encoding + * + * @return string + */ + protected function getBoundary($boundary, $charSet, $contentType, $encoding) + { + $result = ''; + if ('' === $charSet) { + $charSet = $this->CharSet; + } + if ('' === $contentType) { + $contentType = $this->ContentType; + } + if ('' === $encoding) { + $encoding = $this->Encoding; + } + $result .= $this->textLine('--' . $boundary); + $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet); + $result .= static::$LE; + // RFC1341 part 5 says 7bit is assumed if not specified + if (static::ENCODING_7BIT !== $encoding) { + $result .= $this->headerLine('Content-Transfer-Encoding', $encoding); + } + $result .= static::$LE; + + return $result; + } + + /** + * Return the end of a message boundary. + * + * @param string $boundary + * + * @return string + */ + protected function endBoundary($boundary) + { + return static::$LE . '--' . $boundary . '--' . static::$LE; + } + + /** + * Set the message type. + * PHPMailer only supports some preset message types, not arbitrary MIME structures. + */ + protected function setMessageType() + { + $type = []; + if ($this->alternativeExists()) { + $type[] = 'alt'; + } + if ($this->inlineImageExists()) { + $type[] = 'inline'; + } + if ($this->attachmentExists()) { + $type[] = 'attach'; + } + $this->message_type = implode('_', $type); + if ('' === $this->message_type) { + //The 'plain' message_type refers to the message having a single body element, not that it is plain-text + $this->message_type = 'plain'; + } + } + + /** + * Format a header line. + * + * @param string $name + * @param string|int $value + * + * @return string + */ + public function headerLine($name, $value) + { + return $name . ': ' . $value . static::$LE; + } + + /** + * Return a formatted mail line. + * + * @param string $value + * + * @return string + */ + public function textLine($value) + { + return $value . static::$LE; + } + + /** + * Add an attachment from a path on the filesystem. + * Never use a user-supplied path to a file! + * Returns false if the file could not be found or read. + * Explicitly *does not* support passing URLs; PHPMailer is not an HTTP client. + * If you need to do that, fetch the resource yourself and pass it in via a local file or string. + * + * @param string $path Path to the attachment + * @param string $name Overrides the attachment name + * @param string $encoding File encoding (see $Encoding) + * @param string $type File extension (MIME) type + * @param string $disposition Disposition to use + * + * @throws Exception + * + * @return bool + */ + public function addAttachment( + $path, + $name = '', + $encoding = self::ENCODING_BASE64, + $type = '', + $disposition = 'attachment' + ) { + try { + if (!static::isPermittedPath($path) || !@is_file($path)) { + throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE); + } + + // If a MIME type is not specified, try to work it out from the file name + if ('' === $type) { + $type = static::filenameToType($path); + } + + $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME); + if ('' === $name) { + $name = $filename; + } + + if (!$this->validateEncoding($encoding)) { + throw new Exception($this->lang('encoding') . $encoding); + } + + $this->attachment[] = [ + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => $disposition, + 7 => $name, + ]; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + + return true; + } + + /** + * Return the array of attachments. + * + * @return array + */ + public function getAttachments() + { + return $this->attachment; + } + + /** + * Attach all file, string, and binary attachments to the message. + * Returns an empty string on failure. + * + * @param string $disposition_type + * @param string $boundary + * + * @throws Exception + * + * @return string + */ + protected function attachAll($disposition_type, $boundary) + { + // Return text of body + $mime = []; + $cidUniq = []; + $incl = []; + + // Add all attachments + foreach ($this->attachment as $attachment) { + // Check if it is a valid disposition_filter + if ($attachment[6] === $disposition_type) { + // Check for string attachment + $string = ''; + $path = ''; + $bString = $attachment[5]; + if ($bString) { + $string = $attachment[0]; + } else { + $path = $attachment[0]; + } + + $inclhash = hash('sha256', serialize($attachment)); + if (in_array($inclhash, $incl, true)) { + continue; + } + $incl[] = $inclhash; + $name = $attachment[2]; + $encoding = $attachment[3]; + $type = $attachment[4]; + $disposition = $attachment[6]; + $cid = $attachment[7]; + if ('inline' === $disposition && array_key_exists($cid, $cidUniq)) { + continue; + } + $cidUniq[$cid] = true; + + $mime[] = sprintf('--%s%s', $boundary, static::$LE); + //Only include a filename property if we have one + if (!empty($name)) { + $mime[] = sprintf( + 'Content-Type: %s; name="%s"%s', + $type, + $this->encodeHeader($this->secureHeader($name)), + static::$LE + ); + } else { + $mime[] = sprintf( + 'Content-Type: %s%s', + $type, + static::$LE + ); + } + // RFC1341 part 5 says 7bit is assumed if not specified + if (static::ENCODING_7BIT !== $encoding) { + $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, static::$LE); + } + + //Only set Content-IDs on inline attachments + if ((string) $cid !== '' && $disposition === 'inline') { + $mime[] = 'Content-ID: <' . $this->encodeHeader($this->secureHeader($cid)) . '>' . static::$LE; + } + + // If a filename contains any of these chars, it should be quoted, + // but not otherwise: RFC2183 & RFC2045 5.1 + // Fixes a warning in IETF's msglint MIME checker + // Allow for bypassing the Content-Disposition header totally + if (!empty($disposition)) { + $encoded_name = $this->encodeHeader($this->secureHeader($name)); + if (preg_match('/[ ()<>@,;:"\/\[\]?=]/', $encoded_name)) { + $mime[] = sprintf( + 'Content-Disposition: %s; filename="%s"%s', + $disposition, + $encoded_name, + static::$LE . static::$LE + ); + } elseif (!empty($encoded_name)) { + $mime[] = sprintf( + 'Content-Disposition: %s; filename=%s%s', + $disposition, + $encoded_name, + static::$LE . static::$LE + ); + } else { + $mime[] = sprintf( + 'Content-Disposition: %s%s', + $disposition, + static::$LE . static::$LE + ); + } + } else { + $mime[] = static::$LE; + } + + // Encode as string attachment + if ($bString) { + $mime[] = $this->encodeString($string, $encoding); + } else { + $mime[] = $this->encodeFile($path, $encoding); + } + if ($this->isError()) { + return ''; + } + $mime[] = static::$LE; + } + } + + $mime[] = sprintf('--%s--%s', $boundary, static::$LE); + + return implode('', $mime); + } + + /** + * Encode a file attachment in requested format. + * Returns an empty string on failure. + * + * @param string $path The full path to the file + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * + * @return string + */ + protected function encodeFile($path, $encoding = self::ENCODING_BASE64) + { + try { + if (!static::isPermittedPath($path) || !file_exists($path)) { + throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE); + } + $file_buffer = file_get_contents($path); + if (false === $file_buffer) { + throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE); + } + $file_buffer = $this->encodeString($file_buffer, $encoding); + + return $file_buffer; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + + return ''; + } + } + + /** + * Encode a string in requested format. + * Returns an empty string on failure. + * + * @param string $str The text to encode + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * + * @throws Exception + * + * @return string + */ + public function encodeString($str, $encoding = self::ENCODING_BASE64) + { + $encoded = ''; + switch (strtolower($encoding)) { + case static::ENCODING_BASE64: + $encoded = chunk_split( + base64_encode($str), + static::STD_LINE_LENGTH, + static::$LE + ); + break; + case static::ENCODING_7BIT: + case static::ENCODING_8BIT: + $encoded = static::normalizeBreaks($str); + // Make sure it ends with a line break + if (substr($encoded, -(strlen(static::$LE))) !== static::$LE) { + $encoded .= static::$LE; + } + break; + case static::ENCODING_BINARY: + $encoded = $str; + break; + case static::ENCODING_QUOTED_PRINTABLE: + $encoded = $this->encodeQP($str); + break; + default: + $this->setError($this->lang('encoding') . $encoding); + if ($this->exceptions) { + throw new Exception($this->lang('encoding') . $encoding); + } + break; + } + + return $encoded; + } + + /** + * Encode a header value (not including its label) optimally. + * Picks shortest of Q, B, or none. Result includes folding if needed. + * See RFC822 definitions for phrase, comment and text positions. + * + * @param string $str The header value to encode + * @param string $position What context the string will be used in + * + * @return string + */ + public function encodeHeader($str, $position = 'text') + { + $matchcount = 0; + switch (strtolower($position)) { + case 'phrase': + if (!preg_match('/[\200-\377]/', $str)) { + // Can't use addslashes as we don't know the value of magic_quotes_sybase + $encoded = addcslashes($str, "\0..\37\177\\\""); + if (($str === $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) { + return $encoded; + } + + return "\"$encoded\""; + } + $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches); + break; + /* @noinspection PhpMissingBreakStatementInspection */ + case 'comment': + $matchcount = preg_match_all('/[()"]/', $str, $matches); + //fallthrough + case 'text': + default: + $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches); + break; + } + + if ($this->has8bitChars($str)) { + $charset = $this->CharSet; + } else { + $charset = static::CHARSET_ASCII; + } + + // Q/B encoding adds 8 chars and the charset ("` =??[QB]??=`"). + $overhead = 8 + strlen($charset); + + if ('mail' === $this->Mailer) { + $maxlen = static::MAIL_MAX_LINE_LENGTH - $overhead; + } else { + $maxlen = static::STD_LINE_LENGTH - $overhead; + } + + // Select the encoding that produces the shortest output and/or prevents corruption. + if ($matchcount > strlen($str) / 3) { + // More than 1/3 of the content needs encoding, use B-encode. + $encoding = 'B'; + } elseif ($matchcount > 0) { + // Less than 1/3 of the content needs encoding, use Q-encode. + $encoding = 'Q'; + } elseif (strlen($str) > $maxlen) { + // No encoding needed, but value exceeds max line length, use Q-encode to prevent corruption. + $encoding = 'Q'; + } else { + // No reformatting needed + $encoding = false; + } + + switch ($encoding) { + case 'B': + if ($this->hasMultiBytes($str)) { + // Use a custom function which correctly encodes and wraps long + // multibyte strings without breaking lines within a character + $encoded = $this->base64EncodeWrapMB($str, "\n"); + } else { + $encoded = base64_encode($str); + $maxlen -= $maxlen % 4; + $encoded = trim(chunk_split($encoded, $maxlen, "\n")); + } + $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded); + break; + case 'Q': + $encoded = $this->encodeQ($str, $position); + $encoded = $this->wrapText($encoded, $maxlen, true); + $encoded = str_replace('=' . static::$LE, "\n", trim($encoded)); + $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded); + break; + default: + return $str; + } + + return trim(static::normalizeBreaks($encoded)); + } + + /** + * Check if a string contains multi-byte characters. + * + * @param string $str multi-byte text to wrap encode + * + * @return bool + */ + public function hasMultiBytes($str) + { + if (function_exists('mb_strlen')) { + return strlen($str) > mb_strlen($str, $this->CharSet); + } + + // Assume no multibytes (we can't handle without mbstring functions anyway) + return false; + } + + /** + * Does a string contain any 8-bit chars (in any charset)? + * + * @param string $text + * + * @return bool + */ + public function has8bitChars($text) + { + return (bool) preg_match('/[\x80-\xFF]/', $text); + } + + /** + * Encode and wrap long multibyte strings for mail headers + * without breaking lines within a character. + * Adapted from a function by paravoid. + * + * @see http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283 + * + * @param string $str multi-byte text to wrap encode + * @param string $linebreak string to use as linefeed/end-of-line + * + * @return string + */ + public function base64EncodeWrapMB($str, $linebreak = null) + { + $start = '=?' . $this->CharSet . '?B?'; + $end = '?='; + $encoded = ''; + if (null === $linebreak) { + $linebreak = static::$LE; + } + + $mb_length = mb_strlen($str, $this->CharSet); + // Each line must have length <= 75, including $start and $end + $length = 75 - strlen($start) - strlen($end); + // Average multi-byte ratio + $ratio = $mb_length / strlen($str); + // Base64 has a 4:3 ratio + $avgLength = floor($length * $ratio * .75); + + $offset = 0; + for ($i = 0; $i < $mb_length; $i += $offset) { + $lookBack = 0; + do { + $offset = $avgLength - $lookBack; + $chunk = mb_substr($str, $i, $offset, $this->CharSet); + $chunk = base64_encode($chunk); + ++$lookBack; + } while (strlen($chunk) > $length); + $encoded .= $chunk . $linebreak; + } + + // Chomp the last linefeed + return substr($encoded, 0, -strlen($linebreak)); + } + + /** + * Encode a string in quoted-printable format. + * According to RFC2045 section 6.7. + * + * @param string $string The text to encode + * + * @return string + */ + public function encodeQP($string) + { + return static::normalizeBreaks(quoted_printable_encode($string)); + } + + /** + * Encode a string using Q encoding. + * + * @see http://tools.ietf.org/html/rfc2047#section-4.2 + * + * @param string $str the text to encode + * @param string $position Where the text is going to be used, see the RFC for what that means + * + * @return string + */ + public function encodeQ($str, $position = 'text') + { + // There should not be any EOL in the string + $pattern = ''; + $encoded = str_replace(["\r", "\n"], '', $str); + switch (strtolower($position)) { + case 'phrase': + // RFC 2047 section 5.3 + $pattern = '^A-Za-z0-9!*+\/ -'; + break; + /* + * RFC 2047 section 5.2. + * Build $pattern without including delimiters and [] + */ + /* @noinspection PhpMissingBreakStatementInspection */ + case 'comment': + $pattern = '\(\)"'; + /* Intentional fall through */ + case 'text': + default: + // RFC 2047 section 5.1 + // Replace every high ascii, control, =, ? and _ characters + $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern; + break; + } + $matches = []; + if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) { + // If the string contains an '=', make sure it's the first thing we replace + // so as to avoid double-encoding + $eqkey = array_search('=', $matches[0], true); + if (false !== $eqkey) { + unset($matches[0][$eqkey]); + array_unshift($matches[0], '='); + } + foreach (array_unique($matches[0]) as $char) { + $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded); + } + } + // Replace spaces with _ (more readable than =20) + // RFC 2047 section 4.2(2) + return str_replace(' ', '_', $encoded); + } + + /** + * Add a string or binary attachment (non-filesystem). + * This method can be used to attach ascii or binary data, + * such as a BLOB record from a database. + * + * @param string $string String attachment data + * @param string $filename Name of the attachment + * @param string $encoding File encoding (see $Encoding) + * @param string $type File extension (MIME) type + * @param string $disposition Disposition to use + * + * @throws Exception + * + * @return bool True on successfully adding an attachment + */ + public function addStringAttachment( + $string, + $filename, + $encoding = self::ENCODING_BASE64, + $type = '', + $disposition = 'attachment' + ) { + try { + // If a MIME type is not specified, try to work it out from the file name + if ('' === $type) { + $type = static::filenameToType($filename); + } + + if (!$this->validateEncoding($encoding)) { + throw new Exception($this->lang('encoding') . $encoding); + } + + // Append to $attachment array + $this->attachment[] = [ + 0 => $string, + 1 => $filename, + 2 => static::mb_pathinfo($filename, PATHINFO_BASENAME), + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => $disposition, + 7 => 0, + ]; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + + return true; + } + + /** + * Add an embedded (inline) attachment from a file. + * This can include images, sounds, and just about any other document type. + * These differ from 'regular' attachments in that they are intended to be + * displayed inline with the message, not just attached for download. + * This is used in HTML messages that embed the images + * the HTML refers to using the $cid value. + * Never use a user-supplied path to a file! + * + * @param string $path Path to the attachment + * @param string $cid Content ID of the attachment; Use this to reference + * the content when using an embedded image in HTML + * @param string $name Overrides the attachment name + * @param string $encoding File encoding (see $Encoding) + * @param string $type File MIME type + * @param string $disposition Disposition to use + * + * @throws Exception + * + * @return bool True on successfully adding an attachment + */ + public function addEmbeddedImage( + $path, + $cid, + $name = '', + $encoding = self::ENCODING_BASE64, + $type = '', + $disposition = 'inline' + ) { + try { + if (!static::isPermittedPath($path) || !@is_file($path)) { + throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE); + } + + // If a MIME type is not specified, try to work it out from the file name + if ('' === $type) { + $type = static::filenameToType($path); + } + + if (!$this->validateEncoding($encoding)) { + throw new Exception($this->lang('encoding') . $encoding); + } + + $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME); + if ('' === $name) { + $name = $filename; + } + + // Append to $attachment array + $this->attachment[] = [ + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => $disposition, + 7 => $cid, + ]; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + + return true; + } + + /** + * Add an embedded stringified attachment. + * This can include images, sounds, and just about any other document type. + * If your filename doesn't contain an extension, be sure to set the $type to an appropriate MIME type. + * + * @param string $string The attachment binary data + * @param string $cid Content ID of the attachment; Use this to reference + * the content when using an embedded image in HTML + * @param string $name A filename for the attachment. If this contains an extension, + * PHPMailer will attempt to set a MIME type for the attachment. + * For example 'file.jpg' would get an 'image/jpeg' MIME type. + * @param string $encoding File encoding (see $Encoding), defaults to 'base64' + * @param string $type MIME type - will be used in preference to any automatically derived type + * @param string $disposition Disposition to use + * + * @throws Exception + * + * @return bool True on successfully adding an attachment + */ + public function addStringEmbeddedImage( + $string, + $cid, + $name = '', + $encoding = self::ENCODING_BASE64, + $type = '', + $disposition = 'inline' + ) { + try { + // If a MIME type is not specified, try to work it out from the name + if ('' === $type && !empty($name)) { + $type = static::filenameToType($name); + } + + if (!$this->validateEncoding($encoding)) { + throw new Exception($this->lang('encoding') . $encoding); + } + + // Append to $attachment array + $this->attachment[] = [ + 0 => $string, + 1 => $name, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => $disposition, + 7 => $cid, + ]; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + + return true; + } + + /** + * Validate encodings. + * + * @param string $encoding + * + * @return bool + */ + protected function validateEncoding($encoding) + { + return in_array( + $encoding, + [ + self::ENCODING_7BIT, + self::ENCODING_QUOTED_PRINTABLE, + self::ENCODING_BASE64, + self::ENCODING_8BIT, + self::ENCODING_BINARY, + ], + true + ); + } + + /** + * Check if an embedded attachment is present with this cid. + * + * @param string $cid + * + * @return bool + */ + protected function cidExists($cid) + { + foreach ($this->attachment as $attachment) { + if ('inline' === $attachment[6] && $cid === $attachment[7]) { + return true; + } + } + + return false; + } + + /** + * Check if an inline attachment is present. + * + * @return bool + */ + public function inlineImageExists() + { + foreach ($this->attachment as $attachment) { + if ('inline' === $attachment[6]) { + return true; + } + } + + return false; + } + + /** + * Check if an attachment (non-inline) is present. + * + * @return bool + */ + public function attachmentExists() + { + foreach ($this->attachment as $attachment) { + if ('attachment' === $attachment[6]) { + return true; + } + } + + return false; + } + + /** + * Check if this message has an alternative body set. + * + * @return bool + */ + public function alternativeExists() + { + return !empty($this->AltBody); + } + + /** + * Clear queued addresses of given kind. + * + * @param string $kind 'to', 'cc', or 'bcc' + */ + public function clearQueuedAddresses($kind) + { + $this->RecipientsQueue = array_filter( + $this->RecipientsQueue, + static function ($params) use ($kind) { + return $params[0] !== $kind; + } + ); + } + + /** + * Clear all To recipients. + */ + public function clearAddresses() + { + foreach ($this->to as $to) { + unset($this->all_recipients[strtolower($to[0])]); + } + $this->to = []; + $this->clearQueuedAddresses('to'); + } + + /** + * Clear all CC recipients. + */ + public function clearCCs() + { + foreach ($this->cc as $cc) { + unset($this->all_recipients[strtolower($cc[0])]); + } + $this->cc = []; + $this->clearQueuedAddresses('cc'); + } + + /** + * Clear all BCC recipients. + */ + public function clearBCCs() + { + foreach ($this->bcc as $bcc) { + unset($this->all_recipients[strtolower($bcc[0])]); + } + $this->bcc = []; + $this->clearQueuedAddresses('bcc'); + } + + /** + * Clear all ReplyTo recipients. + */ + public function clearReplyTos() + { + $this->ReplyTo = []; + $this->ReplyToQueue = []; + } + + /** + * Clear all recipient types. + */ + public function clearAllRecipients() + { + $this->to = []; + $this->cc = []; + $this->bcc = []; + $this->all_recipients = []; + $this->RecipientsQueue = []; + } + + /** + * Clear all filesystem, string, and binary attachments. + */ + public function clearAttachments() + { + $this->attachment = []; + } + + /** + * Clear all custom headers. + */ + public function clearCustomHeaders() + { + $this->CustomHeader = []; + } + + /** + * Add an error message to the error container. + * + * @param string $msg + */ + protected function setError($msg) + { + ++$this->error_count; + if ('smtp' === $this->Mailer && null !== $this->smtp) { + $lasterror = $this->smtp->getError(); + if (!empty($lasterror['error'])) { + $msg .= $this->lang('smtp_error') . $lasterror['error']; + if (!empty($lasterror['detail'])) { + $msg .= ' Detail: ' . $lasterror['detail']; + } + if (!empty($lasterror['smtp_code'])) { + $msg .= ' SMTP code: ' . $lasterror['smtp_code']; + } + if (!empty($lasterror['smtp_code_ex'])) { + $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex']; + } + } + } + $this->ErrorInfo = $msg; + } + + /** + * Return an RFC 822 formatted date. + * + * @return string + */ + public static function rfcDate() + { + // Set the time zone to whatever the default is to avoid 500 errors + // Will default to UTC if it's not set properly in php.ini + date_default_timezone_set(@date_default_timezone_get()); + + return date('D, j M Y H:i:s O'); + } + + /** + * Get the server hostname. + * Returns 'localhost.localdomain' if unknown. + * + * @return string + */ + protected function serverHostname() + { + $result = ''; + if (!empty($this->Hostname)) { + $result = $this->Hostname; + } elseif (isset($_SERVER) && array_key_exists('SERVER_NAME', $_SERVER)) { + $result = $_SERVER['SERVER_NAME']; + } elseif (function_exists('gethostname') && gethostname() !== false) { + $result = gethostname(); + } elseif (php_uname('n') !== false) { + $result = php_uname('n'); + } + if (!static::isValidHost($result)) { + return 'localhost.localdomain'; + } + + return $result; + } + + /** + * Validate whether a string contains a valid value to use as a hostname or IP address. + * IPv6 addresses must include [], e.g. `[::1]`, not just `::1`. + * + * @param string $host The host name or IP address to check + * + * @return bool + */ + public static function isValidHost($host) + { + //Simple syntax limits + if (empty($host) + || !is_string($host) + || strlen($host) > 256 + ) { + return false; + } + //Looks like a bracketed IPv6 address + if (trim($host, '[]') !== $host) { + return (bool) filter_var(trim($host, '[]'), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); + } + //If removing all the dots results in a numeric string, it must be an IPv4 address. + //Need to check this first because otherwise things like `999.0.0.0` are considered valid host names + if (is_numeric(str_replace('.', '', $host))) { + //Is it a valid IPv4 address? + return (bool) filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); + } + if (filter_var('http://' . $host, FILTER_VALIDATE_URL)) { + //Is it a syntactically valid hostname? + return true; + } + + return false; + } + + /** + * Get an error message in the current language. + * + * @param string $key + * + * @return string + */ + protected function lang($key) + { + if (count($this->language) < 1) { + $this->setLanguage(); // set the default language + } + + if (array_key_exists($key, $this->language)) { + if ('smtp_connect_failed' === $key) { + //Include a link to troubleshooting docs on SMTP connection failure + //this is by far the biggest cause of support questions + //but it's usually not PHPMailer's fault. + return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting'; + } + + return $this->language[$key]; + } + + //Return the key as a fallback + return $key; + } + + /** + * Check if an error occurred. + * + * @return bool True if an error did occur + */ + public function isError() + { + return $this->error_count > 0; + } + + /** + * Add a custom header. + * $name value can be overloaded to contain + * both header name and value (name:value). + * + * @param string $name Custom header name + * @param string|null $value Header value + */ + public function addCustomHeader($name, $value = null) + { + if (null === $value) { + // Value passed in as name:value + $this->CustomHeader[] = explode(':', $name, 2); + } else { + $this->CustomHeader[] = [$name, $value]; + } + } + + /** + * Returns all custom headers. + * + * @return array + */ + public function getCustomHeaders() + { + return $this->CustomHeader; + } + + /** + * Create a message body from an HTML string. + * Automatically inlines images and creates a plain-text version by converting the HTML, + * overwriting any existing values in Body and AltBody. + * Do not source $message content from user input! + * $basedir is prepended when handling relative URLs, e.g. and must not be empty + * will look for an image file in $basedir/images/a.png and convert it to inline. + * If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email) + * Converts data-uri images into embedded attachments. + * If you don't want to apply these transformations to your HTML, just set Body and AltBody directly. + * + * @param string $message HTML message string + * @param string $basedir Absolute path to a base directory to prepend to relative paths to images + * @param bool|callable $advanced Whether to use the internal HTML to text converter + * or your own custom converter @return string $message The transformed message Body + * + * @throws Exception + * + * @see PHPMailer::html2text() + */ + public function msgHTML($message, $basedir = '', $advanced = false) + { + preg_match_all('/(? 1 && '/' !== substr($basedir, -1)) { + // Ensure $basedir has a trailing / + $basedir .= '/'; + } + foreach ($images[2] as $imgindex => $url) { + // Convert data URIs into embedded images + //e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" + if (preg_match('#^data:(image/(?:jpe?g|gif|png));?(base64)?,(.+)#', $url, $match)) { + if (count($match) === 4 && static::ENCODING_BASE64 === $match[2]) { + $data = base64_decode($match[3]); + } elseif ('' === $match[2]) { + $data = rawurldecode($match[3]); + } else { + //Not recognised so leave it alone + continue; + } + //Hash the decoded data, not the URL, so that the same data-URI image used in multiple places + //will only be embedded once, even if it used a different encoding + $cid = substr(hash('sha256', $data), 0, 32) . '@phpmailer.0'; // RFC2392 S 2 + + if (!$this->cidExists($cid)) { + $this->addStringEmbeddedImage( + $data, + $cid, + 'embed' . $imgindex, + static::ENCODING_BASE64, + $match[1] + ); + } + $message = str_replace( + $images[0][$imgindex], + $images[1][$imgindex] . '="cid:' . $cid . '"', + $message + ); + continue; + } + if (// Only process relative URLs if a basedir is provided (i.e. no absolute local paths) + !empty($basedir) + // Ignore URLs containing parent dir traversal (..) + && (strpos($url, '..') === false) + // Do not change urls that are already inline images + && 0 !== strpos($url, 'cid:') + // Do not change absolute URLs, including anonymous protocol + && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url) + ) { + $filename = static::mb_pathinfo($url, PATHINFO_BASENAME); + $directory = dirname($url); + if ('.' === $directory) { + $directory = ''; + } + // RFC2392 S 2 + $cid = substr(hash('sha256', $url), 0, 32) . '@phpmailer.0'; + if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) { + $basedir .= '/'; + } + if (strlen($directory) > 1 && '/' !== substr($directory, -1)) { + $directory .= '/'; + } + if ($this->addEmbeddedImage( + $basedir . $directory . $filename, + $cid, + $filename, + static::ENCODING_BASE64, + static::_mime_types((string) static::mb_pathinfo($filename, PATHINFO_EXTENSION)) + ) + ) { + $message = preg_replace( + '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui', + $images[1][$imgindex] . '="cid:' . $cid . '"', + $message + ); + } + } + } + } + $this->isHTML(); + // Convert all message body line breaks to LE, makes quoted-printable encoding work much better + $this->Body = static::normalizeBreaks($message); + $this->AltBody = static::normalizeBreaks($this->html2text($message, $advanced)); + if (!$this->alternativeExists()) { + $this->AltBody = 'This is an HTML-only message. To view it, activate HTML in your email application.' + . static::$LE; + } + + return $this->Body; + } + + /** + * Convert an HTML string into plain text. + * This is used by msgHTML(). + * Note - older versions of this function used a bundled advanced converter + * which was removed for license reasons in #232. + * Example usage: + * + * ```php + * // Use default conversion + * $plain = $mail->html2text($html); + * // Use your own custom converter + * $plain = $mail->html2text($html, function($html) { + * $converter = new MyHtml2text($html); + * return $converter->get_text(); + * }); + * ``` + * + * @param string $html The HTML text to convert + * @param bool|callable $advanced Any boolean value to use the internal converter, + * or provide your own callable for custom conversion + * + * @return string + */ + public function html2text($html, $advanced = false) + { + if (is_callable($advanced)) { + return $advanced($html); + } + + return html_entity_decode( + trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))), + ENT_QUOTES, + $this->CharSet + ); + } + + /** + * Get the MIME type for a file extension. + * + * @param string $ext File extension + * + * @return string MIME type of file + */ + public static function _mime_types($ext = '') + { + $mimes = [ + 'xl' => 'application/excel', + 'js' => 'application/javascript', + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'bin' => 'application/macbinary', + 'doc' => 'application/msword', + 'word' => 'application/msword', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'class' => 'application/octet-stream', + 'dll' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'exe' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'psd' => 'application/octet-stream', + 'sea' => 'application/octet-stream', + 'so' => 'application/octet-stream', + 'oda' => 'application/oda', + 'pdf' => 'application/pdf', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'mif' => 'application/vnd.mif', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/vnd.ms-powerpoint', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'gtar' => 'application/x-gtar', + 'php3' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'php' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => 'application/x-tar', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'zip' => 'application/zip', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'm4a' => 'audio/mp4', + 'mpga' => 'audio/mpeg', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'ram' => 'audio/x-pn-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'ra' => 'audio/x-realaudio', + 'wav' => 'audio/x-wav', + 'mka' => 'audio/x-matroska', + 'bmp' => 'image/bmp', + 'gif' => 'image/gif', + 'jpeg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'png' => 'image/png', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'webp' => 'image/webp', + 'heif' => 'image/heif', + 'heifs' => 'image/heif-sequence', + 'heic' => 'image/heic', + 'heics' => 'image/heic-sequence', + 'eml' => 'message/rfc822', + 'css' => 'text/css', + 'html' => 'text/html', + 'htm' => 'text/html', + 'shtml' => 'text/html', + 'log' => 'text/plain', + 'text' => 'text/plain', + 'txt' => 'text/plain', + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'vcf' => 'text/vcard', + 'vcard' => 'text/vcard', + 'ics' => 'text/calendar', + 'xml' => 'text/xml', + 'xsl' => 'text/xml', + 'wmv' => 'video/x-ms-wmv', + 'mpeg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mp4' => 'video/mp4', + 'm4v' => 'video/mp4', + 'mov' => 'video/quicktime', + 'qt' => 'video/quicktime', + 'rv' => 'video/vnd.rn-realvideo', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie', + 'webm' => 'video/webm', + 'mkv' => 'video/x-matroska', + ]; + $ext = strtolower($ext); + if (array_key_exists($ext, $mimes)) { + return $mimes[$ext]; + } + + return 'application/octet-stream'; + } + + /** + * Map a file name to a MIME type. + * Defaults to 'application/octet-stream', i.e.. arbitrary binary data. + * + * @param string $filename A file name or full path, does not need to exist as a file + * + * @return string + */ + public static function filenameToType($filename) + { + // In case the path is a URL, strip any query string before getting extension + $qpos = strpos($filename, '?'); + if (false !== $qpos) { + $filename = substr($filename, 0, $qpos); + } + $ext = static::mb_pathinfo($filename, PATHINFO_EXTENSION); + + return static::_mime_types($ext); + } + + /** + * Multi-byte-safe pathinfo replacement. + * Drop-in replacement for pathinfo(), but multibyte- and cross-platform-safe. + * + * @see http://www.php.net/manual/en/function.pathinfo.php#107461 + * + * @param string $path A filename or path, does not need to exist as a file + * @param int|string $options Either a PATHINFO_* constant, + * or a string name to return only the specified piece + * + * @return string|array + */ + public static function mb_pathinfo($path, $options = null) + { + $ret = ['dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '']; + $pathinfo = []; + if (preg_match('#^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^.\\\\/]+?)|))[\\\\/.]*$#m', $path, $pathinfo)) { + if (array_key_exists(1, $pathinfo)) { + $ret['dirname'] = $pathinfo[1]; + } + if (array_key_exists(2, $pathinfo)) { + $ret['basename'] = $pathinfo[2]; + } + if (array_key_exists(5, $pathinfo)) { + $ret['extension'] = $pathinfo[5]; + } + if (array_key_exists(3, $pathinfo)) { + $ret['filename'] = $pathinfo[3]; + } + } + switch ($options) { + case PATHINFO_DIRNAME: + case 'dirname': + return $ret['dirname']; + case PATHINFO_BASENAME: + case 'basename': + return $ret['basename']; + case PATHINFO_EXTENSION: + case 'extension': + return $ret['extension']; + case PATHINFO_FILENAME: + case 'filename': + return $ret['filename']; + default: + return $ret; + } + } + + /** + * Set or reset instance properties. + * You should avoid this function - it's more verbose, less efficient, more error-prone and + * harder to debug than setting properties directly. + * Usage Example: + * `$mail->set('SMTPSecure', static::ENCRYPTION_STARTTLS);` + * is the same as: + * `$mail->SMTPSecure = static::ENCRYPTION_STARTTLS;`. + * + * @param string $name The property name to set + * @param mixed $value The value to set the property to + * + * @return bool + */ + public function set($name, $value = '') + { + if (property_exists($this, $name)) { + $this->$name = $value; + + return true; + } + $this->setError($this->lang('variable_set') . $name); + + return false; + } + + /** + * Strip newlines to prevent header injection. + * + * @param string $str + * + * @return string + */ + public function secureHeader($str) + { + return trim(str_replace(["\r", "\n"], '', $str)); + } + + /** + * Normalize line breaks in a string. + * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format. + * Defaults to CRLF (for message bodies) and preserves consecutive breaks. + * + * @param string $text + * @param string $breaktype What kind of line break to use; defaults to static::$LE + * + * @return string + */ + public static function normalizeBreaks($text, $breaktype = null) + { + if (null === $breaktype) { + $breaktype = static::$LE; + } + // Normalise to \n + $text = str_replace(["\r\n", "\r"], "\n", $text); + // Now convert LE as needed + if ("\n" !== $breaktype) { + $text = str_replace("\n", $breaktype, $text); + } + + return $text; + } + + /** + * Return the current line break format string. + * + * @return string + */ + public static function getLE() + { + return static::$LE; + } + + /** + * Set the line break format string, e.g. "\r\n". + * + * @param string $le + */ + protected static function setLE($le) + { + static::$LE = $le; + } + + /** + * Set the public and private key files and password for S/MIME signing. + * + * @param string $cert_filename + * @param string $key_filename + * @param string $key_pass Password for private key + * @param string $extracerts_filename Optional path to chain certificate + */ + public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '') + { + $this->sign_cert_file = $cert_filename; + $this->sign_key_file = $key_filename; + $this->sign_key_pass = $key_pass; + $this->sign_extracerts_file = $extracerts_filename; + } + + /** + * Quoted-Printable-encode a DKIM header. + * + * @param string $txt + * + * @return string + */ + public function DKIM_QP($txt) + { + $line = ''; + $len = strlen($txt); + for ($i = 0; $i < $len; ++$i) { + $ord = ord($txt[$i]); + if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord === 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) { + $line .= $txt[$i]; + } else { + $line .= '=' . sprintf('%02X', $ord); + } + } + + return $line; + } + + /** + * Generate a DKIM signature. + * + * @param string $signHeader + * + * @throws Exception + * + * @return string The DKIM signature value + */ + public function DKIM_Sign($signHeader) + { + if (!defined('PKCS7_TEXT')) { + if ($this->exceptions) { + throw new Exception($this->lang('extension_missing') . 'openssl'); + } + + return ''; + } + $privKeyStr = !empty($this->DKIM_private_string) ? + $this->DKIM_private_string : + file_get_contents($this->DKIM_private); + if ('' !== $this->DKIM_passphrase) { + $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase); + } else { + $privKey = openssl_pkey_get_private($privKeyStr); + } + if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) { + openssl_pkey_free($privKey); + + return base64_encode($signature); + } + openssl_pkey_free($privKey); + + return ''; + } + + /** + * Generate a DKIM canonicalization header. + * Uses the 'relaxed' algorithm from RFC6376 section 3.4.2. + * Canonicalized headers should *always* use CRLF, regardless of mailer setting. + * + * @see https://tools.ietf.org/html/rfc6376#section-3.4.2 + * + * @param string $signHeader Header + * + * @return string + */ + public function DKIM_HeaderC($signHeader) + { + //Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]` + //@see https://tools.ietf.org/html/rfc5322#section-2.2 + //That means this may break if you do something daft like put vertical tabs in your headers. + //Unfold header lines + $signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader); + //Break headers out into an array + $lines = explode("\r\n", $signHeader); + foreach ($lines as $key => $line) { + //If the header is missing a :, skip it as it's invalid + //This is likely to happen because the explode() above will also split + //on the trailing LE, leaving an empty line + if (strpos($line, ':') === false) { + continue; + } + list($heading, $value) = explode(':', $line, 2); + //Lower-case header name + $heading = strtolower($heading); + //Collapse white space within the value, also convert WSP to space + $value = preg_replace('/[ \t]+/', ' ', $value); + //RFC6376 is slightly unclear here - it says to delete space at the *end* of each value + //But then says to delete space before and after the colon. + //Net result is the same as trimming both ends of the value. + //By elimination, the same applies to the field name + $lines[$key] = trim($heading, " \t") . ':' . trim($value, " \t"); + } + + return implode("\r\n", $lines); + } + + /** + * Generate a DKIM canonicalization body. + * Uses the 'simple' algorithm from RFC6376 section 3.4.3. + * Canonicalized bodies should *always* use CRLF, regardless of mailer setting. + * + * @see https://tools.ietf.org/html/rfc6376#section-3.4.3 + * + * @param string $body Message Body + * + * @return string + */ + public function DKIM_BodyC($body) + { + if (empty($body)) { + return "\r\n"; + } + // Normalize line endings to CRLF + $body = static::normalizeBreaks($body, "\r\n"); + + //Reduce multiple trailing line breaks to a single one + return rtrim($body, "\r\n") . "\r\n"; + } + + /** + * Create the DKIM header and body in a new message header. + * + * @param string $headers_line Header lines + * @param string $subject Subject + * @param string $body Body + * + * @throws Exception + * + * @return string + */ + public function DKIM_Add($headers_line, $subject, $body) + { + $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms + $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization methods of header & body + $DKIMquery = 'dns/txt'; // Query method + $DKIMtime = time(); + //Always sign these headers without being asked + $autoSignHeaders = [ + 'From', + 'To', + 'CC', + 'Date', + 'Subject', + 'Reply-To', + 'Message-ID', + 'Content-Type', + 'Mime-Version', + 'X-Mailer', + ]; + if (stripos($headers_line, 'Subject') === false) { + $headers_line .= 'Subject: ' . $subject . static::$LE; + } + $headerLines = explode(static::$LE, $headers_line); + $currentHeaderLabel = ''; + $currentHeaderValue = ''; + $parsedHeaders = []; + $headerLineIndex = 0; + $headerLineCount = count($headerLines); + foreach ($headerLines as $headerLine) { + $matches = []; + if (preg_match('/^([^ \t]*?)(?::[ \t]*)(.*)$/', $headerLine, $matches)) { + if ($currentHeaderLabel !== '') { + //We were previously in another header; This is the start of a new header, so save the previous one + $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue]; + } + $currentHeaderLabel = $matches[1]; + $currentHeaderValue = $matches[2]; + } elseif (preg_match('/^[ \t]+(.*)$/', $headerLine, $matches)) { + //This is a folded continuation of the current header, so unfold it + $currentHeaderValue .= ' ' . $matches[1]; + } + ++$headerLineIndex; + if ($headerLineIndex >= $headerLineCount) { + //This was the last line, so finish off this header + $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue]; + } + } + $copiedHeaders = []; + $headersToSignKeys = []; + $headersToSign = []; + foreach ($parsedHeaders as $header) { + //Is this header one that must be included in the DKIM signature? + if (in_array($header['label'], $autoSignHeaders, true)) { + $headersToSignKeys[] = $header['label']; + $headersToSign[] = $header['label'] . ': ' . $header['value']; + if ($this->DKIM_copyHeaderFields) { + $copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC + str_replace('|', '=7C', $this->DKIM_QP($header['value'])); + } + continue; + } + //Is this an extra custom header we've been asked to sign? + if (in_array($header['label'], $this->DKIM_extraHeaders, true)) { + //Find its value in custom headers + foreach ($this->CustomHeader as $customHeader) { + if ($customHeader[0] === $header['label']) { + $headersToSignKeys[] = $header['label']; + $headersToSign[] = $header['label'] . ': ' . $header['value']; + if ($this->DKIM_copyHeaderFields) { + $copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC + str_replace('|', '=7C', $this->DKIM_QP($header['value'])); + } + //Skip straight to the next header + continue 2; + } + } + } + } + $copiedHeaderFields = ''; + if ($this->DKIM_copyHeaderFields && count($copiedHeaders) > 0) { + //Assemble a DKIM 'z' tag + $copiedHeaderFields = ' z='; + $first = true; + foreach ($copiedHeaders as $copiedHeader) { + if (!$first) { + $copiedHeaderFields .= static::$LE . ' |'; + } + //Fold long values + if (strlen($copiedHeader) > self::STD_LINE_LENGTH - 3) { + $copiedHeaderFields .= substr( + chunk_split($copiedHeader, self::STD_LINE_LENGTH - 3, static::$LE . ' '), + 0, + -strlen(static::$LE . ' ') + ); + } else { + $copiedHeaderFields .= $copiedHeader; + } + $first = false; + } + $copiedHeaderFields .= ';' . static::$LE; + } + $headerKeys = ' h=' . implode(':', $headersToSignKeys) . ';' . static::$LE; + $headerValues = implode(static::$LE, $headersToSign); + $body = $this->DKIM_BodyC($body); + $DKIMlen = strlen($body); // Length of body + $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body + $ident = ''; + if ('' !== $this->DKIM_identity) { + $ident = ' i=' . $this->DKIM_identity . ';' . static::$LE; + } + //The DKIM-Signature header is included in the signature *except for* the value of the `b` tag + //which is appended after calculating the signature + //https://tools.ietf.org/html/rfc6376#section-3.5 + $dkimSignatureHeader = 'DKIM-Signature: v=1;' . + ' d=' . $this->DKIM_domain . ';' . + ' s=' . $this->DKIM_selector . ';' . static::$LE . + ' a=' . $DKIMsignatureType . ';' . + ' q=' . $DKIMquery . ';' . + ' l=' . $DKIMlen . ';' . + ' t=' . $DKIMtime . ';' . + ' c=' . $DKIMcanonicalization . ';' . static::$LE . + $headerKeys . + $ident . + $copiedHeaderFields . + ' bh=' . $DKIMb64 . ';' . static::$LE . + ' b='; + //Canonicalize the set of headers + $canonicalizedHeaders = $this->DKIM_HeaderC( + $headerValues . static::$LE . $dkimSignatureHeader + ); + $signature = $this->DKIM_Sign($canonicalizedHeaders); + $signature = trim(chunk_split($signature, self::STD_LINE_LENGTH - 3, static::$LE . ' ')); + + return static::normalizeBreaks($dkimSignatureHeader . $signature) . static::$LE; + } + + /** + * Detect if a string contains a line longer than the maximum line length + * allowed by RFC 2822 section 2.1.1. + * + * @param string $str + * + * @return bool + */ + public static function hasLineLongerThanMax($str) + { + return (bool) preg_match('/^(.{' . (self::MAX_LINE_LENGTH + strlen(static::$LE)) . ',})/m', $str); + } + + /** + * Allows for public read access to 'to' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getToAddresses() + { + return $this->to; + } + + /** + * Allows for public read access to 'cc' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getCcAddresses() + { + return $this->cc; + } + + /** + * Allows for public read access to 'bcc' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getBccAddresses() + { + return $this->bcc; + } + + /** + * Allows for public read access to 'ReplyTo' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getReplyToAddresses() + { + return $this->ReplyTo; + } + + /** + * Allows for public read access to 'all_recipients' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getAllRecipientAddresses() + { + return $this->all_recipients; + } + + /** + * Perform a callback. + * + * @param bool $isSent + * @param array $to + * @param array $cc + * @param array $bcc + * @param string $subject + * @param string $body + * @param string $from + * @param array $extra + */ + protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from, $extra) + { + if (!empty($this->action_function) && is_callable($this->action_function)) { + call_user_func($this->action_function, $isSent, $to, $cc, $bcc, $subject, $body, $from, $extra); + } + } + + /** + * Get the OAuth instance. + * + * @return OAuth + */ + public function getOAuth() + { + return $this->oauth; + } + + /** + * Set an OAuth instance. + */ + public function setOAuth(OAuth $oauth) + { + $this->oauth = $oauth; + } +} diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/phpmailer/README.md b/superadmin/vendor/wanghua/general-utility-tools-php/src/phpmailer/README.md new file mode 100644 index 0000000..c2f7960 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/phpmailer/README.md @@ -0,0 +1,95 @@ +### 使用PHP发送邮件 + +#### 依赖composer包:wanghua/general-utility-tools-php +#### 环境要求:PHP7+, ThinkPHP5+ + +示例: + +步骤1:填写配置=>config + + 说明:根据需要读取数据库或文件配置 + +步骤2,调用: + +``` +//原生方式(复制可用) +Logger::send('test title','test body'); +``` + +``` +//调用封装类(复制可用) +Logger::open_debug();//打开调试模式 +Logger::log(['error'=>'系统错误'],'test_log');//调用日志写入 +``` + +#### 封装类: +``` + +/** +(复制可用) + * Class Logger + * @package app\common\tools + */ +class Logger +{ + + /** + * desc:开启调试模式 + * author:wh + */ + static function open_debug(){ + LoggerObj::open_debug(); + } + + /** + * desc:关闭调试模式 + * author:wh + */ + static function close_debug(){ + LoggerObj::close_debug(); + } + /** + * desc:系统未来通用统一日志记录 + * author:wh + * @param array $data + * @param string $file_log_name + */ + static function log(array $data=[], string $file_log_name){ + //服务器配置 start + $send_server_nickname = SundryConfigModel::getConfigVal('send_server_username'); + $send_server_username = SundryConfigModel::getConfigVal('send_server_username'); + $send_server_pwd = SundryConfigModel::getConfigVal('send_server_pwd'); + $send_server_host = SundryConfigModel::getConfigVal('send_server_host'); + //服务器配置 end + + //收件人信息 start + $receiver = SundryConfigModel::getConfigVal('admin_error_log_email'); + $receiver_nickname = '日志管理员';//默认收件人昵称 + //收件人信息 end + + //写入日志 + LoggerObj::set_email_config($send_server_nickname,$send_server_username,$send_server_pwd,$send_server_host); + LoggerObj::set_receiver_config($receiver,$receiver_nickname); + LoggerObj::write_text($data,$file_log_name); + } +} +``` + +#### 原生方式: +``` +class Logger{ + //(复制可用) + static function send($title, $body){ + $host = 'smtp.qq.com';//qq邮件发送服务器 + $nickname = 'test风控';//你的邮件发送服务器昵称 + $username = '1003076666@qq.com';//你的邮件发送服务器账号 + $userpass = 'pdosdyjuurowbcfc';//你的邮件发送服务器密码 + //邮件通知 + $mail = new Mail($nickname, $host, $username, $userpass); + $mail->debug = false;//true 测试模式 + $agent = Db::table('fa_agent')->where(['id'=>1])->find();//查询数据 + + return $mail->send($agent['email'], $agent['nickname'], $title,$body); + } +} +``` \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/phpmailer/SMTP.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/phpmailer/SMTP.php new file mode 100644 index 0000000..c1aeb1c --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/phpmailer/SMTP.php @@ -0,0 +1,1370 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2019 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace wanghua\general_utility_tools_php\phpmailer; + +/** + * PHPMailer RFC821 SMTP email transport class. + * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server. + * + * @author Chris Ryan + * @author Marcus Bointon + */ +class SMTP +{ + /** + * The PHPMailer SMTP version number. + * + * @var string + */ + const VERSION = '6.1.3'; + + /** + * SMTP line break constant. + * + * @var string + */ + const LE = "\r\n"; + + /** + * The SMTP port to use if one is not specified. + * + * @var int + */ + const DEFAULT_PORT = 25; + + /** + * The maximum line length allowed by RFC 5321 section 4.5.3.1.6, + * *excluding* a trailing CRLF break. + * + * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.6 + * + * @var int + */ + const MAX_LINE_LENGTH = 998; + + /** + * The maximum line length allowed for replies in RFC 5321 section 4.5.3.1.5, + * *including* a trailing CRLF line break. + * + * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.5 + * + * @var int + */ + const MAX_REPLY_LENGTH = 512; + + /** + * Debug level for no output. + * + * @var int + */ + const DEBUG_OFF = 0; + + /** + * Debug level to show client -> server messages. + * + * @var int + */ + const DEBUG_CLIENT = 1; + + /** + * Debug level to show client -> server and server -> client messages. + * + * @var int + */ + const DEBUG_SERVER = 2; + + /** + * Debug level to show connection status, client -> server and server -> client messages. + * + * @var int + */ + const DEBUG_CONNECTION = 3; + + /** + * Debug level to show all messages. + * + * @var int + */ + const DEBUG_LOWLEVEL = 4; + + /** + * Debug output level. + * Options: + * * self::DEBUG_OFF (`0`) No debug output, default + * * self::DEBUG_CLIENT (`1`) Client commands + * * self::DEBUG_SERVER (`2`) Client commands and server responses + * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status + * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages. + * + * @var int + */ + public $do_debug = self::DEBUG_OFF; + + /** + * How to handle debug output. + * Options: + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
    `, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * ```php + * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * ``` + * + * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug` + * level output is used: + * + * ```php + * $mail->Debugoutput = new myPsr3Logger; + * ``` + * + * @var string|callable|\Psr\Log\LoggerInterface + */ + public $Debugoutput = 'echo'; + + /** + * Whether to use VERP. + * + * @see http://en.wikipedia.org/wiki/Variable_envelope_return_path + * @see http://www.postfix.org/VERP_README.html Info on VERP + * + * @var bool + */ + public $do_verp = false; + + /** + * The timeout value for connection, in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. + * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure. + * + * @see http://tools.ietf.org/html/rfc2821#section-4.5.3.2 + * + * @var int + */ + public $Timeout = 300; + + /** + * How long to wait for commands to complete, in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. + * + * @var int + */ + public $Timelimit = 300; + + /** + * Patterns to extract an SMTP transaction id from reply to a DATA command. + * The first capture group in each regex will be used as the ID. + * MS ESMTP returns the message ID, which may not be correct for internal tracking. + * + * @var string[] + */ + protected $smtp_transaction_id_patterns = [ + 'exim' => '/[\d]{3} OK id=(.*)/', + 'sendmail' => '/[\d]{3} 2.0.0 (.*) Message/', + 'postfix' => '/[\d]{3} 2.0.0 Ok: queued as (.*)/', + 'Microsoft_ESMTP' => '/[0-9]{3} 2.[\d].0 (.*)@(?:.*) Queued mail for delivery/', + 'Amazon_SES' => '/[\d]{3} Ok (.*)/', + 'SendGrid' => '/[\d]{3} Ok: queued as (.*)/', + 'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/', + ]; + + /** + * The last transaction ID issued in response to a DATA command, + * if one was detected. + * + * @var string|bool|null + */ + protected $last_smtp_transaction_id; + + /** + * The socket for the server connection. + * + * @var ?resource + */ + protected $smtp_conn; + + /** + * Error information, if any, for the last SMTP command. + * + * @var array + */ + protected $error = [ + 'error' => '', + 'detail' => '', + 'smtp_code' => '', + 'smtp_code_ex' => '', + ]; + + /** + * The reply the server sent to us for HELO. + * If null, no HELO string has yet been received. + * + * @var string|null + */ + protected $helo_rply; + + /** + * The set of SMTP extensions sent in reply to EHLO command. + * Indexes of the array are extension names. + * Value at index 'HELO' or 'EHLO' (according to command that was sent) + * represents the server name. In case of HELO it is the only element of the array. + * Other values can be boolean TRUE or an array containing extension options. + * If null, no HELO/EHLO string has yet been received. + * + * @var array|null + */ + protected $server_caps; + + /** + * The most recent reply received from the server. + * + * @var string + */ + protected $last_reply = ''; + + /** + * Output debugging info via a user-selected method. + * + * @param string $str Debug string to output + * @param int $level The debug level of this message; see DEBUG_* constants + * + * @see SMTP::$Debugoutput + * @see SMTP::$do_debug + */ + protected function edebug($str, $level = 0) + { + if ($level > $this->do_debug) { + return; + } + //Is this a PSR-3 logger? + if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { + $this->Debugoutput->debug($str); + + return; + } + //Avoid clash with built-in function names + if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) { + call_user_func($this->Debugoutput, $str, $level); + + return; + } + switch ($this->Debugoutput) { + case 'error_log': + //Don't output, just log + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking, HTML-safe output + echo gmdate('Y-m-d H:i:s'), ' ', htmlentities( + preg_replace('/[\r\n]+/', '', $str), + ENT_QUOTES, + 'UTF-8' + ), "
    \n"; + break; + case 'echo': + default: + //Normalize line breaks + $str = preg_replace('/\r\n|\r/m', "\n", $str); + echo gmdate('Y-m-d H:i:s'), + "\t", + //Trim trailing space + trim( + //Indent for readability, except for trailing break + str_replace( + "\n", + "\n \t ", + trim($str) + ) + ), + "\n"; + } + } + + /** + * Connect to an SMTP server. + * + * @param string $host SMTP server IP or host name + * @param int $port The port number to connect to + * @param int $timeout How long to wait for the connection to open + * @param array $options An array of options for stream_context_create() + * + * @return bool + */ + public function connect($host, $port = null, $timeout = 30, $options = []) + { + static $streamok; + //This is enabled by default since 5.0.0 but some providers disable it + //Check this once and cache the result + if (null === $streamok) { + $streamok = function_exists('stream_socket_client'); + } + // Clear errors to avoid confusion + $this->setError(''); + // Make sure we are __not__ connected + if ($this->connected()) { + // Already connected, generate error + $this->setError('Already connected to a server'); + + return false; + } + if (empty($port)) { + $port = self::DEFAULT_PORT; + } + // Connect to the SMTP server + $this->edebug( + "Connection: opening to $host:$port, timeout=$timeout, options=" . + (count($options) > 0 ? var_export($options, true) : 'array()'), + self::DEBUG_CONNECTION + ); + $errno = 0; + $errstr = ''; + if ($streamok) { + $socket_context = stream_context_create($options); + set_error_handler([$this, 'errorHandler']); + $this->smtp_conn = stream_socket_client( + $host . ':' . $port, + $errno, + $errstr, + $timeout, + STREAM_CLIENT_CONNECT, + $socket_context + ); + restore_error_handler(); + } else { + //Fall back to fsockopen which should work in more places, but is missing some features + $this->edebug( + 'Connection: stream_socket_client not available, falling back to fsockopen', + self::DEBUG_CONNECTION + ); + set_error_handler([$this, 'errorHandler']); + $this->smtp_conn = fsockopen( + $host, + $port, + $errno, + $errstr, + $timeout + ); + restore_error_handler(); + } + // Verify we connected properly + if (!is_resource($this->smtp_conn)) { + $this->setError( + 'Failed to connect to server', + '', + (string) $errno, + $errstr + ); + $this->edebug( + 'SMTP ERROR: ' . $this->error['error'] + . ": $errstr ($errno)", + self::DEBUG_CLIENT + ); + + return false; + } + $this->edebug('Connection: opened', self::DEBUG_CONNECTION); + // SMTP server can take longer to respond, give longer timeout for first read + // Windows does not have support for this timeout function + if (strpos(PHP_OS, 'WIN') !== 0) { + $max = (int) ini_get('max_execution_time'); + // Don't bother if unlimited + if (0 !== $max && $timeout > $max) { + @set_time_limit($timeout); + } + stream_set_timeout($this->smtp_conn, $timeout, 0); + } + // Get any announcement + $announce = $this->get_lines(); + $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER); + + return true; + } + + /** + * Initiate a TLS (encrypted) session. + * + * @return bool + */ + public function startTLS() + { + if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) { + return false; + } + + //Allow the best TLS version(s) we can + $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT; + + //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT + //so add them back in manually if we can + if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { + $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; + $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; + } + + // Begin encrypted connection + set_error_handler([$this, 'errorHandler']); + $crypto_ok = stream_socket_enable_crypto( + $this->smtp_conn, + true, + $crypto_method + ); + restore_error_handler(); + + return (bool) $crypto_ok; + } + + /** + * Perform SMTP authentication. + * Must be run after hello(). + * + * @see hello() + * + * @param string $username The user name + * @param string $password The password + * @param string $authtype The auth type (CRAM-MD5, PLAIN, LOGIN, XOAUTH2) + * @param OAuth $OAuth An optional OAuth instance for XOAUTH2 authentication + * + * @return bool True if successfully authenticated + */ + public function authenticate( + $username, + $password, + $authtype = null, + $OAuth = null + ) { + if (!$this->server_caps) { + $this->setError('Authentication is not allowed before HELO/EHLO'); + + return false; + } + + if (array_key_exists('EHLO', $this->server_caps)) { + // SMTP extensions are available; try to find a proper authentication method + if (!array_key_exists('AUTH', $this->server_caps)) { + $this->setError('Authentication is not allowed at this stage'); + // 'at this stage' means that auth may be allowed after the stage changes + // e.g. after STARTTLS + + return false; + } + + $this->edebug('Auth method requested: ' . ($authtype ?: 'UNSPECIFIED'), self::DEBUG_LOWLEVEL); + $this->edebug( + 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']), + self::DEBUG_LOWLEVEL + ); + + //If we have requested a specific auth type, check the server supports it before trying others + if (null !== $authtype && !in_array($authtype, $this->server_caps['AUTH'], true)) { + $this->edebug('Requested auth method not available: ' . $authtype, self::DEBUG_LOWLEVEL); + $authtype = null; + } + + if (empty($authtype)) { + //If no auth mechanism is specified, attempt to use these, in this order + //Try CRAM-MD5 first as it's more secure than the others + foreach (['CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2'] as $method) { + if (in_array($method, $this->server_caps['AUTH'], true)) { + $authtype = $method; + break; + } + } + if (empty($authtype)) { + $this->setError('No supported authentication methods found'); + + return false; + } + $this->edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL); + } + + if (!in_array($authtype, $this->server_caps['AUTH'], true)) { + $this->setError("The requested authentication method \"$authtype\" is not supported by the server"); + + return false; + } + } elseif (empty($authtype)) { + $authtype = 'LOGIN'; + } + switch ($authtype) { + case 'PLAIN': + // Start authentication + if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) { + return false; + } + // Send encoded username and password + if (!$this->sendCommand( + 'User & Password', + base64_encode("\0" . $username . "\0" . $password), + 235 + ) + ) { + return false; + } + break; + case 'LOGIN': + // Start authentication + if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) { + return false; + } + if (!$this->sendCommand('Username', base64_encode($username), 334)) { + return false; + } + if (!$this->sendCommand('Password', base64_encode($password), 235)) { + return false; + } + break; + case 'CRAM-MD5': + // Start authentication + if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) { + return false; + } + // Get the challenge + $challenge = base64_decode(substr($this->last_reply, 4)); + + // Build the response + $response = $username . ' ' . $this->hmac($challenge, $password); + + // send encoded credentials + return $this->sendCommand('Username', base64_encode($response), 235); + case 'XOAUTH2': + //The OAuth instance must be set up prior to requesting auth. + if (null === $OAuth) { + return false; + } + $oauth = $OAuth->getOauth64(); + + // Start authentication + if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) { + return false; + } + break; + default: + $this->setError("Authentication method \"$authtype\" is not supported"); + + return false; + } + + return true; + } + + /** + * Calculate an MD5 HMAC hash. + * Works like hash_hmac('md5', $data, $key) + * in case that function is not available. + * + * @param string $data The data to hash + * @param string $key The key to hash with + * + * @return string + */ + protected function hmac($data, $key) + { + if (function_exists('hash_hmac')) { + return hash_hmac('md5', $data, $key); + } + + // The following borrowed from + // http://php.net/manual/en/function.mhash.php#27225 + + // RFC 2104 HMAC implementation for php. + // Creates an md5 HMAC. + // Eliminates the need to install mhash to compute a HMAC + // by Lance Rushing + + $bytelen = 64; // byte length for md5 + if (strlen($key) > $bytelen) { + $key = pack('H*', md5($key)); + } + $key = str_pad($key, $bytelen, chr(0x00)); + $ipad = str_pad('', $bytelen, chr(0x36)); + $opad = str_pad('', $bytelen, chr(0x5c)); + $k_ipad = $key ^ $ipad; + $k_opad = $key ^ $opad; + + return md5($k_opad . pack('H*', md5($k_ipad . $data))); + } + + /** + * Check connection state. + * + * @return bool True if connected + */ + public function connected() + { + if (is_resource($this->smtp_conn)) { + $sock_status = stream_get_meta_data($this->smtp_conn); + if ($sock_status['eof']) { + // The socket is valid but we are not connected + $this->edebug( + 'SMTP NOTICE: EOF caught while checking if connected', + self::DEBUG_CLIENT + ); + $this->close(); + + return false; + } + + return true; // everything looks good + } + + return false; + } + + /** + * Close the socket and clean up the state of the class. + * Don't use this function without first trying to use QUIT. + * + * @see quit() + */ + public function close() + { + $this->setError(''); + $this->server_caps = null; + $this->helo_rply = null; + if (is_resource($this->smtp_conn)) { + // close the connection and cleanup + fclose($this->smtp_conn); + $this->smtp_conn = null; //Makes for cleaner serialization + $this->edebug('Connection: closed', self::DEBUG_CONNECTION); + } + } + + /** + * Send an SMTP DATA command. + * Issues a data command and sends the msg_data to the server, + * finializing the mail transaction. $msg_data is the message + * that is to be send with the headers. Each header needs to be + * on a single line followed by a with the message headers + * and the message body being separated by an additional . + * Implements RFC 821: DATA . + * + * @param string $msg_data Message data to send + * + * @return bool + */ + public function data($msg_data) + { + //This will use the standard timelimit + if (!$this->sendCommand('DATA', 'DATA', 354)) { + return false; + } + + /* The server is ready to accept data! + * According to rfc821 we should not send more than 1000 characters on a single line (including the LE) + * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into + * smaller lines to fit within the limit. + * We will also look for lines that start with a '.' and prepend an additional '.'. + * NOTE: this does not count towards line-length limit. + */ + + // Normalize line breaks before exploding + $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data)); + + /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field + * of the first line (':' separated) does not contain a space then it _should_ be a header and we will + * process all lines before a blank line as headers. + */ + + $field = substr($lines[0], 0, strpos($lines[0], ':')); + $in_headers = false; + if (!empty($field) && strpos($field, ' ') === false) { + $in_headers = true; + } + + foreach ($lines as $line) { + $lines_out = []; + if ($in_headers && $line === '') { + $in_headers = false; + } + //Break this line up into several smaller lines if it's too long + //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len), + while (isset($line[self::MAX_LINE_LENGTH])) { + //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on + //so as to avoid breaking in the middle of a word + $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' '); + //Deliberately matches both false and 0 + if (!$pos) { + //No nice break found, add a hard break + $pos = self::MAX_LINE_LENGTH - 1; + $lines_out[] = substr($line, 0, $pos); + $line = substr($line, $pos); + } else { + //Break at the found point + $lines_out[] = substr($line, 0, $pos); + //Move along by the amount we dealt with + $line = substr($line, $pos + 1); + } + //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1 + if ($in_headers) { + $line = "\t" . $line; + } + } + $lines_out[] = $line; + + //Send the lines to the server + foreach ($lines_out as $line_out) { + //RFC2821 section 4.5.2 + if (!empty($line_out) && $line_out[0] === '.') { + $line_out = '.' . $line_out; + } + $this->client_send($line_out . static::LE, 'DATA'); + } + } + + //Message data has been sent, complete the command + //Increase timelimit for end of DATA command + $savetimelimit = $this->Timelimit; + $this->Timelimit *= 2; + $result = $this->sendCommand('DATA END', '.', 250); + $this->recordLastTransactionID(); + //Restore timelimit + $this->Timelimit = $savetimelimit; + + return $result; + } + + /** + * Send an SMTP HELO or EHLO command. + * Used to identify the sending server to the receiving server. + * This makes sure that client and server are in a known state. + * Implements RFC 821: HELO + * and RFC 2821 EHLO. + * + * @param string $host The host name or IP to connect to + * + * @return bool + */ + public function hello($host = '') + { + //Try extended hello first (RFC 2821) + return $this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host); + } + + /** + * Send an SMTP HELO or EHLO command. + * Low-level implementation used by hello(). + * + * @param string $hello The HELO string + * @param string $host The hostname to say we are + * + * @return bool + * + * @see hello() + */ + protected function sendHello($hello, $host) + { + $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250); + $this->helo_rply = $this->last_reply; + if ($noerror) { + $this->parseHelloFields($hello); + } else { + $this->server_caps = null; + } + + return $noerror; + } + + /** + * Parse a reply to HELO/EHLO command to discover server extensions. + * In case of HELO, the only parameter that can be discovered is a server name. + * + * @param string $type `HELO` or `EHLO` + */ + protected function parseHelloFields($type) + { + $this->server_caps = []; + $lines = explode("\n", $this->helo_rply); + + foreach ($lines as $n => $s) { + //First 4 chars contain response code followed by - or space + $s = trim(substr($s, 4)); + if (empty($s)) { + continue; + } + $fields = explode(' ', $s); + if (!empty($fields)) { + if (!$n) { + $name = $type; + $fields = $fields[0]; + } else { + $name = array_shift($fields); + switch ($name) { + case 'SIZE': + $fields = ($fields ? $fields[0] : 0); + break; + case 'AUTH': + if (!is_array($fields)) { + $fields = []; + } + break; + default: + $fields = true; + } + } + $this->server_caps[$name] = $fields; + } + } + } + + /** + * Send an SMTP MAIL command. + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more recipient + * commands may be called followed by a data command. + * Implements RFC 821: MAIL FROM: . + * + * @param string $from Source address of this message + * + * @return bool + */ + public function mail($from) + { + $useVerp = ($this->do_verp ? ' XVERP' : ''); + + return $this->sendCommand( + 'MAIL FROM', + 'MAIL FROM:<' . $from . '>' . $useVerp, + 250 + ); + } + + /** + * Send an SMTP QUIT command. + * Closes the socket if there is no error or the $close_on_error argument is true. + * Implements from RFC 821: QUIT . + * + * @param bool $close_on_error Should the connection close if an error occurs? + * + * @return bool + */ + public function quit($close_on_error = true) + { + $noerror = $this->sendCommand('QUIT', 'QUIT', 221); + $err = $this->error; //Save any error + if ($noerror || $close_on_error) { + $this->close(); + $this->error = $err; //Restore any error from the quit command + } + + return $noerror; + } + + /** + * Send an SMTP RCPT command. + * Sets the TO argument to $toaddr. + * Returns true if the recipient was accepted false if it was rejected. + * Implements from RFC 821: RCPT TO: . + * + * @param string $address The address the message is being sent to + * @param string $dsn Comma separated list of DSN notifications. NEVER, SUCCESS, FAILURE + * or DELAY. If you specify NEVER all other notifications are ignored. + * + * @return bool + */ + public function recipient($address, $dsn = '') + { + if (empty($dsn)) { + $rcpt = 'RCPT TO:<' . $address . '>'; + } else { + $dsn = strtoupper($dsn); + $notify = []; + + if (strpos($dsn, 'NEVER') !== false) { + $notify[] = 'NEVER'; + } else { + foreach (['SUCCESS', 'FAILURE', 'DELAY'] as $value) { + if (strpos($dsn, $value) !== false) { + $notify[] = $value; + } + } + } + + $rcpt = 'RCPT TO:<' . $address . '> NOTIFY=' . implode(',', $notify); + } + + return $this->sendCommand( + 'RCPT TO', + $rcpt, + [250, 251] + ); + } + + /** + * Send an SMTP RSET command. + * Abort any transaction that is currently in progress. + * Implements RFC 821: RSET . + * + * @return bool True on success + */ + public function reset() + { + return $this->sendCommand('RSET', 'RSET', 250); + } + + /** + * Send a command to an SMTP server and check its return code. + * + * @param string $command The command name - not sent to the server + * @param string $commandstring The actual command to send + * @param int|array $expect One or more expected integer success codes + * + * @return bool True on success + */ + protected function sendCommand($command, $commandstring, $expect) + { + if (!$this->connected()) { + $this->setError("Called $command without being connected"); + + return false; + } + //Reject line breaks in all commands + if ((strpos($commandstring, "\n") !== false) || (strpos($commandstring, "\r") !== false)) { + $this->setError("Command '$command' contained line breaks"); + + return false; + } + $this->client_send($commandstring . static::LE, $command); + + $this->last_reply = $this->get_lines(); + // Fetch SMTP code and possible error code explanation + $matches = []; + if (preg_match('/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $this->last_reply, $matches)) { + $code = (int) $matches[1]; + $code_ex = (count($matches) > 2 ? $matches[2] : null); + // Cut off error code from each response line + $detail = preg_replace( + "/{$code}[ -]" . + ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m', + '', + $this->last_reply + ); + } else { + // Fall back to simple parsing if regex fails + $code = (int) substr($this->last_reply, 0, 3); + $code_ex = null; + $detail = substr($this->last_reply, 4); + } + + $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); + + if (!in_array($code, (array) $expect, true)) { + $this->setError( + "$command command failed", + $detail, + $code, + $code_ex + ); + $this->edebug( + 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply, + self::DEBUG_CLIENT + ); + + return false; + } + + $this->setError(''); + + return true; + } + + /** + * Send an SMTP SAML command. + * Starts a mail transaction from the email address specified in $from. + * Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more recipient + * commands may be called followed by a data command. This command + * will send the message to the users terminal if they are logged + * in and send them an email. + * Implements RFC 821: SAML FROM: . + * + * @param string $from The address the message is from + * + * @return bool + */ + public function sendAndMail($from) + { + return $this->sendCommand('SAML', "SAML FROM:$from", 250); + } + + /** + * Send an SMTP VRFY command. + * + * @param string $name The name to verify + * + * @return bool + */ + public function verify($name) + { + return $this->sendCommand('VRFY', "VRFY $name", [250, 251]); + } + + /** + * Send an SMTP NOOP command. + * Used to keep keep-alives alive, doesn't actually do anything. + * + * @return bool + */ + public function noop() + { + return $this->sendCommand('NOOP', 'NOOP', 250); + } + + /** + * Send an SMTP TURN command. + * This is an optional command for SMTP that this class does not support. + * This method is here to make the RFC821 Definition complete for this class + * and _may_ be implemented in future. + * Implements from RFC 821: TURN . + * + * @return bool + */ + public function turn() + { + $this->setError('The SMTP TURN command is not implemented'); + $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); + + return false; + } + + /** + * Send raw data to the server. + * + * @param string $data The data to send + * @param string $command Optionally, the command this is part of, used only for controlling debug output + * + * @return int|bool The number of bytes sent to the server or false on error + */ + public function client_send($data, $command = '') + { + //If SMTP transcripts are left enabled, or debug output is posted online + //it can leak credentials, so hide credentials in all but lowest level + if (self::DEBUG_LOWLEVEL > $this->do_debug && + in_array($command, ['User & Password', 'Username', 'Password'], true)) { + $this->edebug('CLIENT -> SERVER: [credentials hidden]', self::DEBUG_CLIENT); + } else { + $this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT); + } + set_error_handler([$this, 'errorHandler']); + $result = fwrite($this->smtp_conn, $data); + restore_error_handler(); + + return $result; + } + + /** + * Get the latest error. + * + * @return array + */ + public function getError() + { + return $this->error; + } + + /** + * Get SMTP extensions available on the server. + * + * @return array|null + */ + public function getServerExtList() + { + return $this->server_caps; + } + + /** + * Get metadata about the SMTP server from its HELO/EHLO response. + * The method works in three ways, dependent on argument value and current state: + * 1. HELO/EHLO has not been sent - returns null and populates $this->error. + * 2. HELO has been sent - + * $name == 'HELO': returns server name + * $name == 'EHLO': returns boolean false + * $name == any other string: returns null and populates $this->error + * 3. EHLO has been sent - + * $name == 'HELO'|'EHLO': returns the server name + * $name == any other string: if extension $name exists, returns True + * or its options (e.g. AUTH mechanisms supported). Otherwise returns False. + * + * @param string $name Name of SMTP extension or 'HELO'|'EHLO' + * + * @return string|bool|null + */ + public function getServerExt($name) + { + if (!$this->server_caps) { + $this->setError('No HELO/EHLO was sent'); + + return; + } + + if (!array_key_exists($name, $this->server_caps)) { + if ('HELO' === $name) { + return $this->server_caps['EHLO']; + } + if ('EHLO' === $name || array_key_exists('EHLO', $this->server_caps)) { + return false; + } + $this->setError('HELO handshake was used; No information about server extensions available'); + + return; + } + + return $this->server_caps[$name]; + } + + /** + * Get the last reply from the server. + * + * @return string + */ + public function getLastReply() + { + return $this->last_reply; + } + + /** + * Read the SMTP server's response. + * Either before eof or socket timeout occurs on the operation. + * With SMTP we can tell if we have more lines to read if the + * 4th character is '-' symbol. If it is a space then we don't + * need to read anything else. + * + * @return string + */ + protected function get_lines() + { + // If the connection is bad, give up straight away + if (!is_resource($this->smtp_conn)) { + return ''; + } + $data = ''; + $endtime = 0; + stream_set_timeout($this->smtp_conn, $this->Timeout); + if ($this->Timelimit > 0) { + $endtime = time() + $this->Timelimit; + } + $selR = [$this->smtp_conn]; + $selW = null; + while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { + //Must pass vars in here as params are by reference + if (!stream_select($selR, $selW, $selW, $this->Timelimit)) { + $this->edebug( + 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)', + self::DEBUG_LOWLEVEL + ); + break; + } + //Deliberate noise suppression - errors are handled afterwards + $str = @fgets($this->smtp_conn, self::MAX_REPLY_LENGTH); + $this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL); + $data .= $str; + // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled), + // or 4th character is a space or a line break char, we are done reading, break the loop. + // String array access is a significant micro-optimisation over strlen + if (!isset($str[3]) || $str[3] === ' ' || $str[3] === "\r" || $str[3] === "\n") { + break; + } + // Timed-out? Log and break + $info = stream_get_meta_data($this->smtp_conn); + if ($info['timed_out']) { + $this->edebug( + 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)', + self::DEBUG_LOWLEVEL + ); + break; + } + // Now check if reads took too long + if ($endtime && time() > $endtime) { + $this->edebug( + 'SMTP -> get_lines(): timelimit reached (' . + $this->Timelimit . ' sec)', + self::DEBUG_LOWLEVEL + ); + break; + } + } + + return $data; + } + + /** + * Enable or disable VERP address generation. + * + * @param bool $enabled + */ + public function setVerp($enabled = false) + { + $this->do_verp = $enabled; + } + + /** + * Get VERP address generation mode. + * + * @return bool + */ + public function getVerp() + { + return $this->do_verp; + } + + /** + * Set error messages and codes. + * + * @param string $message The error message + * @param string $detail Further detail on the error + * @param string $smtp_code An associated SMTP error code + * @param string $smtp_code_ex Extended SMTP code + */ + protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '') + { + $this->error = [ + 'error' => $message, + 'detail' => $detail, + 'smtp_code' => $smtp_code, + 'smtp_code_ex' => $smtp_code_ex, + ]; + } + + /** + * Set debug output method. + * + * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it + */ + public function setDebugOutput($method = 'echo') + { + $this->Debugoutput = $method; + } + + /** + * Get debug output method. + * + * @return string + */ + public function getDebugOutput() + { + return $this->Debugoutput; + } + + /** + * Set debug output level. + * + * @param int $level + */ + public function setDebugLevel($level = 0) + { + $this->do_debug = $level; + } + + /** + * Get debug output level. + * + * @return int + */ + public function getDebugLevel() + { + return $this->do_debug; + } + + /** + * Set SMTP timeout. + * + * @param int $timeout The timeout duration in seconds + */ + public function setTimeout($timeout = 0) + { + $this->Timeout = $timeout; + } + + /** + * Get SMTP timeout. + * + * @return int + */ + public function getTimeout() + { + return $this->Timeout; + } + + /** + * Reports an error number and string. + * + * @param int $errno The error number returned by PHP + * @param string $errmsg The error message returned by PHP + * @param string $errfile The file the error occurred in + * @param int $errline The line number the error occurred on + */ + protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0) + { + $notice = 'Connection failed.'; + $this->setError( + $notice, + $errmsg, + (string) $errno + ); + $this->edebug( + "$notice Error #$errno: $errmsg [$errfile line $errline]", + self::DEBUG_CONNECTION + ); + } + + /** + * Extract and return the ID of the last SMTP transaction based on + * a list of patterns provided in SMTP::$smtp_transaction_id_patterns. + * Relies on the host providing the ID in response to a DATA command. + * If no reply has been received yet, it will return null. + * If no pattern was matched, it will return false. + * + * @return bool|string|null + */ + protected function recordLastTransactionID() + { + $reply = $this->getLastReply(); + + if (empty($reply)) { + $this->last_smtp_transaction_id = null; + } else { + $this->last_smtp_transaction_id = false; + foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) { + if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) { + $this->last_smtp_transaction_id = trim($matches[1]); + break; + } + } + } + + return $this->last_smtp_transaction_id; + } + + /** + * Get the queue/transaction ID of the last SMTP transaction + * If no reply has been received yet, it will return null. + * If no pattern was matched, it will return false. + * + * @return bool|string|null + * + * @see recordLastTransactionID() + */ + public function getLastTransactionID() + { + return $this->last_smtp_transaction_id; + } +} diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/sms/AliSms.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/sms/AliSms.php new file mode 100644 index 0000000..49fb896 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/sms/AliSms.php @@ -0,0 +1,107 @@ +accessKeyId = $aliyun_oss_config['AccessKeyId']; + $this->accessKeySecret = $aliyun_oss_config['AccessKeySecret']; + $this->aliyun_oss_config = $aliyun_oss_config; + $this->sms_template_code = $sms_template_code; + $this->sms_sign_name = $sms_sign_name; + } + + /** + * 使用AK&SK初始化账号Client + * @return Dysmsapi Client + */ + private function createClient(){ + // 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。 + // 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/311677.html。 + $conf = [ + // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。 + "accessKeyId" => $this->accessKeyId,//getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"), + // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。 + "accessKeySecret" => $this->accessKeySecret,//getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET") + ]; + $config = new Config($conf); + // Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi + $config->endpoint = "dysmsapi.aliyuncs.com"; + return new Dysmsapi($config); + } + + /** + * desc:添加短信模板 + * author:wh + */ + function addTemplate($SignName,$SignSource,$Remark){ + $client = $this->createClient(); + $addSmsSignRequest = new AddSmsSignRequest([]); + $runtime = new RuntimeOptions([]); + + // 复制代码运行请自行打印 API 的返回值 + $res = $client->addSmsSignWithOptions($addSmsSignRequest, $runtime); + dump($res); + } + + /** + * 发送短信 + * + * 文档地址:https://next.api.aliyun.com/api/Dysmsapi/2017-05-25/SendSms?params={%22SignName%22:%22%E4%BC%98%E4%B8%80%E7%A7%91%E6%8A%80%22,%22TemplateCode%22:%22SMS_471455330%22,%22PhoneNumbers%22:%2218290416033%22,%22TemplateParam%22:%22%7B%5C%22code%5C%22%3A%5C%221234%5C%22%7D%22}&RegionId=cn-hangzhou&tab=DEMO&lang=PHP + */ + public function send(string $phoneNumbers,string $templateParam){ + $client = $this->createClient(); + $conf = [ + //签名 + "signName" => $this->sms_sign_name,//"优一科技", + //模板code + "templateCode" => $this->sms_template_code,//"SMS_471455330", + //手机号(多个逗号隔开,上限1000) + "phoneNumbers" => $phoneNumbers, + "templateParam" => $templateParam,//{"code":"1234"} + ]; + $sendSmsRequest = new SendSmsRequest($conf); + // 运行请自行打印 API 的返回值 + $res = $client->sendSmsWithOptions($sendSmsRequest, new RuntimeOptions([])); + return Tools::set_ok('ok', $res); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/AudioAiTool.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/AudioAiTool.php new file mode 100644 index 0000000..340130b --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/AudioAiTool.php @@ -0,0 +1,144 @@ +ai_config = $ai_config; + } + /** + * desc:阶跃AI生成音频 + * + * + * author:wh + * $audio_file_name 生成的音频文件名(推荐使用用户唯一id),默认为当前时间戳 + * + * doc: https://platform.stepfun.com/docs/llm/audio + * + * 使用示例: + $txt = input('txt','收款到账12.03元!支付后有惊喜哦!~'); + $phone = input('phone'); + $ai_config = config('step_fun_ai_config'); + $ai_config['model'] = 'step-tts-mini'; + $ai_config['voice'] = 'jilingshaonv';//'yuanqinansheng'; + $ai_config['speed'] = 1.1; + $ai_config['volume'] = 1.5;//可选值为 0.1~2.0 ,代表着将音量缩小至 10% ~ 增大至 200%(两倍音量) + + //音频文件名称 + $audio_file_name = "/partner/payment_audio/".$phone; + $res = (new AudioAiTool($ai_config))->stepfunBuildAudioMp3($txt,$audio_file_name); + if($res['code'] != 200){ + return Tools::set_fail('生成音频失败'); + } + + $url = request()->domain().'/'.$res['data']['rel']; + echo "点击";die; + //return Tools::set_ok('ok',['url'=>$url]); + */ + public function stepfunBuildAudioMp3(string $txt,$audio_file_name = ''){ + $url = $this->ai_config['base_url']?:'https://api.stepfun.com/v1/audio/speech'; + $apiKey = $this->ai_config['APIKey'];//'KJS9PN05iwG9peJpPHW2y8uBHDLTEu6B6e1xIsA4QSxhVEn2DV7kMQeoWD4H2ecA'; + //speed 参数,可选值为 0.5-2 ,代表着将语速降速为之前的一半 ~ 提速至两倍 + $data = [ + 'model' => $this->ai_config['model']?:'step-tts-mini', + 'input' => $txt?:'智能阶跃,十倍每一个人的可能', + 'voice' => $this->ai_config['voice']?:'cixingnansheng', + 'speed' => $this->ai_config['speed']?:1, + ]; + $headers = [ + 'Content-Type: application/json', + 'Authorization: Bearer ' . $apiKey, + ]; + $dir_arr = $this->createMp3Dir($audio_file_name); + if(empty($dir_arr)){ + return Tools::set_fail('生成音频失败'); + } + $real_path = $dir_arr[0];//物理路径 + $rel_url = $dir_arr[1];//相对路径 + + $result = $this->curlPost($url, $data, $headers, false); + // 将响应内容保存为 MP3 文件 + if ($result !== false) { + // 尝试写入文件 + if (file_put_contents($real_path, $result)) { + //echo "音频文件已保存为:{$filename} 访问\n"; + return Tools::set_ok('ok',['rel'=>$rel_url]); + } else { + //echo "写入文件 {$filename} 失败\n"; + Tools::log_to_write_txt(['error'=>"写入文件失败: {$real_path} "]); + return Tools::set_fail('生成音频失败'); + } + } else { + //echo "请求失败\n"; + Tools::log_to_write_txt(['error'=>"请求失败: {$url} "]); + return Tools::set_fail('生成音频失败'); + } + } + + /** + * desc:创建目录 + * author:wh + * @param string $audio_file_name 文件名不能有"空格"等特殊符号 + */ + private function createMp3Dir($audio_file_name='') + { + $time = $audio_file_name?:(date('YmdHis')); + $root_path = Tools::get_root_path(); + $rel = "audio/{$time}.mp3"; + $real_path = $root_path . "public/" . $rel; + $dir = dirname($real_path); + + // 检查并创建目录 + if (!file_exists($dir)) { + if (!mkdir($dir, 0777, true)) { + //echo "目录 {$dir} 创建成功\n"; + //} else { + //echo "目录 {$dir} 创建失败\n"; + Tools::log_to_write_txt(['error'=>"目录创建失败: {$dir} "]); + return []; + } + } + return [$real_path, $rel]; + } + + protected function curlPost($url, $data, $headers, $verifySSL = true) + { + $jsonData = json_encode($data); + + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + + if (!$verifySSL) { + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + } + + $response = curl_exec($ch); + + if (curl_errno($ch)) { + echo 'Error:' . curl_error($ch); + return false; + } + + curl_close($ch); + + return $response; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/Bank.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/Bank.php new file mode 100644 index 0000000..9a4b1cc --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/Bank.php @@ -0,0 +1,249 @@ +getBankSimpleInfoByBankNo('6217003760019611401'); +* var_dump($res);//中国建设银行 + * + * Class Bank + * @package wanghua\general_utility_tools_php\tool + */ +class Bank +{ + public $gateway = 'https://ccdcapi.alipay.com/validateAndCacheCardInfo.json?_input_charset=utf-8&cardBinCheck=true'; + private $bankArr = []; + public function __construct() + { + //初始化 + $this->bankArr = [ + "SRCB"=> "深圳农村商业银行", + "BGB"=> "广西北部湾银行", + "SHRCB"=> "上海农村商业银行", + "BJBANK"=> "北京银行", + "WHCCB"=> "威海市商业银行", + "BOZK"=> "周口银行", + "KORLABANK"=> "库尔勒市商业银行", + "SPABANK"=> "平安银行", + "SDEB"=> "顺德农商银行", + "HURCB"=> "湖北省农村信用社", + "WRCB"=> "无锡农村商业银行", + "BOCY"=> "朝阳银行", + "CZBANK"=> "浙商银行", + "HDBANK"=> "邯郸银行", + "BOC"=> "中国银行", + "BOD"=> "东莞银行", + "CCB"=> "中国建设银行", + "ZYCBANK"=> "遵义市商业银行", + "SXCB"=> "绍兴银行", + "GZRCU"=> "贵州省农村信用社", + "ZJKCCB"=> "张家口市商业银行", + "BOJZ"=> "锦州银行", + "BOP"=> "平顶山银行", + "HKB"=> "汉口银行", + "SPDB"=> "上海浦东发展银行", + "NXRCU"=> "宁夏黄河农村商业银行", + "NYNB"=> "广东南粤银行", + "GRCB"=> "广州农商银行", + "BOSZ"=> "苏州银行", + "HZCB"=> "杭州银行", + "HSBK"=> "衡水银行", + "HBC"=> "湖北银行", + "JXBANK"=> "嘉兴银行", + "HRXJB"=> "华融湘江银行", + "BODD"=> "丹东银行", + "AYCB"=> "安阳银行", + "EGBANK"=> "恒丰银行", + "CDB"=> "国家开发银行", + "TCRCB"=> "江苏太仓农村商业银行", + "NJCB"=> "南京银行", + "ZZBANK"=> "郑州银行", + "DYCB"=> "德阳商业银行", + "YBCCB"=> "宜宾市商业银行", + "SCRCU"=> "四川省农村信用", + "KLB"=> "昆仑银行", + "LSBANK"=> "莱商银行", + "YDRCB"=> "尧都农商行", + "CCQTGB"=> "重庆三峡银行", + "FDB"=> "富滇银行", + "JSRCU"=> "江苏省农村信用联合社", + "JNBANK"=> "济宁银行", + "CMB"=> "招商银行", + "JINCHB"=> "晋城银行JCBANK", + "FXCB"=> "阜新银行", + "WHRCB"=> "武汉农村商业银行", + "HBYCBANK"=> "湖北银行宜昌分行", + "TZCB"=> "台州银行", + "TACCB"=> "泰安市商业银行", + "XCYH"=> "许昌银行", + "CEB"=> "中国光大银行", + "NXBANK"=> "宁夏银行", + "HSBANK"=> "徽商银行", + "JJBANK"=> "九江银行", + "NHQS"=> "农信银清算中心", + "MTBANK"=> "浙江民泰商业银行", + "LANGFB"=> "廊坊银行", + "ASCB"=> "鞍山银行", + "KSRB"=> "昆山农村商业银行", + "YXCCB"=> "玉溪市商业银行", + "DLB"=> "大连银行", + "DRCBCL"=> "东莞农村商业银行", + "GCB"=> "广州银行", + "NBBANK"=> "宁波银行", + "BOYK"=> "营口银行", + "SXRCCU"=> "陕西信合", + "GLBANK"=> "桂林银行", + "BOQH"=> "青海银行", + "CDRCB"=> "成都农商银行", + "QDCCB"=> "青岛银行", + "HKBEA"=> "东亚银行", + "HBHSBANK"=> "湖北银行黄石分行", + "WZCB"=> "温州银行", + "TRCB"=> "天津农商银行", + "QLBANK"=> "齐鲁银行", + "GDRCC"=> "广东省农村信用社联合社", + "ZJTLCB"=> "浙江泰隆商业银行", + "GZB"=> "赣州银行", + "GYCB"=> "贵阳市商业银行", + "CQBANK"=> "重庆银行", + "DAQINGB"=> "龙江银行", + "CGNB"=> "南充市商业银行", + "SCCB"=> "三门峡银行", + "CSRCB"=> "常熟农村商业银行", + "SHBANK"=> "上海银行", + "JLBANK"=> "吉林银行", + "CZRCB"=> "常州农村信用联社", + "BANKWF"=> "潍坊银行", + "ZRCBANK"=> "张家港农村商业银行", + "FJHXBC"=> "福建海峡银行", + "ZJNX"=> "浙江省农村信用社联合社", + "LZYH"=> "兰州银行", + "JSB"=> "晋商银行", + "BOHAIB"=> "渤海银行", + "CZCB"=> "浙江稠州商业银行", + "YQCCB"=> "阳泉银行", + "SJBANK"=> "盛京银行", + "XABANK"=> "西安银行", + "BSB"=> "包商银行", + "JSBANK"=> "江苏银行", + "FSCB"=> "抚顺银行", + "HNRCU"=> "河南省农村信用", + "COMM"=> "交通银行", + "XTB"=> "邢台银行", + "CITIC"=> "中信银行", + "HXBANK"=> "华夏银行", + "HNRCC"=> "湖南省农村信用社", + "DYCCB"=> "东营市商业银行", + "ORBANK"=> "鄂尔多斯银行", + "BJRCB"=> "北京农村商业银行", + "XYBANK"=> "信阳银行", + "ZGCCB"=> "自贡市商业银行", + "CDCB"=> "成都银行", + "HANABANK"=> "韩亚银行", + "CMBC"=> "中国民生银行", + "LYBANK"=> "洛阳银行", + "GDB"=> "广东发展银行", + "ZBCB"=> "齐商银行", + "CBKF"=> "开封市商业银行", + "H3CB"=> "内蒙古银行", + "CIB"=> "兴业银行", + "CRCBANK"=> "重庆农村商业银行", + "SZSBK"=> "石嘴山银行", + "DZBANK"=> "德州银行", + "SRBANK"=> "上饶银行", + "LSCCB"=> "乐山市商业银行", + "JXRCU"=> "江西省农村信用", + "ICBC"=> "中国工商银行", + "JZBANK"=> "晋中市商业银行", + "HZCCB"=> "湖州市商业银行", + "NHB"=> "南海农村信用联社", + "XXBANK"=> "新乡银行", + "JRCB"=> "江苏江阴农村商业银行", + "YNRCC"=> "云南省农村信用社", + "ABC"=> "中国农业银行", + "GXRCU"=> "广西省农村信用", + "PSBC"=> "中国邮政储蓄银行", + "BZMD"=> "驻马店银行", + "ARCU"=> "安徽省农村信用社", + "GSRCU"=> "甘肃省农村信用", + "LYCB"=> "辽阳市商业银行", + "JLRCU"=> "吉林农信", + "URMQCCB"=> "乌鲁木齐市商业银行", + "XLBANK"=> "中山小榄村镇银行", + "CSCB"=> "长沙银行", + "JHBANK"=> "金华银行", + "BHB"=> "河北银行", + "NBYZ"=> "鄞州银行", + "LSBC"=> "临商银行", + "BOCD"=> "承德银行", + "SDRCU"=> "山东农信", + "NCB"=> "南昌银行", + "TCCB"=> "天津银行", + "WJRCB"=> "吴江农商银行", + "CBBQS"=> "城市商业银行资金清算中心", + "HBRCU"=> "河北省农村信用社" + ]; + } + + /** + * desc:如果配置里面没有匹配的银行卡,可用此方法补充进去 + * + * author:wh + * @param $bank_name 银行英文缩写名称 + * @param $bank_no 卡号 + */ + function addBankInfo($bank_name,$bank_no){ + $this->bankArr[$bank_name] = $bank_no; + } + + /** + * desc:重置银行卡配置 + * + * author:wh + */ + function resetConfig(array $config){ + $this->bankArr = $config;//覆盖 + } + + /** + * desc:获取银行卡中文名称 + * + * author:wh + * @param string $key + * @return mixed|string + */ + function getBankTitle($key=''){ + return empty($this->bankArr[$key])?'':$this->bankArr[$key]; + } + + /** + * desc:根据银行卡号查询银行名称 + * + * author:wh + * @param string $bankno + * @throws \Exception + */ + function getBankSimpleInfoByBankNo(string $bankno){ + $result = Curl::curl_post($this->gateway,['cardNo'=>$bankno]); + if($result['code'] != 200){ + return Tools::set_res(500,'查询失败.'.$result['msg']); + } + $res_data = json_decode($result['data'],true); + if(empty($res_data['bank'])){ + return Tools::set_res(500,'请输入正确的银行卡号',$res_data); + } + return Tools::set_res(200,'ok',$this->getBankTitle($res_data['bank'])); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/EmailTool.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/EmailTool.php new file mode 100644 index 0000000..0e111b6 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/EmailTool.php @@ -0,0 +1,357 @@ +set_email_config($send_server_nickname,$send_server_username,$send_server_pwd,$send_server_host); + $email_obj->set_receiver_config($receiver,$receiver_nickname); + + //写入日志 + return $email_obj->write_text($title,$body); + }catch (\Exception $e){ + Tools::log_to_write_txt([ + 'error'=>'系统未来通用统一日志记录,出错.'.$e->getMessage(), + 'title'=>$title, + 'body'=>$body, + 'error_info'=>$e->getTraceAsString()],$log_file); + return ['code'=>500,'msg'=>'日志写入异常.'.$e->getMessage()]; + } + } + + /** + * @deprecated 不推荐 + * + * desc:向收件人发邮件 + * + * author:wh + * @param string $title 标题 + * @param string $body 内容 + * @param $receiver 多个收件人用逗号隔开 + * @param string $receiver_nickname 收件人昵称 + * @return array + */ + static function log_email_to_person(string $title,string $body,$receiver,$receiver_nickname='默认'){ + $log_file = ''; + try { + //服务器配置 start + $send_server_nickname = SundryConfig::val('send_server_nickname'); + $send_server_username = SundryConfig::val('send_server_username'); + $send_server_pwd = SundryConfig::val('send_server_pwd'); + $send_server_host = SundryConfig::val('send_server_host'); + //服务器配置 end + if(empty($send_server_nickname)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器昵称为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器昵称为空'); + } + if(empty($send_server_username)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器邮箱为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器邮箱为空'); + } + if(empty($send_server_pwd)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器密码为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器密码为空.'); + } + if(empty($send_server_host)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器HOST为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器HOST为空..'); + } + + //收件人信息 start + $receiver = explode(',',SundryConfig::val('admin_error_log_email'));//逗号隔开一次发送多个人 + $receiver_nickname = '日志管理员';//默认收件人昵称 + //收件人信息 end + if(empty($send_server_host)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器HOST为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器HOST为空..'); + } + + $email_obj = new Email(); + $email_obj->set_email_config($send_server_nickname,$send_server_username,$send_server_pwd,$send_server_host); + $email_obj->set_receiver_config($receiver,$receiver_nickname); + + //写入日志 + return $email_obj->write_text($title,$body); + }catch (\Exception $e){ + Tools::log_to_write_txt([ + 'error'=>'系统未来通用统一日志记录,出错.'.$e->getMessage(), + 'title'=>$title, + 'body'=>$body, + 'error_info'=>$e->getTraceAsString()],$log_file); + return ['code'=>500,'msg'=>'日志写入异常.'.$e->getMessage()]; + } + } + + /** + * desc:【推荐】发送国内邮件,支持批量发送 + * + * author:wh + * @param string $title 标题 + * @param string $body 内容 + * @param $receiver 多个收件人用逗号隔开 + * @param string $receiver_nickname 收件人昵称 + * @return array + */ + static function email_to_person(string $title,string $body,$receiver,$receiver_nickname='默认'){ + $log_file = ''; + try { + //服务器配置 start + $send_server_nickname = SundryConfig::val('send_server_nickname'); + $send_server_username = SundryConfig::val('send_server_username'); + $send_server_pwd = SundryConfig::val('send_server_pwd'); + $send_server_host = SundryConfig::val('send_server_host'); + //服务器配置 end + if(empty($send_server_nickname)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器昵称为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器昵称为空'); + } + if(empty($send_server_username)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器邮箱为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器邮箱为空'); + } + if(empty($send_server_pwd)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器密码为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器密码为空.'); + } + if(empty($send_server_host)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器HOST为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器HOST为空..'); + } + + //收件人信息 start + $receiver = explode(',',$receiver);//逗号隔开一次发送多个人 + //$receiver_nickname = '日志管理员';//默认收件人昵称 + //收件人信息 end + if(empty($send_server_host)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器HOST为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器HOST为空..'); + } + + $email_obj = new Email(); + $email_obj->set_email_config($send_server_nickname,$send_server_username,$send_server_pwd,$send_server_host); + $email_obj->set_receiver_config($receiver,$receiver_nickname); + + //写入日志 + return $email_obj->write_text($title,$body); + }catch (\Exception $e){ + Tools::log_to_write_txt([ + 'error'=>'系统未来通用统一日志记录,出错.'.$e->getMessage(), + 'title'=>$title, + 'body'=>$body, + 'error_info'=>$e->getTraceAsString()],$log_file); + return ['code'=>500,'msg'=>'日志写入异常.'.$e->getMessage()]; + } + } + + /** + * desc:发送亚马逊邮件(如果一个项目有多个邮件发送服务器请使用Email类发送,不使用本封装工具) + * author:wh + * @param string $title + * @param string $body + * @param $sender_email 发件人邮箱 + * @param $receiver 收件人邮箱 + * @param string $receiver_nickname 收件人昵称 + * @return array + */ + static function aws_to_person(string $title,string $body,$receiver,$receiver_nickname='默认'){ + $log_file = ''; + try { + //服务器配置 start + $send_server_nickname = SundryConfig::val('send_server_nickname'); + $send_server_username = SundryConfig::val('send_server_username'); + $send_server_pwd = SundryConfig::val('send_server_pwd'); + $send_server_host = SundryConfig::val('send_server_host'); + $sender_email = SundryConfig::val('sender_email'); + //服务器配置 end + if(empty($send_server_nickname)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器昵称为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器昵称为空'); + } + if(empty($send_server_username)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器邮箱为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器邮箱为空'); + } + if(empty($send_server_pwd)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器密码为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器密码为空.'); + } + if(empty($send_server_host)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器HOST为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器HOST为空..'); + } + if(empty($sender_email)){ + Tools::log_to_write_txt(['服务器配置错误.发件方邮箱为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件方邮箱为空..'); + } + + //收件人信息 start + $receiver = explode(',',$receiver);//逗号隔开一次发送多个人 + //$receiver_nickname = '日志管理员';//默认收件人昵称 + //收件人信息 end + if(empty($send_server_host)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器HOST为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器HOST为空..'); + } + + $email_obj = new Email(); + $email_obj->port = 587;//STARTTLS 端口25、587 或 2587;自定义 SSL 客户端支持:TLS 包装器端口465 或 2465 + $email_obj->protocol = 'TLS';//固定 + $email_obj->sender_email = $sender_email; + $email_obj->set_email_config($send_server_nickname,$send_server_username,$send_server_pwd,$send_server_host); + $email_obj->set_receiver_config($receiver,$receiver_nickname); + + //写入日志 + return $email_obj->write_text_aws($title,$body); + }catch (\Exception $e){ + Tools::log_to_write_txt([ + 'error'=>'系统未来通用统一日志记录,出错.'.$e->getMessage(), + 'title'=>$title, + 'body'=>$body, + 'error_info'=>$e->getTraceAsString()],$log_file); + return ['code'=>500,'msg'=>'日志写入异常.'.$e->getMessage()]; + } + } + + /** + * desc:【推荐】错误、异常邮件,固定模板 + * + * author:wh + * @param string $title + * @param string $error_title + * @param string $error_info + * @return array + */ + static function log_error_email(string $title, string $error_title,string $error_info){ + $log_file = ''; + try { + //服务器配置 start + $send_server_nickname = SundryConfig::val('send_server_nickname'); + $send_server_username = SundryConfig::val('send_server_username'); + $send_server_pwd = SundryConfig::val('send_server_pwd'); + $send_server_host = SundryConfig::val('send_server_host'); + //服务器配置 end + if(empty($send_server_nickname)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器昵称为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器昵称为空'); + } + if(empty($send_server_username)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器邮箱为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器邮箱为空'); + } + if(empty($send_server_pwd)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器密码为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器密码为空.'); + } + if(empty($send_server_host)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器HOST为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器HOST为空..'); + } + + //收件人信息 start + $receiver = SundryConfig::val('admin_error_log_email'); + $receiver_nickname = '日志管理员';//默认收件人昵称 + //收件人信息 end + if(empty($send_server_host)){ + Tools::log_to_write_txt(['服务器配置错误.发件服务器HOST为空.'],$log_file); + return Tools::set_res(500,'服务器配置错误.发件服务器HOST为空..'); + } + + $email_obj = new Email(); + $email_obj->set_email_config($send_server_nickname,$send_server_username,$send_server_pwd,$send_server_host); + $email_obj->set_receiver_config(explode(',',$receiver),$receiver_nickname); + + $err_json_input = json_encode(input(),JSON_UNESCAPED_UNICODE); + $url = request()->url(true); + $body = <<错误标题:$error_title

    +

    输入参数(json后):

    +

    $err_json_input

    +

    触发url地址:

    +

    $url

    +

    详细错误信息:

    +

    $error_info

    +EOF; + + //写入日志 + return $email_obj->write_text($title,$body); + }catch (\Exception $e){ + Tools::log_to_write_txt([ + 'error'=>'系统未来通用统一日志记录,出错.'.$e->getMessage(), + 'title'=>$title, + 'body'=>$error_info, + 'error_info'=>$e->getTraceAsString()],$log_file); + return ['code'=>500,'msg'=>'日志写入异常.'.$e->getMessage()]; + } + } + + /** + * desc:通过阿里云邮件服务器发送邮件 + * author:wh + */ + static function sendAliEmail($toAddress,$subject,$content){ + $config = config('aliyun_oss_config'); + return (new AliYunEmail($config))->sendMail($toAddress,$subject,$content); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/Ip.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/Ip.php new file mode 100644 index 0000000..ce7b494 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/Ip.php @@ -0,0 +1,274 @@ +isPrivateIP($ip)){ + return true;//国内 + } + switch ($type){ + case 'qifu_baidu': + return $this->qihuBaidu($ip); + break; + case 'ipapi': + return $this->ipApi($ip); + break; + default: + return $this->ipApi($ip); + } + } + + + /** + * desc:是否为私有IP + * author:wh + * @param $ip + * @return bool + */ + function isPrivateIP($ip) { + // 将IP地址字符串转换为整数 + $ipLong = ip2long($ip); + // 检查转换是否成功以及IP地址是否属于私有地址段 + if ($ipLong !== false) { + // A类私有地址 + if ($ipLong >= ip2long('10.0.0.0') && $ipLong <= ip2long('10.255.255.255')) { + return true; + } + // B类私有地址 + elseif ($ipLong >= ip2long('172.16.0.0') && $ipLong <= ip2long('172.31.255.255')) { + return true; + } + // C类私有地址 + elseif ($ipLong >= ip2long('192.168.0.0') && $ipLong <= ip2long('192.168.255.255')) { + return true; + } + } + // 如果不在私有地址范围内或转换失败,则不是私有IP + return false; + } + + /** + * desc:百度ip查询 + * + * author:wh + * 查询结果: + * {"code":"Success","data":{"continent":"亚洲","country":"中国","zipcode":"401120","timezone":"UTC+8", + * "accuracy":"区县","owner":"中国移动","isp":"中国移动","source":"数据挖掘","areacode":"CN","adcode":"500112", + * "asnumber":"9808","lat":"29.813215","lng":"106.743200","radius":"35.6842","prov":"重庆市","city":"重庆市", + * "district":"渝北区"},"charge":true,"msg":"查询成功","ip":"183.227.88.66","coordsys":"WGS84"} + * @param $ip + * @return bool + */ + function qihuBaidu($ip){ + if($this->isPrivateIP($ip)){ + return true;//国内 + } + if(cache($ip)){ + Tools::log_to_write_txt(['读取缓存ip地址:'.$ip,'$res(yes为国内)'=>cache($ip)]); + return cache($ip) == 'yes'; + } + //先走缓存 + if($this->getRecord($ip)){ + return false; + } + + + + $res = $this->curl_get($ip); + Tools::log_to_write_txt(['error'=>'IP查询,curl结束:'.$ip,'$res'=>$res]); + if($res['code'] == 200){//请求成功 + if(empty($res['data'])){ + Tools::log_to_write_txt(['error'=>'IP查询,curl结果为空','$res'=>$res]); + } + //校验ip属地 + $data = json_decode($res['data'],true); + if($data['code'] != 'Success'){//查询失败 + Tools::log_to_write_txt(['error'=>'IP查询成功,但结果返回为失败.','$res'=>$res]); + return true; + } + //查询成功 + if(empty($data['data']['country'])){ + //country为空时默认为国内 + Tools::log_to_write_txt(['error'=>'IP查询成功,但country返回为空.','$res'=>$res]); + return true; + } + if($data['data']['country'] == '中国'){ + //缓存ip地址 + cache($ip,'yes');//国内 + return true; + } + $this->insertRecord($ip,$data['data']['continent'].' '.$data['data']['prov'],$data['data']['country'],'no'); + //缓存ip地址 + cache($ip,'no');//国外 + Tools::log_to_write_txt(['ip地址不在中国内,禁止访问:'.$ip,'curl data'=>$res['data']]); + //return redirect($this->redirect_url); + return false; + } + return true; + } + /** + * ipapi查询IP + * + * 查询IP归属哪个国家(白嫖查询,不保证每次都能查询成功) + * + * CN表示中国,其它表示国外 + */ + function ipApi($ip){ + if($this->isPrivateIP($ip)){ + return true;//国内 + } + if(cache($ip)){ + Tools::log_to_write_txt(['读取缓存ip地址:'.$ip,'$res(yes为国内)'=>cache($ip)]); + return cache($ip) == 'yes'; + } + //先走缓存 + if($this->getRecord($ip)){ + return false; + } + /** cache($ip)为空时进行实时查询 */ + + $url = "https://ipapi.co/{$ip}/country/"; + $curl = curl_init(); + curl_setopt_array($curl, array( + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => '', + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 0, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => 'GET', + CURLOPT_HTTPHEADER => array( + 'User-Agent: Apifox/1.0.0 (https://apifox.com)', + 'Accept: */*', + 'Host: ipapi.co', + 'Connection: keep-alive' + ), + )); + + $response = curl_exec($curl); + + curl_close($curl); + if($response == 'CN'){ + cache($ip,'yes');//国内 + return true; + } + $this->insertRecord($ip,$response,$response); + Tools::log_to_write_txt(['ip地址不在中国内,禁止访问:'.$ip,'$response'=>$response,'$url'=>$url]); + cache($ip,'no');//国外 + return false; + } + + /** + * desc: + * author:wh + * @param string $territory 属地 + * @param string $country 国家 + * @param string $is_china 是否中国 + */ + private function insertRecord($ip,$territory='',$country='',$is_china='no'){ + /** + + CREATE TABLE `fa_ip_attack_record` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', + `ip` varchar(30) NOT NULL DEFAULT '' COMMENT 'IP', + `territory` varchar(50) DEFAULT '' COMMENT '归属地', + `country` varchar(15) DEFAULT '' COMMENT '国家', + `is_china` varchar(10) NOT NULL DEFAULT '' COMMENT '是否中国', + `url` text NOT NULL COMMENT '带域名查询参数', + `params` text COMMENT '参数', + `header` text COMMENT '请求头', + `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '攻击时间', + PRIMARY KEY (`id`) USING BTREE, + KEY `index_ip` (`ip`) USING BTREE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='IP攻击记录'; + + */ + //IP攻击记录 + Db::table('fa_ip_attack_record') + ->insert([ + 'ip'=>$ip, + 'territory'=>$territory, + 'country'=>$country, + 'is_china'=>$is_china, + 'url'=>request()->url(true), + 'params'=>json_encode(input(),JSON_UNESCAPED_UNICODE), + 'header'=>json_encode(request()->header(),JSON_UNESCAPED_UNICODE), + ]); + } + + /** + * desc:查询IP攻击记录 + * author:wh + * @param $ip + */ + private function getRecord($ip){ + return Db::table('fa_ip_attack_record') + ->where('ip',$ip) + ->field('id,is_china') + ->find(); + } + + /** + * desc:curl + * author:wh + * @param $ip + * @return array + */ + function curl_get($ip){ + $curl = curl_init(); + + curl_setopt_array($curl, array( + CURLOPT_URL => 'https://qifu.baidu.com/ip/geo/v1/district?ip='.$ip, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => '', + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 0, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_HTTP_VERSION => 'CURL_HTTP_VERSION_1_1', + CURLOPT_CUSTOMREQUEST => 'GET', + CURLOPT_HTTPHEADER => array( + 'User-Agent: Apifox/1.0.0 (https://apifox.com)', + 'Accept: */*', + 'Host: qifu.baidu.com', + 'Connection: keep-alive' + ), + )); + + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); + $data = curl_exec($curl); + + // 显示错误信息 + if (curl_error($curl)) { + //print "Error: ".curl_errno($curl).'-' . curl_error($curl); + //返回错误码 + return ['code' => curl_errno($curl), 'msg' => curl_error($curl)]; + } else { + //关闭句柄 + curl_close($curl); + // 返回的内容 + return ['code' => 200, 'msg' => 'cURL ok', 'data' => $data]; + } + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/KuaidiTools.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/KuaidiTools.php new file mode 100644 index 0000000..f85b35e --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/KuaidiTools.php @@ -0,0 +1,232 @@ +getPlatform($num); + if($plt['code'] != 200){ + return Tools::set_res(500,'查询失败.'.$plt['msg']); + } + $company = $plt['data']['company']; + $url = "https://alayn.baidu.com/express/appdetail/get_detail?query_from_srcid=51151&tokenV2=OLRFkDonXGP%2FQTmCKNu41zXJayuyUkfll0i7Tw5IWBFAsbTSXMycQooMPzAMO1Rc&appid=4001&nu={$num}&com={$company}&qid=4879176651996235000"; + $res = Curl::curl_get($url); + if($res['code'] != 200){ + return Tools::set_res($res['code'],'请求失败.'.$res['msg']); + } + $res_data = json_decode($res['data'],true); + if($res_data['error_code']){ + return Tools::set_res($res_data['error_code'],'查询失败.'.$res_data['msg']); + } + return Tools::set_ok('ok',$res_data['data']['context']); + }); + } + + + /** + * desc:查询爱查快递接口 + * { + "mailNo": "315002419145908:6033", + "status": 3, + "errCode": 0, + "message": "", + "expSpellName": "yunda", + "expTextName": "韵达快递", + "tel": "95546", + "data": [ + { + "time": "2024-06-18 18:45", + "context": "【潍坊市】山东潍坊奎文保税区公司-马维华(16602197439) 已揽收" + }, + { + "time": "2024-06-18 20:30", + "context": "【潍坊市】已到达 山东潍坊分拨交付中心" + }, + { + "time": "2024-06-18 20:35", + "context": "【潍坊市】已离开 山东潍坊分拨交付中心;发往 重庆分拨交付中心" + }, + { + "time": "2024-06-20 07:35", + "context": "【重庆市】已到达 重庆分拨交付中心" + }, + { + "time": "2024-06-20 07:55", + "context": "【重庆市】已离开 重庆分拨交付中心;发往 重庆渝北区西政公司" + }, + { + "time": "2024-06-20 13:35", + "context": "【重庆市】已离开 重庆市渝北区西政网格仓;发往 重庆渝北区西政公司" + }, + { + "time": "2024-06-20 13:35", + "context": "【重庆市】已到达 重庆渝北区西政公司[023-88972584]" + }, + { + "time": "2024-06-20 13:36", + "context": "【重庆市】重庆渝北区西政公司[023-88972584] 快递员 高邦明(13224040149) 正在为您派送【95121为韵达快递员外呼专属号码,请放心接听】" + }, + { + "time": "2024-06-20 13:36", + "context": "【重庆市】已到达 重庆渝北区西政公司[023-88972584]" + }, + { + "time": "2024-06-20 13:37", + "context": "【重庆市】重庆渝北区西政公司[023-88972584] 快递员 高邦明(13224040149) 正在为您派送【95121为韵达快递员外呼专属号码,请放心接听】" + }, + { + "time": "2024-06-20 14:25", + "context": "【代收点】您的快件已暂存至 兴科五路81妈妈驿站,地址:重庆市市辖区渝北区兴科五路81号,请及时领取签收,如有疑问请电联快递员:高邦明(13224040149) ,投诉电话:13274912700" + }, + { + "time": "2024-06-22 20:31", + "context": "【代收点】您的快件已签收,签收人在 兴科五路81妈妈驿站(重庆市市辖区渝北区兴科五路81号)领取,投诉电话:13274912700" + } + ], + "update": 1719293097, + "cache": 10576, + "lang": "zh", + "ord": "ASC" + } + * author:wh + * @param $num 快递单号 + * @param $phone_end4 手机后四位 + * @return array 返回data字段二维数组记录 + */ + function queryackd($num,$phone_end4){ + return Mmodel::catch(function () use($num,$phone_end4){ + $ran_time = mt_rand(1000000,2000000); + $tm = Tools::getMillisecond(); + $_ = $tm+$ran_time; + usleep($ran_time);//随机暂停1-2秒(伪装) + $tk = Tools::rand_str(); + + //快递平台 + $plt = $this->getPlatform($num); + if($plt['code'] != 200){ + return Tools::set_res(500,'查询失败.'.$plt['msg']); + } + $company = $plt['data']['company']; + + $url = "https://trace.fkdex.com/{$company}/{$num}:{$phone_end4}?mailNo=315002419145908%3A6033&spellName=&exp-textName=&tk={$tk}&tm={$tm}&_={$_}"; + $res = Curl::curl_get($url); + if($res['code'] != 200){ + return Tools::set_res($res['code'],'查询失败.'.$res['msg']); + } + if(empty($res['data'])){ + return Tools::set_fail('未查询到数据'); + } + $res_data = json_decode($res['data'],true); + if($res_data['errCode']){ + return Tools::set_res($res_data['errCode'],'查询失败.'.$res_data['message']); + } + return Tools::set_ok('ok',$res_data['data']); + }); + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/MySqlTools.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/MySqlTools.php new file mode 100644 index 0000000..23561cb --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/MySqlTools.php @@ -0,0 +1,78 @@ +size; + + //调用方法成功后,会在相应文件夹下生成二维码文件 + \PHPQRCode\QRcode::png($str, $outfile, $size); + + //$QR调用png生成的二维码全路径 + $QR = Tools::get_root_path().'public/uploads/qr_images/'.$qrname; + //$header头像全路径:头像一定是正方形 + $header = $logo; + $QR = \PHPQRCode\QRcode::addHeader($header, $QR); + + return $outfile; + } + + /** + * desc:生成不带logo的二维码 + * + * author:wh + * @param string $content 二维码的内容 + * @param string $qr_file_name 二维码图片文件名称(一般为用户唯一id) + * @return string + */ + function generateQrcode($content,$qr_file_name='') + { + if(empty($qr_file_name)){ + $qr_file_name = 'qrcode_'.time().rand(1000,9999); + } + $out_path = 'uploads/qr_images/'; + if(!file_exists($out_path)){ + mkdir($out_path,0777,true); + } + $qrname = $qr_file_name.'.png'; + //二维码导出的储存地址 + $outfile = $out_path.$qrname; + //二维码的大小 + $size = $this->size; + + //调用方法成功后,会在相应文件夹下生成二维码文件 + \PHPQRCode\QRcode::png($content, $outfile, $size); + return $outfile; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/Tools.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/Tools.php new file mode 100644 index 0000000..af77bca --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/Tools.php @@ -0,0 +1,2560 @@ + $code, + 'msg' => $message, + 'data' => $data, + ]; + + if (is_array($code)) { + $r = array_merge($code, $r); + } + + if ($log) { + $dirname = 'result_log'; + if (is_array($log)) { + $dirname = isset($log['dirname']) ? $log['dirname'] : $dirname;//存储在runtime/log/下面 + } + $root_path = explode('vendor',__DIR__)[0]; + $filepath = $root_path . 'runtime/log/' . ($dirname). '/'.date('Ymd');//运行时日志 + if (!file_exists($filepath)) { + mkdir($filepath, 0777, true); + } + + $filepath .= '/log' . date('YmdH') . '.txt'; + if(!file_exists($filepath)){ + //创建 + touch($filepath); + // 设置文件权限 + chmod($filepath, 0777); + } + $str = "\n" . date('Y-m-d H:i:s') . ' | ' . request()->baseUrl() . "\n"; + + $str .= 'PARAMS: ';//参数 + $str .= json_encode(input(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n"; + + $str .= 'RESULT: ';//结果 + $str .= json_encode($r, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . ' | '; + + file_put_contents($filepath, $str, FILE_APPEND); + } + return $json ? json_encode($r, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $r; + } + + /** + * desc:操作失败 + * author:wh + * @param string $msg 错误信息 + * @param array $data 返回数据 + * @param string $action 操作标识、控制器具体操作方法 + * @return array + */ + static function set_ok($msg='ok',$data=[],$action=''){ + if(is_array($msg)){ + $data = $msg; + $msg = 'ok'; + } + return ['code'=>200,'msg'=>$msg,'action'=>$action,'data'=>$data]; + } + + /** + * desc:操作失败 + * + * author:wh + * @param string $msg 错误信息 + * @param array $data 返回数据 + * @param string $action 操作标识、控制器具体操作方法 + * @return array + */ + static function set_fail($msg='操作失败',$data=[],$action=''){ + return ['code'=>500,'msg'=>$msg,'action'=>$action,'data'=>$data]; + } + + /** + * 设置event-stream的响应成功结果 + * @param string $msg 错误信息 + */ + static function set_event_stream_ok($msg='ok',$data=[],$action=''){ + //数据量太大时不能使用JSON_UNESCAPED_UNICODE + $json_data = json_encode(['code'=>200,'msg'=>$msg,'action'=>$action,'data'=>$data], JSON_UNESCAPED_UNICODE); + return <<500,'msg'=>$msg,'action'=>$action,'data'=>$data], JSON_UNESCAPED_UNICODE); + return << $code, + 'msg' => $message, + 'data' => $data, + ]; + + if (is_array($code)) { + $r = array_merge($code, $r); + } + + if ($log) { + $dirname = 'result_log';//存储在runtime/log/下面 + if (is_array($log)) { + $dirname = isset($log['dirname']) ? $log['dirname'] : $dirname; + } + + $filepath = \think\facade\App::getRuntimePath() . 'log/' . ($dirname). '/'.date('Ymd');//运行时日志 + if (!file_exists($filepath)) { + mkdir($filepath, 0777, true); + } + + $filepath .= '/log' . date('YmdH') . '.txt'; + if(!file_exists($filepath)){ + //创建 + touch($filepath); + // 设置文件权限 + chmod($filepath, 0777); + } + $str = "\n" . date('Y-m-d H:i:s') . ' | ' . request()->baseUrl() . "\n"; + + $str .= 'PARAMS: ';//参数 + $str .= json_encode(input(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n"; + + $str .= 'RESULT: ';//结果 + $str .= json_encode($r, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n"; + + + file_put_contents($filepath, $str, FILE_APPEND); + } + return $json ? json_encode($r, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $r; + } + + /** + * desc:封装json格式的结果 + * author:wh + * @param $code + * @param string $message + * @param array $data + * @return false|string + */ + static function json_res($code, string $message = '', $data = []) + { + $r = [ + 'code' => $code, + 'msg' => $message, + 'data' => $data, + ]; + + if (is_array($code)) { + $r = array_merge($code, $r); + } + + return json_encode($r, JSON_UNESCAPED_UNICODE); + } + static function json_ok(string $message = 'ok', $data = []) + { + $r = [ + 'code' => 200, + 'msg' => $message, + 'data' => $data, + ]; + return json_encode($r, JSON_UNESCAPED_UNICODE); + } + static function json_fail(string $message = 'fail', $data = []) + { + $r = [ + 'code' => 500, + 'msg' => $message, + 'data' => $data, + ]; + return json_encode($r, JSON_UNESCAPED_UNICODE); + } + /** + * @deprecated 废除,2027年1月1日以后移除 + * desc:socket专用json格式 + * + * author:wh + * @param $action + * @param string $msg + * @param array $items + * @param string $method + * @return false|string + */ + static function json_wss($action,$msg='', $items=[], $method='response'){ + $json = [ + 'action'=>$action, + 'method'=>$method, + 'msg'=>$msg, + 'items'=>$items + ]; + return json_encode($json, JSON_UNESCAPED_UNICODE); + } + /** + * desc:socket专用json格式 + * + * author:wh + * @param $action + * @param string $msg + * @param array $items + * @param string $method + * @return false|string + */ + static function wss_json($action, $code=200, $msg='ok', $items=[]){ + $json = [ + 'action'=>$action,//响应业务标识 + 'code'=>$code,//错误码 + 'msg'=>$msg,//错误消息 + 'items'=>$items//返回数据 + ]; + return json_encode($json, JSON_UNESCAPED_UNICODE); + } + /** + * desc:成功结果封装 + * + * socket专用json格式 + * + * author:wh + * @param $action + * @param string $msg + * @param array $items + * @param string $method + * @return false|string + */ + static function wss_json_ok($action, $msg='ok', $items=[]){ + $json = [ + 'action'=>$action,//响应业务标识 + 'code'=>200,//错误码 + 'msg'=>$msg,//错误消息 + 'items'=>$items//返回数据 + ]; + return json_encode($json, JSON_UNESCAPED_UNICODE); + } + /** + * desc:失败结果封装 + * + * socket专用json格式 + * + * author:wh + * @param $action + * @param string $msg + * @param array $items + * @param string $method + * @return false|string + */ + static function wss_json_fail($action, $msg='操作失败.', $items=[]){ + $json = [ + 'action'=>$action,//响应业务标识 + 'code'=>500,//错误码 + 'msg'=>$msg,//错误消息 + 'items'=>$items//返回数据 + ]; + return json_encode($json, JSON_UNESCAPED_UNICODE); + } + /** + * desc:获取框架版本[兼容各个ThinkPHP框架] + * + * 注:要求框架5.0及以上 + * + * author:wh + * + * @param string $prefix_version 框架版本号前缀 eg:thinkPHP5.0就填写5,thinkPHP5.1就填写5.1,thinkPHP6.0就填写6 + * + * @return mixed + * + * 示例代码 eg: + * + * $version = Tools::think_version(5); + * + * dump($version); + */ + static function think_version(string $prefix_version) + { + //框架各个版本获取方式 + if ($prefix_version == '5') {//thinkPHP5.0 + return THINK_VERSION; + } else{ + //thinkPHP5.1+ + return \think\facade\App::version(); + } + } + + + /** + * desc:获取框架运行时目录[兼容各个ThinkPHP框架] + * + * 注:要求框架5.0及以上 + * + * author:wh + * + * + * @param string $prefix_version 框架版本号前缀 eg:thinkPHP5.0就填写5,thinkPHP5.1就填写5.1,thinkPHP6.0就填写6 + * + * + * @return mixed + * + * 示例代码 eg: + * + * $version = Tools::think_version(5); + * + * dump($version); + */ + static function think_runtime_dir() + { + //各个版本框架获取运行目录方式 + //if ($prefix_version == '5') {//thinkPHP5.0 + // return RUNTIME_PATH; + //} else if($prefix_version == '5.1'){//thinkPHP5.1+ + // return Env::get('runtime_path'); + //}else if($prefix_version == '6.0'){ + // return runtime_path(); + //} + //throw new Exception('不支持的框架版本'); + //一句代码秒杀 + return self::get_root_path().'runtime/'; + } + + /** + * desc:清空全部缓存 + * author:wh + */ + static function clear_cache(){ + $dirPath = self::get_root_path().'runtime/cache'; + self::rm_dir($dirPath); + } + private static function rm_dir($dirPath){ + if (!is_dir($dirPath)) { + throw new InvalidArgumentException("$dirPath must be a directory"); + } + if (substr($dirPath, strlen($dirPath) - 1, 1) != '/') { + $dirPath .= '/'; + } + $files = glob($dirPath . '*', GLOB_MARK); + foreach ($files as $file) { + if (is_dir($file)) { + self::rm_dir($file); + } else { + unlink($file); + } + } + rmdir($dirPath); + } + + /** + * UTF8字符串加密 + * @param string $string + * @return string + */ + static function encode($string) + { + list($chars, $length) = ['', strlen($string = iconv('utf-8', 'gbk', $string))]; + for ($i = 0; $i < $length; $i++) { + $chars .= str_pad(base_convert(ord($string[$i]), 10, 36), 2, 0, 0); + } + return $chars; + } + + /** + * UTF8字符串解密 + * @param string $string + * @return string + */ + static function decode($string) + { + $chars = ''; + foreach (str_split($string, 2) as $char) { + $chars .= chr(intval(base_convert($char, 36, 10))); + } + return iconv('gbk', 'utf-8', $chars); + } + + /** + * desc:换行输出(一般用于调试 eg:在循环中输出) + * author:wh + */ + static function br_echo($msg) + { + echo '
    '; + echo $msg; + echo '
    '; + } + + + /** + * 数组转xml字符串 + * @throws + **/ + static function to_xml($data) + { + if (!is_array($data) || count($data) <= 0) { + throw new \Exception("数组数据异常!"); + } + + $xml = ""; + foreach ($data as $key => $val) { + if (is_numeric($val)) { + $xml .= "<" . $key . ">" . $val . ""; + } else { + $xml .= "<" . $key . ">"; + } + } + $xml .= ""; + return $xml; + } + /** + * desc:xml转换为数组 + * author:wh + * @param $xml + * @return mixed + */ + static function xml_to_array($xml) + { + //禁止引用外部xml实体 + libxml_disable_entity_loader(true); + $values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); + return $values; + } + /** + * desc:返回xml 对象 + * author:wh + * @param bool $res + * @return string + */ + static function xml_res_return($res = true) + { + $tmp_suc = $res ? 'SUCCESS' : 'FAIL'; + $tmp_ok = $res ? 'OK' : 'ERR'; + $xml = " + + + "; + + return simplexml_load_string($xml); + } + + /** + * XML编码 + * @param mixed $data 数据 + * @param string $encoding 数据编码 + * @param string $root 根节点名 + * @return string + */ + static function xml_encode($data, $encoding = 'utf-8', $root = 'xml') + { + $xml = ''; + $xml .= '<' . $root . '>'; + $xml .= self::data_to_xml($data); + $xml .= ''; + return $xml; + } + + /** + * 数据XML编码 + * @param mixed $data 数据 + * @return string + */ + static function data_to_xml($data) + { + $xml = ''; + foreach ($data as $key => $val) { + is_numeric($key) && $key = "item id=\"$key\""; + $xml .= "<$key>"; + $xml .= (is_array($val) || is_object($val)) ? self::data_to_xml($val) : $val; + list($key,) = explode(' ', $key); + $xml .= ""; + } + return $xml; + } + + /** + * 生成随机字符串 + * @param int $length 生成长度 + * @param int $type 生成类型:0-小写字母+数字,1-小写字母,2-大写字母, + * 3-数字,4-小写+大写字母,5-小写+大写+数字 + * @return string + */ + static function rand_str($length = 8, $type = 0) + { + $a = 'abcdefghijklmnopqrstuvwxyz'; + $A = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $n = '0123456789'; + + switch ($type) { + case 1: + $chars = $a; + break; + case 2: + $chars = $A; + break; + case 3: + $chars = $n; + break; + case 4: + $chars = $a . $A; + break; + case 5: + $chars = $a . $A . $n; + break; + default: + $chars = $a . $n; + } + + $str = ''; + for ($i = 0; $i < $length; $i++) { + $str .= $chars[mt_rand(0, strlen($chars) - 1)]; + } + return $str; + } + + /** + * @deprecated 不推荐使用,请使用arr_sort_by_field + * desc:php二维数组排序(升、降) + * author:wh + * @param $data 数据源(必须是二维数组) + * @param $field 要排序的字段(必须) eg:年龄或者价格 + * @param bool $sort 排序方式 + * @param bool $unique_field 指定唯一字段 eg:例如userID一般都是唯一的 + * @return array 返回排序后的数据源 + */ + static function arr_arr_to_sort($data, $field, $sort = true, $unique_field) + { + //取出排序源 + $field_arr_key = array_column($data, $unique_field); + $field_arr_val = array_column($data, $field); + + $source_arr = []; + foreach ($field_arr_key as $key => $val) { + $source_arr[$val] = $field_arr_val[$key]; + } + + //排序 + if ($sort) arsort($source_arr); + else asort($source_arr); + //重组数据 + $new_arr = []; + foreach ($source_arr as $k => $v) { + foreach ($data as $a => $b) { + if ($k == $b[$unique_field]) { + array_push($new_arr, $b); + } + } + } + return $new_arr; + } + + /** + * desc:php二维数组排序(升、降) + * author:wh + * @param array $items 源数组 + * @param string $field_name 要排序的字段名 + * @param boolean $type true升序,false降序 + */ + static function arr_arr_sort_by_field(&$items, $field_name, $type=true) { + usort($items, function ($a, $b) use ($field_name,$type){ + if($type){ + return $a[$field_name] <=> $b[$field_name];//升序 + }else{ + return $b[$field_name] <=> $a[$field_name];//降序 + } + }); + } + /** + * desc:日期换算为 今天 昨天 2天前 一周前 一个月前 一年前 + * author:wh + * @param $date 时间戳 + */ + static function date_revert_to_string_type($date) + { + $date = $date * 1; + $arr = [ + 0 => '今天', + 1 => '昨天', + 2 => '前天', + 7 => '一周前', + 30 => '一个月前', + 365 => '一年前', + -1 => '很久以前', + ]; + //今天 + $today = strtotime(date('Y-m-d')); + if (($date - $today) >= 0) { + return $arr[0]; + } else if (($date - $today) < 0 && ($today - $date) <= 86400) { + return $arr[1]; + } else if (($date - $today) < 0 && ($today - $date) <= 86400 * 2) { + return $arr[2]; + } else if (($date - $today) < 0 && ($today - $date) <= 86400 * 7) { + return $arr[7]; + } else if (($date - $today) < 0 && ($today - $date) <= 86400 * 30) { + return $arr[30]; + } else if (($date - $today) < 0 && ($today - $date) <= 86400 * 365) { + return $arr[365]; + } + return $arr[-1]; + } + + /** + * desc:返回由二维数组的其中两个字段(键)组成的一维数组 + * author:wh + */ + static function key_val_arr($data, $key, $key2) + { + $arr = array_column($data, $key); + $arr2 = array_column($data, $key2); + //$tmp = []; + //foreach ($arr as $k => $v) { + // $tmp[$v] = $arr2[$k]; + //} + //修改为-更加高效(数组1和数组2的长度必须一致!) + return array_combine($arr,$arr2); + } + + /** + * desc:查找二维数组中某个字段值为val的数据,并返回这条数据(一维数组) + * author:wh + */ + static function arr_data_one($data, $field, $val) + { + foreach ($data as $k => $v) { + if ($v[$field] == $val) { + return $v; + } + } + return false; + } + + /** + * description:unicode编码 + * author:wh + * @param $str + * @return string + */ + static function unicodeEncode($str) + { + //split word + preg_match_all('/./u', $str, $matches); + + $unicodeStr = ""; + foreach ($matches[0] as $m) { + //拼接 + $unicodeStr .= "&#" . base_convert(bin2hex(iconv('UTF-8', "UCS-4", $m)), 16, 10); + } + return $unicodeStr; + } + + /** + * desc:返回unicode解码后的中文 + * $name = '\u65b0\u6d6a\u5fae\u535a'; + * $data = unicodeDecode($name); //输出新浪微博 + * 配合 unicodeDecode 使用 + * author:wh + * @param $match + * @return string + */ + static function replace_unicode_escape_sequence($match) + { + return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UCS-2BE'); + } + + /** + * desc:中文被unicode编码后了的数据,解码出中文 + * 使用Unicode解码 + * author:wh + * @param $data + * @return null|string|string[] + */ + static function unicodeDecode($data) + { + return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', 'replace_unicode_escape_sequence', $data); + } + + /** + * @deprecated 废弃 + * + * desc:返回11位数编号,末位一位表示年份,第4位开始3位数表示几月几日 + * eg:28431855779;9=2019,318=11月15日 + * 注意:随机数都可能会重复 + * author:wh + * @return string + * @throws \Exception + */ + static function create_number() + { + $day = date('z', mktime(0, 0, 0, date('m'), date('d'), date('Y'))); + $day = sprintf('%03d', $day); + $sp = str_split(date('y')); + + $rand = random_int(1000000, 9999999); + $str1 = substr($rand, 0, 3); + $str2 = substr($rand, 3, strlen($rand)); + + return ($str1 . $day . $str2 . $sp[1]); + } + + /** + * @deprecated 废弃 + * + * [create_batch 生成交易批次号] + * @Author + * @Date 2019-12-26 + * @param [type] $merchant_basic_id [商户id] + * @param [type] $last_batch [最新的批次号] + * @return [type] [description] + */ + static function create_batch($merchant_basic_id, $last_batch) + { + $basic_no = str_pad($merchant_basic_id, 4, '0', STR_PAD_LEFT); + if (empty($last_batch) || substr($last_batch, 0, 8) != date('Ymd')) { + $batch = date('Ymd') . '-' . $basic_no . '01'; + } else { + $batch_no = (int)substr($last_batch, -2); //批次号 末两位字符串数字转换为整数 + $batch = date('Ymd') . '-' . $basic_no . str_pad(($batch_no + 1), 2, '0', STR_PAD_LEFT); + } + return $batch; + } + + /** + * @deprecated 废弃 + * + * [create_serial_number 生成流水号] + * @Author + * @Date 2019-11-27 + * @return [type] [description] + */ + static function create_serial_number() + { + list($t1, $t2) = explode(' ', microtime()); + $microtime = sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000); + $micro_second = substr($microtime, -3); + $rand_str = self::rand_str(6, 3); + return date('YmdHis') . $micro_second . $rand_str; + } + + /** + * @deprecated 废弃 + * + * [create_order_number 生成单据编号 22位] + * @Author wh + * @Date 2019-11-27 + * @return [type] [description] + */ + static function create_order_number() + { + list($t1, $t2) = explode(' ', microtime()); + $microtime = sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000); + $micro_second = substr($microtime, -3); + $rand_str = self::rand_str(8, 3); + return time() . $micro_second . $rand_str; + } + + /** + * @deprecated 废弃 + * + * [create_order_number 生成单据编号 25位] + * @Author wh + * @Date 2019-11-27 + * @return [type] [description] + */ + static function create_order_no() + { + list($t1, $t2) = explode(' ', microtime()); + $microtime = sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000); + $micro_second = substr($microtime, -3); + $rand_str = self::rand_str(8, 3); + return date('YmdHis') . $micro_second . $rand_str; + } + + /** + * @deprecated 废弃 + * + * [create_invoice_apply_number 生成发票申请编号] + * @Author + * @Date 2019-12-23 + * @return [type] [description] + */ + static function create_invoice_apply_number() + { + list($t1, $t2) = explode(' ', microtime()); + $microtime = sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000); + $rand_str = self::rand_str(4, 3); + return $microtime . $rand_str; + } + + /** + * desc:获取当前时间 + * author:wh + * @return false|string + */ + static function get_now_date() + { + return date('Y-m-d H:i:s'); + } + + /** + * desc:获取格林威治初始时间,字符串格式 + * author:wh + * @return string + */ + static function get_init_time() + { + return '1970-01-01 10:00:00'; + } + + /** + * desc:将时间戳转换为字符串的日期格式 + * + * author:wh + * @param int $time eg:1634118487(精确到秒) + * @param string $format eg: Y-m-d 、 Y-m-d H:i:s等 + * @return false|string eg: 2021-10-13 17:48:07 + */ + static function time_to_date(int $time, string $format='Y-m-d H:i:s'){ + return date($format, $time); + } + + /** + * @deprecated 参考http/Curl.php + * + * cURL 网络链接库 + * GET + * author:wh + * @param $url + * @return bool|int|string + */ + static function curl_get($url, $timeout = 60) + { + + $header = array( + 'Accept: application/json', + ); + $curl = curl_init(); + //curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE); + //curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); + //curl_setopt($curl, CURLOPT_SSLVERSION, 3); + //设置抓取的url + curl_setopt($curl, CURLOPT_URL, $url); + //设置头文件的信息作为数据流输出 + curl_setopt($curl, CURLOPT_HEADER, 0); + // 超时设置,以秒为单位 + curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); + + // 超时设置,以毫秒为单位 + // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500); + + // 设置请求头 + curl_setopt($curl, CURLOPT_HTTPHEADER, $header); + //设置获取的信息以文件流的形式返回,而不是直接输出。 + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); + //执行命令 + $data = curl_exec($curl); + + // 显示错误信息 + if (curl_error($curl)) { + //print "Error: ".curl_errno($curl).'-' . curl_error($curl); + //返回错误码 + return ['code' => curl_errno($curl), 'msg' => curl_error($curl)]; + } else { + //关闭句柄 + curl_close($curl); + // 返回的内容 + return ['code' => 200, 'msg' => 'ok', 'data' => $data]; + } + } + + /** + * @deprecated 参考http/Curl.php + * + * cURL 网络链接库[表单] + * POST + * author:wh + * @param $url 是请求的链接 + * @param $postdata 传输的数据,数组格式 + * @return bool|int|string + */ + static function curl_to_post($url, $postdata) + { + + $timeout = 3; + $connect_timeout = 2; + $set_time_limit = 5; + if ($timeout + $connect_timeout < $set_time_limit) throw new \Exception('脚本超时值必须大于等于连接超时与请求处理超时之和'); + set_time_limit($set_time_limit); + $header = array( + 'Accept: application/json', + ); + + //初始化 + $curl = curl_init(); + //设置抓取的url + curl_setopt($curl, CURLOPT_URL, $url); + //设置头文件的信息作为数据流输出 + curl_setopt($curl, CURLOPT_HEADER, 0); + //设置获取的信息以文件流的形式返回,而不是直接输出。 + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + // 超时设置 + curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); + //发起连接前等待的时间,如果设置为0,则无限等待。 + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $connect_timeout); + + // 超时设置,以毫秒为单位 + // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500); + + // 设置请求头 + curl_setopt($curl, CURLOPT_HTTPHEADER, $header); + + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE); + + //设置post方式提交 + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($postdata)); + //执行命令 + $data = curl_exec($curl); + + // 显示错误信息 + if (curl_error($curl)) { + //返回错误码 + return ['code' => curl_errno($curl), 'msg' => curl_error($curl)]; + } else { + //关闭句柄 + curl_close($curl); + // 返回的内容 + return ['code' => 200, 'msg' => 'ok', 'data' => $data]; + } + } + + /** + * @deprecated 参考http/Curl.php + * + * cURL 网络链接库[json] + * POST + * 特别注意:有些情况需要将$postdata参数用json_encode()函数处理一下 + * author:wh + * @param $url 是请求的链接 + * @param $postdata 传输的数据,数组格式 + * @return bool|int|string + */ + static function curl_post($url, $postdata, $timeout = 60) + { + + $header = array( + 'Accept: application/json', + ); + + //初始化 + $curl = curl_init(); + //设置抓取的url + curl_setopt($curl, CURLOPT_URL, $url); + //设置头文件的信息作为数据流输出 + curl_setopt($curl, CURLOPT_HEADER, 0); + //设置获取的信息以文件流的形式返回,而不是直接输出。 + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + // 超时设置 + curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); + + // 超时设置,以毫秒为单位 + // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500); + + // 设置请求头 + curl_setopt($curl, CURLOPT_HTTPHEADER, $header); + + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE); + + //设置post方式提交 + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $postdata); + //执行命令 + $data = curl_exec($curl); + + // 显示错误信息 + if (curl_error($curl)) { + //返回错误码 + return ['code' => curl_errno($curl), 'msg' => curl_error($curl)]; + } else { + //关闭句柄 + curl_close($curl); + // 返回的内容 + return ['code' => 200, 'msg' => 'ok', 'data' => $data]; + } + } + + /** + * @deprecated 参考http/Curl.php + * + * cURL 网络链接库[流] + * [请求Java接口] + * 思考:1、Java端如果用文件流的方式去获取数据,调用此方法 + * @param $url + * @param $postdata + * @param int $timeout + * @return array + * @link + * @example + * @see + */ + static function java_curl_post_file_request($url, $postdata, $timeout = 60) + { + + $header = array( + 'Accept: application/json', + ); + + //初始化 + $curl = curl_init(); + //设置抓取的url + curl_setopt($curl, CURLOPT_URL, $url); + //设置头文件的信息作为数据流输出 + curl_setopt($curl, CURLOPT_HEADER, 0); + //设置获取的信息以文件流的形式返回,而不是直接输出。 + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + // 超时设置 + curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); + + // 超时设置,以毫秒为单位 + // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500); + + // 设置请求头 + curl_setopt($curl, CURLOPT_HTTPHEADER, $header); + + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE); + + //设置post方式提交 + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $postdata); + //执行命令 + $data = curl_exec($curl); + + // 显示错误信息 + if (curl_error($curl)) { + //返回错误码 + return ['code' => curl_errno($curl), 'msg' => curl_error($curl)]; + } else { + //关闭句柄 + curl_close($curl); + // 返回的内容 + return ['code' => 200, 'msg' => 'ok', 'data' => $data]; + } + } + + /** + * desc:将字符串按大写字母分割为数组 + * author:wh + * @param $string + * @return array[]|false|string[] + */ + static function letterByUpperSplit($string) + { + return preg_split("/(?=[A-Z])/", $string); + } + + /** + * desc:将下划线命名 转换为 驼峰式命名 + * author:wh + * @param string $str + * @param bool $ucfirst + * @return mixed|string + */ + static function convertUnderLine(string $str, $ucfirst = true) + { + $str = ucwords(str_replace('_', ' ', $str)); + $str = str_replace(' ', '', lcfirst($str)); + return $ucfirst ? ucfirst($str) : $str; + } + + /** + * desc:将驼峰式命名 转换为 下划线命名 + * author:wh + * @param $str + * @return string + */ + static function convertLineUnder($str) + { + return strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $str)); + } + + + /** + * desc:获取数字的英文单词 + * author:wh + * @param null $key + * @return array|mixed + */ + static function getNumEn($key = null) + { + $num_en = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten']; + if (!is_null($key)) return $num_en[$key]; + return $num_en; + } + + + /** + * @param $filePath //下载文件的路径 + * @param $fileName //下载文件的名称 + * @param int $readBuffer //分段下载 每次下载的字节数 默认1024bytes + * @param array $allowExt //允许下载的文件类型 + * @return void + */ + static function downloadFile($filePath, $fileName = '', $readBuffer = 1024, $allowExt = ['jpeg', 'jpg', 'peg', 'gif', 'zip', 'rar', 'apk']) + { + //检测下载文件是否存在 并且可读 + if (!is_file($filePath) && !is_readable($filePath)) { + return false; + } + //检测文件类型是否允许下载 + $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION)); + if (!in_array($ext, $allowExt)) { + return false; + } + //设置头信息 + //声明浏览器输出的是字节流 + header('Content-Type: application/octet-stream'); + //声明浏览器返回大小是按字节进行计算 + header('Accept-Ranges:bytes'); + //告诉浏览器文件的总大小 + $fileSize = filesize($filePath);//坑 filesize 如果超过2G 低版本php会返回负数 + header('Content-Length:' . $fileSize); //注意是'Content-Length:' 非Accept-Length + //声明下载文件的名称 + header('Content-Disposition:attachment;filename=' . ($fileName ? $fileName : basename($filePath)));//声明作为附件处理和下载后文件的名称 + //获取文件内容 + $handle = fopen($filePath, 'rb');//二进制文件用‘rb’模式读取 + while (!feof($handle)) { //循环到文件末尾 规定每次读取(向浏览器输出为$readBuffer设置的字节数) + echo fread($handle, $readBuffer); + } + fclose($handle);//关闭文件句柄 + exit; + } + + /** + * 文件直接下载[支持本地文件和远程文件] + * sys_download_file('web服务器中的文件地址', 'test.jpg'); + * sys_download_file('远程文件地址', 'test.jpg', true); + * + * @param string $path 文件地址:针对当前服务器环境的相对或绝对地址 + * @param string $name 下载后的文件名(包含扩展名) + * @param boolean $isRemote 是否是远程文件(通过 url 无法获取文件扩展名的必传参数 name) + * @param string $proxy 代理,适用于需要使用代理才能访问外网资源的情况 + * @return true|false 下载结果 + */ + static function downloadUrl($path, $name = null, $isRemote = false, $contentType = 'binary_system', $proxy = '') + { + $fileRelativePath = $path; + $savedFileName = $name; + if (!$savedFileName) { + $file = pathinfo($path); + if (!empty($file['extension'])) { + $savedFileName = $file['basename']; + } else { + throw new \Exception("Extension get failed, parameter 'name' is required!"); + } + } + + // 如果是远程文件,先下载到本地 + if ($isRemote) { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $path); + if ($proxy != '') { + curl_setopt($ch, CURLOPT_PROXY, $proxy); + } + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 50); + $fileContent = curl_exec($ch); + curl_close($ch); + + // 写入临时文件 + $fileRelativePath = tempnam(sys_get_temp_dir(), 'DL'); + $fp = @fopen($fileRelativePath, 'w+'); + fwrite($fp, $fileContent); + } + + $contentTypeArr = [ + //二进制 + 'binary_system' => 'application/octet-stream', + //apk安装包 + 'apk' => 'application/vnd.android.package-archive', + ]; + // 执行下载 + if (is_file($fileRelativePath)) { + //传输类型 + header('Content-Description: File Transfer'); + //Content-Type + if (empty($contentTypeArr[$contentType])) { + //默认二进制格式 + header('Content-Type: application/octet-stream'); + } else { + //根据参数指定下载格式 + header('Content-Type: ' . $contentTypeArr[$contentType]); + } + //文件大小 + header('Content-Length:' . filesize($fileRelativePath)); + + if (preg_match('/MSIE/', $_SERVER['HTTP_USER_AGENT'])) { // for IE + header('Content-Disposition: attachment; filename="' . rawurlencode($savedFileName) . '"'); + } else { + header('Content-Disposition: attachment; filename="' . $savedFileName . '"'); + } + readfile($fileRelativePath); + if ($isRemote) { + unlink($fileRelativePath); // 删除下载远程文件时对应的临时文件 + } + exit; + } else { + throw new \Exception('Invalid file: ' . $fileRelativePath); + } + + } + + /** + * @param $v + * @return bool + * @deprecated 迁移到Validate.php类中 + * desc:是否是url + * author:wh + */ + static function is_url($v) + { + $pattern = "#(http|https)://(.*\.)?.*\..*#i"; + return preg_match($pattern, $v) ? true : false; + } + + /** + * [本地文件] + * desc:根据一个完整的url地址,获取资源文件大小 + * author:wh + * @param $url [一个可访问的站内合法地址。eg: http://39.101.214.157/static/upload/d4e271103e4536db/1d9f980651cdbc35.mp4 ] + * @param string $domain 域名结束不含 "/" 符号 + * @return bool|false|int 文件大小 + */ + static function getFileSize($url, $domain = '') + { + $str = str_replace($domain ? $domain : request()->domain(), '', $url); + + $root_path = explode('vendor',__DIR__)[0]; + $mac_url = $root_path . 'public' . $str; + if (!is_file($mac_url)) return false; + return filesize($mac_url); + } + + /** + * [本地文件] + * desc:根据一个完整的url地址,获取资源文件大小 + * 注:适用于thinkPHP6 + * author:wh + * @param $url [一个可访问的站内合法地址。eg: http://39.101.214.157/static/upload/d4e271103e4536db/1d9f980651cdbc35.mp4 ] + * @param string $domain 域名结束不含 "/" 符号 + * @param string $prefix_version 框架版本号前缀 eg:thinkPHP5.0就填写5,thinkPHP5.1就填写5.1,thinkPHP6.0就填写6 + * @return bool|false|int 文件大小 + */ + static function toGetFileSize($url, $domain = '') + { + $str = str_replace($domain ? $domain : request()->domain(), '', $url); + + + $root_path = explode('vendor',__DIR__)[0]; + $mac_url = $root_path . 'public' . $str; + if (!is_file($mac_url)) return false; + return filesize($mac_url); + } + + + /** + * desc:组装$_FILES原始数据,便于业务逻辑操作 + * author:wh + * @param $name + * @return array + */ + static function groupFile($name) + { + $files = $_FILES[$name]; + $tmp = []; + for ($j = 0; $j < count($files['name']); $j++) { + $t['name'] = $files['name'][$j]; + $t['type'] = $files['type'][$j]; + $t['tmp_name'] = $files['tmp_name'][$j]; + $t['error'] = $files['error'][$j]; + $t['size'] = $files['size'][$j]; + $tmp[] = $t; + } + return $tmp; + } + + /** + * [获取协议类型] + * + * @return string + * @link + * @example + * @see + */ + static function getHttpType() + { + return input('server.REQUEST_SCHEME') . '://' . input('server.SERVER_NAME'); + } + + /** + * @param $str + * @return bool + * @deprecated 迁移到Validate.php类中 + * [是否全部大写] + * + * @example + * @see + * @link + */ + static function isUpper($str) + { + + return preg_match('/^[A-Z]+$/', $str) ? true : false; + } + + /** + * @param $str + * @return bool + * @deprecated 迁移到Validate.php类中 + * [是否全部小写] + * + * @example + * @see + * @link + */ + static function isLower($str) + { + return preg_match('/^[a-z]+$/', $str) ? true : false; + } + + /** + * 将字符串分割为数组 + * @param string $str 字符串 + * @return array 分割得到的数组 + */ + static function mbStrSplit($str) + { + return preg_split('/(?ip(); + list($t1, $t2) = explode(' ', microtime()); + $write_time = date('Y-m-d H:i:s',$t2).' '.sprintf('%.0f',$t1*1000); + + $str = "\n" . $write_time . ' | '. $ip.' | ' . request()->baseUrl() . "\n"; + + $data = is_object($data) || is_array($data) ? $str . json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $str . $data; + + file_put_contents($filepath, $data, FILE_APPEND); + } + + /** + * desc:获取项目根目录 + * + * * 巧妙兼容各个tp版本 + * + * author:wh + * @return mixed|string + */ + static function get_root_path(){ + return explode('vendor',__DIR__)[0]; + } + /** + * desc:获取项目名称 + * + * * 巧妙兼容各个tp版本 + * + * author:wh + * @return mixed|string + */ + static function get_project_name(){ + $root_path = explode('vendor',__DIR__)[0]; + $root_path = str_replace('\\','/',$root_path); + $exp_arr = explode('/',$root_path); + return $exp_arr[count($exp_arr)-2]; + } + + /** + * 记录日志到文本-日志文件可即时删除 + * + * 注:本方法仅适用于thinkPHP5.1+ + * + * @param string $data 数据 + * + * @param string $dirname 指定目录. 默认result_log, 位置:runtime/log/ + * + */ + static function log_to_write_text($data = 'test', $dirname = '') + { + if (!$dirname) { + $dirname = 'result_log';//存储在runtime/log/下面 + } + + $root_path = explode('vendor',__DIR__)[0]; + $runtime_path = $root_path.'runtime/'; + + if ($dirname) { + $filepath = $runtime_path . 'log/' . ($dirname).'/'.date('Ymd');//运行时日志 + }else{ + $filepath = $runtime_path . 'log/'.date('Ym');//运行时日志 + } + + if (!file_exists($filepath)) { + mkdir($filepath, 0777, true); + } + if($dirname){ + $filepath .= '/log' . date('YmdH') . '.txt'; + }else{ + $filepath .= '/' . date('d') . '_error.log'; + } + if(!file_exists($filepath)){ + //创建 + touch($filepath); + // 设置文件权限 + chmod($filepath, 0777); + } + $ip = request()->ip(); + list($t1, $t2) = explode(' ', microtime()); + $write_time = date('Y-m-d H:i:s',$t2).' '.sprintf('%.0f',$t1*1000); + + $str = "\n" . $write_time . ' | '. $ip.' | ' . request()->baseUrl() . "\n"; + + $data = is_object($data) || is_array($data) ? $str . json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $str . $data; + + file_put_contents($filepath, $data, FILE_APPEND); + } + + /** + * 记录数据库操作日志到文本-日志文件可即时删除 + * + * 注:本方法仅适用于thinkPHP5.0 + * + * @param string $data 数据(可以是数组) + * + * @param string $dirname 指定目录. 默认result_log, 位置:runtime/log/ + */ + static function log_to_operate_sql(string $data, string $dirname) + { + $runtime_path = RUNTIME_PATH; + + $filepath = $runtime_path . 'log/' . ('sql_'.$dirname).'/'.date('Ymd');//运行时日志 + if (!file_exists($filepath)) { + mkdir($filepath, 0777, true); + } + + $filepath .= '/log' . date('Ymd') . '.sql'; + if(!file_exists($filepath)){ + //创建 + touch($filepath); + // 设置文件权限 + chmod($filepath, 0777); + } + $str = "\n -- " . date('Y-m-d H:i:s')."\n"; + + $data = is_object($data) || is_array($data) ? $str . json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $str . $data; + + $data .= ';'; + + file_put_contents($filepath, $data, FILE_APPEND); + } + + /** + * 记录数据库操作日志到文本-日志文件可即时删除 + * + * 注:本方法仅适用于thinkPHP5.1+ + * + * @param string $data 数据 + * + * @param string $dirname 指定目录. 默认result_log, 位置:runtime/log/ + * + */ + static function log_to_operate_sqls(string $data, string $dirname) + { + + $runtime_path = \think\facade\App::getRuntimePath(); + + $filepath = $runtime_path . 'log/' . ('sql_'.$dirname).'/'.date('Ymd');//运行时日志 + if (!file_exists($filepath)) { + mkdir($filepath, 0777, true); + } + $filepath .= '/log' . date('Ymd') . '.sql'; + if(!file_exists($filepath)){ + //创建 + touch($filepath); + // 设置文件权限 + chmod($filepath, 0777); + } + $str = "\n -- " . date('Y-m-d H:i:s')."\n"; + + $data = is_object($data) || is_array($data) ? $str . json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $str . $data; + + $data .= ';'; + + file_put_contents($filepath, $data, FILE_APPEND); + } + + /** + * desc: + * + * author:wh + * @param array $info + */ + static function log(array $info){ + request()->LogObj->write($info); + } + /** + * desc:秒转换为n时n分n秒 + * author:wh + * @param int $s 时间戳(秒) + * @return string + */ + static function secondtostr(int $s) + { + $h = 60 * 60; + $m = 60; + + $exp_h = explode('.', $s / $h); + + $hour = $exp_h[0];//小时数 + + $exp_m = explode('.', ($s - $hour * $h) / $m);//$exp_m[0]; + $minute = $exp_m[0];//分钟数 + + $second = ($s - $hour * $h - $minute * $m); + + return ($hour ? $hour : 0) . '小时' . ($minute ? $minute : 0) . '分钟' . ($second ? $second : 0) . '秒'; + } + + /** + * desc:加密 + * author:wh + * @param string $str + * @return string + */ + static function _md5(string $str) + { + return md5(md5($str)); + } + + /** + * desc:浏览器环境检测 + * author:wh + * @return string + */ + static function web_env_type() + { + if (preg_match('~micromessenger~i', request()->header('user-agent'))) { + return 'wechat'; + } else if (preg_match('~alipay~i', request()->header('user-agent'))) { + return 'alipay'; + } + return 'normal'; + } + + /** + * desc:判断设备类型 + * author:wh + * @return string + */ + static function get_device_type() + { + //全部变成小写字母 + $agent = strtolower($_SERVER['HTTP_USER_AGENT']); + $type = 'other'; + //分别进行判断 + if(strpos($agent, 'iphone') || strpos($agent, 'ipad')) + { + $type = 'ios'; + } + + if(strpos($agent, 'android')) + { + $type = 'android'; + } + return $type; + } + + + /** + * desc:签名 + * + * 【注意】: + * 鉴权时,如果数据提交方只用了参数中的部分数据进行签名,那么在校验系统中, + * 签名校验是不合法的。所以,请将你的所有参数进行签名,再传输。 + * + * 签名规则: + * 1:对键值对数组按照键升序排序 + * 2:连接成字符串$tmpStr(去掉空值的键值对、去掉数组和对象) + * 3:有token则在字符串后连接token + * 4:二次md5加密,eg: md5(md5($tmpStr)) + * + * 提示:可按照实际业务需求,对数据处理后再加签 + * + * author:wh + * @param array $sign_params 签名参数 + * @param string $token 加密token + * @return string + */ + static function signature(array $sign_params, string $token='') + { + //对关联数组按照键升序排序 + ksort($sign_params); + //连接成字符串 + $tmpStr = ""; + foreach ($sign_params as $k => $v) { + if($v == ''){ + continue; + } + if(is_array($v) || is_object($v)){ + continue; + } + $tmpStr .= $k . $v; + } + if($token) $tmpStr.='token'.$token; + return md5(md5($tmpStr));//加密 + } + + /** + * desc:CNY 人民币元转换为分,单位元 -> 分 + * author:wh + * @param $yuan + * @return float|int + */ + static function money_yuan_to_cent($yuan){ + return $yuan * 100; + } + + /** + * desc:CNY 人民币分转换为元,单位分 -> 元 + * author:wh + * @param $cent + * @return float|int + */ + static function money_cent_to_yuan($cent){ + return $cent / 100; + } + + + /** + * desc:创建订单号 + * + * author:wh + * @param string $pay_type + * @return string 21位长度字符串 + */ + static function to_create_order_no(string $pay_type){ + if(empty($pay_type)){ + return ''; + } + $prefix = substr($pay_type,0,2); + $randstr = Tools::rand_str(6); + $time = Tools::getMillisecond(); + return $prefix.$randstr.$time; + } + + /** + * desc:将毫秒时间戳转换为时间 + * + * 将毫秒时间戳转换为日期格式 + * + * author:wh + * @param int $milli_time 毫秒时间戳 + * @param string $date_format 目标格式 默认:Y-m-d H:i:s + * @return false|string + */ + static function milli_time_to_date(int $milli_time,string $date_format = 'Y-m-d H:i:s'){ + return date($date_format, ceil($milli_time/1000)); + } + + + /** + * desc:按规则拆分(中文)字符串 + * + * author:wh + * @param string $pattern 规则(正则表达式);eg1:(//u 表示拆分成单个字符) ;eg2:(/ /u 表示使用空格拆分字符串); 你也可使用其它正则。 + * @param string $str 被拆分的字符串 + * @param int $limit 限制条件,-1 表示无限制;正数 表示最多返回 limit 个子串; + * @param int $flags 拆分标记 + * @return array|false|string[] + * + * 主要函数功能说明preg_split: + 说明 array preg_split ( string $pattern, string $subject [, int $limit [, int $flags]] ) + 返回一个数组,包含 subject 中沿着与 pattern 匹配的边界所分割的子串。 + 如果指定了 limit,则最多返回 limit 个子串,如果 limit 是 -1,则意味着没有限制,可以用来继续指定可选参数 flags。 + flags 可以是下列标记的任意组合(用按位或运算符 | 组合): + PREG_SPLIT_NO_EMPTY + 如果设定了本标记,则 preg_split() 只返回非空的成分。 + PREG_SPLIT_DELIM_CAPTURE + 如果设定了本标记,定界符模式中的括号表达式也会被捕获并返回。本标记添加于 PHP 4.0.5。 + PREG_SPLIT_OFFSET_CAPTURE + 如果设定了本标记,对每个出现的匹配结果也同时返回其附属的字符串偏移量。注意这改变了返回的数组的值,使其中的每个单元也是一个数组,其中第一项为匹配字符串,第二项为其在 subject 中的偏移量。本标记自 PHP 4.3.0 起可用。 + * + * 欢迎测试。 + */ + static function preg_split_str(string $str, string $pattern='//u', int $limit=-1, int $flags=PREG_SPLIT_NO_EMPTY){ + + //默认情况下将字符串拆分为单个字符并存储在一维数组中 + //变化较多的可能是变量$pattern + return preg_split($pattern, $str, $limit, $flags); + } + + + /** + * desc:返回用户的注册时间和对应的登录时间 + * + * 注意:【因为登录时间值取的是昨天,所以此功能要今日执行】 + * + * 备注:昨日注册用户今日登录就是次日留存,7日留存表示8天前(不算今天)注册的用户昨天登录,依次类推 + * + * 问:为什么不在每天23:59:59秒执行呢? + * 答:因为不能保证1秒就能执行玩业务流程,而且网络请求也需要时间,一秒时间太少(除非你有100%把握) + * + * author:wh + * + * @param int $keep_type 留存查询标识,数字,2表示2日留存,3表示3日留存,依次类推 + * + * @param string $date_time 从给定的日期开始计算次日到7日留存时间,格式为:2021-10-30,那么计算得到的结果就是22日注册用户的7日留存时间 + * + * @return array 返回注册时间和登录日期格式,按需(转换)使用 + * + * 此功能已在现有项目生产环境中稳定运行 + * + * 案例: + * //初始化日期处理工具(对统计功能非常友好) + $date = new Date(); + //因为是统计功能,所以这里设置为Y-m-d格式 + $date->date_format = 'Y-m-d'; + //统计25日7日留存数据 + $date_time = '2021-10-25'; + //这里+8天是对应的25日,如果不加则从:25日-8日(不算代码执行当日)=17日开始计算次日,3日,4日......留存时间 + $date_time = $date->addTime(8,'d',strtotime($date_time)); + //统计留存的业务逻辑方法 + $logic = (new KeepStatisticsLogic()); + $logic->doKeepData($date_time); + * + * + * //统计留存的业务逻辑方法 + function doKeepData(string $date_time){ + $keep_statistics_log = 'keep_statistics_log'; + + + + //region 2日留存 start + $keep_type = 2; + Tools::log_to_write_txt(['title'=>'es统计 2日留存, 入参:','登录时间(往前推keep_type天,且不含执行当日):'=>$date_time, + 'keep_type'=>$keep_type,],$keep_statistics_log); + //注册数 + $reg_num = $this->getRegNum($keep_type,$date_time); + Tools::log_to_write_txt(['title'=>'es统计 2日留存, 出参:','reg_num'=>$reg_num, + 'es_query_reg_num_json_str'=>$this->es_query_reg_num_json_str],$keep_statistics_log); + //查询留存数据 + Tools::log_to_write_txt(['title'=>'es统计 2日留存,查询留存数据, 入参:','登录时间(往前推keep_type天,且不含执行当日):'=>$date_time, + 'keep_type'=>$keep_type],$keep_statistics_log); + $buckets2 = $this->countUserKeepDataByKeepType($keep_type,$date_time); + Tools::log_to_write_txt(['title'=>'es统计 2日留存,查询留存数据, 出参:','buckets2'=>$buckets2, + 'es_query_json_str'=>$this->es_query_json_str],$keep_statistics_log); + //保存留存数据 + KeepStatisticsModel::insertDataByChannel($keep_type,$buckets2,$reg_num,$date_time,'two'); + //endregion 2日留存 end + + //region 3日留存 start + $keep_type = 3; + Tools::log_to_write_txt(['title'=>'es统计 3日留存, 入参:','登录时间(往前推keep_type天,且不含执行当日):'=>$date_time, + 'keep_type'=>$keep_type,],$keep_statistics_log); + //注册数 + $reg_num = $this->getRegNum($keep_type,$date_time); + Tools::log_to_write_txt(['title'=>'es统计 3日留存, 出参:','reg_num'=>$reg_num, + 'es_query_reg_num_json_str'=>$this->es_query_reg_num_json_str],$keep_statistics_log); + //查询留存数据 + Tools::log_to_write_txt(['title'=>'es统计 3日留存,查询留存数据, 入参:','登录时间(往前推keep_type天,且不含执行当日):'=>$date_time, + 'keep_type'=>$keep_type],$keep_statistics_log); + $buckets3 = $this->countUserKeepDataByKeepType($keep_type,$date_time); + Tools::log_to_write_txt(['title'=>'es统计 3日留存,查询留存数据, 出参:','buckets3'=>$buckets3, + 'es_query_json_str'=>$this->es_query_json_str],$keep_statistics_log); + //保存留存数据 + KeepStatisticsModel::insertDataByChannel($keep_type,$buckets3,$reg_num,$date_time,'three'); + //endregion 3日留存 end + + + + //region 4日留存 start + $keep_type = 4; + Tools::log_to_write_txt(['title'=>'es统计 4日留存, 入参:','登录时间(往前推keep_type天,且不含执行当日):'=>$date_time, + 'keep_type'=>$keep_type],$keep_statistics_log); + //注册数 + $reg_num = $this->getRegNum($keep_type,$date_time); + Tools::log_to_write_txt(['title'=>'es统计 4日留存, 出参:','reg_num'=>$reg_num, + 'es_query_reg_num_json_str'=>$this->es_query_reg_num_json_str],$keep_statistics_log); + //查询留存数据 + Tools::log_to_write_txt(['title'=>'es统计 4日留存,查询留存数据, 入参:','登录时间(往前推keep_type天,且不含执行当日):'=>$date_time, + 'keep_type'=>$keep_type],$keep_statistics_log); + $buckets4 = $this->countUserKeepDataByKeepType($keep_type,$date_time); + Tools::log_to_write_txt(['title'=>'es统计 4日留存,查询留存数据, 出参:','buckets4'=>$buckets4, + 'es_query_json_str'=>$this->es_query_json_str],$keep_statistics_log); + //保存留存数据 + KeepStatisticsModel::insertDataByChannel($keep_type,$buckets4,$reg_num,$date_time,'four'); + //endregion 4日留存 end + + + + //region 5日留存 start + $keep_type = 5; + Tools::log_to_write_txt(['title'=>'es统计 5日留存, 入参:','登录时间(往前推keep_type天,且不含执行当日):'=>$date_time, + 'keep_type'=>$keep_type,],$keep_statistics_log); + //注册数 + $reg_num = $this->getRegNum($keep_type,$date_time); + Tools::log_to_write_txt(['title'=>'es统计 5日留存, 出参:','reg_num'=>$reg_num, + 'es_query_reg_num_json_str'=>$this->es_query_reg_num_json_str],$keep_statistics_log); + //查询留存数据 + Tools::log_to_write_txt(['title'=>'es统计 5日留存,查询留存数据, 入参:','登录时间(往前推keep_type天,且不含执行当日):'=>$date_time, + 'keep_type'=>$keep_type],$keep_statistics_log); + $buckets5 = $this->countUserKeepDataByKeepType($keep_type,$date_time); + Tools::log_to_write_txt(['title'=>'es统计 5日留存,查询留存数据, 出参:','buckets5'=>$buckets5, + 'es_query_json_str'=>$this->es_query_json_str],$keep_statistics_log); + //保存留存数据 + KeepStatisticsModel::insertDataByChannel($keep_type,$buckets5,$reg_num,$date_time,'five'); + //endregion 5日留存 end + + + + //region 6日留存 start + $keep_type = 6; + Tools::log_to_write_txt(['title'=>'es统计 6日留存, 入参:','登录时间(往前推keep_type天,且不含执行当日):'=>$date_time, + 'keep_type'=>$keep_type,],$keep_statistics_log); + //注册数 + $reg_num = $this->getRegNum($keep_type,$date_time); + Tools::log_to_write_txt(['title'=>'es统计 6日留存, 出参:','reg_num'=>$reg_num, + 'es_query_reg_num_json_str'=>$this->es_query_reg_num_json_str],$keep_statistics_log); + //查询留存数据 + Tools::log_to_write_txt(['title'=>'es统计 6日留存,查询留存数据, 入参:','登录时间(往前推keep_type天,且不含执行当日):'=>$date_time, + 'keep_type'=>$keep_type],$keep_statistics_log); + $buckets6 = $this->countUserKeepDataByKeepType($keep_type,$date_time); + Tools::log_to_write_txt(['title'=>'es统计 6日留存,查询留存数据, 出参:','buckets6'=>$buckets6, + 'es_query_json_str'=>$this->es_query_json_str],$keep_statistics_log); + //保存留存数据 + KeepStatisticsModel::insertDataByChannel($keep_type,$buckets6,$reg_num,$date_time,'six'); + //endregion 6日留存 end + + + + //region 7日留存 start + $keep_type = 7; + Tools::log_to_write_txt(['title'=>'es统计 7日留存, 入参:','登录时间(往前推keep_type天,且不含执行当日):'=>$date_time, + 'keep_type'=>$keep_type,],$keep_statistics_log); + //注册数 + $reg_num = $this->getRegNum($keep_type,$date_time); + Tools::log_to_write_txt(['title'=>'es统计 7日留存, 出参:','reg_num'=>$reg_num, + 'es_query_reg_num_json_str'=>$this->es_query_reg_num_json_str],$keep_statistics_log); + //查询留存数据 + Tools::log_to_write_txt(['title'=>'es统计 7日留存,查询留存数据, 入参:','登录时间(往前推keep_type天,且不含执行当日):'=>$date_time, + 'keep_type'=>$keep_type],$keep_statistics_log); + $buckets7 = $this->countUserKeepDataByKeepType($keep_type,$date_time); + Tools::log_to_write_txt(['title'=>'es统计 7日留存,查询留存数据, 出参:','buckets7'=>$buckets7, + 'es_query_json_str'=>$this->es_query_json_str],$keep_statistics_log); + //保存留存数据 + KeepStatisticsModel::insertDataByChannel($keep_type,$buckets7,$reg_num,$date_time,'seven'); + //endregion 7日留存 end + } + * + * + * 注:KeepStatisticsModel::insertDataByChannel方法由开发者自己根据统计结果完成 + * + */ + function revertUserKeepDate(int $keep_type, string $date_time){ + $date = new Date(); + $date->date_format = 'Y-m-d'; + $login_time_start = $date->reduceTime(1,'d',strtotime($date_time));//基础时间-1天 + $time_arr = [ + 7=>[ + 'reg_time_start_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)), + 'reg_time_end_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)).' 23:59:59', + 'login_time_start'=>$date->reduceTime(1,'d',strtotime($login_time_start)), + 'login_time_end'=>$date->reduceTime(1,'d',strtotime($login_time_start)).' 23:59:59', + ], + 6=>[ + 'reg_time_start_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)), + 'reg_time_end_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)).' 23:59:59', + 'login_time_start'=>$date->reduceTime(2,'d',strtotime($login_time_start)), + 'login_time_end'=>$date->reduceTime(2,'d',strtotime($login_time_start)).' 23:59:59', + ], + 5=>[ + 'reg_time_start_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)), + 'reg_time_end_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)).' 23:59:59', + 'login_time_start'=>$date->reduceTime(3,'d',strtotime($login_time_start)), + 'login_time_end'=>$date->reduceTime(3,'d',strtotime($login_time_start)).' 23:59:59', + ], + 4=>[ + 'reg_time_start_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)), + 'reg_time_end_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)).' 23:59:59', + 'login_time_start'=>$date->reduceTime(4,'d',strtotime($login_time_start)), + 'login_time_end'=>$date->reduceTime(4,'d',strtotime($login_time_start)).' 23:59:59', + ], + //3日留存 + 3=>[ + 'reg_time_start_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)), + 'reg_time_end_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)).' 23:59:59', + 'login_time_start'=>$date->reduceTime(5,'d',strtotime($login_time_start)), + 'login_time_end'=>$date->reduceTime(5,'d',strtotime($login_time_start)).' 23:59:59', + ], + //次日留存 + 2=>[ + //注册时间 + 'reg_time_start_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)), + 'reg_time_end_time'=>$date->reduceTime(7,'d',strtotime($login_time_start)).' 23:59:59', + //登录时间 + 'login_time_start'=>$date->reduceTime(6,'d',strtotime($login_time_start)), + 'login_time_end'=>$date->reduceTime(6,'d',strtotime($login_time_start)).' 23:59:59', + ], + ]; + return $time_arr[$keep_type]; + } + + /** + * desc:截取开始字符串(第一次出现),和结束字符串(第一次出现)之间的字符串 + * + * 可以是html标签,也可以是中英文符号,如《,》,[,],{,},=== + * + * 也可以是任意字符串,由调用方指定 + * + * 使用示例: + * //取得json字符串 + $input_data_json = '{"users":"[{"uid":277997,"nickname":"小史姐姐"},{"uid":301005,"nickname":"???M?a?r?y???"},{"uid":302474,"nickname":"A汽车电工"},{"uid":296874,"nickname":"唐唐"}]","quota":5000,"entry_fee":100000,"match_type":4,"match_level":1}'; + + //因为json解析不了多维数组,这里要将子json字符串获取出来 + $between_str = '['.$this->get_between_str($input_data_json,'[',']').']'; + * + * + * author:wh + * @param $input + * @param $start + * @param $end + * @return false|string + */ + static function getBetweenStr($input, $start, $end){ + return substr($input, strlen($start) + strpos($input, $start), (strlen($input) - strpos($input, $end)) * (-1)); + } + + /** + * desc:将一维数组的值转换为json字符串格式 + * + * 备注:不含键名 + * + * 使用频率低 + * + * author:wh + * @param array $arr eg: ['1234','5678','9991','3332'] 数组 + * @return string eg:['1234','5678','9991','3332'] 字符串 + */ + static function arrValToJsonStr(array $arr){ + $tmp_str = '['; + foreach ($arr as $v){ + $tmp_str.=$tmp_str=='['?$v:','.$v; + } + $tmp_str.=']'; + return $tmp_str; + } + + /** + * @deprecated 废弃,效率低下 + * + * desc:保留N位小数,并且向下取整 + * + * author:wh + * @param int $num + * @return float|int + */ + static function floorNum($num, $decimals=2){ + $strpos = strpos($num,'.'); + if(false !== $strpos){ + //存在 + $str = substr($num,0,$strpos); + + return $str.'.'.substr($num,$strpos+1,$decimals); + } + return 1*$num; + } + + /** + * desc:保留N位小数,并且向下取整 + * + * 效率更高 + * + * author:wh + * @param $num + * @param int $decimals + * @return float|int + */ + static function floor_num($num, $decimals=2){ + $dec = 1; + $dec = str_pad($dec,($decimals+1),"0",STR_PAD_RIGHT); + return floor($num*(int)$dec)/(int)$dec; + } + + /** + * desc:返回字符串格式的html结果,显示在浏览器中 + * author:wh + * @param string $strData 要显示的字符串 + * @param false $load 是否跳转页面 + * @return string + */ + static function returnHtml(string $strData='', $load=false){ + $req_str = ''; + if($load){ + $req_str = << + + + + +
    + $strData +
    + +EOF; + return $html; + } + + + /** + * desc:获取当前url(模块/控制器/操作)【小写】 + * + * author:wh + * @return string + */ + static function getRequestUrl(){ + return strtolower(request()->module().'/'.request()->controller().'/'.request()->action()); + } + + /** + * desc:域名满足条件打印日志 + * author:wh + * @return mixed + */ + static function dump_log_in_domain($domain,$data){ + if(is_string($domain) && in_array(request()->host(),[$domain])){ + Tools::log_to_write_txt(['打印日志'=>$data],'aaaa'); + }else if(is_array($domain) && in_array(request()->host(),$domain)){ + Tools::log_to_write_txt(['打印日志'=>$data],'aaaa'); + } + } + + + /** + * desc:jsonp的echo输出 + * + * author:wh + * @param string $callback js回调方法名称 + * @param array $result_data_arr 返回的数组 + */ + static function jsonpReturn(string $callback, array $result_data_arr){ + + echo $callback.'('.json_encode($result_data_arr).')';die;//方法1 + } + /** + * desc:jsonp的return输出 + * + * author:wh + * @param string $callback js回调方法名称 + * @param array $result_data_arr 返回的数组 + */ + static function jsonp_return(string $callback, array $result_data_arr){ + + return $callback.'('.json_encode($result_data_arr).')';//方法2 + } + + /** + * desc:判断付款码来源 + * + * 微信支付码规则:18位纯数字,以10、11、12、13、14、15开头 + * + * 支付宝支付码规则:25 - 30开头的长度为16~24位的数字,实际字符串长度以开发者获取的付款码长度为准 + * + * + * author:wh + */ + static function payment_code_origin($auth_code){ + // $auth_code 为授权码 + $payType = substr($auth_code, 0, 2); + if ($payType < 25) { + // 微信支付 + return 'wechat'; + } + if ($payType > 50) { + // 银行卡支付 + return 'bank'; + } + // 支付宝支付 + return 'alipay'; + } + + /** + * desc:已知期望毛利率,计算售价 + * + * 售价计算器 + * + * author:wh + * @param $cost_price 成本 + * @param $hope_mao_lilv 期望毛利率 + * @return string + */ + static function goto_count_sale_price($cost_price,$hope_mao_lilv){ + + //毛利率=(售价-成本)/售价 + + + //循环,假设售价为N,毛利高于期望毛利,则N减1 + //$cost_price = input('cost_price');//成本 + //$hope_mao_lilv = input('hope_mao_lilv');//期望利率 + + //$hope_mao_lilv = (N-$cost_price) + $num = 1 - $hope_mao_lilv;//毛成本率 + // 总结:毛成本/毛成本率=售价 + $n = $cost_price/$num; + return sprintf('%.2f',$n); + } + + /** + * desc:毛利率计算 + * + * 计算毛利率 + * + * author:wh + * @param $sale_price 售价 + * @param $cost_price 成本 + */ + static function goto_count_mao_li_lv($sale_price,$cost_price){ + //毛利率=(售价-成本)/售价 + + //$sale_price = input('sale_price'); + //$cost_price = input('cost_price'); + + $mao = ($sale_price - $cost_price)/$sale_price; + + + return sprintf('%.3f',$mao); + } + + /** + * 生成随机2位小数 + * + * 默认小数后2位数 + * author:wh + * @param $min 最小值 + * @param $max 最大值 + * @param int $sit 保留小数后几位 + * @return float + * @throws \Exception + */ + static function rand_float($min, $max, $sit=2) + { + if ($min >= $max) { + throw new \Exception('最大值必须大于最小值.', 501); + } + $rand = $min + mt_rand() / mt_getrandmax() * ($max - $min); + return floatval(sprintf("%.{$sit}f", $rand)); + } + + /** + * desc:删除字符串中的空值部分 + * + * author:wh + * @param string $str eg:,url-fa_attachment,,use-fa_auth_group,,,,,,, + * @param string $sign 分隔符,将字符串拆分为数组 + * @return false|string[] + */ + static function delete_str_empty_ele($str,$sign=','){ + $arr = explode($sign,$str); + return array_filter($arr, function($value) { + return $value !== ""; + }); + } + + /** + * desc:删除数组中的"空字符串"元素 + * + * 仅针对一维数组 + * + * author:wh + * @param array $arr eg:array("hello", "world", "", "php", "", "is", "cool") + * @return array + */ + static function delete_arr_empty_str(array $arr){ + return array_filter($arr, function($value) { + return $value !== ""; + }); + } + + + /** + * desc:获取数据库所有的表 + * + * author:wh + * @return array + */ + static function get_tables(){ + $tab = Db::query('SELECT DATABASE() as name'); + return array_column(Db::query('SHOW TABLES;'), 'Tables_in_'.$tab[0]['name']); + } + + /** + * desc:查询当前数据库名 + * author:wh + * @return mixed + */ + static function get_now_db_name(){ + $sql = "SELECT DATABASE() AS dbname;"; + return Db::query($sql)['dbname']; + } + + /** + * desc:获取表的注释 + * + * author:wh + * @param $tablename + * @return mixed + */ + static function get_table_comments($tablename){ + $sql = "SELECT DATABASE() AS dbname;"; + $dbname = Db::query($sql)[0]['dbname']; + $sql = "SELECT TABLE_NAME tablename, TABLE_COMMENT comments +FROM information_schema.TABLES +WHERE TABLE_SCHEMA = '{$dbname}' AND TABLE_NAME = '{$tablename}';"; + + return Db::query($sql)[0]['comments']; + } + + /** + * desc:获取表所有字段的注释 + * author:wh + */ + static function get_comments_by_field($tablename){ + $sql = "SELECT DATABASE() AS dbname;"; + $dbname = Db::query($sql)[0]['dbname']; + $sql = "SELECT COLUMN_NAME column_name,COLUMN_COMMENT column_comment,DATA_TYPE data_type +FROM information_schema.columns WHERE TABLE_NAME='{$tablename}' AND table_schema='{$dbname}'"; + return Db::query($sql); + } + + + /** + * desc:获取表字段注释 + * author:wh + * @param $tablename + * @param $field + * @return mixed + */ + static function get_comment_By_Field($tablename,$field){ + $field_comments = Tools::get_comments_by_field($tablename); + $arr = Tools::key_val_arr($field_comments,'column_name','column_comment'); + return empty($arr[$field])?'':$arr[$field]; + } + + /** + * desc:获取表中所有字段的数据类型 + * + * author:wh + */ + static function get_data_type_by_field($tablename){ + $sql = "SELECT DATABASE() AS dbname;"; + $dbname = Db::query($sql)[0]['dbname']; + $sql = "SELECT COLUMN_NAME column_name,COLUMN_COMMENT column_comment,DATA_TYPE data_type +FROM information_schema.columns WHERE TABLE_NAME='{$tablename}' AND table_schema='{$dbname}'"; + $arr = Db::query($sql); + $arr = Tools::key_val_arr($arr,'column_name','data_type'); + return $arr; + } + + /** + * desc: + * author:wh + * @param $e + */ + static function error_txt_log($e,$title=''){ + Tools::log_to_write_txt([ + 'error'=>$title.'.系统错误.'.$e->getMessage(), + 'input'=>input(), + 'error_info'=>$e->getTraceAsString() + ]); + } + + + /** + * desc:n时间以前 + * 例如:1分钟以前,20分钟以前,3小时以前,1天以前,3天以前,18天以前,1个月以前 + * + * $date 2021-02-13 12:33:43 + * author:wh + */ + static function n_time_before($date){ + if(empty($date)){ + return ''; + } + $time = time()-strtotime($date); + //一个月默认30天 + $day_time = 86400; + //月 + if($time >= 30*$day_time){ + return floor($time/30*$day_time).'月以前'; + } + //半个月 + if($time >= 15*$day_time){ + return '半个月以前'; + } + //天 + if($time >= $day_time){ + return floor($time/$day_time).'天以前'; + } + $hour_time = 3600; + //小时 + if($time >= $hour_time){ + return floor($time/$hour_time).'小时以前'; + } + $min_hour = 60; + //分钟 + if($time >= $min_hour){ + return floor($time/$min_hour).'分钟以前'; + } + //秒 + return (floor($time/$day_time)?:1).'秒以前'; + } + + + /** + * desc:金钱保留2位小数 + * + * 四舍五入 + * + * author:wh + * @param int $money 金额 + * @return string 统一返回2位数格式 + */ + static function money_float_two($money=0){ + return sprintf('%.2f',$money); + } + + /** + * desc:获取框架版本号,返回5.0, 5.1, 6.0, 6.1等 + * author:wh + * @return float|int + * @throws Exception + */ + static function get_thinkphp_version(){ + //$str = ''; + //try { + // $str = \think\App::VERSION; + //}catch (\Exception $e){ + // try { + // $str = THINK_VERSION; + // }catch (\Exception $e){ + // try { + // $str = \think\facade\App::version(); + // }catch (\Exception $e){ + // throw new Exception('未获取到框架版本号'); + // } + // } + //} + //$ver = substr($str,0,3); + //return $ver; + return config('framework_version'); + } + + /** + * desc:根据字符串生成颜色码 + * author:wh + * @param $string + * @return string + */ + static function stringToColor($string) { + // 使用crc32函数计算字符串的哈希值 + $hash = crc32($string); + + // 将整数哈希值转换为16进制,并填充0以确保6位长度 + $color = sprintf("%06X", $hash & 0xFFFFFF); + + // 返回颜色码 + return '#' . $color; + } + + /** + * desc:获取游戏接口配置字段的值 + * + * 自动匹配服务器环境 + * + * author:wh + * @param string $group + * @return float|mixed|string + */ + static function sysEnvDomainConfigVal(string $group,$sys_env){ + return Db::table('fa_sys_env_domain_config') + ->cache() + ->where(['group'=>$group]) + ->where('env',$sys_env) + ->value('domain'); + } + + /** + * 将时间字符串转换为N天N小时N分N秒 + * @param string $initialTime 初始时间 eg:2024-11-15 09:50:13 + * @param string $targetTime 目标时间 eg:2024-11-16 21:49:48 + */ + static function time_to_day_hour_min_sec($initialTime,$targetTime){ + // 定义初始时间和目标时间 + //$initialTime = '2024-11-15 09:50:13'; + //$targetTime = '2024-11-16 21:49:48'; + + // 创建 DateTime 对象 + $dateTime1 = new \DateTime($initialTime); + $dateTime2 = new \DateTime($targetTime); + + // 计算时间差 + $interval = $dateTime1->diff($dateTime2); + + // 提取时间差的各个部分 + $days = $interval->d; + $hours = $interval->h; + $minutes = $interval->i; + $seconds = $interval->s; + + // 格式化输出 + $formattedInterval = "{$days}天{$hours}小时{$minutes}分{$seconds}秒"; + + return $formattedInterval; // 输出:1天11小时59分35秒 + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/Video.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/Video.php new file mode 100644 index 0000000..337939b --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/tool/Video.php @@ -0,0 +1,174 @@ + '/usr/bin/ffmpeg', // 或者其他FFmpeg实际路径 + 'ffprobe.binaries' => '/usr/bin/ffprobe', // 或者其他ffprobe实际路径 + ]) + { + $this->config = $config; + } + + /** + * desc:【不推荐】生成视频封面 + * 读取视频第一帧保存为图片 + * author:wh + * @param $in_file 待处理视频路径(物理路径) + * @param $out_file_name 缩略图文件名称(带后缀) + * @param $path 指定存储路径 + * @return string + */ + function createVideoImg($in_file, $out_file_name, $path=''){ + //缩略图保存路径 + $path = $path?$path:'/public/uploads/video_cover/'.date('Ymd').'/'; + $out_file = Tools::get_root_path().$path; + + if(!file_exists($out_file)){ + mkdir($out_file, 0777, true); + } + $out_file_path = $out_file.$out_file_name; + //要执行的 cmd 命令 + //$cmd = "ffmpeg -i ".$in_file." -y -f image2 -ss 2 -t 0.001 -s 350x240 ".$out_file; + $cmd = "ffmpeg -i {$in_file} -f image2 -ss 2 -t 0.001 {$out_file_path}"; + $cmd = iconv('UTF-8','GB2312',$cmd); + //执行命令 + exec($cmd); + return $path.$out_file_name; + } + /** + * desc:【不推荐】生成视频封面 + * 读取视频第一帧保存为图片 + * 注:仅适用于thinkPHP6 + * author:wh + * @param $in_file 待处理视频路径 + * @param $out_file_name 缩略图文件名称 + * @return string + */ + function toCreateVideoImg($in_file, $out_file_name, $path=''){ + //缩略图保存路径 + $path = $path?$path:'/static/upload/file/user_video_img/'; + $out_file = Tools::get_root_path().'public'.$path; + + if(!file_exists($out_file)){ + mkdir($out_file, 0777, true); + } + $out_file_path = $out_file.$out_file_name; + //要执行的 cmd 命令 + //$cmd = "ffmpeg -i ".$in_file." -y -f image2 -ss 2 -t 0.001 -s 350x240 ".$out_file; + $cmd = "ffmpeg -i {$in_file} -f image2 -ss 2 -t 0.001 {$out_file_path}"; + $cmd = iconv('UTF-8','GB2312',$cmd); + //执行命令 + exec($cmd); + return $path.$out_file_name; + } + + /** + * desc:【推荐】创建视频封面 + * 依赖: + * composer require php-ffmpeg/php-ffmpeg + * + * 安装参考文章: + * https://blog.csdn.net/qq_15941409/article/details/137846947?spm=1001.2014.3001.5501 + * author:wh + * @param string $inputVideoPath 视频物理路径 + * @param string $out_file_name 带后缀的封面文件名 + * @param string $path 封面输出物理目录,不存在会自动创建目录 + * @return string + */ + function createVideoCoverImage($inputVideoPath, $out_file_name='', $path=''){ + // 初始化FFMpeg实例,指向FFmpeg可执行文件的路径(根据实际情况调整路径) + $ffmpeg = FFMpeg::create($this->config); + // 指定输出图像文件的路径和格式(例如,输出为JPEG格式) + $filename = $out_file_name?:Tools::rand_str(32) . '.jpg'; + $path = $path?:Tools::get_root_path() . 'public/uploads/video_cover/' .date('Ymd').'/'; + if(!file_exists($path)){ + mkdir($path, 0777, true); + } + $outputImage = $path. $filename; // 替换为保存第一帧的路径 + // 打开视频文件 + $video = $ffmpeg->open($inputVideoPath); + // 提取第一帧(默认情况下,FFMpeg库将从视频的开始处获取第一帧) + $frame = $video->frame(TimeCode::fromSeconds(0)); + // 将第一帧保存到指定的图像文件 + $frame->save($outputImage, 'image/jpeg'); + return $outputImage; + } + + /** + * desc:视频文件(.m3u8格式)分片下载 + * author:wh + * @param $local_file_path .m3u8格式本地文件路径 + * @param $download_fix_url 'v.f421220_0.ts'文件下载url前缀 eg:'https://v-tos-k.xiaoeknow.com/2919df88vodtranscq1252524126/4010c0b1387702307232915476/drm/' + * @param string $save_path 保存路径 + */ + function videoDownloadM3u8($local_file_path,$download_fix_url,$save_path=''){ + set_time_limit(0); + if($save_path && substr($save_path,-1)!='/'){//windows \ (不影响) + $save_path.='/'; + } + // cURL选项 + $curl = curl_init(); + + + // 设置连接超时时间(例如,300秒) + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 300); + + // 设置整个请求的超时时间(例如,3600秒,即1小时) + curl_setopt($curl, CURLOPT_TIMEOUT, 3600); + + // 忽略信号引起的超时 + curl_setopt($curl, CURLOPT_NOSIGNAL, true); + //$url = 'https://v-tos-k.xiaoeknow.com/2919df88vodtranscq1252524126/4010c0b1387702307232915476/drm/v.f421220.m3u8'; + // 保存.m3u8内容到临时文件 + //$tempM3u8File = 'D:\下载\myfile\v.f421220.m3u8'; + $tempM3u8File = $local_file_path; + $m3u8Content = file_get_contents($tempM3u8File); + // 读取.m3u8文件并获取所有.ts文件的URL + $tsFiles = []; + $lines = explode("\n", $m3u8Content); + foreach ($lines as $line) { + if (strpos($line, '.ts?') !== false) { + $tsFiles[] = $line; + } + } + + // 下载所有.ts文件 + foreach ($tsFiles as $tsFile) { + $url = $download_fix_url.$tsFile; + //var_dump($url);die; + //curl_setopt($curl, CURLOPT_URL, $tsFile); + //设置抓取的url + curl_setopt($curl, CURLOPT_URL, $url); + $tsContent = curl_exec($curl); + $tsFileName = $save_path.'video片段_' .time(). explode('?',$tsFile)[0]; + file_put_contents($tsFileName, $tsContent); + } + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/twitter/Twitter.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/twitter/Twitter.php new file mode 100644 index 0000000..ae88230 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/twitter/Twitter.php @@ -0,0 +1,106 @@ +search($params); + * + * 请求方式:接受请求参数时需post提交 + * + * 参数: + * params 参数 + * headers 请求头 + * + //params参数说明: + //query:必填,查询匹配Tweets。最多512个字符。使用 OR 连接关键词表明返回包含这些关键词中任意一个的推文(OR必须大写) + //start_time:选填,开始的时间(最早可查看7天前)。YYYY-MM-DDTHH:mm:ssZ ('2024-01-01T00:00:00Z')。 + //end_time:选填,结束时间。YYYY-MM-DDTHH:mm:ssZ ('2024-01-01T00:00:00Z')。 + //since_id:选填,返回推文 ID 大于(即更新于)指定 ID 的结果。指定的 ID 是独占的,响应将不包括它 + //until_id:选填,返回推文 ID 小于(即早于)指定 ID 的结果。指定的 ID 是独占的,响应将不包括它。 + //max_results:选填,请求要返回的最大搜索结果数。必须介于 10 和 100 之间的数字,默认为10, + //next_token:选填,此参数用于获取结果的下一“页”。与参数一起使用的值直接从 API 提供的响应中提取,不应修改 + //tweet.fields:选填,这领域参数使您能够选择特定的推文字段将在每个返回的 Tweet 对象中传递。 + //expansions:选填,使你能够请求与最初返回的推文相关的其他数据对象 + //media.fields:选填,能够选择特定的媒体字段将在每条返回的推文中传递 + //place.fields:选填,使您能够选择特定的放置字段将在每条返回的推文中传递 + //poll.fields:选填,能够选择特定的轮询字段将在每条返回的推文中传递 + //user.fields:选填,能够选择特定的推文字段将在每个返回的 Tweet 对象中传递 + //DOC: https://api.twitter.com/2/problems/invalid-request + * author:wh + */ + function search($params){ + //got params must post + $res = $this->request($params); + if(empty($res['data'])){ + return json_encode(['data'=>$res,JSON_UNESCAPED_UNICODE]); + } + + return json_encode($res,JSON_UNESCAPED_UNICODE); + } + + /** + * desc: + * author:wh + * got params must post + */ + function request($params){ + $bearer_token = $this->bearer_token; + + $url = $this->url; + $headers = [ + "Authorization: Bearer $bearer_token", + ]; + $query_str = http_build_query($params); + + return $this->curl_request($url.'?'.$query_str,'GET',$params,$headers); + } + + + private function curl_request($url, $method = 'GET',$data=null,$header=array(),$call_back=null) + { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + if($header){ + curl_setopt($ch, CURLOPT_HTTPHEADER, $header); + } + if($method == 'POST'){ + if($data) curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + } + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + if($call_back){ + curl_setopt($ch, CURLOPT_WRITEFUNCTION, $call_back); + } + $result = curl_exec($ch); + if (curl_errno($ch)) { + return [ + 'status' => 'error', + 'message' => 'curl 错误信息: ' . curl_error($ch) + ]; + } + curl_close($ch); + return $result; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/websocket/BaseEvents.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/websocket/BaseEvents.php new file mode 100644 index 0000000..f86b932 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/websocket/BaseEvents.php @@ -0,0 +1,194 @@ +$businessWorker]); + $app = new Application; + + $app->initialize(); + + + self::$wssObject = new BaseWebsocketLogic(); + } + + /** + * onConnect 事件回调 + * 当客户端连接上gateway进程时(TCP三次握手完毕时)触发 + * + * @access public + * @param int $client_id + * @return void + */ + public static function onConnect($client_id) + { + //一个连接只触发一次 + Tools::log_to_write_txt(['客户端完成TCP握手:'=>"[{$client_id}]"]); + + $json = Tools::json_wss('onConnect','连接成功',['client_id'=>$client_id]); + Gateway::sendToCurrentClient($json); + } + + public static function onWebSocketConnect($client_id, $data) + { + +// var_export($data); + //对应客户端打开连接, 一个连接只触发一次 + Tools::log_to_write_txt(['客户端打开了websocket连接',$client_id,$data]); + //$res = Tools::set_ok('ok',['client_id'=>$client_id,'msg'=>'客户端打开连接时,发送到服务端的消息:','data'=>$data]); + // + //Gateway::sendToCurrentClient(json_encode($res,JSON_UNESCAPED_UNICODE)); + //$json = Tools::json_wss('openConnect','打开连接成功',['client_id'=>$client_id,'data'=>$data]); + //Gateway::sendToCurrentClient($json); + } + + /** + * onMessage 事件回调 + * 当客户端发来数据(Gateway进程收到数据)后触发 + * + * 解析消息,根据action处理业务逻辑 + * + * @access public + * @param int $client_id + * @param mixed $data + * @return void + */ + public static function onMessage($client_id, $data) + { + Tools::log_to_write_txt(['客户端发来数据(Gateway进程收到数据).client_id:'.$client_id,$data]); + //$res = Tools::set_ok('ok',['client_id'=>$client_id,'msg'=>'你发来的消息我接收到了:',$client_id=>$data]); + //Gateway::sendToClient($client_id,json_encode($res,JSON_UNESCAPED_UNICODE)); + + //if(empty($data)){ + // $json = Tools::json_wss('error','消息为空'); + // + // Gateway::sendToClient($client_id, $json); + // return ; + //} + ////解析消息 + //$xunfei_record_config = config('xunfei_record_config'); + //$appId = $xunfei_record_config['appid']; + //$secretKey = $xunfei_record_config['secretKey']; + // + ////解析action + //// 处理接收到的语音数据 + //// 这里需要将二进制数据转换为讯飞API所需的格式 + //// 以下代码仅为示例,具体实现需要根据讯飞API文档进行调整 + //$lfasrClient = new LfasrClient($appId, $secretKey); + //$lfasrClient->sendBinaryData($data, function ($result) use ($client_id) { + // // 处理讯飞API返回的实时转写结果 + // // 将结果发送回客户端 + // Gateway::sendToClient($client_id, json_encode(['type' => 'transcription', 'data' => $result])); + //}); + + //初始化websocket业务逻辑路由控制对象 + //$obj = new BaseWebsocketLogic(); + //$obj->domsg($client_id,$data); + self::$wssObject->domsg($client_id,$data); + + } + + /** + * onClose 事件回调 当用户断开连接时触发的方法 + * + *【重要】:继承该类时重写断开业务逻辑 + * + * @param integer $client_id 断开连接的客户端client_id + * @return void + */ + public static function onClose($client_id) + { + //GateWay::sendToAll("client[$client_id] logout\n"); + Tools::log_to_write_txt(['断开连接.client_id:'.$client_id]); + + //$url = 'https://boomim.playone.cn/api/Partnermerchants/offline'; + //Curl::curl_post($url,['clientid'=>$client_id]); + + //(new TyuserLogic())->offline($client_id); + //self::$wssObject->doOffline($client_id); + } + + /** + * onWorkerStop 事件回调 + * 当businessWorker进程退出时触发。每个进程生命周期内都只会触发一次。 + * + * @param \Workerman\Worker $businessWorker + * @return void + */ + public static function onWorkerStop(Worker $businessWorker) + { + //echo "WorkerStop\n"; + Tools::log_to_write_txt(['businessWorker进程退出时触发。每个进程生命周期内都只会触发一次.',$businessWorker]); + //所有人离线,不需要修改所有用户离线状态, + //因为离线后再上线clint_id会重新生成 + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/websocket/BaseWebsocketLogic.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/websocket/BaseWebsocketLogic.php new file mode 100644 index 0000000..9b2c65f --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/websocket/BaseWebsocketLogic.php @@ -0,0 +1,114 @@ +getinstance($namespace); + $obj->$function($client_id, $res); + } catch (\Exception $e) { + Tools::error_txt_log($e); + $json = Tools::json_wss('error', '服务繁忙'); + Gateway::sendToClient($client_id, $json); + } + } + + function getinstance($className) + { + // 类名字符串 + // 参数数组 + + // 确保类存在 + if (!class_exists($className)) { + throw new \InvalidArgumentException("Class {$className} does not exist."); + } + // 创建反射类实例 + $reflection = new \ReflectionClass($className); + + // 检查构造函数是否存在 + //if (!$reflection->hasMethod('__construct')) { + // throw new \LogicException("Class {$className} has no constructor."); + //} + //$constructor = $reflection->getConstructor(); + + // 如果构造函数有参数,我们需要匹配参数 + //if ($constructor !== null) { + // $constructorParams = $constructor->getParameters(); + // + // // 确保参数数量匹配 + // if (count($constructorParams) !== count($params)) { + // throw new \InvalidArgumentException("Number of constructor parameters does not match provided arguments."); + // + // } + // // 创建参数数组,将参数类型与值匹配 + // $matchedParams = []; + // foreach ($constructorParams as $index => $param) { + // // 如果参数允许null,或者参数类型与传递的值兼容,添加到匹配参数数组 + // if ($param->allowsNull() || $param->getClass() === null || $param->getClass()->isInstance($params[$index])) { + // $matchedParams[] = $params[$index]; + // } else { + // throw new \InvalidArgumentException("Provided argument does not match constructor parameter type at position {$index}."); + // } + // } + // // 使用反射类创建并初始化类实例 + // $instance = $reflection->newInstanceArgs($matchedParams); + //} else { + // + //} + // 构造函数无参数,直接创建实例 + $instance = $reflection->newInstanceWithoutConstructor(); + return $instance; + } + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/Applet.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/Applet.php new file mode 100644 index 0000000..507b99b --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/Applet.php @@ -0,0 +1,164 @@ +wechatConfig = $wechatConfig;//全局配置 + $this->access_token_file_name = $access_token_file_name; + $this->access_token_path = $root_path.$wechatConfig['access_token_path'];//access_token票据存储位置 + + if($this->access_token_file_name){ + $this->access_token_path = $this->access_token_path.'/'.$this->access_token_file_name; + }else{ + $this->access_token_path = $this->access_token_path.'/access_token.txt'; + } + + if(!file_exists($wechatConfig['access_token_path'])){ + mkdir($wechatConfig['access_token_path'], 0777, true); + file_put_contents($this->access_token_path,''); + } + + + $this->checkParams(); + + } + + /** + * desc:检查参数 + * author:wh + * @throws \Exception + */ + protected function checkParams(){ + if(empty($this->wechatConfig['appid'])){ + throw new \Exception('请设置appid'); + } + if(empty($this->wechatConfig['app_secret'])){ + throw new \Exception('请设置app_secret'); + } + + } + + /** + * desc:获取小程序码 + * + * 返回图片Buffer + * + * 文档地址:https://developers.weixin.qq.com/minigame/dev/api-backend/open-api/qr-code/wxacode.getUnlimited.html + * + * @param $scene 必须,最大32个可见字符,只支持数字 + * @param string $page 非必须 + * @param array $other_params 非必须 + * @return array|bool|int|string + * @throws \Exception + */ + function getwxacodeunlimit($scene,$page='',$other_params=[]){ + + try { + + $obj = new UserAuth($this->wechatConfig,$this->access_token_file_name); + $access_token = $obj->getAccessToken(); + $url = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token='.$access_token; + + $params = [ + 'scene'=>$scene,//必须 + //'access_token'=>$access_token//必须 + ]; + + //非必须 + if($page){ + $params['page'] = $page; + } + //非必须 + if($other_params){ + //合并 + $params = array_merge($params,$other_params); + } + return Curl::curl_post_json_return_buffer($url,$params); + }catch (\Exception $e){ + Tools::log_to_write_txt([ + 'error'=>'小程序码获取错误'.$e->getMessage(), + 'input'=>input(), + 'error_info'=>$e->getTraceAsString(), + ]); + return Tools::set_fail('小程序码获取错误.'.$e->getMessage()); + } + } + + + /** + * description:获取票据 + * author:wanghua + */ + function getAccessToken(){ + + $access_token = file_get_contents($this->access_token_path); + //获取票据,并验证是否超时,保证票据有效(全局唯一) + if(!trim($access_token)){ + return $this->getAccessTokenNow();//实时获取 + }else{ + //是否过期 + $exp = explode('==', $access_token); + if(time() - $exp[1]*1 >= 6900){//允许提前5分钟刷新 + return $this->getAccessTokenNow();//实时获取 + }else{ + return $exp[0]; + } + } + } + + + /** + * description:实时获取【基础-非网页授权token】 + * author:wanghua + */ + function getAccessTokenNow(){ + try{ + //实时获取 + $appid = $this->wechatConfig['appid']; + $appsecret = $this->wechatConfig['app_secret']; + $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$appid&secret=$appsecret"; + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + $output = curl_exec($ch); + curl_close($ch); + $jsoninfo = json_decode($output, true); + + file_put_contents($this->access_token_path, $jsoninfo["access_token"].'=='.time());//永久保存 + return $jsoninfo["access_token"]; + }catch (\Exception $e){ + Tools::log_to_write_txt(['error_title'=>'[错误]获取票据出错:'.$e->getMessage(),'error_info'=>$e->getTraceAsString()]); + return false; + } + } + + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/TmpWexinauth.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/TmpWexinauth.php new file mode 100644 index 0000000..7bca219 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/TmpWexinauth.php @@ -0,0 +1,101 @@ +authReturnUrl方法 + * author:wanghua + */ + function usrAuth(){ + $wxconfig = config('pay_config.wechat'); + + (new UserAuth($wxconfig,$wxconfig['access_token_path']))->usrAuth(url('authReturnUrl','',false,true)); + } + + /** + * description:用户授权回调url (以下完整步骤) + * array(10) { + ["openid"]=> + string(28) "o5_r60xfL0speOoDVcAcrfX24wTw" + ["nickname"]=> + string(6) "展望" + ["sex"]=> + int(1) + ["language"]=> + string(5) "zh_CN" + ["city"]=> + string(6) "渝中" + ["province"]=> + string(6) "重庆" + ["country"]=> + string(6) "中国" + ["headimgurl"]=> + string(134) "http://thirdwx.qlogo.cn/mmopen/vi_32/99az237H9C2cahYGicowt3gwGd4wGxgnf4ia4T7ZCFicml7u6EvibAFAZ8ibuB6erwtpe5gUI24VlLuiaaP5ic666HdIQ/132" + ["privilege"]=> + array(0) { + } + ["unionid"]=> + string(28) "o20U21BMNw_3i-jpGjudYxfud6uE" + } + * author:wanghua + */ + function authReturnUrl(){ + $code = input('code'); + $weixin_logic = new WeixinLogic(); + + //1 获取用户授权code + $data = $weixin_logic->usrAccessToken($code); + if(empty($data['openid'])){ + throw new Exception('获取用户授权code出错.'.json_encode($data,JSON_UNESCAPED_UNICODE)); + } + //2 得到 access_token + $access_token = empty($data['access_token'])?$weixin_logic->getAccessToken():$data['access_token']; + //得到 access_token 之后全局保存 - 等之后扩展 + //if($access_token){ + //由于access_token拥有较短的有效期,当access_token超时后,可以使用refresh_token进行刷新, + //refresh_token有效期为30天,当refresh_token失效之后,需要用户重新授权。 + // cookie('usr_auth_access_token', $access_token);//网页授权 access_token 全局存储 + //} + //3 获取openid 并保存全局使用 + + + $openid = $data['openid']; + + //4 通过access_token 拉取用户信息 + $url = "https://api.weixin.qq.com/sns/userinfo?access_token={$access_token}&openid={$openid}&lang=zh_CN"; + $result = Tools::curl_get($url); + //5 存储,以便后续业务使用 + session('wx_user_info', json_decode($result['data'], true)); + //6 授权完成跳转至首页进行下一步业务 + //检查授权之前的url是否存在 + + $this->redirect(url('Storepage/index')); + } + //========================================= [3步获取用户信息 end] ========================================= + +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/UserAuth.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/UserAuth.php new file mode 100644 index 0000000..fc96249 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/UserAuth.php @@ -0,0 +1,349 @@ +wechatConfig = $wechatConfig;//全局配置 + $this->access_token_file_name = $access_token_file_name; + $this->access_token_path = '';//重置 + $this->access_token_path = $root_path.$wechatConfig['access_token_path'];//access_token票据存储位置 + + if($this->access_token_file_name){ + $this->access_token_path = $this->access_token_path.'/'.$this->access_token_file_name; + }else{ + $this->access_token_path = $this->access_token_path.'/access_token.txt'; + } + + if(!file_exists($root_path.$wechatConfig['access_token_path'])){ + mkdir($root_path.$wechatConfig['access_token_path'], 0777, true); + file_put_contents($this->access_token_path,''); + } + + + $this->checkParams(); + + } + /** + * desc:检查参数 + * author:wh + * @throws \Exception + */ + protected function checkParams(){ + if(empty($this->wechatConfig['appid'])){ + throw new \Exception('请设置appid'); + } + if(empty($this->wechatConfig['app_secret'])){ + throw new \Exception('请设置app_secret'); + } + + } + + //请求此页面进行授权,微信将数据发送到重定向接口,接口做下一步业务 + //========================================= [3步获取用户信息 start] ========================================= + /** + * desc:(1)用户同意授权,获取code (2)第二步在Wexinauth.php ->authReturnUrl方法 + * author:wanghua + * author:wh + * @param string $authCallback 授权回调方法,必须包含域名(在微信公众号后台配置网页授权域名,公众号后台->设置与开发->公众号设置) + */ + function usrAuth(string $authCallbackUrl){ + $appid = $this->wechatConfig['appid']; + + //解释:$authCallbackUrl(授权回调,code会在回调url中出现) + $url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=$appid&redirect_uri=$authCallbackUrl&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect"; + echo ""; + } + + /** + * description:用户授权回调url (以下完整步骤) + * + * 【注意】:可以考虑将此代码copy到业务控制器中使用,由Wechat.php->usrAuth授权回调请求 + * + * array(10) { + ["openid"]=> + string(28) "o5_r60xfL0speOoDVcAcrfX24wTw" + ["nickname"]=> + string(6) "展望" + ["sex"]=> + int(1) + ["language"]=> + string(5) "zh_CN" + ["city"]=> + string(6) "渝中" + ["province"]=> + string(6) "重庆" + ["country"]=> + string(6) "中国" + ["headimgurl"]=> + string(134) "http://thirdwx.qlogo.cn/mmopen/vi_32/99az237H9C2cahYGicowt3gwGd4wGxgnf4ia4T7ZCFicml7u6EvibAFAZ8ibuB6erwtpe5gUI24VlLuiaaP5ic666HdIQ/132" + ["privilege"]=> + array(0) { + } + ["unionid"]=> + string(28) "o20U21BMNw_3i-jpGjudYxfud6uE" + } + * author:wanghua + */ + //function authReturnUrl(string $redirectUrl){ + // $code = input('code'); + // //$weixin_logic = new WeixinLogic(); + // + // //1 获取用户授权code + // $data = $this->usrAccessToken($code); + // + // //2 得到 access_token + // $access_token = empty($data['access_token'])?$this->getAccessToken():$data['access_token']; + // //得到 access_token 之后全局保存 - 等之后扩展 + // //if($access_token){ + // //由于access_token拥有较短的有效期,当access_token超时后,可以使用refresh_token进行刷新, + // //refresh_token有效期为30天,当refresh_token失效之后,需要用户重新授权。 + // // cookie('usr_auth_access_token', $access_token);//网页授权 access_token 全局存储 + // //} + // //3 获取openid 并保存全局使用 + // + // + // $openid = $data['openid']; + // cookie('openId', $openid); + // + // //4 通过access_token 拉取用户信息 + // $url = "https://api.weixin.qq.com/sns/userinfo?access_token={$access_token}&openid={$openid}&lang=zh_CN"; + // $result = $this->curl_get($url); + // //5 存储,以便后续业务使用 + // session('wx_user_info', json_decode($result['data'], true)); + // //6 授权完成跳转至首页进行下一步业务 + // //检查授权之前的url是否存在 + // + // $this->redirect($redirectUrl); + //} + //========================================= [3步获取用户信息 end] ========================================= + /** + * 【非基础token】 + * description:(第2步)通过code换取网页授权access_token, + * 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同 + * 得到的数据: + array(6) { + ["access_token"]=> + string(89) "10_ckJ3HZCePXha8yKNCaBEqZnemtt_bWc17IlIAwLh1bewR-3ilWybde_OxjevoO7QYOJzeqqiAL71Vv65vtO-zg" + ["expires_in"]=> + int(7200) + ["refresh_token"]=> + string(89) "10_OHKwmcQPj6UEt6O0hQiXgIqpdaFkGQNgrbB4zfCIjiGG_-bLqEPDXju4cXVH4FvDuhqG_N5h_OCY1LwZNTNU4g" + ["openid"]=> + string(28) "o5_r60xfL0speOoDVcAcrfX24wTw" + ["scope"]=> + string(15) "snsapi_userinfo" + ["unionid"]=> + string(28) "o20U21BMNw_3i-jpGjudYxfud6uE" + } + * @param string $code 来自用户授权code + * author:wanghua + */ + function usrAccessToken(string $code){ + $appid = $this->wechatConfig['appid']; + $secret = $this->wechatConfig['app_secret']; + //获取code后,请求以下链接获取access_token + $url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={$appid}&secret={$secret}&code={$code}&grant_type=authorization_code"; + $res_data = $this->curl_get($url); + $data = json_decode($res_data['data'], true); + if(isset($data['errcode']) && $data['errcode'] == 40163){ + throw new \Exception('code已失效,请重新授权'); + } + cache('wx_access_token', $data["access_token"],7000);//保存 + return $data; + } + + /** + * desc:小程序授权 + * + * https://developers.weixin.qq.com/minigame/dev/api-backend/open-api/login/auth.code2Session.html + * + * author:wh + * @param string $code + * @return { + * ["session_key"] => string(24) "ubVY3tqzoxNsYCVe9orBDg==" + * ["openid"] => string(28) "oIHWi61D2nfwNLZsq-5BfDs_qT-A" + * } + */ + function usrAccessTokenApplet(string $code){ + $appid = $this->wechatConfig['appid']; + $secret = $this->wechatConfig['app_secret']; + //获取code后,请求以下链接获取access_token + $url = "https://api.weixin.qq.com/sns/jscode2session?appid={$appid}&secret={$secret}&js_code={$code}&grant_type=authorization_code"; + $res_data = $this->curl_get($url); + $data = json_decode($res_data['data'], true); + return $data; + } + /** + * description:获取票据 + * author:wanghua + */ + function getAccessToken(){ + + if(!file_exists($this->access_token_path)){ + return $this->getAccessTokenNow();//实时获取 + } + + $access_token = file_get_contents($this->access_token_path); + //获取票据,并验证是否超时,保证票据有效(全局唯一) + $access_token = trim($access_token); + if(empty($access_token)){ + return $this->getAccessTokenNow();//实时获取 + }else{ + //是否过期 + $exp = explode('==', $access_token); + if(time() - $exp[1]*1 >= 6900){//允许提前5分钟刷新 + return $this->getAccessTokenNow();//实时获取 + }else{ + return $exp[0]; + } + } + } + + + /** + * description:实时获取【基础-非网页授权token】 + * author:wanghua + */ + function getAccessTokenNow(){ + try{ + //实时获取 + $appid = $this->wechatConfig['appid']; + $appsecret = $this->wechatConfig['app_secret']; + $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$appid&secret=$appsecret"; + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + $output = curl_exec($ch); + curl_close($ch); + $jsoninfo = json_decode($output, true); + + cache('wx_access_token', $jsoninfo["access_token"],7000);//保存 + return $jsoninfo["access_token"]; + }catch (\Exception $e){ + Tools::log_to_write_txt(['error_title'=>'[错误]获取票据出错:'.$e->getMessage(),'error_info'=>$e->getTraceAsString()]); + return false; + } + } + + /** + * desc:获取微信用户信息(现在接口不返回昵称和头像了) + * author:wh + * @param $openid + * @return mixed + */ + function getWeixinUserInfo($openid){ + $access_token = cache('wx_access_token'); + if(empty($access_token)){ + $access_token = $this->getAccessTokenNow();//实时获取 + } + $url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token={$access_token}&openid={$openid}&lang=zh_CN"; + $res_data = $this->curl_get($url); + $data = json_decode($res_data['data'], true); + return $data; + } + + /** + * desc:获取微信用户信息(V2)(会返回昵称和头像) + * author:wh + * @param $openid + */ + function getWeixinV2UserInfo($openid){ + $access_token = cache('wx_access_token'); + if(empty($access_token)){ + $access_token = $this->getAccessTokenNow();//实时获取 + } + $url = "https://api.weixin.qq.com/sns/userinfo?access_token={$access_token}&openid={$openid}&lang=zh_CN"; + $res_data = $this->curl_get($url); + $data = json_decode($res_data['data'], true); + return $data; + } + /** + * cURL 网络链接库 + * GET + * author:wh + * @param $url + * @return bool|int|string + */ + function curl_get($url, $timeout = 10) + { + + $header = array( + 'Accept: application/json', + ); + $curl = curl_init(); + //curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE); + //curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); + //curl_setopt($curl, CURLOPT_SSLVERSION, 3); + //设置抓取的url + curl_setopt($curl, CURLOPT_URL, $url); + //设置头文件的信息作为数据流输出 + curl_setopt($curl, CURLOPT_HEADER, 0); + // 超时设置,以秒为单位 + curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); + + // 超时设置,以毫秒为单位 + // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500); + + // 设置请求头 + curl_setopt($curl, CURLOPT_HTTPHEADER, $header); + //设置获取的信息以文件流的形式返回,而不是直接输出。 + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); + //执行命令 + $data = curl_exec($curl); + + // 显示错误信息 + if (curl_error($curl)) { + //print "Error: ".curl_errno($curl).'-' . curl_error($curl); + //返回错误码 + return ['code' => curl_errno($curl), 'msg' => curl_error($curl)]; + } else { + //关闭句柄 + curl_close($curl); + // 返回的内容 + return ['code' => 200, 'msg' => 'ok', 'data' => $data]; + } + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/WechatLogic.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/WechatLogic.php new file mode 100644 index 0000000..5e8197f --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/WechatLogic.php @@ -0,0 +1,307 @@ +wechatConfig = $wechatConfig;//全局配置 + $this->access_token_path = Tools::get_root_path().$access_token_path;//access_token票据存储位置 + if(!file_exists($this->access_token_path)){ + mkdir($this->access_token_path, 0777, true); + file_put_contents($this->access_token_path.'/access_token.txt',''); + } + $this->checkParams(); + + + } + + /** + * desc:检查参数 + * author:wh + * @throws \Exception + */ + protected function checkParams(){ + if(empty($this->wechatConfig['appid'])){ + throw new \Exception('请设置appid'); + } + if(empty($this->wechatConfig['app_secret'])){ + throw new \Exception('请设置app_secret'); + } + + } + /** + * description:获取票据 + * author:wanghua + */ + function getAccessToken(){ + $save_path = Tools::get_root_path().$this->wechatConfig['access_token_path'].'/access_token.txt'; + + $access_token = file_get_contents($save_path); + //获取票据,并验证是否超时,保证票据有效(全局唯一) + if(!trim($access_token)){ + return $this->getAccessTokenNow($save_path);//实时获取 + }else{ + //是否过期 + $exp = explode('==', $access_token); + if(time() - $exp[1]*1 >= 6900){//允许提前5分钟刷新 + return $this->getAccessTokenNow($save_path);//实时获取 + }else{ + return $exp[0]; + } + } + } + + /** + * description:实时获取【基础-非网页授权token】 + * author:wanghua + */ + function getAccessTokenNow($save_path){ + try{ + //实时获取 + $appid = $this->wechatConfig['appid']; + $appsecret = $this->wechatConfig['app_secret']; + $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$appid&secret=$appsecret"; + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + $output = curl_exec($ch); + curl_close($ch); + $jsoninfo = json_decode($output, true); + + file_put_contents($save_path, $jsoninfo["access_token"].'=='.time());//永久保存 + return $jsoninfo["access_token"]; + }catch (\Exception $e){ + Tools::log_to_write_txt(['error_title'=>'[错误]获取票据出错:'.$e->getMessage(),'error_info'=>$e->getTraceAsString()]); + return false; + } + } + + /** + * description:公共获取openid 不要在其它地方单独获取 + * author:wanghua + */ + /** + * description:基础openid + * author:wanghua + */ + function getBaseOpenid(){ + require_once Tools::get_root_path()."library/php_sdk_v3/example/WxPay.JsApiPay.php"; + + //$openId = session('wx_user_info.openid'); + //if(!$openId){ + $tools = new \JsApiPay(); + $openId = $tools->GetOpenid();//获取 + //cookie('openId', $openId);//全局存储 + //} + return $openId; + } + + /** + * 【非基础token】 + * description:(第2步)通过code换取网页授权access_token, + * 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同 + * 得到的数据: + array(6) { + ["access_token"]=> + string(89) "10_ckJ3HZCePXha8yKNCaBEqZnemtt_bWc17IlIAwLh1bewR-3ilWybde_OxjevoO7QYOJzeqqiAL71Vv65vtO-zg" + ["expires_in"]=> + int(7200) + ["refresh_token"]=> + string(89) "10_OHKwmcQPj6UEt6O0hQiXgIqpdaFkGQNgrbB4zfCIjiGG_-bLqEPDXju4cXVH4FvDuhqG_N5h_OCY1LwZNTNU4g" + ["openid"]=> + string(28) "o5_r60xfL0speOoDVcAcrfX24wTw" + ["scope"]=> + string(15) "snsapi_userinfo" + ["unionid"]=> + string(28) "o20U21BMNw_3i-jpGjudYxfud6uE" + } + * @param $code 来自用户授权code + * author:wanghua + */ + function usrAccessToken($code){ + $appid = $this->wechatConfig['appid']; + $secret = $this->wechatConfig['app_secret']; + //获取code后,请求以下链接获取access_token + $url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={$appid}&secret={$secret}&code={$code}&grant_type=authorization_code"; + $res_data = Tools::curl_get($url); + $data = json_decode($res_data['data'], true); + return $data; + } + + /** + * description:判断用户是否已关注(未关注返回false) + * 如果已关注则返回: + * array(17) { + ["subscribe"]=> + int(1) + ["openid"]=> + string(28) "o5_r60xfL0speOoDVcAcrfX24wTw" + ["nickname"]=> + string(6) "展望" + ["sex"]=> + int(1) + ["language"]=> + string(5) "zh_CN" + ["city"]=> + string(6) "渝中" + ["province"]=> + string(6) "重庆" + ["country"]=> + string(6) "中国" + ["headimgurl"]=> + string(136) "http://thirdwx.qlogo.cn/mmopen/mdcJghD8vWuRYTs1qGpgsqIiatsxNEXRKcDqEvh3rIyEpbiaXIXmyHNVlRONKicRl3I3HoWygcoAS8gAOJtc2gMic9ibI6DNXwSLl/132" + ["subscribe_time"]=> + int(1529396683) + ["unionid"]=> + string(28) "o20U21BMNw_3i-jpGjudYxfud6uE" + ["remark"]=> + string(0) "" + ["groupid"]=> + int(0) + ["tagid_list"]=> + array(0) { + } + ["subscribe_scene"]=> + string(22) "ADD_SCENE_PROFILE_CARD" + ["qr_scene"]=> + int(0) + ["qr_scene_str"]=> + string(0) "" + } + * author:wanghua + * @return mixed + */ + function checkIsCare(){ + $openId = session('wx_user_info.openid'); + $access_token = $this->getAccessToken(); + $url = 'https://api.weixin.qq.com/cgi-bin/user/info?access_token='.$access_token.'&openid='.$openId.'&lang=zh_CN'; + $res_data = Tools::curl_get($url); + $data = json_decode($res_data['data'], true); + if(empty($data['subscribe'])) return false; + return $data; + } + + /** + * description:实时获取jsapi_ticket + * author:wanghua + */ + function getJsapiTicketNow(){ + $access_token = $this->getAccessToken(); + $url = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token='.$access_token.'&type=jsapi'; + if(!file_exists($this->wechatConfig['js_api_ticket_path'])){ + mkdir($this->wechatConfig['js_api_ticket_path'], 0777, true); + file_put_contents($this->wechatConfig['js_api_ticket_path'].'/js_api_ticket.txt',''); + } + $data = json_decode(file_get_contents($url), true); + if($data['errcode'])return false; + file_put_contents($this->wechatConfig['js_api_ticket_path'].'/js_api_ticket.txt', $data['ticket'].'=='.time());//永久保存 + return $data['ticket']; + } + /** + * description:获取JsapiTicket + * author:wanghua + */ + function getJsapiTicket(){ + $save_path = Tools::get_root_path().$this->wechatConfig['js_api_ticket_path'].'/js_api_ticket.txt'; + + $token = file_get_contents($save_path); + //获取票据,并验证是否超时,保证票据有效(全局唯一) + if(!trim($token)){ + return $this->getJsapiTicketNow($save_path);//实时获取 + }else{ + //是否过期 + $exp = explode('==', $token); + if(time() - $exp[1]*1 >= 6900){//允许提前5分钟刷新 + return $this->getJsapiTicketNow($save_path);//实时获取 + }else{ + return $exp[0]; + } + } + } + + /** + * description:微信常规签名算法 + * author:wanghua + */ + function getSignature($param){ + if(!$param || !is_array($param))return false; + if(false !== strpos($param['url'], '#')){ + $exp = explode('#', $param['url']); + if($param['url'])$param['url'] = $exp[0]; + } + ksort($param); + $string1 = ''; + foreach ($param as $k=>$v){ + $string1.= $k.'='.$v.'&'; + } + $string1 = substr($string1, 0, strlen($string1)-1); + $signature = sha1($string1); + return $signature; + } + + /** + * description:JS-SDK使用权限签名算法 + * 文档地址: + * https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#62 + * + * author:wanghua + * @param $jspai_ticket + * @return array + */ + function getOption(){ + $jspai_ticket = $this->getJsapiTicket();//获取票据 + $options = [ + 'noncestr'=>Tools::rand_str(8,1), + 'jsapi_ticket'=>$jspai_ticket, + 'timestamp'=>time(), + 'url'=>request()->domain().request()->url() + ]; + $sign = $this->getSignature($options);//签名 + + $options['debug'] = false; + $options['appId'] = $this->wechatConfig['appid']; + $options['signature'] = $sign; + //常用接口-需要其它接口添加进来即可 + $options['jsApiList'] = [ + //'onMenuShareTimeline',//分享到朋友圈 + //'onMenuShareAppMessage',//分享给朋友 + 'updateAppMessageShareData', + //'onMenuShareQQ',//分享到QQ + //'onMenuShareWeibo',//分享到腾讯微博 + //'onMenuShareQZone',//分享到QQ空间 + ]; + unset($options['url']);//参与算法完成后删除 + $noncestr = $options['noncestr']; + unset($options['noncestr']); + $options['nonceStr'] = $noncestr; + return $options; + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/WechatMsgPush.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/WechatMsgPush.php new file mode 100644 index 0000000..6930690 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/WechatMsgPush.php @@ -0,0 +1,129 @@ +token = $token; + //$this->logicObject = $logicObject; + } + + /** + * desc:微信消息推送入口 + * + * + * 微信消息入口只有一个,所有的消息推送都会POST到这个入口 + * + * author:wh + * @return string + */ + //function msg(){ + // //Tools::log_to_write_txt(['微信推送消息.'=>input()]); + // + // $this->logicObject->run(); + // + // return $this->checkSignature(); + //} + + /** + * desc:接入微信(小程序、小游戏)消息推送服务 + * + * https://developers.weixin.qq.com/minigame/dev/guide/open-ability/message-push.html#option-url + * + * 使用方法: + * + * //微信消息推送入口(消息推送唯一入口) + * function msg(){ + //入参:{"signature":"78c3552749bdc470d8c13387ceef1ea6c5c5423a","timestamp":"1705482487","nonce":"776591758","ToUserName":"gh_d50089f85963","FromUserName":"oOkYN5aDkdRQWe_b3yKVXzKm7fZM","CreateTime":1705482487,"MsgType":"event","Event":"minigame_deliver_goods","MiniGame":{"OrderId":"b09c1097-c7cc-4872-ba32-352b3c205e61","IsPreview":1,"ToUserOpenid":"oOkYN5cLG1MKjjVYrRPZwirFnw-c","GoodsList":[{"Id":"10001","Num":1}],"Zone":1001,"SendTime":1705482487}} + + $MsgType = input('MsgType'); + $Event = input('Event'); + //根据消息类型和事件类型进行不同的业务逻辑处理 + if($MsgType == 'event' && $Event=='minigame_deliver_goods'){ + //这里要注意一个问题,input获取MiniGame会报错,原因不明,只有换一种方式获取:$all_post = input(); + $this->sendMiniGameGiftOrder($all_post);//发送小游戏礼包 + return 'success';//推荐 + } + //验签,验证是否是微信发来的消息 + return $this->checkSignature(); + } + * + * author:wh + * @return bool + */ + function checkSignature() + { + $echostr = input('echostr'); + $signature = input('signature');//$_GET["signature"]; + $timestamp = input('timestamp');//$_GET["timestamp"]; + $nonce = input('nonce');//$_GET["nonce"]; + + $token = $this->token; + $tmpArr = array($token, $timestamp, $nonce); + sort($tmpArr, SORT_STRING); + $tmpStr = implode( $tmpArr ); + $tmpStr = sha1( $tmpStr ); + + if ($tmpStr == $signature ) { + return $echostr;//原样返回 + } else { + return 'fail'; + } + } + +// function pushmsg(){ +// $root_path = Tools::get_root_path(); +// +// include_once "D:\wanghua\projects\break_mini_game_admin\data\wechat_msg_demo\wxBizMsgCrypt.php"; +// +//// 第三方发送消息给公众平台 +// $encodingAesKey = "k46Mhu1tgHFExrSrRPptKG0CltKFEn2EFocMT1tRbS6"; +// $token = $this->token; +// $timeStamp = time(); +// $nonce = "qwer"; +// $appId = "wx5d121791c36c2fe3"; +// $text = "1407743423"; +// +// +// $pc = new \WXBizMsgCrypt($token, $encodingAesKey, $appId); +// $encryptMsg = ''; +// $errCode = $pc->encryptMsg($text, $timeStamp, $nonce, $encryptMsg); +// if ($errCode == 0) { +// print("加密后: " . $encryptMsg . "\n"); +// } else { +// print($errCode . "\n"); +// } +// +// $xml_tree = new \DOMDocument(); +// $xml_tree->loadXML($encryptMsg); +// $array_e = $xml_tree->getElementsByTagName('Encrypt'); +// $array_s = $xml_tree->getElementsByTagName('MsgSignature'); +// $encrypt = $array_e->item(0)->nodeValue; +// $msg_sign = $array_s->item(0)->nodeValue; +// +// $format = ""; +// $from_xml = sprintf($format, $encrypt); +// +//// 第三方收到公众号平台发送的消息 +// $msg = ''; +// $errCode = $pc->decryptMsg($msg_sign, $timeStamp, $nonce, $from_xml, $msg); +// if ($errCode == 0) { +// print("解密后: " . $msg . "\n"); +// } else { +// print($errCode . "\n"); +// } +// } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/WechatMsgPushLogicDemo.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/WechatMsgPushLogicDemo.php new file mode 100644 index 0000000..4eb0322 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/WechatMsgPushLogicDemo.php @@ -0,0 +1,161 @@ +input()]); + //此行代码包含了消息推送接入逻辑 + return (new \app\api\logic\WechatMsgPushLogic())->run(); + } + * + * author:wh + */ + function run(){ + try { + $MsgType = input('MsgType'); + $Event = input('Event'); + //微信小游戏的游戏礼包接入逻辑 + if($MsgType == 'event' && $Event == 'minigame_deliver_goods'){ + Tools::log_to_write_txt(['发放微信小游戏礼包,开始:'=>['$MsgType'=>$MsgType,'$Event'=>$Event]]); + $all_post = input(); + //订单 + if(empty($all_post['MiniGame'])){ + return json(['ErrCode'=>500,'ErrMsg'=>'礼包不能为空,礼包发放失败']); + } + $res = $this->sendMiniGameGiftOrder($all_post['MiniGame']); + //日志 + Tools::log_to_write_txt(['发放微信小游戏礼包,结束。'=>$res]); + if($res['code'] == 200){ + return json(['ErrCode'=>0,"ErrMsg"=>"Success"]);//推荐 + } + return json(['ErrCode'=>500,'ErrMsg'=>'礼包发放失败']); + } + //下方写其它业务逻辑,例如客服等等 + //...... + + //消息推送接入逻辑 + return (new WechatMsgPush($this->token))->checkSignature(); + }catch (\Exception $e){ + Tools::error_txt_log($e); + return 'error'; + } + } + + /** + * desc:发放微信小游戏礼包 + * + * author:wh + */ + protected function sendMiniGameGiftOrder($MiniGame){ + + Tools::log_to_write_txt(['$MiniGame'=>$MiniGame]); + $MiniGame = is_array($MiniGame)?$MiniGame:json_decode($MiniGame,true); + $OrderId = $MiniGame['OrderId']; + $IsPreview = $MiniGame['IsPreview']; + $SendTime = $MiniGame['SendTime']; + $ToUserOpenid = $MiniGame['ToUserOpenid']; + $Zone = $MiniGame['Zone']; + + //$GiftId = $MiniGame['GiftId']; + //$GiftTypeId = $MiniGame['GiftTypeId']; + //查询重复 + $order = Db::table(TabConf::$fa_giftorder)->where('orderid',$OrderId)->find(); + if($order){ + return Tools::set_fail('重复礼包'); + } + Db::startTrans(); + try { + //新增礼包 + Db::table(TabConf::$fa_giftorder) + ->data([ + 'orderid'=>$OrderId, + 'to_user_openid'=>$ToUserOpenid, + 'is_preview'=>$IsPreview, + 'zone'=>$Zone, + //'gift_typeid'=>$GiftTypeId, + //'giftid'=>$GiftId, + 'send_time'=>date('Y-m-d H:i:s',$SendTime), + ]) + ->insert(); + $GoodsList = $MiniGame['GoodsList']; + if(empty($GoodsList)){ + return Tools::set_fail('礼包商品不能为空'); + } + foreach ($GoodsList as $item){ + //新增礼包商品 + Db::table(TabConf::$fa_giftordergoods) + ->data([ + 'orderid'=>$OrderId,//所属订单 + 'prize_id'=>$item['Id'],//奖品id + 'num'=>$item['Num'],//数量 + ]) + ->insert(); + //查询奖品 + $prize = Db::table(TabConf::$fa_prize) + ->where('id',$item['Id']) + ->find(); + if(empty($prize)){ + Tools::log_to_write_txt([ + '奖品不存在,礼包发放失败' + ]); + continue;//不发 + } + $users = Db::table(TabConf::$fa_users) + ->where('openid',$ToUserOpenid) + ->find(); + if(empty($users)){ + Tools::log_to_write_txt([ + '用户不存在,礼包发放失败' + ]); + continue; + } + //礼包入账 + Db::table(TabConf::$fa_users) + ->where('openid',$ToUserOpenid) + ->setInc('score',$prize['val'] * $item['Num']); + } + + Db::commit(); + return Tools::set_ok(); + }catch (\Exception $e){ + Db::rollback(); + Tools::error_txt_log($e); + return Tools::set_fail(); + } + } +} \ No newline at end of file diff --git a/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/WechatPayLogic.php b/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/WechatPayLogic.php new file mode 100644 index 0000000..c4d0e19 --- /dev/null +++ b/superadmin/vendor/wanghua/general-utility-tools-php/src/wechat/WechatPayLogic.php @@ -0,0 +1,569 @@ +setAppId($config['app_id']); + + $gateway->setMchId(trim($config['mch_id'])); + $gateway->setApiKey($config['api_key']); + + $order_param = [ + 'body' => $order_info['goods_name'], + 'out_trade_no' => $order_info['orderid'], + 'total_fee' => $order_info['real_amount']*100, //=0.01 单位分 + 'spbill_create_ip' => request()->ip(), + 'fee_type' => 'CNY', + 'notify_url' => $notify_url, + 'trade_type' => 'MWEB', + 'nonce_str' => Tools::rand_str(), + ]; + + $request = $gateway->purchase($order_param); + + $response = $request->send(); + + $res_data = $response->getData(); + + if($res_data['return_code'] != 'SUCCESS'){ + return '支付错误'; + } + + if($res_data['result_code'] != 'SUCCESS'){ + return '支付出错了'; + } + + + $mweb_url = $res_data['mweb_url']; + $pay_str = << +EOF; + + return $pay_str; + + } + + /** + * @deprecated 废弃,SDK要报证书错误,实战推荐使用[yurunsoft/pay-sdk]库 + * + * desc:微信jsapi支付 + * author:wh + * @param $order + * @param $notify_url + * @return array + */ + function wxJsApiPay($order,$notify_url){ + try { + $config = wxConfOrderPayFirm(); + // 设置参数 + // 商户号 + $merchantId = $config['mch_id']; + + //dump($config); + // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名 + $merchantPrivateKeyFilePath = $config['merchantPrivateKeyFilePath'];//'file:///path/to/merchant/apiclient_key.pem'; + $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE); + + // 「商户API证书」的「证书序列号」 + $merchantCertificateSerial = $config['merchantCertificateSerial'];//'3775B6A45ACD588826D15E583A95F5DD********'; + + // 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名 + $platformCertificateFilePath = $config['platformCertificateFilePath'];//'file:///path/to/wechatpay/cert.pem'; + $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC); + + // 从「微信支付平台证书」中获取「证书序列号」 + $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath); + + // 构造一个 APIv3 客户端实例 + $instance = Builder::factory([ + 'mchid' => $merchantId, + 'serial' => $merchantCertificateSerial, + 'privateKey' => $merchantPrivateKeyInstance, + 'certs' => [ + $platformCertificateSerial => $platformPublicKeyInstance, + ], + ]); + $resp = $instance + ->chain('v3/pay/transactions/jsapi') + ->post(['json' => [ + 'appid' => $config['appid'],//【公众号ID】 公众号ID + 'mchid' => $config['mch_id'],//【直连商户号】 直连商户号 + 'description' => $order['goods_name'],//【商品描述】 商品描述 + 'out_trade_no' => $order['orderid'],//【商户订单号】 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一。 + 'notify_url' => $notify_url, + 'amount' => [ + 'total' => $order['real_amount']*100,//【总金额】 订单总金额,单位为分。 + 'currency' => 'CNY' + ], + 'payer'=>[ + 'openid' =>$order['openid']//【用户标识】 用户在普通商户AppID下的唯一标识。 下单前需获取到用户的OpenID + ], + ]]); + + //echo $resp->getStatusCode(), PHP_EOL; + //echo $resp->getBody(), PHP_EOL; + + $responseData = json_decode($resp->getBody(), true); + + // 构造JSAPI支付的参数 + $jsapiParameters = [ + 'appId' => $config['appid'], + 'timeStamp' => (string)Formatter::timestamp(), + 'nonceStr' => Formatter::nonce(), + 'package' => 'prepay_id=' . $responseData['prepay_id'], + 'signType' => 'RSA-PSS', + ]; + + // 对JSAPI支付参数进行签名 + $jsapiParameters['paySign'] = Rsa::sign( + Formatter::joinedByLineFeed(...array_values($jsapiParameters)), + $merchantPrivateKeyInstance, + 'sha256' + ); + + // 返回JSAPI支付参数 + //header('Content-Type: application/json'); + //echo json_encode($jsapiParameters); + //dump($jsapiParameters); + + return Tools::set_ok('ok',$jsapiParameters); + } catch (\Exception $e) { + Tools::error_txt_log($e); + // 进行错误处理 + //echo $e->getMessage(), PHP_EOL; + if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) { + $r = $e->getResponse(); + //echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL; + //echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL; + + $res_arr = json_decode($r->getBody(),true); + return Tools::set_fail('异常.1',$res_arr); + } + //echo $e->getTraceAsString(), PHP_EOL; + return Tools::set_fail('异常.2',$e->getMessage()); + } + } + /** + * desc:微信jsapi支付,虽然文档不好用,但是可以正常支付 + * + * 场景:微信内的网页支付 + * + * composer包依赖:请安装[ yurunsoft/pay-sdk ],附带自动安装yurunsoft/yurun-http包 + * 其它工具包:wanghua/general-utility-tools-php + * + * author:wh + * @param array $config 微信支付的配置 必须参数:appid、mch_id、api_key + * @param array $wx_user_info 微信用户信息 必须参数:openid + * @param array $order_info 订单信息 必须参数:goods_name、orderid、goods_price(元) + * @param string $notify_url 回调URL + * @return false|string + */ + function jsapiPay(array $config, array $wx_user_info, array $order_info, string $notify_url){ + // 配置参数 + $params = new PublicParams(); + $params->appID = $config['appid']; // 支付平台分配给开发者的应用ID + $params->mch_id = $config['mch_id']; // 微信支付分配的商户号 + $params->key = $config['api_key']; // API 密钥 + // 如需使用敏感接口(如退款、发送红包等)需要配置 API 证书路径(登录商户平台下载 API 证书) + // $params->certPath = 'path/cert.pem'; // XXX: 绝对路径!!! + // $params->keyPath = 'path/key'; // XXX: 绝对路径!!!! + // 如果是子服务商需要配置 + // $params->sub_appid = ''; // 微信分配的子商户公众账号ID,服务商、银行服务商需要。 + // $params-> sub_mch_id = ''; // 微信支付分配的子商户号,开发者模式下必填,服务商、银行服务商需要。 + // SDK实例化,传入公共配置 + $pay = new \Yurun\PaySDK\Weixin\SDK($params); + // 支付接口(生成支付订单) + $request = new \Yurun\PaySDK\Weixin\JSAPI\Params\Pay\Request; + $request->body = $order_info['goods_name']; // 商品描述 + $request->out_trade_no = $order_info['orderid']; // 订单号 + $request->total_fee = $order_info['real_amount']*100; // 订单总金额,单位为:分 + $request->spbill_create_ip = request()->ip(); // 客户端ip + $request->notify_url = $notify_url; // 异步通知地址 + $request->openid = $wx_user_info['openid']; // 必须设置openid + $request->profit_sharing = empty($order_info['profit_sharing'])?'N':$order_info['profit_sharing'];//是否分账N,Y + // 调用接口 + $result = $pay->execute($request); + //此调试代码禁止删除 + //var_dump('result:', $result); + //var_dump('success:', $pay->checkResult()); + //var_dump('error:', $pay->getError(), 'error_code:', $pay->getErrorCode()); + if(!$pay->checkResult()){ + throw new Exception('下单错误.'.$pay->getError()); + } + + $request = new \Yurun\PaySDK\Weixin\JSAPI\Params\JSParams\Request; + $request->prepay_id = $result['prepay_id']; + $jsapiParams = $pay->execute($request); + // 最后需要将数据传给js,使用WeixinJSBridge进行支付 + return json_encode($jsapiParams); + } + + + /** + * desc: [推荐]微信退款,传入的金额保持“元”为单位,实际是以“分”的格式提交给微信支付 + * + * 依赖:yurunsoft/pay-sdk + * + * author:wh + * @param array $config 支付配置 + * @param array $order_info 订单信息 + * @param string $reason 退款原因(可选) + * @return array + */ + function toWxRefund($config,$order_info,$reason=''){ + return Mmodel::catch(function ()use($config,$order_info,$reason){ + // 配置参数 + $params = new PublicParams(); + $params->appID = $config['appid']; // 支付平台分配给开发者的应用ID + $params->mch_id = $config['mch_id']; // 微信支付分配的商户号 + $params->key = $config['api_key']; // API 密钥 + // 如需使用敏感接口(如退款、发送红包等)需要配置 API 证书路径(登录商户平台下载 API 证书) + $params->certPath = $config['platformCertificateFilePath'];//'path/to/cert.pem'; // 证书路径 + $params->keyPath = $config['merchantPrivateKeyFilePath'];//'path/to/key'; // 密钥路径 + + // 如果是子服务商需要配置 + // $params->sub_appid = ''; // 微信分配的子商户公众账号ID,服务商、银行服务商需要。 + // $params->sub_mch_id = ''; // 微信支付分配的子商户号,开发者模式下必填,服务商、银行服务商需要。 + + // SDK实例化,传入公共配置 + $pay = new SDK($params); + + // 退款请求参数 + $request = new Request(); + $request->out_trade_no = $order_info['orderid']; // 原支付订单号 + $request->out_refund_no = $order_info['orderid']; // 商户退款单号 + $request->total_fee = $order_info['real_amount'] * 100; // 订单总金额,单位为:分 + $request->refund_fee = $order_info['real_amount'] * 100; // 退款金额,单位为:分 + + try { + // 调用退款接口 + Tools::log_to_write_txt(['调用退款接口,入参:orderid='.$order_info['orderid'],$request]); + $result = $pay->execute($request); + Tools::log_to_write_txt(['调用退款接口,出参:',$result]); + }catch (\Exception $e){ + Tools::error_txt_log($e); + return Tools::set_fail('[err]:'.$e->getMessage()); + } + + // 调试代码 + //var_dump('refund result:', $result); + //var_dump('success:', $pay->checkResult()); + //var_dump('error:', $pay->getError(), 'error_code:', $pay->getErrorCode()); + + //打印:$result + //array(18) { + // ["return_code"] => string(7) "SUCCESS" + // ["return_msg"] => string(2) "OK" + // ["appid"] => string(18) "wx5904518b3a0b2965" + // ["mch_id"] => string(10) "1668824977" + // ["nonce_str"] => string(16) "3LyqioO7WmMEyFKk" + // ["sign"] => string(32) "6F0ECED3DED5F66200171B7050AFB635" + // ["result_code"] => string(7) "SUCCESS" + // ["transaction_id"] => string(28) "4200002407202408310164479484" + // ["out_trade_no"] => string(21) "phuzhe9n1725089504331" + // ["out_refund_no"] => string(21) "phuzhe9n1725089504331" + // ["refund_id"] => string(29) "50303600442024083127353984987" + // ["refund_channel"] => object(SimpleXMLElement)#63 (0) { + // } + // ["refund_fee"] => string(2) "10" + // ["coupon_refund_fee"] => string(1) "0" + // ["total_fee"] => string(2) "10" + // ["cash_fee"] => string(2) "10" + // ["coupon_refund_count"] => string(1) "0" + // ["cash_refund_fee"] => string(2) "10" + //} + //dump($result); + if (!$pay->checkResult()) { + Tools::log_to_write_txt([$pay]); + return Tools::set_fail('退款错误:' . $pay->getError(),[$result,$pay]); + } + if($result['return_code'] == 'SUCCESS' && $result['result_code']=='SUCCESS'){ + return Tools::set_ok('退款成功',$result); + } + + // 如果退款成功,$result 将包含退款操作的详细信息 + //echo "退款成功,退款单号:" . $request->out_refund_no; + return Tools::set_fail('退款失败.',$result); + }); + } + + /** + * @deprecated 废弃,推荐使用toWxRefund方法 + * + * SDK要报证书错误,实战推荐使用[yurunsoft/pay-sdk]库 + * + * 微信退款,单位“分” + * + * 依赖:wechatpay/wechatpay微信支付推荐sdk + * + * 注意返回(message不是msg): + * [code:'RESOURCE_NOT_EXISTS','message':'订单不存在'] + * + * 错误码: + * SUCCESS: 退款成功 + CLOSED: 退款关闭 + PROCESSING: 退款处理中 + ABNORMAL: 退款异常 + * + * + * 代码经过测试没有问题,如果出现“The certs(xxxxxxxxxxxx) contains the merchant's certificate serial number(4xxxxxxxxxxxxx) which is not allowed here.” + * 则是sdk代码问题,需要注释掉D:\wanghua\projects\meebo_mid\vendor\wechatpay\wechatpay\src\ClientJsonTrait.php + * 大约文件中第225行代码。 + * 代码: + * //if (array_key_exists($config['serial'], $config['certs'])) { + * // throw new Exception\InvalidArgumentException(sprintf( + * // Exception\ERR_INIT_CERTS_EXCLUDE_MCHSERIAL, implode(',', array_keys($config['certs'])), $config['serial'] + * // )); + * //} + */ + function wxRefund($order, $reason=''){ + $config = wxConfOrderPayFirm(); + // 设置参数 + // 商户号 + $merchantId = $config['mch_id']; + + //dump($config); + // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名 + $merchantPrivateKeyFilePath = $config['merchantPrivateKeyFilePath'];//'file:///path/to/merchant/apiclient_key.pem'; + $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE); + + // 「商户API证书」的「证书序列号」 + $merchantCertificateSerial = $config['merchantCertificateSerial'];//'3775B6A45ACD588826D15E583A95F5DD********'; + + // 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名 + $platformCertificateFilePath = $config['platformCertificateFilePath'];//'file:///path/to/wechatpay/cert.pem'; + $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC); + + // 从「微信支付平台证书」中获取「证书序列号」 + $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath); + + // 构造一个 APIv3 客户端实例 + $instance = Builder::factory([ + 'mchid' => $merchantId, + 'serial' => $merchantCertificateSerial, + 'privateKey' => $merchantPrivateKeyInstance, + 'certs' => [ + $platformCertificateSerial => $platformPublicKeyInstance, + ], + ]); + + try { + $promise = $instance + ->chain('v3/refund/domestic/refunds') + ->postAsync([ + 'json' => [ + 'out_trade_no' => $order['orderid'], + 'out_refund_no' => $order['orderid'],//退款单号 如果分批退款则单号必须每次唯一 + 'reason' => $reason, + 'amount' => [ + 'refund' => $order['real_amount']*100,//单位分 + 'total' => $order['real_amount']*100,//单位分 + 'currency' => 'CNY', + ], + ], + ]) + ->then(static function($response) { + // 正常逻辑回调处理 + //dump(1111); + //echo $response->getBody(), PHP_EOL; + + $res_arr = json_decode($response->getBody(),true); + return Tools::set_ok('ok',$res_arr); + }) + ->otherwise(static function($e){ + // 异常错误处理 + //echo $e->getMessage(), PHP_EOL; + if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) { + $r = $e->getResponse(); + //echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL; + //echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL; + throw new Exception($r->getBody()); + } + //echo $e->getTraceAsString(), PHP_EOL; + + throw new Exception($e->getMessage()); + //return Tools::set_res(5890,'退款失败'.$e->getMessage()); + }); + // 同步等待 + $promise->wait(); + //这里稍作注意,不清楚then返回会不会走这里(经测试现在不会) + }catch (\Exception $e){ + Tools::error_txt_log($e); + $json_decode = @json_decode($e->getMessage(),true); + return Tools::set_res(5890,'退款错误.',$json_decode); + } + } + /** + * + * [推荐]查询单笔退款 + * + * 依赖:yurunsoft/pay-sdk + * + * 代码经过测试没有问题,如果出现“The certs(4xxxxxxxxxxxxx) contains the merchant's certificate serial number(4xxxxxxxxxxx) which is not allowed here.” + * 则是sdk代码问题,需要注释掉D:\wanghua\projects\meebo_mid\vendor\wechatpay\wechatpay\src\ClientJsonTrait.php + * 大约文件中第225行代码。 + * 代码: + * //if (array_key_exists($config['serial'], $config['certs'])) { + * // throw new Exception\InvalidArgumentException(sprintf( + * // Exception\ERR_INIT_CERTS_EXCLUDE_MCHSERIAL, implode(',', array_keys($config['certs'])), $config['serial'] + * // )); + * //} + */ + function toWxRefundQuery($config, $outRefundNo) + { + return Mmodel::catch(function () use ($config, $outRefundNo) { + // 配置参数 + $params = new PublicParams(); + $params->appID = $config['appid']; // 支付平台分配给开发者的应用ID + $params->mch_id = $config['mch_id']; // 微信支付分配的商户号 + $params->key = $config['api_key']; // API 密钥 + + // 如需使用敏感接口(如退款、发送红包等)需要配置 API 证书路径(登录商户平台下载 API 证书) + $params->certPath = $config['platformCertificateFilePath']; // 证书路径 + $params->keyPath = $config['merchantPrivateKeyFilePath']; // 密钥路径 + + // SDK实例化,传入公共配置 + $pay = new \Yurun\PaySDK\Weixin\SDK($params); + + // 退款查询请求参数 + $request = new \Yurun\PaySDK\Weixin\RefundQuery\Request(); + $request->out_refund_no = $outRefundNo; // 商户退款单号 + + // 调用退款查询接口 + Tools::log_to_write_txt(['调用退款查询接口,入参:outRefundNo=',$outRefundNo,$config]); + $result = $pay->execute($request); + Tools::log_to_write_txt(['调用退款查询接口,出参',$result]); + + // 调试代码 + if (!$pay->checkResult()) { + throw new Exception('退款查询错误:' . $pay->getError()); + } + + if($result['return_code'] == 'SUCCESS' && $result['result_code']=='SUCCESS'){ + return Tools::set_ok('退款成功'); + } + + // 如果退款成功,$result 将包含退款操作的详细信息 + //echo "退款成功,退款单号:" . $request->out_refund_no; + return Tools::set_fail('退款失败.',$result); + }); + } + /** + * @deprecated 废弃,推荐使用toWxRefundQuery方法 + * + * SDK要报证书错误,实战推荐使用[yurunsoft/pay-sdk]库 + * + * 查询单笔退款 + * + * 依赖:wechatpay/wechatpay微信支付推荐sdk + * + * 代码经过测试没有问题,如果出现“The certs(4xxxxxxxxxxxxx) contains the merchant's certificate serial number(4xxxxxxxxxxx) which is not allowed here.” + * 则是sdk代码问题,需要注释掉D:\wanghua\projects\meebo_mid\vendor\wechatpay\wechatpay\src\ClientJsonTrait.php + * 大约文件中第225行代码。 + * 代码: + * //if (array_key_exists($config['serial'], $config['certs'])) { + * // throw new Exception\InvalidArgumentException(sprintf( + * // Exception\ERR_INIT_CERTS_EXCLUDE_MCHSERIAL, implode(',', array_keys($config['certs'])), $config['serial'] + * // )); + * //} + */ + function wxRefundQuery($orderid){ + $config = wxConfOrderPayFirm(); + // 设置参数 + // 商户号 + $merchantId = $config['mch_id']; + + //dump($config); + // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名 + $merchantPrivateKeyFilePath = $config['merchantPrivateKeyFilePath'];//'file:///path/to/merchant/apiclient_key.pem'; + $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE); + + // 「商户API证书」的「证书序列号」 + $merchantCertificateSerial = $config['merchantCertificateSerial'];//'3775B6A45ACD588826D15E583A95F5DD********'; + + // 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名 + $platformCertificateFilePath = $config['platformCertificateFilePath'];//'file:///path/to/wechatpay/cert.pem'; + $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC); + + // 从「微信支付平台证书」中获取「证书序列号」 + $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath); + + // 构造一个 APIv3 客户端实例 + $instance = Builder::factory([ + 'mchid' => $merchantId, + 'serial' => $merchantCertificateSerial, + 'privateKey' => $merchantPrivateKeyInstance, + 'certs' => [ + $platformCertificateSerial => $platformPublicKeyInstance, + ], + ]); + // 退款单号 + $outRefundNo = $orderid; // 商户系统内部的退款单号 + + try { + // 发起查询单笔退款请求 + $response = $instance->chain("v3/refund/domestic/refunds/{$outRefundNo}")->get(); + + //echo $response->getStatusCode(), PHP_EOL; + //echo $response->getBody(), PHP_EOL; + $res_arr = json_decode($response->getBody(),true); + return Tools::set_ok('ok',$res_arr); + } catch (\Exception $e) { + Tools::error_txt_log($e); + if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) { + $r = $e->getResponse(); + //echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL; + //echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL; + + $res_arr = json_decode($r->getBody(),true); + return Tools::set_fail('异常.',$res_arr); + } + //echo $e->getTraceAsString(), PHP_EOL; + return Tools::set_fail('异常.',$e->getMessage()); + } + } +} \ No newline at end of file