pull/37/head
kaiend 2018-02-01 10:20:13 +08:00
parent 78f3b10092
commit 0a553141d0
280 changed files with 6041 additions and 58 deletions

0
.travis.yml 100755 → 100644
View File

0
LICENSE 100755 → 100644
View File

View File

@ -0,0 +1,72 @@
<?php
namespace addons\alisms;
use think\Addons;
/**
* Alisms
*/
class Alisms extends Addons
{
/**
* 插件安装方法
* @return bool
*/
public function install()
{
return true;
}
/**
* 插件卸载方法
* @return bool
*/
public function uninstall()
{
return true;
}
/**
* 短信发送行为
* @param Sms $params
* @return boolean
*/
public function smsSend(&$params)
{
$config = get_addon_config('alisms');
$alisms = new library\Alisms();
$result = $alisms->mobile($params->mobile)
->template($config['template'][$params->event])
->param(['code' => $params->code])
->send();
return $result;
}
/**
* 短信发送通知
* @param array $params
* @return boolean
*/
public function smsNotice(&$params)
{
$alisms = library\Alisms::instance();
$result = $alisms->mobile($params['mobile'])
->template($params['template'])
->param($params)
->send();
return $result;
}
/**
* 检测验证是否正确
* @param Sms $params
* @return boolean
*/
public function smsCheck(&$params)
{
return TRUE;
}
}

View File

@ -0,0 +1,70 @@
<?php
return array (
0 =>
array (
'name' => 'key',
'title' => '应用key',
'type' => 'string',
'content' =>
array (
),
'value' => 'LTAIrWkYK52SWqlA',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
),
1 =>
array (
'name' => 'secret',
'title' => '密钥secret',
'type' => 'string',
'content' =>
array (
),
'value' => 'mAoToIZsRvuzM1n1Z66uj5cQctawCX',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
),
2 =>
array (
'name' => 'sign',
'title' => '签名',
'type' => 'string',
'content' =>
array (
),
'value' => '吉泰隆',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
),
3 =>
array (
'name' => 'template',
'title' => '短信模板',
'type' => 'array',
'content' =>
array (
),
'value' =>
array (
'register' => 'SMS_114000000',
'resetpwd' => 'SMS_114000000',
'changepwd' => 'SMS_114000000',
'短信验证码' => 'SMS_119078242',
),
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
),
);

View File

@ -0,0 +1,49 @@
<?php
namespace addons\alisms\controller;
use think\addons\Controller;
/**
* 二维码生成
*
*/
class Index extends Controller
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
}
//
public function index()
{
return $this->view->fetch();
}
public function send()
{
$mobile = $this->request->post('mobile');
$template = $this->request->post('template');
$sign = $this->request->post('sign');
$param = (array) json_decode($this->request->post('param'));
$alisms = new \addons\alisms\library\Alisms();
$ret = $alisms->mobile($mobile)
->template($template)
->sign($sign)
->param($param)
->send();
if ($ret)
{
$this->success("发送成功");
}
else
{
$this->error("发送失败!失败原因:" . $alisms->getError());
}
}
}

View File

@ -0,0 +1,7 @@
name = alisms
title = 阿里短信发送
intro = 阿里短信发送插件
author = Karson
website = http://www.fastadmin.net
version = 1.0.1
state = 1

View File

