@@ -0,0 +1,152 @@
package com.ruoyi.web.controller.jarvis ;
import com.ruoyi.common.annotation.Anonymous ;
import com.ruoyi.common.core.controller.BaseController ;
import com.ruoyi.jarvis.service.ITencentDocService ;
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
import org.springframework.beans.factory.annotation.Autowired ;
import org.springframework.http.MediaType ;
import org.springframework.web.bind.annotation.GetMapping ;
import org.springframework.web.bind.annotation.RequestMapping ;
import org.springframework.web.bind.annotation.RequestParam ;
import org.springframework.web.bind.annotation.RestController ;
/**
* 腾讯文档OAuth回调控制器( 备用路径)
* 用于处理更简单的回调路径,避免前端路由拦截
*
* @author system
*/
@RestController
@RequestMapping ( " /tendoc-callback " )
public class TencentDocCallbackController extends BaseController {
private static final Logger log = LoggerFactory . getLogger ( TencentDocCallbackController . class ) ;
@Autowired
private ITencentDocService tencentDocService ;
@Autowired
private com . ruoyi . jarvis . service . ITencentDocTokenService tencentDocTokenService ;
/**
* OAuth回调 - 通过授权码获取访问令牌
* 路径:/tendoc-callback
* 注意: 在腾讯文档开放平台只需配置域名: jarvis.van333.cn( 不能包含路径)
* 授权URL中的redirect_uri参数会自动使用配置中的完整URL: https://jarvis.van333.cn/tendoc-callback
*/
@Anonymous
@GetMapping ( produces = MediaType . TEXT_HTML_VALUE )
public String oauthCallback ( @RequestParam ( value = " code " , required = false ) String code ,
@RequestParam ( value = " state " , required = false ) String state ,
@RequestParam ( value = " error " , required = false ) String error ,
@RequestParam ( value = " error_description " , required = false ) String errorDescription ) {
try {
// 处理授权错误
if ( error ! = null ) {
log . error ( " 腾讯文档授权失败 - error: {}, error_description: {} " , error , errorDescription ) ;
String errorMsg = errorDescription ! = null ? errorDescription : error ;
return generateCallbackHtml ( false , " 授权失败: " + errorMsg , null ) ;
}
// 验证授权码
if ( code = = null | | code . trim ( ) . isEmpty ( ) ) {
log . error ( " 授权码为空 " ) ;
return generateCallbackHtml ( false , " 授权码不能为空 " , null ) ;
}
log . info ( " 收到腾讯文档授权回调(备用路径)- code: {}, state: {} " , code , state ) ;
// 使用授权码换取access_token
com . alibaba . fastjson2 . JSONObject tokenInfo = tencentDocService . getAccessTokenByCode ( code ) ;
// 验证返回的token信息
if ( tokenInfo = = null | | ! tokenInfo . containsKey ( " access_token " ) ) {
log . error ( " 获取访问令牌失败 - 响应数据: {} " , tokenInfo ) ;
return generateCallbackHtml ( false , " 获取访问令牌失败,响应数据格式不正确 " , null ) ;
}
String accessToken = tokenInfo . getString ( " access_token " ) ;
String refreshToken = tokenInfo . getString ( " refresh_token " ) ;
Integer expiresIn = tokenInfo . getIntValue ( " expires_in " ) ;
log . info ( " 成功获取访问令牌 - access_token: {} " , accessToken ) ;
// 自动保存token到后端
try {
if ( tencentDocTokenService instanceof com . ruoyi . jarvis . service . impl . TencentDocTokenServiceImpl ) {
( ( com . ruoyi . jarvis . service . impl . TencentDocTokenServiceImpl ) tencentDocTokenService )
. setToken ( accessToken , refreshToken , expiresIn ) ;
log . info ( " 访问令牌已自动保存到后端缓存 " ) ;
}
} catch ( Exception e ) {
log . error ( " 保存访问令牌失败 " , e ) ;
return generateCallbackHtml ( false , " 保存访问令牌失败: " + e . getMessage ( ) , null ) ;
}
return generateCallbackHtml ( true , " 授权成功,访问令牌已自动保存 " , null ) ;
} catch ( Exception e ) {
log . error ( " OAuth回调处理失败 " , e ) ;
return generateCallbackHtml ( false , " 授权失败: " + e . getMessage ( ) , null ) ;
}
}
/**
* 生成回调HTML页面
*/
private String generateCallbackHtml ( boolean success , String message , Object data ) {
StringBuilder html = new StringBuilder ( ) ;
html . append ( " <!DOCTYPE html> " ) ;
html . append ( " <html lang='zh-CN'> " ) ;
html . append ( " <head> " ) ;
html . append ( " <meta charset='UTF-8'> " ) ;
html . append ( " <meta name='viewport' content='width=device-width, initial-scale=1.0'> " ) ;
html . append ( " <title>腾讯文档授权 " ) . append ( success ? " 成功 " : " 失败 " ) . append ( " </title> " ) ;
html . append ( " <style> " ) ;
html . append ( " body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; " ) ;
html . append ( " display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; " ) ;
html . append ( " background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } " ) ;
html . append ( " .container { background: white; padding: 40px; border-radius: 10px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); text-align: center; max-width: 400px; } " ) ;
html . append ( " .icon { font-size: 64px; margin-bottom: 20px; } " ) ;
html . append ( " .success { color: #52c41a; } " ) ;
html . append ( " .error { color: #ff4d4f; } " ) ;
html . append ( " .message { font-size: 16px; color: #333; margin-bottom: 20px; line-height: 1.6; } " ) ;
html . append ( " </style> " ) ;
html . append ( " </head> " ) ;
html . append ( " <body> " ) ;
html . append ( " <div class='container'> " ) ;
if ( success ) {
html . append ( " <div class='icon success'>✓</div> " ) ;
html . append ( " <h2 style='color: #52c41a; margin-bottom: 10px;'>授权成功</h2> " ) ;
} else {
html . append ( " <div class='icon error'>✗</div> " ) ;
html . append ( " <h2 style='color: #ff4d4f; margin-bottom: 10px;'>授权失败</h2> " ) ;
}
html . append ( " <div class='message'> " ) . append ( message ) . append ( " </div> " ) ;
html . append ( " <p style='color: #999; font-size: 14px;'>窗口将在3秒后自动关闭...</p> " ) ;
html . append ( " </div> " ) ;
html . append ( " <script> " ) ;
html . append ( " // 通知父窗口授权结果 " ) ;
html . append ( " if (window.opener) { " ) ;
html . append ( " window.opener.postMessage({ " ) ;
html . append ( " type: 'tendoc_oauth_callback', " ) ;
html . append ( " success: " ) . append ( success ) . append ( " , " ) ;
html . append ( " message: ' " ) . append ( message . replace ( " ' " , " \\ ' " ) . replace ( " \ n " , " \\ n " ) . replace ( " \ r " , " \\ r " ) ) . append ( " ' " ) ;
html . append ( " }, '*'); " ) ;
html . append ( " } " ) ;
html . append ( " // 3秒后自动关闭窗口 " ) ;
html . append ( " setTimeout(function() { " ) ;
html . append ( " if (window.opener) { " ) ;
html . append ( " window.close(); " ) ;
html . append ( " } else { " ) ;
html . append ( " window.location.href = '/'; " ) ;
html . append ( " } " ) ;
html . append ( " }, 3000); " ) ;
html . append ( " </script> " ) ;
html . append ( " </body> " ) ;
html . append ( " </html> " ) ;
return html . toString ( ) ;
}
}