初始化

缺陷:主题配色需要优化整体的同风格
This commit is contained in:
2026-08-14 23:21:21 +08:00
parent 6e2769c528
commit bcf54c2727
152 changed files with 13521 additions and 141 deletions

View File

@@ -36,22 +36,46 @@ class LoginService extends BaseNotAuthService
$this->writeLoginLog(0, (string) $phone, '', 1, '用户或密码错误', $equipment, $browser);
UtilsService::getInstance()->errorThrow('用户或密码错误!');
}
if (!password_verify($password, $userModel->password)) {
// 老后台是无盐 sha1没有明文无法预先转 bcrypt所以 bcrypt 校验失败时再回落比对遗留密码
$legacyHit = false;
if (!password_verify($password, (string) $userModel->password)) {
$legacyHit = $this->verifyLegacyPassword($userModel, (string) $password);
if (!$legacyHit) {
$this->writeLoginLog(
(int) $userModel->id,
(string) $phone,
(string) $userModel->nick_name,
1,
'用户或密码错误',
$equipment,
$browser
);
UtilsService::getInstance()->errorThrow('用户或密码错误!');
}
}
// 状态校验放在密码校验之后,避免未通过认证就泄露账号是否存在或被禁用
if ((int) $userModel->status === 1) {
$this->writeLoginLog(
(int) $userModel->id,
(string) $phone,
(string) $userModel->nick_name,
1,
'用户或密码错误',
'账号已被禁用',
$equipment,
$browser
);
UtilsService::getInstance()->errorThrow('用户或密码错误');
UtilsService::getInstance()->errorThrow('账号已被禁用,请联系管理员');
}
$updateData = [
'last_login_time' => get_time(),
'updated_at' => get_time(),
];
if ($legacyHit) {
// 命中遗留密码即刻升级成 bcrypt 并清空遗留列,下次登录走正常校验
$updateData['password'] = password_hash($password, PASSWORD_DEFAULT);
$updateData['legacy_password'] = '';
$updateData['legacy_password_expire_at'] = 0;
}
if ($userModel->ip !== get_ip()) {
$ipTable = json_decode($userModel->ip_table, true);
if (!in_array(get_ip(), $ipTable ?? [])) {
@@ -60,26 +84,14 @@ class LoginService extends BaseNotAuthService
$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('更新失败!');
}
AdminModel::where('id', $userModel->id)->update($updateData);
$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,
'登录成功',
$legacyHit ? '登录成功(遗留密码已升级)' : '登录成功',
$equipment,
$browser
);
@@ -90,16 +102,99 @@ class LoginService extends BaseNotAuthService
'avatar' => $userModel->avatar,
'email' => $userModel->email,
'role_id' => $userModel->role_id,
'role_name' => $userModel->role->name,
'role_value' => $userModel->role->value,
// 迁移过来的账号可能 role_id=0关联为空时不能直接取属性
'role_name' => $userModel->role->name ?? '',
'role_value' => $userModel->role->value ?? '',
'ip' => $userModel->ip,
'ip_table' => $userModel->ip_table,
];
$token = JWTService::getInstance()->generateToken($result);
$result['token'] = $token;
// 前端据此提示尽快改密:无盐 sha1 可被彩虹表秒破,升级后也建议换新密码
$result['legacy_password_upgraded'] = $legacyHit;
return $result;
}
/**
* 退出登录:删掉 Redis 会话,手里那张 token 立刻作废
*
* 注册在免登录组token 缺失或已过期都不报错——前端登出时本地 token 常常已经清掉了,
* 这时报错只会让用户卡在退出流程里。
*/
public function logout(): array
{
$decoded = JWTService::getInstance()->parseExpiringToken($this->refreshTtl());
$userId = (int) ($decoded->data->id ?? 0);
if ($userId > 0) {
JWTService::getInstance()->revoke($userId);
}
return ['logout' => true];
}
/**
* 续签 token
*
* 前端拦截器在 401 时调这里。签名 + Redis 会话都要通过,只是放宽 exp
* 所以「被登出」和「过期太久」两种情况仍然要求重新登录。
* @throws Exception
*/
public function refresh(): array
{
$decoded = JWTService::getInstance()->parseExpiringToken($this->refreshTtl());
$data = isset($decoded->data) ? (array) $decoded->data : [];
$userId = (int) ($data['id'] ?? 0);
if ($userId <= 0) {
UtilsService::getInstance()->notAuth('登录状态已失效,请重新登录');
}
// 会话还在才允许续签;顺带用库里的最新角色刷新 payload改了角色不用等 token 过期
$session = JWTService::getInstance()->getToken()->getUserInfo();
$userModel = AdminModel::with(['role:id,name,value'])
->where('id', $userId)
->where('deleted_at', 0)
->first();
if (empty($userModel) || (int) $userModel->status === 1) {
JWTService::getInstance()->revoke($userId);
UtilsService::getInstance()->notAuth('账号不可用,请重新登录');
}
$payload = array_merge($session, [
'id' => (int) $userModel->id,
'phone' => $userModel->phone,
'nick_name' => $userModel->nick_name,
'avatar' => $userModel->avatar,
'role_id' => (int) $userModel->role_id,
'role_name' => $userModel->role->name ?? '',
'role_value' => $userModel->role->value ?? '',
]);
return ['token' => JWTService::getInstance()->generateToken($payload)];
}
/**
* 续签宽限期token 过期后仍可续签的时长,超过就必须重新登录
*/
private function refreshTtl(): int
{
return (int) config('nl.jwt.refresh_ttl', 7 * 24 * 3600);
}
/**
* 比对老系统的无盐 sha1 密码
*
* 只在 bcrypt 校验失败时调用。遗留列有失效时间,过期后一律走重置流程,
* 因为无盐 sha1 可被彩虹表直接反查,不能无限期留着。
*/
private function verifyLegacyPassword(AdminModel $userModel, string $password): bool
{
$legacy = strtolower(trim((string) ($userModel->legacy_password ?? '')));
if ($legacy === '') {
return false;
}
$expireAt = (int) ($userModel->legacy_password_expire_at ?? 0);
if ($expireAt > 0 && $expireAt < get_time()) {
return false;
}
return hash_equals($legacy, sha1($password));
}
/**
* 注册管理员账号
*
@@ -112,6 +207,10 @@ class LoginService extends BaseNotAuthService
*/
public function register($phone, $password, $email, $code): array
{
// 这是后台管理端,自助注册默认关闭:开着等于任何人都能给自己开一个管理员账号
if (!config('nl.register.enabled', false)) {
UtilsService::getInstance()->errorThrow('后台不开放自助注册,请联系管理员创建账号');
}
$userModel = AdminModel::where('phone', $phone)->where('deleted_at', 0)->first();
if ($userModel) {
UtilsService::getInstance()->errorThrow('账号已被占用!');
@@ -122,9 +221,14 @@ class LoginService extends BaseNotAuthService
'password' => password_hash($password, PASSWORD_DEFAULT),
'nick_name' => '新用户' . Str::random(),
'avatar' => 'https://pic.rmb.bdstatic.com/bjh/80852bfe7c321988191838517ba64e309354.jpeg@h_1280',
'role_id' => 2,
// 迁移的老角色统一偏移到 100 起,脚手架预留的 2默认角色不受影响仍可作为默认值
'role_id' => (int) config('nl.register.default_role_id', 2),
// open_id 是 NOT NULL 且无默认值,不显式赋值会直接插入失败
'open_id' => 'nl_' . bin2hex(random_bytes(15)),
'ip' => get_ip(),
'ip_table' => json_encode([get_ip()]),
// status 列默认值是 1禁用不显式写成 0 新账号登录会被状态校验挡下
'status' => 0,
'created_at' => get_time(),
]);
if (!$createModel) {
@@ -137,8 +241,8 @@ class LoginService extends BaseNotAuthService
'nick_name' => $userInfo->nick_name,
'avatar' => $userInfo->avatar,
'email' => $userInfo->email ?? '',
'role_name' => $userInfo->role->name,
'role_value' => $userInfo->role->value,
'role_name' => $userInfo->role->name ?? '',
'role_value' => $userInfo->role->value ?? '',
'ip' => $userInfo->ip,
'ip_table' => $userInfo->ip_table,
];