tp/app/admin/controller/LoginController.php

389 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace app\admin\controller;
use app\admin\BaseController;
use think\exception\ValidateException;
use think\facade\Db;
use think\facade\Cache;
use think\response\Json;
use app\service\JwtService;
use app\model\System\AdminUser;
use app\model\System\AdminUserGroup;
use app\model\System\SystemSiteSettings;
use app\model\Tenant\Tenant;
use app\model\System\OperationLog;
class LoginController extends BaseController
{
private function generateToken($userInfo): string
{
return JwtService::generateToken($userInfo);
}
private function verifyToken($token): ?array
{
return JwtService::verifyToken($token);
}
/**
* 登录接口
* @return Json
*/
public function login(): Json
{
try {
$data = $this->request->param();
// 1. 先验证租户名称是否传入(新增:必须传租户名称)
try {
$this->validate($data, [
'tenant_name|租户名称' => 'require|length:1,128', // 匹配租户表tenant字段长度
'account|账号' => 'require|length:3,32',
'password|密码' => 'require|length:6,32'
]);
} catch (ValidateException $e) {
$this->logFail('登录管理', '登录', $e->getMessage());
return json([
'code' => 400,
'msg' => $e->getError()
]);
}
// 2. 处理账号兼容(邮箱/手机号转account
if (isset($data['email'])) {
$data['account'] = $data['email'];
} elseif (isset($data['phone'])) {
$data['account'] = $data['phone'];
}
// 3. 查询租户ID新增只查正常状态的租户 status=1
$tenant = Tenant::where('tenant_name', $data['tenant_name'])
->where('status', 1) // 过滤停用/删除的租户
->field(['id', 'tenant_name']) // 只查需要的字段,提升性能
->find(); // ThinkPHP 使用 find()
// 4. 验证租户是否存在
if (!$tenant) {
$this->logFail('登录管理', '登录', '租户不存在或已禁用,租户名称:' . $data['tenant_name']);
return json([
'code' => 401,
'msg' => '租户不存在或已禁用'
]);
}
$tid = $tenant->id;
$tenant_name = $tenant->tenant_name;
// 5. 查询用户新增关联租户ID确保用户属于该租户
$user = AdminUser::where('account', $data['account'])
->where('tid', $tid) // 核心:验证用户所属租户
->where('status', 1)
->find();
// 6. 验证用户是否存在
if (!$user) {
$this->logFail('登录管理', '登录', '账号不存在/已禁用,或不属于当前租户,账号:' . $data['account'] . ',租户:' . $tenant_name);
return json([
'code' => 401,
'msg' => '账号不存在或已禁用,或不属于当前租户'
]);
}
// 7. 验证密码
if (md5($data['password']) !== $user['password']) {
$this->logFail('登录管理', '登录', '密码错误,账号:' . $data['account'] . ',租户:' . $tenant_name);
return json([
'code' => 401,
'msg' => '密码错误'
]);
}
// 8. 更新登录次数和IP
try {
$loginCount = isset($user['login_count']) && $user['login_count'] !== null ? (int)$user['login_count'] : 0;
AdminUser::where('id', $user['id'])->update([
'login_count' => $loginCount + 1,
'last_login_ip' => $this->request->ip(),
'last_login_time' => date('Y-m-d H:i:s') // 新增:记录最后登录时间,更实用
]);
} catch (\Exception $e) {
error_log('更新登录信息失败: ' . $e->getMessage());
}
// 9. 组装用户信息新增加入租户ID和租户名称和角色权限
$userInfo = [
'id' => $user['id'],
'account' => $user['account'],
'name' => $user['name'],
'group_id' => $user['group_id'],
'tid' => $tid, // 新增租户ID
'tenant' => $tenant // 新增:租户名称
];
// 获取角色权限
if ($user['group_id']) {
$userGroup = AdminUserGroup::where('id', $user['group_id'])->find();
if ($userGroup && $userGroup->rights) {
$userInfo['rights'] = json_decode($userGroup->rights, true);
}
}
// 10. 生成TokenToken中已包含租户信息后续可通过Token解析获取
try {
$token = $this->generateToken($userInfo);
} catch (\Exception $e) {
$this->logFail('登录管理', '登录', 'Token生成失败: ' . $e->getMessage());
return json([
'code' => 500,
'msg' => '登录失败,请稍后重试'
]);
}
// 11. 写入用户数据缓存核心缓存包含租户信息示例用Redis可根据你的缓存工具调整
try {
$cacheKey = 'admin_user_' . $user['id'] . '_' . $tid; // 缓存键加入租户ID避免多租户冲突
$cacheExpire = 86400 * 7; // 缓存7天可根据需求调整
// 写入缓存这里假设你使用thinkphp的Cache类若用其他工具可替换
\think\facade\Cache::set($cacheKey, $userInfo, $cacheExpire);
} catch (\Exception $e) {
error_log('用户缓存写入失败: ' . $e->getMessage());
// 缓存写入失败不影响登录,但记录日志
}
// 12. 记录登录成功日志
try {
$this->logSuccess('登录管理', '登录', [
'id' => $user['id'],
'tid' => $tid,
'tenant' => $tenant
], $userInfo);
} catch (\Exception $e) {
error_log('登录日志记录失败: ' . $e->getMessage());
}
// 13. 返回登录结果(包含租户信息)
return json([
'code' => 200,
'msg' => '登录成功',
'data' => [
'token' => $token,
'user' => $userInfo // 前端可直接获取租户ID和名称
]
]);
} catch (\Exception $e) {
$errorMsg = $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine();
error_log('登录失败: ' . $errorMsg);
try {
$this->logFail('登录管理', '登录', $errorMsg);
} catch (\Exception $logError) {
error_log('记录登录失败日志也失败: ' . $logError->getMessage());
}
return json([
'code' => 500,
'msg' => '登录失败:' . $e->getMessage()
]);
}
}
/**
* 退出登录
* @return Json
*/
public function logout(): Json
{
$authHeader = $this->request->header('Authorization', '');
$userInfo = null;
if (preg_match('/Bearer\s+(.+)/i', $authHeader, $matches)) {
$tokenData = $this->verifyToken($matches[1]);
if ($tokenData && isset($tokenData['user'])) {
$userInfo = (array)$tokenData['user'];
}
}
if ($userInfo && isset($userInfo['id'])) {
$this->logSuccess('登录管理', '退出登录', ['result' => 'success'], $userInfo);
} else {
OperationLog::create([
'user_id' => 0,
'user_account' => '',
'user_name' => '未知用户',
'module' => '登录管理',
'action' => '退出登录',
'method' => 'POST',
'url' => $this->request->url(true),
'ip' => $this->request->ip(),
'user_agent' => $this->request->header('user-agent', ''),
'request_data' => null,
'response_data' => json_encode(['result' => 'success'], JSON_UNESCAPED_UNICODE),
'status' => 1,
'error_message' => '',
'execution_time' => 0.0,
]);
}
return json([
'code' => 200,
'msg' => '退出成功'
]);
}
/**
* 获取当前登录用户信息
* @return Json
*/
public function userInfo(): Json
{
$authHeader = $this->request->header('Authorization', '');
if (!preg_match('/Bearer\s+(.+)/i', $authHeader, $matches)) {
return json([
'code' => 401,
'msg' => '未登录'
]);
}
$tokenData = $this->verifyToken($matches[1]);
if (!$tokenData || !isset($tokenData['user'])) {
return json([
'code' => 401,
'msg' => 'Token无效'
]);
}
$user = (array)$tokenData['user'];
$user_id = $user['id'];
$userData = AdminUser::where('id', $user_id)
->where('delete_time', null)
->field('id, account, name, phone, qq, sex, group_id, status, create_time, update_time')
->find();
if (!$userData) {
return json([
'code' => 404,
'msg' => '用户不存在'
]);
}
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $userData->toArray()
]);
}
public function getAdminUserFromToken(): array
{
return JwtService::getUserFromHeader($this->request->header('Authorization', ''));
}
public function loginInfo()
{
$loginInfo = SystemSiteSettings::select();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $loginInfo
]);
}
/**
* 获取极验3.0的id和key
* @return Json
*/
public function getGeetest3Infos()
{
// 定义你需要的 label 列表
$targetLabels = [
'geetest3ID',
'geetest3KEY'
];
$legalInfos = SystemSiteSettings::where('delete_time', null)
->whereIn('label', $targetLabels) // 仅筛选指定的 label
->field('label, value')
->select();
$this->logSuccess('站点设置管理', '查看极验3.0的id和key', [], $this->getAdminUserInfo());
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $legalInfos->toArray()
]);
}
/**
* 获取极验4.0的id和key
* @return Json
*/
public function getGeetest4Infos()
{
// 定义你需要的 label 列表
$targetLabels = [
'geetest4ID',
'geetest4KEY'
];
$legalInfos = SystemSiteSettings::where('delete_time', null)
->whereIn('label', $targetLabels) // 仅筛选指定的 label
->field('label, value')
->select()
->toArray();
// 转换为前端需要的格式
$data = [];
foreach ($legalInfos as $item) {
if ($item['label'] === 'geetest4ID') {
$data['captcha_id'] = $item['value'];
}
if ($item['label'] === 'geetest4KEY') {
$data['captcha_key'] = $item['value'];
}
}
$this->logSuccess('站点设置管理', '查看极验4.0的id和key', [], $this->getAdminUserInfo());
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $data
]);
}
/**
* 判断是否开启验证
* @return Json
*/
public function getOpenVerify()
{
// 定义你需要的 label 列表
$targetLabels = [
'openVerify',
'verifyModel'
];
$legalInfos = SystemSiteSettings::where('delete_time', null)
->whereIn('label', $targetLabels) // 仅筛选指定的 label
->field('label, value')
->select();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $legalInfos->toArray()
]);
}
}