This commit is contained in:
van
2026-03-23 17:04:37 +08:00
parent 458b84f913
commit 9bb7cfc7fb
24 changed files with 1055 additions and 3405 deletions

View File

@@ -0,0 +1,86 @@
package com.ruoyi.web.controller.jarvis;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.jarvis.domain.dto.KdocsTokenInfo;
import com.ruoyi.jarvis.service.IKdocsOAuthService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* 金山文档 OAuth 回调(独立路径,避免前端路由拦截)
* 回调地址示例https://your-domain/kdocs-callback
*/
@Anonymous
@RestController
@RequestMapping("/kdocs-callback")
public class KdocsCallbackController extends BaseController {
private static final Logger log = LoggerFactory.getLogger(KdocsCallbackController.class);
@Autowired
private IKdocsOAuthService kdocsOAuthService;
@Anonymous
@GetMapping
public ResponseEntity<?> oauthCallbackGet(@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) {
return handleOAuthCallback(code, state, error, errorDescription);
}
private ResponseEntity<?> handleOAuthCallback(String code, String state, String error, String errorDescription) {
try {
if (error != null) {
String msg = errorDescription != null ? errorDescription : error;
log.error("金山文档授权失败: {}", msg);
return htmlPage(false, "授权失败: " + msg, null);
}
if (StringUtils.isBlank(code)) {
return htmlPage(false, "缺少授权码 code", null);
}
log.info("金山文档授权回调 code 已收到 state={}", state);
KdocsTokenInfo tokenInfo = kdocsOAuthService.getAccessTokenByCode(code);
if (tokenInfo.getUserId() == null) {
return htmlPage(false, "无法解析用户标识", null);
}
kdocsOAuthService.saveToken(tokenInfo.getUserId(), tokenInfo);
kdocsOAuthService.saveToken("default_user", tokenInfo);
return htmlPage(true, "授权成功,可关闭此窗口", tokenInfo);
} catch (Exception e) {
log.error("OAuth 回调处理失败", e);
return htmlPage(false, "授权失败: " + e.getMessage(), null);
}
}
private ResponseEntity<String> htmlPage(boolean success, String message, KdocsTokenInfo tokenInfo) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_HTML);
String esc = message.replace("\\", "\\\\").replace("'", "\\'").replace("\n", "\\n").replace("\r", "\\r");
String uid = tokenInfo != null && tokenInfo.getUserId() != null ? tokenInfo.getUserId().replace("\\", "\\\\").replace("'", "\\'") : "";
StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE html><html lang='zh-CN'><head><meta charset='UTF-8'><title>")
.append(success ? "授权成功" : "授权失败")
.append("</title></head><body style='font-family:sans-serif;text-align:center;padding:40px'>");
html.append("<h2>").append(success ? "✓ 授权成功" : "✗ 授权失败").append("</h2>");
html.append("<p>").append(message).append("</p>");
html.append("<script>");
html.append("if(window.opener){window.opener.postMessage({type:'kdocs_oauth_callback',success:")
.append(success).append(",message:'").append(esc).append("'");
if (success && !uid.isEmpty()) {
html.append(",userId:'").append(uid).append("'");
}
html.append("},'*');}");
html.append("setTimeout(function(){if(window.opener)window.close();},2000);");
html.append("</script></body></html>");
return new ResponseEntity<>(html.toString(), headers, HttpStatus.OK);
}
}

View File