@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS `__PREFIX__mobile_code` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
`type` varchar(30) NOT NULL DEFAULT '' COMMENT '类型',
`mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号',
`code` varchar(10) DEFAULT '' COMMENT '验证码',
`times` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '验证次数',
`createtime` int(10) unsigned DEFAULT '0' COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='短信验证码表';

View File

@ -0,0 +1,178 @@
<?php
namespace addons\alisms\library;
use think\Config;
/**
* 阿里大于SMS短信发送
*/
class Alisms
{
private $_params = [];
public $error = '';
protected $config = [];
public function __construct($options = [])
{
if ($config = get_addon_config('alisms'))
{
$this->config = array_merge($this->config, $config);
}
$this->config = array_merge($this->config, is_array($options) ? $options : []);
}
/**
* 单例
* @param array $options 参数
* @return Alisms
*/
public static function instance($options = [])
{
if (is_null(self::$instance))
{
self::$instance = new static($options);
}
return self::$instance;
}
/**
* 设置签名
* @param string $sign
* @return Alisms
*/
public function sign($sign = '')
{
$this->_params['SignName'] = $sign;
return $this;
}
/**
* 设置参数
* @param array $param
* @return Alisms
*/
public function param(array $param = [])
{
foreach ($param as $k => &$v)
{
$v = (string) $v;
}
unset($v);
$this->_params['TemplateParam'] = json_encode($param);
return $this;
}
/**
* 设置模板
* @param string $code 短信模板
* @return Alisms
*/
public function template($code = '')
{
$this->_params['TemplateCode'] = $code;
return $this;
}
/**
* 接收手机
* @param string $mobile 手机号码
* @return Alisms
*/
public function mobile($mobile = '')
{
$this->_params['PhoneNumbers'] = $mobile;
return $this;
}
/**
* 立即发送
* @return boolean
*/
public function send()
{
$this->error = '';
$params = $this->_params();
$params['Signature'] = $this->_signed($params);
$response = $this->_curl($params);
if ($response !== FALSE)
{
$res = (array) json_decode($response, TRUE);
if (isset($res['Code']) && $res['Code'] == 'OK')
return TRUE;
$this->error = isset($res['Message']) ? $res['Message'] : 'InvalidResult';
}
else
{
$this->error = 'InvalidResult';
}
return FALSE;
}
/**
* 获取错误信息
* @return array
*/
public function getError()
{
return $this->error;
}
private function _params()
{
return array_merge([
'AccessKeyId' => $this->config['key'],
'SignName' => isset($this->config['sign']) ? $this->config['sign'] : '',
'Action' => 'SendSms',
'Format' => 'JSON',
'Version' => '2017-05-25',
'SignatureVersion' => '1.0',
'SignatureMethod' => 'HMAC-SHA1',
'SignatureNonce' => uniqid(),
'Timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
], $this->_params);
}
private function percentEncode($string)
{
$string = urlencode($string);
$string = preg_replace('/\+/', '%20', $string);
$string = preg_replace('/\*/', '%2A', $string);
$string = preg_replace('/%7E/', '~', $string);
return $string;
}
private function _signed($params)
{
$sign = $this->config['secret'];
ksort($params);
$canonicalizedQueryString = '';
foreach ($params as $key => $value)
{
$canonicalizedQueryString .= '&' . $this->percentEncode($key) . '=' . $this->percentEncode($value);
}
$stringToSign = 'GET&%2F&' . $this->percentencode(substr($canonicalizedQueryString, 1));
$signature = base64_encode(hash_hmac('sha1', $stringToSign, $sign . '&', true));
return $signature;
}
private function _curl($params)
{
$uri = 'http://dysmsapi.aliyuncs.com/?' . http_build_query($params);
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_URL, $uri);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.98 Safari/537.36");
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$reponse = curl_exec($ch);
curl_close($ch);
return $reponse;
}
}

View File

@ -0,0 +1,63 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<title>Alisms短信发送示例</title>
<link href="//cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link href="//cdn.fastadmin.net/assets/css/frontend.min.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="//cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="//cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="container">
<div class="well" style="margin-top:30px;">
<form class="form-horizontal" action="{:addon_url('alisms/index/send')}">
<fieldset>
<legend>阿里大于短信发送</legend>
<div class="form-group">
<label class="col-lg-2 control-label">手机号</label>
<div class="col-lg-10">
<input type="text" class="form-control" name="mobile" placeholder="手机号">
</div>
</div>
<div class="form-group">
<label class="col-lg-2 control-label">消息模板</label>
<div class="col-lg-10">
<input type="text" class="form-control" name="template" placeholder="消息模板">
</div>
</div>
<div class="form-group">
<label class="col-lg-2 control-label">签名</label>
<div class="col-lg-10">
<input type="text" class="form-control" name="sign" placeholder="消息模板(可以留空)">
</div>
</div>
<div class="form-group">
<label class="col-lg-2 control-label">消息参数</label>
<div class="col-lg-10">
<textarea name="param" class="form-control" cols="30" rows="10" placeholder="必须是JSON字符串"></textarea>
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<button type="submit" class="btn btn-primary">发送</button>
<button type="reset" class="btn btn-default">重置</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<script src="//cdn.bootcss.com/jquery/2.1.4/jquery.min.js"></script>
<script src="//cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script type="text/javascript">
$(function () {
});
</script>
</body>
</html>

View File

@ -0,0 +1,49 @@
<?php
namespace addons\crontab;
use app\common\library\Menu;
use think\Addons;
/**
* 定时任务
*/
class Crontab extends Addons
{
/**
* 插件安装方法
* @return bool
*/
public function install()
{
$menu = [
[
'name' => 'general/crontab',
'title' => '定时任务',
'icon' => 'fa fa-tasks',
'remark' => '类似于Linux的Crontab定时任务,可以按照设定的时间进行任务的执行,目前仅支持三种任务:请求URL、执行SQL、执行Shell',
'sublist' => [
['name' => 'general/crontab/index', 'title' => '查看'],
['name' => 'general/crontab/add', 'title' => '添加'],
['name' => 'general/crontab/edit', 'title' => '编辑 '],
['name' => 'general/crontab/del', 'title' => '删除'],
['name' => 'general/crontab/multi', 'title' => '批量更新'],
]
]
];
Menu::create($menu, 'general');
return true;
}
/**
* 插件卸载方法
* @return bool
*/
public function uninstall()
{
Menu::delete('general/crontab');
return true;
}
}

View File

@ -0,0 +1,100 @@
<?php
namespace app\admin\controller\general;
use app\common\controller\Backend;
use Cron\CronExpression;
/**
* 定时任务
*
* @icon fa fa-tasks
* @remark 类似于Linux的Crontab定时任务,可以按照设定的时间进行任务的执行,目前仅支持三种任务:请求URL、执行SQL、执行Shell
*/
class Crontab extends Backend
{
protected $model = null;
protected $noNeedRight = ['check_schedule', 'get_schedule_future'];
public function _initialize()
{
parent::_initialize();
$this->model = model('Crontab');
$this->view->assign('typedata', \app\common\model\Crontab::getTypeList());
}
/**
* 查看
*/
public function index()
{
if ($this->request->isAjax())
{
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model
->where($where)
->order($sort, $order)
->count();
$list = $this->model
->where($where)
->order($sort, $order)
->limit($offset, $limit)
->select();
foreach ($list as $k => &$v)
{
$cron = CronExpression::factory($v['schedule']);
$v['nexttime'] = $cron->getNextRunDate()->getTimestamp();
}
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
/**
* 判断Crontab格式是否正确
* @internal
*/
public function check_schedule()
{
$row = $this->request->post("row/a");
$schedule = isset($row['schedule']) ? $row['schedule'] : '';
if (CronExpression::isValidExpression($schedule))
{
return json(['ok' => '']);
}
else
{
return json(['error' => __('Crontab format invalid')]);
}
}
/**
* 根据Crontab表达式读取未来七次的时间
* @internal
*/
public function get_schedule_future()
{
$time = [];
$schedule = $this->request->post('schedule');
$days = (int) $this->request->post('days');
try
{
$cron = CronExpression::factory($schedule);
for ($i = 0; $i < $days; $i++)
{
$time[] = $cron->getNextRunDate(null, $i)->format('Y-m-d H:i:s');
}
}
catch (\Exception $e)
{
}
return json(['futuretime' => $time]);
}
}

View File

@ -0,0 +1,17 @@
<?php
return [
'Title' => '任务标题',
'Maximums' => '最多执行',
'Sleep' => '延迟秒数',
'Schedule' => '执行周期',
'Executes' => '执行次数',
'No limit' => '无限制',
'Execute time' => '最后执行时间',
'Request Url' => '请求URL',
'Execute Sql Script' => '执行SQL',
'Execute Shell' => '执行Shell',
'Crontab format invalid' => 'Crontab格式错误',
'Next execute time' => '下次预计时间',
'The next %s times the execution time' => '接下来 %s 次的执行时间',
];

View File

@ -0,0 +1,81 @@
<style type="text/css">
#schedulepicker {
padding-top:7px;
}
</style>
<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
<div class="form-group">
<label for="name" class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label>
<div class="col-xs-12 col-sm-8">
<input type="text" class="form-control" id="title" name="row[title]" value="" data-rule="required" />
</div>
</div>
<div class="form-group">
<label for="name" class="control-label col-xs-12 col-sm-2">{:__('Type')}:</label>
<div class="col-xs-12 col-sm-8">
{:build_select('row[type]', $typedata, null, ['class'=>'form-control', 'data-rule'=>'required'])}
</div>
</div>
<div class="form-group">
<label for="content" class="control-label col-xs-12 col-sm-2">{:__('Content')}:</label>
<div class="col-xs-12 col-sm-8">
<textarea name="row[content]" id="conent" cols="30" rows="5" class="form-control" data-rule="required"></textarea>
</div>
</div>
<div class="form-group">
<label for="schedule" class="control-label col-xs-12 col-sm-2">{:__('Schedule')}:</label>
<div class="col-xs-12 col-sm-8">
<input type="text" class="form-control" id="schedule" style="font-size:12px;font-family: Verdana;word-spacing:23px;" name="row[schedule]" value="* * * * *" data-rule="required; remote(general/crontab/check_schedule)" />
<div id="schedulepicker">
<pre><code>* * * * *
- - - - -
| | | | +--- day of week (0 - 7) (Sunday=0 or 7)
| | | +-------- month (1 - 12)
| | +------------- day of month (1 - 31)
| +------------------ hour (0 - 23)
+----------------------- min (0 - 59)</code></pre>
<h5>{:__('The next %s times the execution time', '<input type="number" id="pickdays" class="form-control text-center" value="7" style="display: inline-block;width:80px;">')}</h5>
<ol id="scheduleresult" class="list-group">
</ol>
</div>
</div>
</div>
<div class="form-group">
<label for="maximums" class="control-label col-xs-12 col-sm-2">{:__('Maximums')}:</label>
<div class="col-xs-12 col-sm-4">
<input type="number" class="form-control" id="maximums" name="row[maximums]" value="0" data-rule="required" size="6" />
</div>
</div>
<div class="form-group">
<label for="begintime" class="control-label col-xs-12 col-sm-2">{:__('Begin time')}:</label>
<div class="col-xs-12 col-sm-4">
<input type="text" class="form-control datetimepicker" id="begintime" name="row[begintime]" value="" data-rule="{:__('Begin time')}:required" size="6" />
</div>
</div>
<div class="form-group">
<label for="endtime" class="control-label col-xs-12 col-sm-2">{:__('End time')}:</label>
<div class="col-xs-12 col-sm-4">
<input type="text" class="form-control datetimepicker" id="endtime" name="row[endtime]" value="" data-rule="{:__('End time')}:required;match(gte, row[begintime], datetime)" size="6" />
</div>
</div>
<div class="form-group">
<label for="weigh" class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label>
<div class="col-xs-12 col-sm-4">
<input type="text" class="form-control" id="weigh" name="row[weigh]" value="0" data-rule="required" size="6" />
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
<div class="col-xs-12 col-sm-8">
{:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')])}
</div>
</div>
<div class="form-group hide layer-footer">
<label class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-8">
<button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
<button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
</div>
</div>
</form>

View File

@ -0,0 +1,81 @@
<style type="text/css">
#schedulepicker {
padding-top:7px;
}
</style>
<form id="edit-form" class="form-horizontal form-ajax" role="form" data-toggle="validator" method="POST" action="">
<div class="form-group">
<label for="name" class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label>
<div class="col-xs-12 col-sm-8">
<input type="text" class="form-control" id="title" name="row[title]" value="{$row.title}" data-rule="required" />
</div>
</div>
<div class="form-group">
<label for="name" class="control-label col-xs-12 col-sm-2">{:__('Type')}:</label>
<div class="col-xs-12 col-sm-8">
{:build_select('row[type]', $typedata, $row['type'], ['class'=>'form-control', 'data-rule'=>'required'])}
</div>
</div>
<div class="form-group">
<label for="content" class="control-label col-xs-12 col-sm-2">{:__('Content')}:</label>
<div class="col-xs-12 col-sm-8">
<textarea name="row[content]" id="conent" cols="30" rows="5" class="form-control" data-rule="required">{$row.content}</textarea>
</div>
</div>
<div class="form-group">
<label for="schedule" class="control-label col-xs-12 col-sm-2">{:__('Schedule')}:</label>
<div class="col-xs-12 col-sm-8">
<input type="text" class="form-control" id="schedule" style="font-size:12px;font-family: Verdana;word-spacing:23px;" name="row[schedule]" value="{$row.schedule}" data-rule="required; remote(general/crontab/check_schedule)" />
<div id="schedulepicker">
<pre><code>* * * * *
- - - - -
| | | | +--- day of week (0 - 7) (Sunday=0 or 7)
| | | +-------- month (1 - 12)
| | +------------- day of month (1 - 31)
| +------------------ hour (0 - 23)
+----------------------- min (0 - 59)</code></pre>
<h5>{:__('The next %s times the execution time', '<input type="number" id="pickdays" class="form-control text-center" value="7" style="display: inline-block;width:80px;">')}</h5>
<ol id="scheduleresult" class="list-group">
</ol>
</div>
</div>
</div>
<div class="form-group">
<label for="maximums" class="control-label col-xs-12 col-sm-2">{:__('Maximums')}:</label>
<div class="col-xs-12 col-sm-4">
<input type="number" class="form-control" id="maximums" name="row[maximums]" value="{$row.maximums}" data-rule="required" size="6" />
</div>
</div>
<div class="form-group">
<label for="begintime" class="control-label col-xs-12 col-sm-2">{:__('Begin time')}:</label>
<div class="col-xs-12 col-sm-4">
<input type="text" class="form-control datetimepicker" id="begintime" name="row[begintime]" value="{$row.begintime|datetime}" data-rule="{:__('Begin time')}:required" size="6" />
</div>
</div>
<div class="form-group">
<label for="endtime" class="control-label col-xs-12 col-sm-2">{:__('End time')}:</label>
<div class="col-xs-12 col-sm-4">
<input type="text" class="form-control datetimepicker" id="endtime" name="row[endtime]" value="{$row.endtime|datetime}" data-rule="{:__('End time')}:required;match(gte, row[begintime], datetime)" size="6" />
</div>
</div>
<div class="form-group">
<label for="weigh" class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label>
<div class="col-xs-12 col-sm-4">
<input type="text" class="form-control" id="weigh" name="row[weigh]" value="{$row.weigh}" data-rule="required" size="6" />
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
<div class="col-xs-12 col-sm-8">
{:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')], $row['status'])}
</div>
</div>
<div class="form-group hide layer-footer">
<label class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-8">
<button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
<button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
</div>
</div>
</form>

View File

@ -0,0 +1,28 @@
<div class="panel panel-default panel-intro">
{:build_heading()}
<div class="panel-body">
<div id="myTabContent" class="tab-content">
<div class="tab-pane fade active in" id="one">
<div class="widget-body no-padding">
<div id="toolbar" class="toolbar">
{:build_toolbar()}
<div class="dropdown btn-group {:$auth->check('general/crontab/multi')?'':'hide'}">
<a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
<ul class="dropdown-menu text-left" role="menu">
<li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=normal"><i class="fa fa-eye"></i> {:__('Set to normal')}</a></li>
<li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=hidden"><i class="fa fa-eye-slash"></i> {:__('Set to hidden')}</a></li>
</ul>
</div>
</div>
<table id="table" class="table table-striped table-bordered table-hover"
data-operate-edit="{:$auth->check('general/crontab/edit')}"
data-operate-del="{:$auth->check('general/crontab/del')}"
width="100%">
</table>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,54 @@
<?php
namespace app\common\model;
use think\Model;
class Crontab extends Model
{
// 开启自动写入时间戳字段
protected $autoWriteTimestamp = 'int';
// 定义时间戳字段名
protected $createTime = 'createtime';
protected $updateTime = 'updatetime';
// 定义字段类型
protected $type = [
];
// 追加属性
protected $append = [
'type_text'
];
public static function getTypeList()
{
return [
'url' => __('Request Url'),
'sql' => __('Execute Sql Script'),
'shell' => __('Execute Shell'),
];
}
public function getTypeTextAttr($value, $data)
{
$typelist = self::getTypeList();
$value = $value ? $value : $data['type'];
return $value && isset($typelist[$value]) ? $typelist[$value] : $value;
}
protected function setBegintimeAttr($value)
{
return $value && !is_numeric($value) ? strtotime($value) : $value;
}
protected function setEndtimeAttr($value)
{
return $value && !is_numeric($value) ? strtotime($value) : $value;
}
protected function setExecutetimeAttr($value)
{
return $value && !is_numeric($value) ? strtotime($value) : $value;
}
}

View File

@ -0,0 +1,4 @@
<?php
return [
];

View File

@ -0,0 +1,136 @@
<?php
namespace addons\crontab\controller;
use app\common\model\Crontab;
use Cron\CronExpression;
use fast\Http;
use think\Controller;
use think\Db;
use think\Exception;
use think\Log;
/**
* 定时任务接口
*
* 以Crontab方式每分钟定时执行,且只可以Cli方式运行
* @internal
*/
class Autotask extends Controller
{
/**
* 初始化方法,最前且始终执行
*/
public function _initialize()
{
// 只可以以cli方式执行
if (!$this->request->isCli())
$this->error('Autotask script only work at client!');
parent::_initialize();
// 清除错误
error_reporting(0);
// 设置永不超时
set_time_limit(0);
}
/**
* 执行定时任务
*/
public function index()
{
$time = time();
$logDir = LOG_PATH . 'crontab/';
if (!is_dir($logDir))
{
mkdir($logDir, 0755);
}
//筛选未过期且未完成的任务
$crontabList = Crontab::where('status', '=', 'normal')->order('weigh desc,id desc')->select();
foreach ($crontabList as $crontab)
{
$update = [];
$execute = FALSE;
if ($time < $crontab['begintime'])
{
//任务未开始
continue;
}
if ($crontab['maximums'] && $crontab['executes'] > $crontab['maximums'])
{
//任务已超过最大执行次数
$update['status'] = 'completed';
}
else if ($crontab['endtime'] > 0 && $time > $crontab['endtime'])
{
//任务已过期
$update['status'] = 'expired';
}
else
{
//重复执行
//如果未到执行时间则继续循环
$cron = CronExpression::factory($crontab['schedule']);
if (!$cron->isDue() || date("YmdHi") === date("YmdHi", $crontab['executetime']))
continue;
$execute = TRUE;
}
// 如果允许执行
if ($execute)
{
$update['executetime'] = $time;
$update['executes'] = $crontab['executes'] + 1;
$update['status'] = ($crontab['maximums'] > 0 && $update['executes'] >= $crontab['maximums']) ? 'completed' : 'normal';
}
// 如果需要更新状态
if (!$update)
continue;
// 更新状态
$crontab->save($update);
// 将执行放在后面是为了避免超时导致多次执行
if (!$execute)
continue;
try
{
if ($crontab['type'] == 'url')
{
if (substr($crontab['content'], 0, 1) == "/")
{
// 本地项目URL
exec('nohup php ' . ROOT_PATH . 'public/index.php ' . $crontab['content'] . ' >> ' . $logDir . date("Y-m-d") . '.log 2>&1 &');
}
else
{
// 远程异步调用URL
Http::sendAsyncRequest($crontab['content']);
}
}
else if ($crontab['type'] == 'sql')
{
//这里需要强制重连数据库,使用已有的连接会报2014错误
$connect = Db::connect([], true);
$connect->execute("select 1");
// 执行SQL
$connect->getPdo()->exec($crontab['content']);
}
else if ($crontab['type'] == 'shell')
{
// 执行Shell
exec($crontab['content'] . ' >> ' . $logDir . date("Y-m-d") . '.log 2>&1 &');
}
}
catch (Exception $e)
{
Log::record($e->getMessage());
}
}
return 'Execute completed!';
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace addons\crontab\controller;
use think\addons\Controller;
class Index extends Controller
{
public function index()
{
$this->error("当前插件暂无前台页面");
}
}

View File

@ -0,0 +1,7 @@
name = crontab
title = 定时任务
intro = 可在线管理Crontab定时任务
author = Karson
website = http://www.fastadmin.net
version = 1.0.0
state = 1

View File

@ -0,0 +1,24 @@
CREATE TABLE IF NOT EXISTS `__PREFIX__crontab` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
`type` varchar(10) NOT NULL DEFAULT '' COMMENT '事件类型',
`title` varchar(100) NOT NULL DEFAULT '' COMMENT '事件标题',
`content` text NOT NULL COMMENT '事件内容',
`schedule` varchar(100) NOT NULL DEFAULT '' COMMENT 'Crontab格式',
`sleep` tinyint(1) UNSIGNED NOT NULL DEFAULT '0' COMMENT '延迟秒数执行',
`maximums` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '最大执行次数 0为不限',
`executes` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '已经执行的次数',
`createtime` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间',
`updatetime` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '更新时间',
`begintime` int(10) NOT NULL DEFAULT '0' COMMENT '开始时间',
`endtime` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '结束时间',
`executetime` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '最后执行时间',
`weigh` int(10) NOT NULL DEFAULT '0' COMMENT '权重',
`status` enum('completed','expired','hidden','normal') NOT NULL DEFAULT 'normal' COMMENT '状态',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='定时任务表' ROW_FORMAT=DYNAMIC;
BEGIN;
INSERT INTO `__PREFIX__crontab` (`id`, `type`, `title`, `content`, `schedule`, `sleep`, `maximums`, `executes`, `createtime`, `updatetime`, `begintime`, `endtime`, `executetime`, `weigh`, `status`) VALUES
(1, 'url', '请求FastAdmin', 'http://www.fastadmin.net', '* * * * *', 0, 0, 13548, 1497070825, 1501253101, 1483200000, 1546272000, 1501253101, 1, 'normal'),
(2, 'sql', '查询一条SQL', 'SELECT 1;', '* * * * *', 0, 0, 13548, 1497071095, 1501253101, 1483200000, 1546272000, 1501253101, 2, 'normal');
COMMIT;

View File

@ -0,0 +1,79 @@
define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
var Controller = {
index: function () {
// 初始化表格参数配置
Table.api.init({
extend: {
index_url: 'general/crontab/index',
add_url: 'general/crontab/add',
edit_url: 'general/crontab/edit',
del_url: 'general/crontab/del',
multi_url: 'general/crontab/multi',
table: 'crontab'
}
});
var table = $("#table");
// 初始化表格
table.bootstrapTable({
url: $.fn.bootstrapTable.defaults.extend.index_url,
sortName: 'weigh',
columns: [
[
{field: 'state', checkbox: true, },
{field: 'id', title: 'ID'},
{field: 'type_text', title: __('Type'), operate: false},
{field: 'title', title: __('Title')},
{field: 'maximums', title: __('Maximums'), formatter: Controller.api.formatter.maximums},
{field: 'executes', title: __('Executes')},
{field: 'begintime', title: __('Begin time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange'},
{field: 'endtime', title: __('End time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange'},
{field: 'nexttime', title: __('Next execute time'), formatter: Table.api.formatter.datetime, operate: false},
{field: 'executetime', title: __('Execute time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange'},
{field: 'weigh', title: __('Weigh')},
{field: 'status', title: __('Status'), formatter: Table.api.formatter.status},
{field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
]
]
});
// 为表格绑定事件
Table.api.bindevent(table);
},
add: function () {
Controller.api.bindevent();
},
edit: function () {
Controller.api.bindevent();
},
api: {
bindevent: function () {
$('#schedule').on('valid.field', function (e, result) {
$("#pickdays").trigger("change");
});
Form.api.bindevent($("form[role=form]"));
$(document).on("change", "#pickdays", function () {
$("#scheduleresult").html(__('Loading'));
$.post("general/crontab/get_schedule_future", {schedule: $("#schedule").val(), days: $(this).val()}, function (ret) {
$("#scheduleresult").html("");
if (typeof ret.futuretime !== 'undefined' && $.isArray(ret.futuretime)) {
$.each(ret.futuretime, function (i, j) {
$("#scheduleresult").append("<li class='list-group-item'>" + j + "<span class='badge'>" + (i + 1) + "</span></li>");
});
}
}, 'json');
});
$("#pickdays").trigger("change");
},
formatter: {
maximums: function (value, row, index) {
return value === 0 ? __('No limit') : value;
}
}
}
};
return Controller;
});

View File

@ -0,0 +1,31 @@
<?php
namespace addons\properties;
use think\Addons;
/**
* 插件
*/
class Properties extends Addons
{
/**
* 插件安装方法
* @return bool
*/
public function install()
{
return true;
}
/**
* 插件卸载方法
* @return bool
*/
public function uninstall()
{
return true;
}
}

View File

@ -0,0 +1,38 @@
require(['form', 'upload'], function (Form, Upload) {
var _bindevent = Form.events.bindevent;
Form.events.bindevent = function (form) {
_bindevent.apply(this, [form]);
try {
//绑定自定义属性组件
if ($("textarea[id$=Properties]", form).size() > 0) {
require(['../addons/properties/Properties'], function (Properties){
var obj=$("textarea[id$=Properties]", form);
    var table=Properties.init(obj);
$(form).on("click",".property_add",function(event){
Properties.create(table);
})
$(form).on("click",".property_del",function(event){
Properties.remove(table,$(this).parents("tr").data("index"));
})
$(form).on("click",".property_drag",function(event){
Properties.sort(table,$(this).data("index"),$(this).data("direction"));
})
$(form).on("change","input",function(event){
Properties.set(table,$(this).parents("tr").data("index"));
})
  });
}
} catch (e) {
}
};
});

View File

@ -0,0 +1,5 @@
<?php
return [
];

View File

@ -0,0 +1,16 @@
<?php
namespace addons\properties\controller;
use think\addons\Controller;
class Index extends Controller
{
public function index()
{
$this->error("当前插件暂无前台页面");
}
}

View File

@ -0,0 +1,7 @@
name = properties
title = 自定义属性插件
intro = 自定义属性
author = ChrisLeung
website = https://github.com/Chrisleung
version = 1.0.0
state = 1

View File

@ -0,0 +1,141 @@
define([],function(){
var Properties={
formatter:{
property: function (value, row, index) {
return '<div class="input-group" style="width:100%"><input type="text" class="form-control input-sm" value="'+value+'" ></div>';
},
operate:function(value,row,index){
return '\<div class="btn-group btn-group-justified">\
<span class="btn btn-default property_drag" data-index="'+index+'" data-direction="up"><i class="fa fa-arrow-up" aria-hidden="true"></i></span>\
<span class="btn btn-default property_drag" data-index="'+index+'" data-direction="down"><i class="fa fa-arrow-down" aria-hidden="true"></i></span>\
<span class="btn btn-danger property_del" data-index="'+index+'"><i class="fa fa-times" aria-hidden="true"></i></span></div>';
}
},
//初始化表格
init: function(obj) {
obj.hide();
obj.before("<style>\
.swap_done{animation-name: swap;animation-duration: 1s;animation-timing-function: ease-out;animation-direction: alternate;animation-iteration-count: 1;animation-fill-mode: backwards;animation-play-state: running;}\
@keyframes swap{0%,100%{background-color:#f1f4f6}50%{background-color:#ddd}}\
</style>")
.before('<table class="properties"></table>')
.before('<button type="button" class="btn btn-success btn-embossed property_add" style="margin-top:15px;">添加参数</button>');
var table=obj.parent().find("table.properties")
if(obj.val()!=""){
Properties.create(table);
table.bootstrapTable('load',JSON.parse(obj.val()));
}
return table;
},
//创建表头,若存在则添加一行
create:function(table){
if(table.find("tbody").size()==0){
table.bootstrapTable({
showHeader:true,
mobileResponsive:true,
columns: [{
field: 'name',
title: '参数名',
class: 'property_input',
formatter:Properties.formatter.property
}, {
field: 'value',
title: '参数值',
class: 'property_input',
formatter:Properties.formatter.property
},
{
field: 'operate',
title: '操作',
width: '150px',
formatter:Properties.formatter.operate
}],
data:[{name:'',value:''}]
});
}else{
Properties.append(table);
}
},
//添加
append:function(table){
var property={
name:'',
value:''
}
table.bootstrapTable('append',property)
},
//删除
remove:function(table,index){
var data=table.bootstrapTable('getData',true);
data.splice(index,1),
table.bootstrapTable('load',data)
Properties.save(table);
},
//排序
sort:function(table,index,direction){
var data=table.bootstrapTable('getData',true);
switch(direction){
case 'up':
if(index>0){
table.bootstrapTable('load',Properties.swap(data,index,index-1));
table.find("tr[data-index="+(index-1)+"]").addClass('swap_done')
}else{
layer.tips('已经到达顶部',
table.find(".property_drag[data-index="+index+"][data-direction="+direction+"]"),
{
tips:1
});
}
break;
case 'down':
if(index<data.length-1){
table.bootstrapTable('load',Properties.swap(data,index,index+1));
table.find("tr[data-index="+(index+1)+"]").addClass('swap_done')
}else{
layer.tips('已经到达底部',
table.find(".property_drag[data-index="+index+"][data-direction="+direction+"]"),
{
tips:1
});
}
break;
}
Properties.save(table);
},
//设置属性
set:function(table,index){
var property_input=table.find("tr[data-index="+index+"] td.property_input");
var property={
name:property_input.first().find("input").val(),
value:property_input.last().find("input").val()
}
table.bootstrapTable('updateRow', {
index: index,
row: property
})
Properties.save(table);
},
//保存
save:function(table){
table.parents(".bootstrap-table")
.siblings('textarea').val(JSON.stringify(table.bootstrapTable('getData',true)))
},
//交换排序
swap:function(data, old_pos, new_pos) {
data[old_pos] = data.splice(new_pos, 1, data[old_pos])[0];
return data;
}
}
return Properties;
});

View File

@ -0,0 +1,31 @@
<?php
namespace addons\summernote;
use think\Addons;
/**
* Summernote插件
*/
class Summernote extends Addons
{
/**
* 插件安装方法
* @return bool
*/
public function install()
{
return true;
}
/**
* 插件卸载方法
* @return bool
*/
public function uninstall()
{
return true;
}
}

View File

@ -0,0 +1,58 @@
require(['form', 'upload'], function (Form, Upload) {
var _bindevent = Form.events.bindevent;
Form.events.bindevent = function (form) {
_bindevent.apply(this, [form]);
try {
//绑定summernote事件
if ($(".summernote,.editor", form).size() > 0) {
require(['summernote'], function () {
$(".summernote,.editor", form).summernote({
height: 250,
lang: 'zh-CN',
fontNames: [
'Arial', 'Arial Black', 'Serif', 'Sans', 'Courier',
'Courier New', 'Comic Sans MS', 'Helvetica', 'Impact', 'Lucida Grande',
"Open Sans", "Hiragino Sans GB", "Microsoft YaHei",
'微软雅黑', '宋体', '黑体', '仿宋', '楷体', '幼圆',
],
fontNamesIgnoreCheck: [
"Open Sans", "Microsoft YaHei",
'微软雅黑', '宋体', '黑体', '仿宋', '楷体', '幼圆'
],
toolbar: [
['style', ['style', 'undo', 'redo']],
['font', ['bold', 'underline', 'strikethrough', 'clear']],
['fontname', ['color', 'fontname', 'fontsize']],
['para', ['ul', 'ol', 'paragraph', 'height']],
['table', ['table', 'hr']],
['insert', ['link', 'picture', 'video']],
['view', ['fullscreen', 'codeview', 'help']]
],
dialogsInBody: true,
callbacks: {
onChange: function (contents) {
$(this).val(contents);
$(this).trigger('change');
},
onInit: function () {
},
onImageUpload: function (files) {
var that = this;
//依次上传图片
for (var i = 0; i < files.length; i++) {
Upload.api.send(files[i], function (data) {
var url = Fast.api.cdnurl(data.url);
$(that).summernote("insertImage", url, 'filename');
});
}
}
}
});
});
}
} catch (e) {
}
};
});

View File

@ -0,0 +1,5 @@
<?php
return [
];

View File

@ -0,0 +1,16 @@
<?php
namespace addons\summernote\controller;
use think\addons\Controller;
class Index extends Controller
{
public function index()
{
$this->error("当前插件暂无前台页面");
}
}

View File

@ -0,0 +1,7 @@
name = summernote
title = Summernote插件
intro = 修改后台默认编辑器为Summernote
author = Karson
website = http://www.fastadmin.net
version = 1.0.1
state = 1

View File

@ -0,0 +1,31 @@
<?php
namespace addons\third;
use think\Addons;
/**
* 第三方登录
*/
class Third extends Addons
{
/**
* 插件安装方法
* @return bool
*/
public function install()
{
return true;
}
/**
* 插件卸载方法
* @return bool
*/
public function uninstall()
{
return true;
}
}

View File

@ -0,0 +1,74 @@
<?php
return array(
0 =>
array(
'name' => 'qq',
'title' => 'QQ',
'type' => 'array',
'content' =>
array(
'app_id' => '',
'app_secret' => '',
'scope' => 'get_user_info',
),
'value' =>
array(
'app_id' => '100246200',
'app_secret' => '0d4d1bf5210f167226c49f4eb3715512',
'scope' => 'get_user_info',
),
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
),
1 =>
array(
'name' => 'wechat',
'title' => '微信',
'type' => 'array',
'content' =>
array(
'app_id' => '',
'app_secret' => '',
'callback' => '',
'scope' => 'snsapi_userinfo',
),
'value' =>
array(
'app_id' => 'wx91b3fe578d6467ac',
'app_secret' => 'aa6726df5cb2b6278d7f373d009510d0',
'scope' => 'get_user_info',
),
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
),
2 =>
array(
'name' => 'weibo',
'title' => '微博',
'type' => 'array',
'content' =>
array(
'app_id' => '',
'app_secret' => '',
'scope' => 'get_user_info',
),
'value' =>
array(
'app_id' => '645217067',
'app_secret' => '226b4baaf3799e88dec7fcabf5837185',
'scope' => 'get_user_info',
),
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
),
);

View File

@ -0,0 +1,21 @@
<?php
namespace addons\third\controller;
/**
* 第三方登录
*/
class Index extends \think\addons\Controller
{
public function _initialize()
{
parent::_initialize();
}
public function index()
{
$this->error("当前插件暂无前台页面");
}
}

View File

@ -0,0 +1,7 @@
name = third
title = 第三方登录
intro = 使用微信、QQ、微博登录插件
author = Karson
website = http://www.fastadmin.net
version = 1.0.1
state = 1

View File

@ -0,0 +1,18 @@
CREATE TABLE IF NOT EXISTS `__PREFIX__third` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '会员ID',
`platform` enum('weibo','wechat','qq') NOT NULL COMMENT '第三方应用',
`openid` varchar(50) NOT NULL DEFAULT '' COMMENT '第三方唯一ID',
`openname` varchar(50) NOT NULL DEFAULT '' COMMENT '第三方会员昵称',
`access_token` varchar(100) NOT NULL DEFAULT '',
`refresh_token` varchar(100) NOT NULL DEFAULT '',
`expires_in` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '有效期',
`createtime` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间',
`updatetime` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '更新时间',
`logintime` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '登录时间',
`expiretime` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '过期时间',
PRIMARY KEY (`id`),
UNIQUE KEY `platform` (`platform`,`openid`),
KEY `user_id` (`user_id`,`platform`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='第三方登录表' ROW_FORMAT=DYNAMIC;

View File

@ -0,0 +1,67 @@
<?php
namespace addons\third\library;
class Application
{
/**
* 配置信息
* @var array
*/
private $config = [];
/**
* 服务提供者
* @var array
*/
private $providers = [
'qq' => 'Qq',
'weibo' => 'Weibo',
'wechat' => 'Wechat',
];
/**
* 服务对象信息
* @var array
*/
protected $services = [];
public function __construct($options = [])
{
$this->config = array_merge($this->config, is_array($options) ? $options : []);
//注册服务器提供者
$this->registerProviders();
}
/**
* 注册服务提供者
*/
private function registerProviders()
{
foreach ($this->providers as $k => $v)
{
$this->services[$k] = function() use ($k, $v) {
$options = $this->config[$k];
$options['app_id'] = isset($options['app_id']) ? $options['app_id'] : '';
$options['app_secret'] = isset($options['app_secret']) ? $options['app_secret'] : '';
// 如果未定义回调地址则自动生成
$options['callback'] = isset($options['callback']) && $options['callback'] ? $options['callback'] : url('/', [], false, true);
$objname = __NAMESPACE__ . "\\{$v}";
return new $objname($options);
};
}
}
public function __set($key, $value)
{
$this->services[$key] = $value;
}
public function __get($key)
{
return $this->services[$key]($this);
}
}

View File

@ -0,0 +1,145 @@
<?php
namespace addons\third\library;
use fast\Http;
use think\Config;
use think\Session;
/**
* 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 = [];
public function __construct($options = [])
{
if ($config = Config::get('third.qq'))
{
$this->config = array_merge($this->config, $config);
}
$this->config = array_merge($this->config, is_array($options) ? $options : []);
}
/**
* 登陆
*/
public function login()
{
header("Location:" . $this->getAuthorizeUrl());
}
/**
* 获取authorize_url
*/
public function getAuthorizeUrl()
{
$state = md5(uniqid(rand(), TRUE));
Session::set('state', $state);
$queryarr = array(
"response_type" => "code",
"client_id" => $this->config['app_id'],
"redirect_uri" => $this->config['callback'],
"scope" => $this->config['scope'],
"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 : $_GET;
if (isset($params['access_token']) || (isset($params['state']) && $params['state'] == Session::get('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 = Http::get(self::GET_USERINFO_URL, $queryarr);
$userinfo = 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'] : '';
$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
*/
private function getAccessToken($code = '')
{
if (!$code)
return '';
$queryarr = array(
"grant_type" => "authorization_code",
"client_id" => $this->config['app_id'],
"client_secret" => $this->config['app_secret'],
"redirect_uri" => $this->config['callback'],
"code" => $code,
);
$ret = Http::get(self::GET_ACCESS_TOKEN_URL, $queryarr);
$params = [];
parse_str($ret, $params);
return $params ? $params : [];
}
/**
* 获取open_id
* @param string $access_token
* @return string
*/
private function getOpenId($access_token = '')
{
$response = Http::get(self::GET_OPENID_URL, ['access_token' => $access_token]);
if (strpos($response, "callback") !== false)
{
$lpos = strpos($response, "(");
$rpos = strrpos($response, ")");
$response = substr($response, $lpos + 1, $rpos - $lpos - 1);
}
$user = json_decode($response, TRUE);
return isset($user['openid']) ? $user['openid'] : '';
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace addons\third\library;
use addons\third\model\Third;
use fast\Random;
/**
* 第三方登录服务类
*
* @author Karson
*/
class Service
{
/**
* 第三方登录
* @param string $platform 平台
* @param array $params 参数
* @param int $keeptime 有效时长
* @return boolean
*/
public static function connect($platform, $params = [], $keeptime = 0)
{
$time = time();
$values = [
'platform' => $platform,
'openid' => $params['openid'],
'openname' => isset($params['userinfo']['nickname']) ? $params['userinfo']['nickname'] : '',
'access_token' => $params['access_token'],
'refresh_token' => $params['refresh_token'],
'expires_in' => $params['expires_in'],
'logintime' => $time,
'expiretime' => $time + $params['expires_in'],
];
$auth = \app\common\library\Auth::instance();
$auth->keeptime($keeptime);
$third = Third::get(['platform' => $platform, 'openid' => $params['openid']]);
if ($third)
{
$user = \addons\user\model\User::get($third['user_id']);
if (!$user)
{
return FALSE;
}
$third->save($values);
return $auth->direct($user->id);
}
else
{
// 先随机一个用户名,随后再变更为u+数字id
$username = Random::alnum(20);
$password = Random::alnum(6);
// 默认注册一个会员
$result = $auth->register($username, $password, '', '', [], $keeptime);
if (!$result)
{
return FALSE;
}
$user = $auth->getUser();
$fields = ['username' => 'u' . $user->id];
if (isset($params['userinfo']['nickname']))
$fields['nickname'] = $params['userinfo']['nickname'];
if (isset($params['userinfo']['avatar']))
$fields['avatar'] = $params['userinfo']['avatar'];
// 更新会员资料
$user->save($fields);
// 保存第三方信息
$values['user_id'] = $user->id;
Third::create($values);
// 写入登录Cookies和Token
return $auth->direct($user->id);
}
}
}

View File

@ -0,0 +1,126 @@
<?php
namespace addons\third\library;
use fast\Http;
use think\Config;
use think\Session;
/**
* 微信
*/
class Wechat
{
const GET_AUTH_CODE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize";
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 = [];
public function __construct($options = [])
{
if ($config = Config::get('third.wechat'))
{
$this->config = array_merge($this->config, $config);
}
$this->config = array_merge($this->config, is_array($options) ? $options : []);
}
/**
* 登陆
*/
public function login()
{
header("Location:" . $this->getAuthorizeUrl());
}
/**
* 获取authorize_url
*/
public function getAuthorizeUrl()
{
$state = md5(uniqid(rand(), TRUE));
Session::set('state', $state);
$queryarr = array(
"appid" => $this->config['app_id'],
"redirect_uri" => $this->config['callback'],
"response_type" => "code",
"scope" => $this->config['scope'],
"state" => $state,
);
request()->isMobile() && $queryarr['display'] = 'mobile';
$url = self::GET_AUTH_CODE_URL . '?' . http_build_query($queryarr) . '#wechat_redirect';
return $url;
}
/**
* 获取用户信息
* @param array $params
* @return array
*/
public function getUserInfo($params = [])
{
$params = $params ? $params : $_GET;
if (isset($params['access_token']) || (isset($params['state']) && $params['state'] == Session::get('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'] : '';
//获取用户信息
$queryarr = [
"access_token" => $access_token,
"openid" => $openid,
"lang" => 'zh_CN'
];
$ret = Http::post(self::GET_USERINFO_URL, $queryarr);
$userinfo = json_decode($ret, TRUE);
if (!$userinfo || isset($userinfo['errcode']))
return [];
$userinfo = $userinfo ? $userinfo : [];
$userinfo['avatar'] = isset($userinfo['headimgurl']) ? $userinfo['headimgurl'] : '';
$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
*/
private function getAccessToken($code = '')
{
if (!$code)
return '';
$queryarr = array(
"appid" => $this->config['app_id'],
"secret" => $this->config['app_secret'],
"code" => $code,
"grant_type" => "authorization_code",
);
$response = Http::post(self::GET_ACCESS_TOKEN_URL, $queryarr);
$ret = json_decode($response, TRUE);
return $ret ? $ret : [];
}
}

View File

@ -0,0 +1,124 @@
<?php
namespace addons\third\library;
use fast\Http;
use think\Config;
use think\Session;
/**
* 微博
*/
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 = [];
public function __construct($options = [])
{
if ($config = Config::get('third.weibo'))
{
$this->config = array_merge($this->config, $config);
}
$this->config = array_merge($this->config, is_array($options) ? $options : []);
}
/**
* 登陆
*/
public function login()
{
header("Location:" . $this->getAuthorizeUrl());
}
/**
* 获取authorize_url
*/
public function getAuthorizeUrl()
{
$state = md5(uniqid(rand(), TRUE));
Session::set('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 : $_GET;
if (isset($params['access_token']) || (isset($params['state']) && $params['state'] == Session::get('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 = Http::get(self::GET_USERINFO_URL, $queryarr);
$userinfo = 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'] : '';
$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
*/
private function getAccessToken($code = '')
{
if (!$code)
return '';
$queryarr = array(
"grant_type" => "authorization_code",
"client_id" => $this->config['app_id'],
"client_secret" => $this->config['app_secret'],
"redirect_uri" => $this->config['callback'],
"code" => $code,
);
$response = Http::post(self::GET_ACCESS_TOKEN_URL, $queryarr);
$ret = json_decode($response, TRUE);
return $ret ? $ret : [];
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace addons\third\model;
use think\Model;
/**
* 会员模型
*/
class Third Extends Model
{
// 开启自动写入时间戳字段
protected $autoWriteTimestamp = 'int';
// 定义时间戳字段名
protected $createTime = 'createtime';
protected $updateTime = 'updatetime';
// 追加属性
protected $append = [
];
}

0
application/.htaccess 100755 → 100644
View File

View File

View File

View File

View File

@ -0,0 +1,100 @@
<?php
namespace app\admin\controller\general;
use app\common\controller\Backend;
use Cron\CronExpression;
/**
* 定时任务
*
* @icon fa fa-tasks
* @remark 类似于Linux的Crontab定时任务,可以按照设定的时间进行任务的执行,目前仅支持三种任务:请求URL、执行SQL、执行Shell
*/
class Crontab extends Backend
{
protected $model = null;
protected $noNeedRight = ['check_schedule', 'get_schedule_future'];
public function _initialize()
{
parent::_initialize();
$this->model = model('Crontab');
$this->view->assign('typedata', \app\common\model\Crontab::getTypeList());
}
/**
* 查看
*/
public function index()
{
if ($this->request->isAjax())
{
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model
->where($where)
->order($sort, $order)
->count();
$list = $this->model
->where($where)
->order($sort, $order)
->limit($offset, $limit)
->select();
foreach ($list as $k => &$v)
{
$cron = CronExpression::factory($v['schedule']);
$v['nexttime'] = $cron->getNextRunDate()->getTimestamp();
}
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
/**
* 判断Crontab格式是否正确
* @internal
*/
public function check_schedule()
{
$row = $this->request->post("row/a");
$schedule = isset($row['schedule']) ? $row['schedule'] : '';
if (CronExpression::isValidExpression($schedule))
{
return json(['ok' => '']);
}
else
{
return json(['error' => __('Crontab format invalid')]);
}
}
/**
* 根据Crontab表达式读取未来七次的时间
* @internal
*/
public function get_schedule_future()
{
$time = [];
$schedule = $this->request->post('schedule');
$days = (int) $this->request->post('days');
try
{
$cron = CronExpression::factory($schedule);
for ($i = 0; $i < $days; $i++)
{
$time[] = $cron->getNextRunDate(null, $i)->format('Y-m-d H:i:s');
}
}
catch (\Exception $e)
{
}
return json(['futuretime' => $time]);
}
}

View File

@ -0,0 +1,17 @@
<?php
return [
'Title' => '任务标题',
'Maximums' => '最多执行',
'Sleep' => '延迟秒数',
'Schedule' => '执行周期',
'Executes' => '执行次数',
'No limit' => '无限制',
'Execute time' => '最后执行时间',
'Request Url' => '请求URL',
'Execute Sql Script' => '执行SQL',
'Execute Shell' => '执行Shell',
'Crontab format invalid' => 'Crontab格式错误',
'Next execute time' => '下次预计时间',
'The next %s times the execution time' => '接下来 %s 次的执行时间',
];

View File

@ -0,0 +1,81 @@
<style type="text/css">
#schedulepicker {
padding-top:7px;
}
</style>
<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
<div class="form-group">
<label for="name" class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label>
<div class="col-xs-12 col-sm-8">
<input type="text" class="form-control" id="title" name="row[title]" value="" data-rule="required" />
</div>
</div>
<div class="form-group">
<label for="name" class="control-label col-xs-12 col-sm-2">{:__('Type')}:</label>
<div class="col-xs-12 col-sm-8">
{:build_select('row[type]', $typedata, null, ['class'=>'form-control', 'data-rule'=>'required'])}
</div>
</div>
<div class="form-group">
<label for="content" class="control-label col-xs-12 col-sm-2">{:__('Content')}:</label>
<div class="col-xs-12 col-sm-8">
<textarea name="row[content]" id="conent" cols="30" rows="5" class="form-control" data-rule="required"></textarea>
</div>
</div>
<div class="form-group">
<label for="schedule" class="control-label col-xs-12 col-sm-2">{:__('Schedule')}:</label>
<div class="col-xs-12 col-sm-8">
<input type="text" class="form-control" id="schedule" style="font-size:12px;font-family: Verdana;word-spacing:23px;" name="row[schedule]" value="* * * * *" data-rule="required; remote(general/crontab/check_schedule)" />
<div id="schedulepicker">
<pre><code>* * * * *
- - - - -
| | | | +--- day of week (0 - 7) (Sunday=0 or 7)
| | | +-------- month (1 - 12)
| | +------------- day of month (1 - 31)
| +------------------ hour (0 - 23)
+----------------------- min (0 - 59)</code></pre>
<h5>{:__('The next %s times the execution time', '<input type="number" id="pickdays" class="form-control text-center" value="7" style="display: inline-block;width:80px;">')}</h5>
<ol id="scheduleresult" class="list-group">
</ol>
</div>
</div>
</div>
<div class="form-group">
<label for="maximums" class="control-label col-xs-12 col-sm-2">{:__('Maximums')}:</label>
<div class="col-xs-12 col-sm-4">
<input type="number" class="form-control" id="maximums" name="row[maximums]" value="0" data-rule="required" size="6" />
</div>
</div>
<div class="form-group">
<label for="begintime" class="control-label col-xs-12 col-sm-2">{:__('Begin time')}:</label>
<div class="col-xs-12 col-sm-4">
<input type="text" class="form-control datetimepicker" id="begintime" name="row[begintime]" value="" data-rule="{:__('Begin time')}:required" size="6" />
</div>
</div>
<div class="form-group">
<label for="endtime" class="control-label col-xs-12 col-sm-2">{:__('End time')}:</label>
<div class="col-xs-12 col-sm-4">
<input type="text" class="form-control datetimepicker" id="endtime" name="row[endtime]" value="" data-rule="{:__('End time')}:required;match(gte, row[begintime], datetime)" size="6" />
</div>
</div>
<div class="form-group">
<label for="weigh" class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label>
<div class="col-xs-12 col-sm-4">
<input type="text" class="form-control" id="weigh" name="row[weigh]" value="0" data-rule="required" size="6" />
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
<div class="col-xs-12 col-sm-8">
{:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')])}
</div>
</div>
<div class="form-group hide layer-footer">
<label class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-8">
<button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
<button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
</div>
</div>
</form>

View File

@ -0,0 +1,81 @@
<style type="text/css">
#schedulepicker {
padding-top:7px;
}
</style>
<form id="edit-form" class="form-horizontal form-ajax" role="form" data-toggle="validator" method="POST" action="">
<div class="form-group">
<label for="name" class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label>
<div class="col-xs-12 col-sm-8">
<input type="text" class="form-control" id="title" name="row[title]" value="{$row.title}" data-rule="required" />
</div>
</div>
<div class="form-group">
<label for="name" class="control-label col-xs-12 col-sm-2">{:__('Type')}:</label>
<div class="col-xs-12 col-sm-8">
{:build_select('row[type]', $typedata, $row['type'], ['class'=>'form-control', 'data-rule'=>'required'])}
</div>
</div>
<div class="form-group">
<label for="content" class="control-label col-xs-12 col-sm-2">{:__('Content')}:</label>
<div class="col-xs-12 col-sm-8">
<textarea name="row[content]" id="conent" cols="30" rows="5" class="form-control" data-rule="required">{$row.content}</textarea>
</div>
</div>
<div class="form-group">
<label for="schedule" class="control-label col-xs-12 col-sm-2">{:__('Schedule')}:</label>
<div class="col-xs-12 col-sm-8">
<input type="text" class="form-control" id="schedule" style="font-size:12px;font-family: Verdana;word-spacing:23px;" name="row[schedule]" value="{$row.schedule}" data-rule="required; remote(general/crontab/check_schedule)" />
<div id="schedulepicker">
<pre><code>* * * * *
- - - - -
| | | | +--- day of week (0 - 7) (Sunday=0 or 7)
| | | +-------- month (1 - 12)
| | +------------- day of month (1 - 31)
| +------------------ hour (0 - 23)
+----------------------- min (0 - 59)</code></pre>
<h5>{:__('The next %s times the execution time', '<input type="number" id="pickdays" class="form-control text-center" value="7" style="display: inline-block;width:80px;">')}</h5>
<ol id="scheduleresult" class="list-group">
</ol>
</div>
</div>
</div>
<div class="form-group">
<label for="maximums" class="control-label col-xs-12 col-sm-2">{:__('Maximums')}:</label>
<div class="col-xs-12 col-sm-4">
<input type="number" class="form-control" id="maximums" name="row[maximums]" value="{$row.maximums}" data-rule="required" size="6" />
</div>
</div>
<div class="form-group">
<label for="begintime" class="control-label col-xs-12 col-sm-2">{:__('Begin time')}:</label>
<div class="col-xs-12 col-sm-4">
<input type="text" class="form-control datetimepicker" id="begintime" name="row[begintime]" value="{$row.begintime|datetime}" data-rule="{:__('Begin time')}:required" size="6" />
</div>
</div>
<div class="form-group">
<label for="endtime" class="control-label col-xs-12 col-sm-2">{:__('End time')}:</label>
<div class="col-xs-12 col-sm-4">
<input type="text" class="form-control datetimepicker" id="endtime" name="row[endtime]" value="{$row.endtime|datetime}" data-rule="{:__('End time')}:required;match(gte, row[begintime], datetime)" size="6" />
</div>
</div>
<div class="form-group">
<label for="weigh" class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label>
<div class="col-xs-12 col-sm-4">
<input type="text" class="form-control" id="weigh" name="row[weigh]" value="{$row.weigh}" data-rule="required" size="6" />
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
<div class="col-xs-12 col-sm-8">
{:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')], $row['status'])}
</div>
</div>
<div class="form-group hide layer-footer">
<label class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-8">
<button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
<button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
</div>
</div>
</form>

View File

@ -0,0 +1,28 @@
<div class="panel panel-default panel-intro">
{:build_heading()}
<div class="panel-body">
<div id="myTabContent" class="tab-content">
<div class="tab-pane fade active in" id="one">
<div class="widget-body no-padding">
<div id="toolbar" class="toolbar">
{:build_toolbar()}
<div class="dropdown btn-group {:$auth->check('general/crontab/multi')?'':'hide'}">
<a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
<ul class="dropdown-menu text-left" role="menu">
<li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=normal"><i class="fa fa-eye"></i> {:__('Set to normal')}</a></li>
<li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=hidden"><i class="fa fa-eye-slash"></i> {:__('Set to hidden')}</a></li>
</ul>
</div>
</div>
<table id="table" class="table table-striped table-bordered table-hover"
data-operate-edit="{:$auth->check('general/crontab/edit')}"
data-operate-del="{:$auth->check('general/crontab/del')}"
width="100%">
</table>
</div>
</div>
</div>
</div>
</div>

0
application/command.php 100755 → 100644
View File

0
application/common.php 100755 → 100644
View File

View File

@ -0,0 +1,54 @@
<?php
namespace app\common\model;
use think\Model;
class Crontab extends Model
{
// 开启自动写入时间戳字段
protected $autoWriteTimestamp = 'int';
// 定义时间戳字段名
protected $createTime = 'createtime';
protected $updateTime = 'updatetime';
// 定义字段类型
protected $type = [
];
// 追加属性
protected $append = [
'type_text'
];
public static function getTypeList()
{
return [
'url' => __('Request Url'),
'sql' => __('Execute Sql Script'),
'shell' => __('Execute Shell'),
];
}
public function getTypeTextAttr($value, $data)
{
$typelist = self::getTypeList();
$value = $value ? $value : $data['type'];
return $value && isset($typelist[$value]) ? $typelist[$value] : $value;
}
protected function setBegintimeAttr($value)
{
return $value && !is_numeric($value) ? strtotime($value) : $value;
}
protected function setEndtimeAttr($value)
{
return $value && !is_numeric($value) ? strtotime($value) : $value;
}
protected function setExecutetimeAttr($value)
{
return $value && !is_numeric($value) ? strtotime($value) : $value;
}
}

0
application/config.php 100755 → 100644
View File

112
application/database.php 100755 → 100644
View File

@ -1,56 +1,56 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
use think\Env;
return [
// 数据库类型
'type' => Env::get('database.type', 'mysql'),
// 服务器地址
'hostname' => Env::get('database.hostname', 'localhost'),
// 数据库名
'database' => Env::get('database.database', 'qulvxing'),
// 用户名
'username' => Env::get('database.username', 'root'),
// 密码
'password' => Env::get('database.password', 'root'),
// 端口
'hostport' => Env::get('database.hostport', ''),
// 连接dsn
'dsn' => '',
// 数据库连接参数
'params' => [],
// 数据库编码默认采用utf8
'charset' => Env::get('database.charset', 'utf8'),
// 数据库表前缀
'prefix' => Env::get('database.prefix', 'fa_'),
// 数据库调试模式
'debug' => Env::get('database.debug', true),
// 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
'deploy' => 0,
// 数据库读写是否分离 主从式有效
'rw_separate' => false,
// 读写分离后 主服务器数量
'master_num' => 1,
// 指定从服务器序号
'slave_no' => '',
// 是否严格检查字段是否存在
'fields_strict' => true,
// 数据集返回类型
'resultset_type' => 'array',
// 自动写入时间戳字段
'auto_timestamp' => false,
// 时间字段取出后的默认时间格式,默认为Y-m-d H:i:s
'datetime_format' => false,
// 是否需要进行SQL性能分析
'sql_explain' => false,
];
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
use think\Env;
return [
// 数据库类型
'type' => Env::get('database.type', 'mysql'),
// 服务器地址
'hostname' => Env::get('database.hostname', 'localhost'),
// 数据库名
'database' => Env::get('database.database', 'qulvxing'),
// 用户名
'username' => Env::get('database.username', 'jtl'),
// 密码
'password' => Env::get('database.password', '!jtl123361@GLOD.com'),
// 端口
'hostport' => Env::get('database.hostport', ''),
// 连接dsn
'dsn' => '',
// 数据库连接参数
'params' => [],
// 数据库编码默认采用utf8
'charset' => Env::get('database.charset', 'utf8'),
// 数据库表前缀
'prefix' => Env::get('database.prefix', 'fa_'),
// 数据库调试模式
'debug' => Env::get('database.debug', true),
// 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
'deploy' => 0,
// 数据库读写是否分离 主从式有效
'rw_separate' => false,
// 读写分离后 主服务器数量
'master_num' => 1,
// 指定从服务器序号
'slave_no' => '',
// 是否严格检查字段是否存在
'fields_strict' => true,
// 数据集返回类型
'resultset_type' => 'array',
// 自动写入时间戳字段
'auto_timestamp' => false,
// 时间字段取出后的默认时间格式,默认为Y-m-d H:i:s
'datetime_format' => false,
// 是否需要进行SQL性能分析
'sql_explain' => false,
];

View File

@ -4,6 +4,18 @@ return array (
'autoload' => false,
'hooks' =>
array (
'sms_send' =>
array (
0 => 'alisms',
),
'sms_notice' =>
array (
0 => 'alisms',
),
'sms_check' =>
array (
0 => 'alisms',
),
),
'route' =>
array (

View File

@ -1,7 +1,7 @@
<?php
return array (
'name' => 'FastAdmin',
'name' => '趣侣行',
'beian' => '',
'cdnurl' => '',
'version' => '1.0.1',
@ -18,6 +18,7 @@ return array (
'default' => 'Default',
'page' => 'Page',
'article' => 'Article',
'product_airport' => 'Product_airport',
'test' => 'Test',
),
'configgroup' =>

View File

0
application/route.php 100755 → 100644
View File

0
application/tags.php 100755 → 100644
View File

0
build.php 100755 → 100644
View File

0
composer.json 100755 → 100644
View File

0
public/.htaccess 100755 → 100644
View File

View File

@ -0,0 +1,141 @@
define([],function(){
var Properties={
formatter:{
property: function (value, row, index) {
return '<div class="input-group" style="width:100%"><input type="text" class="form-control input-sm" value="'+value+'" ></div>';
},
operate:function(value,row,index){
return '\<div class="btn-group btn-group-justified">\
<span class="btn btn-default property_drag" data-index="'+index+'" data-direction="up"><i class="fa fa-arrow-up" aria-hidden="true"></i></span>\
<span class="btn btn-default property_drag" data-index="'+index+'" data-direction="down"><i class="fa fa-arrow-down" aria-hidden="true"></i></span>\
<span class="btn btn-danger property_del" data-index="'+index+'"><i class="fa fa-times" aria-hidden="true"></i></span></div>';
}
},
//初始化表格
init: function(obj) {
obj.hide();
obj.before("<style>\
.swap_done{animation-name: swap;animation-duration: 1s;animation-timing-function: ease-out;animation-direction: alternate;animation-iteration-count: 1;animation-fill-mode: backwards;animation-play-state: running;}\
@keyframes swap{0%,100%{background-color:#f1f4f6}50%{background-color:#ddd}}\
</style>")
.before('<table class="properties"></table>')
.before('<button type="button" class="btn btn-success btn-embossed property_add" style="margin-top:15px;">添加参数</button>');
var table=obj.parent().find("table.properties")
if(obj.val()!=""){
Properties.create(table);
table.bootstrapTable('load',JSON.parse(obj.val()));
}
return table;
},
//创建表头,若存在则添加一行
create:function(table){
if(table.find("tbody").size()==0){
table.bootstrapTable({
showHeader:true,
mobileResponsive:true,
columns: [{
field: 'name',
title: '参数名',
class: 'property_input',
formatter:Properties.formatter.property
}, {
field: 'value',
title: '参数值',
class: 'property_input',
formatter:Properties.formatter.property
},
{
field: 'operate',
title: '操作',
width: '150px',
formatter:Properties.formatter.operate
}],
data:[{name:'',value:''}]
});
}else{
Properties.append(table);
}
},
//添加
append:function(table){
var property={
name:'',
value:''
}
table.bootstrapTable('append',property)
},
//删除
remove:function(table,index){
var data=table.bootstrapTable('getData',true);
data.splice(index,1),
table.bootstrapTable('load',data)
Properties.save(table);
},
//排序
sort:function(table,index,direction){
var data=table.bootstrapTable('getData',true);
switch(direction){
case 'up':
if(index>0){
table.bootstrapTable('load',Properties.swap(data,index,index-1));
table.find("tr[data-index="+(index-1)+"]").addClass('swap_done')
}else{
layer.tips('已经到达顶部',
table.find(".property_drag[data-index="+index+"][data-direction="+direction+"]"),
{
tips:1
});
}
break;
case 'down':
if(index<data.length-1){
table.bootstrapTable('load',Properties.swap(data,index,index+1));
table.find("tr[data-index="+(index+1)+"]").addClass('swap_done')
}else{
layer.tips('已经到达底部',
table.find(".property_drag[data-index="+index+"][data-direction="+direction+"]"),
{
tips:1
});
}
break;
}
Properties.save(table);
},
//设置属性
set:function(table,index){
var property_input=table.find("tr[data-index="+index+"] td.property_input");
var property={
name:property_input.first().find("input").val(),
value:property_input.last().find("input").val()
}
table.bootstrapTable('updateRow', {
index: index,
row: property
})
Properties.save(table);
},
//保存
save:function(table){
table.parents(".bootstrap-table")
.siblings('textarea').val(JSON.stringify(table.bootstrapTable('getData',true)))
},
//交换排序
swap:function(data, old_pos, new_pos) {
data[old_pos] = data.splice(new_pos, 1, data[old_pos])[0];
return data;
}
}
return Properties;
});

0
public/assets/fonts/captcha.ttf 100755 → 100644
View File

View File

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

0
public/assets/fonts/verdana.ttf 100755 → 100644
View File

0
public/assets/img/32px.png 100755 → 100644
View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

0
public/assets/img/40px.png 100755 → 100644
View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

0
public/assets/img/avatar.png 100755 → 100644
View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

0
public/assets/img/bg-middle.jpg 100755 → 100644
View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

0
public/assets/img/blank.gif 100755 → 100644
View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

0
public/assets/img/favicon.ico 100755 → 100644
View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

0
public/assets/img/loginbg.jpg 100755 → 100644
View File

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

0
public/assets/img/logo.png 100755 → 100644
View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

0
public/assets/img/throbber.gif 100755 → 100644
View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

0
public/assets/index.html 100755 → 100644
View File

View File

@ -1,3 +1,100 @@
define([], function () {
require(['form', 'upload'], function (Form, Upload) {
var _bindevent = Form.events.bindevent;
Form.events.bindevent = function (form) {
_bindevent.apply(this, [form]);
try {
//绑定自定义属性组件
if ($("textarea[id$=Properties]", form).size() > 0) {
require(['../addons/properties/Properties'], function (Properties){
var obj=$("textarea[id$=Properties]", form);
    var table=Properties.init(obj);
$(form).on("click",".property_add",function(event){
Properties.create(table);
})
$(form).on("click",".property_del",function(event){
Properties.remove(table,$(this).parents("tr").data("index"));
})
$(form).on("click",".property_drag",function(event){
Properties.sort(table,$(this).data("index"),$(this).data("direction"));
})
$(form).on("change","input",function(event){
Properties.set(table,$(this).parents("tr").data("index"));
})
  });
}
} catch (e) {
}
};
});
require(['form', 'upload'], function (Form, Upload) {
var _bindevent = Form.events.bindevent;
Form.events.bindevent = function (form) {
_bindevent.apply(this, [form]);
try {
//绑定summernote事件
if ($(".summernote,.editor", form).size() > 0) {
require(['summernote'], function () {
$(".summernote,.editor", form).summernote({
height: 250,
lang: 'zh-CN',
fontNames: [
'Arial', 'Arial Black', 'Serif', 'Sans', 'Courier',
'Courier New', 'Comic Sans MS', 'Helvetica', 'Impact', 'Lucida Grande',
"Open Sans", "Hiragino Sans GB", "Microsoft YaHei",
'微软雅黑', '宋体', '黑体', '仿宋', '楷体', '幼圆',
],
fontNamesIgnoreCheck: [
"Open Sans", "Microsoft YaHei",
'微软雅黑', '宋体', '黑体', '仿宋', '楷体', '幼圆'
],
toolbar: [
['style', ['style', 'undo', 'redo']],
['font', ['bold', 'underline', 'strikethrough', 'clear']],
['fontname', ['color', 'fontname', 'fontsize']],
['para', ['ul', 'ol', 'paragraph', 'height']],
['table', ['table', 'hr']],
['insert', ['link', 'picture', 'video']],
['view', ['fullscreen', 'codeview', 'help']]
],
dialogsInBody: true,
callbacks: {
onChange: function (contents) {
$(this).val(contents);
$(this).trigger('change');
},
onInit: function () {
},
onImageUpload: function (files) {
var that = this;
//依次上传图片
for (var i = 0; i < files.length; i++) {
Upload.api.send(files[i], function (data) {
var url = Fast.api.cdnurl(data.url);
$(that).summernote("insertImage", url, 'filename');
});
}
}
}
});
});
}
} catch (e) {
}
};
});
});

0
public/assets/js/backend.js 100755 → 100644
View File

View File

View File

View File

View File

View File

@ -0,0 +1,79 @@
define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
var Controller = {
index: function () {
// 初始化表格参数配置
Table.api.init({
extend: {
index_url: 'general/crontab/index',
add_url: 'general/crontab/add',
edit_url: 'general/crontab/edit',
del_url: 'general/crontab/del',
multi_url: 'general/crontab/multi',
table: 'crontab'
}
});
var table = $("#table");
// 初始化表格
table.bootstrapTable({
url: $.fn.bootstrapTable.defaults.extend.index_url,
sortName: 'weigh',
columns: [
[
{field: 'state', checkbox: true, },
{field: 'id', title: 'ID'},
{field: 'type_text', title: __('Type'), operate: false},
{field: 'title', title: __('Title')},
{field: 'maximums', title: __('Maximums'), formatter: Controller.api.formatter.maximums},
{field: 'executes', title: __('Executes')},
{field: 'begintime', title: __('Begin time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange'},
{field: 'endtime', title: __('End time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange'},
{field: 'nexttime', title: __('Next execute time'), formatter: Table.api.formatter.datetime, operate: false},
{field: 'executetime', title: __('Execute time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange'},
{field: 'weigh', title: __('Weigh')},
{field: 'status', title: __('Status'), formatter: Table.api.formatter.status},
{field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
]
]
});
// 为表格绑定事件
Table.api.bindevent(table);
},
add: function () {
Controller.api.bindevent();
},
edit: function () {
Controller.api.bindevent();
},
api: {
bindevent: function () {
$('#schedule').on('valid.field', function (e, result) {
$("#pickdays").trigger("change");
});
Form.api.bindevent($("form[role=form]"));
$(document).on("change", "#pickdays", function () {
$("#scheduleresult").html(__('Loading'));
$.post("general/crontab/get_schedule_future", {schedule: $("#schedule").val(), days: $(this).val()}, function (ret) {
$("#scheduleresult").html("");
if (typeof ret.futuretime !== 'undefined' && $.isArray(ret.futuretime)) {
$.each(ret.futuretime, function (i, j) {
$("#scheduleresult").append("<li class='list-group-item'>" + j + "<span class='badge'>" + (i + 1) + "</span></li>");
});
}
}, 'json');
});
$("#pickdays").trigger("change");
},
formatter: {
maximums: function (value, row, index) {
return value === 0 ? __('No limit') : value;
}
}
}
};
return Controller;
});

View File

0
public/assets/js/backend/index.js 100755 → 100644
View File

0
public/assets/js/html5shiv.js vendored 100755 → 100644
View File

0
public/assets/js/jquery.drag.min.js vendored 100755 → 100644
View File

0
public/assets/js/jquery.drop.min.js vendored 100755 → 100644
View File

0
public/assets/js/require-css.min.js vendored 100755 → 100644
View File

0
public/assets/js/require-form.js 100755 → 100644
View File

View File

0
public/assets/js/require.js 100755 → 100644
View File

Some files were not shown because too many files have changed in this diff Show More