first push
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\service\AuthService;
|
||||
use app\common\validate\UserValidate;
|
||||
use support\Request;
|
||||
|
||||
class AuthController extends BaseController
|
||||
{
|
||||
|
||||
public function login(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->validate(new UserValidate(), $request->post(), 'upwd_login');
|
||||
$result = (new AuthService())->login($data);
|
||||
return $this->success($result, '登录成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function register(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->validate(new UserValidate(), $request->post(), 'upwd_register');
|
||||
$result = (new AuthService())->register($data);
|
||||
return $this->success($result, '注册成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage().'|'.$e->getFile().'|'.$e->getLine());
|
||||
}
|
||||
}
|
||||
|
||||
public function verify(Request $request)
|
||||
{
|
||||
try {
|
||||
$token = $request->post('access_token') ?: $request->header('authorization');
|
||||
$systemCode = $request->post('system_code');
|
||||
if (!$token || !$systemCode) return $this->error('参数缺失', 422);
|
||||
$payload = (new AuthService())->verifyToken($token, $systemCode);
|
||||
return $this->success($payload, '验证成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage() ?: '无效或过期的token', 401);
|
||||
}
|
||||
}
|
||||
|
||||
public function refresh(Request $request)
|
||||
{
|
||||
try {
|
||||
$refresh_token = $request->post('refresh_token');
|
||||
$systemCode = $request->post('system_code');
|
||||
if (!$refresh_token || !$systemCode) return $this->error('参数缺失', 422);
|
||||
$result = (new AuthService())->refreshToken($refresh_token, $systemCode);
|
||||
return $this->success($result, '刷新成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage(), 401);
|
||||
}
|
||||
}
|
||||
|
||||
public function logout(Request $request)
|
||||
{
|
||||
$token = $request->post('access_token') ?: $request->header('Authorization');
|
||||
$systemCode = $request->post('system_code') ?: $request->header('system_code');
|
||||
(new AuthService())->logout($token, $systemCode);
|
||||
return $this->success([], '已登出');
|
||||
}
|
||||
|
||||
public function blockToken(Request $request)
|
||||
{
|
||||
$token = $request->post('token');
|
||||
$systemCode = $request->post('system_code');
|
||||
if (!$token || !$systemCode) return $this->error('参数缺失', 422);
|
||||
(new AuthService())->blockToken($token, $systemCode);
|
||||
return $this->success([], '已强制停用该token');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use support\exception\BusinessException;
|
||||
use app\common\traits\ResponseTrait;
|
||||
use think\Validate;
|
||||
|
||||
abstract class BaseController
|
||||
{
|
||||
use ResponseTrait;
|
||||
|
||||
protected mixed $validator = null;
|
||||
|
||||
/**
|
||||
* 验证数据
|
||||
*
|
||||
* @param Validate $validator 验证器实例
|
||||
* @param array $data 待验证的数据
|
||||
* @param string $scene 验证场景
|
||||
* @return array 验证后的数据
|
||||
* @throws BusinessException 验证失败时抛出业务异常
|
||||
*/
|
||||
public function validate(Validate $validator, array $data, string $scene = ''): array
|
||||
{
|
||||
// 如果指定了验证场景,并且验证器支持场景方法,则设置验证场景
|
||||
if ($scene && method_exists($validator, 'scene')) {
|
||||
$validator->scene($scene);
|
||||
}
|
||||
// 执行数据验证
|
||||
if (!$validator->check($data)) {
|
||||
// 验证失败,抛出业务异常,异常信息为验证器返回的错误信息
|
||||
throw new BusinessException($validator->getError());
|
||||
}
|
||||
// 验证成功,返回原始数据
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
/**
|
||||
* resources 资源表:目录/菜单/权限
|
||||
* @property integer $id 资源ID(主键)
|
||||
* @property string $name 资源名称
|
||||
* @property string $type 资源类型
|
||||
* @property string $code 资源编码(权限标识)
|
||||
* @property integer $parent_id 父资源ID
|
||||
* @property string $url 路由 path 或 外链 URL
|
||||
* @property string $icon 图标,仅目录/菜单有效
|
||||
* @property string $open_type 菜单打开方式
|
||||
* @property integer $sort 排序
|
||||
* @property string $created_at
|
||||
* @property string $updated_at
|
||||
*/
|
||||
class Resource extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'resources';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
|
||||
// 所属系统
|
||||
public function system()
|
||||
{
|
||||
return $this->belongsTo(Systems::class, 'system_id', 'id');
|
||||
}
|
||||
|
||||
// 拥有该资源的角色
|
||||
public function roles()
|
||||
{
|
||||
return $this->belongsToMany(Role::class, 'role_resources', 'resource_id', 'role_id');
|
||||
}
|
||||
|
||||
// 拥有该资源的用户(特殊补权)
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'user_resources', 'resource_id', 'user_id');
|
||||
}
|
||||
|
||||
// 递归
|
||||
public function parent()
|
||||
{
|
||||
return $this->belongsTo(self::class, 'parent_id');
|
||||
}
|
||||
|
||||
public function children()
|
||||
{
|
||||
return $this->hasMany(self::class, 'parent_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
/**
|
||||
* roles 角色表
|
||||
* @property integer $id 角色ID(主键)
|
||||
* @property string $name 角色名称
|
||||
* @property string $desc 角色描述
|
||||
* @property string $created_at
|
||||
* @property string $updated_at
|
||||
*/
|
||||
class Role extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'roles';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
// 角色下所有用户
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'user_roles', 'role_id', 'user_id');
|
||||
}
|
||||
|
||||
// 角色拥有的资源(权限点、菜单)
|
||||
public function resources()
|
||||
{
|
||||
return $this->belongsToMany(Resource::class, 'role_resources', 'role_id', 'resource_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
/**
|
||||
* systems 系统/应用表
|
||||
* @property integer $id 系统ID(主键)
|
||||
* @property string $code 系统编码(唯一英文标识)
|
||||
* @property string $name 系统名称
|
||||
* @property string $type 系统类型
|
||||
* @property string $desc 系统描述
|
||||
* @property string $icon 系统图标
|
||||
* @property string $domain_url 系统URL
|
||||
* @property string $is_role 是否区分角色权限
|
||||
* @property integer $sort 排序
|
||||
* @property integer $status 状态
|
||||
* @property integer $created_at 创建时间
|
||||
* @property integer $updated_at 更新时间
|
||||
*/
|
||||
class Systems extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'systems';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
|
||||
// 系统下的资源
|
||||
public function resources()
|
||||
{
|
||||
return $this->hasMany(Resource::class, 'system_id', 'id');
|
||||
}
|
||||
|
||||
// 拥有该系统访问权限的用户
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'user_systems', 'system_id', 'user_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
/**
|
||||
* users 用户表
|
||||
* @property integer $id 用户ID(主键)
|
||||
* @property string $sn 编号
|
||||
* @property string $name 姓名
|
||||
* @property string $avatar 头像
|
||||
* @property string $password 密码
|
||||
* @property string $phone 手机
|
||||
* @property string $email 邮箱
|
||||
* @property integer $status 状态
|
||||
* @property string $system_code 系统码
|
||||
* @property string $created_at
|
||||
* @property string $updated_at
|
||||
*/
|
||||
class User extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'user';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
protected $dateFormat = 'U';
|
||||
|
||||
protected $fillable = [
|
||||
'sn',
|
||||
'name',
|
||||
'avatar',
|
||||
'password',
|
||||
'phone',
|
||||
'email',
|
||||
'status',
|
||||
'system_code'
|
||||
];
|
||||
|
||||
// 用户所有角色
|
||||
public function roles()
|
||||
{
|
||||
return $this->belongsToMany(Role::class, 'user_roles', 'user_id', 'role_id');
|
||||
}
|
||||
|
||||
// 用户直接拥有的资源(用于特殊补权)
|
||||
public function resources()
|
||||
{
|
||||
return $this->belongsToMany(Resource::class, 'user_resources', 'user_id', 'resource_id');
|
||||
}
|
||||
|
||||
// 用户可访问的系统
|
||||
public function systems()
|
||||
{
|
||||
return $this->belongsToMany(Systems::class, 'user_systems', 'user_id', 'system_id');
|
||||
}
|
||||
|
||||
// 账号查询的作用域
|
||||
public function scopeByAccount($query, $account)
|
||||
{
|
||||
return $query->where('sn', $account)
|
||||
->orWhere('phone', $account)
|
||||
->orWhere('email', $account);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
/**
|
||||
* user_sn 用户-编号
|
||||
* @property integer $user_sn
|
||||
* @property integer $user_id
|
||||
*/
|
||||
class UserSn extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'user_sn';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'user_sn',
|
||||
'user_id'
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
/**
|
||||
* user_token_log 用户token登录日志
|
||||
* @property integer $id (主键)
|
||||
* @property integer $user_id
|
||||
* @property string $token
|
||||
* @property string $login_ip
|
||||
* @property string $user_agent
|
||||
* @property integer $login_time
|
||||
* @property integer $expired_at
|
||||
* @property integer $is_valid 1有效 0已拉黑
|
||||
*/
|
||||
class UserTokenLog extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'user_token_log';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'token',
|
||||
'login_ip',
|
||||
'user_agent',
|
||||
'login_time',
|
||||
'expired_at',
|
||||
'is_valid',
|
||||
'system_code'
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\logic\AuthLogic;
|
||||
use app\common\model\User;
|
||||
use support\Db;
|
||||
|
||||
class AuthService
|
||||
{
|
||||
|
||||
/**
|
||||
* 登录,支持多类型
|
||||
*/
|
||||
public function login(array $data): array
|
||||
{
|
||||
$loginType = $data['login_type'] ?? 'upwd';
|
||||
$logic = new AuthLogic();
|
||||
|
||||
return match ($loginType) {
|
||||
'feishu' => $logic->feishuLogin($data),
|
||||
'qrcode' => $logic->qrCodeLogin($data),
|
||||
default => $logic->uPwdLogin($data),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*/
|
||||
public function register(array $data)
|
||||
{
|
||||
$logic = new AuthLogic();
|
||||
try {
|
||||
Db::beginTransaction();
|
||||
$result = $logic->register($data);
|
||||
Db::commit();
|
||||
return $result;
|
||||
} catch (\Throwable $e) {
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验token
|
||||
*/
|
||||
public function verifyToken(string $token, string $systemCode): array
|
||||
{
|
||||
return (new AuthLogic())->verifyToken($token, $systemCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
* @param string $refreshToken
|
||||
* @param string|null $systemCode
|
||||
*/
|
||||
public function refreshToken(string $refreshToken, string $systemCode): array
|
||||
{
|
||||
return (new AuthLogic())->refreshToken($refreshToken, $systemCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出(失效token)
|
||||
*/
|
||||
public function logout(?string $token, string $systemCode): void
|
||||
{
|
||||
(new AuthLogic())->logout($token, $systemCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制拉黑token
|
||||
*/
|
||||
public function blockToken(string $token, string $systemCode): void
|
||||
{
|
||||
(new AuthLogic())->blockToken($token, $systemCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\logic\AuthLogic;
|
||||
use app\common\model\User;
|
||||
use support\Cache;
|
||||
|
||||
class AuthService_bak
|
||||
{
|
||||
// 缓存 Key
|
||||
protected static function cacheKey(int $userId): string
|
||||
{
|
||||
return "user_resources_{$userId}";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有资源(目录/菜单/权限),合并角色+直接分配,并缓存
|
||||
*/
|
||||
public static function getUserResources(int $userId)
|
||||
{
|
||||
$cacheKey = self::cacheKey($userId);
|
||||
$cached = Cache::get($cacheKey);
|
||||
if ($cached !== null) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$user = User::with(['roles.resources', 'resources'])->find($userId);
|
||||
if (!$user) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
// 合并:角色资源 + 用户直接资源
|
||||
$all = collect();
|
||||
foreach ($user->roles as $role) {
|
||||
$all = $all->merge($role->resources);
|
||||
}
|
||||
$all = $all->merge($user->resources);
|
||||
$unique = $all->unique('id')->values();
|
||||
|
||||
Cache::set($cacheKey, $unique, 3600);
|
||||
return $unique;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建目录/菜单树,并给每个菜单节点挂载权限列表和打开方式
|
||||
*/
|
||||
public static function getMenuTree(int $userId): array
|
||||
{
|
||||
$resources = self::getUserResources($userId);
|
||||
$menus = $resources
|
||||
->filter(fn($r) => in_array($r->type, ['directory','menu']))
|
||||
->values();
|
||||
return self::buildTree($menus, $resources);
|
||||
}
|
||||
|
||||
protected static function buildTree($nodes, $allResources, int $parentId = 0): array
|
||||
{
|
||||
$tree = [];
|
||||
foreach ($nodes as $node) {
|
||||
if ($node->parent_id !== $parentId) continue;
|
||||
|
||||
// 子节点
|
||||
$children = self::buildTree($nodes, $allResources, $node->id);
|
||||
|
||||
// 本级权限列表
|
||||
$authList = $allResources
|
||||
->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id)
|
||||
->map(fn($r) => [
|
||||
'id' => $r->id,
|
||||
'title' => $r->name,
|
||||
'auth_mark' => $r->code,
|
||||
])->values()->all();
|
||||
|
||||
$tree[] = [
|
||||
'id' => $node->id,
|
||||
'name' => $node->name,
|
||||
'path' => $node->url,
|
||||
'meta' => [
|
||||
'title' => $node->name,
|
||||
'icon' => $node->icon,
|
||||
'keepAlive' => true,
|
||||
'authList' => $authList,
|
||||
// 新增打开方式字段
|
||||
'openType' => $node->open_type, // 'internal' | 'external' | 'iframe'
|
||||
],
|
||||
'children' => $children,
|
||||
];
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有权限 code(仅 type=permission)
|
||||
*/
|
||||
public static function getPermissionCodes(int $userId): array
|
||||
{
|
||||
return self::getUserResources($userId)
|
||||
->filter(fn($res) => $res->type === 'permission')
|
||||
->pluck('code')
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/** 清除缓存(角色/资源变更时调用) */
|
||||
public static function clearCache(int $userId): void
|
||||
{
|
||||
Cache::delete(self::cacheKey($userId));
|
||||
}
|
||||
|
||||
public function login(array $data): array
|
||||
{
|
||||
$loginType = $data['login_type'] ?? 'upwd';
|
||||
$logic = new AuthLogic();
|
||||
|
||||
return match ($loginType) {
|
||||
'feishu' => $logic->feishuLogin($data),
|
||||
'qrcode' => $logic->qrCodeLogin($data),
|
||||
default => $logic->uPwdLogin($data),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*
|
||||
* @param array $data
|
||||
* @return User
|
||||
*/
|
||||
public function register(array $data): User
|
||||
{
|
||||
return (new AuthLogic())->register($data);
|
||||
}
|
||||
|
||||
|
||||
public function verifyToken(string $token): array
|
||||
{
|
||||
return (new AuthLogic())->verifyToken($token);
|
||||
}
|
||||
|
||||
public function refreshToken(string $refreshToken): array
|
||||
{
|
||||
return (new AuthLogic())->refreshToken($refreshToken);
|
||||
}
|
||||
|
||||
public function logout(?string $token)
|
||||
{
|
||||
(new AuthLogic())->logout($token);
|
||||
}
|
||||
|
||||
public function blockToken(string $token)
|
||||
{
|
||||
(new AuthLogic())->blockToken($token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\model\User;
|
||||
use support\Cache;
|
||||
|
||||
class PermService
|
||||
{
|
||||
|
||||
// 用户资源缓存key
|
||||
protected static function cacheKey(int $userId): string
|
||||
{
|
||||
return "user_resources_{$userId}";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有资源(合并角色+直接分配),结果缓存
|
||||
*/
|
||||
public static function getUserResources(int $userId)
|
||||
{
|
||||
$cacheKey = self::cacheKey($userId);
|
||||
$cached = Cache::get($cacheKey);
|
||||
if ($cached !== null) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$user = User::with(['roles.resources', 'resources'])->find($userId);
|
||||
if (!$user) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$all = collect();
|
||||
foreach ($user->roles as $role) {
|
||||
$all = $all->merge($role->resources);
|
||||
}
|
||||
$all = $all->merge($user->resources);
|
||||
$unique = $all->unique('id')->values();
|
||||
|
||||
Cache::set($cacheKey, $unique, 3600);
|
||||
return $unique;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建目录/菜单树,节点包含权限与打开方式
|
||||
*/
|
||||
public static function getMenuTree(int $userId): array
|
||||
{
|
||||
$resources = self::getUserResources($userId);
|
||||
$menus = $resources
|
||||
->filter(fn($r) => in_array($r->type, ['directory','menu']))
|
||||
->values();
|
||||
return self::buildTree($menus, $resources);
|
||||
}
|
||||
|
||||
protected static function buildTree($nodes, $allResources, int $parentId = 0): array
|
||||
{
|
||||
$tree = [];
|
||||
foreach ($nodes as $node) {
|
||||
if ($node->parent_id !== $parentId) continue;
|
||||
|
||||
$children = self::buildTree($nodes, $allResources, $node->id);
|
||||
|
||||
$authList = $allResources
|
||||
->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id)
|
||||
->map(fn($r) => [
|
||||
'id' => $r->id,
|
||||
'title' => $r->name,
|
||||
'auth_mark' => $r->code,
|
||||
])->values()->all();
|
||||
|
||||
$tree[] = [
|
||||
'id' => $node->id,
|
||||
'name' => $node->name,
|
||||
'path' => $node->url,
|
||||
'meta' => [
|
||||
'title' => $node->name,
|
||||
'icon' => $node->icon,
|
||||
'keepAlive' => true,
|
||||
'authList' => $authList,
|
||||
'openType' => $node->open_type, // 'internal' | 'external' | 'iframe'
|
||||
],
|
||||
'children' => $children,
|
||||
];
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有权限code
|
||||
*/
|
||||
public static function getPermissionCodes(int $userId): array
|
||||
{
|
||||
return self::getUserResources($userId)
|
||||
->filter(fn($res) => $res->type === 'permission')
|
||||
->pluck('code')
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除资源相关缓存
|
||||
*/
|
||||
public static function clearCache(int $userId): void
|
||||
{
|
||||
Cache::delete(self::cacheKey($userId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\model\User;
|
||||
use app\model\Role;
|
||||
use app\model\Resource;
|
||||
|
||||
class ResourceService
|
||||
{
|
||||
/**
|
||||
* 获取用户可见菜单树
|
||||
*/
|
||||
public static function getMenuTreeForUser($userId)
|
||||
{
|
||||
$menuList = self::getUserMenus($userId);
|
||||
// 转树
|
||||
return self::buildTree($menuList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有有权限的菜单资源(type=menu)
|
||||
*/
|
||||
public static function getUserMenus($userId)
|
||||
{
|
||||
$user = User::with('roles.resources')->find($userId);
|
||||
if (!$user) return [];
|
||||
// 合并所有角色下的菜单资源
|
||||
$allMenus = [];
|
||||
foreach ($user->roles as $role) {
|
||||
foreach ($role->resources as $res) {
|
||||
if ($res->type === 'menu') {
|
||||
$allMenus[$res->id] = $res;
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_values($allMenus); // 去重后返回
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有有权限的按钮/功能权限 code
|
||||
*/
|
||||
public static function getButtonPermissionCodesForUser($userId)
|
||||
{
|
||||
$user = User::with('roles.resources')->find($userId);
|
||||
if (!$user) return [];
|
||||
$codes = [];
|
||||
foreach ($user->roles as $role) {
|
||||
foreach ($role->resources as $res) {
|
||||
if ($res->type === 'button' && $res->code) {
|
||||
$codes[$res->code] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_keys($codes); // 去重后返回权限code数组
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归构建菜单树
|
||||
* @param Resource[] $list
|
||||
* @param int $parentId
|
||||
* @return array
|
||||
*/
|
||||
protected static function buildTree($list, $parentId = 0)
|
||||
{
|
||||
$tree = [];
|
||||
foreach ($list as $item) {
|
||||
if ($item->parent_id == $parentId) {
|
||||
$children = self::buildTree($list, $item->id);
|
||||
$arr = [
|
||||
'id' => $item->id,
|
||||
'name' => $item->name,
|
||||
'path' => $item->url,
|
||||
'icon' => $item->icon ?? null,
|
||||
'code' => $item->code,
|
||||
'children' => $children,
|
||||
];
|
||||
$tree[] = $arr;
|
||||
}
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\model\User;
|
||||
|
||||
class UserService
|
||||
{
|
||||
public static function getInfo($userId)
|
||||
{
|
||||
$user = User::with('roles')->find($userId);
|
||||
if (!$user) return null;
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'username' => $user->username,
|
||||
'roles' => $user->roles->pluck('name')->toArray(),
|
||||
// 可扩展其他字段
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\traits;
|
||||
|
||||
use support\Response;
|
||||
|
||||
trait ResponseTrait
|
||||
{
|
||||
/**
|
||||
* 返回成功响应
|
||||
*
|
||||
* @param mixed $data 响应数据
|
||||
* @param string $message 响应消息,默认“请求成功”
|
||||
* @param int $code 内部状态码,默认 0 表示成功
|
||||
* @param int $httpStatus HTTP 状态码,默认 200
|
||||
* @param array $header 响应头,默认 JSON 格式
|
||||
* @return Response
|
||||
*/
|
||||
public function success(
|
||||
$data = [],
|
||||
string $message = '请求成功',
|
||||
int $code = 0,
|
||||
int $httpStatus = 200,
|
||||
array $header = ['Content-Type' => 'application/json']
|
||||
): Response {
|
||||
return $this->result('success', $code, $httpStatus, $message, $data, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回错误响应
|
||||
*
|
||||
* @param string $message 响应错误消息,默认“请求失败”
|
||||
* @param int $code 内部状态码,默认 1 表示错误
|
||||
* @param int $httpStatus HTTP 状态码,默认 400
|
||||
* @param mixed $data 可选的错误数据
|
||||
* @param array $header 响应头,默认 JSON 格式
|
||||
* @return Response
|
||||
*/
|
||||
public function error(
|
||||
string $message = '请求失败',
|
||||
int $code = 1,
|
||||
int $httpStatus = 400,
|
||||
$data = [],
|
||||
array $header = ['Content-Type' => 'application/json']
|
||||
): Response {
|
||||
return $this->result('error', $code, $httpStatus, $message, $data, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一响应格式方法
|
||||
*
|
||||
* @param string $status 响应状态,例如 "success" 或 "error"
|
||||
* @param int $code 内部状态码
|
||||
* @param int $httpStatus HTTP 状态码
|
||||
* @param string $message 响应消息
|
||||
* @param mixed $data 响应数据
|
||||
* @param array $header 响应头
|
||||
* @return Response
|
||||
*/
|
||||
protected function result(
|
||||
string $status,
|
||||
int $code,
|
||||
int $httpStatus,
|
||||
string $message,
|
||||
$data,
|
||||
array $header
|
||||
): Response {
|
||||
$body = [
|
||||
'status' => $status,
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
'data' => $data,
|
||||
];
|
||||
return response(json_encode($body, JSON_UNESCAPED_UNICODE), $httpStatus, $header);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\utils;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use support\Cache;
|
||||
use Webman\Exception\BusinessException;
|
||||
|
||||
class Feishu
|
||||
{
|
||||
|
||||
protected $appId = '';
|
||||
protected $appSecret = '';
|
||||
protected $baseUri = '';
|
||||
protected $client = null;
|
||||
protected $options = [];
|
||||
protected $error = '';
|
||||
protected $gs = 'hesu';
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
public function __construct(string $appId = '', string $appSecret = '', string $gs = 'hesu')
|
||||
{
|
||||
|
||||
if ($gs === 'mbaf') {
|
||||
$this->baseUri = 'https://open.larksuite.com/open-apis/';
|
||||
} else {
|
||||
$this->baseUri = 'https://open.feishu.cn/open-apis/';
|
||||
}
|
||||
|
||||
$this->appId = $appId;
|
||||
$this->appSecret = $appSecret;
|
||||
$this->gs = $gs;
|
||||
|
||||
$this->client = new Client([
|
||||
'base_uri' => $this->baseUri
|
||||
]);
|
||||
|
||||
$this->options = [
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/json; charset=utf-8'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常
|
||||
*/
|
||||
public function error()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求
|
||||
*/
|
||||
public function requestResult($url = "", $method = "POST")
|
||||
{
|
||||
try {
|
||||
$res = $this->client->request($method, $this->baseUri.$url, $this->options);
|
||||
if ($res->getBody()) {
|
||||
$result = json_decode($res->getBody(), true);
|
||||
// var_dump($result);
|
||||
if ($result["code"] === 0) {
|
||||
return $result;
|
||||
} else {
|
||||
$this->error = $result["msg"];
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->error = "result error";
|
||||
return false;
|
||||
}
|
||||
} catch (RequestException $e) {
|
||||
if ($e->hasResponse()) {
|
||||
var_dump('2 :'.$e->getResponse()->getBody());
|
||||
throw new BusinessException($e->getResponse()->getBody());
|
||||
} else {
|
||||
var_dump('3 :'.$e->getMessage());
|
||||
throw new BusinessException($e->getMessage());
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
var_dump('4 :'.$th->getFile()."|".$th->getLine()."|".$th->getMessage());
|
||||
$this->error = $th->getFile()."|".$th->getLine()."|".$th->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 授权
|
||||
*/
|
||||
public function authorize($certificate, $url = 'authen/v1/authorize')
|
||||
{
|
||||
$arr = [
|
||||
'app_id' => $certificate['appId'],
|
||||
'redirect_uri' => $certificate['rUrl'],
|
||||
'scope' => $certificate['scope'],
|
||||
'state' => $certificate['state']
|
||||
];
|
||||
$queryData = http_build_query($arr);
|
||||
return $this->baseUri.$url.'?'.$queryData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自建应用获取 tenant_access_token
|
||||
*/
|
||||
public function tenantAccessToken()
|
||||
{
|
||||
$tenantAccessToken = Cache::get($this->gs.'-tenantAccessToken');
|
||||
if (!$tenantAccessToken) {
|
||||
$this->options["json"] = [
|
||||
"app_id" => $this->appId,
|
||||
"app_secret" => $this->appSecret
|
||||
];
|
||||
$rst = $this->requestResult('auth/v3/tenant_access_token/internal', 'POST');
|
||||
if ($rst) {
|
||||
$tenantAccessToken = $rst['tenant_access_token'];
|
||||
Cache::set($this->gs.'-tenantAccessToken', $tenantAccessToken, $rst['expire']);
|
||||
} else {
|
||||
throw new BusinessException('tenant_access_token 获取失败');
|
||||
}
|
||||
}
|
||||
return $tenantAccessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过手机号或邮箱获取用户 ID
|
||||
*/
|
||||
public function getUserIdByPhoneOrEmail($phone = '', $userIdType = 'open_id')
|
||||
{
|
||||
$this->options["headers"] = [
|
||||
"Authorization" => "Bearer " . $this->tenantAccessToken(),
|
||||
"Content-Type" => "application/json; charset=utf-8"
|
||||
];
|
||||
$this->options["json"] = [
|
||||
"emails" => [],
|
||||
"mobiles" => [$phone],
|
||||
"include_resigned" => false
|
||||
];
|
||||
$rst = $this->requestResult('contact/v3/users/batch_get_id?user_id_type='.$userIdType, 'POST');
|
||||
return $rst;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
public function sendMessages($receiveId = '', $content = '', $msgType = 'text', $receiveIdtype = 'open_id')
|
||||
{
|
||||
$this->options["headers"] = [
|
||||
"Authorization" => "Bearer " . $this->tenantAccessToken(),
|
||||
"Content-Type" => "application/json; charset=utf-8"
|
||||
];
|
||||
$this->options["json"] = [
|
||||
"receive_id" => $receiveId,
|
||||
"msg_type" => $msgType,
|
||||
"content" => $content
|
||||
];
|
||||
$rst = $this->requestResult('im/v1/messages?receive_id_type='.$receiveIdtype, 'POST');
|
||||
return $rst;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑消息
|
||||
*/
|
||||
public function updateSendMessages($messageId = '', $content = '', $msgType = 'text')
|
||||
{
|
||||
$this->options["headers"] = [
|
||||
"Authorization" => "Bearer " . $this->tenantAccessToken(),
|
||||
"Content-Type" => "application/json; charset=utf-8"
|
||||
];
|
||||
$this->options["json"] = [
|
||||
"msg_type" => $msgType,
|
||||
"content" => $content
|
||||
];
|
||||
$rst = $this->requestResult('im/v1/messages/'.$messageId, 'PUT');
|
||||
return $rst;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户 user_access_token
|
||||
*/
|
||||
public function userAccessToken($code = "", $redirect_uri = "")
|
||||
{
|
||||
$this->options["headers"] = [
|
||||
"Content-Type" => "application/json; charset=utf-8"
|
||||
];
|
||||
$this->options["json"] = [
|
||||
"grant_type" => "authorization_code",
|
||||
"client_id" => $this->appId,
|
||||
"client_secret" => $this->appSecret,
|
||||
"code" => $code,
|
||||
"redirect_uri" => $redirect_uri
|
||||
];
|
||||
return $this->requestResult('authen/v2/oauth/token');
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
public function userInfo($userAccessToken = "")
|
||||
{
|
||||
$this->options["headers"] = [
|
||||
"Authorization" => "Bearer " . $userAccessToken,
|
||||
"Content-Type" => "application/json; charset=utf-8"
|
||||
];
|
||||
return $this->requestResult('authen/v1/user_info', "GET");
|
||||
}
|
||||
|
||||
// public function user_access_token($code = "")
|
||||
// {
|
||||
// $url = "";
|
||||
// $this->options["headers"] = [
|
||||
// "Authorization" => "Bearer ".$this->tenantAccessToken(),
|
||||
// "Content-Type" => "application/json; charset=utf-8"
|
||||
// ];
|
||||
// $this->options["json"] = [
|
||||
// "grant_type" => "authorization_code",
|
||||
// "code" => $code
|
||||
// ];
|
||||
// $this->options["form_params"] = [
|
||||
// "app_id" => $this->app_id,
|
||||
// "app_secret" => $this->app_secret
|
||||
// ];
|
||||
// return $this->requestResult($url);
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\utils;
|
||||
|
||||
use Firebase\JWT\JWT as FirebaseJWT;
|
||||
use Firebase\JWT\Key;
|
||||
use support\exception\BusinessException;
|
||||
|
||||
class Jwt
|
||||
{
|
||||
protected $secretKey;
|
||||
protected $algorithm;
|
||||
protected $issuer;
|
||||
protected $expiration;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->secretKey = config('jwt.jwt_secret_key');
|
||||
$this->algorithm = config('jwt.jwt_algorithm');
|
||||
$this->issuer = config('jwt.jwt_issuer');
|
||||
$this->expiration = config('jwt.jwt_expiration');
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 JWT 令牌
|
||||
*
|
||||
* @param array $payload
|
||||
* @return string
|
||||
* @throws BusinessException
|
||||
*/
|
||||
public function generateToken(array $payload): string
|
||||
{
|
||||
$defaultPayload = [
|
||||
// // —— 注册 Claim ——
|
||||
'iss' => $this->issuer, // 签发者
|
||||
// 'sub' => 'user_1001', // 面向的用户
|
||||
// 'aud' => 'api.gateway.example.com', // 接收方
|
||||
// 'iat' => time(), // 签发时间
|
||||
// 'nbf' => time(), // 生效时间
|
||||
// 'exp' => time() + $this->expiration, // 1 小时后过期
|
||||
'jti' => bin2hex(random_bytes(16)), // 唯一 ID
|
||||
// // —— 自定义 Claim ——
|
||||
// 'uid' => 1001, // 业务用户 ID
|
||||
// 'roles' => ['admin', 'editor'], // 用户角色
|
||||
// 'meta' => ['region' => 'CN', 'tier' => 5], // 任意自定义数据
|
||||
];
|
||||
|
||||
$payload = array_merge($defaultPayload, $payload);
|
||||
|
||||
try {
|
||||
return FirebaseJWT::encode($payload, $this->secretKey, $this->algorithm);
|
||||
} catch (\Exception $e) {
|
||||
throw new BusinessException("令牌生成失败:" . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 JWT 令牌
|
||||
*
|
||||
* @param string $token
|
||||
* @return object
|
||||
* @throws BusinessException
|
||||
*/
|
||||
public function verifyToken(string $token)
|
||||
{
|
||||
try {
|
||||
return FirebaseJWT::decode($token, new Key($this->secretKey, $this->algorithm));
|
||||
} catch (\Firebase\JWT\ExpiredException $e) {
|
||||
throw new BusinessException('令牌已过期', 401);
|
||||
} catch (\Firebase\JWT\SignatureInvalidException $e) {
|
||||
throw new BusinessException('无效的令牌签名', 401);
|
||||
} catch (\Firebase\JWT\BeforeValidException $e) {
|
||||
throw new BusinessException('令牌尚未生效', 401);
|
||||
} catch (\Exception $e) {
|
||||
throw new BusinessException('令牌验证失败:' . $e->getMessage(), 400);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function describe()
|
||||
{
|
||||
/* payload
|
||||
1. 注册 Claim 详解
|
||||
|
||||
Claim 名称 含义 类型 建议取值
|
||||
iss Issuer,发行者,标识该 JWT 的签发主体 string or URI 你的服务域名或项目标识,比如 https://api.example.com
|
||||
sub Subject,主题,标识该 JWT 所面向的用户 string or int 用户在你系统里的唯一 ID
|
||||
aud Audience,受众,标识该 JWT 的接收方 string or array 接收该令牌的服务标识,比如 ["service-a","service-b"]
|
||||
exp Expiration Time,过期时间,Unix 时间戳,到达此时间后不可用 int time() + 3600(1 小时后过期)
|
||||
nbf Not Before,生效时间,Unix 时间戳,表示在此时间之前不可用 int time() 或者 time() + 60(1 分钟后生效)
|
||||
iat Issued At,签发时间,Unix 时间戳 int time()
|
||||
jti JWT ID,JWT 的唯一标识符,用于去重或一次性令牌 string uniqid('', true) 或者 UUID
|
||||
|
||||
注意:exp、nbf、iat 必须是整数(秒级时间戳),否则库会抛类型异常或验证失败。
|
||||
|
||||
2. 自定义 Claim
|
||||
|
||||
除了上面这些“注册”字段,你可以任意添加,比如:
|
||||
|
||||
php
|
||||
$payload['uid'] = 123; // 业务用户 ID
|
||||
$payload['roles'] = ['admin','user']; // 权限数组
|
||||
$payload['feature'] = 'beta'; // 标记某个功能开关
|
||||
|
||||
这些字段在你的应用程序里可以自由取用。
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\utils;
|
||||
|
||||
class OauthClients
|
||||
{
|
||||
|
||||
/**
|
||||
* 生成 OAuth2 client 凭证
|
||||
*
|
||||
* @param int $idBytes 用于生成 client_id 的随机字节长度(最终 hex 长度 = $idBytes * 2)
|
||||
* @param int $secretBytes 用于生成 client_secret 的随机字节长度(最终 hex 长度 = $secretBytes * 2)
|
||||
* @return array ['client_id' => string, 'client_secret' => string]
|
||||
*/
|
||||
public function generateClientCredentials(int $idBytes = 16, int $secretBytes = 32): array
|
||||
{
|
||||
// 生成随机字节,并转为十六进制字符串
|
||||
$clientId = bin2hex(random_bytes($idBytes));
|
||||
$clientSecret = bin2hex(random_bytes($secretBytes));
|
||||
|
||||
return [
|
||||
'client_id' => $clientId,
|
||||
'client_secret' => $clientSecret,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\validate;
|
||||
|
||||
use app\common\model\Systems;
|
||||
use app\common\model\User;
|
||||
use think\Validate;
|
||||
|
||||
class UserValidate extends Validate
|
||||
{
|
||||
protected $rule = [
|
||||
'name' => 'chsDash|max:60',
|
||||
'avatar' => 'regex:/^(https?:\/\/\S+|\\/[\S\/]+)$/',
|
||||
'password' => 'require|min:8|max:20|alphaNum',
|
||||
'confirm_password' => 'require|confirm:password',
|
||||
'account' => 'require|checkAccount|checkAccountUnique',
|
||||
'phone' => 'checkMobile|checkPhoneUnique',
|
||||
'email' => 'email|checkEmailUnique',
|
||||
'login_type' => 'require|in:upwd,feishu,qrcode',
|
||||
'system_code' => 'require|checkSystemCode'
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'name.chsDash' => '姓名只能是汉字、字母、数字和下划线_及破折号-',
|
||||
'name.max' => '姓名最多不能超过60个字符',
|
||||
'avatar.regex' => '头像必须是合法的URL或有效的文件路径',
|
||||
'password.require' => '密码不能为空',
|
||||
'password.min' => '密码长度不能少于8位',
|
||||
'password.max' => '密码长度不能超过20位',
|
||||
'password.alphaNum' => '密码只能包含字母和数字',
|
||||
'confirm_password.require' => '请再次输入密码',
|
||||
'confirm_password.confirm' => '两次输入的密码不一致',
|
||||
'account.require' => '账号不能为空',
|
||||
'account.checkAccount' => '账号必须是有效的手机号或邮箱',
|
||||
'account.checkAccountUnique' => '账号已被注册',
|
||||
'phone.checkMobile' => '手机号格式不正确',
|
||||
'phone.checkPhoneUnique' => '手机号已被使用',
|
||||
'email.email' => '邮箱格式不正确',
|
||||
'email.checkEmailUnique' => '邮箱已被使用',
|
||||
'login_type.require' => '请选择登录方式',
|
||||
'login_type.in' => '登录方式不支持',
|
||||
'system_code.require' => '系统码不能为空',
|
||||
'system_code.checkSystemCode' => '系统码不存在或不合法'
|
||||
];
|
||||
|
||||
protected $scene = [
|
||||
'upwd_register' => ['name', 'account', 'password', 'confirm_password', 'login_type', 'system_code'],
|
||||
'upwd_login' => ['account', 'password', 'login_type', 'system_code'],
|
||||
];
|
||||
|
||||
/**
|
||||
* 验证账号格式(手机号或邮箱)
|
||||
*/
|
||||
protected function checkAccount($value)
|
||||
{
|
||||
return $this->isMobile($value) || $this->isEmail($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证账号唯一性(注册场景用)
|
||||
*/
|
||||
protected function checkAccountUnique($value, $rule, $data, $field, $title)
|
||||
{
|
||||
// 登录场景不检查唯一性
|
||||
if (isset($this->currentScene) && $this->currentScene === 'login') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$type = $this->getAccountType($value);
|
||||
$field = ($type === 'phone') ? 'phone' : 'email';
|
||||
$value = ($type === 'phone') ? $this->formatPhone($value) : $value;
|
||||
|
||||
return !User::where($field, $value)->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为手机号
|
||||
*/
|
||||
protected function isMobile($value)
|
||||
{
|
||||
$mobile = preg_replace('/[\s+\-]/', '', $value);
|
||||
return preg_match('/^(\+86|0086)?1[3-9]\d{9}$/', $mobile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为邮箱
|
||||
*/
|
||||
protected function isEmail($value)
|
||||
{
|
||||
return filter_var($value, FILTER_VALIDATE_EMAIL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账号类型(phone/email)
|
||||
*/
|
||||
public function getAccountType($account)
|
||||
{
|
||||
return $this->isMobile($account) ? 'phone' : 'email';
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化手机号(移除+86和空格)
|
||||
*/
|
||||
public function formatPhone($phone)
|
||||
{
|
||||
return preg_replace('/^(\+86|0086)|[\s\-]/', '', $phone);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号唯一性验证
|
||||
*/
|
||||
protected function checkPhoneUnique($value)
|
||||
{
|
||||
return !User::where('phone', $this->formatPhone($value))->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 邮箱唯一性验证
|
||||
*/
|
||||
protected function checkEmailUnique($value)
|
||||
{
|
||||
return !User::where('email', $value)->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 system_code 是否在系统表中存在
|
||||
*/
|
||||
protected function checkSystemCode($value, $rule, $data, $field)
|
||||
{
|
||||
return Systems::where('code', $value)->exists();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user