@@ -0,0 +1,310 @@
package com.ruoyi.web.controller.jarvis;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.jarvis.domain.dto.KdocsTokenInfo;
import com.ruoyi.jarvis.service.IKdocsOAuthService;
import com.ruoyi.jarvis.service.IKdocsOpenApiService;
import com.ruoyi.jarvis.service.impl.KdocsOAuthServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 金山文档个人云developer.kdocs.cn
*/
@RestController
@RequestMapping("/jarvis/kdocs")
public class KdocsCloudController extends BaseController {
private static final Logger log = LoggerFactory.getLogger(KdocsCloudController.class);
@Autowired
private IKdocsOAuthService kdocsOAuthService;
@Autowired
private IKdocsOpenApiService kdocsOpenApiService;
@Autowired
private KdocsOAuthServiceImpl kdocsOAuthServiceImpl;
@GetMapping("/authUrl")
public AjaxResult getAuthUrl(@RequestParam(required = false) String state) {
try {
return AjaxResult.success("获取授权URL成功", kdocsOAuthService.getAuthUrl(state));
} catch (Exception e) {
log.error("获取授权URL失败", e);
return AjaxResult.error("获取授权URL失败: " + e.getMessage());
}
}
@Anonymous
@GetMapping("/oauth/callback")
public AjaxResult oauthCallback(@RequestParam String code, @RequestParam(required = false) String state) {
try {
KdocsTokenInfo tokenInfo = kdocsOAuthService.getAccessTokenByCode(code);
if (tokenInfo.getUserId() != null) {
kdocsOAuthService.saveToken(tokenInfo.getUserId(), tokenInfo);
kdocsOAuthService.saveToken("default_user", tokenInfo);
}
return AjaxResult.success("授权成功", tokenInfo);
} catch (Exception e) {
log.error("OAuth回调失败", e);
return AjaxResult.error("授权失败: " + e.getMessage());
}
}
@PostMapping("/refreshToken")
public AjaxResult refreshToken(@RequestBody Map<String, Object> params) {
try {
String refreshToken = (String) params.get("refreshToken");
String userId = (String) params.get("userId");
if (StringUtils.isBlank(refreshToken)) {
return AjaxResult.error("refreshToken不能为空");
}
if (StringUtils.isBlank(userId)) {
userId = "default_user";
}
KdocsTokenInfo tokenInfo = kdocsOAuthService.refreshAccessToken(refreshToken, userId);
kdocsOAuthService.saveToken(userId, tokenInfo);
if (!userId.equals(tokenInfo.getUserId()) && tokenInfo.getUserId() != null) {
kdocsOAuthService.saveToken(tokenInfo.getUserId(), tokenInfo);
}
kdocsOAuthService.saveToken("default_user", tokenInfo);
return AjaxResult.success("刷新令牌成功", tokenInfo);
} catch (Exception e) {
log.error("刷新访问令牌失败", e);
return AjaxResult.error("刷新令牌失败: " + e.getMessage());
}
}
@GetMapping("/tokenStatus")
public AjaxResult getTokenStatus(@RequestParam(required = false) String userId) {
try {
KdocsTokenInfo tokenInfo = null;
if (StringUtils.isNotBlank(userId)) {
tokenInfo = kdocsOAuthServiceImpl.getTokenByUserId(userId);
if (tokenInfo == null && "default_user".equals(userId)) {
tokenInfo = kdocsOAuthService.getCurrentToken();
if (tokenInfo != null) {
kdocsOAuthService.saveToken("default_user", tokenInfo);
}
}
} else {
tokenInfo = kdocsOAuthService.getCurrentToken();
}
if (tokenInfo == null) {
JSONObject r = new JSONObject();
r.put("hasToken", false);
r.put("isValid", false);
return AjaxResult.success("未授权", r);
}
boolean valid = kdocsOAuthService.isTokenValid(tokenInfo);
JSONObject r = new JSONObject();
r.put("hasToken", true);
r.put("isValid", valid);
r.put("userId", tokenInfo.getUserId());
r.put("expired", tokenInfo.isExpired());
if (tokenInfo.getExpiresIn() != null) {
r.put("expiresIn", tokenInfo.getExpiresIn());
}
return AjaxResult.success("获取Token状态成功", r);
} catch (Exception e) {
log.error("获取Token状态失败", e);
return AjaxResult.error("获取Token状态失败: " + e.getMessage());
}
}
@PostMapping("/setToken")
public AjaxResult setToken(@RequestBody Map<String, Object> params) {
try {
String accessToken = (String) params.get("accessToken");
String refreshToken = (String) params.get("refreshToken");
String userId = (String) params.get("userId");
Integer expiresIn = params.get("expiresIn") != null
? Integer.valueOf(params.get("expiresIn").toString()) : 86400;
if (StringUtils.isBlank(accessToken)) {
return AjaxResult.error("accessToken不能为空");
}
if (StringUtils.isBlank(userId)) {
return AjaxResult.error("userId不能为空");
}
KdocsTokenInfo info = new KdocsTokenInfo();
info.setAccessToken(accessToken);
info.setRefreshToken(refreshToken);
info.setExpiresIn(expiresIn);
info.setUserId(userId);
kdocsOAuthService.saveToken(userId, info);
return AjaxResult.success("设置Token成功");
} catch (Exception e) {
log.error("设置Token失败", e);
return AjaxResult.error("设置Token失败: " + e.getMessage());
}
}
private KdocsTokenInfo ensureToken(String userId) {
KdocsTokenInfo tokenInfo = kdocsOAuthServiceImpl.getTokenByUserId(userId);
if (tokenInfo == null) {
throw new IllegalStateException("用户未授权,请先完成授权");
}
if (tokenInfo.isExpired() && tokenInfo.getRefreshToken() != null) {
tokenInfo = kdocsOAuthService.refreshAccessToken(tokenInfo.getRefreshToken(), userId);
kdocsOAuthService.saveToken(userId, tokenInfo);
kdocsOAuthService.saveToken("default_user", tokenInfo);
}
return tokenInfo;
}
@GetMapping("/userInfo")
public AjaxResult getUserInfo(@RequestParam String userId) {
try {
KdocsTokenInfo t = ensureToken(userId);
JSONObject userInfo = kdocsOpenApiService.getUserInfoFlat(t.getAccessToken());
return AjaxResult.success("获取用户信息成功", userInfo);
} catch (IllegalStateException e) {
return AjaxResult.error(e.getMessage());
} catch (Exception e) {
log.error("获取用户信息失败", e);
return AjaxResult.error("获取用户信息失败: " + e.getMessage());
}
}
@GetMapping("/files")
public AjaxResult getFileList(@RequestParam String userId,
@RequestParam(required = false, defaultValue = "1") Integer page,
@RequestParam(required = false, defaultValue = "20") Integer pageSize,
@RequestParam(required = false) Integer next_offset,
@RequestParam(required = false) String next_filter) {
try {
KdocsTokenInfo t = ensureToken(userId);
Map<String, Object> p = new java.util.HashMap<>();
p.put("page", page);
p.put("page_size", pageSize);
p.put("pageSize", pageSize);
if (next_offset != null) {
p.put("next_offset", next_offset);
}
if (next_filter != null) {
p.put("next_filter", next_filter);
}
JSONObject fileList = kdocsOpenApiService.getFileList(t.getAccessToken(), p);
return AjaxResult.success("获取文件列表成功", fileList);
} catch (IllegalStateException e) {
return AjaxResult.error(e.getMessage());
} catch (Exception e) {
log.error("获取文件列表失败", e);
return AjaxResult.error("获取文件列表失败: " + e.getMessage());
}
}
@GetMapping("/fileInfo")
public AjaxResult getFileInfo(@RequestParam String userId, @RequestParam String fileToken) {
try {
KdocsTokenInfo t = ensureToken(userId);
return AjaxResult.success("获取文件信息成功", kdocsOpenApiService.getFileInfo(t.getAccessToken(), fileToken));
} catch (IllegalStateException e) {
return AjaxResult.error(e.getMessage());
} catch (Exception e) {
log.error("获取文件信息失败", e);
return AjaxResult.error("获取文件信息失败: " + e.getMessage());
}
}
@GetMapping("/sheets")
public AjaxResult getSheetList(@RequestParam String userId, @RequestParam String fileToken) {
try {
KdocsTokenInfo t = ensureToken(userId);
return AjaxResult.success("获取工作表列表成功", kdocsOpenApiService.getSheetList(t.getAccessToken(), fileToken));
} catch (IllegalStateException e) {
return AjaxResult.error(e.getMessage());
} catch (Exception e) {
log.error("获取工作表列表失败", e);
return AjaxResult.error("获取工作表列表失败: " + e.getMessage());
}
}
@GetMapping("/readCells")
public AjaxResult readCells(@RequestParam String userId,
@RequestParam String fileToken,
@RequestParam(defaultValue = "0") int sheetIdx,
@RequestParam(required = false) String range) {
try {
KdocsTokenInfo t = ensureToken(userId);
JSONObject data = kdocsOpenApiService.readCells(t.getAccessToken(), fileToken, sheetIdx, range);
return AjaxResult.success("读取单元格数据成功", data);
} catch (IllegalStateException e) {
return AjaxResult.error(e.getMessage());
} catch (Exception e) {
log.error("读取单元格失败", e);
return AjaxResult.error("读取单元格数据失败: " + e.getMessage());
}
}
@PostMapping("/updateCells")
public AjaxResult updateCells(@RequestBody Map<String, Object> params) {
try {
String userId = (String) params.get("userId");
String fileToken = (String) params.get("fileToken");
int sheetIdx = params.get("sheetIdx") != null ? Integer.parseInt(params.get("sheetIdx").toString()) : 0;
String range = (String) params.get("range");
@SuppressWarnings("unchecked")
List<List<Object>> values = (List<List<Object>>) params.get("values");
if (userId == null || fileToken == null || range == null || values == null || values.isEmpty()) {
return AjaxResult.error("userId、fileToken、range、values 不能为空");
}
KdocsTokenInfo t = ensureToken(userId);
JSONObject r = kdocsOpenApiService.updateCells(t.getAccessToken(), fileToken, sheetIdx, range, values);
return AjaxResult.success("更新单元格数据成功", r);
} catch (IllegalStateException e) {
return AjaxResult.error(e.getMessage());
} catch (Exception e) {
log.error("更新单元格失败", e);
return AjaxResult.error("更新单元格数据失败: " + e.getMessage());
}
}
@PostMapping("/createSheet")
public AjaxResult createSheet(@RequestBody Map<String, Object> params) {
try {
String userId = (String) params.get("userId");
String fileToken = (String) params.get("fileToken");
String sheetName = (String) params.get("sheetName");
if (userId == null || fileToken == null || sheetName == null) {
return AjaxResult.error("userId、fileToken、sheetName 不能为空");
}
KdocsTokenInfo t = ensureToken(userId);
return AjaxResult.success("创建数据表成功", kdocsOpenApiService.createSheet(t.getAccessToken(), fileToken, sheetName));
} catch (IllegalStateException e) {
return AjaxResult.error(e.getMessage());
} catch (Exception e) {
log.error("创建数据表失败", e);
return AjaxResult.error("创建数据表失败: " + e.getMessage());
}
}
@PostMapping("/batchUpdateCells")
public AjaxResult batchUpdateCells(@RequestBody Map<String, Object> params) {
try {
String userId = (String) params.get("userId");
String fileToken = (String) params.get("fileToken");
int sheetIdx = params.get("sheetIdx") != null ? Integer.parseInt(params.get("sheetIdx").toString()) : 0;
@SuppressWarnings("unchecked")
List<Map<String, Object>> updates = (List<Map<String, Object>>) params.get("updates");
if (userId == null || fileToken == null || updates == null || updates.isEmpty()) {
return AjaxResult.error("参数不完整");
}
KdocsTokenInfo t = ensureToken(userId);
return AjaxResult.success("批量更新成功", kdocsOpenApiService.batchUpdateCells(t.getAccessToken(), fileToken, sheetIdx, updates));
} catch (IllegalStateException e) {
return AjaxResult.error(e.getMessage());
} catch (Exception e) {
log.error("批量更新失败", e);
return AjaxResult.error("批量更新单元格数据失败: " + e.getMessage());
}
}
}

