作者 zhangsan

新增支付插件

正在显示 97 个修改的文件 包含 4669 行增加0 行删除

要显示太多修改。

为保证性能只显示 97 of 97+ 个文件。

{"files":["application\/admin\/controller\/Epay.php","public\/assets\/addons\/epay\/images\/wechat.png","public\/assets\/addons\/epay\/images\/expired.png","public\/assets\/addons\/epay\/images\/scan.png","public\/assets\/addons\/epay\/images\/screenshot-alipay.png","public\/assets\/addons\/epay\/images\/paid.png","public\/assets\/addons\/epay\/images\/logo-alipay.png","public\/assets\/addons\/epay\/images\/logo-wechat.png","public\/assets\/addons\/epay\/images\/screenshot-wechat.png","public\/assets\/addons\/epay\/images\/alipay.png","public\/assets\/addons\/epay\/css\/common.css","public\/assets\/addons\/epay\/js\/common.js","public\/assets\/addons\/epay\/js\/jquery.qrcode.min.js","public\/assets\/addons\/epay\/less\/common.less"],"license":"regular","licenseto":"10789","licensekey":"voJNsYxGQctDz7Pf fSYtpPOpHiUd+ok5ElOwJg==","domains":["jcyang.cn"],"licensecodes":[],"validations":["cc3c992ff40a3a54f6c81d2768163d23"]}
\ No newline at end of file
... ...
<?php
namespace addons\epay;
use addons\epay\library\Service;
use think\Addons;
use think\Config;
use think\Loader;
/**
* 微信支付宝整合插件
*/
class Epay extends Addons
{
/**
* 插件安装方法
* @return bool
*/
public function install()
{
return true;
}
/**
* 插件卸载方法
* @return bool
*/
public function uninstall()
{
return true;
}
/**
* 插件启用方法
* @return bool
*/
public function enable()
{
return true;
}
/**
* 插件禁用方法
* @return bool
*/
public function disable()
{
return true;
}
// 支持自定义加载
public function epayConfigInit()
{
$this->actionBegin();
}
// 插件方法加载开始
public function addonActionBegin()
{
$this->actionBegin();
}
// 模块控制器方法加载开始
public function actionBegin()
{
//添加命名空间
if (!class_exists('\Yansongda\Pay\Pay')) {
//SDK版本
$version = Service::getSdkVersion();
$libraryDir = ADDON_PATH . 'epay' . DS . 'library' . DS;
Loader::addNamespace('Yansongda\Pay', $libraryDir . $version . DS . 'Yansongda' . DS . 'Pay' . DS);
$checkArr = [
'\Hyperf\Context\Context' => 'context',
'\Hyperf\Contract\Castable' => 'contract',
'\Hyperf\Engine\Constant' => 'engine',
'\Hyperf\Macroable\Macroable' => 'macroable',
'\Hyperf\Pimple\Container' => 'pimple',
'\Hyperf\Utils\Arr' => 'utils',
];
foreach ($checkArr as $index => $item) {
if (!class_exists($index)) {
Loader::addNamespace(substr($index, 1, strrpos($index, '\\') - 1), $libraryDir . 'hyperf' . DS . $item . DS . 'src' . DS);
}
}
if (!class_exists('\Yansongda\Supports\Logger')) {
Loader::addNamespace('Yansongda\Supports', $libraryDir . $version . DS . 'Yansongda' . DS . 'Supports' . DS);
}
// V3需载入辅助函数
if ($version == Service::SDK_VERSION_V3) {
require_once $libraryDir . $version . DS . 'Yansongda' . DS . 'Pay' . DS . 'Functions.php';
}
}
}
}
... ...
<form id="config-form" class="edit-form form-horizontal" role="form" data-toggle="validator" method="POST" action="">
<div class="panel panel-default panel-intro">
<div class="panel-heading">
<ul class="nav nav-tabs nav-group">
<li class="active"><a href="#wechat" data-toggle="tab">微信支付</a></li>
<li><a href="#alipay" data-toggle="tab">支付宝</a></li>
</ul>
</div>
<div class="panel-body">
<div id="myTabContent" class="tab-content">
{foreach $addon.config as $item}
{if $item.name=='version'}
<input type="hidden" value="{$item.value}" name="row[version]"/>
{elseif $item.name=='wechat'/}
<div class="tab-pane fade active in" id="wechat">
<table class="table table-striped table-config">
<tbody>
<tr>
<td width="20%">APP的app_id</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][appid]" value="{$item.value.appid|default=''}" class="form-control" data-rule="" data-tip="APP应用中支付时使用"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>公众号的app_id</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][app_id]" value="{$item.value.app_id|default=''}" class="form-control" data-rule="" data-tip="公众号中支付时使用"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>公众号的app_secret</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][app_secret]" value="{$item.value.app_secret|default=''}" class="form-control" data-rule="" data-tip="公众号中支付时使用"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>小程序的app_id</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][miniapp_id]" value="{$item.value.miniapp_id|default=''}" class="form-control" data-rule="" data-tip="仅在小程序支付时使用"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>微信支付商户号</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][mch_id]" value="{$item.value.mch_id|default=''}" class="form-control" data-rule="" data-tip=""/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>微信支付商户API密钥V2</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][key]" value="{$item.value.key|default=''}" class="form-control" data-rule="" data-tip=""/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>微信支付商户API密钥V3</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][key_v3]" value="{$item.value.key_v3|default=''}" class="form-control" data-rule="" data-tip=""/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>支付模式</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
{:Form::radios('row[wechat][mode]',['normal'=>'正式环境','dev'=>'沙箱环境','service'=>'服务商模式'],$item.value.mode??'normal')}
<div style="margin-top:5px;" data-type="dev" class="text-muted {if ($item.value.mode??'')!=='dev'}hidden{/if}">
<i class="fa fa-info-circle"></i> 沙箱环境:<a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_1&index=2" target="_blank">微信支付验收指引</a>
</div>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr data-type="service" class="{:$item.value.mode!='service'?'hidden':''}">
<td>子商户商户号ID</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][sub_mch_id]" value="{$item.value.sub_mch_id|default=''}" class="form-control" data-rule="" data-tip="如果未用到子商户,请勿填写"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr data-type="service" class="{:$item.value.mode!='service'?'hidden':''}">
<td>子商户APP的app_id</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][sub_appid]" value="{$item.value.sub_appid|default=''}" class="form-control" data-rule="" data-tip="如果未用到子商户,请勿填写"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr data-type="service" class="{:$item.value.mode!='service'?'hidden':''}">
<td>子商户公众号的app_id</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][sub_app_id]" value="{$item.value.sub_app_id|default=''}" class="form-control" data-rule="" data-tip="如果未用到子商户,请勿填写"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr data-type="service" class="{:$item.value.mode!='service'?'hidden':''}">
<td>子商户小程序的app_id</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][sub_miniapp_id]" value="{$item.value.sub_miniapp_id|default=''}" class="form-control" data-rule="" data-tip="如果未用到子商户,请勿填写"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>回调通知地址</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][notify_url]" value="{$item.value.notify_url|default=''}" class="form-control" data-rule="" data-tip="请勿随意修改,实际以逻辑代码中请求的为准"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>微信支付API证书cert</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<div class="input-group">
<input id="c-cert_client" class="form-control" size="50" name="row[wechat][cert_client]" type="text" value="{$item.value.cert_client|htmlentities}" data-tip="可选, 仅在退款、红包等情况时需要用到">
<div class="input-group-addon no-border no-padding">
<span><button type="button" id="faupload-cert_client" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"cert_client"}' data-mimetype="pem" data-input-id="c-cert_client" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
</div>
<span class="msg-box n-right" for="c-cert_client"></span>
</div>
<div style="margin-top:5px;"><a href="https://pay.weixin.qq.com" target="_blank"><i class="fa fa-question-circle"></i> 如何获取微信支付API证书?</a></div>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>微信支付API证书key</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<div class="input-group">
<input id="c-cert_key" class="form-control" size="50" name="row[wechat][cert_key]" type="text" value="{$item.value.cert_key|htmlentities}" data-tip="可选, 仅在退款、红包等情况时需要用到">
<div class="input-group-addon no-border no-padding">
<span><button type="button" id="faupload-cert_key" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"cert_key"}' data-mimetype="pem" data-input-id="c-cert_key" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
</div>
<span class="msg-box n-right" for="c-cert_key"></span>
</div>
<div style="margin-top:5px;"><a href="https://pay.weixin.qq.com" target="_blank"><i class="fa fa-question-circle"></i> 如何获取微信支付API证书?</a></div>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>记录日志</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
{:Form::radios('row[wechat][log]',['1'=>'开启','0'=>'关闭'],$item.value.log)}
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
{elseif $item.name=='alipay'}
<div class="tab-pane fade" id="alipay">
<table class="table table-striped table-config">
<tbody>
<tr>
<td>支付模式</td>
<td>
<div class="row">
<div class="col-sm-12 col-xs-12">
{:Form::radios('row[alipay][mode]',['normal'=>'正式环境','dev'=>'沙箱环境', 'service'=>'服务商模式'],$item.value.mode??'normal')}
<div style="margin-top:5px;" data-mode="dev" class="text-muted {if ($item.value.mode??'')!=='dev'}hidden{/if}">
<i class="fa fa-info-circle"></i> 如果使用沙箱环境,务必使用沙箱的app_id和沙箱配置,以及使用沙箱账号进行测试。<br>
沙箱环境:<a href="https://openhome.alipay.com/develop/sandbox/app" target="_blank">https://openhome.alipay.com/develop/sandbox/app</a>
</div>
</div>
</div>
</td>
</tr>
<tr class="text-muted {if ($item.value.mode??'')!=='service'}hidden{/if}" data-mode="service">
<td width="20%">服务商ID(pid)</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[alipay][pid]" value="{$item.value.pid|default=''}" class="form-control" data-rule="" data-tip=""/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td width="20%">应用ID(app_id)</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[alipay][app_id]" value="{$item.value.app_id|default=''}" class="form-control" data-rule="" data-tip=""/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>回调通知地址</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[alipay][notify_url]" value="{$item.value.notify_url|default=''}" class="form-control" data-rule="" data-tip="请勿随意修改,实际以逻辑代码中请求的为准"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>支付跳转地址</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[alipay][return_url]" value="{$item.value.return_url|default=''}" class="form-control" data-rule="" data-tip="请勿随意修改,实际以逻辑代码中请求的为准"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>应用私钥(private_key)</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[alipay][private_key]" value="{$item.value.private_key|default=''}" class="form-control" data-rule=""/>
<div style="margin-top:5px;"><a href="https://opensupport.alipay.com/support/helpcenter/207/201602469554" target="_blank"><i class="fa fa-question-circle"></i> 如何获取应用私钥?</a></div>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>签名方式</td>
<td>
<div>
<div class="radio">
<label for="row[alipay][signtype]-publickey"><input id="row[alipay][signtype]-publickey" name="row[alipay][signtype]" {if isset($item.value.signtype)&&$item.value.signtype=='publickey'}checked{/if} type="radio" value="publickey"> 普通公钥</label>
<label for="row[alipay][signtype]-cert"><input id="row[alipay][signtype]-cert" {if isset($item.value.signtype)&&$item.value.signtype=='cert'}checked{/if} name="row[alipay][signtype]" type="radio" value="cert"> 公钥证书</label>
</div>
</div>
<div style="margin-top:5px;" class="text-muted">
<i class="fa fa-info-circle"></i> 如果要使用转账、提现功能,则必须使用公钥证书
</div>
</td>
</tr>
<tr>
<td>
<span data-signtype="publickey" class="{if ($item.value.signtype??'')==='cert'}hidden{/if}">支付宝公钥</span>
<span data-signtype="cert" class="{if ($item.value.signtype??'')==='publickey' || ($item.value.signtype??'')==''}hidden{/if}">支付宝公钥证书路径</span>
(alipay_public_key)
</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<div class="input-group">
<input id="c-ali_public_key" class="form-control" size="50" name="row[alipay][ali_public_key]" type="text" value="{$item.value.ali_public_key|default=''|htmlentities}" placeholder="普通公钥请直接粘贴,公钥证书请点击右侧的上传">
<div class="input-group-addon no-border no-padding {if ($item.value.signtype??'')==='publickey' || ($item.value.signtype??'')==''}hidden{/if}" data-signtype="cert">
<span><button type="button" id="faupload-ali_public_key" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"ali_public_key"}' data-mimetype="crt" data-input-id="c-ali_public_key" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
</div>
<span class="msg-box n-right" for="c-ali_public_key"></span>
</div>
<div style="margin-top:5px;"><a href="https://opensupport.alipay.com/support/FAQ/aba5803b-ad15-4474-aed6-92e43ea253ea" target="_blank"><i class="fa fa-question-circle"></i> 如何获取支付宝公钥?</a></div>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>
<span data-signtype="publickey" class="{if ($item.value.signtype??'')==='cert'}hidden{/if}">应用公钥</span>
<span data-signtype="cert" class="{if ($item.value.signtype??'')==='publickey' || ($item.value.signtype??'')==''}hidden{/if}">应用公钥证书路径</span>
(app_cert_public_key)
</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<div class="input-group">
<input id="c-app_cert_public_key" class="form-control" size="50" name="row[alipay][app_cert_public_key]" type="text" value="{$item.value.app_cert_public_key|default=''|htmlentities}">
<div class="input-group-addon no-border no-padding {if ($item.value.signtype??'')==='publickey' || ($item.value.signtype??'')==''}hidden{/if}" data-signtype="cert">
<span><button type="button" id="faupload-app_cert_public_key" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"app_cert_public_key"}' data-mimetype="crt" data-input-id="c-app_cert_public_key" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
</div>
<span class="msg-box n-right" for="c-app_cert_public_key"></span>
</div>
<div style="margin-top:5px;"><a href="https://opensupport.alipay.com/support/FAQ/aba5803b-ad15-4474-aed6-92e43ea253ea" target="_blank"><i class="fa fa-question-circle"></i> 如何获取应用公钥?</a></div>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr class="{if ($item.value.signtype??'')==='publickey' || ($item.value.signtype??'')==''}hidden{/if}" data-signtype="cert">
<td>支付宝根证书路径(alipay_root_cert)</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<div class="input-group">
<input id="c-alipay_root_cert" class="form-control" size="50" name="row[alipay][alipay_root_cert]" type="text" value="{$item.value.alipay_root_cert|default=''|htmlentities}">
<div class="input-group-addon no-border no-padding">
<span><button type="button" id="faupload-alipay_root_cert" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"alipay_root_cert"}' data-mimetype="crt" data-input-id="c-alipay_root_cert" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
</div>
<span class="msg-box n-right" for="c-alipay_root_cert"></span>
</div>
<div style="margin-top:5px;"><a href="https://opensupport.alipay.com/support/helpcenter/271/201602474998" target="_blank"><i class="fa fa-question-circle"></i> 如何获取支付宝根证书?</a></div>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>记录日志</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
{:Form::radios('row[alipay][log]',['1'=>'开启','0'=>'关闭'],$item.value.log)}
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>PC端使用扫码支付</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
{:Form::radios('row[alipay][scanpay]',['1'=>'开启','0'=>'关闭'],$item.value.scanpay??0)}
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
{/if}
{/foreach}
<div class="form-group layer-footer">
<label class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-8">
<button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
<button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
</div>
</div>
</div>
</div>
</div>
</form>
<script>
require.callback = function () {
define('backend/addon', ['backend', 'form'], function (Backend, Form) {
var Controller = {
config: function () {
$(document).on("click", ".nav-group li a[data-toggle='tab']", function () {
if ($(this).attr("href") == "#all") {
$(".tab-pane").addClass("active in");
}
return;
});
$(document).on("click", "input[name='row[wechat][mode]']", function () {
$("#wechat [data-type]").addClass("hidden");
$("#wechat [data-type='" + $(this).val() + "']").removeClass("hidden");
});
$(document).on("click", "input[name='row[alipay][mode]']", function () {
$("#alipay [data-mode]").addClass("hidden");
$("#alipay [data-mode='" + $(this).val() + "']").removeClass("hidden");
});
$(document).on("click", "input[name='row[alipay][signtype]']", function () {
$("#alipay [data-signtype]").addClass("hidden");
$("#alipay [data-signtype='" + $(this).val() + "']").removeClass("hidden");
});
Form.api.bindevent($("form[role=form]"));
}
};
return Controller;
});
};
</script>
... ...
<?php
return [
[
'name' => 'version',
'title' => 'API版本(请勿修改该值)',
'type' => 'radio',
'content' => [],
'value' => 'v2',
'rule' => '',
'msg' => '',
'tip' => 'V2版本只支持微信支付V2密钥,V3版本只支持微信支付V3密钥,请勿修改该值!!!',
'ok' => '',
'extend' => '',
],
[
'name' => 'wechat',
'title' => '微信',
'type' => 'array',
'content' => [],
'value' => [
'appid' => '',
'app_id' => 'wx2a6d9ce4bffbe304',
'app_secret' => 'c52864075c132c27c286e7c842cf07bf',
'miniapp_id' => 'wx2a6d9ce4bffbe304',
'mch_id' => '1641391520',
'key' => 'shanghuhaokaifaduijiemiyao202300',
'key_v3' => 'shanghuhaokaifaduijiemiyao202300',
'mode' => 'normal',
'sub_mch_id' => '',
'sub_appid' => '',
'sub_app_id' => '',
'sub_miniapp_id' => '',
'notify_url' => '',
'cert_client' => '/addons/epay/certs/apiclient_cert.pem',
'cert_key' => '/addons/epay/certs/apiclient_key.pem',
'log' => '1',
],
'rule' => 'required',
'msg' => '',
'tip' => '微信参数配置',
'ok' => '',
'extend' => '',
],
[
'name' => 'alipay',
'title' => '支付宝',
'type' => 'array',
'content' => [],
'value' => [
'mode' => 'normal',
'pid' => '',
'app_id' => '',
'notify_url' => '/addons/epay/api/notifyx/type/alipay',
'return_url' => '/addons/epay/api/returnx/type/alipay',
'private_key' => '',
'signtype' => 'cert',
'ali_public_key' => '',
'app_cert_public_key' => '',
'alipay_root_cert' => '',
'log' => '1',
'scanpay' => '0',
],
'rule' => 'required',
'msg' => '',
'tip' => '支付宝参数配置',
'ok' => '',
'extend' => '',
],
];
... ...
<?php
namespace addons\epay\controller;
use addons\epay\library\Service;
use addons\epay\library\Wechat;
use addons\third\model\Third;
use app\common\library\Auth;
use Exception;
use think\addons\Controller;
use think\Response;
use think\Session;
use Yansongda\Pay\Exceptions\GatewayException;
use Yansongda\Pay\Pay;
/**
* API接口控制器
*
* @package addons\epay\controller
*/
class Api extends Controller
{
protected $layout = 'default';
protected $config = [];
/**
* 默认方法
*/
public function index()
{
return;
}
/**
* 外部提交
*/
public function submit()
{
$this->request->filter('trim');
$out_trade_no = $this->request->request("out_trade_no");
$title = $this->request->request("title");
$amount = $this->request->request('amount');
$type = $this->request->request('type', $this->request->request('paytype'));
$method = $this->request->request('method', 'web');
$openid = $this->request->request('openid', '');
$auth_code = $this->request->request('auth_code', '');
$notifyurl = $this->request->request('notifyurl', '');
$returnurl = $this->request->request('returnurl', '');
if (!$amount || $amount < 0) {
$this->error("支付金额必须大于0");
}
if (!$type || !in_array($type, ['alipay', 'wechat'])) {
$this->error("支付类型错误");
}
$params = [
'type' => $type,
'out_trade_no' => $out_trade_no,
'title' => $title,
'amount' => $amount,
'method' => $method,
'openid' => $openid,
'auth_code' => $auth_code,
'notifyurl' => $notifyurl,
'returnurl' => $returnurl,
];
return Service::submitOrder($params);
}
/**
* 微信支付(公众号支付&PC扫码支付)
*/
public function wechat()
{
$config = Service::getConfig('wechat');
$isWechat = stripos($this->request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false;
$isMobile = $this->request->isMobile();
$this->view->assign("isWechat", $isWechat);
$this->view->assign("isMobile", $isMobile);
//发起PC支付(Scan支付)(PC扫码模式)
if ($this->request->isAjax()) {
$pay = Pay::wechat($config);
$orderid = $this->request->post("orderid");
try {
$result = Service::isVersionV3() ? $pay->find(['out_trade_no' => $orderid]) : $pay->find($orderid, 'scan');
$this->success("", "", ['status' => $result['trade_state'] ?? 'NOTPAY']);
} catch (GatewayException $e) {
$this->error("查询失败(1001)");
}
}
$orderData = Session::get("wechatorderdata");
if (!$orderData) {
$this->error("请求参数错误");
}
if ($isWechat && $isMobile) {
//发起公众号(jsapi支付),openid必须
//如果没有openid,则自动去获取openid
if (!isset($orderData['openid']) || !$orderData['openid']) {
$orderData['openid'] = Service::getOpenid();
}
$orderData['method'] = 'mp';
$type = 'jsapi';
$payData = Service::submitOrder($orderData);
if (!isset($payData['paySign'])) {
$this->error("创建订单失败,请返回重试", "");
}
} else {
$orderData['method'] = 'scan';
$type = 'pc';
$payData = Service::submitOrder($orderData);
if (!isset($payData['code_url'])) {
$this->error("创建订单失败,请返回重试", "");
}
}
$this->view->assign("orderData", $orderData);
$this->view->assign("payData", $payData);
$this->view->assign("type", $type);
$this->view->assign("title", "微信支付");
return $this->view->fetch();
}
/**
* 支付宝支付(PC扫码支付)
*/
public function alipay()
{
$config = Service::getConfig('alipay');
$isWechat = stripos($this->request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false;
$isMobile = $this->request->isMobile();
$this->view->assign("isWechat", $isWechat);
$this->view->assign("isMobile", $isMobile);
if ($this->request->isAjax()) {
$orderid = $this->request->post("orderid");
$pay = Pay::alipay($config);
try {
$result = $pay->find(['out_trade_no' => $orderid]);
if ($result['code'] == '10000' && $result['trade_status'] == 'TRADE_SUCCESS') {
$this->success("", "", ['status' => $result['trade_status']]);
} else {
$this->error("查询失败");
}
} catch (GatewayException $e) {
$this->error("查询失败(1001)");
}
}
//发起PC支付(Scan支付)(PC扫码模式)
$orderData = Session::get("alipayorderdata");
if (!$orderData) {
$this->error("请求参数错误");
}
$orderData['method'] = 'scan';
$payData = Service::submitOrder($orderData);
if (!isset($payData['qr_code'])) {
$this->error("创建订单失败,请返回重试");
}
$type = 'pc';
$this->view->assign("orderData", $orderData);
$this->view->assign("payData", $payData);
$this->view->assign("type", $type);
$this->view->assign("title", "支付宝支付");
return $this->view->fetch();
}
/**
* 支付成功回调
*/
public function notifyx()
{
$paytype = $this->request->param('paytype');
$pay = Service::checkNotify($paytype);
if (!$pay) {
return json(['code' => 'FAIL', 'message' => '失败'], 500, ['Content-Type' => 'application/json']);
}
// 获取回调数据,V3和V2的回调接收不同
$data = Service::isVersionV3() ? $pay->callback() : $pay->verify();
try {
//微信支付V3返回和V2不同
if (Service::isVersionV3() && $paytype === 'wechat') {
$data = $data['resource']['ciphertext'];
$data['total_fee'] = $data['amount']['total'];
}
\think\Log::record($data);
//获取支付金额、订单号
$payamount = $paytype == 'alipay' ? $data['total_amount'] : $data['total_fee'] / 100;
$out_trade_no = $data['out_trade_no'];
\think\Log::record("回调成功,订单号:{$out_trade_no},金额:{$payamount}");
//你可以在此编写订单逻辑
} catch (Exception $e) {
\think\Log::record("回调逻辑处理错误:" . $e->getMessage(), "error");
}
//下面这句必须要执行,且在此之前不能有任何输出
if (Service::isVersionV3()) {
return $pay->success()->getBody()->getContents();
} else {
return $pay->success()->send();
}
}
/**
* 支付成功返回
*/
public function returnx()
{
$paytype = $this->request->param('paytype');
if (Service::checkReturn($paytype)) {
echo '签名错误';
return;
}
//你可以在这里定义你的提示信息,但切记不可在此编写逻辑
$this->success("恭喜你!支付成功!", addon_url("epay/index/index"));
}
}
... ...
<?php
namespace addons\epay\controller;
use addons\epay\library\Service;
use fast\Random;
use think\addons\Controller;
use Exception;
/**
* 微信支付宝整合插件首页
*
* 此控制器仅用于开发展示说明和测试,请自行添加一个新的控制器进行处理返回和回调事件,同时删除此控制器文件
*
* Class Index
* @package addons\epay\controller
*/
class Index extends Controller
{
protected $layout = 'default';
protected $config = [];
public function _initialize()
{
parent::_initialize();
if (!config("app_debug")) {
$this->error("仅在开发环境下查看");
}
}
public function index()
{
$this->view->assign("title", "微信支付宝整合");
return $this->view->fetch();
}
/**
* 体验,仅供开发测试
*/
public function experience()
{
$amount = $this->request->post('amount');
$type = $this->request->post('type');
$method = $this->request->post('method');
$openid = $this->request->post('openid', "");
if (!$amount || $amount < 0) {
$this->error("支付金额必须大于0");
}
if (!$type || !in_array($type, ['alipay', 'wechat'])) {
$this->error("支付类型不能为空");
}
if (in_array($method, ['miniapp', 'mp']) && !$openid) {
$this->error("openid不能为空");
}
//订单号
$out_trade_no = date("YmdHis") . mt_rand(100000, 999999);
//订单标题
$title = '测试订单';
//回调链接
$notifyurl = $this->request->root(true) . '/addons/epay/index/notifyx/paytype/' . $type;
$returnurl = $this->request->root(true) . '/addons/epay/index/returnx/paytype/' . $type . '/out_trade_no/' . $out_trade_no;
$response = Service::submitOrder($amount, $out_trade_no, $type, $title, $notifyurl, $returnurl, $method, $openid);
return $response;
}
/**
* 支付成功,仅供开发测试
*/
public function notifyx()
{
$paytype = $this->request->param('paytype');
$pay = Service::checkNotify($paytype);
if (!$pay) {
return json(['code' => 'FAIL', 'message' => '失败'], 500, ['Content-Type' => 'application/json']);
}
// 获取回调数据,V3和V2的回调接收不同
$data = Service::isVersionV3() ? $pay->callback() : $pay->verify();
try {
//微信支付V3返回和V2不同
if (Service::isVersionV3() && $paytype === 'wechat') {
$data = $data['resource']['ciphertext'];
$data['total_fee'] = $data['amount']['total'];
}
\think\Log::record($data);
//获取支付金额、订单号
$payamount = $paytype == 'alipay' ? $data['total_amount'] : $data['total_fee'] / 100;
$out_trade_no = $data['out_trade_no'];
\think\Log::record("回调成功,订单号:{$out_trade_no},金额:{$payamount}");
//你可以在此编写订单逻辑
} catch (Exception $e) {
\think\Log::record("回调逻辑处理错误:" . $e->getMessage(), "error");
}
//下面这句必须要执行,且在此之前不能有任何输出
if (Service::isVersionV3()) {
return $pay->success()->getBody()->getContents();
} else {
return $pay->success()->send();
}
}
/**
* 支付返回,仅供开发测试
*/
public function returnx()
{
$paytype = $this->request->param('paytype');
$out_trade_no = $this->request->param('out_trade_no');
$pay = Service::checkReturn($paytype);
if (!$pay) {
$this->error('签名错误', '');
}
//你可以在这里定义你的提示信息,但切记不可在此编写逻辑
$this->success("请返回网站查看支付结果", addon_url("epay/index/index"));
}
}
... ...
name = epay
title = 微信支付宝整合
intro = 可用于快速整合企业微信、支付宝支付功能
author = FastAdmin
website = https://www.fastadmin.net
version = 1.3.7
state = 1
url = /addons/epay
license = regular
licenseto = 10789
... ...
<?php
namespace addons\epay\library;
class Collection extends \Yansongda\Supports\Collection
{
/**
* 创建 Collection 实例
* @access public
* @param array $items 数据
* @return static
*/
public static function make($items = [])
{
return new static($items);
}
}
... ...
<?php
namespace addons\epay\library;
use think\Exception;
class OrderException extends Exception
{
public function __construct($message = "", $code = 0, $data = [])
{
$this->message = $message;
$this->code = $code;
$this->data = $data;
}
}
... ...
<?php
namespace addons\epay\library;
class RedirectResponse extends \Symfony\Component\HttpFoundation\RedirectResponse implements \JsonSerializable, \Serializable
{
public function __toString()
{
return $this->getContent();
}
public function setTargetUrl($url)
{
if ('' === ($url ?? '')) {
throw new \InvalidArgumentException('无法跳转到空页面');
}
$this->targetUrl = $url;
$this->setContent(
sprintf('<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="refresh" content="0;url=\'%1$s\'" />
<title>正在跳转支付 %1$s</title>
</head>
<body>
<div id="redirect" style="display:none;">正在跳转支付 <a href="%1$s">%1$s</a></div>
<script type="text/javascript">
setTimeout(function(){
document.getElementById("redirect").style.display = "block";
}, 1000);
</script>
</body>
</html>', htmlspecialchars($url, \ENT_QUOTES, 'UTF-8')));
$this->headers->set('Location', $url);
return $this;
}
public function jsonSerialize()
{
return $this->getContent();
}
public function serialize()
{
return serialize($this->content);
}
public function unserialize($serialized)
{
return $this->content = unserialize($serialized);
}
}
... ...
<?php
namespace addons\epay\library;
class Response extends \Symfony\Component\HttpFoundation\Response implements \JsonSerializable, \Serializable
{
public function __toString()
{
return $this->getContent();
}
public function jsonSerialize()
{
return $this->getContent();
}
public function serialize()
{
return serialize($this->content);
}
public function unserialize($serialized)
{
return $this->content = unserialize($serialized);
}
}
... ...
<?php
namespace addons\epay\library;
use addons\third\model\Third;
use app\common\library\Auth;
use Exception;
use think\Hook;
use think\Session;
use Yansongda\Pay\Pay;
use Yansongda\Supports\Str;
/**
* 订单服务类
*
* @package addons\epay\library
*/
class Service
{
public const SDK_VERSION_V2 = 'v2';
public const SDK_VERSION_V3 = 'v3';
/**
* 提交订单
* @param array|float $amount 订单金额
* @param string $orderid 订单号
* @param string $type 支付类型,可选alipay或wechat
* @param string $title 订单标题
* @param string $notifyurl 通知回调URL
* @param string $returnurl 跳转返回URL
* @param string $method 支付方法
* @param string $openid Openid
* @param array $custom 自定义微信支付宝相关配置
* @return Response|RedirectResponse|Collection
* @throws Exception
*/
public static function submitOrder($amount, $orderid = null, $type = null, $title = null, $notifyurl = null, $returnurl = null, $method = null, $openid = '', $custom = [])
{
$version = self::getSdkVersion();
$request = request();
$addonConfig = get_addon_config('epay');
if (!is_array($amount)) {
$params = [
'amount' => $amount,
'orderid' => $orderid,
'type' => $type,
'title' => $title,
'notifyurl' => $notifyurl,
'returnurl' => $returnurl,
'method' => $method,
'openid' => $openid,
'custom' => $custom,
];
} else {
$params = $amount;
}
$type = isset($params['type']) && in_array($params['type'], ['alipay', 'wechat']) ? $params['type'] : 'wechat';
$method = $params['method'] ?? 'web';
$orderid = $params['orderid'] ?? date("YmdHis") . mt_rand(100000, 999999);
$amount = $params['amount'] ?? 1;
$title = $params['title'] ?? "支付";
$auth_code = $params['auth_code'] ?? '';
$openid = $params['openid'] ?? '';
//自定义微信支付宝相关配置
$custom = $params['custom'] ?? [];
//未定义则使用默认回调和跳转
$notifyurl = !empty($params['notifyurl']) ? $params['notifyurl'] : $request->root(true) . '/addons/epay/index/notifyx/paytype/' . $type;
$returnurl = !empty($params['returnurl']) ? $params['returnurl'] : $request->root(true) . '/addons/epay/index/returnx/paytype/' . $type . '/out_trade_no/' . $orderid;
$html = '';
$config = Service::getConfig($type, array_merge($custom, ['notify_url' => $notifyurl, 'return_url' => $returnurl]));
//判断是否移动端或微信内浏览器
$isMobile = $request->isMobile();
$isWechat = strpos($request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false;
$result = null;
if ($type == 'alipay') {
//如果是PC支付,判断当前环境,进行跳转
if ($method == 'web') {
//如果是微信环境或后台配置PC使用扫码支付
if ($isWechat || $addonConfig['alipay']['scanpay']) {
Session::set("alipayorderdata", $params);
$url = addon_url('epay/api/alipay', [], true, true);
return new RedirectResponse($url);
} elseif ($isMobile) {
$method = 'wap';
}
}
//创建支付对象
$pay = Pay::alipay($config);
$params = [
'out_trade_no' => $orderid,//你的订单号
'total_amount' => $amount,//单位元
'subject' => $title,
];
switch ($method) {
case 'web':
//电脑支付
$result = $pay->web($params);
break;
case 'wap':
//手机网页支付
$result = $pay->wap($params);
break;
case 'app':
//APP支付
$result = $pay->app($params);
break;
case 'scan':
//扫码支付
$result = $pay->scan($params);
break;
case 'pos':
//刷卡支付必须要有auth_code
$params['auth_code'] = $auth_code;
$result = $pay->pos($params);
break;
case 'mini':
case 'miniapp':
//小程序支付,直接返回字符串
//小程序支付必须要有buyer_id或buyer_open_id
if (is_numeric($openid) && strlen($openid) === 16) {
$params['buyer_id'] = $openid;
} else {
$params['buyer_open_id'] = $openid;
}
$result = $pay->mini($params);
break;
default:
}
} else {
//如果是PC支付,判断当前环境,进行跳转
if ($method == 'web') {
//如果是移动端,但不是微信环境
if ($isMobile && !$isWechat) {
$method = 'wap';
} else {
Session::set("wechatorderdata", $params);
$url = addon_url('epay/api/wechat', [], true, true);
return new RedirectResponse($url);
}
}
//单位分
$total_fee = function_exists('bcmul') ? bcmul($amount, 100) : $amount * 100;
$total_fee = (int)$total_fee;
$ip = $request->ip();
//微信服务商模式时需传递sub_openid参数
$openidName = $addonConfig['wechat']['mode'] == 'service' ? 'sub_openid' : 'openid';
//创建支付对象
$pay = Pay::wechat($config);
if (self::isVersionV3()) {
//V3支付
$params = [
'out_trade_no' => $orderid,
'description' => $title,
'amount' => [
'total' => $total_fee,
]
];
switch ($method) {
case 'mp':
//公众号支付
//公众号支付必须有openid
$params['payer'] = [$openidName => $openid];
$result = $pay->mp($params);
break;
case 'wap':
//手机网页支付,跳转
$params['scene_info'] = [
'payer_client_ip' => $ip,
'h5_info' => [
'type' => 'Wap',
]
];
$result = $pay->wap($params);
break;
case 'app':
//APP支付,直接返回字符串
$result = $pay->app($params);
break;
case 'scan':
//扫码支付,直接返回字符串
$result = $pay->scan($params);
break;
case 'pos':
//刷卡支付,直接返回字符串
//刷卡支付必须要有auth_code
$params['auth_code'] = $auth_code;
$result = $pay->pos($params);
break;
case 'mini':
case 'miniapp':
//小程序支付,直接返回字符串
//小程序支付必须要有openid
$params['payer'] = [$openidName => $openid];
$result = $pay->mini($params);
break;
default:
}
} else {
//V2支付
$params = [
'out_trade_no' => $orderid,
'body' => $title,
'total_fee' => $total_fee,
];
switch ($method) {
case 'mp':
//公众号支付
//公众号支付必须有openid
$params[$openidName] = $openid;
$result = $pay->mp($params);
break;
case 'wap':
//手机网页支付,跳转
$params['spbill_create_ip'] = $ip;
$result = $pay->wap($params);
break;
case 'app':
//APP支付,直接返回字符串
$result = $pay->app($params);
break;
case 'scan':
//扫码支付,直接返回字符串
$result = $pay->scan($params);
break;
case 'pos':
//刷卡支付,直接返回字符串
//刷卡支付必须要有auth_code
$params['auth_code'] = $auth_code;
$result = $pay->pos($params);
break;
case 'mini':
case 'miniapp':
//小程序支付,直接返回字符串
//小程序支付必须要有openid
$params[$openidName] = $openid;
$result = $pay->miniapp($params);
break;
default:
}
}
}
//使用重写的Response类、RedirectResponse、Collection类
if ($result instanceof \Symfony\Component\HttpFoundation\RedirectResponse) {
$result = new RedirectResponse($result->getTargetUrl());
} elseif ($result instanceof \Symfony\Component\HttpFoundation\Response) {
$result = new Response($result->getContent());
} elseif ($result instanceof \Yansongda\Supports\Collection) {
$result = Collection::make($result->all());
} elseif ($result instanceof \GuzzleHttp\Psr7\Response) {
$result = new Response($result->getBody());
}
return $result;
}
/**
* 验证回调是否成功
* @param string $type 支付类型
* @param array $custom 自定义配置信息
* @return bool|\Yansongda\Pay\Gateways\Alipay|\Yansongda\Pay\Gateways\Wechat|\Yansongda\Pay\Provider\Wechat|\Yansongda\Pay\Provider\Alipay
*/
public static function checkNotify($type, $custom = [])
{
$type = strtolower($type);
if (!in_array($type, ['wechat', 'alipay'])) {
return false;
}
$version = self::getSdkVersion();
try {
$config = self::getConfig($type, $custom);
$pay = $type == 'wechat' ? Pay::wechat($config) : Pay::alipay($config);
$data = Service::isVersionV3() ? $pay->callback() : $pay->verify();
if ($type == 'alipay') {
if (in_array($data['trade_status'], ['TRADE_SUCCESS', 'TRADE_FINISHED'])) {
return $pay;
}
} else {
return $pay;
}
} catch (Exception $e) {
\think\Log::record("回调请求参数解析错误", "error");
return false;
}
return false;
}
/**
* 验证返回是否成功,请勿用于判断是否支付成功的逻辑验证
* 已弃用
*
* @param string $type 支付类型
* @param array $custom 自定义配置信息
* @return bool
* @deprecated 已弃用,请勿用于逻辑验证
*/
public static function checkReturn($type, $custom = [])
{
//由于PC及移动端无法获取请求的参数信息,取消return验证,均返回true
return true;
}
/**
* 获取配置
* @param string $type 支付类型
* @param array $custom 自定义配置,用于覆盖插件默认配置
* @return array
*/
public static function getConfig($type = 'wechat', $custom = [])
{
$addonConfig = get_addon_config('epay');
$config = $addonConfig[$type] ?? $addonConfig['wechat'];
// SDK版本
$version = self::getSdkVersion();
if (isset($config['cert_client']) && substr($config['cert_client'], 0, 8) == '/addons/') {
$config['cert_client'] = ROOT_PATH . str_replace('/', DS, substr($config['cert_client'], 1));
}
if (isset($config['cert_key']) && substr($config['cert_key'], 0, 8) == '/addons/') {
$config['cert_key'] = ROOT_PATH . str_replace('/', DS, substr($config['cert_key'], 1));
}
if (isset($config['app_cert_public_key']) && substr($config['app_cert_public_key'], 0, 8) == '/addons/') {
$config['app_cert_public_key'] = ROOT_PATH . str_replace('/', DS, substr($config['app_cert_public_key'], 1));
}
if (isset($config['alipay_root_cert']) && substr($config['alipay_root_cert'], 0, 8) == '/addons/') {
$config['alipay_root_cert'] = ROOT_PATH . str_replace('/', DS, substr($config['alipay_root_cert'], 1));
}
if (isset($config['ali_public_key']) && (Str::endsWith($config['ali_public_key'], '.crt') || Str::endsWith($config['ali_public_key'], '.pem'))) {
$config['ali_public_key'] = ROOT_PATH . str_replace('/', DS, substr($config['ali_public_key'], 1));
}
// V3支付
if (self::isVersionV3()) {
if ($type == 'wechat') {
$config['mp_app_id'] = $config['app_id'] ?? '';
$config['app_id'] = $config['appid'] ?? '';
$config['mini_app_id'] = $config['miniapp_id'] ?? '';
$config['combine_mch_id'] = $config['combine_mch_id'] ?? '';
$config['mch_secret_key'] = $config['key_v3'] ?? '';
$config['mch_secret_cert'] = $config['cert_key'];
$config['mch_public_cert_path'] = $config['cert_client'];
$config['sub_mp_app_id'] = $config['sub_appid'] ?? '';
$config['sub_app_id'] = $config['sub_app_id'] ?? '';
$config['sub_mini_app_id'] = $config['sub_miniapp_id'] ?? '';
$config['sub_mch_id'] = $config['sub_mch_id'] ?? '';
} elseif ($type == 'alipay') {
$config['app_secret_cert'] = $config['private_key'] ?? '';
$config['app_public_cert_path'] = $config['app_cert_public_key'] ?? '';
$config['alipay_public_cert_path'] = $config['ali_public_key'] ?? '';
$config['alipay_root_cert_path'] = $config['alipay_root_cert'] ?? '';
$config['service_provider_id'] = $config['pid'] ?? '';
}
$modeArr = ['normal' => 0, 'dev' => 1, 'service' => 2];
$config['mode'] = $modeArr[$config['mode']] ?? 0;
}
// 日志
if ($config['log']) {
$config['log'] = [
'enable' => true,
'file' => LOG_PATH . 'epaylogs' . DS . $type . '-' . date("Y-m-d") . '.log',
'level' => 'debug'
];
} else {
$config['log'] = [
'enable' => false,
];
}
// GuzzleHttp配置,可选
$config['http'] = [
'timeout' => 10,
'connect_timeout' => 10,
// 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html)
];
$config['notify_url'] = empty($config['notify_url']) ? addon_url('epay/api/notifyx', [], false) . '/type/' . $type : $config['notify_url'];
$config['notify_url'] = !preg_match("/^(http:\/\/|https:\/\/)/i", $config['notify_url']) ? request()->root(true) . $config['notify_url'] : $config['notify_url'];
$config['return_url'] = empty($config['return_url']) ? addon_url('epay/api/returnx', [], false) . '/type/' . $type : $config['return_url'];
$config['return_url'] = !preg_match("/^(http:\/\/|https:\/\/)/i", $config['return_url']) ? request()->root(true) . $config['return_url'] : $config['return_url'];
//合并自定义配置
$config = array_merge($config, $custom);
//v3版本时返回的结构不同
if (self::isVersionV3()) {
$config = [$type => ['default' => $config], 'logger' => $config['log'], 'http' => $config['http'], '_force' => true];
}
return $config;
}
/**
* 获取微信Openid
*
* @param array $custom 自定义配置信息
* @return mixed|string
*/
public static function getOpenid($custom = [])
{
$openid = '';
$auth = Auth::instance();
if ($auth->isLogin()) {
$third = get_addon_info('third');
if ($third && $third['state']) {
$thirdInfo = Third::where('user_id', $auth->id)->where('platform', 'wechat')->where('apptype', 'mp')->find();
$openid = $thirdInfo ? $thirdInfo['openid'] : '';
}
}
if (!$openid) {
$openid = Session::get("openid");
//如果未传openid,则去读取openid
if (!$openid) {
$addonConfig = get_addon_config('epay');
$wechat = new Wechat($custom['app_id'] ?? $addonConfig['wechat']['app_id'], $custom['app_secret'] ?? $addonConfig['wechat']['app_secret']);
$openid = $wechat->getOpenid();
}
}
return $openid;
}
/**
* 获取SDK版本
* @return mixed|string
*/
public static function getSdkVersion()
{
$addonConfig = get_addon_config('epay');
return $addonConfig['version'] ?? self::SDK_VERSION_V2;
}
/**
* 判断是否V2支付
* @return bool
*/
public static function isVersionV2()
{
return self::getSdkVersion() === self::SDK_VERSION_V2;
}
/**
* 判断是否V3支付
* @return bool
*/
public static function isVersionV3()
{
return self::getSdkVersion() === self::SDK_VERSION_V3;
}
}
... ...
<?php
namespace addons\epay\library;
use fast\Http;
use think\Cache;
use think\Session;
/**
* 微信授权
*
*/
class Wechat
{
private $app_id = '';
private $app_secret = '';
private $scope = 'snsapi_userinfo';
public function __construct($app_id, $app_secret)
{
$this->app_id = $app_id;
$this->app_secret = $app_secret;
}
/**
* 获取微信授权链接
*
* @return string
*/
public function getAuthorizeUrl()
{
$redirect_uri = addon_url('epay/api/wechat', [], true, true);
$redirect_uri = urlencode($redirect_uri);
$state = \fast\Random::alnum();
Session::set('state', $state);
return "https://open.weixin.qq.com/connect/oauth2/authorize?appid={$this->app_id}&redirect_uri={$redirect_uri}&response_type=code&scope={$this->scope}&state={$state}#wechat_redirect";
}
/**
* 获取微信openid
*
* @return mixed|string
*/
public function getOpenid()
{
$openid = Session::get('openid');
if (!$openid) {
if (!isset($_GET['code'])) {
$url = $this->getAuthorizeUrl();
Header("Location: $url");
exit();
} else {
$state = Session::get('state');
if ($state == $_GET['state']) {
$code = $_GET['code'];
$token = $this->getAccessToken($code);
if (!isset($token['openid']) && isset($token['errmsg'])) {
exception($token['errmsg']);
}
$openid = $token['openid'] ?? '';
if ($openid) {
Session::set("openid", $openid);
}
}
}
}
return $openid;
}
/**
* 获取授权token网页授权
*
* @param string $code
* @return mixed|string
*/
public function getAccessToken($code = '')
{
$params = [
'appid' => $this->app_id,
'secret' => $this->app_secret,
'code' => $code,
'grant_type' => 'authorization_code'
];
$ret = Http::sendRequest('https://api.weixin.qq.com/sns/oauth2/access_token', $params, 'GET');
if ($ret['ret']) {
$ar = json_decode($ret['msg'], true);
return $ar;
}
return [];
}
public function getJsticket($code = '')
{
$jsticket = Session::get('jsticket');
if (!$jsticket) {
$token = $this->getAccessToken($code);
$params = [
'access_token' => 'token',
'type' => 'jsapi',
];
$ret = Http::sendRequest('https://api.weixin.qq.com/cgi-bin/ticket/getticket', $params, 'GET');
if ($ret['ret']) {
$ar = json_decode($ret['msg'], true);
return $ar;
}
}
return $jsticket;
}
}
... ...
/tests export-ignore
/.github export-ignore
... ...
The MIT License (MIT)
Copyright (c) Hyperf
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
... ...
{
"name": "hyperf/context",
"description": "A coroutine context library.",
"license": "MIT",
"keywords": [
"php",
"swoole",
"hyperf",
"context"
],
"homepage": "https://hyperf.io",
"support": {
"docs": "https://hyperf.wiki",
"issues": "https://github.com/hyperf/hyperf/issues",
"pull-request": "https://github.com/hyperf/hyperf/pulls",
"source": "https://github.com/hyperf/hyperf"
},
"require": {
"php": ">=7.2",
"hyperf/engine": "^1.1"
},
"autoload": {
"psr-4": {
"Hyperf\\Context\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"HyperfTest\\Context\\": "tests/"
}
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
}
}
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Context;
use Hyperf\Engine\Coroutine;
class Context
{
protected static $nonCoContext = [];
public static function set(string $id, $value)
{
if (Coroutine::id() > 0) {
Coroutine::getContextFor()[$id] = $value;
} else {
static::$nonCoContext[$id] = $value;
}
return $value;
}
public static function get(string $id, $default = null, $coroutineId = null)
{
if (Coroutine::id() > 0) {
return Coroutine::getContextFor($coroutineId)[$id] ?? $default;
}
return static::$nonCoContext[$id] ?? $default;
}
public static function has(string $id, $coroutineId = null)
{
if (Coroutine::id() > 0) {
return isset(Coroutine::getContextFor($coroutineId)[$id]);
}
return isset(static::$nonCoContext[$id]);
}
/**
* Release the context when you are not in coroutine environment.
*/
public static function destroy(string $id)
{
unset(static::$nonCoContext[$id]);
}
/**
* Copy the context from a coroutine to current coroutine.
* This method will delete the origin values in current coroutine.
*/
public static function copy(int $fromCoroutineId, array $keys = []): void
{
$from = Coroutine::getContextFor($fromCoroutineId);
if ($from === null) {
return;
}
$current = Coroutine::getContextFor();
if ($keys) {
$map = array_intersect_key($from->getArrayCopy(), array_flip($keys));
} else {
$map = $from->getArrayCopy();
}
$current->exchangeArray($map);
}
/**
* Retrieve the value and override it by closure.
*/
public static function override(string $id, \Closure $closure)
{
$value = null;
if (self::has($id)) {
$value = self::get($id);
}
$value = $closure($value);
self::set($id, $value);
return $value;
}
/**
* Retrieve the value and store it if not exists.
* @param mixed $value
*/
public static function getOrSet(string $id, $value)
{
if (! self::has($id)) {
return self::set($id, value($value));
}
return self::get($id);
}
public static function getContainer()
{
if (Coroutine::id() > 0) {
return Coroutine::getContextFor();
}
return static::$nonCoContext;
}
}
... ...
/tests export-ignore
/.github export-ignore
... ...
The MIT License (MIT)
Copyright (c) Hyperf
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
... ...
{
"name": "hyperf/contract",
"description": "The contracts of Hyperf.",
"license": "MIT",
"keywords": [
"php",
"swoole",
"hyperf"
],
"homepage": "https://hyperf.io",
"support": {
"docs": "https://hyperf.wiki",
"issues": "https://github.com/hyperf/hyperf/issues",
"pull-request": "https://github.com/hyperf/hyperf/pulls",
"source": "https://github.com/hyperf/hyperf"
},
"require": {
"php": ">=7.2"
},
"autoload": {
"psr-4": {
"Hyperf\\Contract\\": "src/"
}
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
}
}
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface ApplicationInterface
{
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface Castable
{
/**
* Get the name of the caster class to use when casting from / to this cast target.
*
* @return CastsAttributes|CastsInboundAttributes|string
*/
public static function castUsing();
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface CastsAttributes
{
/**
* Transform the attribute from the underlying model values.
*
* @param object $model
* @param mixed $value
* @return mixed
*/
public function get($model, string $key, $value, array $attributes);
/**
* Transform the attribute to its underlying model values.
*
* @param object $model
* @param mixed $value
* @return array|string
*/
public function set($model, string $key, $value, array $attributes);
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface CastsInboundAttributes
{
/**
* Transform the attribute to its underlying model values.
*
* @param object $model
* @param mixed $value
* @return array
*/
public function set($model, string $key, $value, array $attributes);
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface CompressInterface
{
public function compress(): UnCompressInterface;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface ConfigInterface
{
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $key identifier of the entry to look for
* @param mixed $default default value of the entry when does not found
* @return mixed entry
*/
public function get(string $key, $default = null);
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* @param string $keys identifier of the entry to look for
* @return bool
*/
public function has(string $keys);
/**
* Set a value to the container by its identifier.
*
* @param string $key identifier of the entry to set
* @param mixed $value the value that save to container
*/
public function set(string $key, $value);
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface ConnectionInterface
{
/**
* Get the real connection from pool.
*/
public function getConnection();
/**
* Reconnect the connection.
*/
public function reconnect(): bool;
/**
* Check the connection is valid.
*/
public function check(): bool;
/**
* Close the connection.
*/
public function close(): bool;
/**
* Release the connection to pool.
*/
public function release(): void;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Psr\Container\ContainerInterface as PsrContainerInterface;
interface ContainerInterface extends PsrContainerInterface
{
/**
* Build an entry of the container by its name.
* This method behave like get() except resolves the entry again every time.
* For example if the entry is a class then a new instance will be created each time.
* This method makes the container behave like a factory.
*
* @param string $name entry name or a class name
* @param array $parameters Optional parameters to use to build the entry. Use this to force specific parameters
* to specific values. Parameters not defined in this array will be resolved using
* the container.
* @throws InvalidArgumentException the name parameter must be of type string
* @throws NotFoundException no entry found for the given name
*/
public function make(string $name, array $parameters = []);
/**
* Bind an arbitrary resolved entry to an identifier.
* Useful for testing 'get'.
*
* @param mixed $entry
*/
public function set(string $name, $entry);
/**
* Unbind an arbitrary resolved entry.
*/
public function unbind(string $name);
/**
* Bind an arbitrary definition to an identifier.
* Useful for testing 'make'.
*
* @param array|callable|string $definition
*/
public function define(string $name, $definition);
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface DispatcherInterface
{
public function dispatch(...$params);
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface FrequencyInterface
{
/**
* Number of hit per time.
*/
public function hit(int $number = 1): bool;
/**
* Hits per second.
*/
public function frequency(): float;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface IdGeneratorInterface
{
public function generate();
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface LengthAwarePaginatorInterface extends PaginatorInterface
{
/**
* Create a range of pagination URLs.
*/
public function getUrlRange(int $start, int $end): array;
/**
* Determine the total number of items in the data store.
*/
public function total(): int;
/**
* Get the page number of the last available page.
*/
public function lastPage(): int;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface MiddlewareInitializerInterface
{
public function initCoreMiddleware(string $serverName): void;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface NormalizerInterface
{
/**
* Normalizes an object into a set of arrays/scalars.
*
* @param mixed $object
* @return null|array|\ArrayObject|bool|float|int|string
*/
public function normalize($object);
/**
* Denormalizes data back into an object of the given class.
*
* @param mixed $data Data to restore
* @param string $class The expected class to instantiate
* @return mixed|object
*/
public function denormalize($data, string $class);
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Swoole\Http\Response;
use Swoole\Server;
interface OnCloseInterface
{
/**
* @param Response|Server $server
*/
public function onClose($server, int $fd, int $reactorId): void;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Swoole\Http\Request;
use Swoole\Http\Response;
interface OnHandShakeInterface
{
public function onHandShake(Request $request, Response $response): void;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Swoole\Http\Response;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
interface OnMessageInterface
{
/**
* @param Response|Server $server
*/
public function onMessage($server, Frame $frame): void;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\WebSocket\Server;
interface OnOpenInterface
{
/**
* @param Response|Server $server
*/
public function onOpen($server, Request $request): void;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Swoole\WebSocket\Server;
interface OnPacketInterface
{
/**
* @param Server $server
* @param mixed $data
* @param array $clientInfo
*/
public function onPacket($server, $data, $clientInfo): void;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Swoole\Coroutine\Server\Connection;
use Swoole\Server as SwooleServer;
interface OnReceiveInterface
{
/**
* @param Connection|SwooleServer $server
*/
public function onReceive($server, int $fd, int $reactorId, string $data): void;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface OnRequestInterface
{
/**
* @param mixed $request swoole request or psr server request
* @param mixed $response swoole response or swow session
*/
public function onRequest($request, $response): void;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface PackerInterface
{
public function pack($data): string;
public function unpack(string $data);
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface PaginatorInterface
{
/**
* Get the URL for a given page.
*/
public function url(int $page): string;
/**
* Add a set of query string values to the paginator.
*
* @param array|string $key
* @return $this
*/
public function appends($key, ?string $value = null);
/**
* Get / set the URL fragment to be appended to URLs.
*
* @return $this|string
*/
public function fragment(?string $fragment = null);
/**
* The URL for the next page, or null.
*/
public function nextPageUrl(): ?string;
/**
* Get the URL for the previous page, or null.
*/
public function previousPageUrl(): ?string;
/**
* Get all of the items being paginated.
*/
public function items(): array;
/**
* Get the "index" of the first item being paginated.
*/
public function firstItem(): ?int;
/**
* Get the "index" of the last item being paginated.
*/
public function lastItem(): ?int;
/**
* Determine how many items are being shown per page.
*/
public function perPage(): int;
/**
* Determine the current page being paginated.
*/
public function currentPage(): int;
/**
* Determine if there are enough items to split into multiple pages.
*/
public function hasPages(): bool;
/**
* Determine if there is more items in the data store.
*/
public function hasMorePages(): bool;
/**
* Determine if the list of items is empty or not.
*/
public function isEmpty(): bool;
/**
* Determine if the list of items is not empty.
*/
public function isNotEmpty(): bool;
/**
* Render the paginator using a given view.
*/
public function render(?string $view = null, array $data = []): string;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface PoolInterface
{
/**
* Get a connection from the connection pool.
*/
public function get(): ConnectionInterface;
/**
* Release a connection back to the connection pool.
*/
public function release(ConnectionInterface $connection): void;
/**
* Close and clear the connection pool.
*/
public function flush(): void;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface PoolOptionInterface
{
public function getMaxConnections(): int;
public function getMinConnections(): int;
public function getConnectTimeout(): float;
public function getWaitTimeout(): float;
public function getHeartbeat(): float;
public function getMaxIdleTime(): float;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Swoole\Coroutine\Http\Server as CoHttpServer;
use Swoole\Coroutine\Server as CoServer;
use Swoole\Server;
interface ProcessInterface
{
/**
* Create the process object according to process number and bind to server.
* @param CoHttpServer|CoServer|Server $server
*/
public function bind($server): void;
/**
* Determine if the process should start ?
* @param CoServer|Server $server
*/
public function isEnable($server): bool;
/**
* The logical of process will place in here.
*/
public function handle(): void;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Psr\Http\Message\ResponseInterface;
interface ResponseEmitterInterface
{
/**
* @param mixed $connection swoole response or swow session
*/
public function emit(ResponseInterface $response, $connection, bool $withContent = true);
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface SessionInterface
{
/**
* Starts the session storage.
*
* @throws \RuntimeException if session fails to start
* @return bool True if session started
*/
public function start(): bool;
/**
* Returns the session ID.
*
* @return string The session ID
*/
public function getId(): string;
/**
* Sets the session ID.
*/
public function setId(string $id);
/**
* Returns the session name.
*/
public function getName(): string;
/**
* Sets the session name.
*/
public function setName(string $name);
/**
* Invalidates the current session.
*
* Clears all session attributes and flashes and regenerates the
* session and deletes the old session from persistence.
*
* @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
* will leave the system settings unchanged, 0 sets the cookie
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
*
* @return bool True if session invalidated, false if error
*/
public function invalidate(?int $lifetime = null): bool;
/**
* Migrates the current session to a new session id while maintaining all
* session attributes.
*
* @param bool $destroy Whether to delete the old session or leave it to garbage collection
* @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
* will leave the system settings unchanged, 0 sets the cookie
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
*
* @return bool True if session migrated, false if error
*/
public function migrate(bool $destroy = false, ?int $lifetime = null): bool;
/**
* Force the session to be saved and closed.
*
* This method is generally not required for real sessions as
* the session will be automatically saved at the end of
* code execution.
*/
public function save(): void;
/**
* Checks if an attribute is defined.
*
* @param string $name The attribute name
*
* @return bool true if the attribute is defined, false otherwise
*/
public function has(string $name): bool;
/**
* Returns an attribute.
*
* @param string $name The attribute name
* @param mixed $default The default value if not found
*/
public function get(string $name, $default = null);
/**
* Sets an attribute.
* @param mixed $value
*/
public function set(string $name, $value): void;
/**
* Put a key / value pair or array of key / value pairs in the session.
*
* @param array|string $key
* @param null|mixed $value
*/
public function put($key, $value = null): void;
/**
* Returns attributes.
*/
public function all(): array;
/**
* Sets attributes.
*/
public function replace(array $attributes): void;
/**
* Removes an attribute, returning its value.
*
* @return mixed The removed value or null when it does not exist
*/
public function remove(string $name);
/**
* Remove one or many items from the session.
*
* @param array|string $keys
*/
public function forget($keys): void;
/**
* Clears all attributes.
*/
public function clear(): void;
/**
* Checks if the session was started.
*/
public function isStarted(): bool;
/**
* Get the previous URL from the session.
*/
public function previousUrl(): ?string;
/**
* Set the "previous" URL in the session.
*/
public function setPreviousUrl(string $url): void;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Psr\Log\LoggerInterface;
interface StdoutLoggerInterface extends LoggerInterface
{
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface Synchronized
{
/**
* Whether the data has been synchronized.
*/
public function isSynchronized(): bool;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface TranslatorInterface
{
/**
* Get the translation for a given key.
*/
public function trans(string $key, array $replace = [], ?string $locale = null);
/**
* Get a translation according to an integer value.
*
* @param array|\Countable|int $number
*/
public function transChoice(string $key, $number, array $replace = [], ?string $locale = null): string;
/**
* Get the default locale being used.
*/
public function getLocale(): string;
/**
* Set the default locale.
*/
public function setLocale(string $locale);
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface TranslatorLoaderInterface
{
/**
* Load the messages for the given locale.
*/
public function load(string $locale, string $group, ?string $namespace = null): array;
/**
* Add a new namespace to the loader.
*/
public function addNamespace(string $namespace, string $hint);
/**
* Add a new JSON path to the loader.
*/
public function addJsonPath(string $path);
/**
* Get an array of all the registered namespaces.
*/
public function namespaces(): array;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
interface UnCompressInterface
{
public function uncompress();
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Contract;
use Hyperf\Utils\Contracts\MessageBag;
use Hyperf\Utils\Contracts\MessageProvider;
interface ValidatorInterface extends MessageProvider
{
/**
* Run the validator's rules against its data.
*/
public function validate(): array;
/**
* Get the attributes and values that were validated.
*/
public function validated(): array;
/**
* Determine if the data fails the validation rules.
*/
public function fails(): bool;
/**
* Get the failed validation rules.
*/
public function failed(): array;
/**
* Add conditions to a given field based on a Closure.
*
* @param array|string $attribute
* @param array|string $rules
* @return $this
*/
public function sometimes($attribute, $rules, callable $callback);
/**
* Add an after validation callback.
*
* @param callable|string $callback
* @return $this
*/
public function after($callback);
/**
* Get all of the validation error messages.
*/
public function errors(): MessageBag;
}
... ...
/.github export-ignore
/examples export-ignore
/tests export-ignore
... ...
/vendor/
composer.lock
*.cache
*.log
\ No newline at end of file
... ...
<?php
$header = <<<'EOF'
This file is part of Hyperf.
@link https://www.hyperf.io
@document https://hyperf.wiki
@contact group@hyperf.io
@license https://github.com/hyperf/hyperf/blob/master/LICENSE
EOF;
return (new PhpCsFixer\Config())
->setRiskyAllowed(true)
->setRules([
'@PSR2' => true,
'@Symfony' => true,
'@DoctrineAnnotation' => true,
'@PhpCsFixer' => true,
'header_comment' => [
'comment_type' => 'PHPDoc',
'header' => $header,
'separate' => 'none',
'location' => 'after_declare_strict',
],
'array_syntax' => [
'syntax' => 'short'
],
'list_syntax' => [
'syntax' => 'short'
],
'concat_space' => [
'spacing' => 'one'
],
'blank_line_before_statement' => [
'statements' => [
'declare',
],
],
'general_phpdoc_annotation_remove' => [
'annotations' => [
'author'
],
],
'ordered_imports' => [
'imports_order' => [
'class', 'function', 'const',
],
'sort_algorithm' => 'alpha',
],
'single_line_comment_style' => [
'comment_types' => [
],
],
'yoda_style' => [
'always_move_variable' => false,
'equal' => false,
'identical' => false,
],
'phpdoc_align' => [
'align' => 'left',
],
'multiline_whitespace_before_semicolons' => [
'strategy' => 'no_multi_line',
],
'constant_case' => [
'case' => 'lower',
],
'class_attributes_separation' => true,
'combine_consecutive_unsets' => true,
'declare_strict_types' => true,
'linebreak_after_opening_tag' => true,
'lowercase_static_reference' => true,
'no_useless_else' => true,
'no_unused_imports' => true,
'not_operator_with_successor_space' => true,
'not_operator_with_space' => false,
'ordered_class_elements' => true,
'php_unit_strict' => false,
'phpdoc_separation' => false,
'single_quote' => true,
'standardize_not_equals' => true,
'multiline_comment_opening_closing' => true,
])
->setFinder(
PhpCsFixer\Finder::create()
->exclude('vendor')
->in(__DIR__)
)
->setUsingCache(false);
... ...
<?php
namespace PHPSTORM_META {
// Reflect
override(\Psr\Container\ContainerInterface::get(0), map('@'));
}
\ No newline at end of file
... ...
# Default Dockerfile
#
# @link https://www.hyperf.io
# @document https://hyperf.wiki
# @contact group@hyperf.io
# @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
ARG PHP_VERSION
ARG ALPINE_VERSION
FROM hyperf/hyperf:${PHP_VERSION}-alpine-${ALPINE_VERSION}-swoole
LABEL maintainer="Hyperf Developers <group@hyperf.io>" version="1.0" license="MIT" app.name="Hyperf"
ARG timezone
ARG PHP_VERSION
ENV TIMEZONE=${timezone:-"Asia/Shanghai"}
ENV COMPOSER_ROOT_VERSION="v1.2.0"
# update
RUN set -ex \
# show php version and extensions
&& php -v \
&& php -m \
&& php --ri swoole \
# ---------- some config ----------
&& cd "/etc/php${PHP_VERSION%\.*}" \
# - config PHP
&& { \
echo "upload_max_filesize=128M"; \
echo "post_max_size=128M"; \
echo "memory_limit=1G"; \
echo "date.timezone=${TIMEZONE}"; \
} | tee conf.d/99_overrides.ini \
# - config timezone
&& ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime \
&& echo "${TIMEZONE}" > /etc/timezone \
# ---------- clear works ----------
&& rm -rf /var/cache/apk/* /tmp/* /usr/share/man \
&& echo -e "\033[42;37m Build Completed :).\033[0m\n"
WORKDIR /opt/www
COPY . /opt/www
RUN composer install -o
... ...
# Swoole Engine
![Swoole Engine Test](https://github.com/hyperf/engine/workflows/Swoole%20Engine%20Test/badge.svg)
```
composer require hyperf/engine
```
... ...
{
"name": "hyperf/engine",
"type": "library",
"license": "MIT",
"keywords": [
"php",
"hyperf"
],
"description": "",
"autoload": {
"psr-4": {
"Hyperf\\Engine\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"HyperfTest\\": "tests"
}
},
"require": {
"php": ">=7.4"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.0",
"hyperf/guzzle": "^2.2",
"phpstan/phpstan": "^1.0",
"phpunit/phpunit": "^9.4",
"swoole/ide-helper": "dev-master"
},
"suggest": {
"ext-swoole": ">=4.5"
},
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"optimize-autoloader": true,
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "1.2-dev"
}
},
"scripts": {
"test": "phpunit -c phpunit.xml --colors=always",
"analyse": "phpstan analyse --memory-limit 1024M -l 0 ./src",
"cs-fix": "php-cs-fixer fix $1"
}
}
... ...
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php"
backupGlobals="false"
backupStaticAttributes="false"
verbose="true"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuite name="Testsuite">
<directory>./tests/</directory>
</testsuite>
</phpunit>
\ No newline at end of file
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine;
use Hyperf\Engine\Contract\ChannelInterface;
use Hyperf\Engine\Exception\RuntimeException;
if (PHP_VERSION_ID > 80000 && SWOOLE_VERSION_ID >= 50000) {
class Channel extends \Swoole\Coroutine\Channel implements ChannelInterface
{
protected bool $closed = false;
public function push(mixed $data, float $timeout = -1): bool
{
return parent::push($data, $timeout);
}
public function pop(float $timeout = -1): mixed
{
return parent::pop($timeout);
}
public function getCapacity(): int
{
return $this->capacity;
}
public function getLength(): int
{
return $this->length();
}
public function isAvailable(): bool
{
return ! $this->isClosing();
}
public function close(): bool
{
$this->closed = true;
return parent::close();
}
public function hasProducers(): bool
{
throw new RuntimeException('Not supported.');
}
public function hasConsumers(): bool
{
throw new RuntimeException('Not supported.');
}
public function isReadable(): bool
{
throw new RuntimeException('Not supported.');
}
public function isWritable(): bool
{
throw new RuntimeException('Not supported.');
}
public function isClosing(): bool
{
return $this->closed || $this->errCode === SWOOLE_CHANNEL_CLOSED;
}
public function isTimeout(): bool
{
return ! $this->closed && $this->errCode === SWOOLE_CHANNEL_TIMEOUT;
}
}
} else {
class Channel extends \Swoole\Coroutine\Channel implements ChannelInterface
{
/**
* @var bool
*/
protected $closed = false;
public function getCapacity(): int
{
return $this->capacity;
}
public function getLength(): int
{
return $this->length();
}
public function isAvailable(): bool
{
return ! $this->isClosing();
}
public function close(): bool
{
$this->closed = true;
return parent::close();
}
public function hasProducers(): bool
{
throw new RuntimeException('Not supported.');
}
public function hasConsumers(): bool
{
throw new RuntimeException('Not supported.');
}
public function isReadable(): bool
{
throw new RuntimeException('Not supported.');
}
public function isWritable(): bool
{
throw new RuntimeException('Not supported.');
}
public function isClosing(): bool
{
return $this->closed || $this->errCode === SWOOLE_CHANNEL_CLOSED;
}
public function isTimeout(): bool
{
return ! $this->closed && $this->errCode === SWOOLE_CHANNEL_TIMEOUT;
}
}
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine;
use Swoole\Coroutine\Http\Server as HttpServer;
use Swoole\Coroutine\Server;
class Constant
{
public const ENGINE = 'Swoole';
public static function isCoroutineServer($server): bool
{
return $server instanceof Server || $server instanceof HttpServer;
}
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Contract;
if (PHP_VERSION_ID > 80000 && SWOOLE_VERSION_ID >= 50000) {
interface ChannelInterface
{
/**
* @param float|int $timeout [optional] = -1
*/
public function push(mixed $data, float $timeout = -1): bool;
/**
* @param float $timeout seconds [optional] = -1
* @return mixed when pop failed, return false
*/
public function pop(float $timeout = -1): mixed;
/**
* Swow: When the channel is closed, all the data in it will be destroyed.
* Swoole: When the channel is closed, the data in it can still be popped out, but push behavior will no longer succeed.
*/
public function close(): bool;
public function getCapacity(): int;
public function getLength(): int;
public function isAvailable(): bool;
public function hasProducers(): bool;
public function hasConsumers(): bool;
public function isEmpty(): bool;
public function isFull(): bool;
public function isReadable(): bool;
public function isWritable(): bool;
public function isClosing(): bool;
public function isTimeout(): bool;
}
} else {
interface ChannelInterface
{
/**
* @param mixed $data [required]
* @param float|int $timeout [optional] = -1
* @return bool
*/
public function push($data, $timeout = -1);
/**
* @param float $timeout seconds [optional] = -1
* @return mixed when pop failed, return false
*/
public function pop($timeout = -1);
/**
* Swow: When the channel is closed, all the data in it will be destroyed.
* Swoole: When the channel is closed, the data in it can still be popped out, but push behavior will no longer succeed.
* @return mixed
*/
public function close(): bool;
/**
* @return int
*/
public function getCapacity();
/**
* @return int
*/
public function getLength();
/**
* @return bool
*/
public function isAvailable();
/**
* @return bool
*/
public function hasProducers();
/**
* @return bool
*/
public function hasConsumers();
/**
* @return bool
*/
public function isEmpty();
/**
* @return bool
*/
public function isFull();
/**
* @return bool
*/
public function isReadable();
/**
* @return bool
*/
public function isWritable();
/**
* @return bool
*/
public function isClosing();
/**
* @return bool
*/
public function isTimeout();
}
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Contract;
use Hyperf\Engine\Exception\CoroutineDestroyedException;
use Hyperf\Engine\Exception\RunningInNonCoroutineException;
interface CoroutineInterface
{
/**
* @param callable $callable [required]
*/
public function __construct(callable $callable);
/**
* @param mixed ...$data
* @return $this
*/
public function execute(...$data);
/**
* @return int
*/
public function getId();
/**
* @param callable $callable [required]
* @param mixed ...$data
* @return $this
*/
public static function create(callable $callable, ...$data);
/**
* @return int returns coroutine id from current coroutine, -1 in non coroutine environment
*/
public static function id();
/**
* Returns the parent coroutine ID.
* Returns 0 when running in the top level coroutine.
* @throws RunningInNonCoroutineException when running in non-coroutine context
* @throws CoroutineDestroyedException when the coroutine has been destroyed
*/
public static function pid(?int $id = null);
/**
* Set config to coroutine.
*/
public static function set(array $config);
/**
* @param null|int $id coroutine id
* @return null|\ArrayObject
*/
public static function getContextFor(?int $id = null);
/**
* Execute callback when coroutine destruct.
*/
public static function defer(callable $callable);
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Contract\Http;
use Hyperf\Engine\Http\RawResponse;
interface ClientInterface
{
public function set(array $settings): bool;
/**
* @param string[][] $headers
*/
public function request(string $method = 'GET', string $path = '/', array $headers = [], string $contents = '', string $version = '1.1'): RawResponse;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Contract\WebSocket;
interface WebSocketInterface
{
public const ON_MESSAGE = 'message';
public const ON_CLOSE = 'close';
public function on(string $event, callable $callback): void;
public function start(): void;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine;
use Hyperf\Engine\Contract\CoroutineInterface;
use Hyperf\Engine\Exception\CoroutineDestroyedException;
use Hyperf\Engine\Exception\RunningInNonCoroutineException;
use Hyperf\Engine\Exception\RuntimeException;
use Swoole\Coroutine as SwooleCo;
class Coroutine implements CoroutineInterface
{
/**
* @var callable
*/
private $callable;
/**
* @var int
*/
private $id;
public function __construct(callable $callable)
{
$this->callable = $callable;
}
public static function create(callable $callable, ...$data)
{
$coroutine = new static($callable);
$coroutine->execute(...$data);
return $coroutine;
}
public function execute(...$data)
{
$this->id = SwooleCo::create($this->callable, ...$data);
return $this;
}
public function getId()
{
if (is_null($this->id)) {
throw new RuntimeException('Coroutine was not be executed.');
}
return $this->id;
}
public static function id()
{
return SwooleCo::getCid();
}
public static function pid(?int $id = null)
{
if ($id) {
$cid = SwooleCo::getPcid($id);
if ($cid === false) {
throw new CoroutineDestroyedException(sprintf('Coroutine #%d has been destroyed.', $id));
}
} else {
$cid = SwooleCo::getPcid();
}
if ($cid === false) {
throw new RunningInNonCoroutineException('Non-Coroutine environment don\'t has parent coroutine id.');
}
return max(0, $cid);
}
public static function set(array $config)
{
SwooleCo::set($config);
}
/**
* @return null|\ArrayObject
*/
public static function getContextFor(?int $id = null)
{
if ($id === null) {
return SwooleCo::getContext();
}
return SwooleCo::getContext($id);
}
public static function defer(callable $callable)
{
SwooleCo::defer($callable);
}
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Exception;
class CoroutineDestroyedException extends RuntimeException
{
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Exception;
class HttpClientException extends RuntimeException
{
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Exception;
class RunningInNonCoroutineException extends RuntimeException
{
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Exception;
class RuntimeException extends \RuntimeException
{
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine;
class Extension
{
public static function isLoaded(): bool
{
return extension_loaded('Swoole');
}
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Http;
use Hyperf\Engine\Contract\Http\ClientInterface;
use Hyperf\Engine\Exception\HttpClientException;
use Swoole\Coroutine\Http\Client as HttpClient;
class Client extends HttpClient implements ClientInterface
{
public function set(array $settings): bool
{
return parent::set($settings);
}
/**
* @param string[][] $headers
*/
public function request(string $method = 'GET', string $path = '/', array $headers = [], string $contents = '', string $version = '1.1'): RawResponse
{
$this->setMethod($method);
$this->setData($contents);
$this->setHeaders($this->encodeHeaders($headers));
$this->execute($path);
if ($this->errCode !== 0) {
throw new HttpClientException($this->errMsg, $this->errCode);
}
return new RawResponse(
$this->statusCode,
$this->decodeHeaders($this->headers ?? []),
$this->body,
$version
);
}
/**
* @param string[] $headers
* @return string[][]
*/
private function decodeHeaders(array $headers): array
{
$result = [];
foreach ($headers as $name => $header) {
// The key of header is lower case.
$result[$name][] = $header;
}
if ($this->set_cookie_headers) {
$result['set-cookie'] = $this->set_cookie_headers;
}
return $result;
}
/**
* Swoole engine not support two dimensional array.
* @param string[][] $headers
* @return string[]
*/
private function encodeHeaders(array $headers): array
{
$result = [];
foreach ($headers as $name => $value) {
$result[$name] = is_array($value) ? implode(',', $value) : $value;
}
return $result;
}
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Http;
use Swoole\Http\Response;
class FdGetter
{
public function get(Response $response): int
{
return $response->fd;
}
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Http;
final class RawResponse
{
/**
* @var int
*/
public $statusCode = 0;
/**
* @var string[][]
*/
public $headers = [];
/**
* @var string
*/
public $body = '';
/**
* Protocol version.
* @var string
*/
public $version = '';
/**
* @param string[][] $headers
*/
public function __construct(int $statusCode, array $headers, string $body, string $version)
{
$this->statusCode = $statusCode;
$this->headers = $headers;
$this->body = $body;
$this->version = $version;
}
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine;
class Socket extends \Swoole\Coroutine\Socket
{
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine;
class WaitGroup extends \Swoole\Coroutine\WaitGroup
{
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\WebSocket;
class Frame
{
public const PING = '27890027';
public const PONG = '278a0027';
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\WebSocket;
class Opcode
{
public const CONTINUATION = 0;
public const TEXT = 1;
public const BINARY = 2;
public const CLOSE = 8;
public const PING = 9;
public const PONG = 10;
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\WebSocket;
use Hyperf\Engine\Contract\WebSocket\WebSocketInterface;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\WebSocket\CloseFrame;
class WebSocket implements WebSocketInterface
{
/**
* @var Response
*/
protected $connection;
/**
* @var array<string, callable>
*/
protected $events = [];
public function __construct(Response $connection, Request $request)
{
$this->connection = $connection;
$this->connection->upgrade();
}
public function on(string $event, callable $callback): void
{
$this->events[$event] = $callback;
}
public function start(): void
{
while (true) {
$frame = $this->connection->recv();
if ($frame === false || $frame instanceof CloseFrame || $frame === '') {
$callback = $this->events[static::ON_CLOSE];
$callback($this->connection, $this->connection->fd);
break;
}
$callback = $this->events[static::ON_MESSAGE];
$callback($this->connection, $frame);
}
$this->connection = null;
$this->events = [];
}
}
... ...
/tests export-ignore
/.github export-ignore
... ...
The MIT License (MIT)
Copyright (c) Taylor Otwell
Copyright (c) Hyperf
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
... ...
{
"name": "hyperf/macroable",
"description": "Hyperf Macroable package which come from illuminate/macroable",
"license": "MIT",
"keywords": [
"php",
"swoole",
"hyperf",
"macroable"
],
"homepage": "https://hyperf.io",
"support": {
"docs": "https://hyperf.wiki",
"issues": "https://github.com/hyperf/hyperf/issues",
"pull-request": "https://github.com/hyperf/hyperf/pulls",
"source": "https://github.com/hyperf/hyperf"
},
"require": {
"php": ">=7.3"
},
"autoload": {
"psr-4": {
"Hyperf\\Macroable\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"HyperfTest\\Macroable\\": "tests/"
}
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
}
}
}
... ...
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Macroable;
use BadMethodCallException;
use Closure;
use ReflectionClass;
use ReflectionMethod;
/**
* This file come from illuminate/macroable,
* thanks Laravel Team provide such a useful class.
*/
trait Macroable
{
/**
* The registered string macros.
*
* @var array
*/
protected static $macros = [];
/**
* Dynamically handle calls to the class.
*
* @param string $method
* @param array $parameters
* @throws \BadMethodCallException
* @return mixed
*/
public static function __callStatic($method, $parameters)
{
if (! static::hasMacro($method)) {
throw new BadMethodCallException(sprintf(
'Method %s::%s does not exist.',
static::class,
$method
));
}
$macro = static::$macros[$method];
if ($macro instanceof Closure) {
$macro = $macro->bindTo(null, static::class);
}
return $macro(...$parameters);
}
/**
* Dynamically handle calls to the class.
*
* @param string $method
* @param array $parameters
* @throws \BadMethodCallException
* @return mixed
*/
public function __call($method, $parameters)
{
if (! static::hasMacro($method)) {
throw new BadMethodCallException(sprintf(
'Method %s::%s does not exist.',
static::class,
$method
));
}
$macro = static::$macros[$method];
if ($macro instanceof Closure) {
$macro = $macro->bindTo($this, static::class);
}
return $macro(...$parameters);
}
/**
* Register a custom macro.
*
* @param string $name
* @param callable|object $macro
*/
public static function macro($name, $macro)
{
static::$macros[$name] = $macro;
}
/**
* Mix another object into the class.
*
* @param object $mixin
* @param bool $replace
*
* @throws \ReflectionException
*/
public static function mixin($mixin, $replace = true)
{
$methods = (new ReflectionClass($mixin))->getMethods(
ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
);
foreach ($methods as $method) {
if ($replace || ! static::hasMacro($method->name)) {
$method->setAccessible(true);
static::macro($method->name, $method->invoke($mixin));
}
}
}
/**
* Checks if macro is registered.
*
* @param string $name
* @return bool
*/
public static function hasMacro($name)
{
return isset(static::$macros[$name]);
}
}
... ...
/tests export-ignore
/.github export-ignore
... ...
/vendor/
composer.lock
*.cache
*.log
\ No newline at end of file
... ...
<?php
$header = <<<'EOF'
This file is part of Hyperf.
@link https://www.hyperf.io
@document https://hyperf.wiki
@contact group@hyperf.io
@license https://github.com/hyperf/hyperf/blob/master/LICENSE
EOF;
return (new PhpCsFixer\Config())
->setRiskyAllowed(true)
->setRules([
'@PSR2' => true,
'@Symfony' => true,
'@DoctrineAnnotation' => true,
'@PhpCsFixer' => true,
'header_comment' => [
'comment_type' => 'PHPDoc',
'header' => $header,
'separate' => 'none',
'location' => 'after_declare_strict',
],
'array_syntax' => [
'syntax' => 'short'
],
'list_syntax' => [
'syntax' => 'short'
],
'concat_space' => [
'spacing' => 'one'
],
'blank_line_before_statement' => [
'statements' => [
'declare',
],
],
'general_phpdoc_annotation_remove' => [
'annotations' => [
'author'
],
],
'ordered_imports' => [
'imports_order' => [
'class', 'function', 'const',
],
'sort_algorithm' => 'alpha',
],
'single_line_comment_style' => [
'comment_types' => [
],
],
'yoda_style' => [
'always_move_variable' => false,
'equal' => false,
'identical' => false,
],
'phpdoc_align' => [
'align' => 'left',
],
'multiline_whitespace_before_semicolons' => [
'strategy' => 'no_multi_line',
],
'constant_case' => [
'case' => 'lower',
],
'class_attributes_separation' => true,
'combine_consecutive_unsets' => true,
'declare_strict_types' => true,
'linebreak_after_opening_tag' => true,
'lowercase_static_reference' => true,
'no_useless_else' => true,
'no_unused_imports' => true,
'not_operator_with_successor_space' => true,
'not_operator_with_space' => false,
'ordered_class_elements' => true,
'php_unit_strict' => false,
'phpdoc_separation' => false,
'single_quote' => true,
'standardize_not_equals' => true,
'multiline_comment_opening_closing' => true,
])
->setFinder(
PhpCsFixer\Finder::create()
->exclude('bin')
->exclude('public')
->exclude('runtime')
->exclude('vendor')
->in(__DIR__)
)
->setUsingCache(false);
... ...
<?php
namespace PHPSTORM_META {
// Reflect
override(\Psr\Container\ContainerInterface::get(0), map('@'));
}
\ No newline at end of file
... ...
language: php
sudo: required
matrix:
include:
- php: 7.2
env: SW_VERSION="4.4.17"
- php: 7.3
env: SW_VERSION="4.4.17"
- php: 7.4
env: SW_VERSION="4.4.17"
- php: master
env: SW_VERSION="4.4.17"
allow_failures:
- php: master
services:
- mysql
- redis-server
- docker
before_install:
- export PHP_MAJOR="$(`phpenv which php` -r 'echo phpversion();' | cut -d '.' -f 1)"
- export PHP_MINOR="$(`phpenv which php` -r 'echo phpversion();' | cut -d '.' -f 2)"
- echo $PHP_MAJOR
- echo $PHP_MINOR
install:
- cd $TRAVIS_BUILD_DIR
- bash ./tests/swoole.install.sh
- phpenv config-rm xdebug.ini || echo "xdebug not available"
- phpenv config-add ./tests/ci.ini
before_script:
- cd $TRAVIS_BUILD_DIR
- composer config -g process-timeout 900 && composer update
script:
- composer analyse
- composer test
... ...
# Pimple Container
[![Build Status](https://travis-ci.org/hyperf-cloud/pimple-integration.svg?branch=master)](https://travis-ci.org/hyperf-cloud/pimple-integration)
`hyperf/pimple` 是基于 `pimple/pimple` 实现的轻量级符合 `PSR11 规范` 的容器组件。可以减少其他框架使用 Hyperf 组件时的成本。
## 安装
```
composer require "hyperf/pimple:1.1.*"
```
## 使用
```php
<?php
use Hyperf\Pimple\ContainerFactory;
$container = (new ContainerFactory())();
```
### `EasySwoole` 接入 `hyperf/translation`
因为 `EasySwoole` 的容器组件暂时并没有实现 `PSR11` 规范,所以无法直接使用。
1. 首先引入相关组件
```
composer require "hyperf/translation:1.1.*"
composer require "hyperf/config:1.1.*"
```
2. 添加 国际化相关的 Provider
```php
<?php
declare(strict_types=1);
namespace App\Provider;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Contract\ContainerInterface;
use Hyperf\Contract\TranslatorLoaderInterface;
use Hyperf\Pimple\ProviderInterface;
use Hyperf\Translation\FileLoader;
use Hyperf\Utils\Filesystem\Filesystem;
class TranslatorLoaderProvider implements ProviderInterface
{
public function register(ContainerInterface $container)
{
$container->set(TranslatorLoaderInterface::class, function () use ($container) {
$config = $container->get(ConfigInterface::class);
$files = $container->get(Filesystem::class);
$path = $config->get('translation.path');
return make(FileLoader::class, compact('files', 'path'));
});
}
}
```
```php
<?php
declare(strict_types=1);
namespace App\Provider;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Contract\ContainerInterface;
use Hyperf\Contract\TranslatorInterface;
use Hyperf\Contract\TranslatorLoaderInterface;
use Hyperf\Pimple\ProviderInterface;
use Hyperf\Translation\Translator;
class TranslatorProvider implements ProviderInterface
{
public function register(ContainerInterface $container)
{
$container->set(TranslatorInterface::class, function () use ($container) {
$config = $container->get(ConfigInterface::class);
$locale = $config->get('translation.locale');
$fallbackLocale = $config->get('translation.fallback_locale');
$loader = $container->get(TranslatorLoaderInterface::class);
$translator = make(Translator::class, compact('loader', 'locale'));
$translator->setFallback((string) $fallbackLocale);
return $translator;
});
}
}
```
3. `EasySwoole` 事件注册器在 `EasySwooleEvent.php` 中,所以我们需要在 `initialize()` 中初始化我们的容器和国际化组件。
> 以下 Config 组件,可以自行封装,这里方便起见直接配置。
```php
<?php
declare(strict_types=1);
namespace EasySwoole\EasySwoole;
use EasySwoole\EasySwoole\AbstractInterface\Event;
use EasySwoole\EasySwoole\Swoole\EventRegister;
use EasySwoole\Http\Request;
use EasySwoole\Http\Response;
use Hyperf\Config\Config;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Pimple\ContainerFactory;
use App\Provider\TranslatorProvider;
use App\Provider\TranslatorLoaderProvider;
class EasySwooleEvent implements Event
{
public static function initialize()
{
date_default_timezone_set('Asia/Shanghai');
$container = (new ContainerFactory([
TranslatorProvider::class,
TranslatorLoaderProvider::class,
]))();
$container->set(ConfigInterface::class, new Config([
'translation' => [
'locale' => 'zh_CN',
'fallback_locale' => 'en',
'path' => EASYSWOOLE_ROOT . '/storage/languages',
],
]));
}
}
```
4. 修改控制器,使用国际化组件
```php
<?php
declare(strict_types=1);
namespace App\HttpController;
use EasySwoole\Http\AbstractInterface\Controller;
use Hyperf\Contract\TranslatorInterface;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Utils\Codec\Json;
class Index extends Controller
{
public function index()
{
$container = ApplicationContext::getContainer();
$translator = $container->get(TranslatorInterface::class);
$data = [
'message' => $translator->trans('message.hello', ['name' => 'Hyperf']),
];
$this->response()->write(Json::encode($data));
}
}
```
5. 添加国际化配置
```php
// storage/languages/en/message.php
return [
'hello' => 'Hello :name',
];
// storage/languages/zh_CN/message.php
return [
'hello' => '你好 :name',
];
```
6. 测试
```
$ curl http://127.0.0.1:9501/
{"message":"你好 Hyperf"}
```
... ...