2026-02-25 15:22:12 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
2026-02-25 18:09:04 +08:00
|
|
|
|
namespace app\controller;
|
2026-02-25 15:22:12 +08:00
|
|
|
|
|
2026-02-25 18:09:04 +08:00
|
|
|
|
use app\service\FeishuService;
|
|
|
|
|
|
use app\util\JwtUtil;
|
|
|
|
|
|
use app\model\User;
|
|
|
|
|
|
use app\model\Company;
|
2026-02-25 15:22:12 +08:00
|
|
|
|
use Webman\Http\Request;
|
|
|
|
|
|
use Webman\Http\Response;
|
|
|
|
|
|
|
|
|
|
|
|
class AuthController extends BaseController
|
|
|
|
|
|
{
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 用户登录
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function login(Request $request): Response
|
|
|
|
|
|
{
|
|
|
|
|
|
$username = $request->post('username');
|
|
|
|
|
|
$password = $request->post('password');
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: 实现真实的登录逻辑
|
|
|
|
|
|
if (!$username || !$password) {
|
|
|
|
|
|
return $this->error('用户名或密码不能为空');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟用户数据(用于测试)
|
|
|
|
|
|
$user = User::first();
|
|
|
|
|
|
|
|
|
|
|
|
if (!$user) {
|
|
|
|
|
|
return $this->error('用户不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$token = JwtUtil::encode([
|
|
|
|
|
|
'user_id' => $user->id,
|
|
|
|
|
|
'company_id' => $user->company_id,
|
|
|
|
|
|
'username' => $user->name,
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
return $this->success([
|
|
|
|
|
|
'token' => $token,
|
|
|
|
|
|
'user' => $user,
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 飞书免登
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function feishuLogin(Request $request): Response
|
|
|
|
|
|
{
|
|
|
|
|
|
$authCode = $request->post('auth_code');
|
|
|
|
|
|
$tenantKey = $request->post('tenant_key');
|
|
|
|
|
|
|
|
|
|
|
|
if (!$authCode) {
|
|
|
|
|
|
return $this->error('缺少授权码');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
$feishuService = new FeishuService();
|
|
|
|
|
|
|
|
|
|
|
|
// 根据 tenant_key 获取公司
|
|
|
|
|
|
$company = $feishuService->getCompanyByTenantKey($tenantKey);
|
|
|
|
|
|
|
|
|
|
|
|
if (!$company) {
|
|
|
|
|
|
return $this->error('公司不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置公司并获取用户信息
|
|
|
|
|
|
$feishuService->setCompany($company->id);
|
|
|
|
|
|
$feishuData = $feishuService->getUserInfo($authCode);
|
|
|
|
|
|
|
|
|
|
|
|
// 创建或更新用户
|
|
|
|
|
|
$user = $feishuService->createOrUpdateUser($feishuData, $company->id);
|
|
|
|
|
|
|
|
|
|
|
|
// 生成 JWT Token
|
|
|
|
|
|
$token = JwtUtil::encode([
|
|
|
|
|
|
'user_id' => $user->id,
|
|
|
|
|
|
'company_id' => $user->company_id,
|
|
|
|
|
|
'feishu_user_id' => $user->feishu_user_id,
|
|
|
|
|
|
'union_id' => $user->union_id,
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
return $this->success([
|
|
|
|
|
|
'token' => $token,
|
|
|
|
|
|
'user' => $user->load('company'),
|
|
|
|
|
|
]);
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
return $this->error($e->getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取当前用户信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function me(Request $request): Response
|
|
|
|
|
|
{
|
|
|
|
|
|
$userId = $this->getUserId($request);
|
|
|
|
|
|
|
|
|
|
|
|
if (!$userId) {
|
|
|
|
|
|
return $this->error('未登录', 401);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::with('company')->find($userId);
|
|
|
|
|
|
|
|
|
|
|
|
if (!$user) {
|
|
|
|
|
|
return $this->error('用户不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $this->success($user);
|
|
|
|
|
|
}
|
2026-02-26 09:58:19 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成飞书扫码登录二维码
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function qrcodeLogin(Request $request): Response
|
|
|
|
|
|
{
|
|
|
|
|
|
$tenantKey = $request->post('tenant_key');
|
|
|
|
|
|
|
|
|
|
|
|
if (!$tenantKey) {
|
|
|
|
|
|
return $this->error('缺少租户密钥');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
$feishuService = new \app\service\FeishuService();
|
|
|
|
|
|
|
|
|
|
|
|
// 根据 tenant_key 获取公司
|
|
|
|
|
|
$company = $feishuService->getCompanyByTenantKey($tenantKey);
|
|
|
|
|
|
|
|
|
|
|
|
if (!$company) {
|
|
|
|
|
|
return $this->error('公司不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$feishuService->setCompany($company->id);
|
|
|
|
|
|
|
|
|
|
|
|
// 生成登录会话
|
|
|
|
|
|
$loginSession = \app\model\FeishuLoginSession::create([
|
|
|
|
|
|
'company_id' => $company->id,
|
|
|
|
|
|
'tenant_key' => $tenantKey,
|
|
|
|
|
|
'status' => 'pending',
|
|
|
|
|
|
'expires_at' => date('Y-m-d H:i:s', time() + 300), // 5分钟过期
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
2026-02-26 10:20:48 +08:00
|
|
|
|
// 构建扫码登录 URL(使用官方 passport 端点)
|
2026-02-26 09:58:19 +08:00
|
|
|
|
$appId = 'cli_a8033d607cba500d';
|
2026-02-26 10:43:29 +08:00
|
|
|
|
// 使用固定的 redirect_uri,session_id 通过 state 传递
|
|
|
|
|
|
$redirectUri = urlencode("https://xsm.dev.hnxinyouhe.com/login/callback");
|
2026-02-26 10:20:48 +08:00
|
|
|
|
$authUrl = "https://passport.feishu.cn/suite/passport/oauth/authorize?client_id={$appId}&redirect_uri={$redirectUri}&response_type=code&state={$loginSession->id}";
|
2026-02-26 09:58:19 +08:00
|
|
|
|
|
|
|
|
|
|
// 生成二维码内容(返回 URL,前端生成二维码)
|
|
|
|
|
|
return $this->success([
|
|
|
|
|
|
'session_id' => $loginSession->id,
|
|
|
|
|
|
'auth_url' => $authUrl,
|
|
|
|
|
|
'expires_at' => $loginSession->expires_at,
|
|
|
|
|
|
]);
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
return $this->error($e->getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 轮询扫码登录状态
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function qrcodeLoginStatus(Request $request): Response
|
|
|
|
|
|
{
|
|
|
|
|
|
$sessionId = $request->get('session_id');
|
|
|
|
|
|
|
|
|
|
|
|
if (!$sessionId) {
|
|
|
|
|
|
return $this->error('缺少会话ID');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$session = \app\model\FeishuLoginSession::find($sessionId);
|
|
|
|
|
|
|
|
|
|
|
|
if (!$session) {
|
|
|
|
|
|
return $this->error('登录会话不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否过期
|
|
|
|
|
|
if (strtotime($session->expires_at) < time()) {
|
|
|
|
|
|
$session->status = 'expired';
|
|
|
|
|
|
$session->save();
|
|
|
|
|
|
return $this->error('二维码已过期,请刷新重试');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 返回当前状态
|
|
|
|
|
|
if ($session->status === 'success') {
|
|
|
|
|
|
// 生成 token
|
|
|
|
|
|
$user = \app\model\User::find($session->user_id);
|
|
|
|
|
|
if (!$user) {
|
|
|
|
|
|
return $this->error('用户不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$token = \app\util\JwtUtil::encode([
|
|
|
|
|
|
'user_id' => $user->id,
|
|
|
|
|
|
'company_id' => $user->company_id,
|
|
|
|
|
|
'feishu_user_id' => $user->feishu_user_id,
|
|
|
|
|
|
'union_id' => $user->feishu_union_id,
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
// 清除会话
|
|
|
|
|
|
$session->delete();
|
|
|
|
|
|
|
|
|
|
|
|
return $this->success([
|
|
|
|
|
|
'status' => 'success',
|
|
|
|
|
|
'token' => $token,
|
|
|
|
|
|
'user' => $user->load('company'),
|
|
|
|
|
|
]);
|
|
|
|
|
|
} else if ($session->status === 'expired') {
|
|
|
|
|
|
return $this->error('二维码已过期,请刷新重试');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// pending 或 scanning
|
|
|
|
|
|
return $this->success([
|
|
|
|
|
|
'status' => $session->status,
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 飞书扫码登录回调(飞书授权后的回调)
|
|
|
|
|
|
*/
|
2026-02-26 10:43:29 +08:00
|
|
|
|
public function qrcodeLoginCallback(Request $request): Response
|
2026-02-26 09:58:19 +08:00
|
|
|
|
{
|
2026-02-26 10:43:29 +08:00
|
|
|
|
// 从 state 参数获取 session_id
|
|
|
|
|
|
$sessionId = $request->get('state');
|
|
|
|
|
|
// OAuth 2.0 标准参数是 code
|
|
|
|
|
|
$authCode = $request->get('code');
|
2026-02-26 09:58:19 +08:00
|
|
|
|
|
|
|
|
|
|
if (!$sessionId) {
|
|
|
|
|
|
return $this->error('缺少会话ID');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!$authCode) {
|
|
|
|
|
|
return $this->error('缺少授权码');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
$session = \app\model\FeishuLoginSession::find($sessionId);
|
|
|
|
|
|
|
|
|
|
|
|
if (!$session) {
|
|
|
|
|
|
return $this->error('登录会话不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否过期
|
|
|
|
|
|
if (strtotime($session->expires_at) < time()) {
|
|
|
|
|
|
$session->status = 'expired';
|
|
|
|
|
|
$session->save();
|
|
|
|
|
|
return $this->error('二维码已过期');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 session 的公司信息
|
|
|
|
|
|
$feishuService = new \app\service\FeishuService();
|
|
|
|
|
|
$feishuService->setCompany($session->company_id);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取用户信息
|
|
|
|
|
|
$feishuData = $feishuService->getUserInfo($authCode);
|
|
|
|
|
|
|
|
|
|
|
|
// 创建或更新用户
|
|
|
|
|
|
$user = $feishuService->createOrUpdateUser($feishuData, $session->company_id);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新会话状态
|
|
|
|
|
|
$session->status = 'success';
|
|
|
|
|
|
$session->user_id = $user->id;
|
|
|
|
|
|
$session->save();
|
|
|
|
|
|
|
2026-02-26 11:04:31 +08:00
|
|
|
|
// 返回简单页面,让用户知道可以关闭了
|
|
|
|
|
|
return new \Webman\Http\Response(200, [], '<!DOCTYPE html>
|
|
|
|
|
|
<html>
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
|
<title>登录成功</title>
|
|
|
|
|
|
<style>
|
|
|
|
|
|
body {
|
|
|
|
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
.container {
|
|
|
|
|
|
padding: 40px 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
h1 {
|
|
|
|
|
|
margin: 0 0 10px 0;
|
|
|
|
|
|
font-size: 28px;
|
|
|
|
|
|
}
|
|
|
|
|
|
p {
|
|
|
|
|
|
margin: 10px 0 0 0;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
opacity: 0.9;
|
|
|
|
|
|
}
|
|
|
|
|
|
.icon {
|
|
|
|
|
|
font-size: 64px;
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
|
|
|
<div class="container">
|
|
|
|
|
|
<div class="icon">✅</div>
|
|
|
|
|
|
<h1>登录成功</h1>
|
|
|
|
|
|
<p>您可以关闭此页面返回电脑端继续操作</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>');
|
2026-02-26 09:58:19 +08:00
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
return $this->error($e->getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-25 15:22:12 +08:00
|
|
|
|
}
|