作者 Karson

新增会员分组、权限规则、会员编辑Token验证

新增分类编辑Token验证
新增后台禁用Referer携带
新增弹窗自定义关闭按钮事件
新增后台安全提示
新增插件伪静态优先级配置
新增插件卸载移除空目录
修复语言包更新后不生效的BUG
修复fa_area和fa_version表不存在的BUG
修复编辑会员日期错误的BUG
修复会员删除事件未触发的BUG
修复页面跳转BUG
修复后台左侧菜单指示未切换的BUG
修复Bootstrap-Table切换语言不生效的BUG
更新PHPMailer版本
移除EasyWechat和Qrcode依赖
优化本地插件加载速度
/*
FastAdmin Install SQL
Date: 2020年04月23日
Date: 2020-06-11 22:11:09
*/
SET FOREIGN_KEY_CHECKS = 0;
... ... @@ -54,6 +54,27 @@ CREATE TABLE `fa_admin_log` (
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='管理员日志表';
-- ----------------------------
-- Table structure for fa_area
-- ----------------------------
DROP TABLE IF EXISTS `fa_area`;
CREATE TABLE `fa_area` (
`id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`pid` int(10) DEFAULT NULL COMMENT '父id',
`shortname` varchar(100) DEFAULT NULL COMMENT '简称',
`name` varchar(100) DEFAULT NULL COMMENT '名称',
`mergename` varchar(255) DEFAULT NULL COMMENT '全称',
`level` tinyint(4) DEFAULT NULL COMMENT '层级 0 1 2 省市区县',
`pinyin` varchar(100) DEFAULT NULL COMMENT '拼音',
`code` varchar(100) DEFAULT NULL COMMENT '长途区号',
`zip` varchar(100) DEFAULT NULL COMMENT '邮编',
`first` varchar(50) DEFAULT NULL COMMENT '首字母',
`lng` varchar(100) DEFAULT NULL COMMENT '经度',
`lat` varchar(100) DEFAULT NULL COMMENT '纬度',
PRIMARY KEY (`id`),
KEY `pid` (`pid`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='地区表';
-- ----------------------------
-- Table structure for fa_attachment
-- ----------------------------
DROP TABLE IF EXISTS `fa_attachment`;
... ... @@ -337,7 +358,7 @@ CREATE TABLE `fa_ems` (
`code` varchar(10) NOT NULL DEFAULT '' COMMENT '验证码',
`times` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '验证次数',
`ip` varchar(30) NOT NULL DEFAULT '' COMMENT 'IP',
`createtime` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '创建时间',
`createtime` int(10) DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='邮箱验证码表';
... ... @@ -418,7 +439,7 @@ CREATE TABLE `fa_user` (
`avatar` varchar(255) NOT NULL DEFAULT '' COMMENT '头像',
`level` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '等级',
`gender` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '性别',
`birthday` date COMMENT '生日',
`birthday` date DEFAULT NULL COMMENT '生日',
`bio` varchar(100) NOT NULL DEFAULT '' COMMENT '格言',
`money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '余额',
`score` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '积分',
... ... @@ -546,4 +567,24 @@ CREATE TABLE `fa_user_token` (
`expiretime` int(10) DEFAULT NULL COMMENT '过期时间',
PRIMARY KEY (`token`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='会员Token表';
-- ----------------------------
-- Table structure for fa_version
-- ----------------------------
DROP TABLE IF EXISTS `fa_version`;
CREATE TABLE `fa_version` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`oldversion` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '旧版本号',
`newversion` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '新版本号',
`packagesize` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '包大小',
`content` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '升级内容',
`downloadurl` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '下载地址',
`enforce` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '强制更新',
`createtime` int(10) DEFAULT NULL COMMENT '创建时间',
`updatetime` int(10) DEFAULT NULL COMMENT '更新时间',
`weigh` int(10) NOT NULL DEFAULT 0 COMMENT '权重',
`status` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '状态',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='版本表';
SET FOREIGN_KEY_CHECKS = 1;
... ...
... ... @@ -316,7 +316,10 @@ class Addon extends Backend
$onlineaddons = Cache::get("onlineaddons");
if (!is_array($onlineaddons)) {
$onlineaddons = [];
$result = Http::sendRequest(config('fastadmin.api_url') . '/addon/index');
$result = Http::sendRequest(config('fastadmin.api_url') . '/addon/index', [], 'GET', [
CURLOPT_HTTPHEADER => ['Accept-Encoding:gzip'],
CURLOPT_ENCODING => "gzip"
]);
if ($result['ret']) {
$json = (array)json_decode($result['msg'], true);
$rows = isset($json['rows']) ? $json['rows'] : [];
... ...
... ... @@ -82,6 +82,17 @@ class Category extends Backend
}
/**
* 添加
*/
public function add()
{
if ($this->request->isPost()) {
$this->token();
}
return parent::add();
}
/**
* 编辑
*/
public function edit($ids = null)
... ... @@ -97,6 +108,7 @@ class Category extends Backend
}
}
if ($this->request->isPost()) {
$this->token();
$params = $this->request->post("row/a");
if ($params) {
$params = $this->preExcludeFields($params);
... ...
... ... @@ -26,16 +26,23 @@ class Group extends Backend
public function add()
{
if ($this->request->isPost()) {
$this->token();
}
$nodeList = \app\admin\model\UserRule::getTreeList();
$this->assign("nodeList", $nodeList);
return parent::add();
}
public function edit($ids = NULL)
public function edit($ids = null)
{
if ($this->request->isPost()) {
$this->token();
}
$row = $this->model->get($ids);
if (!$row)
if (!$row) {
$this->error(__('No Results were found'));
}
$rules = explode(',', $row['rules']);
$nodeList = \app\admin\model\UserRule::getTreeList($rules);
$this->assign("nodeList", $nodeList);
... ...
... ... @@ -13,7 +13,6 @@ use fast\Tree;
class Rule extends Backend
{
/**
* @var \app\admin\model\UserRule
*/
... ... @@ -28,8 +27,7 @@ class Rule extends Backend
$this->view->assign("statusList", $this->model->getStatusList());
// 必须将结果集转换为数组
$ruleList = collection($this->model->order('weigh', 'desc')->select())->toArray();
foreach ($ruleList as $k => &$v)
{
foreach ($ruleList as $k => &$v) {
$v['title'] = __($v['title']);
$v['remark'] = __($v['remark']);
}
... ... @@ -37,10 +35,10 @@ class Rule extends Backend
Tree::instance()->init($ruleList);
$this->rulelist = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0), 'title');
$ruledata = [0 => __('None')];
foreach ($this->rulelist as $k => &$v)
{
if (!$v['ismenu'])
foreach ($this->rulelist as $k => &$v) {
if (!$v['ismenu']) {
continue;
}
$ruledata[$v['id']] = $v['title'];
}
$this->view->assign('ruledata', $ruledata);
... ... @@ -51,8 +49,7 @@ class Rule extends Backend
*/
public function index()
{
if ($this->request->isAjax())
{
if ($this->request->isAjax()) {
$list = $this->rulelist;
$total = count($this->rulelist);
... ... @@ -64,21 +61,40 @@ class Rule extends Backend
}
/**
* 添加
*/
public function add()
{
if ($this->request->isPost()) {
$this->token();
}
return parent::add();
}
/**
* 编辑
*/
public function edit($ids = null)
{
if ($this->request->isPost()) {
$this->token();
}
return parent::edit($ids);
}
/**
* 删除
*/
public function del($ids = "")
{
if ($ids)
{
if ($ids) {
$delIds = [];
foreach (explode(',', $ids) as $k => $v)
{
$delIds = array_merge($delIds, Tree::instance()->getChildrenIds($v, TRUE));
foreach (explode(',', $ids) as $k => $v) {
$delIds = array_merge($delIds, Tree::instance()->getChildrenIds($v, true));
}
$delIds = array_unique($delIds);
$count = $this->model->where('id', 'in', $delIds)->delete();
if ($count)
{
if ($count) {
$this->success();
}
}
... ...
... ... @@ -3,6 +3,7 @@
namespace app\admin\controller\user;
use app\common\controller\Backend;
use app\common\library\Auth;
/**
* 会员管理
... ... @@ -13,7 +14,7 @@ class User extends Backend
{
protected $relationSearch = true;
protected $searchFields = 'id,username,nickname';
/**
* @var \app\admin\model\User
... ... @@ -61,16 +62,45 @@ class User extends Backend
}
/**
* 添加
*/
public function add()
{
if ($this->request->isPost()) {
$this->token();
}
return parent::add();
}
/**
* 编辑
*/
public function edit($ids = NULL)
public function edit($ids = null)
{
if ($this->request->isPost()) {
$this->token();
}
$row = $this->model->get($ids);
$this->modelValidate = true;
if (!$row)
if (!$row) {
$this->error(__('No Results were found'));
}
$this->view->assign('groupList', build_select('row[group_id]', \app\admin\model\UserGroup::column('id,name'), $row['group_id'], ['class' => 'form-control selectpicker']));
return parent::edit($ids);
}
/**
* 删除
*/
public function del($ids = "")
{
$row = $this->model->get($ids);
$this->modelValidate = true;
if (!$row) {
$this->error(__('No Results were found'));
}
Auth::instance()->delete($row['id']);
$this->success();
}
}
... ...
... ... @@ -54,4 +54,5 @@ return [
'Forum' => '交流社区',
'QQ qun' => 'QQ交流群',
'Captcha' => '验证码',
'Security tips' => '<i class="fa fa-warning"></i> 安全提示:为了你的后台安全,请勿将后台管理入口设置为admin或admin.php',
];
... ...
... ... @@ -101,6 +101,11 @@ class User extends Model
return $value && !is_numeric($value) ? strtotime($value) : $value;
}
protected function setBirthdayAttr($value)
{
return $value ? $value : null;
}
public function group()
{
return $this->belongsTo('UserGroup', 'group_id', 'id', [], 'LEFT')->setEagerlyType(0);
... ...
... ... @@ -10,13 +10,13 @@ class User extends Validate
* 验证规则
*/
protected $rule = [
'username' => 'require|regex:\w{3,12}|unique:user',
'username' => 'require|regex:\w{3,32}|unique:user',
'nickname' => 'require|unique:user',
'password' => 'regex:\S{6,32}',
'email' => 'require|email|unique:user',
'mobile' => 'require|unique:user'
'mobile' => 'unique:user'
];
/**
* 字段描述
*/
... ... @@ -46,5 +46,5 @@ class User extends Validate
];
parent::__construct($rules, $message, $field);
}
}
... ...
<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
{:token()}
<div class="alert alert-warning-light">
{:__('Category warmtips')}
</div>
... ...
<form id="edit-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
{:token()}
<div class="form-group">
<label for="c-type" class="control-label col-xs-12 col-sm-2">{:__('Type')}:</label>
<div class="col-xs-12 col-sm-8">
... ...
... ... @@ -2,6 +2,7 @@
<title>{$title|default=''}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta name="renderer" content="webkit">
<meta name="referrer" content="never">
<link rel="shortcut icon" href="__CDN__/assets/img/favicon.ico" />
<!-- Loading Bootstrap -->
... ... @@ -16,4 +17,4 @@
var require = {
config: {$config|json_encode}
};
</script>
\ No newline at end of file
</script>
... ...
... ... @@ -5,10 +5,17 @@
{include file="common/meta" /}
</head>
<body class="hold-transition skin-green sidebar-mini fixed {if $Think.config.fastadmin.multiplenav}multiplenav{/if}" id="tabs">
<div class="wrapper">
<!-- 头部区域 -->
<header id="header" class="main-header">
{if preg_match('/\/admin\/|admin\.php|admin_d75KABNWt\.php/i', url())}
<div class="alert alert-danger-light text-center" style="margin-bottom:0;border:none;">
{:__('Security tips')}
</div>
{/if}
{include file='common/header' /}
</header>
... ... @@ -46,4 +53,4 @@
<!-- 加载JS脚本 -->
{include file="common/script" /}
</body>
</html>
\ No newline at end of file
</html>
... ...
<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
{:token()}
<input type="hidden" name="row[rules]" />
<div class="form-group">
<label for="c-name" class="control-label col-xs-12 col-sm-2">{:__('Name')}:</label>
... ... @@ -21,7 +22,7 @@
<div class="radio">
{foreach name="statusList" item="vo"}
<label for="row[status]-{$key}"><input id="row[status]-{$key}" name="row[status]" type="radio" value="{$key}" {in name="key" value="normal"}checked{/in} /> {$vo}</label>
<label for="row[status]-{$key}"><input id="row[status]-{$key}" name="row[status]" type="radio" value="{$key}" {in name="key" value="normal"}checked{/in} /> {$vo}</label>
{/foreach}
</div>
... ... @@ -37,4 +38,4 @@
</form>
<script>
var nodeData = {:json_encode($nodeList); };
</script>
\ No newline at end of file
</script>
... ...
<form id="edit-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
{:token()}
<input type="hidden" name="row[rules]" value="{$row.rules}" />
<div class="form-group">
<label for="c-name" class="control-label col-xs-12 col-sm-2">{:__('Name')}:</label>
... ... @@ -18,10 +19,10 @@
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
<div class="col-xs-12 col-sm-8">
<div class="radio">
{foreach name="statusList" item="vo"}
<label for="row[status]-{$key}"><input id="row[status]-{$key}" name="row[status]" type="radio" value="{$key}" {in name="key" value="$row.status"}checked{/in} /> {$vo}</label>
<label for="row[status]-{$key}"><input id="row[status]-{$key}" name="row[status]" type="radio" value="{$key}" {in name="key" value="$row.status"}checked{/in} /> {$vo}</label>
{/foreach}
</div>
... ...
<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
{:token()}
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Ismenu')}:</label>
<div class="col-xs-12 col-sm-8">
... ...
<form id="edit-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
{:token()}
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Ismenu')}:</label>
<div class="col-xs-12 col-sm-8">
... ...
<form id="edit-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
{:token()}
<input type="hidden" name="row[id]" value="{$row.id}">
<div class="form-group">
<label for="c-group_id" class="control-label col-xs-12 col-sm-2">{:__('Group')}:</label>
... ...
... ... @@ -90,7 +90,8 @@ class User extends Model
$user = self::get($user_id);
if ($user && $money != 0) {
$before = $user->money;
$after = $user->money + $money;
//$after = $user->money + $money;
$after = function_exists('bcadd') ? bcadd($user->money, $money, 2) : $user->money + $money;
//更新会员信息
$user->save(['money' => $after]);
//写入日志
... ...
... ... @@ -52,7 +52,7 @@
var interval = setInterval(function () {
var time = --wait.innerHTML;
if (time <= 0) {
location.href = history.length <= 1 ? "/" : "{$url}";
location.href = "{$url}";
clearInterval(interval);
}
}, 1000);
... ... @@ -60,4 +60,4 @@
</script>
{/if}
</body>
</html>
\ No newline at end of file
</html>
... ...
... ... @@ -213,7 +213,7 @@ return [
// cookie 启用安全传输
'secure' => false,
// httponly设置
'httponly' => '',
'httponly' => true,
// 是否使用 setcookie
'setcookie' => true,
],
... ... @@ -276,7 +276,7 @@ return [
//自动检测更新
'checkupdate' => false,
//版本号
'version' => '1.0.0.20200506_beta',
'version' => '1.1.0.20200612_beta',
//API接口地址
'api_url' => 'https://api.fastadmin.net',
],
... ...
... ... @@ -8,4 +8,7 @@ return array (
'route' =>
array (
),
'priority' =>
array (
),
);
\ No newline at end of file
... ...
... ... @@ -3,7 +3,7 @@
"description": "the fastest admin framework",
"main": "",
"license": "Apache2.0",
"homepage": "http://www.fastadmin.net",
"homepage": "https://www.fastadmin.net",
"private": true,
"dependencies": {
"jquery": "^2.1.4",
... ...
... ... @@ -11,18 +11,15 @@
"authors": [
{
"name": "Karson",
"email": "karsonzhang@163.com"
"email": "karson@fastadmin.net"
}
],
"require": {
"php": ">=5.6.0",
"php": ">=7.0.0",
"topthink/framework": "~5.0.24",
"overtrue/wechat": "~3.1",
"endroid/qr-code": "^1.9",
"topthink/think-captcha": "^1.0",
"mtdowling/cron-expression": "^1.2",
"phpmailer/phpmailer": "~6.0.6",
"karsonzhang/fastadmin-addons": "~1.1.9",
"phpmailer/phpmailer": "~6.1.6",
"karsonzhang/fastadmin-addons": "~1.1.11",
"overtrue/pinyin": "~3.0",
"phpoffice/phpspreadsheet": "^1.2"
},
... ...
... ... @@ -1005,4 +1005,11 @@ table.table-nowrap thead > tr > th {
text-align: center;
display: inline-block;
}
.sidebar-menu li.treeview-open > a > .fa-angle-left,
.sidebar-menu li.treeview-open > a > .pull-right-container > .fa-angle-left {
-webkit-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
-o-transform: rotate(-90deg);
transform: rotate(-90deg);
}
/*# sourceMappingURL=backend.css.map */
\ No newline at end of file
... ...
... ... @@ -408,7 +408,6 @@ function _init() {
//Get the clicked link and the next element
var $this = $(this);
var checkElement = $this.next();
//Check if the next element is a menu and is visible
if ((checkElement.is('.treeview-menu')) && (checkElement.is(':visible')) && (!$('body').hasClass('sidebar-collapse'))) {
//Close the menu
... ... @@ -418,6 +417,7 @@ function _init() {
//_this.layout.fix();
});
checkElement.parent("li").removeClass("active");
checkElement.parent("li").removeClass('treeview-open');
}
//If the menu is not visible
else if ((checkElement.is('.treeview-menu')) && (!checkElement.is(':visible'))) {
... ... @@ -429,6 +429,7 @@ function _init() {
var ul = parent.find('ul:visible').slideUp(animationSpeed);
//Remove the menu-open class from the parent
ul.removeClass('menu-open');
parent.find('li.treeview').removeClass("treeview-open");
}
//Get the parent li
var parent_li = $this.parent("li");
... ... @@ -442,6 +443,7 @@ function _init() {
//Fix the layout in case the sidebar stretches over the height of the window
_this.layout.fix();
});
parent_li.addClass('treeview-open');
} else {
if (!$this.parent().hasClass("active")) {
$this.parent().addClass("active");
... ...
... ... @@ -142,7 +142,7 @@ require(['jquery', 'bootstrap'], function ($, undefined) {
window.Config = Config;
// 配置语言包的路径
var paths = {};
paths['lang'] = Config.moduleurl + '/ajax/lang?callback=define&controllername=' + Config.controllername + '&lang=' + Config.language;
paths['lang'] = Config.moduleurl + '/ajax/lang?callback=define&controllername=' + Config.controllername + '&lang=' + Config.language + '&v=' + Config.site.version;
// 避免目录冲突
paths['backend/'] = 'backend/';
require.config({paths: paths});
... ...
... ... @@ -83,6 +83,12 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U
//移除提交按钮的disabled类
$(".layer-footer [type=submit],.fixed-footer [type=submit],.normal-footer [type=submit]", form).removeClass("disabled");
//自定义关闭按钮事件
form.on("click", ".layer-close", function () {
var index = parent.Layer.getFrameIndex(window.name);
parent.Layer.close(index);
return false;
});
},
selectpicker: function (form) {
//绑定select元素事件
... ... @@ -503,4 +509,4 @@ define(['jquery', 'bootstrap', 'upload', 'validator'], function ($, undefined, U
},
};
return Form;
});
\ No newline at end of file
});
... ...
... ... @@ -141,7 +141,7 @@ require(['jquery', 'bootstrap'], function ($, undefined) {
window.Config = Config;
// 配置语言包的路径
var paths = {};
paths['lang'] = Config.moduleurl + '/ajax/lang?callback=define&controllername=' + Config.controllername;
paths['lang'] = Config.moduleurl + '/ajax/lang?callback=define&controllername=' + Config.controllername + '&lang=' + Config.language + '&v=' + Config.site.version;
// 避免目录冲突
paths['frontend/'] = 'frontend/';
require.config({paths: paths});
... ...
... ... @@ -28,7 +28,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
singleSelect: false, //是否启用单选
showRefresh: false,
showJumpto: true,
locale: 'zh-CN',
locale: Config.language == 'zh-cn' ? 'zh-CN' : 'en-US',
showToggle: true,
showColumns: true,
pk: 'id',
... ...
... ... @@ -1255,4 +1255,11 @@ table.table-nowrap {
margin-right: 5px;
text-align: center;
display: inline-block;
}
\ No newline at end of file
}
.sidebar-menu li.treeview-open > a > .fa-angle-left, .sidebar-menu li.treeview-open > a > .pull-right-container > .fa-angle-left {
-webkit-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
-o-transform: rotate(-90deg);
transform: rotate(-90deg);
}
... ...