1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-07-10 10:40:03 +08:00
* !6 develop->master 1.1.0
* 更新版本号
* 完善后台今日统计,增加权限白名单,增加后台首页菜单,调整后台登录页样式
* Merge branch 'koogua/I1XFCF' of https://gitee.com/koogua/course-tencen…
* 前台学习资料部分完成
* !2 后台运营统计合并
* 后台学习资料部分完成
* Merge branch 'master' into develop
* Merge branch 'master' of https://github.com/xiaochong0302/course-tencent-cloud
* 1.增加changelog.md
* 1.简化部分路由地址
* Merge pull request #2 from xiaochong0302/dependabot/composer/symfony/h…
* Bump symfony/http-foundation from 4.3.4 to 5.1.6

* !7 纠正迁移文件和代码实际使用字段不一致
* 纠正迁移文件和时间代码中字段不一致
* 更新版本号
* 完善后台今日统计,增加权限白名单,增加后台首页菜单,调整后台登录页样式
* Merge branch 'koogua/I1XFCF' of https://gitee.com/koogua/course-tencen…
* 前台学习资料部分完成
* !2 后台运营统计合并
* 后台学习资料部分完成
* Merge branch 'master' into develop
* Merge branch 'master' of https://github.com/xiaochong0302/course-tencent-cloud
* 1.增加changelog.md
* 1.简化部分路由地址
* Merge pull request #2 from xiaochong0302/dependabot/composer/symfony/h…
* Bump symfony/http-foundation from 4.3.4 to 5.1.6

* !9 修正插入数据不一致以及后台菜单参数类型报错
* 1.修正插入的管理帐号数据
* 纠正迁移文件和时间代码中字段不一致
* 更新版本号
* 完善后台今日统计,增加权限白名单,增加后台首页菜单,调整后台登录页样式
* Merge branch 'koogua/I1XFCF' of https://gitee.com/koogua/course-tencen…
* 前台学习资料部分完成
* !2 后台运营统计合并
* 后台学习资料部分完成
* Merge branch 'master' into develop
* Merge branch 'master' of https://github.com/xiaochong0302/course-tencent-cloud
* 1.增加changelog.md
* 1.简化部分路由地址
* Merge pull request #2 from xiaochong0302/dependabot/composer/symfony/h…
* Bump symfony/http-foundation from 4.3.4 to 5.1.6

* !12 修正退款项目空白以及弹窗自适应
* 修复退款项目空白,以及弹窗高度自适应

* !13 修正退款项目空白以及弹窗自适应
* 修复退款项目空白,以及弹窗高度自适应

* !14 修正点击退款404
* 修复退款项目空白,以及弹窗高度自适应,取消退款内部错误

* 删除文件 LICENSE

* add LICENSE.

* update app/Http/Admin/Controllers/UploadController.php.
去除重复的signatureAction方法

* !19 v1.2.0阶段性合并
* 增加微信H5支付需要的Referer头信息

