2026-03-03 09:45:46 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
namespace app\common\controller;
|
|
|
|
|
|
|
|
|
|
|
|
use app\common\traits\ResponseTrait;
|
|
|
|
|
|
use support\Request;
|
|
|
|
|
|
use support\Response;
|
|
|
|
|
|
use app\common\utils\SystemAuth;
|
|
|
|
|
|
use app\common\utils\Jwt;
|
|
|
|
|
|
use app\common\model\User;
|
|
|
|
|
|
use app\common\model\Systems;
|
|
|
|
|
|
use support\Redis;
|
|
|
|
|
|
|
|
|
|
|
|
class SystemController
|
|
|
|
|
|
{
|
|
|
|
|
|
use ResponseTrait;
|
|
|
|
|
|
|
|
|
|
|
|
protected $noNeedLogin = ['auth'];
|
|
|
|
|
|
|
|
|
|
|
|
protected $noNeedHeaderData = ['auth'];
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 系统间认证接口
|
|
|
|
|
|
* POST /api/system/auth
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function auth(Request $request): Response
|
|
|
|
|
|
{
|
|
|
|
|
|
$userId = $request->post('user_id');
|
|
|
|
|
|
$timestamp = (int)$request->post('timestamp');
|
|
|
|
|
|
$signature = $request->post('signature');
|
|
|
|
|
|
$systemCode = $request->post('system_code');
|
|
|
|
|
|
|
|
|
|
|
|
// 参数验证
|
|
|
|
|
|
if (empty($userId) || empty($timestamp) || empty($signature)) {
|
|
|
|
|
|
return $this->error('参数不完整', 400);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取配置
|
|
|
|
|
|
$secret = config('system_auth.shared_secret');
|
|
|
|
|
|
$expire = config('system_auth.signature_expire', 600);
|
|
|
|
|
|
|
|
|
|
|
|
// 验证时间戳(10分钟有效期)
|
|
|
|
|
|
if (!SystemAuth::verifyTimestamp($timestamp, $expire)) {
|
|
|
|
|
|
return $this->error('请求已过期', 401);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证签名
|
|
|
|
|
|
if (!SystemAuth::verifySignature($userId, $timestamp, $signature, $secret)) {
|
|
|
|
|
|
return $this->error('签名验证失败', 401);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查找用户
|
|
|
|
|
|
$user = User::find($userId);
|
|
|
|
|
|
if (!$user) {
|
|
|
|
|
|
return $this->error('用户不存在', 404);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证系统(如果提供了 system_code)
|
|
|
|
|
|
$system = null;
|
|
|
|
|
|
if ($systemCode) {
|
|
|
|
|
|
$system = Systems::where('code', $systemCode)->first();
|
|
|
|
|
|
if (!$system) {
|
|
|
|
|
|
return $this->error('系统不存在', 404);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取 corp_abbr(优先从请求参数获取,否则通过用户 corp_id 查询)
|
|
|
|
|
|
$corpAbbr = $request->post('corp_abbr');
|
|
|
|
|
|
if (!$corpAbbr && $user->corp_id) {
|
|
|
|
|
|
$corp = \app\common\model\Corp::find($user->corp_id);
|
|
|
|
|
|
$corpAbbr = $corp ? $corp->abbr : '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建 JWT payload
|
|
|
|
|
|
$payload = $this->buildJwtPayload($user, $system);
|
|
|
|
|
|
|
|
|
|
|
|
// 生成 access_token
|
|
|
|
|
|
$jwt = new Jwt();
|
|
|
|
|
|
$token = $jwt->generateToken($payload);
|
|
|
|
|
|
|
|
|
|
|
|
// 生成 refresh_token
|
|
|
|
|
|
$refreshToken = $this->generateRefreshToken($user->id, $systemCode ?? '');
|
|
|
|
|
|
|
|
|
|
|
|
// 获取 home_url
|
|
|
|
|
|
$homeUrl = $system ? $this->getHome($system->id, $payload) : '';
|
|
|
|
|
|
|
|
|
|
|
|
// 记录 token 到 Redis 和 MySQL
|
|
|
|
|
|
$this->recordUserToken($user->id, $token, $systemCode ?? '');
|
|
|
|
|
|
|
|
|
|
|
|
// 构建用户信息(去除敏感信息)
|
|
|
|
|
|
$userInfo = [
|
|
|
|
|
|
'id' => $user->id,
|
|
|
|
|
|
'sn' => $user->sn ?? '',
|
|
|
|
|
|
'name' => $user->name ?? '',
|
|
|
|
|
|
'avatar' => $user->avatar ?? '',
|
|
|
|
|
|
'phone' => $user->phone ?? '',
|
|
|
|
|
|
'email' => $user->email ?? '',
|
|
|
|
|
|
'sex' => $user->sex ?? '',
|
|
|
|
|
|
'position' => $user->position ?? '',
|
|
|
|
|
|
'status' => $user->status ?? 1,
|
|
|
|
|
|
'corp_id' => $user->corp_id,
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
return $this->success([
|
|
|
|
|
|
'access_token' => $token,
|
|
|
|
|
|
'refresh_token' => $refreshToken,
|
|
|
|
|
|
'expires_in' => config('jwt.jwt_expiration'),
|
|
|
|
|
|
'system_code' => $systemCode ?? '',
|
|
|
|
|
|
'corp_abbr' => $corpAbbr ?? '',
|
|
|
|
|
|
'login_type' => 'system',
|
|
|
|
|
|
'home_url' => $homeUrl,
|
|
|
|
|
|
'user_info' => $userInfo,
|
|
|
|
|
|
], '认证成功');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 构建 JWT payload
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function buildJwtPayload(User $user, ?Systems $system): array
|
|
|
|
|
|
{
|
2026-03-03 16:08:53 +08:00
|
|
|
|
$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() : [];
|
2026-03-03 09:45:46 +08:00
|
|
|
|
$payload = [
|
2026-03-03 16:08:53 +08:00
|
|
|
|
'sub' => 'user_' . $user->id,
|
|
|
|
|
|
'aud' => $system->code,
|
|
|
|
|
|
'iat' => $now,
|
|
|
|
|
|
'exp' => $exp,
|
|
|
|
|
|
'nbf' => $now,
|
|
|
|
|
|
'typ' => 'access',
|
|
|
|
|
|
'uid' => $user->id,
|
|
|
|
|
|
'uname' => $user->name,
|
|
|
|
|
|
'role_ids' => $role_ids,
|
|
|
|
|
|
'system_ids' => $system_ids,
|
|
|
|
|
|
// 'user_id' => $user->id,
|
|
|
|
|
|
// 'sn' => $user->sn ?? '',
|
|
|
|
|
|
// 'name' => $user->name ?? '',
|
2026-03-03 09:45:46 +08:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
if ($system) {
|
|
|
|
|
|
$payload['system_code'] = $system->code;
|
|
|
|
|
|
$payload['system_id'] = $system->id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $payload;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成 refresh_token
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function generateRefreshToken(int $userId, string $systemCode): string
|
|
|
|
|
|
{
|
|
|
|
|
|
$refreshToken = bin2hex(random_bytes(32));
|
|
|
|
|
|
|
|
|
|
|
|
// 存储到 Redis
|
|
|
|
|
|
$key = "refresh_token:{$refreshToken}";
|
|
|
|
|
|
$expire = config('jwt.jwt_refresh_token_expiration');
|
|
|
|
|
|
Redis::setex($key, $expire, json_encode([
|
|
|
|
|
|
'user_id' => $userId,
|
|
|
|
|
|
'system_code' => $systemCode,
|
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
|
|
|
|
return $refreshToken;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取用户首页地址
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function getHome(int $systemId, array $payload): string
|
|
|
|
|
|
{
|
|
|
|
|
|
// 简单实现,可以根据实际需求调整
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 记录用户token到 Redis 和 MySQL
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function recordUserToken(int $userId, string $token, string $systemCode): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$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 持久化
|
|
|
|
|
|
\app\common\model\UserTokenLog::create([
|
|
|
|
|
|
'user_id' => $userId,
|
|
|
|
|
|
'token' => $token,
|
|
|
|
|
|
'system_code' => $systemCode,
|
|
|
|
|
|
'login_ip' => $ip,
|
|
|
|
|
|
'user_agent' => $ua,
|
2026-03-03 16:08:53 +08:00
|
|
|
|
'expired_at' => $expireAt,
|
2026-03-03 09:45:46 +08:00
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|