This commit is contained in:
2025-11-05 23:13:05 +08:00
parent 6958db464f
commit b9dcaca1aa

View File

@@ -13,6 +13,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.MediaType;
import java.util.List;
import java.util.Map;
@@ -112,23 +113,26 @@ public class TencentDocController extends BaseController {
* OAuth回调 - 通过授权码获取访问令牌
* 根据腾讯文档官方文档https://docs.qq.com/open/document/app/oauth2/authorize.html
* 用户授权成功后腾讯文档会重定向到此回调地址并携带code和state参数
* 返回HTML页面自动关闭窗口并通知父窗口
*/
@Anonymous
@GetMapping("/oauth/callback")
public AjaxResult oauthCallback(@RequestParam("code") String code,
@RequestParam(value = "state", required = false) String state,
@RequestParam(value = "error", required = false) String error,
@RequestParam(value = "error_description", required = false) String errorDescription) {
@GetMapping(value = "/oauth/callback", 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);
return AjaxResult.error("授权失败: " + (errorDescription != null ? errorDescription : error));
String errorMsg = errorDescription != null ? errorDescription : error;
return generateCallbackHtml(false, "授权失败: " + errorMsg, null);
}
// 验证授权码
if (code == null || code.trim().isEmpty()) {
return AjaxResult.error("授权码不能为空");
log.error("授权码为空");
return generateCallbackHtml(false, "授权码不能为空", null);
}
log.info("收到腾讯文档授权回调 - code: {}, state: {}", code, state);
@@ -139,7 +143,7 @@ public class TencentDocController extends BaseController {
// 验证返回的token信息
if (tokenInfo == null || !tokenInfo.containsKey("access_token")) {
log.error("获取访问令牌失败 - 响应数据: {}", tokenInfo);
return AjaxResult.error("获取访问令牌失败,响应数据格式不正确");
return generateCallbackHtml(false, "获取访问令牌失败,响应数据格式不正确", null);
}
String accessToken = tokenInfo.getString("access_token");
@@ -157,16 +161,73 @@ public class TencentDocController extends BaseController {
}
} catch (Exception e) {
log.error("保存访问令牌失败", e);
// 即使保存失败也返回token信息
return generateCallbackHtml(false, "保存访问令牌失败: " + e.getMessage(), null);
}
return AjaxResult.success("授权成功,访问令牌已自动保存", tokenInfo);
return generateCallbackHtml(true, "授权成功,访问令牌已自动保存", null);
} catch (Exception e) {
log.error("OAuth回调处理失败", e);
return AjaxResult.error("授权失败: " + e.getMessage());
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();
}
/**
* 刷新访问令牌
*/