* v1.2.0阶段性合并 (#11)

* !6 develop->master 1.1.0
* 更新版本号
* 完善后台今日统计,增加权限白名单,增加后台首页菜单,调整后台登录页样式
* Merge branch 'koogua/I1XFCF' of https://gitee.com/koogua/course-tencen…
* 前台学习资料部分完成
* !2 后台运营统计合并
* 后台学习资料部分完成
* Merge branch 'master' into develop
* Merge branch 'master' of https://github.com/xiaochong0302/course-tencent-cloud
* 1.增加changelog.md
* 1.简化部分路由地址
* Merge pull request #2 from xiaochong0302/dependabot/composer/symfony/h…
* Bump symfony/http-foundation from 4.3.4 to 5.1.6

* !7 纠正迁移文件和代码实际使用字段不一致
* 纠正迁移文件和时间代码中字段不一致
* 更新版本号
* 完善后台今日统计,增加权限白名单,增加后台首页菜单,调整后台登录页样式
* Merge branch 'koogua/I1XFCF' of https://gitee.com/koogua/course-tencen…
* 前台学习资料部分完成
* !2 后台运营统计合并
* 后台学习资料部分完成
* Merge branch 'master' into develop
* Merge branch 'master' of https://github.com/xiaochong0302/course-tencent-cloud
* 1.增加changelog.md
* 1.简化部分路由地址
* Merge pull request #2 from xiaochong0302/dependabot/composer/symfony/h…
* Bump symfony/http-foundation from 4.3.4 to 5.1.6

* !9 修正插入数据不一致以及后台菜单参数类型报错
* 1.修正插入的管理帐号数据
* 纠正迁移文件和时间代码中字段不一致
* 更新版本号
* 完善后台今日统计,增加权限白名单,增加后台首页菜单,调整后台登录页样式
* Merge branch 'koogua/I1XFCF' of https://gitee.com/koogua/course-tencen…
* 前台学习资料部分完成
* !2 后台运营统计合并
* 后台学习资料部分完成
* Merge branch 'master' into develop
* Merge branch 'master' of https://github.com/xiaochong0302/course-tencent-cloud
* 1.增加changelog.md
* 1.简化部分路由地址
* Merge pull request #2 from xiaochong0302/dependabot/composer/symfony/h…
* Bump symfony/http-foundation from 4.3.4 to 5.1.6

* !12 修正退款项目空白以及弹窗自适应
* 修复退款项目空白,以及弹窗高度自适应

* !13 修正退款项目空白以及弹窗自适应
* 修复退款项目空白,以及弹窗高度自适应

* !14 修正点击退款404
* 修复退款项目空白,以及弹窗高度自适应,取消退款内部错误

* 删除文件 LICENSE

* add LICENSE.

* update app/Http/Admin/Controllers/UploadController.php.
去除重复的signatureAction方法

* !19 v1.2.0阶段性合并
* 增加微信H5支付需要的Referer头信息

* 更新H5支付方式

* 更新H5支付方式

* 更新H5支付方式

* !23 修复添加课时后进入编辑页面500错误 
* 修复添加课时后进入编辑页面500错误

* !24 修复添加课时后进入编辑页面500错误
* 修复添加课时后进入编辑页面500错误

* !33 开放登录阶段性合并
* Merge remote-tracking branch 'gitee/xiaochong0302/I280IZ' into xiaocho…
* 初步完成开放登录,待线上测试7
* Merge branch 'demo' of gitee.com:koogua/course-tencent-cloud into xiao…
* 初步完成开放登录,待线上测试6
* !30 开放登录线上测试5
* !29 开放登录线上测试5
* 初步完成开放登录,待线上测试5
* !28 开放登录线上测试4
* 初步完成开放登录,待线上测试4
* !27 开放登录线上测试3
* 初步完成开放登录,待线上测试3
* !26 开放登录线上测试2
* 初步完成开放登录,待线上测试2
* !25 开放登录线上测试
* 初步完成开放登录,待线上测试
* !22 验证更新h5支付
* Merge remote-tracking branch 'remotes/gitee/develop' into demo
* !20 验证更新h5支付
* Merge branch 'develop' of https://gitee.com/koogua/course-tencent-clou…
* !16 v1.2.0阶段性合并
* 删除调试断点代码
* 删除重复的signature方法
* Merge branch 'develop' of https://gitee.com/koogua/course-tencent-clou…
* demo后台增加统计
* !5 更新版本号1.1.0
* !4 v1.1.0版本develop->demo
* Merge branch 'develop' into demo
* 1.增加changelog.md
* Merge branch 'develop' into demo
* Merge branch 'develop' into demo
* Merge branch 'develop' into demo
* !1 精简优化代码
* Merge branch 'develop' into demo
* 合并修改

* !34 修复创建课时相关属性表数据未生成的问题 
* 修复创建课时相关属性表数据未生成的问题

* !35 修复腾讯云回调数据结构改变导致的错误
* 修复腾讯云回调数据结构改变导致的错误,缩短vod_event计划任务时间

* !36 修复添加课程后进入列表500错误
* 修复未填充教师和分类的列表错误

* 优化第三方登录,修复注册密码加密问题

* !38 修复课程分类未过滤
* 过滤课程分类

* !39 修复课程分类未过滤
* 过滤课程分类

* !40 修复课程分类未过滤2
* 过滤课程分类
* 过滤课程分类

* !41 修复课程分类未过滤2
* 过滤课程分类
* 过滤课程分类

* 优化开发登录,计划任务执行路径,周期

* v1.2.1阶段性合并 (#13)

* !6 develop->master 1.1.0
* 更新版本号
* 完善后台今日统计,增加权限白名单,增加后台首页菜单,调整后台登录页样式
* Merge branch 'koogua/I1XFCF' of https://gitee.com/koogua/course-tencen…
* 前台学习资料部分完成
* !2 后台运营统计合并
* 后台学习资料部分完成
* Merge branch 'master' into develop
* Merge branch 'master' of https://github.com/xiaochong0302/course-tencent-cloud
* 1.增加changelog.md
* 1.简化部分路由地址
* Merge pull request #2 from xiaochong0302/dependabot/composer/symfony/h…
* Bump symfony/http-foundation from 4.3.4 to 5.1.6

* !7 纠正迁移文件和代码实际使用字段不一致
* 纠正迁移文件和时间代码中字段不一致
* 更新版本号
* 完善后台今日统计,增加权限白名单,增加后台首页菜单,调整后台登录页样式
* Merge branch 'koogua/I1XFCF' of https://gitee.com/koogua/course-tencen…
* 前台学习资料部分完成
* !2 后台运营统计合并
* 后台学习资料部分完成
* Merge branch 'master' into develop
* Merge branch 'master' of https://github.com/xiaochong0302/course-tencent-cloud
* 1.增加changelog.md
* 1.简化部分路由地址
* Merge pull request #2 from xiaochong0302/dependabot/composer/symfony/h…
* Bump symfony/http-foundation from 4.3.4 to 5.1.6

* !9 修正插入数据不一致以及后台菜单参数类型报错
* 1.修正插入的管理帐号数据
* 纠正迁移文件和时间代码中字段不一致
* 更新版本号
* 完善后台今日统计,增加权限白名单,增加后台首页菜单,调整后台登录页样式
* Merge branch 'koogua/I1XFCF' of https://gitee.com/koogua/course-tencen…
* 前台学习资料部分完成
* !2 后台运营统计合并
* 后台学习资料部分完成
* Merge branch 'master' into develop
* Merge branch 'master' of https://github.com/xiaochong0302/course-tencent-cloud
* 1.增加changelog.md
* 1.简化部分路由地址
* Merge pull request #2 from xiaochong0302/dependabot/composer/symfony/h…
* Bump symfony/http-foundation from 4.3.4 to 5.1.6

* !12 修正退款项目空白以及弹窗自适应
* 修复退款项目空白,以及弹窗高度自适应

* !13 修正退款项目空白以及弹窗自适应
* 修复退款项目空白,以及弹窗高度自适应

* !14 修正点击退款404
* 修复退款项目空白,以及弹窗高度自适应,取消退款内部错误

* 删除文件 LICENSE

* add LICENSE.

* update app/Http/Admin/Controllers/UploadController.php.
去除重复的signatureAction方法

* !19 v1.2.0阶段性合并
* 增加微信H5支付需要的Referer头信息

* 更新H5支付方式

* 更新H5支付方式

* 更新H5支付方式

* !23 修复添加课时后进入编辑页面500错误 
* 修复添加课时后进入编辑页面500错误

* !24 修复添加课时后进入编辑页面500错误
* 修复添加课时后进入编辑页面500错误

* !33 开放登录阶段性合并
* Merge remote-tracking branch 'gitee/xiaochong0302/I280IZ' into xiaocho…
* 初步完成开放登录,待线上测试7
* Merge branch 'demo' of gitee.com:koogua/course-tencent-cloud into xiao…
* 初步完成开放登录,待线上测试6
* !30 开放登录线上测试5
* !29 开放登录线上测试5
* 初步完成开放登录,待线上测试5
* !28 开放登录线上测试4
* 初步完成开放登录,待线上测试4
* !27 开放登录线上测试3
* 初步完成开放登录,待线上测试3
* !26 开放登录线上测试2
* 初步完成开放登录,待线上测试2
* !25 开放登录线上测试
* 初步完成开放登录,待线上测试
* !22 验证更新h5支付
* Merge remote-tracking branch 'remotes/gitee/develop' into demo
* !20 验证更新h5支付
* Merge branch 'develop' of https://gitee.com/koogua/course-tencent-clou…
* !16 v1.2.0阶段性合并
* 删除调试断点代码
* 删除重复的signature方法
* Merge branch 'develop' of https://gitee.com/koogua/course-tencent-clou…
* demo后台增加统计
* !5 更新版本号1.1.0
* !4 v1.1.0版本develop->demo
* Merge branch 'develop' into demo
* 1.增加changelog.md
* Merge branch 'develop' into demo
* Merge branch 'develop' into demo
* Merge branch 'develop' into demo
* !1 精简优化代码
* Merge branch 'develop' into demo
* 合并修改

* !34 修复创建课时相关属性表数据未生成的问题 
* 修复创建课时相关属性表数据未生成的问题

* !35 修复腾讯云回调数据结构改变导致的错误
* 修复腾讯云回调数据结构改变导致的错误,缩短vod_event计划任务时间

* !36 修复添加课程后进入列表500错误
* 修复未填充教师和分类的列表错误

* 优化第三方登录,修复注册密码加密问题

* !38 修复课程分类未过滤
* 过滤课程分类

* !39 修复课程分类未过滤
* 过滤课程分类

* !40 修复课程分类未过滤2
* 过滤课程分类
* 过滤课程分类

* !41 修复课程分类未过滤2
* 过滤课程分类
* 过滤课程分类

* 优化开发登录,计划任务执行路径,周期

* 优化开发登录逻辑
This commit is contained in:
jacky huang 2020-12-09 19:40:49 +08:00 committed by GitHub
parent 0f4bbad4eb
commit 54a0102872
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 1967 additions and 229 deletions

View File

@ -1,3 +1,7 @@
### [v1.2.1](https://gitee.com/koogua/course-tencent-cloud/releases/v1.2.1)(2020-12-10)
- 增加QQ微信微博第三方登录
- 代码优化以及问题修复
### [v1.2.0](https://gitee.com/koogua/course-tencent-cloud/releases/v1.2.0)(2020-11-25)
- 增加客户端api
- 代码优化以及问题修复

339
LICENSE Normal file
View File

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@ -6,9 +6,9 @@
酷瓜云课堂依托腾讯云基础服务架构采用C扩展框架Phalcon开发GPL-2.0开源协议,致力开源网课系统,开源网校系统,开源在线教育系统。
![](https://img.shields.io/static/v1?label=release&message=1.2.0&color=blue)
![](https://img.shields.io/static/v1?label=stars&message=101&color=blue)
![](https://img.shields.io/static/v1?label=forks&message=40&color=blue)
![](https://img.shields.io/static/v1?label=release&message=1.2.1&color=blue)
![](https://img.shields.io/static/v1?label=stars&message=112&color=blue)
![](https://img.shields.io/static/v1?label=forks&message=41&color=blue)
![](https://img.shields.io/static/v1?label=license&message=GPL-2.0&color=blue)
#### 系统功能

View File

@ -45,7 +45,8 @@ class VodEventTask extends Task
protected function handleNewFileUploadEvent($event)
{
$fileId = $event['FileUploadEvent']['FileId'];
$format = $event['FileUploadEvent']['MediaBasicInfo']['Type'];
$width = $event['FileUploadEvent']['MetaData']['Height'];
$height = $event['FileUploadEvent']['MetaData']['Width'];
$duration = $event['FileUploadEvent']['MetaData']['Duration'];
$chapterRepo = new ChapterRepo();
@ -56,7 +57,7 @@ class VodEventTask extends Task
$vodService = new VodService();
if ($this->isAudioFile($format)) {
if ($width == 0 && $height == 0) {
$vodService->createTransAudioTask($fileId);
} else {
$vodService->createTransVideoTask($fileId);
@ -144,13 +145,6 @@ class VodEventTask extends Task
return $vodService->confirmEvents($handles);
}
protected function isAudioFile($format)
{
$formats = ['mp3', 'm4a', 'wav', 'flac', 'ogg'];
return in_array(strtolower($format), $formats);
}
protected function updateVodAttrs(ChapterModel $chapter)
{
$courseStats = new CourseStatService();

View File

@ -110,8 +110,6 @@ class ChapterController extends Controller
$this->view->pick('chapter/edit_lesson');
$resources = $chapterService->getResources($chapter->id);
$cos = $chapterService->getSettings('cos');
$this->view->setVar('cos', $cos);

View File

@ -298,4 +298,33 @@ class SettingController extends Controller
}
}
/**
* @Route("/oauth", name="admin.setting.oauth")
*/
public function oauthAction()
{
$settingService = new SettingService();
if ($this->request->isPost()) {
$section = $this->request->getPost('section', 'string');
$data = $this->request->getPost();
$settingService->updateSettings($section, $data);
return $this->jsonSuccess(['msg' => '更新配置成功']);
} else {
$qqAuth = $settingService->getQQAuthSettings();
$weixinAuth = $settingService->getWeixinAuthSettings();
$weiboAuth = $settingService->getWeiboAuthSettings();
$this->view->setVar('qq_auth', $qqAuth);
$this->view->setVar('weixin_auth', $weixinAuth);
$this->view->setVar('weibo_auth', $weiboAuth);
}
}
}

View File

@ -744,6 +744,12 @@ class AuthNode extends Service
'type' => 'menu',
'route' => 'admin.setting.im',
],
[
'id' => '5-1-12',
'title' => '开放登录',
'type' => 'menu',
'route' => 'admin.setting.oauth',
],
],
],
],

View File

@ -78,6 +78,7 @@ class Chapter extends Service
$data['parent_id'] = $parent->id;
$data['free'] = $validator->checkFreeStatus($post['free']);
$data['priority'] = $chapterRepo->maxLessonPriority($post['parent_id']);
$parentId = $parent->id;
} else {
$data['priority'] = $chapterRepo->maxChapterPriority($post['course_id']);
$data['parent_id'] = $parentId;
@ -120,7 +121,7 @@ class Chapter extends Service
}
if ($attrs === false) {
throw new \RuntimeException("Create Chapter {$course->model} Attrs Failed");
throw new \RuntimeException("Create Chapter Related Attrs Failed");
}
}

View File

@ -81,10 +81,20 @@ class ChapterContent extends Service
$vod = $chapterRepo->findChapterVod($chapter->id);
/**
* 无新文件上传
*/
if ($fileId == $vod->file_id) {
return;
}
/**
* 删除旧文件
*/
if ($vod->file_id) {
$this->deleteVodFile($vod->file_id);
}
$vod->update([
'file_id' => $fileId,
'file_transcode' => '',
@ -102,10 +112,6 @@ class ChapterContent extends Service
$chapter->update(['attrs' => $attrs]);
$this->updateCourseVodAttrs($vod->course_id);
if (!empty($vod->file_id)) {
$this->deleteVodFile($vod->file_id);
}
}
protected function updateChapterLive(ChapterModel $chapter)

View File

@ -8,6 +8,7 @@ use App\Caches\CourseCategoryList as CourseCategoryListCache;
use App\Caches\CourseRelatedList as CourseRelatedListCache;
use App\Caches\CourseTeacherList as CourseTeacherListCache;
use App\Library\Paginator\Query as PagerQuery;
use App\Models\Category as CategoryModel;
use App\Models\Course as CourseModel;
use App\Models\CourseCategory as CourseCategoryModel;
use App\Models\CourseRating as CourseRatingModel;
@ -224,7 +225,10 @@ class Course extends Service
{
$categoryRepo = new CategoryRepo();
$allCategories = $categoryRepo->findAll(['deleted' => 0]);
$allCategories = $categoryRepo->findAll([
'type' => CategoryModel::TYPE_COURSE,
'deleted' => 0,
]);
if ($allCategories->count() == 0) {
return [];
@ -247,8 +251,11 @@ class Course extends Service
$list = [];
/**
* 没有二级分类的不显示
*/
foreach ($allCategories as $category) {
if ($category->level == 1) {
if ($category->level == 1 && $category->child_count > 0) {
$list[$category->id] = [
'name' => $category->name,
'value' => $category->id,

View File

@ -9,10 +9,39 @@ use App\Repos\Vip as VipRepo;
class Setting extends Service
{
public function getQQAuthSettings()
{
$oauth = $this->getSettings('oauth.qq');
$oauth['redirect_uri'] = $oauth['redirect_uri'] ?: kg_full_url(['for' => 'home.oauth.qq_callback']);
return $oauth;
}
public function getWeixinAuthSettings()
{
$oauth = $this->getSettings('oauth.weixin');
$oauth['redirect_uri'] = $oauth['redirect_uri'] ?: kg_full_url(['for' => 'home.oauth.weixin_callback']);
return $oauth;
}
public function getWeiboAuthSettings()
{
$oauth = $this->getSettings('oauth.weibo');
$oauth['redirect_uri'] = $oauth['redirect_uri'] ?: kg_full_url(['for' => 'home.oauth.weibo_callback']);
$oauth['refuse_uri'] = $oauth['refuse_uri'] ?: kg_full_url(['for' => 'home.oauth.weibo_refuse']);
return $oauth;
}
public function getAlipaySettings()
{
$alipay = $this->getSettings('pay.alipay');
$alipay['return_url'] = $alipay['return_url'] ?: kg_full_url(['for' => 'home.alipay_callback']);
$alipay['notify_url'] = $alipay['notify_url'] ?: kg_full_url(['for' => 'home.alipay_notify']);
return $alipay;
@ -22,6 +51,7 @@ class Setting extends Service
{
$wxpay = $this->getSettings('pay.wxpay');
$wxpay['return_url'] = $wxpay['return_url'] ?: kg_full_url(['for' => 'home.wxpay_callback']);
$wxpay['notify_url'] = $wxpay['notify_url'] ?: kg_full_url(['for' => 'home.wxpay_notify']);
return $wxpay;

View File

@ -1,3 +1,5 @@
{% set file_id = vod ? vod.file_id : '' %}
{% if play_urls %}
<fieldset class="layui-elem-field layui-field-title">
<legend>视频信息</legend>
@ -51,7 +53,7 @@
<div class="layui-form-item">
<label class="layui-form-label">文件编号</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="file_id" value="{{ vod.file_id }}" readonly="readonly" lay-verify="required">
<input class="layui-input" type="text" name="file_id" value="{{ file_id }}" readonly="readonly" lay-verify="required">
</div>
</div>
<div class="layui-form-item">

View File

@ -30,10 +30,10 @@
<div class="layui-form-item">
<label class="layui-form-label">难度</label>
<div class="layui-input-block">
<input type="radio" name="level" value="1" title="入门" {% if course.level == '1' %}checked="checked"{% endif %}>
<input type="radio" name="level" value="2" title="初级" {% if course.level == '2' %}checked="checked"{% endif %}>
<input type="radio" name="level" value="3" title="中级" {% if course.level == '3' %}checked="checked"{% endif %}>
<input type="radio" name="level" value="4" title="高级" {% if course.level == '4' %}checked="checked"{% endif %}>
<input type="radio" name="level" value="1" title="入门" {% if course.level == 1 %}checked="checked"{% endif %}>
<input type="radio" name="level" value="2" title="初级" {% if course.level == 2 %}checked="checked"{% endif %}>
<input type="radio" name="level" value="3" title="中级" {% if course.level == 3 %}checked="checked"{% endif %}>
<input type="radio" name="level" value="4" title="高级" {% if course.level == 4 %}checked="checked"{% endif %}>
</div>
</div>
<div class="layui-form-item">

View File

@ -9,6 +9,8 @@
<span class="layui-badge layui-bg-blue">直播</span>
{% elseif value == 3 %}
<span class="layui-badge layui-bg-black">专栏</span>
{% else %}
<span class="layui-badge layui-bg-gray">未知</span>
{% endif %}
{%- endmacro %}
@ -22,19 +24,21 @@
中级
{% elseif value == 4 %}
高级
{% else %}
未知
{% endif %}
</span>
{%- endmacro %}
{%- macro category_info(category) %}
{% if category %}
{% if category.id is defined %}
{% set url = url({'for':'admin.course.list'},{'category_id':category.id}) %}
分类:<a class="layui-badge layui-bg-gray" href="{{ url }}">{{ category.name }}</a>
{% endif %}
{%- endmacro %}
{%- macro teacher_info(teacher) %}
{% if teacher %}
{% if teacher.id is defined %}
{% set url = url({'for':'admin.course.list'},{'teacher_id':teacher.id}) %}
讲师:<a class="layui-badge layui-bg-gray" href="{{ url }}">{{ teacher.name }}</a>
{% endif %}

View File

@ -9,11 +9,13 @@
<span class="layui-badge layui-bg-blue">聊</span>
{% elseif value == 3 %}
<span class="layui-badge layui-bg-cyan">职</span>
{% else %}
<span class="layui-badge layui-bg-gray">未知</span>
{% endif %}
{%- endmacro %}
{%- macro owner_info(owner) %}
{% if owner %}
{% if owner.id is defined %}
{{ owner.name }}{{ owner.id }}
{% else %}
未设置

View File

@ -0,0 +1,24 @@
{% extends 'templates/main.volt' %}
{% block content %}
<div class="layui-tab layui-tab-brief">
<ul class="layui-tab-title kg-tab-title">
<li class="layui-this">QQ登录</li>
<li>微信登录</li>
<li>新浪微博</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
{{ partial('setting/oauth_qq') }}
</div>
<div class="layui-tab-item">
{{ partial('setting/oauth_weixin') }}
</div>
<div class="layui-tab-item">
{{ partial('setting/oauth_weibo') }}
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,35 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.setting.oauth'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">开启登录</label>
<div class="layui-input-block">
<input type="radio" name="enabled" value="1" title="是" {% if qq_auth.enabled == "1" %}checked="checked"{% endif %}>
<input type="radio" name="enabled" value="0" title="否" {% if qq_auth.enabled == "0" %}checked="checked"{% endif %}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">App ID</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="client_id" value="{{ qq_auth.client_id }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">App Secret</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="client_secret" value="{{ qq_auth.client_secret }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Callback Url</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="redirect_uri" value="{{ qq_auth.redirect_uri }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
<input type="hidden" name="section" value="oauth.qq">
</div>
</div>
</form>

View File

@ -0,0 +1,41 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.setting.oauth'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">开启登录</label>
<div class="layui-input-block">
<input type="radio" name="enabled" value="1" title="是" {% if weibo_auth.enabled == "1" %}checked="checked"{% endif %}>
<input type="radio" name="enabled" value="0" title="否" {% if weibo_auth.enabled == "0" %}checked="checked"{% endif %}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">App Key</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="client_id" value="{{ weibo_auth.client_id }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">App Secret</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="client_secret" value="{{ weibo_auth.client_secret }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Callback Url</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="redirect_uri" value="{{ weibo_auth.redirect_uri }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Refuse Url</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="refuse_uri" value="{{ weibo_auth.refuse_uri }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
<input type="hidden" name="section" value="oauth.weibo">
</div>
</div>
</form>

View File

@ -0,0 +1,35 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.setting.oauth'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">开启登录</label>
<div class="layui-input-block">
<input type="radio" name="enabled" value="1" title="是" {% if weixin_auth.enabled == "1" %}checked="checked"{% endif %}>
<input type="radio" name="enabled" value="0" title="否" {% if weixin_auth.enabled == "0" %}checked="checked"{% endif %}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">App ID</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="client_id" value="{{ weixin_auth.client_id }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">App Secret</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="client_secret" value="{{ weixin_auth.client_secret }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Callback Url</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="redirect_uri" value="{{ weixin_auth.redirect_uri }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
<input type="hidden" name="section" value="oauth.weixin">
</div>
</div>
</form>

View File

@ -24,6 +24,12 @@
<input class="layui-input" type="text" name="notify_url" value="{{ alipay.notify_url }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Return Url</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="return_url" value="{{ alipay.return_url }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">

View File

@ -30,6 +30,12 @@
<input class="layui-input" type="text" name="notify_url" value="{{ wxpay.notify_url }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Return Url</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="return_url" value="{{ wxpay.return_url }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">

View File

@ -22,5 +22,6 @@
{% block include_js %}{% endblock %}
{% block inline_js %}{% endblock %}
</body>
</html>

View File

@ -25,26 +25,6 @@ class TradeController extends Controller
return $this->jsonSuccess(['trade' => $trade]);
}
/**
* @Get("/h5/pay", name="api.trade.h5_pay")
*/
public function h5PayAction()
{
$sn = $this->request->getQuery('sn', 'string');
$service = new TradeService();
$response = $service->h5Pay($sn);
if (!$response) {
echo "H5支付跳转失败请回退重试";
}
$response->send();
exit();
}
/**
* @Post("/h5/create", name="api.trade.h5_create")
*/
@ -52,13 +32,9 @@ class TradeController extends Controller
{
$service = new TradeService();
$trade = $service->createH5Trade();
$content = $service->createH5Trade();
$service = new TradeInfoService();
$trade = $service->handle($trade->sn);
return $this->jsonSuccess(['trade' => $trade]);
return $this->jsonSuccess($content);
}
/**

View File

@ -5,6 +5,7 @@ namespace App\Http\Api\Services;
use App\Models\Client as ClientModel;
use App\Models\Trade as TradeModel;
use App\Services\Logic\OrderTrait;
use App\Services\Logic\Trade\TradeInfo;
use App\Services\Logic\TradeTrait;
use App\Services\Pay\Alipay;
use App\Services\Pay\Wxpay;
@ -17,23 +18,6 @@ class Trade extends Service
use OrderTrait;
use TradeTrait;
public function h5Pay($sn)
{
$trade = $this->checkTradeBySn($sn);
$response = null;
if ($trade->channel == TradeModel::CHANNEL_ALIPAY) {
$alipay = new Alipay();
$response = $alipay->wap($trade);
} elseif ($trade->channel == TradeModel::CHANNEL_WXPAY) {
$wxpay = new Wxpay();
$response = $wxpay->wap($trade);
}
return $response;
}
public function createH5Trade()
{
$post = $this->request->getPost();
@ -62,7 +46,24 @@ class Trade extends Service
$trade->create();
return $trade;
$redirect = '';
if ($trade->channel == TradeModel::CHANNEL_ALIPAY) {
$alipay = new Alipay();
$response = $alipay->wap($trade);
$redirect = $response ? $response->getTargetUrl() : '';
} elseif ($trade->channel == TradeModel::CHANNEL_WXPAY) {
$wxpay = new Wxpay();
$response = $wxpay->wap($trade);
$redirect = $response ? $response->getTargetUrl() : '';
}
$payment = ['redirect' => $redirect];
return [
'trade' => $this->handleTradeInfo($trade->sn),
'payment' => $payment,
];
}
public function createMpTrade()
@ -122,4 +123,11 @@ class Trade extends Service
return $this->request->getHeader('X-Platform');
}
protected function handleTradeInfo($sn)
{
$service = new TradeInfo();
return $service->handle($sn);
}
}

View File

@ -4,6 +4,7 @@ namespace App\Http\Home\Controllers;
use App\Http\Home\Services\Account as AccountService;
use App\Services\Logic\Account\EmailUpdate as EmailUpdateService;
use App\Services\Logic\Account\OAuthProvider as OAuthProviderService;
use App\Services\Logic\Account\PasswordReset as PasswordResetService;
use App\Services\Logic\Account\PasswordUpdate as PasswordUpdateService;
use App\Services\Logic\Account\PhoneUpdate as PhoneUpdateService;
@ -62,8 +63,13 @@ class AccountController extends Controller
$captcha = $service->getSettings('captcha');
$service = new OAuthProviderService();
$oauthProvider = $service->handle();
$returnUrl = $this->request->getHTTPReferer();
$this->view->setVar('oauth_provider', $oauthProvider);
$this->view->setVar('return_url', $returnUrl);
$this->view->setVar('captcha', $captcha);
}

View File

@ -0,0 +1,138 @@
<?php
namespace App\Http\Home\Controllers;
use App\Http\Home\Services\Connect as ConnectService;
use App\Models\Connect as ConnectModel;
/**
* @RoutePrefix("/oauth")
*/
class ConnectController extends Controller
{
/**
* @Get("/qq", name="home.oauth.qq")
*/
public function qqAction()
{
$service = new ConnectService();
$url = $service->getAuthorizeUrl(ConnectModel::PROVIDER_QQ);
return $this->response->redirect($url, true);
}
/**
* @Get("/weixin", name="home.oauth.weixin")
*/
public function weixinAction()
{
$service = new ConnectService();
$url = $service->getAuthorizeUrl(ConnectModel::PROVIDER_WEIXIN);
return $this->response->redirect($url, true);
}
/**
* @Get("/weibo", name="home.oauth.weibo")
*/
public function weiboAction()
{
$service = new ConnectService();
$url = $service->getAuthorizeUrl(ConnectModel::PROVIDER_WEIBO);
return $this->response->redirect($url, true);
}
/**
* @Get("/qq/callback", name="home.oauth.qq_callback")
*/
public function qqCallbackAction()
{
$this->handleCallback(ConnectModel::PROVIDER_QQ);
}
/**
* @Get("/weixin/callback", name="home.oauth.weixin_callback")
*/
public function weixinCallbackAction()
{
$this->handleCallback(ConnectModel::PROVIDER_WEIXIN);
}
/**
* @Get("/weibo/callback", name="home.oauth.weibo_callback")
*/
public function weiboCallbackAction()
{
$this->handleCallback(ConnectModel::PROVIDER_WEIBO);
}
/**
* @Get("/weibo/refuse", name="home.oauth.weibo_refuse")
*/
public function weiboRefuseAction()
{
return $this->response->redirect(['for' => 'home.account.login']);
}
/**
* @Post("/bind/login", name="home.oauth.bind_login")
*/
public function bindLoginAction()
{
$service = new ConnectService();
$service->bindLogin();
$location = $this->url->get(['for' => 'home.uc.account']);
return $this->jsonSuccess(['location' => $location]);
}
/**
* @Post("/bind/register", name="home.oauth.bind_register")
*/
public function bindRegisterAction()
{
$service = new ConnectService();
$service->bindRegister();
$location = $this->url->get(['for' => 'home.uc.account']);
return $this->jsonSuccess(['location' => $location]);
}
protected function handleCallback($provider)
{
$code = $this->request->getQuery('code');
$state = $this->request->getQuery('state');
$service = new ConnectService();
$openUser = $service->getOpenUserInfo($code, $state, $provider);
$connect = $service->getConnectRelation($openUser['id'], $openUser['provider']);
if ($connect) {
if ($this->authUser->id > 0) {
$service->bindUser($openUser);
return $this->response->redirect(['for' => 'home.uc.account']);
} else {
$service->authLogin($connect);
return $this->response->redirect(['for' => 'home.index']);
}
}
$captcha = $service->getSettings('captcha');
$this->view->pick('connect/bind');
$this->view->setVar('captcha', $captcha);
$this->view->setVar('provider', $provider);
$this->view->setVar('open_user', $openUser);
}
}

View File

@ -7,6 +7,7 @@ use App\Services\Logic\Order\OrderCancel as OrderCancelService;
use App\Services\Logic\Order\OrderConfirm as OrderConfirmService;
use App\Services\Logic\Order\OrderCreate as OrderCreateService;
use App\Services\Logic\Order\OrderInfo as OrderInfoService;
use App\Services\Logic\Order\PayProvider as PayProviderService;
use Phalcon\Mvc\Dispatcher;
use Phalcon\Mvc\View;
@ -82,6 +83,10 @@ class OrderController extends Controller
{
$sn = $this->request->getQuery('sn', 'string');
$service = new PayProviderService();
$payProvider = $service->handle();
$service = new OrderInfoService();
$order = $service->handle($sn);
@ -90,6 +95,7 @@ class OrderController extends Controller
$this->response->redirect(['for' => 'home.uc.orders']);
}
$this->view->setVar('pay_provider', $payProvider);
$this->view->setVar('order', $order);
}

View File

@ -75,6 +75,22 @@ class PublicController extends \Phalcon\Mvc\Controller
return $this->jsonSuccess(['token' => $token]);
}
/**
* @Get("/alipay/callback", name="home.alipay_callback")
*/
public function alipayCallbackAction()
{
return $this->response->redirect('/h5/#/pages/me/index', true);
}
/**
* @Get("/wxpay/callback", name="home.wxpay_callback")
*/
public function wxpayCallbackAction()
{
return $this->response->redirect('/h5/#/pages/me/index', true);
}
/**
* @Post("/alipay/notify", name="home.alipay_notify")
*/

View File

@ -2,7 +2,10 @@
namespace App\Http\Home\Controllers;
use App\Services\Logic\Account\OAuthProvider as OAuthProviderService;
use App\Services\Logic\User\Console\AccountInfo as AccountInfoService;
use App\Services\Logic\User\Console\ConnectDelete as ConnectDeleteService;
use App\Services\Logic\User\Console\ConnectList as ConnectListService;
use App\Services\Logic\User\Console\ConsultList as ConsultListService;
use App\Services\Logic\User\Console\CourseList as CourseListService;
use App\Services\Logic\User\Console\FavoriteList as FavoriteListService;
@ -59,13 +62,21 @@ class UserConsoleController extends Controller
*/
public function accountAction()
{
$type = $this->request->getQuery('type', 'string', 'info');
$service = new AccountInfoService();
$captcha = $service->getSettings('captcha');
$account = $service->handle();
$type = $this->request->getQuery('type', 'string', 'info');
$service = new OAuthProviderService();
$oauthProvider = $service->handle();
$service = new ConnectListService();
$connects = $service->handle();
if ($type == 'info') {
$this->view->pick('user/console/account_info');
@ -77,6 +88,8 @@ class UserConsoleController extends Controller
$this->view->pick('user/console/account_password');
}
$this->view->setVar('oauth_provider', $oauthProvider);
$this->view->setVar('connects', $connects);
$this->view->setVar('captcha', $captcha);
$this->view->setVar('account', $account);
}
@ -207,4 +220,23 @@ class UserConsoleController extends Controller
return $this->jsonSuccess($content);
}
/**
* @Post("/connect/{id:[0-9]+}/delete", name="home.uc.unconnect")
*/
public function deleteConnectAction($id)
{
$service = new ConnectDeleteService();
$service->handle($id);
$location = $this->url->get(['for' => 'home.uc.account']);
$content = [
'location' => $location,
'msg' => '解除登录绑定成功',
];
return $this->jsonSuccess($content);
}
}

View File

@ -0,0 +1,211 @@
<?php
namespace App\Http\Home\Services;
use App\Models\Connect as ConnectModel;
use App\Models\User as UserModel;
use App\Repos\Connect as ConnectRepo;
use App\Repos\User as UserRepo;
use App\Services\Auth\Home as AuthService;
use App\Services\Logic\Account\Register as RegisterService;
use App\Services\OAuth\QQ as QQAuth;
use App\Services\OAuth\WeiBo as WeiBoAuth;
use App\Services\OAuth\WeiXin as WeiXinAuth;
use App\Validators\Account as AccountValidator;
class Connect extends Service
{
public function bindLogin()
{
$post = $this->request->getPost();
$auth = $this->getConnectAuth($post['provider']);
$auth->checkState($post['state']);
$validator = new AccountValidator();
$user = $validator->checkUserLogin($post['account'], $post['password']);
$openUser = json_decode($post['open_user'], true);
$this->handleConnectRelation($user, $openUser);
$auth = $this->getAppAuth();
$auth->saveAuthInfo($user);
}
public function bindRegister()
{
$post = $this->request->getPost();
$auth = $this->getConnectAuth($post['provider']);
$auth->checkState($post['state']);
$openUser = json_decode($post['open_user'], true);
$registerService = new RegisterService();
$account = $registerService->handle();
$userRepo = new UserRepo();
$user = $userRepo->findById($account->id);
$this->handleConnectRelation($user, $openUser);
$auth = $this->getAppAuth();
$auth->saveAuthInfo($user);
}
public function bindUser(array $openUser)
{
$user = $this->getLoginUser();
$this->handleConnectRelation($user, $openUser);
}
public function authLogin(ConnectModel $connect)
{
$userRepo = new UserRepo();
$user = $userRepo->findById($connect->user_id);
$auth = $this->getAppAuth();
$auth->saveAuthInfo($user);
}
public function getAuthorizeUrl($provider)
{
$auth = $this->getConnectAuth($provider);
return $auth->getAuthorizeUrl();
}
public function getOpenUserInfo($code, $state, $provider)
{
$auth = $this->getConnectAuth($provider);
$auth->checkState($state);
$token = $auth->getAccessToken($code);
$openId = $auth->getOpenId($token);
return $auth->getUserInfo($token, $openId);
}
public function getConnectRelation($openId, $provider)
{
$connectRepo = new ConnectRepo();
return $connectRepo->findByOpenId($openId, $provider);
}
public function getConnectAuth($provider)
{
$auth = null;
switch ($provider) {
case ConnectModel::PROVIDER_QQ:
$auth = $this->getQQAuth();
break;
case ConnectModel::PROVIDER_WEIXIN:
$auth = $this->getWeiXinAuth();
break;
case ConnectModel::PROVIDER_WEIBO:
$auth = $this->getWeiBoAuth();
break;
}
if (!$auth) {
throw new \Exception('Invalid OAuth Provider');
}
return $auth;
}
protected function getQQAuth()
{
$settings = $this->getSettings('oauth.qq');
return new QQAuth(
$settings['client_id'],
$settings['client_secret'],
$settings['redirect_uri']
);
}
protected function getWeiXinAuth()
{
$settings = $this->getSettings('oauth.weixin');
return new WeiXinAuth(
$settings['client_id'],
$settings['client_secret'],
$settings['redirect_uri']
);
}
protected function getWeiBoAuth()
{
$settings = $this->getSettings('oauth.weibo');
return new WeiBoAuth(
$settings['client_id'],
$settings['client_secret'],
$settings['redirect_uri']
);
}
protected function getAppAuth()
{
/**
* @var $auth AuthService
*/
$auth = $this->getDI()->get('auth');
return $auth;
}
protected function handleConnectRelation(UserModel $user, array $openUser)
{
$connectRepo = new ConnectRepo();
$connect = $connectRepo->findByOpenId($openUser['id'], $openUser['provider']);
if ($connect) {
$connect->open_name = $openUser['name'];
$connect->open_avatar = $openUser['avatar'];
if ($connect->user_id != $user->id) {
$connect->user_id = $user->id;
}
if ($connect->deleted == 1) {
$connect->deleted = 0;
}
$connect->update();
} else {
$connect = new ConnectModel();
$connect->user_id = $user->id;
$connect->open_id = $openUser['id'];
$connect->open_name = $openUser['name'];
$connect->open_avatar = $openUser['avatar'];
$connect->provider = $openUser['provider'];
$connect->create();
}
}
}

View File

@ -27,6 +27,17 @@
<span class="separator">·</span>
<a class="forget-link" href="{{ url({'for':'home.account.forget_pwd'}) }}">忘记密码</a>
</div>
<div class="oauth">
{% if oauth_provider.qq.enabled == 1 %}
<a class="layui-icon layui-icon-login-qq login-qq" href="{{ url({'for':'home.oauth.qq'}) }}"></a>
{% endif %}
{% if oauth_provider.weixin.enabled == 1 %}
<a class="layui-icon layui-icon-login-wechat login-wechat" href="{{ url({'for':'home.oauth.weixin'}) }}"></a>
{% endif %}
{% if oauth_provider.weibo.enabled == 1 %}
<a class="layui-icon layui-icon-login-weibo login-weibo" href="{{ url({'for':'home.oauth.weibo'}) }}"></a>
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,34 @@
{% extends 'templates/main.volt' %}
{% block content %}
<div class="layui-breadcrumb breadcrumb">
<a href="/">首页</a>
<a><cite>登录绑定</cite></a>
</div>
<div class="login-wrap wrap">
<div class="layui-tab layui-tab-brief login-tab">
<ul class="layui-tab-title login-tab-title">
<li class="layui-this">绑定已有帐号</li>
<li>注册并绑定帐号</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
{{ partial('connect/bind_login') }}
</div>
<div class="layui-tab-item">
{{ partial('connect/bind_register') }}
</div>
</div>
</div>
</div>
{% endblock %}
{% block include_js %}
{{ js_include('https://ssl.captcha.qq.com/TCaptcha.js',false) }}
{{ js_include('home/js/captcha.verify.js') }}
{% endblock %}

View File

@ -0,0 +1,21 @@
<form class="layui-form account-form" method="POST" action="{{ url({'for':'home.oauth.bind_login'}) }}">
<div class="layui-form-item">
<div class="layui-input-block">
<input class="layui-input" type="text" name="account" autocomplete="off" placeholder="手机 / 邮箱" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<input class="layui-input" type="password" name="password" autocomplete="off" placeholder="密码" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button id="submit-btn" class="layui-btn layui-btn-fluid" lay-submit="true" lay-filter="go">登录并绑定已有帐号</button>
<input type="hidden" name="provider" value="{{ provider }}">
<input type="hidden" name="code" value="{{ request.get('code') }}">
<input type="hidden" name="state" value="{{ request.get('state') }}">
<input type="hidden" name="open_user" value='{{ open_user|json_encode }}'>
</div>
</div>
</form>

View File

@ -0,0 +1,32 @@
<form class="layui-form account-form" method="POST" action="{{ url({'for':'home.oauth.bind_register'}) }}">
<div class="layui-form-item">
<div class="layui-input-block">
<input id="cv-account" class="layui-input" type="text" name="account" autocomplete="off" placeholder="手机 / 邮箱" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<input class="layui-input" type="password" name="password" autocomplete="off" placeholder="密码字母数字特殊字符6-16位" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-inline verify-input-inline">
<input class="layui-input" type="text" name="verify_code" placeholder="验证码" lay-verify="required">
</div>
<div class="layui-input-inline verify-btn-inline">
<button id="cv-verify-emit" class="layui-btn layui-btn-primary layui-btn-disabled" type="button" disabled="disabled">获取验证码</button>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button id="cv-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">注册并绑定帐号</button>
<input type="hidden" name="provider" value="{{ provider }}">
<input type="hidden" name="code" value="{{ request.get('code') }}">
<input type="hidden" name="state" value="{{ request.get('state') }}">
<input type="hidden" name="open_user" value='{{ open_user|json_encode }}'>
<input id="cv-app-id" type="hidden" value="{{ captcha.app_id }}">
<input id="cv-ticket" type="hidden" name="ticket">
<input id="cv-rand" type="hidden" name="rand">
</div>
</div>
</form>

View File

@ -17,8 +17,12 @@
支付金额:<span class="amount">{{ '¥%0.2f'|format(order.amount) }}</span>
</div>
<div class="channel">
<a class="alipay btn-pay" href="javascript:" data-channel="alipay">{{ image('home/img/alipay.png') }}</a>
<a class="wxpay btn-pay" href="javascript:" data-channel="wxpay">{{ image('home/img/wxpay.png') }}</a>
{% if pay_provider.alipay.enabled == 1 %}
<a class="alipay btn-pay" href="javascript:" data-channel="alipay">{{ image('home/img/alipay.png') }}</a>
{% endif %}
{% if pay_provider.wxpay.enabled == 1 %}
<a class="wxpay btn-pay" href="javascript:" data-channel="wxpay">{{ image('home/img/wxpay.png') }}</a>
{% endif %}
</div>
<div class="footer">
<span class="tips">友情提示请在12小时内完成支付有问题请联系客服</span>

View File

@ -2,6 +2,23 @@
{% block content %}
{%- macro connect_provider(item) %}
{% if item.provider == 1 %}
<i class="layui-icon layui-icon-login-qq login-qq"></i>
{% elseif item.provider == 2 %}
<i class="layui-icon layui-icon-login-wechat login-wechat"></i>
{% elseif item.provider == 3 %}
<i class="layui-icon layui-icon-login-weibo login-weibo"></i>
{% endif %}
{%- endmacro %}
{%- macro connect_user(item) %}
{% if item.open_avatar %}
<span class="open-avatar"><img src="{{ item.open_avatar }}"></span>
{% endif %}
<span class="open-name">{{ item.open_name }}</span>
{%- endmacro %}
{% set edit_pwd_url = url({'for':'home.uc.account'},{'type':'password'}) %}
{% set edit_phone_url = url({'for':'home.uc.account'},{'type':'phone'}) %}
{% set edit_email_url = url({'for':'home.uc.account'},{'type':'email'}) %}
@ -34,7 +51,7 @@
<div class="security-item">
<span class="icon"><i class="layui-icon layui-icon-email"></i></span>
<span class="title">邮箱绑定</span>
{% if account.phone %}
{% if account.email %}
<span class="summary">已绑定邮箱:{{ account.email|anonymous }}</span>
<span class="action"><a class="layui-btn layui-btn-sm btn-edit-email" href="{{ edit_email_url }}">修改</a></span>
{% else %}
@ -43,6 +60,45 @@
{% endif %}
</div>
</div>
<div class="my-nav">
<span class="title">开放登录</span>
</div>
{% if connects %}
<div class="connect-tips">已经绑定的第三方帐号</div>
<div class="connect-list">
<table class="layui-table">
<tr>
<td>序号</td>
<td>提供方</td>
<td>用户信息</td>
<td>创建日期</td>
<td width="15%">操作</td>
</tr>
{% for connect in connects %}
{% set url = url({'for':'home.uc.unconnect','id':connect.id}) %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ connect_provider(connect) }}</td>
<td>{{ connect_user(connect) }}</td>
<td>{{ date('Y-m-d H:i',connect.create_time) }}</td>
<td><a class="layui-btn layui-btn-danger layui-btn-sm kg-delete" href="javascript:" data-url="{{ url }}" data-tips="确定要解除绑定吗?">解除绑定</a></td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
<div class="connect-tips">支持绑定的第三方帐号</div>
<div class="oauth-list">
{% if oauth_provider.qq.enabled == 1 %}
<a class="layui-icon layui-icon-login-qq login-qq" href="{{ url({'for':'home.oauth.qq'}) }}"></a>
{% endif %}
{% if oauth_provider.qq.enabled == 1 %}
<a class="layui-icon layui-icon-login-wechat login-wechat" href="{{ url({'for':'home.oauth.weixin'}) }}"></a>
{% endif %}
{% if oauth_provider.qq.enabled == 1 %}
<a class="layui-icon layui-icon-login-weibo login-weibo" href="{{ url({'for':'home.oauth.weibo'}) }}"></a>
{% endif %}
</div>
</div>
</div>
</div>

View File

@ -7,6 +7,8 @@
{% set full_user_url = full_url({'for':'home.user.show','id':user.id}) %}
{% set qrcode_url = url({'for':'home.qrcode'},{'text':full_user_url}) %}
{% set user.area = user.area ? user.area : '火星' %}
{% set user.about = user.about ? user.about : '这个家伙很懒,什么都没留下!' %}
<div class="breadcrumb">
<span class="layui-breadcrumb">
@ -32,9 +34,7 @@
<p><span><i class="layui-icon layui-icon-location"></i></span><span>{{ user.area }}</span></p>
<p><span><i class="layui-icon layui-icon-time"></i></span><span>{{ date('Y-m-d H:i',user.active_time) }}</span></p>
</div>
{% if user.about %}
<div class="about">{{ user.about }}</div>
{% endif %}
<div class="about">{{ user.about }}</div>
</div>
{% set show_tab_courses = user.course_count > 0 %}

View File

@ -11,7 +11,7 @@ class AppInfo
protected $link = 'https://gitee.com/koogua';
protected $version = '1.2.0';
protected $version = '1.2.1';
public function __get($name)
{

View File

@ -1,53 +0,0 @@
<?php
namespace App\Library;
use GuzzleHttp\Client as HttpClient;
abstract class OAuth
{
protected $appId;
protected $appSecret;
protected $redirectUri;
protected $accessToken;
protected $openId;
public function __construct($appId, $appSecret, $redirectUri)
{
$this->appId = $appId;
$this->appSecret = $appSecret;
$this->redirectUri = $redirectUri;
}
public function httpGet($uri, $params = [], $headers = [])
{
$client = new HttpClient();
$options = ['query' => $params, 'headers' => $headers];
$response = $client->get($uri, $options);
return $response->getBody();
}
public function httpPost($uri, $params = [], $headers = [])
{
$client = new HttpClient();
$options = ['query' => $params, 'headers' => $headers];
$response = $client->post($uri, $options);
return $response->getBody();
}
abstract public function getAuthorizeUrl();
abstract public function getAccessToken($code);
abstract public function getOpenId($accessToken);
abstract public function getUserInfo($accessToken, $openId);
}

104
app/Models/Connect.php Normal file
View File

@ -0,0 +1,104 @@
<?php
namespace App\Models;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
class Connect extends Model
{
const PROVIDER_QQ = 1; // QQ
const PROVIDER_WEIXIN = 2; // 微信
const PROVIDER_WEIBO = 3; // 微博
/**
* 主键编号
*
* @var int
*/
public $id;
/**
* 用户编号
*
* @var int
*/
public $user_id;
/**
* 开放ID
*
* @var string
*/
public $open_id;
/**
* 开放名称
*
* @var string
*/
public $open_name;
/**
* 开放头像
*
* @var string
*/
public $open_avatar;
/**
* 提供商
*
* @var int
*/
public $provider;
/**
* 删除标识
*
* @var int
*/
public $deleted;
/**
* 创建时间
*
* @var int
*/
public $create_time;
/**
* 更新时间
*
* @var int
*/
public $update_time;
public function getSource(): string
{
return 'kg_connect';
}
public function initialize()
{
parent::initialize();
$this->addBehavior(
new SoftDelete([
'field' => 'deleted',
'value' => 1,
])
);
}
public function beforeCreate()
{
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
}
}

62
app/Repos/Connect.php Normal file
View File

@ -0,0 +1,62 @@
<?php
namespace App\Repos;
use App\Models\Connect as ConnectModel;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class Connect extends Repository
{
/**
* @param array $where
* @return ResultsetInterface|Resultset|ConnectModel[]
*/
public function findAll($where = [])
{
$query = ConnectModel::query();
$query->where('1 = 1');
if (isset($where['user_id'])) {
$query->andWhere('user_id = :user_id:', ['user_id' => $where['user_id']]);
}
if (isset($where['provider'])) {
$query->andWhere('provider = :provider:', ['provider' => $where['provider']]);
}
if (isset($where['deleted'])) {
$query->andWhere('deleted = :deleted:', ['deleted' => $where['deleted']]);
}
$query->orderBy('id DESC');
return $query->execute();
}
/**
* @param int $id
* @return ConnectModel|Model|bool
*/
public function findById($id)
{
return ConnectModel::findFirst($id);
}
/**
* @param string $openId
* @param int $provider
* @return ConnectModel|Model|bool
*/
public function findByOpenId($openId, $provider)
{
return ConnectModel::findFirst([
'conditions' => 'open_id = ?1 and provider = ?2',
'bind' => [1 => $openId, 2 => $provider],
]);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Services\Logic\Account;
use App\Services\Logic\Service;
class OAuthProvider extends Service
{
public function handle()
{
$weixin = $this->getSettings('oauth.weixin');
$weibo = $this->getSettings('oauth.weibo');
$qq = $this->getSettings('oauth.qq');
return [
'weixin' => ['enabled' => $weixin['enabled']],
'weibo' => ['enabled' => $weibo['enabled']],
'qq' => ['enabled' => $qq['enabled']],
];
}
}

View File

@ -2,6 +2,7 @@
namespace App\Services\Logic\Account;
use App\Library\Utils\Password as PasswordUtil;
use App\Library\Validators\Common as CommonValidator;
use App\Models\Account as AccountModel;
use App\Models\ImUser as ImUserModel;
@ -42,6 +43,10 @@ class Register extends Service
$data['password'] = $accountValidator->checkPassword($post['password']);
$data['salt'] = PasswordUtil::salt();
$data['password'] = PasswordUtil::hash($data['password'], $data['salt']);
try {
$this->db->begin();

View File

@ -0,0 +1,21 @@
<?php
namespace App\Services\Logic\Order;
use App\Services\Logic\Service;
class PayProvider extends Service
{
public function handle()
{
$alipay = $this->getSettings('pay.alipay');
$wxpay = $this->getSettings('pay.wxpay');
return [
'alipay' => ['enabled' => $alipay['enabled']],
'wxpay' => ['enabled' => $wxpay['enabled']],
];
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Services\Logic\User\Console;
use App\Services\Logic\Service;
use App\Validators\Connect as ConnectValidator;
class ConnectDelete extends Service
{
public function handle($id)
{
$user = $this->getLoginUser();
$validator = new ConnectValidator();
$connect = $validator->checkConnect($id);
$validator->checkOwner($user->id, $connect->user_id);
$connect->deleted = 1;
$connect->update();
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Services\Logic\User\Console;
use App\Repos\Connect as ConnectRepo;
use App\Services\Logic\Service;
class ConnectList extends Service
{
public function handle()
{
$user = $this->getLoginUser();
$params = [
'user_id' => $user->id,
'deleted' => 0,
];
$connectRepo = new ConnectRepo();
$connects = $connectRepo->findAll($params);
if ($connects->count() == 0) {
return [];
}
$items = [];
foreach ($connects as $connect) {
$items[] = [
'id' => $connect->id,
'open_id' => $connect->open_id,
'open_name' => $connect->open_name,
'open_avatar' => $connect->open_avatar,
'provider' => $connect->provider,
'create_time' => $connect->create_time,
'update_time' => $connect->update_time,
];
}
return $items;
}
}

107
app/Services/OAuth.php Normal file
View File

@ -0,0 +1,107 @@
<?php
namespace App\Services;
use GuzzleHttp\Client as HttpClient;
use Phalcon\Crypt;
use Phalcon\Di;
abstract class OAuth extends Service
{
/**
* @var string
*/
protected $clientId;
/**
* @var string
*/
protected $clientSecret;
/**
* @var string
*/
protected $redirectUri;
/**
* @var string
*/
protected $accessToken;
/**
* @var string
*/
protected $openId;
public function __construct($clientId, $clientSecret, $redirectUri)
{
$this->clientId = $clientId;
$this->clientSecret = $clientSecret;
$this->redirectUri = $redirectUri;
}
public function httpGet($uri, $params = [], $headers = [])
{
$client = new HttpClient();
$options = ['query' => $params, 'headers' => $headers];
$response = $client->get($uri, $options);
return $response->getBody();
}
public function httpPost($uri, $params = [], $headers = [])
{
$client = new HttpClient();
$options = ['query' => $params, 'headers' => $headers];
$response = $client->post($uri, $options);
return $response->getBody();
}
public function getState()
{
/**
* @var $crypt Crypt
*/
$crypt = Di::getDefault()->get('crypt');
return $crypt->encryptBase64(rand(1000, 9999));
}
public function checkState($state)
{
/**
* 注意事项:
* callback中的state参数并未做encode处理参数中含有"+"
* 获取参数的时候却自动做了decode处理"+"变成了空格
*/
$state = str_replace(' ', '+', $state);
/**
* @var $crypt Crypt
*/
$crypt = Di::getDefault()->get('crypt');
$value = $crypt->decryptBase64($state);
if ($value < 1000 || $value > 9999) {
throw new \Exception('Invalid OAuth State Value');
}
return true;
}
abstract public function getAuthorizeUrl();
abstract public function getAccessToken($code);
abstract public function getOpenId($accessToken);
abstract public function getUserInfo($accessToken, $openId);
}

View File

@ -1,8 +1,9 @@
<?php
namespace App\Library\OAuth;
namespace App\Services\OAuth;
use App\Library\OAuth;
use App\Models\Connect as ConnectModel;
use App\Services\OAuth;
class QQ extends OAuth
{
@ -15,12 +16,13 @@ class QQ extends OAuth
public function getAuthorizeUrl()
{
$params = [
'client_id' => $this->appId,
'client_id' => $this->clientId,
'redirect_uri' => $this->redirectUri,
'state' => $this->getState(),
'response_type' => 'code',
'scope' => '',
'scope' => 'get_user_info',
];
return self::AUTHORIZE_URL . '?' . http_build_query($params);
}
@ -28,86 +30,85 @@ class QQ extends OAuth
{
$params = [
'code' => $code,
'client_id' => $this->appId,
'client_secret' => $this->appSecret,
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'redirect_uri' => $this->redirectUri,
'grant_type' => 'authorization_code',
'state' => 'ok',
];
$response = $this->httpPost(self::ACCESS_TOKEN_URL, $params);
$this->accessToken = $this->parseAccessToken($response);
return $this->accessToken;
}
public function getOpenId($accessToken)
{
$params = ['access_token' => $accessToken];
$response = $this->httpGet(self::OPENID_URL, $params);
$this->openId = $this->parseOpenId($response);
return $this->openId;
}
public function getUserInfo($accessToken, $openId)
{
$params = [
'oauth_consumer_key' => $this->clientId,
'access_token' => $accessToken,
'openid' => $openId,
'oauth_consumer_key' => $this->appId,
];
$response = $this->httpGet(self::USER_INFO_URL, $params);
$this->parseUserInfo($response);
return $this->parseUserInfo($response);
}
protected function parseAccessToken($response)
{
$result = [];
parse_str($response, $result);
if (!isset($result['access_token'])) {
throw new \Exception("Fetch Access Token Failed:{$response}");
}
return $result['access_token'];
}
protected function parseOpenId($response)
{
$result = $match = [];
$result = $matches = [];
if (!empty($response)) {
preg_match('/callback\(\s+(.*?)\s+\)/i', $response, $match);
$result = json_decode($match[1], true);
preg_match('/callback\(\s+(.*?)\s+\)/i', $response, $matches);
$result = json_decode($matches[1], true);
}
if (!isset($result['openid'])) {
throw new \Exception("Fetch Openid Failed:{$response}");
throw new \Exception("Fetch OpenId Failed:{$response}");
}
return $result['openid'];
}
protected function parseUserInfo($response)
{
$data = json_decode($response, true);
if ($data['ret'] != 0) {
throw new \Exception("Fetch User Info Failed{$data['msg']}");
if (isset($data['ret']) && $data['ret'] != 0) {
throw new \Exception("Fetch User Info Failed:{$response}");
}
$userInfo['type'] = 'QQ';
$userInfo['id'] = $this->openId;
$userInfo['name'] = $data['nickname'];
$userInfo['nick'] = $data['nickname'];
$userInfo['head'] = $data['figureurl_2'];
$userInfo['avatar'] = $data['figureurl'];
$userInfo['provider'] = ConnectModel::PROVIDER_QQ;
return $userInfo;
}

View File

@ -1,24 +1,26 @@
<?php
namespace App\Library\OAuth;
namespace App\Services\OAuth;
use App\Library\OAuth;
use App\Models\Connect as ConnectModel;
use App\Services\OAuth;
class WeiBo extends OAuth
{
const AUTHORIZE_URL = 'https://api.weibo.com/oauth2/authorize';
const ACCESS_TOKEN_URL = 'https://api.weibo.com/oauth2/oauth2/access_token';
const ACCESS_TOKEN_URL = 'https://api.weibo.com/oauth2/access_token';
const USER_INFO_URL = 'https://api.weibo.com/2/users/show.json';
public function getAuthorizeUrl()
{
$params = [
'client_id' => $this->appId,
'client_id' => $this->clientId,
'redirect_uri' => $this->redirectUri,
'state' => $this->getState(),
'response_type' => 'code',
];
return self::AUTHORIZE_URL . '?' . http_build_query($params);
}
@ -26,16 +28,16 @@ class WeiBo extends OAuth
{
$params = [
'code' => $code,
'client_id' => $this->appId,
'client_secret' => $this->appSecret,
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'redirect_uri' => $this->redirectUri,
'grant_type' => 'authorization_code',
];
$response = $this->httpPost(self::ACCESS_TOKEN_URL, $params);
$this->accessToken = $this->parseAccessToken($response);
return $this->accessToken;
}
@ -50,38 +52,38 @@ class WeiBo extends OAuth
'access_token' => $accessToken,
'uid' => $openId,
];
$response = $this->httpGet(self::USER_INFO_URL, $params);
return $this->parseUserInfo($response);
}
private function parseAccessToken($response)
{
$data = json_decode($response, true);
if (!isset($data['access_token']) || isset($data['uid'])) {
if (!isset($data['access_token']) || !isset($data['uid'])) {
throw new \Exception("Fetch Access Token Failed:{$response}");
}
$this->openId = $data['uid'];
return $data['access_token'];
}
private function parseUserInfo($response)
{
$data = json_decode($response, true);
if ($data['error_code'] != 0) {
throw new \Exception("Fetch User Info Failed:{$data['error']}");
if (isset($data['error_code']) && $data['error_code'] != 0) {
throw new \Exception("Fetch User Info Failed:{$response}");
}
$userInfo['type'] = 'WEIBO';
$userInfo['id'] = $data['id'];
$userInfo['name'] = $data['name'];
$userInfo['nick'] = $data['screen_name'];
$userInfo['head'] = $data['avatar_large'];
$userInfo['avatar'] = $data['profile_image_url'];
$userInfo['provider'] = ConnectModel::PROVIDER_WEIBO;
return $userInfo;
}

View File

@ -1,8 +1,9 @@
<?php
namespace App\Library\OAuth;
namespace App\Services\OAuth;
use App\Library\OAuth;
use App\Models\Connect as ConnectModel;
use App\Services\OAuth;
class WeiXin extends OAuth
{
@ -14,13 +15,13 @@ class WeiXin extends OAuth
public function getAuthorizeUrl()
{
$params = [
'appid' => $this->appId,
'appid' => $this->clientId,
'redirect_uri' => $this->redirectUri,
'state' => $this->getState(),
'response_type' => 'code',
'scope' => 'snsapi_login',
'state' => 'dev',
];
return self::AUTHORIZE_URL . '?' . http_build_query($params);
}
@ -28,15 +29,15 @@ class WeiXin extends OAuth
{
$params = [
'code' => $code,
'appid' => $this->appId,
'secret' => $this->appSecret,
'appid' => $this->clientId,
'secret' => $this->clientSecret,
'grant_type' => 'authorization_code',
];
$response = $this->httpPost(self::ACCESS_TOKEN_URL, $params);
$this->accessToken = $this->parseAccessToken($response);
return $this->accessToken;
}
@ -51,38 +52,38 @@ class WeiXin extends OAuth
'access_token' => $accessToken,
'openid' => $openId,
];
$response = $this->httpGet(self::USER_INFO_URL, $params);
return $this->parseUserInfo($response);
}
private function parseAccessToken($response)
{
$data = json_decode($response, true);
if (isset($data['errcode']) && $data['errcode'] != 0) {
throw new \Exception("Fetch Access Token Failed:{$data['errmsg']}");
throw new \Exception("Fetch Access Token Failed:{$response}");
}
$this->openId = $data['openid'];
return $data['access_token'];
}
private function parseUserInfo($response)
{
$data = json_decode($response, true);
if (isset($data['errcode']) && $data['errcode'] != 0) {
throw new \Exception("Fetch User Info Failed:{$data['errmsg']}");
throw new \Exception("Fetch User Info Failed:{$response}");
}
$userInfo['type'] = 'WEIXIN';
$userInfo['name'] = $data['name'];
$userInfo['nick'] = $data['screen_name'];
$userInfo['head'] = $data['avatar_large'];
$userInfo['id'] = $data['openid'];
$userInfo['name'] = $data['nickname'];
$userInfo['avatar'] = $data['headimgurl'];
$userInfo['provider'] = ConnectModel::PROVIDER_WEIXIN;
return $userInfo;
}

View File

@ -99,6 +99,7 @@ class Alipay extends PayService
'out_trade_no' => $trade->sn,
'total_amount' => $trade->amount,
'subject' => $trade->subject,
'http_method' => 'GET',
]);
} catch (\Exception $e) {

View File

@ -47,6 +47,7 @@ class AlipayGateway extends Service
'alipay_root_cert' => config_path('alipay/alipayRootCert.crt'), // 支付宝根证书
'app_cert_public_key' => config_path('alipay/appCertPublicKey.crt'), // 应用公钥证书
'notify_url' => $this->settings['notify_url'],
'return_url' => $this->settings['return_url'],
'log' => [
'file' => log_path('alipay.log'),
'level' => $level,

View File

@ -6,6 +6,7 @@ use App\Models\Refund as RefundModel;
use App\Models\Trade as TradeModel;
use App\Repos\Trade as TradeRepo;
use App\Services\Pay as PayService;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Yansongda\Pay\Gateways\Wechat;
use Yansongda\Pay\Log;
@ -90,7 +91,7 @@ class Wxpay extends PayService
* wap支付
*
* @param TradeModel $trade
* @return Response|bool
* @return RedirectResponse|bool
*/
public function wap(TradeModel $trade)
{
@ -102,11 +103,6 @@ class Wxpay extends PayService
'body' => $trade->subject,
]);
/**
* 微信H5支付会检查Referer构造Referer头信息
*/
$result->headers->set('Referer', kg_site_url());
} catch (\Exception $e) {
Log::error('Wxpay Wap Exception', [

View File

@ -21,6 +21,11 @@ class WxpayGateway extends Service
$this->settings = array_merge($defaults, $options);
}
public function setReturnUrl($returnUrl)
{
$this->settings['return_url'] = $returnUrl;
}
public function setNotifyUrl($notifyUrl)
{
$this->settings['notify_url'] = $notifyUrl;
@ -42,6 +47,7 @@ class WxpayGateway extends Service
'mch_id' => $this->settings['mch_id'],
'key' => $this->settings['key'],
'notify_url' => $this->settings['notify_url'],
'return_url' => $this->settings['return_url'],
'cert_client' => config_path('wxpay/apiclient_cert.pem'),
'cert_key' => config_path('wxpay/apiclient_key.pem'),
'log' => [

View File

@ -0,0 +1,41 @@
<?php
namespace App\Validators;
use App\Exceptions\BadRequest as BadRequestException;
use App\Repos\Connect as ConnectRepo;
class Connect extends Validator
{
public function checkConnect($id)
{
return $this->checkConnectById($id);
}
public function checkConnectById($id)
{
$connectRepo = new ConnectRepo();
$connect = $connectRepo->findById($id);
if (!$connect) {
throw new BadRequestException('connect.not_found');
}
return $connect;
}
public function checkConnectByOpenId($openId, $provider)
{
$connectRepo = new ConnectRepo();
$connect = $connectRepo->findByOpenId($openId, $provider);
if (!$connect) {
throw new BadRequestException('connect.not_found');
}
return $connect;
}
}

View File

@ -0,0 +1,92 @@
<?php
use Phinx\Db\Adapter\MysqlAdapter;
class CreateConnectTable extends Phinx\Migration\AbstractMigration
{
public function change()
{
$this->table('kg_connect', [
'id' => false,
'primary_key' => ['id'],
'engine' => 'InnoDB',
'encoding' => 'utf8mb4',
'collation' => 'utf8mb4_general_ci',
'comment' => '',
'row_format' => 'DYNAMIC',
])
->addColumn('id', 'integer', [
'null' => false,
'limit' => MysqlAdapter::INT_REGULAR,
'identity' => 'enable',
'comment' => '主键编号',
])
->addColumn('user_id', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '用户编号',
'after' => 'id',
])
->addColumn('open_id', 'string', [
'null' => false,
'default' => '',
'limit' => 50,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'comment' => '开放ID',
'after' => 'user_id',
])
->addColumn('open_name', 'string', [
'null' => false,
'default' => '',
'limit' => 30,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'comment' => '开放名称',
'after' => 'open_id',
])
->addColumn('open_avatar', 'string', [
'null' => false,
'default' => '',
'limit' => 150,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'comment' => '开放头像',
'after' => 'open_name',
])
->addColumn('provider', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '提供方',
'after' => 'open_avatar',
])
->addColumn('deleted', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '删除标识',
'after' => 'provider',
])
->addColumn('create_time', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '创建时间',
'after' => 'deleted',
])
->addColumn('update_time', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '更新时间',
'after' => 'create_time',
])
->addIndex(['open_id', 'provider'], [
'name' => 'openid_provider',
'unique' => false,
])
->create();
}
}

View File

@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class InsertOauthSettingData extends AbstractMigration
{
public function up()
{
$rows = [
[
'section' => 'oauth.qq',
'item_key' => 'enabled',
'item_value' => '0',
],
[
'section' => 'oauth.qq',
'item_key' => 'client_id',
'item_value' => '',
],
[
'section' => 'oauth.qq',
'item_key' => 'client_secret',
'item_value' => '',
],
[
'section' => 'oauth.qq',
'item_key' => 'redirect_uri',
'item_value' => '',
],
[
'section' => 'oauth.weixin',
'item_key' => 'enabled',
'item_value' => '0',
],
[
'section' => 'oauth.weixin',
'item_key' => 'client_id',
'item_value' => '',
],
[
'section' => 'oauth.weixin',
'item_key' => 'client_secret',
'item_value' => '',
],
[
'section' => 'oauth.weixin',
'item_key' => 'redirect_uri',
'item_value' => '',
],
[
'section' => 'oauth.weibo',
'item_key' => 'enabled',
'item_value' => '0',
],
[
'section' => 'oauth.weibo',
'item_key' => 'client_id',
'item_value' => '',
],
[
'section' => 'oauth.weibo',
'item_key' => 'client_secret',
'item_value' => '',
],
[
'section' => 'oauth.weibo',
'item_key' => 'redirect_uri',
'item_value' => '',
],
[
'section' => 'oauth.weibo',
'item_key' => 'refuse_uri',
'item_value' => '',
],
];
$this->table('kg_setting')->insert($rows)->save();
}
public function down()
{
$this->execute("DELETE FROM kg_setting WHERE section = 'oauth.qq'");
$this->execute("DELETE FROM kg_setting WHERE section = 'oauth.weixin'");
$this->execute("DELETE FROM kg_setting WHERE section = 'oauth.weibo'");
}
}

View File

@ -1188,7 +1188,7 @@
}
.login-wrap .link {
margin-bottom: 30px;
margin-bottom: 20px;
font-size: 12px;
text-align: center;
}
@ -1202,6 +1202,26 @@
color: #999;
}
.login-wrap .oauth {
text-align: center;
}
.login-wrap .oauth a {
margin: 0 10px;
}
.login-qq {
color: dodgerblue;
}
.login-wechat {
color: green;
}
.login-weibo {
color: red;
}
.user-profile {
position: relative;
}
@ -1537,9 +1557,12 @@
margin-right: 0;
}
.security-item-list {
margin-top: -20px;
}
.security-item {
padding: 15px;
line-height: 50px;
line-height: 80px;
border-bottom: 1px dashed #ccc;
}
@ -1563,6 +1586,29 @@
float: right;
}
.connect-list {
margin-bottom: 20px;
}
.open-avatar img {
width: 16px;
height: 16px;
border-radius: 100%;
}
.connect-tips {
color: #666;
margin-bottom: 20px;
}
.oauth-list {
margin-bottom: 20px;
}
.oauth-list a {
margin: 0 10px;
}
.order-filter {
padding: 15px 20px;
}

View File

@ -8,7 +8,7 @@ $scheduler = new Scheduler();
$script = __DIR__ . '/console.php';
$bin = '/usr/bin/php';
$bin = '/usr/local/bin/php';
$scheduler->php($script, $bin, ['--task' => 'deliver', '--action' => 'main'])
->at('*/3 * * * *');
@ -20,7 +20,7 @@ $scheduler->php($script, $bin, ['--task' => 'sync_learning', '--action' => 'main
->at('*/7 * * * *');
$scheduler->php($script, $bin, ['--task' => 'vod_event', '--action' => 'main'])
->at('*/9 * * * *');
->at('*/5 * * * *');
$scheduler->php($script, $bin, ['--task' => 'close_trade', '--action' => 'main'])
->at('*/13 * * * *');