View File

@@ -1,278 +0,0 @@
package com.ruoyi.web.controller.jarvis;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.jarvis.domain.dto.WPS365TokenInfo;
import com.ruoyi.jarvis.service.IWPS365OAuthService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* WPS365 OAuth回调控制器
* 用于处理OAuth回调避免前端路由拦截
*
* @author system
*/
@RestController
@RequestMapping("/wps365-callback")
public class WPS365CallbackController extends BaseController {
private static final Logger log = LoggerFactory.getLogger(WPS365CallbackController.class);
@Autowired
private IWPS365OAuthService wps365OAuthService;
/**
* OAuth回调 - 通过授权码获取访问令牌GET请求
* 路径:/wps365-callback
* 注意在WPS365开放平台只需配置域名jarvis.van333.cn不能包含路径
* 授权URL中的redirect_uri参数会自动使用配置中的完整URLhttps://jarvis.van333.cn/wps365-callback
*/
@Anonymous
@GetMapping
public ResponseEntity<?> oauthCallbackGet(@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,
@RequestParam(value = "challenge", required = false) String challenge) {
return handleOAuthCallback(code, state, error, errorDescription, challenge, null);
}
/**
* OAuth回调 - 通过授权码获取访问令牌POST请求
* WPS365在验证回调URL时会发送POST请求进行challenge验证
* 支持application/json和application/x-www-form-urlencoded两种格式
*/
@Anonymous
@PostMapping(consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE, MediaType.TEXT_PLAIN_VALUE})
public ResponseEntity<?> oauthCallbackPost(@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,
@RequestParam(value = "challenge", required = false) String challenge,
@RequestBody(required = false) String requestBody) {
log.info("收到WPS365 POST回调请求 - code: {}, challenge: {}, requestBody: {}",
code != null ? "" : "",
challenge != null ? challenge : "",
requestBody != null && requestBody.length() > 0 ? requestBody.substring(0, Math.min(100, requestBody.length())) : "");
// 如果challenge在URL参数中直接使用
// 如果不在URL参数中尝试从请求体中解析可能是JSON或form-data
if (challenge == null && requestBody != null && !requestBody.trim().isEmpty()) {
String bodyTrimmed = requestBody.trim();
// 尝试解析JSON格式
if (bodyTrimmed.startsWith("{")) {
try {
com.alibaba.fastjson2.JSONObject json = com.alibaba.fastjson2.JSON.parseObject(requestBody);
if (json.containsKey("challenge")) {
challenge = json.getString("challenge");
log.info("从JSON请求体中解析到challenge: {}", challenge);
}
} catch (Exception e) {
log.debug("解析JSON请求体失败", e);
}
}
// 尝试解析form-urlencoded格式 (challenge=xxx)
else if (bodyTrimmed.contains("challenge=")) {
try {
String[] pairs = bodyTrimmed.split("&");
for (String pair : pairs) {
if (pair.startsWith("challenge=")) {
challenge = java.net.URLDecoder.decode(pair.substring("challenge=".length()), "UTF-8");
log.info("从form-urlencoded请求体中解析到challenge: {}", challenge);
break;
}
}
} catch (Exception e) {
log.debug("解析form-urlencoded请求体失败", e);
}
}
// 如果请求体就是challenge值本身纯文本
else if (bodyTrimmed.length() < 200) {
challenge = bodyTrimmed;
log.info("将请求体作为challenge值: {}", challenge);
}
}
return handleOAuthCallback(code, state, error, errorDescription, challenge, requestBody);
}
/**
* 处理OAuth回调的核心逻辑
*/
private ResponseEntity<?> handleOAuthCallback(String code, String state, String error,
String errorDescription, String challenge, String requestBody) {
try {
// 处理challenge验证WPS365后台配置时用于验证回调URL
if (challenge != null && !challenge.trim().isEmpty()) {
log.info("收到WPS365 challenge验证请求 - challenge: {}", challenge);
// 尝试返回JSON格式符合OAuth标准
// 格式:{"challenge": "xxx"} 或直接返回challenge值
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
String jsonResponse = "{\"challenge\":\"" + challenge + "\"}";
return new ResponseEntity<>(jsonResponse, headers, HttpStatus.OK);
} catch (Exception e) {
log.warn("返回JSON格式失败尝试纯文本格式", e);
// 如果JSON失败尝试纯文本
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
return new ResponseEntity<>(challenge, headers, HttpStatus.OK);
}
}
// 处理授权错误
if (error != null) {
log.error("WPS365授权失败 - error: {}, error_description: {}", error, errorDescription);
String errorMsg = errorDescription != null ? errorDescription : error;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_HTML);
return new ResponseEntity<>(generateCallbackHtml(false, "授权失败: " + errorMsg, null), headers, HttpStatus.OK);
}
// 如果没有code也没有challenge可能是直接访问显示提示信息
if (code == null || code.trim().isEmpty()) {
log.warn("访问回调地址但没有授权码,可能是测试访问");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_HTML);
return new ResponseEntity<>(generateCallbackHtml(false, "等待授权回调... 如果没有授权码,请通过授权流程访问", null), headers, HttpStatus.OK);
}
log.info("收到WPS365授权回调 - code: {}, state: {}", code, state);
// 使用授权码换取access_token
WPS365TokenInfo tokenInfo = wps365OAuthService.getAccessTokenByCode(code);
// 验证返回的token信息
if (tokenInfo == null || tokenInfo.getAccessToken() == null) {
log.error("获取访问令牌失败 - tokenInfo: {}", tokenInfo);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_HTML);
return new ResponseEntity<>(generateCallbackHtml(false, "获取访问令牌失败,响应数据格式不正确", null), headers, HttpStatus.OK);
}
log.info("成功获取访问令牌 - userId: {}, access_token: {}",
tokenInfo.getUserId(),
tokenInfo.getAccessToken() != null ? tokenInfo.getAccessToken().substring(0, 20) + "..." : "null");
// 自动保存token到后端用 WPS 返回的 userId 存一份,再用 default_user 存一份,便于前端固定用 default_user 查询)
try {
if (tokenInfo.getUserId() != null) {
wps365OAuthService.saveToken(tokenInfo.getUserId(), tokenInfo);
log.info("访问令牌已自动保存到后端缓存 - userId: {}", tokenInfo.getUserId());
// 同时以 default_user 保存,前端 WPS365 页固定传 userId=default_user否则会提示未授权
wps365OAuthService.saveToken("default_user", tokenInfo);
log.info("已同时保存为 default_user前端可直接使用");
} else {
log.warn("userId为空无法保存Token");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_HTML);
return new ResponseEntity<>(generateCallbackHtml(false, "获取用户ID失败无法保存Token", null), headers, HttpStatus.OK);
}
} catch (Exception e) {
log.error("保存访问令牌失败", e);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_HTML);
return new ResponseEntity<>(generateCallbackHtml(false, "保存访问令牌失败: " + e.getMessage(), null), headers, HttpStatus.OK);
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_HTML);
return new ResponseEntity<>(generateCallbackHtml(true, "授权成功,访问令牌已自动保存", tokenInfo), headers, HttpStatus.OK);
} catch (Exception e) {
log.error("OAuth回调处理失败", e);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_HTML);
return new ResponseEntity<>(generateCallbackHtml(false, "授权失败: " + e.getMessage(), null), headers, HttpStatus.OK);
}
}
/**
* 生成回调HTML页面
*/
private String generateCallbackHtml(boolean success, String message, WPS365TokenInfo tokenInfo) {
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>WPS365授权").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: 500px; }");
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(".info { font-size: 14px; color: #666; margin-top: 20px; padding: 15px; background: #f5f5f5; border-radius: 5px; text-align: left; }");
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>");
// 显示Token信息仅成功时
if (success && tokenInfo != null) {
html.append("<div class='info'>");
html.append("<strong>授权信息:</strong><br>");
if (tokenInfo.getUserId() != null) {
html.append("用户ID: ").append(tokenInfo.getUserId()).append("<br>");
}
if (tokenInfo.getExpiresIn() != null) {
html.append("有效期: ").append(tokenInfo.getExpiresIn()).append(" 秒<br>");
}
html.append("</div>");
}
html.append("<p style='color: #999; font-size: 14px; margin-top: 20px;'>窗口将在3秒后自动关闭...</p>");
html.append("</div>");
html.append("<script>");
html.append("// 通知父窗口授权结果");
html.append("if (window.opener) {");
html.append(" window.opener.postMessage({");
html.append(" type: 'wps365_oauth_callback',");
html.append(" success: ").append(success).append(",");
html.append(" message: '").append(message.replace("'", "\\'").replace("\n", "\\n").replace("\r", "\\r")).append("'");
if (success && tokenInfo != null && tokenInfo.getUserId() != null) {
html.append(",");
html.append(" userId: '").append(tokenInfo.getUserId()).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();
}
}

