first commit

This commit is contained in:
Mr.Qin
2022-08-19 19:48:37 +08:00
commit afdd648b65
3275 changed files with 631084 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
<?php
declare (strict_types = 1);
/**
* php构建哈希表类.
* User: Lustre
* Date: 17/3/9
* Time: 上午9:10
**/
namespace SensitiveHelper;
class HashMap
{
/**
* 哈希表变量
*
* @var array|null
*/
protected $hashTable = array();
public function __construct()
{}
/**
* 向HashMap中添加一个键值对
*
* @param $key
* @param $value
* @return mixed|null
*/
public function put($key, $value)
{
if (!array_key_exists($key, $this->hashTable)) {
$this->hashTable[$key] = $value;
return null;
}
$_temp = $this->hashTable[$key];
$this->hashTable[$key] = $value;
return $_temp;
}
/**
* 根据key获取对应的value
*
* @param $key
* @return mixed|null
*/
public function get($key)
{
if (array_key_exists($key, $this->hashTable)) {
return $this->hashTable[$key];
}
return null;
}
/**
* 删除指定key的键值对
*
* @param $key
* @return mixed|null
*/
public function remove($key)
{
$temp_table = array();
if (array_key_exists($key, $this->hashTable)) {
$tempValue = $this->hashTable[$key];
while ($curValue = current($this->hashTable)) {
if (!(key($this->hashTable) == $key)) {
$temp_table[key($this->hashTable)] = $curValue;
}
next($this->hashTable);
}
$this->hashTable = null;
$this->hashTable = $temp_table;
return $tempValue;
}
return null;
}
/**
* 获取HashMap的所有键值
*
* @return array
*/
public function keys()
{
return array_keys($this->hashTable);
}
/**
* 获取HashMap的所有value值
*
* @return array
*/
public function values()
{
return array_values($this->hashTable);
}
/**
* 将一个HashMap的值全部put到当前HashMap中
*
* @param \DfaFilter\HashMap $map
*/
public function putAll($map)
{
if (!$map->isEmpty() && $map->size() > 0) {
$keys = $map->keys();
foreach ($keys as $key) {
$this->put($key, $map->get($key));
}
}
return;
}
/**
* 移除HashMap中所有元素
*
* @return bool
*/
public function removeAll()
{
$this->hashTable = null;
return true;
}
/**
* 判断HashMap中是否包含指定的值
*
* @param $value
* @return bool
*/
public function containsValue($value)
{
while ($curValue = current($this->hashTable)) {
if ($curValue == $value) {
return true;
}
next($this->hashTable);
}
return false;
}
/**
* 判断HashMap中是否包含指定的键key
*
* @param $key
* @return bool
*/
public function containsKey($key)
{
if (array_key_exists($key, $this->hashTable)) {
return true;
} else {
return false;
}
}
/**
* 获取HashMap中元素个数
*
* @return int
*/
public function size()
{
return count($this->hashTable);
}
/**
* 判断HashMap是否为空
*
* @return bool
*/
public function isEmpty()
{
return (count($this->hashTable) == 0);
}
}

View File

