2025-07-01 19:39:13 +08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace app\common\logic;
|
|
|
|
|
|
2025-07-11 18:01:23 +08:00
|
|
|
use app\common\model\Corp;
|
|
|
|
|
use app\common\model\CorpFeishuApp;
|
2025-07-29 09:00:04 +08:00
|
|
|
use app\common\model\Resource;
|
2025-07-01 19:39:13 +08:00
|
|
|
use app\common\model\UserTokenLog;
|
|
|
|
|
use app\common\model\Systems;
|
|
|
|
|
use app\common\model\User;
|
|
|
|
|
use app\common\model\UserSn;
|
2025-07-11 18:01:23 +08:00
|
|
|
use app\common\utils\Feishu;
|
2025-07-01 19:39:13 +08:00
|
|
|
use app\common\utils\Jwt;
|
|
|
|
|
use support\Redis;
|
|
|
|
|
use support\exception\BusinessException;
|
|
|
|
|
use support\Log;
|
|
|
|
|
|
|
|
|
|
class AuthLogic
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* 账号密码登录
|
|
|
|
|
*/
|
|
|
|
|
public function uPwdLogin(array $data): array
|
|
|
|
|
{
|
|
|
|
|
$user = $this->validateUser($data['account'], $data['password']);
|
|
|
|
|
$system = $this->validateSystem($data['system_code']);
|
|
|
|
|
$this->validateSystemPermission($user, $system);
|
|
|
|
|
|
2025-07-11 18:01:23 +08:00
|
|
|
return $this->generateTokensAndReturn($user, $system, $data);
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 飞书登录
|
2025-07-11 18:01:23 +08:00
|
|
|
* 如果用户不存在则新增用户信息
|
2025-07-01 19:39:13 +08:00
|
|
|
*/
|
|
|
|
|
public function feishuLogin(array $data): array
|
|
|
|
|
{
|
2025-07-11 18:01:23 +08:00
|
|
|
$feishuUserInfo = $this->getFeishuUserInfo($data);
|
2025-07-01 19:39:13 +08:00
|
|
|
$user = $this->getOrCreateUser($feishuUserInfo);
|
|
|
|
|
|
|
|
|
|
$system = $this->validateSystem($data['system_code']);
|
2025-07-11 18:01:23 +08:00
|
|
|
if ($user->isLogin) {
|
|
|
|
|
$this->validateSystemPermission($user, $system);
|
|
|
|
|
}
|
2025-07-01 19:39:13 +08:00
|
|
|
|
2025-07-11 18:01:23 +08:00
|
|
|
return $this->generateTokensAndReturn($user, $system, $data);
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 18:01:23 +08:00
|
|
|
* 注册
|
2025-07-01 19:39:13 +08:00
|
|
|
*/
|
2025-07-11 18:01:23 +08:00
|
|
|
public function register(array $data)
|
2025-07-01 19:39:13 +08:00
|
|
|
{
|
2025-07-11 18:01:23 +08:00
|
|
|
try {
|
|
|
|
|
// 获取可用用户编号
|
|
|
|
|
$sn = $this->getAvailableUserSn();
|
|
|
|
|
$data['sn'] = $sn->user_sn;
|
2025-07-01 19:39:13 +08:00
|
|
|
|
2025-07-11 18:01:23 +08:00
|
|
|
// 对密码进行哈希处理
|
|
|
|
|
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
|
2025-07-01 19:39:13 +08:00
|
|
|
|
2025-07-11 18:01:23 +08:00
|
|
|
// 获取企业ID
|
|
|
|
|
$corpInfo = $this->validateCorp($data['corp_abbr']);
|
|
|
|
|
$data['corp_id'] = $corpInfo->id;
|
2025-07-01 19:39:13 +08:00
|
|
|
|
2025-07-11 18:01:23 +08:00
|
|
|
// 处理账户
|
|
|
|
|
$data = $data + normalizeAccount($data['account']);
|
|
|
|
|
|
|
|
|
|
// 创建用户记录
|
|
|
|
|
$user = $this->createUser($data);
|
|
|
|
|
|
|
|
|
|
// 更新用户编号的用户 ID
|
|
|
|
|
$this->updateUserId($user->id, $sn->user_sn);
|
|
|
|
|
|
|
|
|
|
$system = $this->validateSystem($data['system_code']);
|
|
|
|
|
return $this->generateTokensAndReturn($user, $system, $data);
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
throw $e;
|
|
|
|
|
}
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 18:01:23 +08:00
|
|
|
* 查找或创建飞书用户
|
2025-07-01 19:39:13 +08:00
|
|
|
*/
|
2025-07-11 18:01:23 +08:00
|
|
|
private function getOrCreateUser(array $feishuUserInfo)
|
2025-07-01 19:39:13 +08:00
|
|
|
{
|
2025-07-11 18:01:23 +08:00
|
|
|
try {
|
|
|
|
|
$phone = $this->sanitizePhone($feishuUserInfo['mobile'] ?? '');
|
2025-07-29 09:00:04 +08:00
|
|
|
$email = $feishuUserInfo['enterprise_email'];
|
2025-08-21 14:31:37 +08:00
|
|
|
// $user = User::where('corp_id', $feishuUserInfo['corp_id'])
|
|
|
|
|
// ->where(function($query) use ($phone, $email) {
|
|
|
|
|
// $query->where('phone', $phone)
|
|
|
|
|
// ->orWhere('email', $email);
|
|
|
|
|
// })
|
|
|
|
|
// ->first();
|
|
|
|
|
$userByPhone = User::where('corp_id', $feishuUserInfo['corp_id'])
|
|
|
|
|
->where('phone', $phone)
|
|
|
|
|
->where('phone', '!=', '')
|
|
|
|
|
->first();
|
|
|
|
|
$userByEmail = User::where('corp_id', $feishuUserInfo['corp_id'])
|
|
|
|
|
->where('email', $email)
|
|
|
|
|
->where('email', '!=', '')
|
|
|
|
|
->first();
|
|
|
|
|
$user = $userByPhone ?: $userByEmail;
|
2025-07-11 18:01:23 +08:00
|
|
|
if (!$user) {
|
|
|
|
|
// 获取编号
|
|
|
|
|
$userSn = $this->getAvailableUserSn();
|
|
|
|
|
// 创建用户
|
|
|
|
|
$user = User::create([
|
2025-07-29 09:00:04 +08:00
|
|
|
'name' => $feishuUserInfo['name'],
|
2025-07-11 18:01:23 +08:00
|
|
|
'avatar' => $feishuUserInfo['avatar_middle'],
|
|
|
|
|
'phone' => $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''),
|
2025-07-29 09:00:04 +08:00
|
|
|
'email' => $feishuUserInfo['enterprise_email'],
|
2025-07-11 18:01:23 +08:00
|
|
|
'union_id' => $feishuUserInfo['union_id'],
|
|
|
|
|
'user_id' => $feishuUserInfo['user_id'],
|
|
|
|
|
'open_id' => $feishuUserInfo['open_id'],
|
|
|
|
|
'tenant_key' => $feishuUserInfo['tenant_key'],
|
|
|
|
|
'corp_id' => $feishuUserInfo['corp_id'],
|
|
|
|
|
'password' => password_hash($userSn->user_sn, PASSWORD_DEFAULT)
|
|
|
|
|
]);
|
|
|
|
|
// 更新使用
|
|
|
|
|
$this->updateUserId($user->id, $userSn->user_sn);
|
|
|
|
|
// 更新编号
|
|
|
|
|
if (!$user->update(['sn'=>$userSn->user_sn])) {
|
|
|
|
|
throw new BusinessException('编号绑定失败');
|
|
|
|
|
}
|
|
|
|
|
// 是否登陆
|
|
|
|
|
$user->isLogin = false;
|
2025-08-14 20:08:57 +08:00
|
|
|
|
|
|
|
|
$options = [
|
|
|
|
|
'corp_id' => $user->corp_id,
|
|
|
|
|
'user_id' => $user->id
|
|
|
|
|
];
|
|
|
|
|
\Webman\RedisQueue\Redis::send('perm-agt', $options);
|
2025-07-11 18:01:23 +08:00
|
|
|
} else {
|
2025-07-29 09:00:04 +08:00
|
|
|
$updated = [
|
2025-07-11 18:01:23 +08:00
|
|
|
'union_id' => $feishuUserInfo['union_id'],
|
|
|
|
|
'user_id' => $feishuUserInfo['user_id'],
|
|
|
|
|
'open_id' => $feishuUserInfo['open_id'],
|
|
|
|
|
'tenant_key' => $feishuUserInfo['tenant_key']
|
2025-07-29 09:00:04 +08:00
|
|
|
];
|
|
|
|
|
if ($phone && $phone == $user->phone) {
|
|
|
|
|
$updated['phone'] = $phone;
|
|
|
|
|
}
|
|
|
|
|
if ($phone && $phone == $user->phone) {
|
|
|
|
|
$updated['email'] = $email;
|
|
|
|
|
}
|
|
|
|
|
// 更新飞书信息
|
|
|
|
|
$user->update($updated);
|
2025-07-31 10:34:15 +08:00
|
|
|
// 是否登陆
|
|
|
|
|
$user->isLogin = true;
|
2025-07-11 18:01:23 +08:00
|
|
|
}
|
|
|
|
|
return $user;
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
throw $e;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-01 19:39:13 +08:00
|
|
|
|
2025-07-11 18:01:23 +08:00
|
|
|
/**
|
|
|
|
|
* 过滤和格式化手机号
|
|
|
|
|
*/
|
|
|
|
|
private function sanitizePhone($mobile)
|
|
|
|
|
{
|
|
|
|
|
return isset($mobile) ? str_replace('+86', '', $mobile) : '';
|
|
|
|
|
}
|
2025-07-01 19:39:13 +08:00
|
|
|
|
2025-07-11 18:01:23 +08:00
|
|
|
/**
|
|
|
|
|
* 生成并记录token,返回包含token的响应数据
|
|
|
|
|
*/
|
|
|
|
|
private function generateTokensAndReturn(User $user, Systems $system, array $data): array
|
|
|
|
|
{
|
2025-07-01 19:39:13 +08:00
|
|
|
$payload = $this->buildJwtPayload($user, $system);
|
|
|
|
|
$token = $this->generateAccessToken($payload);
|
|
|
|
|
$refreshToken = $this->generateRefreshToken($user->id, $data['system_code']);
|
2025-11-03 18:16:51 +08:00
|
|
|
$homeUrl = $this->getHome($system->id, $payload);
|
2025-07-11 18:01:23 +08:00
|
|
|
|
|
|
|
|
// 记录 token 到 Redis 和 MySQL
|
2025-07-01 19:39:13 +08:00
|
|
|
$this->recordUserToken($user->id, $token, $data['system_code']);
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'access_token' => $token,
|
|
|
|
|
'refresh_token' => $refreshToken,
|
|
|
|
|
'expires_in' => config('jwt.jwt_expiration'),
|
2025-07-11 18:01:23 +08:00
|
|
|
'system_code' => $data['system_code'],
|
|
|
|
|
'corp_abbr' => $data['corp_abbr'],
|
2025-07-29 09:00:04 +08:00
|
|
|
'login_type' => $data['login_type'],
|
|
|
|
|
'home_url' => $homeUrl,
|
2025-07-11 18:01:23 +08:00
|
|
|
// 'payload' => $payload,
|
2025-07-01 19:39:13 +08:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 18:01:23 +08:00
|
|
|
* 获取飞书用户信息
|
2025-07-01 19:39:13 +08:00
|
|
|
*/
|
2025-07-11 18:01:23 +08:00
|
|
|
private function getFeishuUserInfo(array $data)
|
2025-07-01 19:39:13 +08:00
|
|
|
{
|
2025-07-11 18:01:23 +08:00
|
|
|
$certificate = $this->feishuAppInfo($data['corp_abbr'], $data['system_code']);
|
|
|
|
|
$feishu = new Feishu($certificate['app_id'], $certificate['app_secret'], $certificate['abbr']);
|
|
|
|
|
$userAccessToken = $feishu->userAccessToken($data['feishu_code'], $certificate['r_url']);
|
|
|
|
|
$userInfo = $feishu->userInfo($userAccessToken['access_token']);
|
|
|
|
|
|
|
|
|
|
if ($userInfo['code'] != 0) {
|
|
|
|
|
throw new BusinessException('授权失败,请重试');
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
2025-07-11 18:01:23 +08:00
|
|
|
|
|
|
|
|
return $userInfo['data'] + $data + $certificate;
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 18:01:23 +08:00
|
|
|
* 更新用户编号
|
2025-07-01 19:39:13 +08:00
|
|
|
*/
|
2025-07-11 18:01:23 +08:00
|
|
|
private function updateUserId($userId, $userSn)
|
2025-07-01 19:39:13 +08:00
|
|
|
{
|
2025-07-11 18:01:23 +08:00
|
|
|
$updated = UserSn::where('user_sn', $userSn)
|
|
|
|
|
->update(['user_id' => $userId]);
|
2025-07-01 19:39:13 +08:00
|
|
|
|
2025-07-11 18:01:23 +08:00
|
|
|
if ($updated === 0) {
|
|
|
|
|
throw new BusinessException('用户编号不存在或更新失败');
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 18:01:23 +08:00
|
|
|
return $updated;
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 记录用户token到 Redis 和 MySQL
|
|
|
|
|
*/
|
|
|
|
|
protected function recordUserToken(int $userId, string $token, string $systemCode)
|
|
|
|
|
{
|
|
|
|
|
$request = request();
|
|
|
|
|
$ip = $request ? $request->getRealIp() : '';
|
|
|
|
|
$ua = $request ? $request->header('user-agent', '') : '';
|
|
|
|
|
$now = time();
|
|
|
|
|
$expireAt = $now + config('jwt.jwt_expiration');
|
|
|
|
|
|
|
|
|
|
// Redis Set 用户token集合
|
|
|
|
|
Redis::sAdd("user_tokens:{$userId}:{$systemCode}", $token);
|
|
|
|
|
Redis::expire("user_tokens:{$userId}:{$systemCode}", config('jwt.jwt_expiration'));
|
|
|
|
|
|
|
|
|
|
// Redis Hash token元信息
|
|
|
|
|
$meta = [
|
|
|
|
|
'system_code' => $systemCode,
|
|
|
|
|
'login_ip' => $ip,
|
|
|
|
|
'user_agent' => $ua,
|
|
|
|
|
'login_time' => $now,
|
|
|
|
|
'expired_at' => $expireAt,
|
|
|
|
|
];
|
|
|
|
|
Redis::hMSet("user_token_meta:{$token}", $meta);
|
|
|
|
|
Redis::expire("user_token_meta:{$token}", config('jwt.jwt_expiration'));
|
|
|
|
|
|
|
|
|
|
// 可选:MySQL 持久化
|
|
|
|
|
try {
|
|
|
|
|
UserTokenLog::create([
|
|
|
|
|
'user_id' => $userId,
|
|
|
|
|
'token' => $token,
|
|
|
|
|
'system_code'=> $systemCode,
|
|
|
|
|
'login_ip' => $ip,
|
|
|
|
|
'user_agent' => $ua,
|
|
|
|
|
'login_time' => $now,
|
|
|
|
|
'expired_at' => $expireAt,
|
|
|
|
|
'is_valid' => 1,
|
|
|
|
|
]);
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
Log::error('Token log write failed: '.$e->getMessage());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 校验token有效性
|
|
|
|
|
*/
|
|
|
|
|
public function verifyToken(string $token, string $systemCode): array
|
|
|
|
|
{
|
|
|
|
|
if (!$systemCode) throw new BusinessException('缺少system_code', 422);
|
|
|
|
|
|
|
|
|
|
$payload = (new Jwt())->verifyToken($token);
|
|
|
|
|
|
|
|
|
|
// 校验黑名单
|
|
|
|
|
if (Redis::has("token_blacklist:{$token}")) {
|
|
|
|
|
throw new BusinessException('token已失效', 403);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 强制校验aud(系统隔离)
|
|
|
|
|
if ($systemCode && $payload['aud'] != $systemCode && $payload['aud'] != ($payload['system_code'] ?? '')) {
|
|
|
|
|
throw new BusinessException('token不属于当前系统', 403);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (array)$payload;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 18:01:23 +08:00
|
|
|
* 获取飞书应用信息
|
2025-07-01 19:39:13 +08:00
|
|
|
*/
|
2025-07-11 18:01:23 +08:00
|
|
|
public function feishuAppInfo(string $corpAbbr, string $systemCode)
|
2025-07-01 19:39:13 +08:00
|
|
|
{
|
2025-07-11 18:01:23 +08:00
|
|
|
$corpInfo = $this->validateCorp($corpAbbr);
|
|
|
|
|
$systemInfo = $this->validateSystem($systemCode);
|
|
|
|
|
$fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemInfo->id);
|
|
|
|
|
return [
|
|
|
|
|
'app_id' => $fsAppInfo->app_id,
|
|
|
|
|
'app_secret' => $fsAppInfo->app_secret,
|
|
|
|
|
'r_url' => $fsAppInfo->r_url,
|
|
|
|
|
'scope' => $fsAppInfo->scopes,
|
|
|
|
|
'state' => $fsAppInfo->state,
|
|
|
|
|
'abbr' => $corpAbbr,
|
|
|
|
|
'corp_id' => $corpInfo->id,
|
|
|
|
|
'system_id' => $systemInfo->id
|
|
|
|
|
];
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 18:01:23 +08:00
|
|
|
* 验证用户信息
|
2025-07-01 19:39:13 +08:00
|
|
|
*/
|
2025-07-11 18:01:23 +08:00
|
|
|
private function validateUser(string $account, string $password)
|
2025-07-01 19:39:13 +08:00
|
|
|
{
|
2025-07-11 18:01:23 +08:00
|
|
|
$user = User::byAccount($account)->first();
|
|
|
|
|
if (!$user || !password_verify($password, $user->password)) {
|
|
|
|
|
throw new BusinessException('账号或密码错误', 401);
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
2025-07-11 18:01:23 +08:00
|
|
|
if ($user->status != 1) {
|
|
|
|
|
throw new BusinessException('账号已禁用', 403);
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
2025-07-11 18:01:23 +08:00
|
|
|
return $user;
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 18:01:23 +08:00
|
|
|
* 验证系统信息
|
2025-07-01 19:39:13 +08:00
|
|
|
*/
|
2025-07-11 18:01:23 +08:00
|
|
|
private function validateSystem(string $systemCode)
|
2025-07-01 19:39:13 +08:00
|
|
|
{
|
2025-07-11 18:01:23 +08:00
|
|
|
$system = Systems::where('code', $systemCode)->first();
|
|
|
|
|
if (!$system) {
|
|
|
|
|
throw new BusinessException('系统不存在', 403);
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
2025-07-11 18:01:23 +08:00
|
|
|
return $system;
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 18:01:23 +08:00
|
|
|
* 验证公司信息
|
2025-07-01 19:39:13 +08:00
|
|
|
*/
|
2025-07-11 18:01:23 +08:00
|
|
|
private function validateCorp(string $corpAbbr)
|
2025-07-01 19:39:13 +08:00
|
|
|
{
|
2025-07-11 18:01:23 +08:00
|
|
|
$corp = Corp::where('abbr', $corpAbbr)->first();
|
|
|
|
|
if (!$corp) {
|
|
|
|
|
throw new BusinessException('公司不存在', 403);
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
2025-07-11 18:01:23 +08:00
|
|
|
return $corp;
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 18:01:23 +08:00
|
|
|
* 验证飞书应用信息
|
2025-07-01 19:39:13 +08:00
|
|
|
*/
|
2025-07-11 18:01:23 +08:00
|
|
|
private function validateFeishuApp(int $corpId, int $systemId)
|
2025-07-01 19:39:13 +08:00
|
|
|
{
|
2025-07-11 18:01:23 +08:00
|
|
|
$feishuAppInfo = CorpFeishuApp::where('corp_id', $corpId)->where('system_id', $systemId)->first();
|
|
|
|
|
if (!$feishuAppInfo) {
|
|
|
|
|
throw new BusinessException('飞书应用不存在', 403);
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
2025-07-11 18:01:23 +08:00
|
|
|
return $feishuAppInfo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 验证用户系统权限
|
|
|
|
|
*/
|
|
|
|
|
private function validateSystemPermission(User $user, Systems $system)
|
|
|
|
|
{
|
|
|
|
|
if ($system->is_role) {
|
|
|
|
|
$systemIds = $user->systems->pluck('id')->toArray();
|
|
|
|
|
if (!in_array($system->id, $systemIds)) {
|
|
|
|
|
throw new BusinessException('无该系统权限', 403);
|
|
|
|
|
}
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 18:01:23 +08:00
|
|
|
* 获取可用的用户编号
|
|
|
|
|
*
|
|
|
|
|
* @return \app\common\model\UserSn 用户编号模型实例
|
|
|
|
|
* @throws BusinessException 当未找到适用编号时抛出
|
2025-07-01 19:39:13 +08:00
|
|
|
*/
|
2025-07-11 18:01:23 +08:00
|
|
|
private function getAvailableUserSn(): \app\common\model\UserSn
|
2025-07-01 19:39:13 +08:00
|
|
|
{
|
2025-07-11 18:01:23 +08:00
|
|
|
$userSnModel = new UserSn();
|
|
|
|
|
$sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first();
|
|
|
|
|
if (!$sn) {
|
|
|
|
|
Log::error('用户注册时未找到适用编号');
|
|
|
|
|
throw new BusinessException('未找到适用编号');
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
2025-07-11 18:01:23 +08:00
|
|
|
return $sn;
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 18:01:23 +08:00
|
|
|
* 创建用户记录
|
|
|
|
|
*
|
|
|
|
|
* @param array $data 包含用户信息的数组
|
|
|
|
|
* @return \app\common\model\User 用户模型实例
|
2025-07-01 19:39:13 +08:00
|
|
|
*/
|
2025-07-11 18:01:23 +08:00
|
|
|
private function createUser(array $data): \app\common\model\User
|
2025-07-01 19:39:13 +08:00
|
|
|
{
|
2025-07-11 18:01:23 +08:00
|
|
|
try {
|
|
|
|
|
return User::create($data);
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
Log::error('用户注册时创建用户记录失败', [
|
|
|
|
|
'data' => $data,
|
|
|
|
|
'error' => $e->getMessage()
|
|
|
|
|
]);
|
|
|
|
|
throw new BusinessException('用户注册失败,请稍后重试', 500);
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* JWT payload 构建
|
|
|
|
|
* 可扩展 payload 字段
|
|
|
|
|
*/
|
|
|
|
|
private function buildJwtPayload(User $user, ?Systems $system = null): array
|
|
|
|
|
{
|
|
|
|
|
$now = time();
|
|
|
|
|
$exp = $now + config('jwt.jwt_expiration');
|
|
|
|
|
$system_ids = $user->systems ? $user->systems->pluck('id')->toArray() : [];
|
|
|
|
|
$role_ids = $user->roles ? $user->roles->pluck('id')->toArray() : [];
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'sub' => 'user_' . $user->id,
|
|
|
|
|
'aud' => $system->code,
|
|
|
|
|
'iat' => $now,
|
|
|
|
|
'nbf' => $now,
|
|
|
|
|
'exp' => $exp,
|
|
|
|
|
'typ' => 'access',
|
|
|
|
|
'uid' => $user->id,
|
2025-09-26 15:50:23 +08:00
|
|
|
'uname' => $user->name,
|
2025-07-01 19:39:13 +08:00
|
|
|
'role_ids' => $role_ids,
|
|
|
|
|
'system_ids' => $system_ids
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* JWT签发
|
|
|
|
|
*/
|
|
|
|
|
private function generateAccessToken(array $payload)
|
|
|
|
|
{
|
|
|
|
|
return (new Jwt())->generateToken($payload);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 刷新令牌生成
|
|
|
|
|
*/
|
|
|
|
|
private function generateRefreshToken(int $userId, string $systemCode)
|
|
|
|
|
{
|
|
|
|
|
$refreshToken = bin2hex(random_bytes(32));
|
|
|
|
|
// user_id+system_code只允许一个refresh_token生效
|
|
|
|
|
Redis::set("refresh_token:{$userId}:{$systemCode}", $refreshToken, config('jwt.jwt_refresh_token_expiration'));
|
|
|
|
|
// 建立refresh_token到user_id+system_code映射,便于校验
|
|
|
|
|
Redis::set("refresh_token_map:{$refreshToken}:{$systemCode}", $userId, config('jwt.jwt_refresh_token_expiration'));
|
|
|
|
|
return $refreshToken;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-11 18:01:23 +08:00
|
|
|
|
2025-07-01 19:39:13 +08:00
|
|
|
/**
|
2025-07-11 18:01:23 +08:00
|
|
|
* 刷新token
|
|
|
|
|
* @param string $refreshToken
|
|
|
|
|
* @param string|null $systemCode 可选,指定系统
|
|
|
|
|
* @return array
|
2025-07-01 19:39:13 +08:00
|
|
|
*/
|
2025-07-11 18:01:23 +08:00
|
|
|
public function refreshToken(string $refreshToken, ?string $systemCode = null): array
|
2025-07-01 19:39:13 +08:00
|
|
|
{
|
2025-07-11 18:01:23 +08:00
|
|
|
if (!$systemCode) throw new BusinessException('缺少system_code', 422);
|
|
|
|
|
|
|
|
|
|
// 校验refresh_token
|
|
|
|
|
$userId = Redis::get("refresh_token_map:{$refreshToken}:{$systemCode}");
|
2025-07-01 19:39:13 +08:00
|
|
|
if (!$userId) {
|
2025-07-11 18:01:23 +08:00
|
|
|
throw new BusinessException('refresh_token无效或已过期', 401);
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
2025-07-11 18:01:23 +08:00
|
|
|
|
|
|
|
|
$validToken = Redis::get("refresh_token:{$userId}:{$systemCode}");
|
|
|
|
|
if ($validToken !== $refreshToken) {
|
|
|
|
|
throw new BusinessException('refresh_token无效或已过期', 401);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$user = User::find($userId);
|
|
|
|
|
if (!$user || $user->status != 1) {
|
|
|
|
|
throw new BusinessException('用户无效', 403);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$system = $this->validateSystem($systemCode);
|
|
|
|
|
$this->validateSystemPermission($user, $system);
|
|
|
|
|
|
|
|
|
|
$payload = $this->buildJwtPayload($user, $system);
|
|
|
|
|
$token = $this->generateAccessToken($payload);
|
|
|
|
|
$refreshToken = $this->generateRefreshToken($user->id, $systemCode);
|
|
|
|
|
$this->recordUserToken($user->id, $token, $systemCode);
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'access_token' => $token,
|
|
|
|
|
'refresh_token' => $refreshToken,
|
|
|
|
|
'expires_in' => config('jwt.jwt_expiration'),
|
|
|
|
|
// 'payload' => $payload,
|
|
|
|
|
];
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 18:01:23 +08:00
|
|
|
* 登出/失效token
|
2025-07-01 19:39:13 +08:00
|
|
|
*/
|
2025-07-11 18:01:23 +08:00
|
|
|
public function logout(?string $token, ?string $systemCode = null)
|
2025-07-01 19:39:13 +08:00
|
|
|
{
|
2025-07-11 18:01:23 +08:00
|
|
|
if ($token) {
|
|
|
|
|
Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration'));
|
|
|
|
|
$payload = null;
|
|
|
|
|
try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {}
|
|
|
|
|
$userId = $payload['uid'] ?? null;
|
|
|
|
|
$aud = $payload['aud'] ?? null;
|
|
|
|
|
$sc = $systemCode ?? $aud;
|
|
|
|
|
if ($userId && $sc) {
|
|
|
|
|
Redis::sRem("user_tokens:{$userId}:{$sc}", $token);
|
|
|
|
|
}
|
|
|
|
|
Redis::del("user_token_meta:{$token}");
|
|
|
|
|
UserTokenLog::where('token', $token)->update(['is_valid'=>0]);
|
|
|
|
|
}
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 18:01:23 +08:00
|
|
|
* 强制拉黑token
|
2025-07-01 19:39:13 +08:00
|
|
|
*/
|
2025-07-11 18:01:23 +08:00
|
|
|
public function blockToken(string $token, ?string $systemCode = null)
|
2025-07-01 19:39:13 +08:00
|
|
|
{
|
2025-07-11 18:01:23 +08:00
|
|
|
if (!$token) {
|
|
|
|
|
throw new BusinessException('token不能为空', 422);
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
2025-07-11 18:01:23 +08:00
|
|
|
Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration'));
|
|
|
|
|
$payload = null;
|
|
|
|
|
try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {}
|
|
|
|
|
$userId = $payload['uid'] ?? null;
|
|
|
|
|
$aud = $payload['aud'] ?? null;
|
|
|
|
|
$sc = $systemCode ?? $aud;
|
|
|
|
|
if ($userId && $sc) {
|
|
|
|
|
Redis::sRem("user_tokens:{$userId}:{$sc}", $token);
|
|
|
|
|
}
|
|
|
|
|
Redis::del("user_token_meta:{$token}");
|
|
|
|
|
UserTokenLog::where('token', $token)->update(['is_valid'=>0]);
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|
2025-07-29 09:00:04 +08:00
|
|
|
|
|
|
|
|
|
2025-11-03 18:16:51 +08:00
|
|
|
public function getHome($systemId, $data)
|
2025-07-29 09:00:04 +08:00
|
|
|
{
|
2025-11-03 18:16:51 +08:00
|
|
|
// 2025.10.30 临时处理 ====== start
|
|
|
|
|
if ($systemId == 1) {
|
|
|
|
|
if (in_array(1, $data['role_ids'])) {
|
|
|
|
|
return '/dashboard/console';
|
|
|
|
|
}
|
|
|
|
|
if (count($data['role_ids']) == 1 && in_array(5, $data['role_ids'])) {
|
|
|
|
|
return '/dashboard/console/backoffice';
|
|
|
|
|
}
|
|
|
|
|
if (count($data['role_ids']) == 1 && in_array(6, $data['role_ids'])) {
|
|
|
|
|
return '/dashboard/console/finance';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 2025.10.30 临时处理 ====== end
|
|
|
|
|
|
2025-07-29 09:00:04 +08:00
|
|
|
$info = Resource::where('system_id', $systemId)
|
|
|
|
|
->where('is_home', 1)
|
|
|
|
|
->first();
|
|
|
|
|
|
|
|
|
|
if (!$info) {
|
|
|
|
|
return '/dashboard/console';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 递归拼接 URL
|
|
|
|
|
$url = $this->buildParentUrl($info);
|
|
|
|
|
|
|
|
|
|
return $url ?: '/dashboard/console';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 递归构建父级 URL
|
|
|
|
|
* @param Resource $resource
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
protected function buildParentUrl(Resource $resource)
|
|
|
|
|
{
|
|
|
|
|
$url = $resource->url ?? '';
|
|
|
|
|
|
|
|
|
|
// 如果存在父级,则递归拼接
|
|
|
|
|
if ($resource->parent_id != 0) {
|
|
|
|
|
$parent = Resource::find($resource->parent_id);
|
|
|
|
|
if ($parent) {
|
|
|
|
|
$parentUrl = $this->buildParentUrl($parent);
|
|
|
|
|
$url = $parentUrl . ($url ? '/' . ltrim($url, '/') : '');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $url;
|
|
|
|
|
}
|
2025-07-01 19:39:13 +08:00
|
|
|
}
|