1
This commit is contained in:
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -45,6 +45,15 @@ public class TencentDocController extends BaseController {
|
|||||||
/** Redis key前缀,用于存储上次处理的最大行数 */
|
/** Redis key前缀,用于存储上次处理的最大行数 */
|
||||||
private static final String LAST_PROCESSED_ROW_KEY_PREFIX = "tendoc:last_row:";
|
private static final String LAST_PROCESSED_ROW_KEY_PREFIX = "tendoc:last_row:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试回调接口是否可访问(用于调试)
|
||||||
|
*/
|
||||||
|
@Anonymous
|
||||||
|
@GetMapping("/test-callback")
|
||||||
|
public AjaxResult testCallback() {
|
||||||
|
return AjaxResult.success("回调接口测试成功,路由配置正确");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取授权URL(用于手动授权,获取token后通过setToken接口保存)
|
* 获取授权URL(用于手动授权,获取token后通过setToken接口保存)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -193,8 +193,8 @@ tencent:
|
|||||||
app-id: 90aa0b70e7704c2abd2a42695d5144a4
|
app-id: 90aa0b70e7704c2abd2a42695d5144a4
|
||||||
# 应用密钥(需要在腾讯文档开放平台申请)
|
# 应用密钥(需要在腾讯文档开放平台申请)
|
||||||
app-secret: G8ZdSWcoViIawygo7JSolE86PL32UO0O
|
app-secret: G8ZdSWcoViIawygo7JSolE86PL32UO0O
|
||||||
# 授权回调地址(需要在腾讯文档开放平台配置,必须使用HTTPS)
|
# 授权回调地址(需要在腾讯文档开放平台配置域名:jarvis.van333.cn,这里使用完整URL)
|
||||||
redirect-uri: https://jarvis.van333.cn/jarvis/tendoc/oauth/callback
|
redirect-uri: https://jarvis.van333.cn/tendoc-callback
|
||||||
# API基础地址
|
# API基础地址
|
||||||
api-base-url: https://docs.qq.com/open/v1
|
api-base-url: https://docs.qq.com/open/v1
|
||||||
# OAuth授权地址
|
# OAuth授权地址
|
||||||
|
|||||||
@@ -192,8 +192,8 @@ tencent:
|
|||||||
app-id: 90aa0b70e7704c2abd2a42695d5144a4
|
app-id: 90aa0b70e7704c2abd2a42695d5144a4
|
||||||
# 应用密钥(需要在腾讯文档开放平台申请)
|
# 应用密钥(需要在腾讯文档开放平台申请)
|
||||||
app-secret: G8ZdSWcoViIawygo7JSolE86PL32UO0O
|
app-secret: G8ZdSWcoViIawygo7JSolE86PL32UO0O
|
||||||
# 授权回调地址(需要在腾讯文档开放平台配置,必须使用HTTPS)
|
# 授权回调地址(需要在腾讯文档开放平台配置域名:jarvis.van333.cn,这里使用完整URL)
|
||||||
redirect-uri: https://jarvis.van333.cn/jarvis/tendoc/oauth/callback
|
redirect-uri: https://jarvis.van333.cn/tendoc-callback
|
||||||
# API基础地址
|
# API基础地址
|
||||||
api-base-url: https://docs.qq.com/open/v1
|
api-base-url: https://docs.qq.com/open/v1
|
||||||
# OAuth授权地址
|
# OAuth授权地址
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ public class SecurityConfig
|
|||||||
.antMatchers("/public/**").permitAll()
|
.antMatchers("/public/**").permitAll()
|
||||||
// 腾讯文档OAuth回调接口,允许匿名访问
|
// 腾讯文档OAuth回调接口,允许匿名访问
|
||||||
.antMatchers("/jarvis/tendoc/oauth/callback").permitAll()
|
.antMatchers("/jarvis/tendoc/oauth/callback").permitAll()
|
||||||
|
// 腾讯文档OAuth回调接口(备用路径),允许匿名访问
|
||||||
|
.antMatchers("/tendoc-callback").permitAll()
|
||||||
// 静态资源,可匿名访问
|
// 静态资源,可匿名访问
|
||||||
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
|
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
|
||||||
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
|
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
|
||||||
|
|||||||
Reference in New Issue
Block a user