@@ -0,0 +1,336 @@
<?php
declare (strict_types = 1);
/**
* 敏感词类库.
* User: Lustre
* Date: 17/3/9
* Time: 上午9:11
*/
namespace SensitiveHelper;
use app\common\controller\common\model\system\Tags;
class SensitiveHelper
{
/**
* 待检测语句长度
*
* @var int
*/
protected $contentLength = 0;
/**
* 敏感词单例
*
* @var object|null
*/
private static $_instance = null;
/**
* 敏感词库树
*
* @var HashMap|null
*/
protected $wordTree = null;
/**
* 存放待检测语句敏感词
*
* @var array|null
*/
protected static $badWordList = null;
/**
* 获取单例
*
* @return self
*/
public static function instance()
{
if (!self::$_instance instanceof self) {
self::$_instance = new self();
}
return self::$_instance;
}
/**
* 构建敏感词树【文件模式】
* @param string $filepath
* @return $this
* @throws \Exception
*/
public function setTreeByFile($filepath = null)
{
if (!file_exists($filepath)) {
throw new \Exception('没有词库');
}
// 词库树初始化
$this->wordTree = $this->wordTree ?: new HashMap();
foreach ($this->yieldToReadFile($filepath) as $word) {
$this->buildWordToTree(trim($word));
}
return $this;
}
/**
* 构建敏感词树【数组模式】
* @param null $sensitiveWords
* @return $this
* @throws \Exception
*/
public function setTree($sensitiveWords = null, bool $type = true)
{
// 默认从数据库读取
if (empty($sensitiveWords)) {
$sensitiveWords = Tags::where([
'type'=> $type,
'status'=> true,
])->column('name');
}
$this->wordTree = new HashMap();
foreach ($sensitiveWords as $word) {
$this->buildWordToTree($word);
}
return $this;
}
/**
* 检测文字中的敏感词
*
* @param string $content 待检测内容
* @param int $matchType 匹配类型 [默认为最小匹配规则]
* @param int $wordNum 需要获取的敏感词数量 [默认获取全部]
* @return array
*/
public function getBadWord($content, $matchType = 1, $wordNum = 0)
{
$this->contentLength = mb_strlen($content, 'utf-8');
$badWordList = array();
for ($length = 0; $length < $this->contentLength; $length++) {
$matchFlag = 0;
$flag = false;
$tempMap = $this->wordTree;
for ($i = $length; $i < $this->contentLength; $i++) {
$keyChar = mb_substr($content, $i, 1, 'utf-8');
// 获取指定节点树
$nowMap = $tempMap->get($keyChar);
// 不存在节点树,直接返回
if (empty($nowMap)) {
break;
}
// 存在,则判断是否为最后一个
$tempMap = $nowMap;
// 找到相应key偏移量+1
$matchFlag++;
// 如果为最后一个匹配规则,结束循环,返回匹配标识数
if (false === $nowMap->get('ending')) {
continue;
}
$flag = true;
// 最小规则,直接退出
if (1 === $matchType) {
break;
}
}
if (!$flag) {
$matchFlag = 0;
}
// 找到相应key
if ($matchFlag <= 0) {
continue;
}
$badWordList[] = mb_substr($content, $length, $matchFlag, 'utf-8');
// 有返回数量限制
if ($wordNum > 0 && count($badWordList) == $wordNum) {
return $badWordList;
}
// 需匹配内容标志位往后移
$length = $length + $matchFlag - 1;
}
return $badWordList;
}
/**
* 替换敏感字字符
*
* @param $content 文本内容
* @param string $replaceChar 替换字符
* @param bool $repeat true=>重复替换为敏感词相同长度的字符
* @param int $matchType
* @return mixed
*/
public function replace($content, $replaceChar = '', $repeat = false, $matchType = 1)
{
if (empty($content)) {
throw new \Exception('请填写检测的内容');
}
$badWordList = self::$badWordList ? self::$badWordList : $this->getBadWord($content, $matchType);
// 未检测到敏感词,直接返回
if (empty($badWordList)) {
return $content;
}
foreach ($badWordList as $badWord) {
$hasReplacedChar = $replaceChar;
if ($repeat) {
$hasReplacedChar = $this->dfaBadWordConversChars($badWord, $replaceChar);
}
$content = str_replace($badWord, $hasReplacedChar, $content);
}
return $content;
}
/**
* 标记敏感词
* @param $content 文本内容
* @param string $sTag 标签开头,如<mark>
* @param string $eTag 标签结束,如</mark>
* @param int $matchType
* @return mixed
*/
public function mark($content, $sTag, $eTag, $matchType = 1)
{
if (empty($content)) {
throw new \Exception('请填写检测的内容');
}
$badWordList = self::$badWordList ? self::$badWordList : $this->getBadWord($content, $matchType);
// 未检测到敏感词,直接返回
if (empty($badWordList)) {
return $content;
}
foreach ($badWordList as $badWord) {
$replaceChar = $sTag . $badWord . $eTag;
$content = str_replace($badWord, $replaceChar, $content);
}
return $content;
}
/**
* 被检测内容是否合法
* @param $content
* @return bool
*/
public function islegal($content)
{
$this->contentLength = mb_strlen($content, 'utf-8');
for ($length = 0; $length < $this->contentLength; $length++) {
$matchFlag = 0;
$tempMap = $this->wordTree;
for ($i = $length; $i < $this->contentLength; $i++) {
$keyChar = mb_substr($content, $i, 1, 'utf-8');
// 获取指定节点树
$nowMap = $tempMap->get($keyChar);
// 不存在节点树,直接返回
if (empty($nowMap)) {
break;
}
// 找到相应key偏移量+1
$tempMap = $nowMap;
$matchFlag++;
// 如果为最后一个匹配规则,结束循环,返回匹配标识数
if (false === $nowMap->get('ending')) {
continue;
}
return true;
}
// 找到相应key
if ($matchFlag <= 0) {
continue;
}
// 需匹配内容标志位往后移
$length = $length + $matchFlag - 1;
}
return false;
}
protected function yieldToReadFile($filepath)
{
$fp = fopen($filepath, 'r');
while (!feof($fp)) {
yield fgets($fp);
}
fclose($fp);
}
// 将单个敏感词构建成树结构
protected function buildWordToTree($word = '')
{
if ('' === $word) {
return;
}
$tree = $this->wordTree;
$wordLength = mb_strlen($word, 'utf-8');
for ($i = 0; $i < $wordLength; $i++) {
$keyChar = mb_substr($word, $i, 1, 'utf-8');
// 获取子节点树结构
$tempTree = $tree->get($keyChar);
if ($tempTree) {
$tree = $tempTree;
} else {
// 设置标志位
$newTree = new HashMap();
$newTree->put('ending', false);
// 添加到集合
$tree->put($keyChar, $newTree);
$tree = $newTree;
}
// 到达最后一个节点
if ($i == $wordLength - 1) {
$tree->put('ending', true);
}
}
return;
}
/**
* 敏感词替换为对应长度的字符
* @param $word
* @param $char
* @return string
*/
protected function dfaBadWordConversChars($word, $char)
{
$str = '';
$length = mb_strlen($word, 'utf-8');
for ($counter = 0; $counter < $length; ++$counter) {
$str .= $char;
}
return $str;
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare (strict_types = 1);
namespace WordAnalysis;
class Analysis
{
/**
* Notes:关键字提取
* @auther: xxf
* Date: 2019/8/19
* Time: 11:09
* @param string $content
* @param int $num 获取数量
* @return string
*/
public static function getKeywords(string $content = null,int $num = 2) {
if (empty ($content )) {
return '';
}
require_once 'phpanalysis.class.php';
\PhpAnalysis::$loadInit = false;
$pa = new \PhpAnalysis ( 'utf-8', 'utf-8', false );
$pa->LoadDict();
$pa->SetSource($content);
$pa->StartAnalysis(true);
return $pa->GetFinallyKeywords($num);
}
}

Binary file not shown.

View File

@@ -0,0 +1,25 @@
文件说明:
1、base_dic_full.dic
hash索引 -- 字典带有词频和词性标志。
2、words_addons.dic
s 开头的表示停止词 u 后缀词(地名后缀、数学单位等) n 前导词(姓、汉字数词等) a 后导词(地区,部门等)
3、 not-build/base_dic_full.txt
没编译过的词典源码
4、重新编译词典的方法
<?php
header('Content-Type: text/html; charset=utf-8');
require_once('phpanalysis.class.php');
$pa = new PhpAnalysis('utf-8', 'utf-8', false);
$pa->MakeDict( sourcefile, 16 , 'dict/base_dic_full.dic');
echo "OK";
?>

View File

@@ -0,0 +1,17 @@
s:停止词
并,让,才,上,被,把,近,而,是,为,由,等,合,子,除,均,很,也,称,还,分,据,后,向,经,对,但,只,则,设,靠,至,到,将,及,与,或,来,了,从,说,就,的,和,在,方,以,已,有,都,给,要
n:姓或其它专用前缀词
新,肖,胡,罗,程,施,满,石,秦,苏,范,包,袁,许,舒,薛,蒋,董,白,田,季,丁,汪,段,梁,林,杜,杨,毛,江,熊,王,潘,沈,汤,谢,谭,韩,顾,雷,陈,阎,陆,马,高,龙,龚,黎,黄,魏,钱,钟,赵,邓,赖,贾,贺,邱,邵,郭,金,郝,郑,邹,李,武,余,夏,唐,朱,何,姚,孟,孙,孔,姜,周,吴,卢,单,刘,冯,史,叶,吕,候,傅,宋,任,文,戴,徐,张,万,方,曾,曹,易,廖,彭,常,尹,乔,于,康,崔,布,钟离,令狐,公冶,公孙,闻人,鲜于,上官,仲孙,万俟,东方,闾丘,长孙,诸葛,申屠,皇甫,尉迟,濮阳,澹台,欧阳,慕容,淳于,宗政,宇文,司徒,轩辕,单于,赫连,司空,太叔,夏侯,司马,公羊,勿,成吉,埃,哈
u:单位或专用后缀词
u‰,℃,℉,毛,段,步,毫,池,滴,派,洲,款,次,桩,档,桌,桶,梯,楼,棵,炮,点,盏,盆,界,盒,盘,眼,画,男,环,版,片,班,瓣,生,瓶,案,格,族,方,斤,日,时,期,月,曲,斗,文,指,拳,拨,掌,排,丈,撮,本,朵,栋,柜,柄,栏,株,根,样,架,枪,条,束,村,杯,枝,枚,石,码,辈,辆,轮,连,通,里,部,遍,转,车,言,角,袋,课,起,路,趟,重,针,项,顷,顶,顿,颗,首,餐,页,集,锅,钱,钟,门,间,隅,队,行,节,筐,笔,筒,箱,篮,篓,篇,章,站,磅,碟,碗,种,科,窝,秒,簇,米,脚,股,群,船,艇,色,艘,罐,级,粒,类,组,维,缸,缕,招,支,发,双,厘,口,句,台,只,厅,卷,包,勺,匙,匹,升,区,叶,号,地,圈,圆,场,块,堆,坪,团,回,吨,名,拍,员,周,副,剑,代,付,件,伏,份,人,亩,世,下,两,个,串,伙,位,划,分,列,则,剂,刻,刀,出,倍,例,元,克,册,具,声,听,幅,帧,房,批,师,岁,尾,尺,局,层,届,手,壶,成,张,截,户,扇,年,度,座,尊,幢,室,寸,头,宗,字,孔,所,女,套,拉,家,处,折,天,把,夜,担,號,个月,公斤,公分,公克,公担,公亩,公升,公尺,像素,月份,盎司,位数,公里,年级,点钟,克拉,英亩,平方,加仑,公顷,秒钟,千克,世纪,千米,分钟,海里,英寸,英尺,英里,年代,周年,小时,阶段,平米,立方米,立方码,平方米,平方码,平方厘米,立方英寸,立方厘米,立方分米,立方公尺,立方英尺,平方公尺,平方英尺,平方英寸,平方分米,平方公里,平方英里,百位,十位,百次,千次,千名,千亩,千里,千人,千台,千位,万次,万元,万里,万位,万件,万单,万个,万台,万名,万人,亿元,亿,万,千,萬
a:地名等后置词
语,署,苑,街,省,湖,乡,海,观,路,娃,山,阁,部,镇,江,河,厅,郡,厂,楼,园,区,党,井,亭,塔,县,家,市,弄,巷,寺,局,中路,村委,诺夫,斯基,维奇,村委会,机,型,率
c:数量前缀词
零,一,二,三,四,五,六,七,八,九,十,百,千,万,亿,第,半,几,俩,卅,两,壹,贰,叁,肆,伍,陆,柒,捌,玖,拾,伯,仟
t:省会等专用词
京,津,沪,渝,冀,豫,云,辽,黑,湘,皖,鲁,新,苏,浙,赣,鄂,桂,甘,晋,蒙,陕,吉,闽,贵,粤,青,藏,川,宁,琼

File diff suppressed because it is too large Load Diff

4
extend/conf/.htaccess Normal file
View File

@@ -0,0 +1,4 @@
<IfModule mod_rewrite.c>
RewriteCond % !^$
RewriteRule ^.*\.(php|php3|php4|php5|php7|pht|phtml|asp|aspx|jsp|exe) - [F]
</IfModule>

BIN
extend/conf/ip/ip2region.db Normal file

Binary file not shown.

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex>
<volist name="list_map" id="vo">
<sitemap>
<loc>{$vo.url}</loc>
<lastmod>{$vo.createtime}</lastmod>
</sitemap>
</volist>
</sitemapindex>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset>
<volist name="listmap" id="vo">
<php>echo '<'.'url'.'>';</php>
<loc>{$vo.url}</loc>
<lastmod>{$vo.createtime}</lastmod>
<changefreq>always</changefreq>
<priority>1.0</priority>
<php>echo '</'.'url'.'>';</php>
</volist>
</urlset>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>{$site_http}</loc>
<lastmod>{:date('Y-m-d',time())}</lastmod>
<changefreq>hourly</changefreq>
<priority>1.0</priority>
</url>
<volist name="list_map" id="vo">
<url>
<loc>{$vo.url}</loc>
<lastmod>{$vo.createtime}</lastmod>
<changefreq>daily</changefreq>
<priority>0.8</priority>
</url>
</volist>
</urlset>

52
extend/conf/sms/sms.php Normal file
View File

@@ -0,0 +1,52 @@
<?php
return array(
/*
* 短信模板配置
*/
'alisms' => array(
'register' => array(
'name' => '用户注册',
'auto' => true,
'template' => 'SMS_206595526'
),
'forgot' => array(
'name' => '找回密码',
'auto' => true,
'template' => 'SMS_206854581'
),
'change' => array(
'name' => '修改信息',
'auto' => true,
'template' => 'SMS_206595526'
),
'notice' => array(
'name' => '消息通知',
'auto' => false,
'template' => 'SMS_206854581'
)
),
'tensms' => array(
'register' => array(
'name' => '用户注册',
'auto' => true,
'template' => '1361749'
),
'forgot' => array(
'name' => '找回密码',
'auto' => true,
'template' => '1361742'
),
'change' => array(
'name' => '修改信息',
'auto' => true,
'template' => '1401023'
),
'notice' => array(
'name' => '消息通知',
'auto' => false,
'template' => '901315'
)
),
// TODO...
);

View File

@@ -0,0 +1,25 @@
<div style="background-color:#ECECEC; padding: 35px;">
<table cellpadding="0" align="center" style="width: 80%; margin: 0px auto; text-align: left; position: relative; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; font-size: 14px; font-family:微软雅黑, 黑体; line-height: 1.5; box-shadow: rgb(153, 153, 153) 0px 0px 5px; border-collapse: collapse; background-position: initial initial; background-repeat: initial initial;background:#fff;">
<tbody>
<tr>
<th valign="middle" style="height: 25px; line-height: 25px; padding: 15px 35px; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #C46200; background-color: #e01b3c; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px;">
<font face="微软雅黑" size="5" style="color: rgb(255, 255, 255); ">{site_name}</font></th>
</tr>
<tr>
<td>
<div style="padding:25px 35px 40px; background-color:#fff;">
<h2 style="margin: 5px 0px; ">
<font color="#333333" style="line-height: 20px; ">
<font style="line-height: 22px; " size="4">亲爱的用户:</font></font>
</h2>
<p style="font-size:14px;">
验证码为 {code}, 10分钟有效<br/>
如果不是您自己的操作,请忽略!
</p><br/>
<p align="right" style="font-size:14px;">{site_name} 官方团队</p>
<p align="right" style="font-size:14px;">{time}</p></div>
</td>
</tr>
</tbody>
</table>
</div>

41
extend/conf/tpl/close.tpl Normal file
View File

@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>您访问的站点已被关闭</title>
<meta name="renderer" content="webkit" />
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no" />
<style>
*{ padding:0; margin:0; box-sizing: border-box;font-family: "微软雅黑"; }
body,html{ width:100%; height:100%; }
body {background: #f2f2f2;font-family: -apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;}
img{ -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; }
.container{ max-width:90%; margin:10% auto; padding:80px 0px; }
.bg{ display:block; max-width:100%; margin:0px auto; text-align: center;}
.btn{ width:400px; margin:0 auto; max-width:100%; margin-top:40px; }
.btn a{ float:left; text-decoration: none; width:46.5%; border:1px solid #5298ff; background:#5298ff; color:#FFF; display:block; height:46px; line-height:44px; text-align:center; font-size:16px; border-radius:3px; overflow: hidden; }
.btn .goindex{ margin-right:7%; }
.btn .lx{ border: 1px solid #d8d8d8; background: #ffffff; color: #8c8c8c; }
.btn .lx:hover{ border:1px solid #5298ff; background:#5298ff; color:#FFF; }
.font {font-size: 50px;font-weight: 600;margin-bottom: 80px;}
@media screen and (max-width: 500px) {
.btn{ width:85%; }
.btn a{ width:100%; font-size:15px; height:42px; line-height:42px; }
.btn .goindex{ margin-right:0; margin-bottom:20px; }
}
</style>
</head>
<body>
<div class="container">
<div class="bg">
<div class="font">网站维护中</div>
<div>{text}</div>
</div>
<div class="btn">
<a href="//www.swiftadmin.net" class="goindex" target="_blank">开发者</a>
<a href="mailto:#email" title="发送邮件" class="lx">联系站长</a>
<div style="clear: both;"></div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,26 @@
<div style="background-color:#ECECEC; padding: 35px;">
<table cellpadding="0" align="center" style="width: 80%; margin: 0px auto; text-align: left; position: relative; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; font-size: 14px; font-family:微软雅黑, 黑体; line-height: 1.5; box-shadow: rgb(153, 153, 153) 0px 0px 5px; border-collapse: collapse; background-position: initial initial; background-repeat: initial initial;background:#fff;">
<tbody>
<tr>
<th valign="middle" style="height: 25px; line-height: 25px; padding: 15px 35px; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #C46200; background-color: #e01b3c; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px;">
<font face="微软雅黑" size="5" style="color: rgb(255, 255, 255); ">{site_name}</font></th>
</tr>
<tr>
<td>
<div style="padding:25px 35px 40px; background-color:#fff;">
<h2 style="margin: 5px 0px; ">
<font color="#333333" style="line-height: 20px; ">
<font style="line-height: 22px; " size="4">亲爱的用户:</font></font>
</h2>
<p style="font-size:14px;">
找回密码链接请点击:{url} 10分钟有效<br/>
如果您有什么疑问可以联系管理员Email: {email}
</p><br/>
<p align="right" style="font-size:14px;">{site_name} 官方团队</p>
<p align="right" style="font-size:14px;">{time}</p></div>
</td>
</tr>
</tbody>
</table>
</div>

View File

@@ -0,0 +1,26 @@
<div style="background-color:#ECECEC; padding: 35px;">
<table cellpadding="0" align="center" style="width: 80%; margin: 0px auto; text-align: left; position: relative; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; font-size: 14px; font-family:微软雅黑, 黑体; line-height: 1.5; box-shadow: rgb(153, 153, 153) 0px 0px 5px; border-collapse: collapse; background-position: initial initial; background-repeat: initial initial;background:#fff;">
<tbody>
<tr>
<th valign="middle" style="height: 25px; line-height: 25px; padding: 15px 35px; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #C46200; background-color: #e01b3c; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px;">
<font face="微软雅黑" size="5" style="color: rgb(255, 255, 255); ">{site_name}</font></th>
</tr>
<tr>
<td>
<div style="padding:25px 35px 40px; background-color:#fff;">
<h2 style="margin: 5px 0px; ">
<font color="#333333" style="line-height: 20px; ">
<font style="line-height: 22px; " size="5">亲爱的 {username}</font></font>
</h2>
<p style="font-size:14px;">
感谢您加入{site_name} 请您在发表言论时,遵守当地法律法规。<br/>
请点击链接激活:{url} 10分钟有效<br/>
如果您有什么疑问可以联系管理员Email: {email}
</p><br/>
<p align="right" style="font-size:14px;">{site_name} 官方团队</p>
<p align="right" style="font-size:14px;">{time}</p></div>
</td>
</tr>
</tbody>
</table>
</div>

12
extend/conf/tpl/tpl.php Normal file
View File

@@ -0,0 +1,12 @@
<?php
return array (
'1'=>array(
'name'=>'用户注册',
'path'=>'extend/conf/tpl/register.tpl'
),
'2'=>array(
'name'=>'找回密码',
'path'=>'extend/conf/tpl/forgot.tpl'
),
// TODO...
);

1
extend/conf/version.txt Normal file
View File

@@ -0,0 +1 @@
swiftadmin dev

151
extend/system/File.php Normal file
View File

@@ -0,0 +1,151 @@
<?php
namespace system;
/**
* 文件操作类
* @author meystack
*/
class File
{
/**
* 递归创建文件夹
* @param string $dirs
*/
public static function mkDirs(string $dirs)
{
if (!is_dir($dirs)) {
self::mkDirs(dirname($dirs));
mkdir($dirs, 0755);
}
}
/**
* 递归删除文件夹
* @param string $dirs
* @return mixed
*/
public static function rmDirs(string $dirs)
{
if (!is_dir($dirs)) {
return false;
}
$files = scandir($dirs);
foreach ($files as $file) {
if ($file == '.' || $file == '..') {
continue;
}
if (is_dir($dirs . '/' . $file)) {
self::rmDirs($dirs . '/' . $file);
} else {
unlink($dirs . '/' . $file);
}
}
rmdir($dirs);
}
/**
* 获取当前文件夹大小
* @param string $dirs
* @return mixed
*/
public static function getDirSize(string $dirs)
{
$handle = opendir($dirs);
$size = 0;
while (false !== ($FolderOrFile = readdir($handle))) {
if ($FolderOrFile != "." && $FolderOrFile != "..") {
if (is_dir("$dirs/$FolderOrFile")) {
$size += self::getDirSize("$dirs/$FolderOrFile");
} else {
$size += filesize("$dirs/$FolderOrFile");
}
}
}
closedir($handle);
return $size;
}
/**
* 获取文件夹文件列表
* @param string $dirs
* @return array
*/
public static function getDirFile(string $dirs): array
{
$handle = opendir($dirs);
$file = [];
while (false !== ($FolderOrFile = readdir($handle))) {
if ($FolderOrFile != "." && $FolderOrFile != "..") {
if (is_dir("$dirs/$FolderOrFile")) {
$file[] = self::getDirFile("$dirs/$FolderOrFile");
} else {
$file[] = "$dirs/$FolderOrFile";
}
}
}
closedir($handle);
return $file;
}
/**
* 返回 [app, public] 的路径
* @param string $name
* @return array
*/
public static function getCopyDirs(string $name): array
{
return [
plugin_path($name) . 'app',
plugin_path($name) . 'public'
];
}
/**
* 文件比较
* @param $source
* @param $destFileOrPath
* @param string $prefix
* @param bool $onlyFiles
* @return mixed
*/
public static function mutexCompare($source, $destFileOrPath, string $prefix = '', bool $onlyFiles = false): array
{
$list = [];
$destFileOrPath = $destFileOrPath ?: root_path();
if (!is_array($source) && is_file($source) && is_file($destFileOrPath)) {
return md5_file($source) !== md5_file($destFileOrPath);
}
foreach ($source as $filesPath) {
if (is_dir($filesPath)) {
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($filesPath, \FilesystemIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $file) {
if ($file->isFile()) {
$filePath = $file->getPathname();
$appPath = str_replace($prefix, '', $filePath);
$destPath = $destFileOrPath . $appPath;
if ($onlyFiles) {
if (is_file($destPath)) {
if (md5_file($filePath) != md5_file($destPath)) {
$list[] = $appPath;
}
}
} else {
$list[] = $appPath;
}
}
}
}
}
return $list;
}
}

659
extend/system/Form.php Normal file
View File

@@ -0,0 +1,659 @@
<?php
declare(strict_types=1);
// +----------------------------------------------------------------------
// | swiftAdmin 极速开发框架 [基于WebMan开发]
// +----------------------------------------------------------------------
// | Copyright (c) 2020-2030 http://www.swiftadmin.net
// +----------------------------------------------------------------------
// | swiftAdmin.net High Speed Development Framework
// +----------------------------------------------------------------------
// | Author: meystack <coolsec@foxmail.com> Apache2 License
// +----------------------------------------------------------------------
namespace system;
use think\Facade;
/**
* 表单生成器
* SAPHP框架专用
*/
class Form extends Facade
{
protected static function getFacadeClass()
{
return 'system\FormBuilder';
}
}
class FormBuilder
{
/**
* Item宽度
*
* @var integer
*/
public $width = 100;
/**
* 标签宽度
*
* @var integer
*/
public $labelwidth = 110;
/**
* 公用属性
*
* @var array
*/
public $attrs = [
'type',
'name',
'min',
'max',
'maxlength',
'required',
'readonly',
'disabled',
'placeholder',
];
public $replace = [];
/**
* @var object 对象实例
*/
protected static $instance = null;
/**
* 表单类型
*
* @var boolean
*/
protected $formtype = true;
/**
* 类构造函数
* class constructor.
*/
public function __construct()
{}
/**
* 初始化
* @access public
* @param array $options 参数
* @return object|FormBuilder|null
*/
public static function instance(array $options = [])
{
if (is_null(self::$instance)) {
self::$instance = new static($options);
}
// 返回实例
return self::$instance;
}
/**
* 开始生成元素
*
* @param array $data
* @param bool $formType
* @return string
*/
public function itemElem(array $data = [], bool $formType = true): string
{
$this->formtype = $formType;
if ($data['tag'] == 'tab') {
return $this->tab($data);
}
if ($data['tag'] == 'grid') {
return $this->grid($data);
}
$itemHtml = '<div class="layui-form-item" ';
if (isset($data['width']) && $data['width']) {
if ($data['width'] != $this->width) {
$itemHtml .= 'style="width:' . $data['width'] . '%;"';
}
}
$itemHtml .= '>' . PHP_EOL;
if (isset($data['label'])) {
$itemHtml .= $this->label($data['label'], $data) . PHP_EOL;
}
$itemHtml .= $this->block($data) . PHP_EOL;
$itemHtml .= '</div>' . PHP_EOL;
return $itemHtml;
}
/**
* 生成Label标签
*
* @param string $text
* @param array $data
* @return string
*/
public function label(string $text, array $data = []): string
{
$label = '<label class="layui-form-label';
if ($data['labelhide']) {
$label .= ' layui-hide';
}
$label .= '"';
if ($data['labelwidth'] && $data['labelwidth'] != $this->labelwidth) {
$label .= ' style="width:' . $data['labelwidth'] . 'px;"';
}
$label .= '>';
if (isset($data['required']) && $data['required']) {
$label .= '<font color="red">* </font>';
}
return $label .= $text . '</label>';
}
/**
* 生成BLOCK区块
*
* @param array $data
* @return string
*/
public function block(array $data = []): string
{
$block = '<div class="layui-input-block"';
if (isset($data['labelhide'])) {
if ($data['labelhide']) {
$style = 'margin-left:0';
} else {
if ($data['labelwidth'] && $data['labelwidth'] != $this->labelwidth) {
$style = 'margin-left:' . ($data['labelwidth'] + 30) . 'px';
}
}
}
if (isset($style)) {
$block .= ' style="' . $style . '"';
}
$block .= '>';
$block .= call_user_func([Form::instance(), $data['tag']], $data);
$block .= '</div>';
return $block;
}
/**
* 获取input
*
* @param array $data
* @return string
*/
public function input(array $data = []): string
{
$value = $this->formtype ? 'value="{$data.' . $data['name'] . '}"' : '';
return '<input class="layui-input" ' . $this->attributes($data) . $value . ' >';
}
/**
* 获取多行编辑
*
* @param array $data
* @return string
*/
public function textarea(array $data = []): string
{
$value = $this->formtype ? '{$data.' . $data['name'] . '}' : '';
return '<textarea class="layui-textarea"' . $this->attributes($data) . ' >' . $value . '</textarea>';
}
/**
* 获取单选框
*
* @param array $data
* @return string|string[]
* @throws \Exception
*/
public function radio(array $data = [])
{
return $this->radioCheckSelect($data,'radio');
}
/**
* 获取多选框
*
* @param array $data
* @return string|string[]
* @throws \Exception
*/
public function checkbox(array $data = [])
{
if (!$this->formtype) {
throw new \Exception('多选框不支持生成内置表单');
}
return $this->radioCheckSelect($data,'checkbox','[]');
}
/**
* 获取下拉框
*
* @param array $data
* @return string|string[]
* @throws \Exception
*/
public function select(array $data = [])
{
return $this->radioCheckSelect($data,'select');
}
/**
* 验证选项
*
* @param array $options
* @return string|string[]|null
* @throws \Exception
*/
public function validOptions(array $options = [])
{
if (!is_array($options) || !$options) {
throw new \Exception("Options is Empty", 1);
}
$export = var_exports($options, true);
return preg_replace('/\s+/', '', $export);
}
/**
* 获取PHP代码
*
* @param [type] $argc
* @param [type] $options
* @return string
*/
public function getVarPHPList($argc = null, $options = null): string
{
return PHP_EOL . "<php>$$argc = $options;</php>";
}
/**
* 获取模板
*
* @param array $data
* @param string $type
* @param string $attr
* @return string|string[]
* @throws \Exception
*/
public function radioCheckSelect(array $data = [], string $type = '', string $attr = '' )
{
$options = $this->validOptions($data['options']);
$varName = ucfirst($data['name']).'_LIST';
$getAttr = $this->attributes($data,$attr);
$varHtml = $this->getVarPHPList($varName, $options);
$varHtml .= read_file($this->getHtmlTpl($type));
$this->replace = [
'varlist' => $varName,
'field' => $data['name'],
'attributes' => $getAttr,
];
foreach ($this->replace as $key => $value) {
$varHtml = str_replace("{%$key%}", $value, $varHtml);
}
return $varHtml;
}
/**
* 获取日期
*
* @param array $data
* @return string
*/
public function date(array $data = []): string
{
$value = $this->formtype ? 'value="{$data.' . $data['name'] . '}"' : '';
return '<input class="layui-input" lay-datetime="" ' . $this->attributes($data) . $value . ' >';
}
/**
* 获取颜色选择器
*
* @param array $data
* @return string
* @throws \Exception
*/
public function colorpicker(array $data = []): string
{
$value = $this->formtype ? 'value="{$data.' . $data['name'] . '}"' : '';
if (!$this->formtype) {
throw new \Exception('颜色选择器不支持生成内置表单');
}
return <<<Eof
<input class="layui-input layui-hide" {$this->attributes($data)} {$value} >
<div lay-colorpicker="{$data['name']}"></div>
Eof;
}
/**
* 获取滑块
*
* @param array $data
* @return string
*/
public function slider(array $data = []): string
{
$value = $this->formtype ? 'value="{$data.' . $data['name'] . '}"' : '';
return <<<Eof
<input class="layui-input layui-hide" name="{$data['name']}" {$value} >
<div class="lay-slider" lay-slider="{$data['name']}" {$this->attributes($data)} ></div>
Eof;
}
/**
* 获取评分
*
* @param array $data
* @return string
* @throws \Exception
*/
public function rate(array $data = []): string
{
$value = $this->formtype ? 'value="{$data.' . $data['name'] . '}"' : '';
if (!$this->formtype) {
throw new \Exception("评分组件不支持生成内置表单");
}
return <<<Eof
<input class="layui-input layui-hide" name="{$data['name']}" {$value} >
<div lay-rate="{$data['name']}" {$this->attributes($data)} ></div>
Eof;
}
/**
* 获取开关
*
* @param array $data
* @return string
*/
public function switch(array $data = []): string
{
$value = $this->formtype ? 'value="{$data.' . $data['name'] . '}"' : '';
$param = '$data.' . $data['name'];
return <<<Eof
<input type="hidden" type="checkbox" name="{$data['name']}" value="0" />
<input type="checkbox" name="{$data['name']}" value="1" <eq name="{$param}" value="1" > checked </eq> lay-skin="switch" />
Eof;
}
/**
* 获取级联选择器
*
* @param array $data
* @return string
* @throws \Exception
*/
public function cascader(array $data = [])
{
if (!$this->formtype) {
throw new \Exception("级联选择器不支持生成内置表单");
}
$value = 'value="{$data.' . $data['name'] . '}"';
return <<<Eof
<input type="text" id="{$data['name']}" class="layui-hide" lay-cascader="" {$this->attributes($data)} {$value} />
Eof;
}
/**
* 获取富文本
*
* @param array $data
* @return string
* @throws \Exception
*/
public function editor(array $data = []): string
{
if (!$this->formtype) {
throw new \Exception("富文本不支持生成内置表单");
}
// 非INPUT表单 值
$value = '{$data.' . $data['name'] . '}';
return <<<Eof
<textarea id="{$data['name']}" {$data['editorType']} class="layui-hide" {$this->attributes($data)} type="layui-textarea" >{$value}</textarea>
Eof;
}
/**
* 获取上传模板
*
* @param array $data
* @return false|string|string[]
* @throws \Exception
*/
public function upload(array $data = [])
{
if (!$this->formtype && ($data['uploadtype'] == 'multiple' || $data['uploadtype'] == 'images')) {
throw new \Exception("上传组件仅支持 File类型 生成内置表单");
}
$value = $this->formtype ? '{$data.' . $data['name'] . '}' : '';
$varHtml = read_file($this->getHtmlTpl($data['uploadtype']));
$this->replace = [
'value' => $value,
'field' => $data['name'],
'accept' => $data['data_accept'],
'size' => (string)$data['data_size'],
];
foreach ($this->replace as $key => $value) {
$varHtml = str_replace("{%$key%}", $value, $varHtml);
}
return $varHtml;
}
/**
* 获取TAGS模板
*
* @param array $data
* @return string
* @throws \Exception
*/
public function tags(array $data = []): string
{
$value = 'value="{$data.' . $data['name'] . '}"';
return '<input type="text" lay-tags="" id="' . $data['name'] . '" name="' . $data['name'] .'" '. $value .' class="layui-input" >';
}
/**
* 获取JSON模板
*
* @param array $data
* @return false|string|string[]
* @throws \Exception
*/
public function json(array $data = [])
{
if (!$this->formtype) {
throw new \Exception("JSON组件不支持生成内置表单");
}
$value = $this->formtype ? 'value="{$data.' . $data['name'] . '}"' : '';
$jsonHtml = read_file($this->getHtmlTpl($data['tag']));
$this->replace = [
'value' => $value,
'field' => $data['name'],
];
foreach ($this->replace as $key => $value) {
$jsonHtml = str_replace("{%$key%}", $value, $jsonHtml);
}
return $jsonHtml;
}
/**
* 获取提示器
*
* @param array $data
* @return string
*/
public function tips(array $data = []): string
{
return '<div class="layui-input-inline"><i class="layui-icon layui-icon-about" lay-tips="' . $data['msg'] . '" data-offset="' . $data['offset'] . '"></i></div>';
}
/**
* 获取便签
*
* @param array $data
* @return string
*/
public function note(array $data = []): string
{
return '<blockquote class="layui-elem-quote">' . $data['textarea'] . '</blockquote>';
}
/**
* 获取横线
*
* @param array $data
* @return string
*/
public function subtraction(array $data = []): string
{
return '<hr class="' . $data['border'] . '">';
}
/**
* 获取行高
*
* @param array $data
* @return string
*/
public function space(array $data = []): string
{
return '<div style="height:' . $data['height'] . 'px;"></div>';
}
/**
* 获取选项卡
*
* @param array $data
* @return string
*/
public function tab(array $data = []): string
{
$tabHtml = '<div id="layui-tab" id="' . $data['name'] . '" class="layui-tab layui-tab-brief">';
$tabHtml .= '<ul class="layui-tab-title">';
$tabContent = '';
foreach ($data['options'] as $key => $option) {
$tabHtml .= '<li class="' . ($option['checked'] ? 'layui-this' : '') . '">' . $option['title'] . '</li>';
$tabContent .= '<div class="layui-tab-item ' . ($option['checked'] ? 'layui-show ' : '') . '" data-index="' . $key . '">';
foreach ($data['children'][$key] as $children) {
foreach ($children as $elem) {
$tabContent .= $this->itemElem($elem);
}
}
$tabContent .= '</div>';
}
$tabHtml .= '</ul>';
$tabHtml .= '<div class="layui-tab-content">' . $tabContent . '</div>';
$tabHtml .= '</div>';
return $tabHtml;
}
/**
* 获取布局组件
*
* @param array $data
* @return string
*/
public function grid(array $data = []): string
{
$gridHtml = '<div class="layui-form-item layui-row" >';
$col = 12 / $data['column'];
for ($key=0; $key < $data['column']; $key++) {
$gridHtml .= '<div class="layui-col-md' .$col. ' layui-grid-' .$key. '" data-index="' .$key. '">';
foreach ($data['children'][$key] as $children) {
foreach ($children as $elem) {
$gridHtml .= $this->itemElem($elem);
}
}
$gridHtml .= '</div>';
}
$gridHtml .= '</div>';
return $gridHtml;
}
/**
* 获取表单属性
*
* @param array $data
* @param string $suffix
* @return string
*/
public function attributes(array $data = [], string $suffix = ''): string
{
$vars = [];
foreach ($data as $key => $elem) {
if (array_search($key, $this->attrs)) {
if (!$elem) {
continue;
}
// 单独处理NAME值
if ($key == 'name') {
$elem .= $suffix;
}
$vars[] = $key . '="' . $elem . '"';
} else {
if (strstr($key, 'lay_') || strstr($key, 'data_')) {
$_key = str_replace('_', '-', $key);
$vars[] = $_key . '="' . $elem . '"';
}
}
}
return count($vars) > 0 ? ' ' . implode(' ', $vars) : '';
}
/**
* 获取模板文件
*
* @param [type] $name
* @return string
*/
protected function getHtmlTpl($name): string
{
return __DIR__ . DIRECTORY_SEPARATOR . 'form' . DIRECTORY_SEPARATOR . $name . '.html';
}
}

105
extend/system/Http.php Normal file
View File

@@ -0,0 +1,105 @@
<?php
namespace system;
use GuzzleHttp\Client;
/**
* Http 请求类
*/
class Http
{
/**
* PC/Mobile 标识
* @var object 对象实例
*/
protected static $agent = [
'Opera/9.80 (Android 2.3.4; Linux; Opera Mobi/build-1107180945; U; en-GB) Presto/2.8.149 Version/11.10',
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
];
/**
* 发送一个POST请求
* @param string $url
* @param array $params
* @param bool $agent
* @param array $options
* @param $header
* @return mixed|string
*/
public static function post(string $url, array $params = [], bool $agent = true, array $options = [], $header = '')
{
$req = self::request($url, $params, $agent, 'POST', $options, $header);
return $req['ret'] ? $req['msg'] : '';
}
/**
* 发送一个GET请求
* @param string $url
* @param array $params
* @param bool $agent
* @param array $options
* @param $header
* @return mixed|string
*/
public static function get(string $url, array $params = [], bool $agent = true, array $options = [], $header = [])
{
$req = self::request($url, $params, $agent, 'GET', $options, $header);
return $req['ret'] ? $req['msg'] : '';
}
/**
* @param string $url
* @param array $params
* @param bool $agent
* @param string $method
* @param array $options
* @param array $header
* @return array
*/
public static function request(string $url, array $params, bool $agent, string $method = 'GET', array $options = [], array $header = []): array
{
try {
$client = self::getClient($agent, $options, $header);
$response = $client->request($method, $url, $params ? ['query' => $params] : [])->getBody()->getContents();
if (!empty($response)) {
return ['ret' => true, 'msg' => $response];
}
} catch (\Throwable $e) {
return ['ret' => false, 'msg' => $e->getMessage()];
}
return ['ret' => false, 'msg' => $response];
}
/**
* 获取访问客户端
* @param bool $agent
* @param array $options
* @param array $header
* @return mixed
*/
private static function getClient(bool $agent, array $options = [], array $header = [])
{
if (empty($options)) {
$options = [
'timeout' => 60,
'connect_timeout' => 60,
'verify' => false,
'http_errors' => false,
'headers' => [
'X-REQUESTED-WITH' => 'XMLHttpRequest',
'Referer' => dirname(request()->url()),
'User-Agent' => self::$agent[$agent]
]
];
}
if (!empty($header)) {
$options['headers'] = array_merge($options['headers'], $header);
}
return new Client($options);
}
}

146
extend/system/Random.php Normal file
View File

@@ -0,0 +1,146 @@
<?php
declare(strict_types=1);
// +----------------------------------------------------------------------
// | swiftAdmin 极速开发框架 [基于WebMan开发]
// +----------------------------------------------------------------------
// | Copyright (c) 2020-2030 http://www.swiftadmin.net
// +----------------------------------------------------------------------
// | swiftAdmin.net High Speed Development Framework
// +----------------------------------------------------------------------
// | Author: meystack <coolsec@foxmail.com> Apache2 License
// +----------------------------------------------------------------------
namespace system;
class Random
{
/**
* @var object 对象实例
*/
protected static $instance = null;
/**
* 类构造函数
* class constructor.
*/
public function __construct()
{}
/**
* 初始化
* @access public
* @param array $options 参数
* @return self
*/
public static function instance(array $options = [])
{
if (is_null(self::$instance)) {
self::$instance = new static($options);
}
// 返回实例
return self::$instance;
}
/**
* 生成大小写字母
*
* @param integer $length
* @return string
*/
public static function alpha(int $length = 6): string
{
return self::Generate('alpha', $length);
}
/**
* 生成纯数字
* @param integer $length
* @return string
*/
public static function number(int $length = 6): string
{
return self::Generate('number', $length);
}
/**
* 生成小写字母
* @param integer $length
* @return string
*/
public static function lower(int $length = 6): string
{
return self::Generate('lower', $length);
}
/**
* 生成大写字母
* @param integer $length
* @return string
*/
public static function upper(int $length = 6): string
{
return self::Generate('upper', $length);
}
/**
* 下划线随机
*
* @param integer $length
* @return string
*/
public static function alphaDash(int $length = 6): string
{
return self::Generate('alphaDash', $length);
}
/**
* 生成数字+字母
* @param integer $length
* @return string
*/
public static function alphaNum(int $length = 6): string
{
return self::Generate('alphaNum', $length);
}
/**
* 生成随机字符
* @param string $type
* @param integer $length
* @return string
*/
public static function Generate(string $type = 'alpha', int $length = 6): string
{
$config = [
'number' => '1234567890',
'lower' => 'abcdefghijklmnopqrstuvwxyz',
'upper' => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'alpha' => 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
'alphaDash' => '_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
'alphaNum' => '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
];
$letter = str_shuffle($config[$type]);
return substr($letter, 0, $length);
}
/**
* 生成订单ID
*
* @param boolean $other
* @return string
*/
public static function orderId(bool $other = false): string
{
if (!$other) {
return date('Ymd') . str_pad((string)mt_rand(1, 99999), 5, '0', STR_PAD_LEFT);
} else {
return date('Ymd') . substr(implode('', array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}
}
}

View File

@@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
// +----------------------------------------------------------------------
// | swiftAdmin 极速开发框架 [基于WebMan开发]
// +----------------------------------------------------------------------
// | Copyright (c) 2020-2030 http://www.swiftadmin.net
// +----------------------------------------------------------------------
// | swiftAdmin.net High Speed Development Framework
// +----------------------------------------------------------------------
// | Author: meystack <coolsec@foxmail.com> Apache2 License
// +----------------------------------------------------------------------
namespace system;
use FilesystemIterator;
/**
* 文件压缩类
* @author meystack
* @version 1.0
*/
class ZipArchives
{
/**
* 解压文件
* @param string $fileName
* @param string $filePath
* @param string $search
* @param bool $delete
* @return mixed
* @throws \Exception
*/
public static function unzip(string $fileName, string $filePath = '', string $search = '', bool $delete = false)
{
if (!is_file($fileName) && preg_match('/^[a-z]{3,32}/', $fileName)) {
$fileName = plugin_path() . $fileName . '.zip';
}
if (!is_file($fileName)) {
throw new \Exception(__('解压文件不存在'), -113);
}
$fileStream = '';
$filePath = $filePath ?: plugin_path();
$zip = new \ZipArchive();
if ($zip->open($fileName) !== TRUE) {
throw new \Exception(__("访问解压文件失败"), -114);
}
try {
if (!empty($search)) {
for ($i = 0; $i < $zip->numFiles; $i++) {
$filePath = str_replace('\\','/',$zip->getNameIndex($i));
$fileName = explode('/', $filePath);
if (end($fileName) == $search) {
$fileStream = $zip->getFromIndex($i);
break;
}
}
} else {
if (!is_dir($filePath)) {
@mkdir($filePath, 0755, true);
}
$zip->extractTo($filePath);
}
} catch (\Throwable $th) {
throw new \Exception("解压 " . $fileName . " 包失败", -115);
} finally {
$zip->close();
if ($delete && !$search) {
unlink($fileName);
}
}
return $search ? $fileStream : $filePath;
}
/**
* 压缩文件夹
* @param string $fileName
* @param string $filePath
* @param string $rootPath
* @return bool
* @throws \Exception
*/
public static function compression(string $fileName, string $filePath, string $rootPath = ''): bool
{
$zip = new \ZipArchive();
try {
@unlink($fileName);
$zip->open($fileName, \ZipArchive::CREATE);
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($filePath, FilesystemIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
// 默认为插件目录
$rootPath = $rootPath ?: plugin_path();
foreach ($files as $fileinfo) {
if ($fileinfo->isFile()) {
// 过滤冗余文件
$filePath = str_replace('\\','/',$fileinfo->getRealPath());
if (!in_array($fileinfo->getFilename(), ['.git', '.vscode', 'Thumbs.db'])) {
$zip->addFile($filePath, str_replace($rootPath, '', $filePath));
}
} else {
$localDir = str_replace('\\','/',$fileinfo->getPathName());
$localDir = str_replace($rootPath, '', $localDir);
$zip->addEmptyDir($localDir);
}
}
} catch (\Throwable $th) {
throw new \Exception("压缩 " . $fileName . " 包失败", -115);
} finally {
$zip->close();
}
return true;
}
}

View File

@@ -0,0 +1,92 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>系统发生错误</title>
<meta name="robots" content="noindex,nofollow" />
<style>
body{color:#333;margin:0;padding:0 20px 20px;min-height:100%;background:#edf1f4;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:"Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",,Arial,sans-serif}
h1{margin:10px 0 0;font-size:28px;font-weight:500;line-height:32px}
h2{color:#4288ce;font-weight:400;padding:6px 0;margin:6px 0 0;font-size:18px;border-bottom:1px solid #eee}
h3{margin:12px;font-size:16px;font-weight:bold}
abbr{cursor:help;text-decoration:underline;text-decoration-style:dotted}
a{color:#868686;cursor:pointer}
a:hover{text-decoration:underline}
.line-error{background:#f8cbcb}
.echo table{width:100%}
.echo pre{padding:16px;overflow:auto;font-size:85%;line-height:1.45;background-color:#f7f7f7;border:0;border-radius:3px;font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace}
.echo pre > pre{padding:0;margin:0}
.exception{margin-top:20px}
.exception .message{padding:12px;border:1px solid #ddd;border-bottom:0 none;line-height:18px;font-size:16px;border-top-left-radius:4px;border-top-right-radius:4px;font-family:Consolas,"Liberation Mono",Courier,Verdana,"微软雅黑",serif}
.exception .code{float:left;text-align:center;color:#fff;margin-right:12px;padding:16px;border-radius:4px;background:#999}
.exception .source-code{padding:6px;border:1px solid #ddd;background:#f9f9f9;overflow-x:auto}
.exception .source-code pre{margin:0}
.exception .source-code pre ol{margin:0;color:#4288ce;display:inline-block;min-width:100%;box-sizing:border-box;font-size:14px;font-family:"Century Gothic",Consolas,"Liberation Mono",Courier,Verdana,serif;padding-left:<?php echo (isset($source) && !empty($source)) ? parse_padding($source):40;?>px}
.exception .source-code pre li{border-left:1px solid #ddd;height:18px;line-height:18px}
.exception .source-code pre code{color:#333;height:100%;display:inline-block;border-left:1px solid #fff;font-size:14px;font-family:Consolas,"Liberation Mono",Courier,Verdana,"微软雅黑",serif}
.exception .trace{padding:6px;border:1px solid #ddd;border-top:0 none;line-height:16px;font-size:14px;font-family:Consolas,"Liberation Mono",Courier,Verdana,"微软雅黑",serif}
.exception .trace h2:hover{text-decoration:underline;cursor:pointer}
.exception .trace ol{margin:12px}
.exception .trace ol li{padding:2px 4px}
.exception div:last-child{border-bottom-left-radius:4px;border-bottom-right-radius:4px}
.exception-var table{width:100%;margin:12px 0;box-sizing:border-box;table-layout:fixed;word-wrap:break-word}
.exception-var table caption{text-align:left;font-size:16px;font-weight:bold;padding:6px 0}
.exception-var table caption small{font-weight:300;display:inline-block;margin-left:10px;color:#ccc}
.exception-var table tbody{font-size:13px;font-family:Consolas,"Liberation Mono",Courier,"微软雅黑",serif}
.exception-var table td{padding:0 6px;vertical-align:top;word-break:break-all}
.exception-var table td:first-child{width:28%;font-weight:bold;white-space:nowrap}
.exception-var table td pre{margin:0}
.copyright{margin-top:24px;padding:12px 0;border-top:1px solid #eee}
pre.prettyprint .pln{color:#000}
pre.prettyprint .str{color:#080}
pre.prettyprint .kwd{color:#008}
pre.prettyprint .com{color:#800}
pre.prettyprint .typ{color:#606}
pre.prettyprint .lit{color:#066}
pre.prettyprint .pun,pre.prettyprint .opn,pre.prettyprint .clo{color:#660}
pre.prettyprint .tag{color:#008}
pre.prettyprint .atn{color:#606}
pre.prettyprint .atv{color:#080}
pre.prettyprint .dec,pre.prettyprint .var{color:#606}
pre.prettyprint .fun{color:red}
.exception-container{border-radius:5px;text-align:center;box-shadow:0 0 30px rgba(99,99,99,0.06);padding:50px;background-color:#fff;width:100%;left:50%;top:50%;max-width:456px;position:absolute;margin-top:-280px;margin-left:-280px}
.exception-container .head-line{transition:color .2s linear;font-size:40px;line-height:60px;letter-spacing:-1px;color:#777}
.exception-container .subheader{transition:color .2s linear;font-size:26px;line-height:46px;color:#494949}
.exception-container .hr{height:1px;background-color:#eee;width:80%;max-width:350px;margin:23px auto}
.exception-container .context{transition:color .2s linear;font-size:16px;line-height:27px;color:#aaa}
.exception-container .buttons-container{margin-top:35px;overflow:hidden}
.exception-container .buttons-container a{transition:text-indent .2s ease-out,color .2s linear,background-color .2s linear;text-indent:0px;font-size:14px;text-transform:uppercase;text-decoration:none;color:#fff;background-color:#1890ff;border-radius:10px;padding:10px 10px;text-align:center;display:inline-block;overflow:hidden;position:relative;width:40%;margin:0px 8px 0px 8px}
.status-ico{width:72px;height:72px;line-height:72px;font-size:42px;color:#fff;text-align:center;border-radius:50%;display:inline-block;margin-bottom:24px;background-color:#52c41a!important}
.status-error{background-color:#ff4d4f!important}
@media screen and (max-width:580px){padding:30px 5%}
.head-line{font-size:36px}
.subheader{font-size:27px;line-height:37px}
.hr{margin:30px auto;width:215px}
}@media screen and (max-width:450px){padding:30px}
.head-line{font-size:32px}
.hr{margin:25px auto;width:180px}
.context{font-size:15px;line-height:22px}
.context p:nth-child(n+2){margin-top:10px}
.buttons-container{margin-top:29px}
.buttons-container a{float:none !important;width:65%;margin:0 auto;font-size:13px;padding:9px 0}
.buttons-container a:nth-child(2){margin-top:12px}
}
</style>
</head>
<body>
<div class="exception-container">
<div class="head-line"><span class="status-ico status-error">X</span></div>
<div class="subheader">服务器异常</div>
<div class="hr"></div>
<div class="context">
<p>你可以返回上一页重试,或直接向我们反馈错误报告</p>
</div>
<div class="buttons-container">
<a href="/">返回主页</a>
<a href="/">反馈错误</a>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,10 @@
<volist name="{%varlist%}" id="vo">
<input type="checkbox" {%attributes%} value="{$vo.value}" title="{$vo.title}"
<if (isset($data['id']) && $data['id']) >
<in name="$vo.value" value="$data.{%field%}">checked</in>
<else/>
<eq name="$vo.checked" value="true">checked</eq>
</if>
>
</volist>

View File

@@ -0,0 +1,8 @@
<input type="text" name="{%field%}" value="{%value%}" class="layui-input layui-input-upload {%field%}" >
<button type="button" class="layui-btn" lay-upload="{%field%}" data-type="normal" data-accept="{%accept%}" data-size="{%size%}">
<i class="layui-icon layui-icon-upload"></i> 上传
</button>
<button type="button" class="layui-btn ml10" lay-choose="{%field%}" data-type="file" >
<i class="layui-icon layui-icon-windows"></i> 选择
</button>

View File

@@ -0,0 +1,24 @@
<input class="layui-input layui-input-upload {%field%}" name="{%field%}" value="{%value%}">
<button type="button" class="layui-btn" lay-choose="{%field%}" data-type="images" >
<i class="layui-icon layui-icon-windows"></i> 选择
</button>
<div class="clear"></div>
<notempty name="$data['{%field%}']" >
<div class="layui-upload-drag layui-uplpad-image mt10" lay-upload="{%field%}" data-type="images" data-accept="{%accept%}" data-size="{%size%}">
<i class="layui-icon layui-icon-upload layui-hide"></i>
<p class="layui-hide">点击上传,或将文件拖拽到此处</p>
<div >
<hr><img src="{%value%}" class="layui-upload-dragimg {%field%}" alt="上传成功后渲染" >
<span class="layui-badge layui-upload-clear">删除</span>
</div>
</div>
<else/>
<div class="layui-upload-drag layui-uplpad-image mt10" lay-upload="{%field%}" data-type="images" data-accept="{%accept%}" data-size="{%size%}">
<i class="layui-icon layui-icon-upload"></i>
<p>点击上传,或将文件拖拽到此处</p>
<div class="layui-hide">
<hr><img src="{%value%}" class="layui-upload-dragimg {%field%}" alt="上传成功后渲染" >
<span class="layui-badge layui-upload-clear">删除</span>
</div>
</div>
</notempty>

View File

@@ -0,0 +1,21 @@
<table class="layui-table">
<thead>
<tr>
<th>名称</th>
<th>变量值</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<notempty name="data.{%field%}">
<volist name="data.{%field%}" id="vo" key="i">
<tr>
<td><input type="text" class="layui-input" name="{%field%}[key][]" value="{$key}" ></td>
<td><input type="text" class="layui-input" name="{%field%}[value][]" value="{$vo}"></td>
<td><i class="layui-icon fa-times" data-name="{%field%}" onclick="layui.admin.resetInput(this);"></i></td>
</tr>
</volist>
</notempty>
</tbody>
</table>
<button type="button" class="layui-btn layui-btn-normal layui-jsonvar-add" data-name="{%field%}">追加</button>

View File

@@ -0,0 +1,25 @@
<div class="layui-imagesbox">
<!-- // 循环输出代码 -->
<notempty name="$data['{%field%}']" >
<volist name="$data['{%field%}']" id="vo">
<div class="layui-input-inline layui-uplpad-image">
<img src="{$vo.src}" lay-image-hover >
<input type="text" name="{%field%}[{$key}][src]" class="layui-hide" value="{$vo.src}" >
<input type="text" name="{%field%}[{$key}][title]" class="layui-input" value="{$vo.title}" placeholder="图片简介">
<span class="layui-badge layui-badge-red" data-name="{%field%}" onclick="layui.admin.resetInput(this,'images');">删除</span>
</div>
</volist>
</notempty>
<div class="layui-input-inline layui-uplpad-image">
<div class="layui-upload-drag" lay-upload="{%field%}" data-type="multiple" data-accept="{%accept%}" data-size="{%size%}">
<i class="layui-icon layui-icon-upload"></i>
<p>点击上传,或将文件拖拽到此处</p>
<div class="layui-hide"></div>
</div>
<button type="button" class="layui-btn layui-btn-xs layui-btn-fluid" lay-choose="{%field%}" data-name="{%field%}" data-type="multiple">
<i class="layui-icon layui-icon-windows"></i> 选择
</button>
</div>
</div>

View File

@@ -0,0 +1,10 @@
<volist name="{%varlist%}" id="vo">
<input type="radio" {%attributes%} value="{$vo.value}" title="{$vo.title}"
<if (isset($data['id']) && $data['id']) >
<eq name="$vo.value" value="$data.{%field%}">checked</eq>
<else/>
<eq name="$vo.checked" value="true">checked</eq>
</if>
>
</volist>

View File

@@ -0,0 +1,13 @@
<select {%attributes%} >
<volist name="{%varlist%}" id="vo">
<option value="{$vo.value}"
<if (isset($data['id']) && $data['id']) >
<in name="$vo.value" value="$data.{%field%}">selected</in>
<else/>
<eq name="$vo.checked" value="true">selected</eq>
</if>
>{$vo.title}</option>
</volist>
</select>

View File

@@ -0,0 +1,146 @@
<?php
declare (strict_types = 1);
namespace system\third;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
/**
* Gitee登录类
*/
class gitee
{
const GET_AUTH_CODE_URL = "https://gitee.com/oauth/authorize";
const GET_ACCESS_TOKEN_URL = "https://gitee.com/oauth/token";
const GET_USERINFO_URL = "https://gitee.com/api/v5/user";
/**
* 配置信息
* @var array
*/
private $config = [];
/**
* Http实例
* @var Object
*/
protected $http = null;
public function __construct($options = [])
{
if ($config = saenv('gitee')) {
$this->config = array_merge($this->config, $config);
}
$this->config = array_merge($this->config, is_array($options) ? $options : []);
$this->http = new Client();
}
/**
* 用户登录
*/
public function login() {
return redirect($this->getAuthorizeUrl());
}
/**
* 获取登录地址
*/
public function getAuthorizeUrl()
{
$state = hash('sha256',uniqid((string)mt_rand()));
session('state', $state);
$queryarr = array(
"response_type" => "code",
"client_id" => $this->config['app_id'],
"redirect_uri" => $this->config['callback'],
// "scope" => 'user_info',
"state" => $state,
);
request()->isMobile() && $queryarr['display'] = 'mobile';
$url = self::GET_AUTH_CODE_URL . '?' . http_build_query($queryarr);
return $url;
}
/**
* 获取用户信息
* @param array $params
* @return array
* @throws GuzzleException
*/
public function getUserInfo(array $params = []): array
{
$params = $params ? $params : input();
if ((isset($params['state']) && $params['state'] == session('state') && isset($params['code']))) {
// 获取access_token
$data = isset($params['code']) ? $this->getAccessToken($params['code']) : $params;
$access_token = isset($data['access_token']) ? $data['access_token'] : '';
$refresh_token = isset($data['refresh_token']) ? $data['refresh_token'] : '';
$expires_in = isset($data['expires_in']) ? $data['expires_in'] : 0;
if ($access_token) {
// 获取用户信息
$queryarr = [
"access_token" => $access_token,
];
$ret = $this->http->get(self::GET_USERINFO_URL.'?'.http_build_query($queryarr))->getBody()->getContents();
$userinfo = json_decode($ret, true);
if (!$userinfo || !is_array($userinfo)) {
return [];
}
$userinfo['avatar'] = isset($userinfo['avatar_url']) ? $userinfo['avatar_url'] : '';
$userinfo['avatar'] = str_replace('http://','https://',$userinfo['avatar']);
$data = [
'access_token' => $access_token,
'refresh_token' => $refresh_token,
'expires_in' => $expires_in,
'userinfo' => $userinfo,
'id' => $userinfo['id'],
];
return $data;
}
}
return [];
}
/**
* 获取access_token
* @param string $code
* @return array
*/
public function getAccessToken($code = '')
{
if (!$code) {
return [];
}
$queryarr = array(
"grant_type" => "authorization_code",
"client_id" => $this->config['app_id'],
"client_secret" => $this->config['app_key'],
"redirect_uri" => $this->config['callback'],
"code" => $code,
);
try {
$params = $this->http->post(self::GET_ACCESS_TOKEN_URL,['query'=>$queryarr])->getBody()->getContents();
} catch (\Throwable $th) {
if (strstr($th->getMessage(),'error_description')) {
throw new \Exception('登录已过期,请重新登录');
}
}
return $params ? json_decode($params,true): [];
}
}

154
extend/system/third/qq.php Normal file
View File

@@ -0,0 +1,154 @@
<?php
declare (strict_types = 1);
namespace system\third;
use GuzzleHttp\Client;
/**
* QQ登录类
*/
class qq
{
const GET_AUTH_CODE_URL = "https://graph.qq.com/oauth2.0/authorize";
const GET_ACCESS_TOKEN_URL = "https://graph.qq.com/oauth2.0/token";
const GET_USERINFO_URL = "https://graph.qq.com/user/get_user_info";
const GET_OPENID_URL = "https://graph.qq.com/oauth2.0/me";
/**
* 配置信息
* @var array
*/
private $config = [];
/**
* Http实例
* @var Object
*/
protected $http = null;
public function __construct($options = [])
{
if ($config = saenv('qq')) {
$this->config = array_merge($this->config, $config);
}
$this->config = array_merge($this->config, is_array($options) ? $options : []);
$this->http = new Client();
}
/**
* 用户登录
*/
public function login() {
return redirect($this->getAuthorizeUrl());
}
/**
* 获取登录地址
*/
public function getAuthorizeUrl()
{
$state = hash('sha256',uniqid((string)mt_rand()));
session('state', $state);
$queryarr = array(
"response_type" => "code",
"client_id" => $this->config['app_id'],
"redirect_uri" => $this->config['callback'],
"scope" => 'get_user_info',
"state" => $state,
);
request()->isMobile() && $queryarr['display'] = 'mobile';
$url = self::GET_AUTH_CODE_URL . '?' . http_build_query($queryarr);
return $url;
}
/**
* 获取用户信息
* @param array $params
* @return array
*/
public function getUserInfo($params = [])
{
$params = $params ? $params : input();
if (isset($params['access_token']) || (isset($params['state']) && $params['state'] == session('state') && isset($params['code']))) {
//获取access_token
$data = isset($params['code']) ? $this->getAccessToken($params['code']) : $params;
$access_token = isset($data['access_token']) ? $data['access_token'] : '';
$refresh_token = isset($data['refresh_token']) ? $data['refresh_token'] : '';
$expires_in = isset($data['expires_in']) ? $data['expires_in'] : 0;
if ($access_token) {
$openid = $this->getOpenId($access_token);
//获取用户信息
$queryarr = [
"access_token" => $access_token,
"oauth_consumer_key" => $this->config['app_id'],
"openid" => $openid,
];
$ret = $this->http->get(self::GET_USERINFO_URL.'?'.http_build_query($queryarr))->getBody()->getContents();
$userinfo = (array)json_decode($ret, true);
if (!$userinfo || !isset($userinfo['ret']) || $userinfo['ret'] !== 0) {
return [];
}
$userinfo = $userinfo ? $userinfo : [];
$userinfo['avatar'] = isset($userinfo['figureurl_qq_2']) ? $userinfo['figureurl_qq_2'] : '';
$userinfo['avatar'] = str_replace('http://','https://',$userinfo['avatar']);
$data = [
'access_token' => $access_token,
'refresh_token' => $refresh_token,
'expires_in' => $expires_in,
'openid' => $openid,
'userinfo' => $userinfo
];
return $data;
}
}
return [];
}
/**
* 获取access_token
* @param string $code
* @return array
*/
public function getAccessToken($code = '')
{
if (!$code) {
return [];
}
$queryarr = array(
"grant_type" => "authorization_code",
"client_id" => $this->config['app_id'],
"client_secret" => $this->config['app_key'],
"redirect_uri" => $this->config['callback'],
"code" => $code,
);
$ret = $this->http->get(self::GET_ACCESS_TOKEN_URL.'?'.http_build_query($queryarr))->getBody()->getContents();
$params = [];
parse_str($ret, $params);
return $params ? $params : [];
}
/**
* 获取open_id
* @param string $access_token
* @return string
*/
private function getOpenId($access_token = '')
{
$response = $this->http->get(self::GET_OPENID_URL.'?access_token='.$access_token)->getBody()->getContents();
if (strpos($response, "callback") !== false) {
$lpos = strpos($response, "(");
$rpos = strrpos($response, ")");
$response = substr($response, $lpos + 1, $rpos - $lpos - 1);
}
$user = (array)json_decode($response, true);
return isset($user['openid']) ? $user['openid'] : '';
}
}

View File

@@ -0,0 +1,133 @@
<?php
declare (strict_types = 1);
namespace system\third;
use GuzzleHttp\Client;
/**
* 微博登录类
*/
class weibo
{
const GET_AUTH_CODE_URL = "https://api.weibo.com/oauth2/authorize";
const GET_ACCESS_TOKEN_URL = "https://api.weibo.com/oauth2/access_token";
const GET_USERINFO_URL = "https://api.weibo.com/2/users/show.json";
/**
* 配置信息
* @var array
*/
private $config = [];
/**
* Http实例
* @var Object
*/
protected $http = null;
public function __construct($options = [])
{
if ($config = saenv('weibo')) {
$this->config = array_merge($this->config, $config);
}
$this->config = array_merge($this->config, is_array($options) ? $options : []);
$this->http = new Client();
}
/**
* 用户登录
*/
public function login() {
return redirect($this->getAuthorizeUrl());
}
/**
* 获取登录地址
*/
public function getAuthorizeUrl()
{
$state = hash('sha256',uniqid((string)mt_rand()));
session('state', $state);
$queryarr = array(
"response_type" => "code",
"client_id" => $this->config['app_id'],
"redirect_uri" => $this->config['callback'],
"state" => $state,
);
request()->isMobile() && $queryarr['display'] = 'mobile';
$url = self::GET_AUTH_CODE_URL . '?' . http_build_query($queryarr);
return $url;
}
/**
* 获取用户信息
* @param array $params
* @return array
*/
public function getUserInfo($params = [])
{
$params = $params ? $params : input();
if (isset($params['access_token']) || (isset($params['state']) && $params['state'] == session('state') && isset($params['code']))) {
//获取access_token
$data = isset($params['code']) ? $this->getAccessToken($params['code']) : $params;
$access_token = isset($data['access_token']) ? $data['access_token'] : '';
$refresh_token = isset($data['refresh_token']) ? $data['refresh_token'] : '';
$expires_in = isset($data['expires_in']) ? $data['expires_in'] : 0;
if ($access_token) {
$uid = isset($data['uid']) ? $data['uid'] : '';
//获取用户信息
$queryarr = [
"access_token" => $access_token,
"uid" => $uid,
];
$ret = $this->http->get(self::GET_USERINFO_URL.'?'.http_build_query($queryarr))->getBody()->getContents();
$userinfo = (array)json_decode($ret, true);
if (!$userinfo || isset($userinfo['error_code'])) {
return [];
}
$userinfo = $userinfo ? $userinfo : [];
$userinfo['nickname'] = isset($userinfo['screen_name']) ? $userinfo['screen_name'] : '';
$userinfo['avatar'] = isset($userinfo['profile_image_url']) ? $userinfo['profile_image_url'] : '';
$userinfo['avatar'] = str_replace('http://','https://',$userinfo['avatar']);
$data = [
'access_token' => $access_token,
'refresh_token' => $refresh_token,
'expires_in' => $expires_in,
'openid' => $uid,
'userinfo' => $userinfo
];
return $data;
}
}
return [];
}
/**
* 获取access_token
* @param string code
* @return array
*/
public function getAccessToken($code = '')
{
if (!$code) {
return [];
}
$queryarr = array(
"grant_type" => "authorization_code",
"client_id" => $this->config['app_id'],
"client_secret" => $this->config['app_key'],
"redirect_uri" => $this->config['callback'],
"code" => $code,
);
$response = $this->http->post(self::GET_ACCESS_TOKEN_URL,['query'=>$queryarr])->getBody()->getContents();
$ret = (array)json_decode($response, true);
return $ret ? $ret : [];
}
}

View File

@@ -0,0 +1,143 @@
<?php
declare (strict_types = 1);
namespace system\third;
use GuzzleHttp\Client;
/**
* 微信登录类
*/
class weixin
{
const GET_AUTH_CODE_URL = "https://open.weixin.qq.com/connect/qrconnect";
const GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token";
const GET_USERINFO_URL = "https://api.weixin.qq.com/sns/userinfo";
/**
* 配置信息
* @var array
*/
private $config = [];
/**
* Http实例
* @var Object
*/
protected $http = null;
public function __construct($options = [])
{
if ($config = saenv('weixin')) {
$this->config = array_merge($this->config, $config);
}
$this->config = array_merge($this->config, is_array($options) ? $options : []);
$this->http = new Client();
}
/**
* 用户登录
*/
public function login() {
return redirect($this->getAuthorizeUrl());
}
/**
* 获取登录地址
*/
public function getAuthorizeUrl()
{
$state = hash('sha256',uniqid((string)mt_rand()));
session('state', $state);
$queryarr = array(
"response_type" => "code",
"appid" => $this->config['app_id'],
"redirect_uri" => $this->config['callback'],
"scope" => 'snsapi_login,',
"state" => $state,
);
request()->isMobile() && $queryarr['display'] = 'mobile';
$url = self::GET_AUTH_CODE_URL . '?' . http_build_query($queryarr);
return $url;
}
/**
* 获取用户信息
* @param array $params
* @return array
*/
public function getUserInfo($params = [])
{
$params = $params ? $params : input();
if (isset($params['access_token']) || (isset($params['state']) && $params['state'] == session('state') && isset($params['code']))) {
//获取access_token
$data = isset($params['code']) ? $this->getAccessToken($params['code']) : $params;
$access_token = isset($data['access_token']) ? $data['access_token'] : '';
$refresh_token = isset($data['refresh_token']) ? $data['refresh_token'] : '';
$expires_in = isset($data['expires_in']) ? $data['expires_in'] : 0;
if ($access_token) {
$openid = isset($data['openid']) ? $data['openid'] : '';
$unionid = isset($data['unionid']) ? $data['unionid'] : '';
if (stripos($data['scope'], 'snsapi_login') !== false) {
//获取用户信息
$queryarr = [
"access_token" => $access_token,
"openid" => $openid,
"lang" => 'zh_CN'
];
$ret = $this->http->get(self::GET_USERINFO_URL.'?'.http_build_query($queryarr))->getBody()->getContents();
$userinfo = (array)json_decode($ret, true);
if (!$userinfo || isset($userinfo['errcode'])) {
return [];
}
$userinfo = $userinfo ? $userinfo : [];
$userinfo['avatar'] = isset($userinfo['headimgurl']) ? $userinfo['headimgurl'] : '';
$userinfo['avatar'] = str_replace('http://','https://',$userinfo['avatar']);
} else {
$userinfo = [];
}
$data = [
'access_token' => $access_token,
'refresh_token' => $refresh_token,
'expires_in' => $expires_in,
'openid' => $openid,
'unionid' => $unionid,
'userinfo' => $userinfo
];
return $data;
}
}
return [];
}
/**
* 获取access_token
* @param string code
* @return array
*/
public function getAccessToken($code = '')
{
if (!$code) {
return [];
}
$queryarr = array(
"grant_type" => "authorization_code",
"appid" => $this->config['app_id'],
"secret" => $this->config['app_key'],
"code" => $code,
);
$response = $this->http->get(self::GET_ACCESS_TOKEN_URL.'?'.http_build_query($queryarr))->getBody()->getContents();
$ret = (array)json_decode($response, true);
return $ret ? $ret : [];
}
}