View File

@@ -1,585 +0,0 @@
package com.ruoyi.web.controller.jarvis;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.jarvis.domain.dto.WPS365TokenInfo;
import com.ruoyi.jarvis.service.IWPS365ApiService;
import com.ruoyi.jarvis.service.IWPS365OAuthService;
import com.ruoyi.jarvis.service.impl.WPS365OAuthServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* WPS365控制器
*
* @author system
*/
@RestController
@RequestMapping("/jarvis/wps365")
public class WPS365Controller extends BaseController {
private static final Logger log = LoggerFactory.getLogger(WPS365Controller.class);
@Autowired
private IWPS365OAuthService wps365OAuthService;
@Autowired
private IWPS365ApiService wps365ApiService;
@Autowired
private WPS365OAuthServiceImpl wps365OAuthServiceImpl;
@Autowired
private RedisCache redisCache;
/**
* 获取授权URL
*/
@GetMapping("/authUrl")
public AjaxResult getAuthUrl(@RequestParam(required = false) String state) {
try {
String authUrl = wps365OAuthService.getAuthUrl(state);
return AjaxResult.success("获取授权URL成功", authUrl);
} catch (Exception e) {
log.error("获取授权URL失败", e);
return AjaxResult.error("获取授权URL失败: " + e.getMessage());
}
}
/**
* OAuth回调处理已废弃请使用 /wps365-callback
* 保留此接口用于兼容,实际回调请使用 WPS365CallbackController
*/
@Anonymous
@GetMapping("/oauth/callback")
public AjaxResult oauthCallback(@RequestParam String code,
@RequestParam(required = false) String state) {
try {
log.warn("使用已废弃的回调接口 /jarvis/wps365/oauth/callback建议使用 /wps365-callback");
log.info("收到OAuth回调 - code: {}, state: {}", code, state);
// 通过授权码获取访问令牌
WPS365TokenInfo tokenInfo = wps365OAuthService.getAccessTokenByCode(code);
// 保存Token到Redis使用userId作为key
if (tokenInfo.getUserId() != null) {
wps365OAuthService.saveToken(tokenInfo.getUserId(), tokenInfo);
}
return AjaxResult.success("授权成功", tokenInfo);
} catch (Exception e) {
log.error("OAuth回调处理失败", e);
return AjaxResult.error("授权失败: " + e.getMessage());
}
}
/**
* 刷新访问令牌
*/
@PostMapping("/refreshToken")
public AjaxResult refreshToken(@RequestBody Map<String, Object> params) {
try {
String refreshToken = (String) params.get("refreshToken");
if (refreshToken == null || refreshToken.trim().isEmpty()) {
return AjaxResult.error("refreshToken不能为空");
}
WPS365TokenInfo tokenInfo = wps365OAuthService.refreshAccessToken(refreshToken);
// 更新Token到Redis
if (tokenInfo.getUserId() != null) {
wps365OAuthService.saveToken(tokenInfo.getUserId(), tokenInfo);
}
return AjaxResult.success("刷新令牌成功", tokenInfo);
} catch (Exception e) {
log.error("刷新访问令牌失败", e);
return AjaxResult.error("刷新令牌失败: " + e.getMessage());
}
}
/**
* 获取当前用户的Token状态
* 前端 WPS365 页固定传 userId=default_user若该 key 下无 token则尝试用任意已存 token 并写回 default_user避免“已授权却提示未授权”
*/
@GetMapping("/tokenStatus")
public AjaxResult getTokenStatus(@RequestParam(required = false) String userId) {
try {
WPS365TokenInfo tokenInfo = null;
if (userId != null && !userId.trim().isEmpty()) {
tokenInfo = wps365OAuthServiceImpl.getTokenByUserId(userId);
// 前端固定用 default_user但回调可能只存了 WPS 返回的 userId如 wps365_xxx此处兜底用任意已存 token 并写回 default_user
if (tokenInfo == null && "default_user".equals(userId)) {
tokenInfo = wps365OAuthService.getCurrentToken();
if (tokenInfo != null) {
wps365OAuthService.saveToken("default_user", tokenInfo);
log.info("已用已有 WPS365 Token 补写 default_user前端可正常显示已授权");
}
}
} else {
tokenInfo = wps365OAuthService.getCurrentToken();
}
if (tokenInfo == null) {
JSONObject result = new JSONObject();
result.put("hasToken", false);
result.put("isValid", false);
return AjaxResult.success("未授权", result);
}
boolean isValid = wps365OAuthService.isTokenValid(tokenInfo);
JSONObject result = new JSONObject();
result.put("hasToken", true);
result.put("isValid", isValid);
result.put("userId", tokenInfo.getUserId());
result.put("expired", tokenInfo.isExpired());
if (tokenInfo.getExpiresIn() != null) {
result.put("expiresIn", tokenInfo.getExpiresIn());
}
return AjaxResult.success("获取Token状态成功", result);
} catch (Exception e) {
log.error("获取Token状态失败", e);
return AjaxResult.error("获取Token状态失败: " + e.getMessage());
}
}
/**
* 手动设置Token用于测试或手动授权
*/
@PostMapping("/setToken")
public AjaxResult setToken(@RequestBody Map<String, Object> params) {
try {
String accessToken = (String) params.get("accessToken");
String refreshToken = (String) params.get("refreshToken");
String userId = (String) params.get("userId");
Integer expiresIn = params.get("expiresIn") != null ?
Integer.valueOf(params.get("expiresIn").toString()) : 7200;
if (accessToken == null || accessToken.trim().isEmpty()) {
return AjaxResult.error("accessToken不能为空");
}
if (userId == null || userId.trim().isEmpty()) {
return AjaxResult.error("userId不能为空");
}
WPS365TokenInfo tokenInfo = new WPS365TokenInfo();
tokenInfo.setAccessToken(accessToken);
tokenInfo.setRefreshToken(refreshToken);
tokenInfo.setExpiresIn(expiresIn);
tokenInfo.setUserId(userId);
wps365OAuthService.saveToken(userId, tokenInfo);
return AjaxResult.success("设置Token成功");
} catch (Exception e) {
log.error("设置Token失败", e);
return AjaxResult.error("设置Token失败: " + e.getMessage());
}
}
/**
* 获取用户信息
*/
@GetMapping("/userInfo")
public AjaxResult getUserInfo(@RequestParam String userId) {
try {
WPS365TokenInfo tokenInfo = wps365OAuthServiceImpl.getTokenByUserId(userId);
if (tokenInfo == null) {
return AjaxResult.error("用户未授权,请先完成授权");
}
// 检查Token是否有效如果过期则尝试刷新
if (tokenInfo.isExpired() && tokenInfo.getRefreshToken() != null) {
try {
tokenInfo = wps365OAuthService.refreshAccessToken(tokenInfo.getRefreshToken());
wps365OAuthService.saveToken(userId, tokenInfo);
} catch (Exception e) {
log.error("刷新Token失败", e);
return AjaxResult.error("Token已过期且刷新失败请重新授权");
}
}
JSONObject userInfo = wps365ApiService.getUserInfo(tokenInfo.getAccessToken());
if (userInfo == null) {
// WPS 未提供可用的“当前用户信息”接口或路径变更,降级返回 token 中的 user_id避免 500
userInfo = new JSONObject();
userInfo.put("user_id", tokenInfo.getUserId());
userInfo.put("name", "已授权用户");
userInfo.put("email", "-");
}
return AjaxResult.success("获取用户信息成功", userInfo);
} catch (Exception e) {
log.error("获取用户信息失败", e);
return AjaxResult.error("获取用户信息失败: " + e.getMessage());
}
}
/**
* 获取文件列表
*/
@GetMapping("/files")
public AjaxResult getFileList(@RequestParam String userId,
@RequestParam(required = false, defaultValue = "1") Integer page,
@RequestParam(required = false, defaultValue = "20") Integer pageSize) {
try {
WPS365TokenInfo tokenInfo = wps365OAuthServiceImpl.getTokenByUserId(userId);
if (tokenInfo == null) {
return AjaxResult.error("用户未授权,请先完成授权");
}
// 检查Token是否有效如果过期则尝试刷新
if (tokenInfo.isExpired() && tokenInfo.getRefreshToken() != null) {
try {
tokenInfo = wps365OAuthService.refreshAccessToken(tokenInfo.getRefreshToken());
wps365OAuthService.saveToken(userId, tokenInfo);
} catch (Exception e) {
log.error("刷新Token失败", e);
return AjaxResult.error("Token已过期且刷新失败请重新授权");
}
}
Map<String, Object> params = new java.util.HashMap<>();
params.put("page", page);
params.put("page_size", pageSize);
JSONObject fileList = wps365ApiService.getFileList(tokenInfo.getAccessToken(), params);
return AjaxResult.success("获取文件列表成功", fileList);
} catch (Exception e) {
log.error("获取文件列表失败", e);
return AjaxResult.error("获取文件列表失败: " + e.getMessage());
}
}
/**
* 获取文件信息
*/
@GetMapping("/fileInfo")
public AjaxResult getFileInfo(@RequestParam String userId,
@RequestParam String fileToken) {
try {
WPS365TokenInfo tokenInfo = wps365OAuthServiceImpl.getTokenByUserId(userId);
if (tokenInfo == null) {
return AjaxResult.error("用户未授权,请先完成授权");
}
// 检查Token是否有效
if (tokenInfo.isExpired() && tokenInfo.getRefreshToken() != null) {
try {
tokenInfo = wps365OAuthService.refreshAccessToken(tokenInfo.getRefreshToken());
wps365OAuthService.saveToken(userId, tokenInfo);
} catch (Exception e) {
log.error("刷新Token失败", e);
return AjaxResult.error("Token已过期且刷新失败请重新授权");
}
}
JSONObject fileInfo = wps365ApiService.getFileInfo(tokenInfo.getAccessToken(), fileToken);
return AjaxResult.success("获取文件信息成功", fileInfo);
} catch (Exception e) {
log.error("获取文件信息失败 - fileToken: {}", fileToken, e);
return AjaxResult.error("获取文件信息失败: " + e.getMessage());
}
}
/**
* 获取工作表列表
*/
@GetMapping("/sheets")
public AjaxResult getSheetList(@RequestParam String userId,
@RequestParam String fileToken) {
try {
WPS365TokenInfo tokenInfo = wps365OAuthServiceImpl.getTokenByUserId(userId);
if (tokenInfo == null) {
return AjaxResult.error("用户未授权,请先完成授权");
}
// 检查Token是否有效
if (tokenInfo.isExpired() && tokenInfo.getRefreshToken() != null) {
try {
tokenInfo = wps365OAuthService.refreshAccessToken(tokenInfo.getRefreshToken());
wps365OAuthService.saveToken(userId, tokenInfo);
} catch (Exception e) {
log.error("刷新Token失败", e);
return AjaxResult.error("Token已过期且刷新失败请重新授权");
}
}
JSONObject sheetList = wps365ApiService.getSheetList(tokenInfo.getAccessToken(), fileToken);
return AjaxResult.success("获取工作表列表成功", sheetList);
} catch (Exception e) {
log.error("获取工作表列表失败 - fileToken: {}", fileToken, e);
return AjaxResult.error("获取工作表列表失败: " + e.getMessage());
}
}
/**
* 读取单元格数据
*/
@GetMapping("/readCells")
public AjaxResult readCells(@RequestParam String userId,
@RequestParam String fileToken,
@RequestParam(defaultValue = "0") int sheetIdx,
@RequestParam(required = false) String range) {
try {
WPS365TokenInfo tokenInfo = wps365OAuthServiceImpl.getTokenByUserId(userId);
if (tokenInfo == null) {
return AjaxResult.error("用户未授权,请先完成授权");
}
// 检查Token是否有效
if (tokenInfo.isExpired() && tokenInfo.getRefreshToken() != null) {
try {
tokenInfo = wps365OAuthService.refreshAccessToken(tokenInfo.getRefreshToken());
wps365OAuthService.saveToken(userId, tokenInfo);
} catch (Exception e) {
log.error("刷新Token失败", e);
return AjaxResult.error("Token已过期且刷新失败请重新授权");
}
}
JSONObject result = wps365ApiService.readCells(tokenInfo.getAccessToken(), fileToken, sheetIdx, range);
return AjaxResult.success("读取单元格数据成功", result);
} catch (Exception e) {
log.error("读取单元格数据失败 - fileToken: {}, sheetIdx: {}, range: {}", fileToken, sheetIdx, range, e);
return AjaxResult.error("读取单元格数据失败: " + e.getMessage());
}
}
/**
* 更新单元格数据
*/
@PostMapping("/updateCells")
public AjaxResult updateCells(@RequestBody Map<String, Object> params) {
try {
String userId = (String) params.get("userId");
String fileToken = (String) params.get("fileToken");
Integer sheetIdx = params.get("sheetIdx") != null ?
Integer.valueOf(params.get("sheetIdx").toString()) : 0;
String range = (String) params.get("range");
@SuppressWarnings("unchecked")
List<List<Object>> values = (List<List<Object>>) params.get("values");
if (userId == null || userId.trim().isEmpty()) {
return AjaxResult.error("userId不能为空");
}
if (fileToken == null || fileToken.trim().isEmpty()) {
return AjaxResult.error("fileToken不能为空");
}
if (range == null || range.trim().isEmpty()) {
return AjaxResult.error("range不能为空");
}
if (values == null || values.isEmpty()) {
return AjaxResult.error("values不能为空");
}
WPS365TokenInfo tokenInfo = wps365OAuthServiceImpl.getTokenByUserId(userId);
if (tokenInfo == null) {
return AjaxResult.error("用户未授权,请先完成授权");
}
// 检查Token是否有效
if (tokenInfo.isExpired() && tokenInfo.getRefreshToken() != null) {
try {
tokenInfo = wps365OAuthService.refreshAccessToken(tokenInfo.getRefreshToken());
wps365OAuthService.saveToken(userId, tokenInfo);
} catch (Exception e) {
log.error("刷新Token失败", e);
return AjaxResult.error("Token已过期且刷新失败请重新授权");
}
}
JSONObject result = wps365ApiService.updateCells(
tokenInfo.getAccessToken(),
fileToken,
sheetIdx,
range,
values
);
return AjaxResult.success("更新单元格数据成功", result);
} catch (Exception e) {
log.error("更新单元格数据失败", e);
return AjaxResult.error("更新单元格数据失败: " + e.getMessage());
}
}
/**
* 创建数据表
*/
@PostMapping("/createSheet")
public AjaxResult createSheet(@RequestBody Map<String, Object> params) {
try {
String userId = (String) params.get("userId");
String fileToken = (String) params.get("fileToken");
String sheetName = (String) params.get("sheetName");
if (userId == null || userId.trim().isEmpty()) {
return AjaxResult.error("userId不能为空");
}
if (fileToken == null || fileToken.trim().isEmpty()) {
return AjaxResult.error("fileToken不能为空");
}
if (sheetName == null || sheetName.trim().isEmpty()) {
return AjaxResult.error("sheetName不能为空");
}
WPS365TokenInfo tokenInfo = wps365OAuthServiceImpl.getTokenByUserId(userId);
if (tokenInfo == null) {
return AjaxResult.error("用户未授权,请先完成授权");
}
// 检查Token是否有效
if (tokenInfo.isExpired() && tokenInfo.getRefreshToken() != null) {
try {
tokenInfo = wps365OAuthService.refreshAccessToken(tokenInfo.getRefreshToken());
wps365OAuthService.saveToken(userId, tokenInfo);
} catch (Exception e) {
log.error("刷新Token失败", e);
return AjaxResult.error("Token已过期且刷新失败请重新授权");
}
}
JSONObject result = wps365ApiService.createSheet(tokenInfo.getAccessToken(), fileToken, sheetName);
return AjaxResult.success("创建数据表成功", result);
} catch (Exception e) {
log.error("创建数据表失败", e);
return AjaxResult.error("创建数据表失败: " + e.getMessage());
}
}
/**
* 批量更新单元格数据
*/
@PostMapping("/batchUpdateCells")
public AjaxResult batchUpdateCells(@RequestBody Map<String, Object> params) {
try {
String userId = (String) params.get("userId");
String fileToken = (String) params.get("fileToken");
Integer sheetIdx = params.get("sheetIdx") != null ?
Integer.valueOf(params.get("sheetIdx").toString()) : 0;
@SuppressWarnings("unchecked")
List<Map<String, Object>> updates = (List<Map<String, Object>>) params.get("updates");
if (userId == null || userId.trim().isEmpty()) {
return AjaxResult.error("userId不能为空");
}
if (fileToken == null || fileToken.trim().isEmpty()) {
return AjaxResult.error("fileToken不能为空");
}
if (updates == null || updates.isEmpty()) {
return AjaxResult.error("updates不能为空");
}
WPS365TokenInfo tokenInfo = wps365OAuthServiceImpl.getTokenByUserId(userId);
if (tokenInfo == null) {
return AjaxResult.error("用户未授权,请先完成授权");
}
// 检查Token是否有效
if (tokenInfo.isExpired() && tokenInfo.getRefreshToken() != null) {
try {
tokenInfo = wps365OAuthService.refreshAccessToken(tokenInfo.getRefreshToken());
wps365OAuthService.saveToken(userId, tokenInfo);
} catch (Exception e) {
log.error("刷新Token失败", e);
return AjaxResult.error("Token已过期且刷新失败请重新授权");
}
}
JSONObject result = wps365ApiService.batchUpdateCells(
tokenInfo.getAccessToken(),
fileToken,
sheetIdx,
updates
);
return AjaxResult.success("批量更新单元格数据成功", result);
} catch (Exception e) {
log.error("批量更新单元格数据失败", e);
return AjaxResult.error("批量更新单元格数据失败: " + e.getMessage());
}
}
/**
* 读取AirSheet工作表数据
*/
@GetMapping("/readAirSheetCells")
public AjaxResult readAirSheetCells(@RequestParam String userId,
@RequestParam String fileId,
@RequestParam(required = false, defaultValue = "0") String worksheetId,
@RequestParam(required = false) String range) {
try {
WPS365TokenInfo tokenInfo = wps365OAuthServiceImpl.getTokenByUserId(userId);
if (tokenInfo == null) {
return AjaxResult.error("用户未授权,请先完成授权");
}
// 检查Token是否有效
if (tokenInfo.isExpired() && tokenInfo.getRefreshToken() != null) {
try {
tokenInfo = wps365OAuthService.refreshAccessToken(tokenInfo.getRefreshToken());
wps365OAuthService.saveToken(userId, tokenInfo);
} catch (Exception e) {
log.error("刷新Token失败", e);
return AjaxResult.error("Token已过期且刷新失败请重新授权");
}
}
JSONObject result = wps365ApiService.readAirSheetCells(tokenInfo.getAccessToken(), fileId, worksheetId, range);
return AjaxResult.success("读取AirSheet数据成功", result);
} catch (Exception e) {
log.error("读取AirSheet数据失败 - fileId: {}, worksheetId: {}, range: {}", fileId, worksheetId, range, e);
return AjaxResult.error("读取AirSheet数据失败: " + e.getMessage());
}
}
/**
* 更新AirSheet工作表数据
*/
@PostMapping("/updateAirSheetCells")
public AjaxResult updateAirSheetCells(@RequestBody Map<String, Object> params) {
try {
String userId = (String) params.get("userId");
String fileId = (String) params.get("fileId");
String worksheetId = params.get("worksheetId") != null ? params.get("worksheetId").toString() : "0";
String range = (String) params.get("range");
@SuppressWarnings("unchecked")
List<List<Object>> values = (List<List<Object>>) params.get("values");
if (userId == null || fileId == null) {
return AjaxResult.error("userId和fileId不能为空");
}
WPS365TokenInfo tokenInfo = wps365OAuthServiceImpl.getTokenByUserId(userId);
if (tokenInfo == null) {
return AjaxResult.error("用户未授权,请先完成授权");
}
// 检查Token是否有效
if (tokenInfo.isExpired() && tokenInfo.getRefreshToken() != null) {
try {
tokenInfo = wps365OAuthService.refreshAccessToken(tokenInfo.getRefreshToken());
wps365OAuthService.saveToken(userId, tokenInfo);
} catch (Exception e) {
log.error("刷新Token失败", e);
return AjaxResult.error("Token已过期且刷新失败请重新授权");
}
}
JSONObject result = wps365ApiService.updateAirSheetCells(tokenInfo.getAccessToken(), fileId, worksheetId, range, values);
return AjaxResult.success("更新AirSheet数据成功", result);
} catch (Exception e) {
log.error("更新AirSheet数据失败", e);
return AjaxResult.error("更新AirSheet数据失败: " + e.getMessage());
}
}
}

