mirror of
https://gitee.com/wonderful-code/buildadmin
synced 2024-11-21 22:55:36 +00:00
feat:增加会员权限类、完善前端基类、其他代码优化
This commit is contained in:
parent
2bd9e2c0c0
commit
a58a638ea6
@ -52,7 +52,7 @@ class Admin extends Backend
|
||||
}
|
||||
|
||||
$salt = Random::build('alnum', 16);
|
||||
$passwd = $this->model->encryptPassword($data['password'], $salt);
|
||||
$passwd = encrypt_password($data['password'], $salt);
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
$result = false;
|
||||
|
@ -37,7 +37,7 @@ class User extends Backend
|
||||
}
|
||||
|
||||
$salt = Random::build('alnum', 16);
|
||||
$passwd = $this->model->encryptPassword($data['password'], $salt);
|
||||
$passwd = encrypt_password($data['password'], $salt);
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
$result = false;
|
||||
|
@ -118,7 +118,7 @@ class Auth extends \ba\Auth
|
||||
$this->setError('Please try again after 1 day');
|
||||
return false;
|
||||
}
|
||||
if ($this->model->password != Admin::encryptPassword($password, $this->model->salt)) {
|
||||
if ($this->model->password != encrypt_password($password, $this->model->salt)) {
|
||||
$this->loginFailed();
|
||||
$this->setError('Password is incorrect');
|
||||
return false;
|
||||
@ -276,23 +276,6 @@ class Auth extends \ba\Auth
|
||||
return $this->allowFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测一个方法是否在传递的数组内
|
||||
* @return bool
|
||||
*/
|
||||
public function actionInArr($arr = [])
|
||||
{
|
||||
$arr = is_array($arr) ? $arr : explode(',', $arr);
|
||||
if (!$arr) {
|
||||
return false;
|
||||
}
|
||||
$arr = array_map('strtolower', $arr);
|
||||
if (in_array(strtolower(request()->action()), $arr) || in_array('*', $arr)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置允许输出字段
|
||||
* @param $fields
|
||||
|
@ -71,16 +71,8 @@ class Admin extends Model
|
||||
public function resetPassword($uid, $newPassword)
|
||||
{
|
||||
$salt = Random::build('alnum', 16);
|
||||
$passwd = self::encryptPassword($newPassword, $salt);
|
||||
$passwd = encrypt_password($newPassword, $salt);
|
||||
$ret = $this->where(['id' => $uid])->update(['password' => $passwd, 'salt' => $salt]);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密密码
|
||||
*/
|
||||
public static function encryptPassword($password, $salt = '', $encrypt = 'md5')
|
||||
{
|
||||
return $encrypt($encrypt($password) . $salt);
|
||||
}
|
||||
}
|
@ -31,19 +31,11 @@ class User extends Model
|
||||
public function resetPassword($uid, $newPassword)
|
||||
{
|
||||
$salt = Random::build('alnum', 16);
|
||||
$passwd = self::encryptPassword($newPassword, $salt);
|
||||
$passwd = encrypt_password($newPassword, $salt);
|
||||
$ret = $this->where(['id' => $uid])->update(['password' => $passwd, 'salt' => $salt]);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密密码
|
||||
*/
|
||||
public static function encryptPassword($password, $salt = '', $encrypt = 'md5')
|
||||
{
|
||||
return $encrypt($encrypt($password) . $salt);
|
||||
}
|
||||
|
||||
public function group()
|
||||
{
|
||||
return $this->belongsTo(UserGroup::class, 'group_id');
|
||||
|
@ -138,6 +138,35 @@ if (!function_exists('full_url')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('encrypt_password')) {
|
||||
/**
|
||||
* 加密密码
|
||||
*/
|
||||
function encrypt_password($password, $salt = '', $encrypt = 'md5')
|
||||
{
|
||||
return $encrypt($encrypt($password) . $salt);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('action_in_arr')) {
|
||||
/**
|
||||
* 检测一个方法是否在传递的数组内
|
||||
* @return bool
|
||||
*/
|
||||
function action_in_arr($arr = [])
|
||||
{
|
||||
$arr = is_array($arr) ? $arr : explode(',', $arr);
|
||||
if (!$arr) {
|
||||
return false;
|
||||
}
|
||||
$arr = array_map('strtolower', $arr);
|
||||
if (in_array(strtolower(request()->action()), $arr) || in_array('*', $arr)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('build_suffix_svg')) {
|
||||
/**
|
||||
* 构建文件后缀的svg图片
|
||||
|
@ -98,14 +98,14 @@ class Backend extends Api
|
||||
$this->auth = Auth::instance();
|
||||
$routePath = $this->app->request->controllerPath . '/' . $this->request->action(true);
|
||||
$token = $this->request->server('HTTP_BATOKEN', $this->request->request('batoken', Cookie::get('batoken') ?: false));
|
||||
if (!$this->auth->actionInArr($this->noNeedLogin)) {
|
||||
if (!action_in_arr($this->noNeedLogin)) {
|
||||
$this->auth->init($token);
|
||||
if (!$this->auth->isLogin()) {
|
||||
$this->error(__('Please login first'), [
|
||||
'routeName' => 'adminLogin'
|
||||
], 302);
|
||||
}
|
||||
if (!$this->auth->actionInArr($this->noNeedPermission)) {
|
||||
if (!action_in_arr($this->noNeedPermission)) {
|
||||
if (!$this->auth->check($routePath)) {
|
||||
$this->error(__('You have no permission'), [
|
||||
'routeName' => 'admin'
|
||||
|
@ -2,11 +2,15 @@
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use think\facade\Event;
|
||||
use think\facade\Cookie;
|
||||
use app\common\library\Auth;
|
||||
|
||||
class Frontend extends Api
|
||||
{
|
||||
/**
|
||||
* 无需登录的方法
|
||||
* 访问本控制器的此方法,无需管理员登录
|
||||
* 访问本控制器的此方法,无需会员登录
|
||||
*/
|
||||
protected $noNeedLogin = [];
|
||||
|
||||
@ -17,12 +21,37 @@ class Frontend extends Api
|
||||
|
||||
/**
|
||||
* 权限类实例
|
||||
* @var
|
||||
* @var Auth
|
||||
*/
|
||||
protected $auth = null;
|
||||
|
||||
public function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
$this->auth = Auth::instance();
|
||||
$routePath = $this->app->request->controllerPath . '/' . $this->request->action(true);
|
||||
$token = $this->request->server('HTTP_BA_USER_TOKEN', $this->request->request('ba-user-token', Cookie::get('ba-user-token') ?: false));
|
||||
if (!action_in_arr($this->noNeedLogin)) {
|
||||
$this->auth->init($token);
|
||||
if (!$this->auth->isLogin()) {
|
||||
$this->error(__('Please login first'), [
|
||||
'routeName' => 'userLogin'
|
||||
], 302);
|
||||
}
|
||||
if (!action_in_arr($this->noNeedPermission)) {
|
||||
if (!$this->auth->check($routePath)) {
|
||||
$this->error(__('You have no permission'), [
|
||||
'routeName' => 'user'
|
||||
], 302);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($token) {
|
||||
$this->auth->init($token);
|
||||
}
|
||||
}
|
||||
|
||||
// 会员验权和登录标签位
|
||||
Event::trigger('frontendInit');
|
||||
}
|
||||
}
|
367
app/common/library/Auth.php
Normal file
367
app/common/library/Auth.php
Normal file
@ -0,0 +1,367 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\library;
|
||||
|
||||
use ba\Random;
|
||||
use think\Exception;
|
||||
use think\facade\Db;
|
||||
use think\facade\Config;
|
||||
use app\common\model\User;
|
||||
use app\common\facade\Token;
|
||||
use think\facade\Validate;
|
||||
|
||||
/**
|
||||
* 公共权限类(会员权限类)
|
||||
*/
|
||||
class Auth extends \ba\Auth
|
||||
{
|
||||
/**
|
||||
* @var bool 是否登录
|
||||
*/
|
||||
protected $logined = false;
|
||||
|
||||
/**
|
||||
* @var string 错误消息
|
||||
*/
|
||||
protected $error = '';
|
||||
|
||||
/**
|
||||
* @var User Model实例
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
/**
|
||||
* @var string 令牌
|
||||
*/
|
||||
protected $token = '';
|
||||
|
||||
/**
|
||||
* @var string 刷新令牌
|
||||
*/
|
||||
protected $refreshToken = '';
|
||||
|
||||
/**
|
||||
* @var int 令牌默认有效期
|
||||
*/
|
||||
protected $keeptime = 86400;
|
||||
|
||||
/**
|
||||
* @var string[] 允许输出的字段
|
||||
*/
|
||||
protected $allowFields = ['id', 'username', 'nickname', 'avatar', 'lastlogintime', 'lastloginip'];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct([
|
||||
'auth_group' => 'user_group', // 用户组数据表名
|
||||
'auth_group_access' => '', // 用户-用户组关系表(关系字段)
|
||||
'user_rule' => 'menu_rule', // 权限规则表
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 魔术方法-会员信息字段
|
||||
* @param $name
|
||||
* @return null|string 字段信息
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
return $this->model ? $this->model->$name : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Token初始化管理员登录态
|
||||
* @param $token
|
||||
* @return bool
|
||||
*/
|
||||
public function init($token)
|
||||
{
|
||||
if ($this->logined) {
|
||||
return true;
|
||||
}
|
||||
if ($this->error) {
|
||||
return false;
|
||||
}
|
||||
$tokenData = Token::get($token);
|
||||
if (!$tokenData) {
|
||||
return false;
|
||||
}
|
||||
$userId = intval($tokenData['user_id']);
|
||||
if ($tokenData['type'] == 'user' && $userId > 0) {
|
||||
$this->model = User::where('id', $userId)->find();
|
||||
if (!$this->model) {
|
||||
$this->setError('Account not exist');
|
||||
return false;
|
||||
}
|
||||
if ($this->model['status'] != 'enable') {
|
||||
$this->setError('Account disabled');
|
||||
return false;
|
||||
}
|
||||
$this->token = $token;
|
||||
$this->loginSuccessful();
|
||||
return true;
|
||||
} else {
|
||||
$this->setError('Token login failed');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员登录
|
||||
* @param $username
|
||||
* @param $password
|
||||
* @param bool $keeptime
|
||||
* @return bool
|
||||
*/
|
||||
public function login($username, $password, $keeptime = false)
|
||||
{
|
||||
// 判断账户类型
|
||||
$accountType = false;
|
||||
$validate = Validate::rule([
|
||||
'mobile' => 'mobile',
|
||||
'email' => 'email',
|
||||
'username' => 'regex:^[a-zA-Z][a-zA-Z0-9_]{2,15}$',
|
||||
]);
|
||||
if ($validate->check(['mobile' => $username])) $accountType = 'mobile';
|
||||
if ($validate->check(['email' => $username])) $accountType = 'email';
|
||||
if ($validate->check(['username' => $username])) $accountType = 'username';
|
||||
if (!$accountType) {
|
||||
$this->setError('Account is incorrect');
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->model = User::where($accountType, $username)->find();
|
||||
if (!$this->model) {
|
||||
$this->setError('Account is incorrect');
|
||||
return false;
|
||||
}
|
||||
if ($this->model['status'] == 'disable') {
|
||||
$this->setError('Account disabled');
|
||||
return false;
|
||||
}
|
||||
$userLoginRetry = Config::get('buildadmin.user_login_retry');
|
||||
if ($userLoginRetry && $this->model->loginfailure >= $userLoginRetry && time() - $this->model->lastlogintime < 86400) {
|
||||
$this->setError('Please try again after 1 day');
|
||||
return false;
|
||||
}
|
||||
if ($this->model->password != encrypt_password($password, $this->model->salt)) {
|
||||
$this->loginFailed();
|
||||
$this->setError('Password is incorrect');
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($keeptime) {
|
||||
$this->setRefreshToken(2592000);
|
||||
}
|
||||
$this->loginSuccessful();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录成功
|
||||
* @return bool
|
||||
*/
|
||||
public function loginSuccessful(): bool
|
||||
{
|
||||
if (!$this->model) {
|
||||
return false;
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
$this->model->loginfailure = 0;
|
||||
$this->model->lastlogintime = time();
|
||||
$this->model->lastloginip = request()->ip();
|
||||
$this->model->save();
|
||||
$this->logined = true;
|
||||
|
||||
if (!$this->token) {
|
||||
$this->token = Random::uuid();
|
||||
Token::set($this->token, 'user', $this->model->id, $this->keeptime);
|
||||
}
|
||||
Db::commit();
|
||||
} catch (Exception $e) {
|
||||
Db::rollback();
|
||||
$this->setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录失败
|
||||
* @return bool
|
||||
*/
|
||||
public function loginFailed(): bool
|
||||
{
|
||||
if (!$this->model) {
|
||||
return false;
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
$this->model->loginfailure++;
|
||||
$this->model->lastlogintime = time();
|
||||
$this->model->lastloginip = request()->ip();
|
||||
$this->model->save();
|
||||
|
||||
$this->token = '';
|
||||
$this->model = null;
|
||||
$this->logined = false;
|
||||
Db::commit();
|
||||
} catch (Exception $e) {
|
||||
Db::rollback();
|
||||
$this->setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
* @return bool
|
||||
*/
|
||||
public function logout()
|
||||
{
|
||||
if (!$this->logined) {
|
||||
$this->setError('You are not logged in');
|
||||
return false;
|
||||
}
|
||||
$this->logined = false;
|
||||
Token::delete($this->token);
|
||||
$this->token = '';
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否登录
|
||||
* @return bool
|
||||
*/
|
||||
public function isLogin()
|
||||
{
|
||||
return $this->logined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员模型
|
||||
* @return null
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员Token
|
||||
* @return string
|
||||
*/
|
||||
public function getToken()
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置刷新Token
|
||||
* @param int $keeptime
|
||||
*/
|
||||
public function setRefreshToken($keeptime = 0)
|
||||
{
|
||||
$this->refreshToken = Random::uuid();
|
||||
Token::set($this->refreshToken, 'user-refresh', $this->model->id, $keeptime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员刷新Token
|
||||
* @return string
|
||||
*/
|
||||
public function getRefreshToken()
|
||||
{
|
||||
return $this->refreshToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员信息 - 只输出允许输出的字段
|
||||
* @return array
|
||||
*/
|
||||
public function getUserInfo()
|
||||
{
|
||||
if (!$this->model) {
|
||||
return [];
|
||||
}
|
||||
$info = $this->model->toArray();
|
||||
$info = array_intersect_key($info, array_flip($this->getAllowFields()));
|
||||
$info['token'] = $this->getToken();
|
||||
$info['refreshToken'] = $this->getRefreshToken();
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取允许输出字段
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAllowFields()
|
||||
{
|
||||
return $this->allowFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置允许输出字段
|
||||
* @param $fields
|
||||
*/
|
||||
public function setAllowFields($fields)
|
||||
{
|
||||
$this->allowFields = $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Token有效期
|
||||
* @param int $keeptime
|
||||
*/
|
||||
public function setKeeptime($keeptime = 0)
|
||||
{
|
||||
$this->keeptime = $keeptime;
|
||||
}
|
||||
|
||||
public function check($name, $uid = null, $relation = 'or', $mode = 'url')
|
||||
{
|
||||
return parent::check($name, $uid ?: $this->id, $relation, $mode);
|
||||
}
|
||||
|
||||
public function getRuleList($uid = null)
|
||||
{
|
||||
return parent::getRuleList($uid ?: $this->id);
|
||||
}
|
||||
|
||||
public function getRuleIds($uid = null)
|
||||
{
|
||||
return parent::getRuleIds($uid ?: $this->id);
|
||||
}
|
||||
|
||||
public function getMenus($uid = null)
|
||||
{
|
||||
return parent::getMenus($uid ?: $this->id);
|
||||
}
|
||||
|
||||
public function isSuperUser()
|
||||
{
|
||||
return in_array('*', $this->getRuleIds());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置错误消息
|
||||
* @param $error
|
||||
* @return $this
|
||||
*/
|
||||
public function setError($error)
|
||||
{
|
||||
$this->error = $error;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误消息
|
||||
* @return int|mixed|string|null
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return $this->error ? __($this->error) : '';
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ class AllowCrossDomain
|
||||
'Access-Control-Allow-Credentials' => 'true',
|
||||
'Access-Control-Max-Age' => 1800,
|
||||
'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers' => 'think-lang, batoken, Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With',
|
||||
'Access-Control-Allow-Headers' => 'think-lang, ba-user-token, batoken, Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With',
|
||||
];
|
||||
|
||||
/**
|
||||
|
19
app/common/model/User.php
Normal file
19
app/common/model/User.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
use think\facade\Config;
|
||||
|
||||
class User extends Model
|
||||
{
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = 'updatetime';
|
||||
|
||||
public function getAvatarAttr($value)
|
||||
{
|
||||
return full_url($value, true, Config::get('buildadmin.default_avatar'));
|
||||
}
|
||||
}
|
@ -8,6 +8,8 @@ return [
|
||||
'cors_request_domain' => 'localhost,127.0.0.1',
|
||||
// 是否开启管理员登录验证码
|
||||
'admin_login_captcha' => true,
|
||||
// 会员登录失败可重试次数,false则无限
|
||||
'user_login_retry' => 10,
|
||||
// 管理员登录失败可重试次数,false则无限
|
||||
'admin_login_retry' => 10,
|
||||
// 表格拖拽排序时,两个权重相等则自动重新整理;控制器类中也有此项(作为单控制器自定义配置)
|
||||
|
@ -237,12 +237,21 @@ class Auth
|
||||
return $groups[$uid];
|
||||
}
|
||||
|
||||
$userGroups = Db::name($this->config['auth_group_access'])
|
||||
->alias('aga')
|
||||
->join($this->config['auth_group'] . ' ag', 'aga.group_id = ag.id', 'LEFT')
|
||||
->field('aga.uid,aga.group_id,ag.id,ag.pid,ag.name,ag.rules')
|
||||
->where("aga.uid='{$uid}' and ag.status='1'")
|
||||
->select();
|
||||
if ($this->config['auth_group_access']) {
|
||||
$userGroups = Db::name($this->config['auth_group_access'])
|
||||
->alias('aga')
|
||||
->join($this->config['auth_group'] . ' ag', 'aga.group_id = ag.id', 'LEFT')
|
||||
->field('aga.uid,aga.group_id,ag.id,ag.pid,ag.name,ag.rules')
|
||||
->where("aga.uid='{$uid}' and ag.status='1'")
|
||||
->select();
|
||||
} else {
|
||||
$userGroups = Db::name('user')
|
||||
->alias('u')
|
||||
->join($this->config['auth_group'] . ' ag', 'u.group_id = ag.id', 'LEFT')
|
||||
->field('u.id as uid,u.group_id,ag.id,ag.name,ag.rules')
|
||||
->where("u.id='{$uid}' and ag.status='1'")
|
||||
->select();
|
||||
}
|
||||
|
||||
$groups[$uid] = $userGroups ?: [];
|
||||
return $groups[$uid];
|
||||
|
@ -209,6 +209,7 @@ import { buildCaptchaUrl } from '/@/api/common'
|
||||
import { uuid } from '/@/utils/random'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { buildValidatorData, validatorAccount } from '/@/utils/validate'
|
||||
import { login, logout } from '/@/api/frontend/user/index'
|
||||
import type { ElForm, FormItemRule } from 'element-plus'
|
||||
|
||||
const { t } = useI18n()
|
||||
@ -284,7 +285,9 @@ const onChangeCaptcha = () => {
|
||||
const onSubmit = (formRef: InstanceType<typeof ElForm> | undefined = undefined) => {
|
||||
formRef!.validate((valid) => {
|
||||
if (valid) {
|
||||
login(state.form).then((res) => {
|
||||
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user