first push
This commit is contained in:
@@ -0,0 +1,475 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\model\UserTokenLog;
|
||||
use app\common\model\Systems;
|
||||
use app\common\model\User;
|
||||
use app\common\model\UserSn;
|
||||
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);
|
||||
$payload = $this->buildJwtPayload($user, $system);
|
||||
$token = $this->generateAccessToken($payload);
|
||||
$refreshToken = $this->generateRefreshToken($user->id, $data['system_code']);
|
||||
|
||||
// 记录 token 到 Redis 和 MySQL
|
||||
$this->recordUserToken($user->id, $token, $data['system_code']);
|
||||
|
||||
return [
|
||||
'access_token' => $token,
|
||||
'refresh_token' => $refreshToken,
|
||||
'expires_in' => config('jwt.jwt_expiration'),
|
||||
'payload' => $payload,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 飞书登录
|
||||
*/
|
||||
public function feishuLogin(array $data): array
|
||||
{
|
||||
$feishuUserInfo = $this->getFeishuUserInfo($data['feishu_code']);
|
||||
$user = $this->getOrCreateUser($feishuUserInfo);
|
||||
|
||||
$system = $this->validateSystem($data['system_code']);
|
||||
$this->validateSystemPermission($user, $system);
|
||||
$payload = $this->buildJwtPayload($user, $system);
|
||||
$token = $this->generateAccessToken($payload);
|
||||
$refreshToken = $this->generateRefreshToken($user->id, $data['system_code']);
|
||||
|
||||
// 记录 token 到 Redis 和 MySQL
|
||||
$this->recordUserToken($user->id, $token, $data['system_code']);
|
||||
|
||||
return [
|
||||
'access_token' => $token,
|
||||
'refresh_token' => $refreshToken,
|
||||
'expires_in' => config('jwt.jwt_expiration'),
|
||||
'payload' => $payload,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫码登录
|
||||
*/
|
||||
public function qrCodeLogin(array $data): array
|
||||
{
|
||||
$userId = $this->validateQrCodeToken($data['qr_code_token']);
|
||||
$user = User::find($userId);
|
||||
if (!$user || $user->status != 1) {
|
||||
throw new BusinessException('用户无效', 403);
|
||||
}
|
||||
|
||||
$system = $this->validateSystem($data['system_code']);
|
||||
$this->validateSystemPermission($user, $system);
|
||||
$payload = $this->buildJwtPayload($user, $system);
|
||||
$token = $this->generateAccessToken($payload);
|
||||
$refreshToken = $this->generateRefreshToken($user->id, $data['system_code']);
|
||||
|
||||
// 记录 token 到 Redis 和 MySQL
|
||||
$this->recordUserToken($user->id, $token, $data['system_code']);
|
||||
|
||||
return [
|
||||
'access_token' => $token,
|
||||
'refresh_token' => $refreshToken,
|
||||
'expires_in' => config('jwt.jwt_expiration'),
|
||||
'payload' => $payload,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
public function register(array $data)
|
||||
{
|
||||
// 获取可用用户编号
|
||||
$sn = $this->getAvailableUserSn();
|
||||
$data['sn'] = $sn->user_sn;
|
||||
|
||||
// 对密码进行哈希处理
|
||||
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
|
||||
|
||||
// 创建用户记录
|
||||
$user = $this->createUser($data);
|
||||
|
||||
// 更新用户编号的用户 ID
|
||||
$this->updateUserId($user->id, $sn->user_sn);
|
||||
|
||||
// 注册后如果直接登录(返回token)
|
||||
$system = $this->validateSystem($data['system_code']);
|
||||
$payload = $this->buildJwtPayload($user, $system);
|
||||
$token = $this->generateAccessToken($payload);
|
||||
$refreshToken = $this->generateRefreshToken($user->id, $data['system_code']);
|
||||
$this->recordUserToken($user->id, $token, $data['system_code']);
|
||||
|
||||
return [
|
||||
'user' => $user,
|
||||
'access_token' => $token,
|
||||
'refresh_token' => $refreshToken,
|
||||
'expires_in' => config('jwt.jwt_expiration'),
|
||||
'payload' => $payload,
|
||||
];
|
||||
}
|
||||
|
||||
public function updateUserId($userId, $userSn)
|
||||
{
|
||||
$updated = UserSn::where('user_sn', $userSn)
|
||||
->update(['user_id' => $userId]);
|
||||
|
||||
if ($updated === 0) {
|
||||
throw new BusinessException('用户编号不存在或更新失败');
|
||||
}
|
||||
|
||||
return $updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用的用户编号
|
||||
*
|
||||
* @return \app\common\model\UserSn 用户编号模型实例
|
||||
* @throws BusinessException 当未找到适用编号时抛出
|
||||
*/
|
||||
private function getAvailableUserSn(): \app\common\model\UserSn
|
||||
{
|
||||
$userSnModel = new UserSn();
|
||||
$sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first();
|
||||
if (!$sn) {
|
||||
Log::error('用户注册时未找到适用编号');
|
||||
throw new BusinessException('未找到适用编号');
|
||||
}
|
||||
return $sn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建用户记录
|
||||
*
|
||||
* @param array $data 包含用户信息的数组
|
||||
* @return \app\common\model\User 用户模型实例
|
||||
*/
|
||||
private function createUser(array $data): \app\common\model\User
|
||||
{
|
||||
try {
|
||||
return User::create($data);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('用户注册时创建用户记录失败', [
|
||||
'data' => $data,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
throw new BusinessException('用户注册失败,请稍后重试', 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
* @param string $refreshToken
|
||||
* @param string|null $systemCode 可选,指定系统
|
||||
* @return array
|
||||
*/
|
||||
public function refreshToken(string $refreshToken, ?string $systemCode = null): array
|
||||
{
|
||||
if (!$systemCode) throw new BusinessException('缺少system_code', 422);
|
||||
|
||||
// 校验refresh_token
|
||||
$userId = Redis::get("refresh_token_map:{$refreshToken}:{$systemCode}");
|
||||
if (!$userId) {
|
||||
throw new BusinessException('refresh_token无效或已过期', 401);
|
||||
}
|
||||
$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);
|
||||
$this->recordUserToken($user->id, $token, $systemCode);
|
||||
|
||||
return [
|
||||
'access_token' => $token,
|
||||
'expires_in' => config('jwt.jwt_expiration'),
|
||||
'payload' => $payload,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录用户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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出/失效token
|
||||
*/
|
||||
public function logout(?string $token, ?string $systemCode = null)
|
||||
{
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制拉黑token
|
||||
*/
|
||||
public function blockToken(string $token, ?string $systemCode = null)
|
||||
{
|
||||
if (!$token) {
|
||||
throw new BusinessException('token不能为空', 422);
|
||||
}
|
||||
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]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有活跃token及元信息(按system_code)
|
||||
*/
|
||||
public function getUserTokens(int $userId, string $systemCode): array
|
||||
{
|
||||
$tokens = Redis::sMembers("user_tokens:{$userId}:{$systemCode}") ?: [];
|
||||
$result = [];
|
||||
foreach ($tokens as $token) {
|
||||
$meta = Redis::hGetAll("user_token_meta:{$token}");
|
||||
$result[] = [
|
||||
'token' => $token,
|
||||
'meta' => $meta,
|
||||
];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制某用户所有token全部下线
|
||||
*/
|
||||
public function blockAllTokensOfUser(int $userId, string $systemCode)
|
||||
{
|
||||
$tokens = Redis::sMembers("user_tokens:{$userId}:{$systemCode}") ?: [];
|
||||
foreach ($tokens as $token) {
|
||||
$this->blockToken($token, $systemCode);
|
||||
}
|
||||
}
|
||||
|
||||
/** ----------- 内部方法 ------------- **/
|
||||
|
||||
/**
|
||||
* 用户验证
|
||||
*/
|
||||
private function validateUser(string $account, string $password)
|
||||
{
|
||||
$user = User::byAccount($account)->first();
|
||||
if (!$user || !password_verify($password, $user->password)) {
|
||||
throw new BusinessException('账号或密码错误', 401);
|
||||
}
|
||||
if ($user->status != 1) {
|
||||
throw new BusinessException('账号已禁用', 403);
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统验证
|
||||
*/
|
||||
private function validateSystem(string $systemCode)
|
||||
{
|
||||
$system = Systems::where('code', $systemCode)->first();
|
||||
if (!$system) {
|
||||
throw new BusinessException('系统不存在', 403);
|
||||
}
|
||||
return $system;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户系统权限校验
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
'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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证扫码令牌
|
||||
*/
|
||||
private function validateQrCodeToken(string $qrCodeToken)
|
||||
{
|
||||
$userId = Redis::get("qr_code_token:{$qrCodeToken}");
|
||||
if (!$userId) {
|
||||
throw new BusinessException('扫码令牌无效或已过期', 401);
|
||||
}
|
||||
return $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用飞书API获取用户信息(示例)
|
||||
*/
|
||||
private function getFeishuUserInfo(string $code)
|
||||
{
|
||||
// 实际实现需要调用飞书 API
|
||||
return [
|
||||
'user_id' => 'feishu_user_123',
|
||||
'name' => '飞书用户',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找或创建飞书用户(示例)
|
||||
*/
|
||||
private function getOrCreateUser(array $feishuUserInfo)
|
||||
{
|
||||
$user = User::where('feishu_user_id', $feishuUserInfo['user_id'])->first();
|
||||
if (!$user) {
|
||||
$user = new User();
|
||||
$user->feishu_user_id = $feishuUserInfo['user_id'];
|
||||
$user->name = $feishuUserInfo['name'];
|
||||
$user->status = 1;
|
||||
$user->save();
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\model\Systems;
|
||||
use app\common\model\User;
|
||||
use app\common\model\UserSn;
|
||||
use app\common\utils\Jwt;
|
||||
use support\Cache;
|
||||
use support\exception\BusinessException;
|
||||
|
||||
class AuthLogic_bak
|
||||
{
|
||||
|
||||
/**
|
||||
* 账号密码登录方法
|
||||
*
|
||||
* 该方法接收包含账号、密码和系统编码的数据数组,验证用户信息和系统信息,
|
||||
* 检查用户对系统的权限,生成访问令牌和刷新令牌并返回。
|
||||
*
|
||||
* @param array $data 包含登录信息的数组,键名为 'account'(账号)、'password'(密码)、'system_code'(系统编码)
|
||||
* @return array 包含访问令牌、刷新令牌和令牌过期时间的数组
|
||||
*/
|
||||
public function uPwdLogin(array $data): array
|
||||
{
|
||||
// 验证用户账号和密码,返回用户对象
|
||||
$user = $this->validateUser($data['account'], $data['password']);
|
||||
// 验证系统编码,返回系统对象
|
||||
$system = $this->validateSystem($data['system_code']);
|
||||
// 验证用户对该系统是否有访问权限
|
||||
$this->validateSystemPermission($user, $system);
|
||||
// 准备 JWT 令牌的负载数据
|
||||
$payload = $this->preparePayload($user, $system);
|
||||
// 根据负载数据生成访问令牌
|
||||
$token = $this->generateAccessToken($payload);
|
||||
// 根据用户 ID 生成刷新令牌
|
||||
$refreshToken = $this->generateRefreshToken($user->id);
|
||||
|
||||
// 返回包含访问令牌、刷新令牌和令牌过期时间的数组
|
||||
return [
|
||||
'access_token' => $token,
|
||||
'refresh_token' => $refreshToken,
|
||||
'expires_in' => config('jwt.jwt_expiration'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 飞书登录
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function feishuLogin(array $data): array
|
||||
{
|
||||
$feishuUserInfo = $this->getFeishuUserInfo($data['feishu_code']);
|
||||
$user = $this->getOrCreateUser($feishuUserInfo);
|
||||
|
||||
$system = $this->validateSystem($data['system_code']);
|
||||
$this->validateSystemPermission($user, $system);
|
||||
$payload = $this->preparePayload($user, $system);
|
||||
$token = $this->generateAccessToken($payload);
|
||||
$refreshToken = $this->generateRefreshToken($user->id);
|
||||
|
||||
return [
|
||||
'access_token' => $token,
|
||||
'refresh_token' => $refreshToken,
|
||||
'expires_in' => config('jwt.jwt_expiration'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫码登录
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function qrCodeLogin(array $data): array
|
||||
{
|
||||
$userId = $this->validateQrCodeToken($data['qr_code_token']);
|
||||
$user = User::find($userId);
|
||||
|
||||
if (!$user || $user->status != 1) {
|
||||
throw new \Exception('用户无效', 403);
|
||||
}
|
||||
|
||||
$system = $this->validateSystem($data['system_code']);
|
||||
$this->validateSystemPermission($user, $system);
|
||||
$payload = $this->preparePayload($user, $system);
|
||||
$token = $this->generateAccessToken($payload);
|
||||
$refreshToken = $this->generateRefreshToken($user->id);
|
||||
|
||||
return [
|
||||
'access_token' => $token,
|
||||
'refresh_token' => $refreshToken,
|
||||
'expires_in' => config('jwt.jwt_expiration'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户信息
|
||||
*
|
||||
* @param string $account
|
||||
* @param string $password
|
||||
* @return User|null
|
||||
*/
|
||||
private function validateUser(string $account, string $password)
|
||||
{
|
||||
$user = User::byAccount($account)->first();
|
||||
if (!$user || !password_verify($password, $user->password)) {
|
||||
throw new \Exception('账号或密码错误', 401);
|
||||
}
|
||||
|
||||
if ($user->status != 1) {
|
||||
throw new \Exception('账号已禁用', 403);
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证系统信息
|
||||
*
|
||||
* @param string $systemCode
|
||||
* @return Systems|null
|
||||
*/
|
||||
private function validateSystem(string $systemCode)
|
||||
{
|
||||
$system = Systems::where('code', $systemCode)->first();
|
||||
if (!$system) {
|
||||
throw new \Exception('系统不存在', 403);
|
||||
}
|
||||
|
||||
return $system;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户系统权限
|
||||
*
|
||||
* @param User $user
|
||||
* @param Systems $system
|
||||
*/
|
||||
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 \Exception('无该系统权限', 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备 JWT 负载
|
||||
*
|
||||
* @param User $user
|
||||
* @param Systems $system
|
||||
* @return array
|
||||
*/
|
||||
private function preparePayload(User $user, Systems $system)
|
||||
{
|
||||
$payload = [
|
||||
'uid' => $user->id,
|
||||
'sub' => 'user_'.$user->id,
|
||||
'aud' => $system->domain_url,
|
||||
'role_ids' => [],
|
||||
'system_ids' => []
|
||||
];
|
||||
|
||||
if ($system->is_role) {
|
||||
$systemIds = $user->systems->pluck('id')->toArray();
|
||||
$payload['role_ids'] = $user->roles->pluck('id')->toArray();
|
||||
$payload['system_ids'] = $systemIds;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成访问令牌
|
||||
*
|
||||
* @param array $payload
|
||||
* @return string
|
||||
*/
|
||||
private function generateAccessToken(array $payload)
|
||||
{
|
||||
return (new Jwt())->generateToken($payload);
|
||||
}
|
||||
|
||||
// private function verifyRefreshToken(string $token)
|
||||
// {
|
||||
// return (new Jwt())->verifyToken($token);
|
||||
// }
|
||||
|
||||
/**
|
||||
* 生成刷新令牌
|
||||
*
|
||||
* @param int $userId
|
||||
* @return string
|
||||
*/
|
||||
private function generateRefreshToken(int $userId)
|
||||
{
|
||||
$refreshToken = bin2hex(random_bytes(32));
|
||||
Cache::set("refresh_token:{$refreshToken}", $userId, config('jwt.jwt_refresh_token_expiration'));
|
||||
return $refreshToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用飞书 API 获取用户信息
|
||||
*
|
||||
* @param string $code
|
||||
* @return array
|
||||
*/
|
||||
private function getFeishuUserInfo(string $code)
|
||||
{
|
||||
// 实际实现需要调用飞书 API,这里只是示例
|
||||
return [
|
||||
'user_id' => 'feishu_user_123',
|
||||
'name' => '飞书用户',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据飞书用户信息查找或创建本地用户
|
||||
*
|
||||
* @param array $feishuUserInfo
|
||||
* @return User
|
||||
*/
|
||||
private function getOrCreateUser(array $feishuUserInfo)
|
||||
{
|
||||
// 实际实现需要根据飞书用户信息查找或创建本地用户,这里只是示例
|
||||
$user = User::where('feishu_user_id', $feishuUserInfo['user_id'])->first();
|
||||
if (!$user) {
|
||||
$user = new User();
|
||||
$user->feishu_user_id = $feishuUserInfo['user_id'];
|
||||
$user->name = $feishuUserInfo['name'];
|
||||
$user->status = 1;
|
||||
$user->save();
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证扫码令牌
|
||||
*
|
||||
* @param string $qrCodeToken
|
||||
* @return int
|
||||
*/
|
||||
private function validateQrCodeToken(string $qrCodeToken)
|
||||
{
|
||||
// 实际实现需要验证扫码令牌,这里只是示例
|
||||
$userId = Cache::get("qr_code_token:{$qrCodeToken}");
|
||||
if (!$userId) {
|
||||
throw new \Exception('扫码令牌无效或已过期', 401);
|
||||
}
|
||||
return $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理用户注册逻辑
|
||||
*
|
||||
* @param array $data
|
||||
* @return User
|
||||
*/
|
||||
public function register(array $data)
|
||||
{
|
||||
$userSnModel = new UserSn();
|
||||
$sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first();
|
||||
if (!$sn) {
|
||||
throw new BusinessException('未找到适用编号');
|
||||
}
|
||||
$data['sn'] = $sn->user_sn;
|
||||
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
|
||||
$user = User::create($data);
|
||||
return $this->updateUserId($user->id, $sn->user_sn);
|
||||
}
|
||||
|
||||
|
||||
public function updateUserId($userId, $userSn)
|
||||
{
|
||||
$userSn = User::where('user_sn', $userSn)->first();
|
||||
if (!$userSn) {
|
||||
throw new BusinessException('用户编号不存在');
|
||||
}
|
||||
return $userSn->update([
|
||||
'user_id' => $userId
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验token
|
||||
*/
|
||||
public function verifyToken(string $token): array
|
||||
{
|
||||
$payload = (new Jwt())->verifyToken($token);
|
||||
if (Cache::has("token_blacklist:{$token}")) {
|
||||
throw new \Exception('token已失效', 403);
|
||||
}
|
||||
return (array)$payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
* @param string $refreshToken
|
||||
* @return array
|
||||
*/
|
||||
public function refreshToken(string $refreshToken): array
|
||||
{
|
||||
$userId = Cache::get("refresh_token:{$refreshToken}");
|
||||
if (!$userId) {
|
||||
throw new \Exception('refresh_token无效或已过期', 401);
|
||||
}
|
||||
|
||||
$user = User::find($userId);
|
||||
if (!$user || $user->status != 1) {
|
||||
throw new \Exception('用户无效', 403);
|
||||
}
|
||||
|
||||
// 获取角色ID、系统ID(和你登录时保持一致)
|
||||
$role_ids = $user->roles ? $user->roles->pluck('id')->toArray() : [];
|
||||
$system_ids = $user->systems ? $user->systems->pluck('id')->toArray() : [];
|
||||
|
||||
$payload = [
|
||||
'uid' => $user->id,
|
||||
'sub' => 'user_' . $user->id,
|
||||
'role_ids' => $role_ids,
|
||||
'system_ids' => $system_ids,
|
||||
];
|
||||
// 可补充aud、exp等字段,保持和登录生成payload一致
|
||||
|
||||
$token = (new Jwt())->generateToken($payload);
|
||||
|
||||
return [
|
||||
'access_token' => $token,
|
||||
'expires_in' => config('jwt.jwt_expiration'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出,将token加入黑名单
|
||||
*/
|
||||
public function logout(?string $token)
|
||||
{
|
||||
if ($token) {
|
||||
Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function blockToken(string $token)
|
||||
{
|
||||
if (!$token) {
|
||||
throw new \Exception('token不能为空', 422);
|
||||
}
|
||||
// 加入黑名单
|
||||
Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\model\UserTokenLog;
|
||||
use app\common\model\Systems;
|
||||
use app\common\model\User;
|
||||
use app\common\model\UserSn;
|
||||
use app\common\utils\Jwt;
|
||||
use support\Cache;
|
||||
use support\exception\BusinessException;
|
||||
use support\Log;
|
||||
|
||||
class AuthLogic_bak2
|
||||
{
|
||||
/**
|
||||
* 账号密码登录
|
||||
*/
|
||||
public function uPwdLogin(array $data): array
|
||||
{
|
||||
$user = $this->validateUser($data['account'], $data['password']);
|
||||
|
||||
$system = $this->validateSystem($data['system_code']);
|
||||
$this->validateSystemPermission($user, $system);
|
||||
$payload = $this->buildJwtPayload($user, $system);
|
||||
$token = $this->generateAccessToken($payload);
|
||||
$refreshToken = $this->generateRefreshToken($user->id);
|
||||
|
||||
// 记录 token 到 Redis 和 MySQL
|
||||
$this->recordUserToken($user->id, $token);
|
||||
|
||||
return [
|
||||
'access_token' => $token,
|
||||
'refresh_token' => $refreshToken,
|
||||
'expires_in' => config('jwt.jwt_expiration'),
|
||||
'payload' => $payload, // 方便前端调试,可移除
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 飞书登录
|
||||
*/
|
||||
public function feishuLogin(array $data): array
|
||||
{
|
||||
$feishuUserInfo = $this->getFeishuUserInfo($data['feishu_code']);
|
||||
$user = $this->getOrCreateUser($feishuUserInfo);
|
||||
|
||||
$system = $this->validateSystem($data['system_code']);
|
||||
$this->validateSystemPermission($user, $system);
|
||||
$payload = $this->buildJwtPayload($user, $system);
|
||||
$token = $this->generateAccessToken($payload);
|
||||
$refreshToken = $this->generateRefreshToken($user->id);
|
||||
|
||||
return [
|
||||
'access_token' => $token,
|
||||
'refresh_token' => $refreshToken,
|
||||
'expires_in' => config('jwt.jwt_expiration'),
|
||||
'payload' => $payload,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫码登录
|
||||
*/
|
||||
public function qrCodeLogin(array $data): array
|
||||
{
|
||||
$userId = $this->validateQrCodeToken($data['qr_code_token']);
|
||||
$user = User::find($userId);
|
||||
if (!$user || $user->status != 1) {
|
||||
throw new BusinessException('用户无效', 403);
|
||||
}
|
||||
|
||||
$system = $this->validateSystem($data['system_code']);
|
||||
$this->validateSystemPermission($user, $system);
|
||||
$payload = $this->buildJwtPayload($user, $system);
|
||||
$token = $this->generateAccessToken($payload);
|
||||
$refreshToken = $this->generateRefreshToken($user->id);
|
||||
|
||||
return [
|
||||
'access_token' => $token,
|
||||
'refresh_token' => $refreshToken,
|
||||
'expires_in' => config('jwt.jwt_expiration'),
|
||||
'payload' => $payload,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
public function register(array $data)
|
||||
{
|
||||
$userSnModel = new UserSn();
|
||||
$sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first();
|
||||
if (!$sn) {
|
||||
throw new BusinessException('未找到适用编号');
|
||||
}
|
||||
$data['sn'] = $sn->user_sn;
|
||||
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
|
||||
$user = User::create($data);
|
||||
$this->updateUserId($user->id, $sn->user_sn);
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function updateUserId($userId, $userSn)
|
||||
{
|
||||
$userSn = User::where('user_sn', $userSn)->first();
|
||||
if (!$userSn) {
|
||||
throw new BusinessException('用户编号不存在');
|
||||
}
|
||||
return $userSn->update([
|
||||
'user_id' => $userId
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
* @param string $refreshToken
|
||||
* @param string|null $systemCode 可选,指定系统
|
||||
* @return array
|
||||
*/
|
||||
public function refreshToken(string $refreshToken, ?string $systemCode = null): array
|
||||
{
|
||||
$userId = Cache::get("refresh_token:{$refreshToken}");
|
||||
if (!$userId) {
|
||||
throw new BusinessException('refresh_token无效或已过期', 401);
|
||||
}
|
||||
$user = User::find($userId);
|
||||
if (!$user || $user->status != 1) {
|
||||
throw new BusinessException('用户无效', 403);
|
||||
}
|
||||
|
||||
$system = null;
|
||||
if ($systemCode) {
|
||||
$system = Systems::where('code', $systemCode)->first();
|
||||
if (!$system) {
|
||||
throw new BusinessException('系统不存在', 403);
|
||||
}
|
||||
$this->validateSystemPermission($user, $system);
|
||||
}
|
||||
|
||||
$payload = $this->buildJwtPayload($user, $system);
|
||||
$token = $this->generateAccessToken($payload);
|
||||
|
||||
return [
|
||||
'access_token' => $token,
|
||||
'expires_in' => config('jwt.jwt_expiration'),
|
||||
'payload' => $payload,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录用户token到 Redis 和 MySQL
|
||||
*/
|
||||
protected function recordUserToken(int $userId, string $token)
|
||||
{
|
||||
$request = request();
|
||||
$ip = $request ? $request->getRealIp() : '';
|
||||
$ua = $request ? $request->header('user-agent', '') : '';
|
||||
$now = time();
|
||||
$expireAt = $now + config('jwt.jwt_expiration');
|
||||
|
||||
// Redis Set 用户token集合
|
||||
Cache::sAdd("user_tokens:{$userId}", $token);
|
||||
Cache::expire("user_tokens:{$userId}", config('jwt.jwt_expiration'));
|
||||
|
||||
// Redis Hash token元信息
|
||||
$meta = [
|
||||
'login_ip' => $ip,
|
||||
'user_agent' => $ua,
|
||||
'login_time' => $now,
|
||||
'expired_at' => $expireAt,
|
||||
];
|
||||
Cache::hMSet("user_token_meta:{$token}", $meta);
|
||||
Cache::expire("user_token_meta:{$token}", config('jwt.jwt_expiration'));
|
||||
|
||||
// 可选:MySQL 持久化
|
||||
try {
|
||||
UserTokenLog::create([
|
||||
'user_id' => $userId,
|
||||
'token' => $token,
|
||||
'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): array
|
||||
{
|
||||
$payload = (new Jwt())->verifyToken($token);
|
||||
if (Cache::has("token_blacklist:{$token}")) {
|
||||
throw new BusinessException('token已失效', 403);
|
||||
}
|
||||
return (array)$payload;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 登出/失效token
|
||||
// */
|
||||
// public function logout(?string $token)
|
||||
// {
|
||||
// if ($token) {
|
||||
// Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration'));
|
||||
// }
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * 强制拉黑token
|
||||
// */
|
||||
// public function blockToken(string $token)
|
||||
// {
|
||||
// if (!$token) {
|
||||
// throw new BusinessException('token不能为空', 422);
|
||||
// }
|
||||
// Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration'));
|
||||
// }
|
||||
|
||||
public function logout(?string $token)
|
||||
{
|
||||
if ($token) {
|
||||
Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration'));
|
||||
// 1. 解析token查用户
|
||||
$payload = null;
|
||||
try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {}
|
||||
$userId = $payload['uid'] ?? null;
|
||||
if ($userId) {
|
||||
Cache::sRem("user_tokens:{$userId}", $token);
|
||||
}
|
||||
// 2. 删除meta
|
||||
Cache::del("user_token_meta:{$token}");
|
||||
|
||||
// 3. 更新MySQL为失效
|
||||
\app\common\model\UserTokenLog::where('token', $token)->update(['is_valid'=>0]);
|
||||
}
|
||||
}
|
||||
|
||||
public function blockToken(string $token)
|
||||
{
|
||||
if (!$token) {
|
||||
throw new BusinessException('token不能为空', 422);
|
||||
}
|
||||
Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration'));
|
||||
|
||||
// 1. 解析token查用户
|
||||
$payload = null;
|
||||
try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {}
|
||||
$userId = $payload['uid'] ?? null;
|
||||
if ($userId) {
|
||||
Cache::sRem("user_tokens:{$userId}", $token);
|
||||
}
|
||||
Cache::del("user_token_meta:{$token}");
|
||||
\app\common\model\UserTokenLog::where('token', $token)->update(['is_valid'=>0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有活跃token及元信息
|
||||
*/
|
||||
public function getUserTokens(int $userId): array
|
||||
{
|
||||
$tokens = Cache::sMembers("user_tokens:{$userId}") ?: [];
|
||||
$result = [];
|
||||
foreach ($tokens as $token) {
|
||||
$meta = Cache::hGetAll("user_token_meta:{$token}");
|
||||
// 可选再查 MySQL 补充日志记录等
|
||||
$result[] = [
|
||||
'token' => $token,
|
||||
'meta' => $meta,
|
||||
];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制某用户所有token全部下线
|
||||
*/
|
||||
public function blockAllTokensOfUser(int $userId)
|
||||
{
|
||||
$tokens = Cache::sMembers("user_tokens:{$userId}") ?: [];
|
||||
foreach ($tokens as $token) {
|
||||
$this->blockToken($token);
|
||||
}
|
||||
}
|
||||
|
||||
/** ----------- 内部方法 ------------- **/
|
||||
|
||||
/**
|
||||
* 用户验证
|
||||
*/
|
||||
private function validateUser(string $account, string $password)
|
||||
{
|
||||
$user = User::byAccount($account)->first();
|
||||
if (!$user || !password_verify($password, $user->password)) {
|
||||
throw new BusinessException('账号或密码错误', 401);
|
||||
}
|
||||
if ($user->status != 1) {
|
||||
throw new BusinessException('账号已禁用', 403);
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统验证
|
||||
*/
|
||||
private function validateSystem(string $systemCode)
|
||||
{
|
||||
$system = Systems::where('code', $systemCode)->first();
|
||||
if (!$system) {
|
||||
throw new BusinessException('系统不存在', 403);
|
||||
}
|
||||
return $system;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户系统权限校验
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ? $system->domain_url : '',
|
||||
'iat' => $now,
|
||||
'nbf' => $now,
|
||||
'exp' => $exp,
|
||||
'typ' => 'access',
|
||||
'uid' => $user->id,
|
||||
'role_ids' => $role_ids,
|
||||
'system_ids' => $system_ids
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT签发
|
||||
*/
|
||||
private function generateAccessToken(array $payload)
|
||||
{
|
||||
return (new Jwt())->generateToken($payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新令牌生成
|
||||
*/
|
||||
private function generateRefreshToken(int $userId)
|
||||
{
|
||||
$refreshToken = bin2hex(random_bytes(32));
|
||||
Cache::set("refresh_token:{$refreshToken}", $userId, config('jwt.jwt_refresh_token_expiration'));
|
||||
return $refreshToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证扫码令牌
|
||||
*/
|
||||
private function validateQrCodeToken(string $qrCodeToken)
|
||||
{
|
||||
$userId = Cache::get("qr_code_token:{$qrCodeToken}");
|
||||
if (!$userId) {
|
||||
throw new BusinessException('扫码令牌无效或已过期', 401);
|
||||
}
|
||||
return $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用飞书API获取用户信息(示例)
|
||||
*/
|
||||
private function getFeishuUserInfo(string $code)
|
||||
{
|
||||
// 实际实现需要调用飞书 API
|
||||
return [
|
||||
'user_id' => 'feishu_user_123',
|
||||
'name' => '飞书用户',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找或创建飞书用户(示例)
|
||||
*/
|
||||
private function getOrCreateUser(array $feishuUserInfo)
|
||||
{
|
||||
$user = User::where('feishu_user_id', $feishuUserInfo['user_id'])->first();
|
||||
if (!$user) {
|
||||
$user = new User();
|
||||
$user->feishu_user_id = $feishuUserInfo['user_id'];
|
||||
$user->name = $feishuUserInfo['name'];
|
||||
$user->status = 1;
|
||||
$user->save();
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user