Store the customized Shopro mall so it can be kept on the remote.
Co-authored-by: Cursor <cursoragent@cursor.com>
这个提交包含在:
14
.bowerrc
普通文件
14
.bowerrc
普通文件
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"directory": "public/assets/libs",
|
||||
"ignoredDependencies": [
|
||||
"es6-promise",
|
||||
"file-saver",
|
||||
"html2canvas",
|
||||
"jspdf",
|
||||
"jspdf-autotable",
|
||||
"pdfmake"
|
||||
],
|
||||
"scripts":{
|
||||
"postinstall": "node bower-cleanup.js"
|
||||
}
|
||||
}
|
||||
11
.env.sample
普通文件
11
.env.sample
普通文件
@@ -0,0 +1,11 @@
|
||||
[app]
|
||||
debug = false
|
||||
trace = false
|
||||
|
||||
[database]
|
||||
hostname = 127.0.0.1
|
||||
database = fastadmin
|
||||
username = root
|
||||
password = root
|
||||
hostport = 3306
|
||||
prefix = fa_
|
||||
18
.gitignore
vendored
普通文件
18
.gitignore
vendored
普通文件
@@ -0,0 +1,18 @@
|
||||
/nbproject/
|
||||
/thinkphp/
|
||||
/vendor/
|
||||
/runtime/*
|
||||
/public/assets/libs/
|
||||
/public/uploads/*
|
||||
sql.sql
|
||||
.DS_Store
|
||||
.idea
|
||||
composer.lock
|
||||
*.log
|
||||
*.css.map
|
||||
!.gitkeep
|
||||
.env
|
||||
.svn
|
||||
.vscode
|
||||
node_modules
|
||||
.user.ini
|
||||
4
.npmrc
普通文件
4
.npmrc
普通文件
@@ -0,0 +1,4 @@
|
||||
# 使用自定义镜像源
|
||||
registry=http://mirrors.tencent.com/npm/
|
||||
#关闭SSL验证
|
||||
strict-ssl=false
|
||||
@@ -0,0 +1 @@
|
||||
ui9gocxa2r8uqhi6q1m88rg4u6msrdhm.01cS3ljZfUKOsBdQBh7oqc3sOuTMvsSfwkVmnYf7VfA
|
||||
7
404.html
普通文件
7
404.html
普通文件
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<head><title>404 Not Found</title></head>
|
||||
<body>
|
||||
<center><h1>404 Not Found</h1></center>
|
||||
<hr><center>nginx</center>
|
||||
</body>
|
||||
</html>
|
||||
139
Gruntfile.js
普通文件
139
Gruntfile.js
普通文件
@@ -0,0 +1,139 @@
|
||||
module.exports = function (grunt) {
|
||||
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
copy: {
|
||||
main: {
|
||||
files: []
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var build = function (module, type, callback) {
|
||||
var config = {
|
||||
compile: {
|
||||
options: type === 'js' ? {
|
||||
optimizeCss: "standard",
|
||||
optimize: "uglify", //可使用uglify|closure|none
|
||||
preserveLicenseComments: true,
|
||||
removeCombined: false,
|
||||
baseUrl: "./public/assets/js/", //JS文件所在的基础目录
|
||||
name: "require-" + module, //来源文件,不包含后缀
|
||||
out: "./public/assets/js/require-" + module + ".min.js" //目标文件
|
||||
} : {
|
||||
optimizeCss: "default",
|
||||
optimize: "uglify", //可使用uglify|closure|none
|
||||
cssIn: "./public/assets/css/" + module + ".css", //CSS文件所在的基础目录
|
||||
out: "./public/assets/css/" + module + ".min.css" //目标文件
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var content = grunt.file.read("./public/assets/js/require-" + module + ".js"),
|
||||
pattern = /^require\.config\(\{[\r\n]?[\n]?(.*?)[\r\n]?[\n]?}\);/is;
|
||||
|
||||
var matches = content.match(pattern);
|
||||
if (matches) {
|
||||
if (type === 'js') {
|
||||
var data = matches[1].replaceAll(/(urlArgs|baseUrl):(.*)\n/gi, '');
|
||||
const parse = require('parse-config-file'), fs = require('fs');
|
||||
require('jsonminify');
|
||||
|
||||
data = JSON.minify("{\n" + data + "\n}");
|
||||
let options = parse(data);
|
||||
options.paths.tableexport = "empty:";
|
||||
Object.assign(config.compile.options, options);
|
||||
}
|
||||
let requirejs = require("./application/admin/command/Min/r");
|
||||
|
||||
try {
|
||||
requirejs.optimize(config.compile.options, function (buildResponse) {
|
||||
// var contents = require('fs').readFileSync(config.compile.options.out, 'utf8');
|
||||
callback();
|
||||
}, function (err) {
|
||||
console.error(err);
|
||||
callback();
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 加载 "copy" 插件
|
||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||
|
||||
grunt.registerTask('frontend:js', 'build frontend js', function () {
|
||||
var done = this.async();
|
||||
build('frontend', 'js', done);
|
||||
});
|
||||
|
||||
grunt.registerTask('backend:js', 'build backend js', function () {
|
||||
var done = this.async();
|
||||
build('backend', 'js', done);
|
||||
});
|
||||
|
||||
grunt.registerTask('frontend:css', 'build frontend css', function () {
|
||||
var done = this.async();
|
||||
build('frontend', 'css', done);
|
||||
});
|
||||
|
||||
grunt.registerTask('backend:css', 'build frontend css', function () {
|
||||
var done = this.async();
|
||||
build('backend', 'css', done);
|
||||
});
|
||||
|
||||
// 注册部署JS和CSS任务
|
||||
grunt.registerTask('deploy', 'deploy', function () {
|
||||
const fs = require('fs');
|
||||
const path = require("path")
|
||||
const nodeModulesDir = path.resolve(__dirname, "./node_modules");
|
||||
|
||||
const getAllFiles = function (dirPath, arrayOfFiles) {
|
||||
files = fs.readdirSync(dirPath)
|
||||
|
||||
arrayOfFiles = arrayOfFiles || []
|
||||
|
||||
files.forEach(function (file) {
|
||||
if (fs.statSync(dirPath + "/" + file).isDirectory()) {
|
||||
arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles)
|
||||
} else {
|
||||
arrayOfFiles.push(path.join(__dirname, dirPath, "/", file))
|
||||
}
|
||||
});
|
||||
|
||||
return arrayOfFiles
|
||||
};
|
||||
const mainPackage = grunt.config.get('pkg');
|
||||
let dists = mainPackage.dists || [];
|
||||
let files = [];
|
||||
|
||||
// 兼容旧版本bower使用的目录
|
||||
let specialKey = {
|
||||
'fastadmin-bootstraptable': 'bootstrap-table',
|
||||
'sortablejs': 'Sortable',
|
||||
'tableexport.jquery.plugin': 'tableExport.jquery.plugin',
|
||||
};
|
||||
Object.keys(dists).forEach(key => {
|
||||
let src = ["**/*LICENSE*", "**/*license*"];
|
||||
src = src.concat(Array.isArray(dists[key]) ? dists[key] : [dists[key]]);
|
||||
files.push({expand: true, cwd: nodeModulesDir + "/" + key, src: src, dest: 'public/assets/libs/' + (specialKey[key] || key) + "/"});
|
||||
});
|
||||
|
||||
// 兼容bower历史路径文件
|
||||
files = [...files,
|
||||
{src: nodeModulesDir + "/toastr/build/toastr.min.css", dest: "public/assets/libs/toastr/toastr.min.css"},
|
||||
{src: nodeModulesDir + "/bootstrap-slider/dist/css/bootstrap-slider.css", dest: "public/assets/libs/bootstrap-slider/slider.css"},
|
||||
{expand: true, cwd: nodeModulesDir + "/bootstrap-slider/dist", src: ["*.js"], dest: "public/assets/libs/bootstrap-slider/"}
|
||||
]
|
||||
|
||||
grunt.config.set('copy.main.files', files);
|
||||
grunt.task.run("copy:main");
|
||||
});
|
||||
|
||||
// 注册默认任务
|
||||
grunt.registerTask('default', ['deploy', 'frontend:js', 'backend:js', 'frontend:css', 'backend:css']);
|
||||
|
||||
};
|
||||
191
LICENSE
普通文件
191
LICENSE
普通文件
@@ -0,0 +1,191 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "{}" replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same "printed page" as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright 2017 Karson
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
93
README.md
普通文件
93
README.md
普通文件
@@ -0,0 +1,93 @@
|
||||
FastAdmin是一款基于ThinkPHP+Bootstrap的极速后台开发框架。
|
||||
|
||||
|
||||
## 主要特性
|
||||
|
||||
* 基于`Auth`验证的权限管理系统
|
||||
* 支持无限级父子级权限继承,父级的管理员可任意增删改子级管理员及权限设置
|
||||
* 支持单管理员多角色
|
||||
* 支持管理子级数据或个人数据
|
||||
* 强大的一键生成功能
|
||||
* 一键生成CRUD,包括控制器、模型、视图、JS、语言包、菜单、回收站等
|
||||
* 一键压缩打包JS和CSS文件,一键CDN静态资源部署
|
||||
* 一键生成控制器菜单和规则
|
||||
* 一键生成API接口文档
|
||||
* 完善的前端功能组件开发
|
||||
* 基于`AdminLTE`二次开发
|
||||
* 基于`Bootstrap`开发,自适应手机、平板、PC
|
||||
* 基于`RequireJS`进行JS模块管理,按需加载
|
||||
* 基于`Less`进行样式开发
|
||||
* 强大的插件扩展功能,在线安装卸载升级插件
|
||||
* 通用的会员模块和API模块
|
||||
* 共用同一账号体系的Web端会员中心权限验证和API接口会员权限验证
|
||||
* 二级域名部署支持,同时域名支持绑定到应用插件
|
||||
* 多语言支持,服务端及客户端支持
|
||||
* 支持大文件分片上传、剪切板粘贴上传、拖拽上传,进度条显示,图片上传前压缩
|
||||
* 支持表格固定列、固定表头、跨页选择、Excel导出、模板渲染等功能
|
||||
* 强大的第三方应用模块支持([CMS](https://www.fastadmin.net/store/cms.html)、[CRM](https://www.fastadmin.net/store/facrm.html)、[企业网站管理系统](https://www.fastadmin.net/store/ldcms.html)、[知识库文档系统](https://www.fastadmin.net/store/knowbase.html)、[在线投票系统](https://www.fastadmin.net/store/vote.html)、[B2C商城](https://www.fastadmin.net/store/shopro.html)、[B2B2C商城](https://www.fastadmin.net/store/wanlshop.html))
|
||||
* 整合第三方短信接口(阿里云、腾讯云短信)
|
||||
* 无缝整合第三方云存储(七牛云、阿里云OSS、腾讯云存储、又拍云)功能,支持云储存分片上传
|
||||
* 第三方富文本编辑器支持(Summernote、百度编辑器)
|
||||
* 第三方登录(QQ、微信、微博)整合
|
||||
* 第三方支付(微信、支付宝)无缝整合,微信支持PC端扫码支付
|
||||
* 丰富的插件应用市场
|
||||
|
||||
## 安装使用
|
||||
|
||||
https://doc.fastadmin.net
|
||||
|
||||
## 在线演示
|
||||
|
||||
https://demo.fastadmin.net
|
||||
|
||||
用户名:admin
|
||||
|
||||
密 码:123456
|
||||
|
||||
提 示:演示站数据无法进行修改,请下载源码安装体验全部功能
|
||||
|
||||
## 界面截图
|
||||

|
||||
|
||||
## 问题反馈
|
||||
|
||||
在使用中有任何问题,请使用以下联系方式联系我们
|
||||
|
||||
问答社区: https://ask.fastadmin.net
|
||||
|
||||
Github: https://github.com/fastadminnet/fastadmin
|
||||
|
||||
Gitee: https://gitee.com/fastadminnet/fastadmin
|
||||
|
||||
## 特别鸣谢
|
||||
|
||||
感谢以下的项目,排名不分先后
|
||||
|
||||
ThinkPHP:http://www.thinkphp.cn
|
||||
|
||||
AdminLTE:https://adminlte.io
|
||||
|
||||
Bootstrap:http://getbootstrap.com
|
||||
|
||||
jQuery:http://jquery.com
|
||||
|
||||
Bootstrap-table:https://github.com/wenzhixin/bootstrap-table
|
||||
|
||||
Nice-validator: https://validator.niceue.com
|
||||
|
||||
SelectPage: https://github.com/TerryZ/SelectPage
|
||||
|
||||
Layer: https://layuion.com/layer/
|
||||
|
||||
DropzoneJS: https://www.dropzonejs.com
|
||||
|
||||
|
||||
## 版权信息
|
||||
|
||||
FastAdmin遵循Apache2开源协议发布,并提供免费使用。
|
||||
|
||||
本项目包含的第三方源码和二进制文件之版权信息另行标注。
|
||||
|
||||
版权所有Copyright © 2017-2024 by FastAdmin (https://www.fastadmin.net)
|
||||
|
||||
All rights reserved。
|
||||
1
addons/.gitkeep
普通文件
1
addons/.gitkeep
普通文件
@@ -0,0 +1 @@
|
||||
|
||||
1
addons/.htaccess
普通文件
1
addons/.htaccess
普通文件
@@ -0,0 +1 @@
|
||||
deny from all
|
||||
1
addons/epay/.addonrc
普通文件
1
addons/epay/.addonrc
普通文件
@@ -0,0 +1 @@
|
||||
{"files":["application\/admin\/controller\/Epay.php","public\/assets\/addons\/epay\/js\/common.js","public\/assets\/addons\/epay\/js\/jquery.qrcode.min.js","public\/assets\/addons\/epay\/less\/common.less","public\/assets\/addons\/epay\/images\/alipay.png","public\/assets\/addons\/epay\/images\/scan.png","public\/assets\/addons\/epay\/images\/screenshot-wechat.png","public\/assets\/addons\/epay\/images\/paid.png","public\/assets\/addons\/epay\/images\/screenshot-alipay.png","public\/assets\/addons\/epay\/images\/logo-alipay.png","public\/assets\/addons\/epay\/images\/expired.png","public\/assets\/addons\/epay\/images\/wechat.png","public\/assets\/addons\/epay\/images\/logo-wechat.png","public\/assets\/addons\/epay\/css\/common.css"],"license":"regular","licenseto":"26425","licensekey":"UVWY9d6PbrOtmDZg mHyhYpqqrgq9YyRcqDmfrQ==","domains":["14996.com"],"licensecodes":[],"validations":["82dcffa78fca7d832197ff1e1d536e2a"]}
|
||||
112
addons/epay/Epay.php
普通文件
112
addons/epay/Epay.php
普通文件
@@ -0,0 +1,112 @@
|
||||
<?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;
|
||||
|
||||
if (PHP_VERSION_ID >= 80000 && $version == Service::SDK_VERSION_V3) {
|
||||
$yansongdaDir = $libraryDir . $version . 'new' . DS . 'Yansongda' . DS;
|
||||
|
||||
// PHP 8.0+ 版本且为V3接口,需要加载 Artful 的命名空间
|
||||
Loader::addNamespace('Yansongda\Artful', $yansongdaDir . 'Artful' . DS);
|
||||
require_once $yansongdaDir . 'Artful' . DS . 'Functions.php';
|
||||
} else {
|
||||
$yansongdaDir = $libraryDir . $version . DS . 'Yansongda' . DS;
|
||||
}
|
||||
|
||||
// 注册Pay命名空间
|
||||
Loader::addNamespace('Yansongda\Pay', $yansongdaDir . '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', $yansongdaDir . 'Supports' . DS);
|
||||
}
|
||||
|
||||
// V3需载入辅助函数
|
||||
if ($version == Service::SDK_VERSION_V3) {
|
||||
require_once $yansongdaDir . 'Pay' . DS . 'Functions.php';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
529
addons/epay/config.html
普通文件
529
addons/epay/config.html
普通文件
@@ -0,0 +1,529 @@
|
||||
<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|htmlentities}" 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=''|htmlentities}" class="form-control" data-rule="appid" 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=''|htmlentities}" class="form-control" data-rule="appid" 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=''|htmlentities}" class="form-control" data-rule="keysecret" 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=''|htmlentities}" class="form-control" data-rule="appid" 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="number" name="row[wechat][mch_id]" value="{$item.value.mch_id|default=''|htmlentities}" class="form-control" data-rule="mchid" data-tip=""/>
|
||||
</div>
|
||||
<div class="col-sm-4"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>商户APIv2密钥</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=''|htmlentities}" class="form-control" data-rule="keysecret" data-tip="仅用于微信支付V2接口"/>
|
||||
</div>
|
||||
<div class="col-sm-4"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>V3配置</td>
|
||||
<td>
|
||||
<table class="table table-config mb-0">
|
||||
<tbody>
|
||||
<!-- V3 特有字段 -->
|
||||
<tr>
|
||||
<td>商户APIv3密钥</td>
|
||||
<td>
|
||||
<div class="row">
|
||||
<div class="col-sm-7 col-xs-12">
|
||||
<input type="text" name="row[wechat][key_v3]" value="{$item.value.key_v3|default=''|htmlentities}" class="form-control" data-rule="keysecret" data-tip=""/>
|
||||
</div>
|
||||
<div class="col-sm-4"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>公钥ID</td>
|
||||
<td>
|
||||
<div class="row">
|
||||
<div class="col-sm-7 col-xs-12">
|
||||
<input type="text" name="row[wechat][public_key_id]" value="{$item.value.public_key_id|default=''|htmlentities}" class="form-control" data-rule="public_key_id" data-tip=""/>
|
||||
</div>
|
||||
<div class="col-sm-4"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>公钥证书路径</td>
|
||||
<td>
|
||||
<div class="row">
|
||||
<div class="col-sm-7 col-xs-12">
|
||||
<div class="input-group">
|
||||
<input id="c-public_key" class="form-control" size="50" name="row[wechat][public_key]" type="text" value="{$item.value.public_key|default=''|htmlentities}" data-tip="可选, 仅在退款、红包等情况时需要用到">
|
||||
<div class="input-group-addon no-border no-padding">
|
||||
<span><button type="button" id="faupload-public_key" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"public_key"}' data-mimetype="pem" data-input-id="c-public_key" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
|
||||
</div>
|
||||
<span class="msg-box n-right" for="c-public_key"></span>
|
||||
</div>
|
||||
<div style="margin-top:5px;"><a href="https://pay.weixin.qq.com/doc/v3/partner/4012925323" target="_blank"><i class="fa fa-question-circle"></i> 如何获取微信支付公钥证书?</a></div>
|
||||
</div>
|
||||
<div class="col-sm-4"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</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://kf.qq.com/faq/161222NneAJf161222U7fARv.html" 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://kf.qq.com/faq/161222NneAJf161222U7fARv.html" 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][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=''|htmlentities}" class="form-control" data-rule="mchid" 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=''|htmlentities}" class="form-control" data-rule="appid" 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=''|htmlentities}" class="form-control" data-rule="appid" 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=''|htmlentities}" class="form-control" data-rule="appid" 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=''|htmlentities}" 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][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="number" name="row[alipay][pid]" value="{$item.value.pid|default=''|htmlentities}" class="form-control" data-rule="pid" 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="number" name="row[alipay][app_id]" value="{$item.value.app_id|default=''|htmlentities}" class="form-control" data-rule="pid" 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=''|htmlentities}" 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=''|htmlentities}" class="form-control" data-rule="" data-tip="请勿随意修改,实际以逻辑代码中请求的为准"/>
|
||||
</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:5px 0;" class="text-muted">
|
||||
<i class="fa fa-info-circle"></i> 如果要使用转账、提现功能,则必须使用公钥证书
|
||||
</div>
|
||||
<div data-signtype="publickey" class="{if ($item.value.signtype??'')==='cert'}hidden{/if}">
|
||||
<a href="https://opensupport.alipay.com/support/FAQ/65b9c843a8e10e054512d07dprod" target="_blank"><i class="fa fa-info-circle"></i> 如何生成支付宝公钥、应用私钥?</a>
|
||||
</div>
|
||||
<div data-signtype="cert" class="{if ($item.value.signtype??'')==='publickey'}hidden{/if}">
|
||||
<a href="https://opensupport.alipay.com/support/FAQ/6718ab4563fae8044fe13dc7prod" target="_blank"><i class="fa fa-info-circle"></i> 支付宝公钥证书、应用公钥证书、支付宝根证书?</a>
|
||||
</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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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=''|htmlentities}" class="form-control" data-rule=""/>
|
||||
<div style="margin-top:5px;">
|
||||
<span data-signtype="publickey" class="{if ($item.value.signtype??'')==='cert'}hidden{/if}">
|
||||
<a href="https://opensupport.alipay.com/support/FAQ/671860f40f6363044540b717prod" target="_blank"><i class="fa fa-question-circle"></i> 如何获取应用私钥?</a>
|
||||
</span>
|
||||
<span data-signtype="cert" class="{if ($item.value.signtype??'')==='publickey' || ($item.value.signtype??'')==''}hidden{/if}">
|
||||
<a href="https://opensupport.alipay.com/support/FAQ/6718ab4563fae8044fe13dc7prod" target="_blank"><i class="fa fa-question-circle"></i> 如何获取应用私钥?</a>
|
||||
</span>
|
||||
</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 () {
|
||||
$.validator.config({
|
||||
rules: {
|
||||
appid: function (element) {
|
||||
if (!element.value.toString().match(/^wx[a-zA-Z0-9]{16}$/)) {
|
||||
return __('必须以wx开头,且长度为18位');
|
||||
}
|
||||
},
|
||||
keysecret: function (element) {
|
||||
if (!element.value.toString().match(/^[a-zA-Z0-9]{32}$/)) {
|
||||
return __('长度为32位,且为字母数字');
|
||||
}
|
||||
},
|
||||
public_key_id: function (element) {
|
||||
if (!element.value.toString().match(/^PUB_KEY_ID_[0-9]{34}$/)) {
|
||||
return __('必须以PUB_KEY_ID_开头,长度为45位');
|
||||
}
|
||||
},
|
||||
mchid: function (element) {
|
||||
if (!element.value.toString().match(/^[0-9]{8,32}$/)) {
|
||||
return __('长度为至少8位,且为数字');
|
||||
}
|
||||
},
|
||||
pid: function (element) {
|
||||
if (!element.value.toString().match(/^[0-9]{16}$/)) {
|
||||
return __('长度为16位,且为数字');
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
$(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 () {
|
||||
let value = $(this).val();
|
||||
$("#alipay [data-signtype]").addClass("hidden");
|
||||
$("#alipay [data-signtype='" + value + "']").removeClass("hidden");
|
||||
});
|
||||
|
||||
Form.api.bindevent($("form[role=form]"), undefined, undefined, function () {
|
||||
let value = $("input[name='row[alipay][signtype]']:checked").val();
|
||||
// 如果选择了普通公钥,则需要清空未使用的应用公钥证书路径值和支付宝根证书路径值
|
||||
if (value === 'publickey') {
|
||||
$("#c-app_cert_public_key,#c-alipay_root_cert").val('');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
return Controller;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
72
addons/epay/config.php
普通文件
72
addons/epay/config.php
普通文件
@@ -0,0 +1,72 @@
|
||||
<?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' => '',
|
||||
'app_secret' => '',
|
||||
'miniapp_id' => '',
|
||||
'mch_id' => '',
|
||||
'key' => '',
|
||||
'key_v3' => '',
|
||||
'public_key_id' => '',
|
||||
'public_key' => '/addons/epay/certs/public_key.pem',
|
||||
'cert_client' => '/addons/epay/certs/apiclient_cert.pem',
|
||||
'cert_key' => '/addons/epay/certs/apiclient_key.pem',
|
||||
'mode' => 'normal',
|
||||
'sub_mch_id' => '',
|
||||
'sub_appid' => '',
|
||||
'sub_app_id' => '',
|
||||
'sub_miniapp_id' => '',
|
||||
'notify_url' => '',
|
||||
'log' => '1',
|
||||
],
|
||||
'rule' => 'required',
|
||||
'msg' => '',
|
||||
'tip' => '微信参数配置',
|
||||
'ok' => '',
|
||||
'extend' => '',
|
||||
],
|
||||
[
|
||||
'name' => 'alipay',
|
||||
'title' => '支付宝',
|
||||
'type' => 'array',
|
||||
'content' => [],
|
||||
'value' => [
|
||||
'app_id' => '',
|
||||
'mode' => 'normal',
|
||||
'notify_url' => '/addons/epay/api/notifyx/type/alipay',
|
||||
'return_url' => '/addons/epay/api/returnx/type/alipay',
|
||||
'private_key' => '',
|
||||
'signtype' => 'cert',
|
||||
'pid' => '',
|
||||
'ali_public_key' => '',
|
||||
'app_cert_public_key' => '',
|
||||
'alipay_root_cert' => '',
|
||||
'log' => '1',
|
||||
'scanpay' => '0',
|
||||
],
|
||||
'rule' => 'required',
|
||||
'msg' => '',
|
||||
'tip' => '支付宝参数配置',
|
||||
'ok' => '',
|
||||
'extend' => '',
|
||||
]
|
||||
];
|
||||
234
addons/epay/controller/Api.php
普通文件
234
addons/epay/controller/Api.php
普通文件
@@ -0,0 +1,234 @@
|
||||
<?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"));
|
||||
}
|
||||
|
||||
}
|
||||
131
addons/epay/controller/Index.php
普通文件
131
addons/epay/controller/Index.php
普通文件
@@ -0,0 +1,131 @@
|
||||
<?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"));
|
||||
}
|
||||
}
|
||||
10
addons/epay/info.ini
普通文件
10
addons/epay/info.ini
普通文件
@@ -0,0 +1,10 @@
|
||||
name = epay
|
||||
title = 微信支付宝整合
|
||||
intro = 可用于快速整合微信支付、支付宝支付功能
|
||||
author = FastAdmin
|
||||
website =
|
||||
version = 1.3.15
|
||||
state = 1
|
||||
url = /addons/epay
|
||||
license = regular
|
||||
licenseto = 26425
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace addons\epay\library;
|
||||
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse as BaseRedirectResponse;
|
||||
use JsonSerializable;
|
||||
|
||||
class RedirectResponse extends BaseRedirectResponse implements JsonSerializable
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->getContent();
|
||||
}
|
||||
|
||||
// 使用 PHP 8 兼容的新序列化方式
|
||||
public function __serialize(): array
|
||||
{
|
||||
return ['content' => $this->content];
|
||||
}
|
||||
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
$this->content = $data['content'] ?? '';
|
||||
}
|
||||
}
|
||||
46
addons/epay/library/Response.php
普通文件
46
addons/epay/library/Response.php
普通文件
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace addons\epay\library;
|
||||
|
||||
class Response extends \Symfony\Component\HttpFoundation\Response implements \JsonSerializable, \Serializable
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getContent();
|
||||
}
|
||||
|
||||
public function jsonSerialize(): mixed
|
||||
{
|
||||
return $this->getContent();
|
||||
}
|
||||
|
||||
public function serialize()
|
||||
{
|
||||
return serialize($this->content);
|
||||
}
|
||||
|
||||
public function unserialize($data)
|
||||
{
|
||||
return $this->content = unserialize($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* (PHP 8.1+) Magic method for serialization.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __serialize()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* (PHP 8.1+) Magic method for unserialization.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
$this->content = $data;
|
||||
}
|
||||
}
|
||||
494
addons/epay/library/Service.php
普通文件
494
addons/epay/library/Service.php
普通文件
@@ -0,0 +1,494 @@
|
||||
<?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 array $config 配置
|
||||
* @param string $field 字段
|
||||
* @return void
|
||||
*/
|
||||
private static function processAddonsPath(&$config, $field)
|
||||
{
|
||||
if (isset($config[$field]) && substr($config[$field], 0, 8) == '/addons/') {
|
||||
$config[$field] = ROOT_PATH . str_replace('/', DS, substr($config[$field], 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置
|
||||
* @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 ($type === 'wechat') {
|
||||
$certFields = ['cert_client', 'cert_key', 'public_key'];
|
||||
foreach ($certFields as $field) {
|
||||
self::processAddonsPath($config, $field);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理支付宝证书路径
|
||||
if ($type === 'alipay') {
|
||||
$config['signtype'] = $config['signtype'] ?? 'publickey';
|
||||
if ($config['signtype'] == 'cert') {
|
||||
$certFields = ['app_cert_public_key', 'alipay_root_cert', 'ali_public_key'];
|
||||
foreach ($certFields as $field) {
|
||||
self::processAddonsPath($config, $field);
|
||||
}
|
||||
} else {
|
||||
// 如果是普通公钥需要将app_cert_public_key和alipay_root_cert设置为空,不然会导致错误
|
||||
$config['app_cert_public_key'] = '';
|
||||
$config['alipay_root_cert'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
// 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'];
|
||||
|
||||
// 配置微信支付公钥ID及证书路径
|
||||
$config['wechat_public_cert_path'] = [
|
||||
($config['public_key_id'] ?? '') => ($config['public_key'] ?? '')
|
||||
];
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
110
addons/epay/library/Wechat.php
普通文件
110
addons/epay/library/Wechat.php
普通文件
@@ -0,0 +1,110 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
2
addons/epay/library/hyperf/context/.gitattributes
vendored
普通文件
2
addons/epay/library/hyperf/context/.gitattributes
vendored
普通文件
@@ -0,0 +1,2 @@
|
||||
/tests export-ignore
|
||||
/.github export-ignore
|
||||
@@ -0,0 +1,21 @@
|
||||
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.
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
2
addons/epay/library/hyperf/contract/.gitattributes
vendored
普通文件
2
addons/epay/library/hyperf/contract/.gitattributes
vendored
普通文件
@@ -0,0 +1,2 @@
|
||||
/tests export-ignore
|
||||
/.github export-ignore
|
||||
@@ -0,0 +1,21 @@
|
||||
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.
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?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);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?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);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?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);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?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();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?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);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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);
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?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();
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?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;
|
||||
}
|
||||
4
addons/epay/library/hyperf/engine/.gitattributes
vendored
普通文件
4
addons/epay/library/hyperf/engine/.gitattributes
vendored
普通文件
@@ -0,0 +1,4 @@
|
||||
/.github export-ignore
|
||||
/examples export-ignore
|
||||
/tests export-ignore
|
||||
|
||||
4
addons/epay/library/hyperf/engine/.gitignore
vendored
普通文件
4
addons/epay/library/hyperf/engine/.gitignore
vendored
普通文件
@@ -0,0 +1,4 @@
|
||||
/vendor/
|
||||
composer.lock
|
||||
*.cache
|
||||
*.log
|
||||
@@ -0,0 +1,89 @@
|
||||
<?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);
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSTORM_META {
|
||||
// Reflect
|
||||
override(\Psr\Container\ContainerInterface::get(0), map('@'));
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
# 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
|
||||
@@ -0,0 +1,7 @@
|
||||
# Swoole Engine
|
||||
|
||||

|
||||
|
||||
```
|
||||
composer require hyperf/engine
|
||||
```
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,142 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?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';
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?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 = [];
|
||||
}
|
||||
}
|
||||
某些文件未显示,因为此 diff 中更改的文件太多 显示更多
在新工单中引用
屏蔽一个用户