Files
x-server-mobile/backend/app/controller/AuthController.php
T
root df07b16c7a feat: 实现多端飞书登录功能
1. 飞书客户端内:静默自动登录
   - 检测飞书环境 (userAgent + ttjsBridge)
   - 调用 ttjsBridge.getAuthCode() 获取授权码
   - 自动登录到后台

2. 飞书客户端外:扫码登录
   - 后端新增三个接口:
     * POST /api/v1/auth/qrcode-login - 生成登录会话和二维码
     * GET /api/v1/auth/qrcode-login/status - 轮询登录状态
     * GET /login/qrcode/{id} - 飞书授权回调
   - 前端新增 QRCodeLogin 组件:
     * 使用 qrcode.react 生成二维码
     * 每 2 秒轮询登录状态
     * 状态:loading → pending → scanning → success/expired

3. 数据库表:feishu_login_session
   - 存储 session_id, auth_url, status, user_id
   - 状态:pending, scanning, success, expired
   - 2 分钟过期机制

4. 其他优化
   - 移除审批详情页的 header
   - 更新 PROJECT_ISSUES.md 文档

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 09:58:19 +08:00

268 lines
7.7 KiB
PHP

<?php
namespace app\controller;
use app\service\FeishuService;
use app\util\JwtUtil;
use app\model\User;
use app\model\Company;
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);
}
/**
* 生成飞书扫码登录二维码
*/
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分钟过期
]);
// 构建扫码登录 URL
$appId = 'cli_a8033d607cba500d';
$redirectUri = urlencode("https://xsm.dev.hnxinyouhe.com/login/qrcode/{$loginSession->id}");
$authUrl = "https://open.feishu.cn/open-apis/authen/v1/oidc/authorize?app_id={$appId}&redirect_uri={$redirectUri}&state={$loginSession->id}";
// 生成二维码内容(返回 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,
]);
}
}
/**
* 飞书扫码登录回调(飞书授权后的回调)
*/
public function qrcodeLoginCallback(Request $request): Response
{
$sessionId = $this->getRouteParam($request, 'id');
$authCode = $request->get('auth_code');
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();
// 重定向到前端页面
return \Webman\Http\Response::redirect('https://xsm.dev.hnxinyouhe.com/login');
} catch (\Exception $e) {
return $this->error($e->getMessage());
}
}
}