发票管理apiadmin
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

248 lines
9.1 KiB

<?php
declare (strict_types=1);
/**
* 登录登出
* @since 2017-11-02
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
namespace app\controller\admin;
use app\model\AdminAuthGroupAccess;
use app\model\AdminAuthRule;
use app\model\AdminMenu;
use app\model\AdminUser;
use app\model\AdminUserData;
use app\util\ReturnCode;
use app\util\RouterTool;
use app\util\Tools;
use think\captcha\facade\Captcha;
use think\facade\Cache;
use think\Response;
class Login extends Base {
protected $maxAttempts = 5; // 最大失败次数
protected $lockMinutes = 1; // 锁定分钟数
protected $captchaThreshold = 3; // 失败多少次后需要验证码
/**
* 用户登录【账号密码登录】
* @return Response
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function index(): Response {
$username = $this->request->post('username');
$password = $this->request->post('password');
$captchaToken = $this->request->post('captcha_token');
$captchaInput = $this->request->post('captcha');
if (!$username) {
return $this->buildFailed(ReturnCode::LOGIN_ERROR, '缺少用户名!');
}
if (!$password) {
return $this->buildFailed(ReturnCode::LOGIN_ERROR, '缺少密码!');
} else {
$password = Tools::userMd5($password);
}
// 2. 【硬拦截】检查是否已被锁定
$failKey = "login:fail:{$username}";
$failCount = (int) Cache::get($failKey, 0);
if ($failCount >= $this->maxAttempts) {
return $this->buildFailed(ReturnCode::LOGIN_ERROR, "账户已被临时锁定,请{$this->lockMinutes}分钟后再试");
}
// 3. 判断是否需要验证码(失败 ≥ 3 次)
$needCaptcha = $failCount >= $this->captchaThreshold;
// 4. 如需验证码,进行校验
if ($needCaptcha) {
if (empty($captchaToken) || empty($captchaInput)) {
return $this->buildFailed(ReturnCode::LOGIN_ERROR, "请输入验证码", ['need_captcha' => true]);
}
$storedCode = Cache::get($captchaToken);
if (!$storedCode || strtolower($storedCode) !== strtolower($captchaInput)) {
// 验证码错误也记一次失败[reference:8]
$this->incrementFailCount($failKey);
return $this->buildFailed(ReturnCode::LOGIN_ERROR, "验证码错误", ['need_captcha' => true]);
}
// 验证码校验通过后立即删除,防止重放攻击[reference:9]
Cache::delete($captchaToken);
}
$userInfo = (new AdminUser())->where('username', $username)->where('password', $password)->find();
if (!empty($userInfo)) {
if ($userInfo['status']) {
//更新用户数据
$userData = $userInfo->userData;
$data = [];
if ($userData) {
$userData->login_times++;
$userData->last_login_ip = sprintf("%u", ip2long($this->request->ip()));
$userData->last_login_time = time();
$userData->save();
} else {
$data['login_times'] = 1;
$data['uid'] = $userInfo['id'];
$data['last_login_ip'] = sprintf("%u", ip2long($this->request->ip()));
$data['last_login_time'] = time();
$data['head_img'] = '';
AdminUserData::create($data);
$userInfo['userData'] = $data;
}
} else {
return $this->buildFailed(ReturnCode::LOGIN_ERROR, '用户已被封禁,请联系管理员');
}
} else {
$count = $this->setFailKey($username);
return $this->buildFailed(
ReturnCode::LOGIN_ERROR,
'用户名密码不正确',
['need_captcha' => $count >= $this->captchaThreshold]
);
}
$userInfo['access'] = $this->getAccess($userInfo['id']);
$userInfo['menu'] = $this->getAccessMenuData($userInfo['id']);
$apiAuth = md5(uniqid() . time());
cache('Login:' . $apiAuth, json_encode($userInfo), config('apiadmin.ONLINE_TIME'));
cache('Login:' . $userInfo['id'], $apiAuth, config('apiadmin.ONLINE_TIME'));
$userInfo['apiAuth'] = $apiAuth;
return $this->buildSuccess($userInfo->toArray(), '登录成功');
}
/**
* 获取用户信息
* @return Response
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function getUserInfo(): Response {
return $this->buildSuccess($this->userInfo);
}
/**
* 用户登出
* @return Response
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function logout(): Response {
$ApiAuth = $this->request->header('Api-Auth');
cache('Login:' . $ApiAuth, null);
cache('Login:' . $this->userInfo['id'], null);
return $this->buildSuccess([], '登出成功');
}
/**
* 获取当前用户的允许菜单
* @return Response
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function getAccessMenu(): Response {
return $this->buildSuccess($this->getAccessMenuData($this->userInfo['id']));
}
/**
* 获取当前用户的允许菜单
* @param int $uid
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function getAccessMenuData(int $uid): array {
$returnData = [];
$isSupper = Tools::isAdministrator($uid);
if ($isSupper) {
$access = (new AdminMenu())->where('router', '<>', '')->select();
$returnData = Tools::listToTree(Tools::buildArrFromObj($access));
} else {
$groups = (new AdminAuthGroupAccess())->where('uid', $uid)->find();
if (isset($groups) && $groups->group_id) {
$access = (new AdminAuthRule())->whereIn('group_id', $groups->group_id)->select();
$access = array_unique(array_column(Tools::buildArrFromObj($access), 'url'));
array_push($access, "");
$menus = (new AdminMenu())->whereIn('url', $access)->where('show', 1)->select();
$returnData = Tools::listToTree(Tools::buildArrFromObj($menus));
RouterTool::buildVueRouter($returnData);
}
}
return array_values($returnData);
}
/**
* 获取用户权限数据
* @param $uid
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author zhaoxiang <zhaoxiang051405@gmail.com>
*/
public function getAccess(int $uid): array {
$isSupper = Tools::isAdministrator($uid);
if ($isSupper) {
$access = (new AdminMenu())->select();
$access = Tools::buildArrFromObj($access);
return array_values(array_filter(array_column($access, 'url')));
} else {
$groups = (new AdminAuthGroupAccess())->where('uid', $uid)->find();
if (isset($groups) && $groups->group_id) {
$access = (new AdminAuthRule())->whereIn('group_id', $groups->group_id)->select();
$access = Tools::buildArrFromObj($access);
return array_values(array_unique(array_column($access, 'url')));
} else {
return [];
}
}
}
public function getCaptcha(): Response
{
// 生成验证码(api模式下返回数组:['code' => 验证码文本, 'img' => base64图片])
$captcha = Captcha::create();
// 生成唯一 token 作为 Redis key
$token = 'captcha_' . md5(uniqid() . microtime(true));
// 存入 Redis,有效期 300 秒
Cache::set($token, $captcha['code'], 300);
return $this->buildSuccess([
'token' => $token,
'img' => $captcha['img'], // 已是完整的 base64 data:image/png;base64,...
]);
}
protected function setFailKey($username)
{
$failKey = "login:fail:{$username}";
$failCount = (int) Cache::get($failKey, 0);
$failCount += 1;
Cache::set($failKey, $failCount, $this->lockMinutes * 60);
return $failCount;
}
/**
* 原子增加失败次数
*/
protected function incrementFailCount(string $key): int
{
$count = Cache::inc($key);
if ($count == 1) {
Cache::expire($key, $this->lockMinutes * 60);
}
return $count;
}
}