1
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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参数会自动使用配置中的完整URL:https://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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,46 +9,13 @@ tencent:
|
||||
push:
|
||||
# 延迟时间(分钟),默认10分钟
|
||||
minutes: 10
|
||||
# WPS365开放平台配置
|
||||
# 文档地址:https://open.wps.cn/
|
||||
wps365:
|
||||
# 应用ID(AppId)- 需要在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
|
||||
|
||||
Reference in New Issue
Block a user