2025-05-12 00:19:35 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
namespace App\Service;
|
|
|
|
|
|
|
2025-05-12 13:12:57 +08:00
|
|
|
|
use App\BaseApp\BaseNotAuthService;
|
2025-05-12 09:04:46 +08:00
|
|
|
|
use App\Models\AdminModel;
|
2026-08-10 18:32:06 +08:00
|
|
|
|
use App\Models\LoginLogModel;
|
2025-05-12 00:19:35 +08:00
|
|
|
|
use App\Service\common\JWTService;
|
2026-08-10 18:32:06 +08:00
|
|
|
|
use App\Service\common\UserAgentService;
|
2025-05-12 00:19:35 +08:00
|
|
|
|
use App\Service\common\UtilsService;
|
|
|
|
|
|
use Exception;
|
|
|
|
|
|
use Illuminate\Support\Str;
|
|
|
|
|
|
|
2026-08-10 18:32:06 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 登录 / 注册服务:成功与失败均写登录日志,成功时更新 last_login_time
|
|
|
|
|
|
*/
|
2025-05-12 13:12:57 +08:00
|
|
|
|
class LoginService extends BaseNotAuthService
|
2025-05-12 00:19:35 +08:00
|
|
|
|
{
|
|
|
|
|
|
/**
|
2026-08-10 18:32:06 +08:00
|
|
|
|
* 账号密码登录
|
|
|
|
|
|
* 失败时先写日志再抛错,保证审计完整;成功始终更新 last_login_time
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $phone 手机号/账号
|
|
|
|
|
|
* @param string $password 明文密码
|
|
|
|
|
|
* @return array 用户信息 + token
|
2025-05-12 00:19:35 +08:00
|
|
|
|
* @throws Exception
|
|
|
|
|
|
*/
|
2025-05-12 09:04:46 +08:00
|
|
|
|
public function login($phone, $password): array
|
2025-05-12 00:19:35 +08:00
|
|
|
|
{
|
2026-08-10 18:32:06 +08:00
|
|
|
|
$ua = UserAgentService::getInstance();
|
|
|
|
|
|
$equipment = $ua->parseEquipment();
|
|
|
|
|
|
$browser = $ua->parseBrowser();
|
2025-05-12 13:12:57 +08:00
|
|
|
|
$userModel = AdminModel::with(['role:id,name,value'])->where('phone', $phone)->first();
|
2025-05-12 00:19:35 +08:00
|
|
|
|
if (empty($userModel)) {
|
2026-08-10 18:32:06 +08:00
|
|
|
|
$this->writeLoginLog(0, (string) $phone, '', 1, '用户或密码错误', $equipment, $browser);
|
2025-05-12 00:19:35 +08:00
|
|
|
|
UtilsService::getInstance()->errorThrow('用户或密码错误!');
|
|
|
|
|
|
}
|
2026-08-10 18:32:06 +08:00
|
|
|
|
if (!password_verify($password, $userModel->password)) {
|
|
|
|
|
|
$this->writeLoginLog(
|
|
|
|
|
|
(int) $userModel->id,
|
|
|
|
|
|
(string) $phone,
|
|
|
|
|
|
(string) $userModel->nick_name,
|
|
|
|
|
|
1,
|
|
|
|
|
|
'用户或密码错误',
|
|
|
|
|
|
$equipment,
|
|
|
|
|
|
$browser
|
|
|
|
|
|
);
|
|
|
|
|
|
UtilsService::getInstance()->errorThrow('用户或密码错误!');
|
2025-05-12 00:19:35 +08:00
|
|
|
|
}
|
2026-08-10 18:32:06 +08:00
|
|
|
|
$updateData = [
|
|
|
|
|
|
'last_login_time' => get_time(),
|
|
|
|
|
|
'updated_at' => get_time(),
|
|
|
|
|
|
];
|
2025-05-12 00:19:35 +08:00
|
|
|
|
if ($userModel->ip !== get_ip()) {
|
|
|
|
|
|
$ipTable = json_decode($userModel->ip_table, true);
|
2026-08-10 18:32:06 +08:00
|
|
|
|
if (!in_array(get_ip(), $ipTable ?? [])) {
|
2025-05-12 00:19:35 +08:00
|
|
|
|
$ipTable[] = get_ip();
|
|
|
|
|
|
}
|
2026-08-10 18:32:06 +08:00
|
|
|
|
$updateData['ip'] = get_ip();
|
|
|
|
|
|
$updateData['ip_table'] = json_encode($ipTable);
|
|
|
|
|
|
}
|
|
|
|
|
|
$update = AdminModel::where('id', $userModel->id)->update($updateData);
|
|
|
|
|
|
if (!$update) {
|
|
|
|
|
|
$this->writeLoginLog(
|
|
|
|
|
|
(int) $userModel->id,
|
|
|
|
|
|
(string) $phone,
|
|
|
|
|
|
(string) $userModel->nick_name,
|
|
|
|
|
|
1,
|
|
|
|
|
|
'更新登录信息失败',
|
|
|
|
|
|
$equipment,
|
|
|
|
|
|
$browser
|
|
|
|
|
|
);
|
|
|
|
|
|
UtilsService::getInstance()->errorThrow('更新失败!');
|
2025-05-12 00:19:35 +08:00
|
|
|
|
}
|
2026-08-10 18:32:06 +08:00
|
|
|
|
$userModel = AdminModel::with(['role:id,name,value'])->where('id', $userModel->id)->first();
|
|
|
|
|
|
$this->writeLoginLog(
|
|
|
|
|
|
(int) $userModel->id,
|
|
|
|
|
|
(string) $userModel->phone,
|
|
|
|
|
|
(string) $userModel->nick_name,
|
|
|
|
|
|
0,
|
|
|
|
|
|
'登录成功',
|
|
|
|
|
|
$equipment,
|
|
|
|
|
|
$browser
|
|
|
|
|
|
);
|
2025-05-12 00:19:35 +08:00
|
|
|
|
$result = [
|
|
|
|
|
|
'id' => $userModel->id,
|
2025-05-12 09:04:46 +08:00
|
|
|
|
'phone' => $userModel->phone,
|
2025-05-12 00:19:35 +08:00
|
|
|
|
'nick_name' => $userModel->nick_name,
|
|
|
|
|
|
'avatar' => $userModel->avatar,
|
|
|
|
|
|
'email' => $userModel->email,
|
2025-05-12 16:13:46 +08:00
|
|
|
|
'role_id' => $userModel->role_id,
|
2025-05-12 13:12:57 +08:00
|
|
|
|
'role_name' => $userModel->role->name,
|
|
|
|
|
|
'role_value' => $userModel->role->value,
|
2025-05-12 00:19:35 +08:00
|
|
|
|
'ip' => $userModel->ip,
|
|
|
|
|
|
'ip_table' => $userModel->ip_table,
|
|
|
|
|
|
];
|
|
|
|
|
|
$token = JWTService::getInstance()->generateToken($result);
|
|
|
|
|
|
$result['token'] = $token;
|
|
|
|
|
|
return $result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-08-10 18:32:06 +08:00
|
|
|
|
* 注册管理员账号
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $phone 手机号
|
|
|
|
|
|
* @param string $password 密码
|
|
|
|
|
|
* @param string $email 邮箱
|
|
|
|
|
|
* @param mixed $code 验证码(预留)
|
2025-05-12 00:19:35 +08:00
|
|
|
|
* @return array
|
|
|
|
|
|
* @throws Exception
|
|
|
|
|
|
*/
|
2025-05-12 09:04:46 +08:00
|
|
|
|
public function register($phone, $password, $email, $code): array
|
2025-05-12 00:19:35 +08:00
|
|
|
|
{
|
2025-05-12 09:04:46 +08:00
|
|
|
|
$userModel = AdminModel::where('phone', $phone)->first();
|
2025-05-12 00:19:35 +08:00
|
|
|
|
if ($userModel) {
|
|
|
|
|
|
UtilsService::getInstance()->errorThrow('账号已被占用!');
|
|
|
|
|
|
}
|
2025-05-12 09:04:46 +08:00
|
|
|
|
$createModel = AdminModel::create([
|
|
|
|
|
|
'phone' => $phone,
|
2025-05-12 00:19:35 +08:00
|
|
|
|
'email' => $email,
|
|
|
|
|
|
'password' => password_hash($password, PASSWORD_DEFAULT),
|
2026-08-10 18:32:06 +08:00
|
|
|
|
'nick_name' => '新用户' . Str::random(),
|
2025-05-12 00:19:35 +08:00
|
|
|
|
'avatar' => 'https://pic.rmb.bdstatic.com/bjh/80852bfe7c321988191838517ba64e309354.jpeg@h_1280',
|
|
|
|
|
|
'role_id' => 2,
|
|
|
|
|
|
'ip' => get_ip(),
|
|
|
|
|
|
'ip_table' => json_encode([get_ip()]),
|
2026-08-10 18:32:06 +08:00
|
|
|
|
'created_at' => get_time(),
|
2025-05-12 00:19:35 +08:00
|
|
|
|
]);
|
|
|
|
|
|
if (!$createModel) {
|
|
|
|
|
|
UtilsService::getInstance()->errorThrow('注册失败!');
|
|
|
|
|
|
}
|
2025-05-12 13:12:57 +08:00
|
|
|
|
$userInfo = AdminModel::with(['role:id,name,value'])->where('id', $createModel->id)->first();
|
2025-05-12 00:19:35 +08:00
|
|
|
|
$result = [
|
|
|
|
|
|
'id' => $userInfo->id,
|
2025-05-12 09:04:46 +08:00
|
|
|
|
'phone' => $userInfo->phone,
|
2025-05-12 00:19:35 +08:00
|
|
|
|
'nick_name' => $userInfo->nick_name,
|
|
|
|
|
|
'avatar' => $userInfo->avatar,
|
2026-08-10 18:32:06 +08:00
|
|
|
|
'email' => $userInfo->email ?? '',
|
2025-05-12 13:12:57 +08:00
|
|
|
|
'role_name' => $userInfo->role->name,
|
|
|
|
|
|
'role_value' => $userInfo->role->value,
|
2025-05-12 00:19:35 +08:00
|
|
|
|
'ip' => $userInfo->ip,
|
|
|
|
|
|
'ip_table' => $userInfo->ip_table,
|
|
|
|
|
|
];
|
|
|
|
|
|
$token = JWTService::getInstance()->generateToken($result);
|
|
|
|
|
|
$result['token'] = $token;
|
|
|
|
|
|
return $result;
|
|
|
|
|
|
}
|
2026-08-10 18:32:06 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 写入登录日志(失败也尽量落库,内部吞异常避免掩盖业务错误)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param int $userId 管理员 ID,失败未知用户可为 0
|
|
|
|
|
|
* @param string $phone 登录账号
|
|
|
|
|
|
* @param string $nickName 昵称快照
|
|
|
|
|
|
* @param int $status 0成功 1失败
|
|
|
|
|
|
* @param string $message 结果说明
|
|
|
|
|
|
* @param string $equipment 操作系统
|
|
|
|
|
|
* @param string $browser 浏览器
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function writeLoginLog(
|
|
|
|
|
|
int $userId,
|
|
|
|
|
|
string $phone,
|
|
|
|
|
|
string $nickName,
|
|
|
|
|
|
int $status,
|
|
|
|
|
|
string $message,
|
|
|
|
|
|
string $equipment,
|
|
|
|
|
|
string $browser
|
|
|
|
|
|
): void {
|
|
|
|
|
|
try {
|
|
|
|
|
|
LoginLogModel::insert([
|
|
|
|
|
|
'user_id' => $userId,
|
|
|
|
|
|
'phone' => $phone,
|
|
|
|
|
|
'nick_name' => $nickName,
|
|
|
|
|
|
'ip' => (string) get_ip(),
|
|
|
|
|
|
'equipment' => $equipment,
|
|
|
|
|
|
'browser' => $browser,
|
|
|
|
|
|
'status' => $status,
|
|
|
|
|
|
'message' => $message,
|
|
|
|
|
|
'created_at' => time(),
|
|
|
|
|
|
]);
|
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
|
// 日志失败不影响登录主流程
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-05-12 00:19:35 +08:00
|
|
|
|
}
|