View File

@@ -9,46 +9,13 @@ tencent:
push:
# 延迟时间分钟默认10分钟
minutes: 10
# WPS365开放平台配置
# 文档地址https://open.wps.cn/
wps365:
# 应用IDAppId- 需要在WPS365开放平台申请
app-id: AK20260114NNQJKV
# 应用密钥AppKey- 需要在WPS365开放平台申请注意保密
app-key: 4c58bc1642e5e8fa731f75af9370496a
# 授权回调地址需要在WPS365开放平台配置授权域名
# 注意:使用 /wps365-callback 路径,避免前端路由拦截
redirect-uri: https://jarvis.van333.cn/wps365-callback
# API基础地址
api-base-url: https://openapi.wps.cn/api/v1
# OAuth授权地址正确格式https://openapi.wps.cn/oauth2/auth
oauth-url: https://openapi.wps.cn/oauth2/auth
# 获取Token地址
token-url: https://openapi.wps.cn/oauth2/token
# 刷新Token地址
refresh-token-url: https://openapi.wps.cn/oauth2/token
# OAuth授权请求的scope权限可选
# 如果不配置默认使用kso.file.readwrite文件读写权限支持在线表格操作
#
# ⚠️ 重要如果报错invalid_scope必须按以下步骤操作
# 1. 登录WPS365开放平台https://open.wps.cn/
# 2. 进入"开发配置" > "权限管理"
# 3. 查看已申请权限的准确名称(必须以 kso. 开头)
# 4. 在下方配置scope使用英文逗号分隔WPS365官方要求
#
# 根据WPS365官方文档https://open.wps.cn/documents/app-integration-dev/wps365/server/
# - 必须使用英文逗号分隔(不是空格)
# - 权限名称必须以 kso. 开头格式如kso.file.read, kso.file.readwrite
# - 常见权限名称:
# * kso.file.read (文件读取)
# * kso.file.readwrite (文件读写,支持在线表格操作)
# * kso.doclib.readwrite (文档库读写)
# * kso.wiki.readwrite (知识库读写)
#
# 示例配置(根据平台后台实际显示的权限名称修改):
# scope: kso.file.readwrite
# scope: kso.file.read,kso.file.readwrite
# scope: kso.doclib.readwrite
# 后端写入「智能表格」时,授权必须包含 kso.airsheet.readwrite例如
# scope: kso.file.readwrite,kso.airsheet.readwrite
scope: kso.file.readwrite,kso.airsheet.readwrite
# 金山文档开放平台个人云https://developer.kdocs.cn
kdocs:
api-host: https://developer.kdocs.cn
# 在开发者后台创建应用后填写 app_id / app_key
app-id: ""
app-key: ""
# 与后台登记的回调一致,建议使用独立路径(勿被前端路由拦截)
redirect-uri: https://jarvis.van333.cn/kdocs-callback
# 逗号分隔须与应用申请权限一致https://developer.kdocs.cn/server/guide/permission.html
scope: user_basic,access_personal_files,edit_personal_files