1
This commit is contained in:
226
doc/WPS365集成使用说明.md
Normal file
226
doc/WPS365集成使用说明.md
Normal file
@@ -0,0 +1,226 @@
|
||||
# WPS365 在线表格API集成使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
本功能实现了WPS365在线表格(KSheet)的完整API集成,支持用户授权、文档查看和编辑等功能。
|
||||
|
||||
## 功能特性
|
||||
|
||||
1. **OAuth用户授权**:支持WPS365标准的OAuth 2.0授权流程
|
||||
2. **Token管理**:自动保存和管理访问令牌,支持Token刷新
|
||||
3. **文件管理**:获取文件列表、文件信息
|
||||
4. **工作表管理**:获取工作表列表、创建数据表
|
||||
5. **单元格编辑**:支持读取和更新单元格数据,支持批量更新
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 1. 在WPS365开放平台注册应用
|
||||
|
||||
1. 访问 [WPS365开放平台](https://open.wps.cn/)
|
||||
2. 注册成为服务商并创建应用
|
||||
3. 获取 `app_id` 和 `app_key`
|
||||
4. 配置授权回调地址(需要与配置文件中的 `redirect-uri` 一致)
|
||||
|
||||
### 2. 更新配置文件
|
||||
|
||||
编辑 `application-dev.yml`(或对应的环境配置文件),添加WPS365配置:
|
||||
|
||||
```yaml
|
||||
wps365:
|
||||
# 应用ID(从WPS365开放平台获取)
|
||||
app-id: YOUR_APP_ID
|
||||
# 应用密钥(从WPS365开放平台获取)
|
||||
app-key: YOUR_APP_KEY
|
||||
# 授权回调地址(需要在WPS365开放平台配置)
|
||||
redirect-uri: https://your-domain.com/jarvis/wps365/oauth/callback
|
||||
# API基础地址(一般不需要修改)
|
||||
api-base-url: https://open.wps.cn/api/v1
|
||||
# OAuth授权地址(一般不需要修改)
|
||||
oauth-url: https://open.wps.cn/oauth2/v1/authorize
|
||||
# 获取Token地址(一般不需要修改)
|
||||
token-url: https://open.wps.cn/oauth2/v1/token
|
||||
# 刷新Token地址(一般不需要修改)
|
||||
refresh-token-url: https://open.wps.cn/oauth2/v1/token
|
||||
```
|
||||
|
||||
## API接口说明
|
||||
|
||||
### 1. 获取授权URL
|
||||
|
||||
**接口**: `GET /jarvis/wps365/authUrl`
|
||||
|
||||
**参数**:
|
||||
- `state` (可选): 状态参数,用于防止CSRF攻击
|
||||
|
||||
**返回**: 授权URL,用户需要访问此URL完成授权
|
||||
|
||||
**示例**:
|
||||
```javascript
|
||||
// 前端调用
|
||||
import { getWPS365AuthUrl } from '@/api/jarvis/wps365'
|
||||
|
||||
const response = await getWPS365AuthUrl()
|
||||
const authUrl = response.data
|
||||
// 打开授权页面
|
||||
window.open(authUrl, '_blank')
|
||||
```
|
||||
|
||||
### 2. OAuth回调处理
|
||||
|
||||
**接口**: `GET /jarvis/wps365/oauth/callback`
|
||||
|
||||
**参数**:
|
||||
- `code`: 授权码(由WPS365回调时自动传入)
|
||||
- `state`: 状态参数(可选)
|
||||
|
||||
**说明**: 此接口处理WPS365的授权回调,自动获取并保存Token。
|
||||
|
||||
### 3. 获取Token状态
|
||||
|
||||
**接口**: `GET /jarvis/wps365/tokenStatus`
|
||||
|
||||
**参数**:
|
||||
- `userId`: 用户ID
|
||||
|
||||
**返回**: Token状态信息(是否授权、是否有效等)
|
||||
|
||||
### 4. 刷新Token
|
||||
|
||||
**接口**: `POST /jarvis/wps365/refreshToken`
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"refreshToken": "refresh_token_value"
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 获取用户信息
|
||||
|
||||
**接口**: `GET /jarvis/wps365/userInfo`
|
||||
|
||||
**参数**:
|
||||
- `userId`: 用户ID
|
||||
|
||||
### 6. 获取文件列表
|
||||
|
||||
**接口**: `GET /jarvis/wps365/files`
|
||||
|
||||
**参数**:
|
||||
- `userId`: 用户ID
|
||||
- `page`: 页码(默认1)
|
||||
- `pageSize`: 每页数量(默认20)
|
||||
|
||||
### 7. 获取工作表列表
|
||||
|
||||
**接口**: `GET /jarvis/wps365/sheets`
|
||||
|
||||
**参数**:
|
||||
- `userId`: 用户ID
|
||||
- `fileToken`: 文件token
|
||||
|
||||
### 8. 读取单元格数据
|
||||
|
||||
**接口**: `GET /jarvis/wps365/readCells`
|
||||
|
||||
**参数**:
|
||||
- `userId`: 用户ID
|
||||
- `fileToken`: 文件token
|
||||
- `sheetIdx`: 工作表索引(从0开始)
|
||||
- `range`: 单元格范围(如:A1:B10,可选)
|
||||
|
||||
### 9. 更新单元格数据
|
||||
|
||||
**接口**: `POST /jarvis/wps365/updateCells`
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"userId": "user_id",
|
||||
"fileToken": "file_token",
|
||||
"sheetIdx": 0,
|
||||
"range": "A1:B2",
|
||||
"values": [
|
||||
["值1", "值2"],
|
||||
["值3", "值4"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 10. 批量更新单元格数据
|
||||
|
||||
**接口**: `POST /jarvis/wps365/batchUpdateCells`
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"userId": "user_id",
|
||||
"fileToken": "file_token",
|
||||
"sheetIdx": 0,
|
||||
"updates": [
|
||||
{
|
||||
"range": "A1:B2",
|
||||
"values": [["值1", "值2"], ["值3", "值4"]]
|
||||
},
|
||||
{
|
||||
"range": "C1:D2",
|
||||
"values": [["值5", "值6"], ["值7", "值8"]]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 使用流程
|
||||
|
||||
### 1. 用户授权流程
|
||||
|
||||
1. 前端调用 `/jarvis/wps365/authUrl` 获取授权URL
|
||||
2. 在新窗口打开授权URL,用户完成授权
|
||||
3. WPS365会回调到配置的 `redirect-uri`,自动处理授权码
|
||||
4. 系统自动获取并保存Token到Redis
|
||||
|
||||
### 2. 编辑文档流程
|
||||
|
||||
1. 调用 `/jarvis/wps365/files` 获取文件列表
|
||||
2. 选择要编辑的文件,调用 `/jarvis/wps365/sheets` 获取工作表列表
|
||||
3. 调用 `/jarvis/wps365/readCells` 读取现有数据
|
||||
4. 修改数据后,调用 `/jarvis/wps365/updateCells` 更新数据
|
||||
|
||||
### 3. Token刷新
|
||||
|
||||
- Token过期前,系统会自动尝试刷新
|
||||
- 也可以手动调用 `/jarvis/wps365/refreshToken` 刷新Token
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **用户权限**:只有文档的所有者或被授予编辑权限的用户才能编辑文档
|
||||
2. **Token管理**:Token存储在Redis中,有效期30天。过期后需要重新授权
|
||||
3. **API限制**:注意WPS365的API调用频率限制
|
||||
4. **文件Token**:WPS365使用 `file_token` 而不是文件ID,需要从文件列表中获取
|
||||
|
||||
## 前端页面
|
||||
|
||||
访问 `/jarvis/wps365` 可以打开WPS365管理页面,包含:
|
||||
- 授权状态显示
|
||||
- 用户信息查看
|
||||
- 文件列表浏览
|
||||
- 在线编辑表格功能
|
||||
|
||||
## 错误处理
|
||||
|
||||
- **未授权**:返回错误提示,引导用户完成授权
|
||||
- **Token过期**:自动尝试刷新Token,刷新失败则提示重新授权
|
||||
- **权限不足**:返回错误码10003,提示用户没有编辑权限
|
||||
|
||||
## 技术实现
|
||||
|
||||
- **后端**:Spring Boot + Redis(Token存储)
|
||||
- **前端**:Vue.js + Element UI
|
||||
- **HTTP客户端**:HttpURLConnection(不使用代理,直接连接)
|
||||
|
||||
## 参考文档
|
||||
|
||||
- [WPS365开放平台文档](https://open.wps.cn/)
|
||||
- [用户授权流程](https://open.wps.cn/documents/app-integration-dev/wps365/server/certification-authorization/user-authorization/flow)
|
||||
- [KSheet API文档](https://developer.kdocs.cn/server/ksheet/)
|
||||
|
||||
@@ -0,0 +1,486 @@
|
||||
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回调处理
|
||||
*/
|
||||
@Anonymous
|
||||
@GetMapping("/oauth/callback")
|
||||
public AjaxResult oauthCallback(@RequestParam String code,
|
||||
@RequestParam(required = false) String state) {
|
||||
try {
|
||||
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状态
|
||||
*/
|
||||
@GetMapping("/tokenStatus")
|
||||
public AjaxResult getTokenStatus(@RequestParam(required = false) String userId) {
|
||||
try {
|
||||
// 如果没有提供userId,可以尝试从当前登录用户获取
|
||||
// 这里暂时需要前端传入userId,后续可以集成到认证系统中
|
||||
if (userId == null || userId.trim().isEmpty()) {
|
||||
return AjaxResult.error("userId不能为空");
|
||||
}
|
||||
|
||||
WPS365TokenInfo tokenInfo = wps365OAuthServiceImpl.getTokenByUserId(userId);
|
||||
if (tokenInfo == null) {
|
||||
return AjaxResult.success("未授权", false);
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
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());
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,3 +222,21 @@ tencent:
|
||||
# 刷新Token地址(用于通过refresh_token刷新access_token)
|
||||
refresh-token-url: https://docs.qq.com/oauth/v2/token
|
||||
|
||||
# WPS365开放平台配置
|
||||
# 文档地址:https://open.wps.cn/
|
||||
wps365:
|
||||
# 应用ID(AppId)- 需要在WPS365开放平台申请
|
||||
app-id: YOUR_APP_ID
|
||||
# 应用密钥(AppKey)- 需要在WPS365开放平台申请,注意保密
|
||||
app-key: YOUR_APP_KEY
|
||||
# 授权回调地址(需要在WPS365开放平台配置授权域名)
|
||||
redirect-uri: https://jarvis.van333.cn/jarvis/wps365/oauth/callback
|
||||
# API基础地址
|
||||
api-base-url: https://open.wps.cn/api/v1
|
||||
# OAuth授权地址
|
||||
oauth-url: https://open.wps.cn/oauth2/v1/authorize
|
||||
# 获取Token地址
|
||||
token-url: https://open.wps.cn/oauth2/v1/token
|
||||
# 刷新Token地址
|
||||
refresh-token-url: https://open.wps.cn/oauth2/v1/token
|
||||
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.ruoyi.jarvis.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* WPS365开放平台配置
|
||||
*
|
||||
* @author system
|
||||
*/
|
||||
@Configuration
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "wps365")
|
||||
public class WPS365Config {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WPS365Config.class);
|
||||
|
||||
/** 应用ID(AppId) */
|
||||
private String appId;
|
||||
|
||||
/** 应用密钥(AppKey) */
|
||||
private String appKey;
|
||||
|
||||
/** 授权回调地址 */
|
||||
private String redirectUri;
|
||||
|
||||
/** API基础地址 */
|
||||
private String apiBaseUrl = "https://open.wps.cn/api/v1";
|
||||
|
||||
/** OAuth授权地址 */
|
||||
private String oauthUrl = "https://open.wps.cn/oauth2/v1/authorize";
|
||||
|
||||
/** 获取Token地址 */
|
||||
private String tokenUrl = "https://open.wps.cn/oauth2/v1/token";
|
||||
|
||||
/** 刷新Token地址 */
|
||||
private String refreshTokenUrl = "https://open.wps.cn/oauth2/v1/token";
|
||||
|
||||
/**
|
||||
* 配置初始化后验证
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
log.info("WPS365配置加载 - appId: {}, redirectUri: {}, apiBaseUrl: {}",
|
||||
appId != null && appId.length() > 10 ? appId.substring(0, 10) + "..." : (appId != null ? appId : "null"),
|
||||
redirectUri != null ? redirectUri : "null",
|
||||
apiBaseUrl);
|
||||
|
||||
if (appId == null || appId.trim().isEmpty()) {
|
||||
log.warn("WPS365应用ID未配置!请检查application.yml中的wps365.app-id");
|
||||
}
|
||||
if (appKey == null || appKey.trim().isEmpty()) {
|
||||
log.warn("WPS365应用密钥未配置!请检查application.yml中的wps365.app-key");
|
||||
}
|
||||
if (redirectUri == null || redirectUri.trim().isEmpty()) {
|
||||
log.warn("WPS365回调地址未配置!请检查application.yml中的wps365.redirect-uri");
|
||||
}
|
||||
}
|
||||
|
||||
public String getAppId() {
|
||||
return appId;
|
||||
}
|
||||
|
||||
public void setAppId(String appId) {
|
||||
this.appId = appId;
|
||||
}
|
||||
|
||||
public String getAppKey() {
|
||||
return appKey;
|
||||
}
|
||||
|
||||
public void setAppKey(String appKey) {
|
||||
this.appKey = appKey;
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
public void setRedirectUri(String redirectUri) {
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public String getApiBaseUrl() {
|
||||
return apiBaseUrl;
|
||||
}
|
||||
|
||||
public void setApiBaseUrl(String apiBaseUrl) {
|
||||
this.apiBaseUrl = apiBaseUrl;
|
||||
}
|
||||
|
||||
public String getOauthUrl() {
|
||||
return oauthUrl;
|
||||
}
|
||||
|
||||
public void setOauthUrl(String oauthUrl) {
|
||||
this.oauthUrl = oauthUrl;
|
||||
}
|
||||
|
||||
public String getTokenUrl() {
|
||||
return tokenUrl;
|
||||
}
|
||||
|
||||
public void setTokenUrl(String tokenUrl) {
|
||||
this.tokenUrl = tokenUrl;
|
||||
}
|
||||
|
||||
public String getRefreshTokenUrl() {
|
||||
return refreshTokenUrl;
|
||||
}
|
||||
|
||||
public void setRefreshTokenUrl(String refreshTokenUrl) {
|
||||
this.refreshTokenUrl = refreshTokenUrl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.ruoyi.jarvis.domain.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* WPS365 Token信息
|
||||
*
|
||||
* @author system
|
||||
*/
|
||||
public class WPS365TokenInfo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 访问令牌 */
|
||||
private String accessToken;
|
||||
|
||||
/** 刷新令牌 */
|
||||
private String refreshToken;
|
||||
|
||||
/** 令牌类型 */
|
||||
private String tokenType;
|
||||
|
||||
/** 过期时间(秒) */
|
||||
private Integer expiresIn;
|
||||
|
||||
/** 作用域 */
|
||||
private String scope;
|
||||
|
||||
/** 用户ID */
|
||||
private String userId;
|
||||
|
||||
/** 创建时间戳(毫秒) */
|
||||
private Long createTime;
|
||||
|
||||
public WPS365TokenInfo() {
|
||||
this.createTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public void setRefreshToken(String refreshToken) {
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
public String getTokenType() {
|
||||
return tokenType;
|
||||
}
|
||||
|
||||
public void setTokenType(String tokenType) {
|
||||
this.tokenType = tokenType;
|
||||
}
|
||||
|
||||
public Integer getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public void setExpiresIn(Integer expiresIn) {
|
||||
this.expiresIn = expiresIn;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public void setScope(String scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public Long getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(Long createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查token是否过期
|
||||
*/
|
||||
public boolean isExpired() {
|
||||
if (expiresIn == null || createTime == null) {
|
||||
return true;
|
||||
}
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long expireTime = createTime + (expiresIn * 1000L);
|
||||
// 提前5分钟认为过期,留出刷新时间
|
||||
return currentTime >= (expireTime - 5 * 60 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.ruoyi.jarvis.service;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* WPS365 API服务接口
|
||||
*
|
||||
* @author system
|
||||
*/
|
||||
public interface IWPS365ApiService {
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*
|
||||
* @param accessToken 访问令牌
|
||||
* @return 用户信息
|
||||
*/
|
||||
JSONObject getUserInfo(String accessToken);
|
||||
|
||||
/**
|
||||
* 获取文件列表
|
||||
*
|
||||
* @param accessToken 访问令牌
|
||||
* @param params 查询参数(page, page_size等)
|
||||
* @return 文件列表
|
||||
*/
|
||||
JSONObject getFileList(String accessToken, Map<String, Object> params);
|
||||
|
||||
/**
|
||||
* 获取文件信息
|
||||
*
|
||||
* @param accessToken 访问令牌
|
||||
* @param fileToken 文件token
|
||||
* @return 文件信息
|
||||
*/
|
||||
JSONObject getFileInfo(String accessToken, String fileToken);
|
||||
|
||||
/**
|
||||
* 更新单元格数据(KSheet - 在线表格)
|
||||
*
|
||||
* @param accessToken 访问令牌
|
||||
* @param fileToken 文件token
|
||||
* @param sheetIdx 工作表索引(从0开始)
|
||||
* @param range 单元格范围(如:A1:B2)
|
||||
* @param values 单元格值(二维数组,第一维是行,第二维是列)
|
||||
* @return 更新结果
|
||||
*/
|
||||
JSONObject updateCells(String accessToken, String fileToken, int sheetIdx, String range, List<List<Object>> values);
|
||||
|
||||
/**
|
||||
* 读取单元格数据
|
||||
*
|
||||
* @param accessToken 访问令牌
|
||||
* @param fileToken 文件token
|
||||
* @param sheetIdx 工作表索引
|
||||
* @param range 单元格范围
|
||||
* @return 单元格数据
|
||||
*/
|
||||
JSONObject readCells(String accessToken, String fileToken, int sheetIdx, String range);
|
||||
|
||||
/**
|
||||
* 获取工作表列表
|
||||
*
|
||||
* @param accessToken 访问令牌
|
||||
* @param fileToken 文件token
|
||||
* @return 工作表列表
|
||||
*/
|
||||
JSONObject getSheetList(String accessToken, String fileToken);
|
||||
|
||||
/**
|
||||
* 创建数据表
|
||||
*
|
||||
* @param accessToken 访问令牌
|
||||
* @param fileToken 文件token
|
||||
* @param sheetName 工作表名称
|
||||
* @return 创建结果
|
||||
*/
|
||||
JSONObject createSheet(String accessToken, String fileToken, String sheetName);
|
||||
|
||||
/**
|
||||
* 批量更新单元格数据
|
||||
*
|
||||
* @param accessToken 访问令牌
|
||||
* @param fileToken 文件token
|
||||
* @param sheetIdx 工作表索引
|
||||
* @param updates 更新列表,每个元素包含range和values
|
||||
* @return 更新结果
|
||||
*/
|
||||
JSONObject batchUpdateCells(String accessToken, String fileToken, int sheetIdx, List<Map<String, Object>> updates);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.ruoyi.jarvis.service;
|
||||
|
||||
import com.ruoyi.jarvis.domain.dto.WPS365TokenInfo;
|
||||
|
||||
/**
|
||||
* WPS365 OAuth授权服务接口
|
||||
*
|
||||
* @author system
|
||||
*/
|
||||
public interface IWPS365OAuthService {
|
||||
|
||||
/**
|
||||
* 获取授权URL
|
||||
*
|
||||
* @param state 状态参数(可选,用于防止CSRF攻击)
|
||||
* @return 授权URL
|
||||
*/
|
||||
String getAuthUrl(String state);
|
||||
|
||||
/**
|
||||
* 通过授权码获取访问令牌
|
||||
*
|
||||
* @param code 授权码
|
||||
* @return Token信息
|
||||
*/
|
||||
WPS365TokenInfo getAccessTokenByCode(String code);
|
||||
|
||||
/**
|
||||
* 刷新访问令牌
|
||||
*
|
||||
* @param refreshToken 刷新令牌
|
||||
* @return 新的Token信息
|
||||
*/
|
||||
WPS365TokenInfo refreshAccessToken(String refreshToken);
|
||||
|
||||
/**
|
||||
* 获取当前用户的访问令牌
|
||||
*
|
||||
* @return Token信息,如果未授权则返回null
|
||||
*/
|
||||
WPS365TokenInfo getCurrentToken();
|
||||
|
||||
/**
|
||||
* 保存用户令牌信息
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param tokenInfo Token信息
|
||||
*/
|
||||
void saveToken(String userId, WPS365TokenInfo tokenInfo);
|
||||
|
||||
/**
|
||||
* 清除用户令牌信息
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
void clearToken(String userId);
|
||||
|
||||
/**
|
||||
* 检查令牌是否有效
|
||||
*
|
||||
* @param tokenInfo Token信息
|
||||
* @return true表示有效,false表示需要刷新或重新授权
|
||||
*/
|
||||
boolean isTokenValid(WPS365TokenInfo tokenInfo);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
package com.ruoyi.jarvis.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.jarvis.config.WPS365Config;
|
||||
import com.ruoyi.jarvis.service.IWPS365ApiService;
|
||||
import com.ruoyi.jarvis.util.WPS365ApiUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* WPS365 API服务实现类
|
||||
*
|
||||
* @author system
|
||||
*/
|
||||
@Service
|
||||
public class WPS365ApiServiceImpl implements IWPS365ApiService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WPS365ApiServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private WPS365Config wps365Config;
|
||||
|
||||
@Override
|
||||
public JSONObject getUserInfo(String accessToken) {
|
||||
try {
|
||||
String url = wps365Config.getApiBaseUrl() + "/user/info";
|
||||
return WPS365ApiUtil.httpRequest("GET", url, accessToken, null);
|
||||
} catch (Exception e) {
|
||||
log.error("获取用户信息失败", e);
|
||||
throw new RuntimeException("获取用户信息失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getFileList(String accessToken, Map<String, Object> params) {
|
||||
try {
|
||||
StringBuilder url = new StringBuilder(wps365Config.getApiBaseUrl() + "/files");
|
||||
|
||||
// 添加查询参数
|
||||
if (params != null && !params.isEmpty()) {
|
||||
url.append("?");
|
||||
boolean first = true;
|
||||
for (Map.Entry<String, Object> entry : params.entrySet()) {
|
||||
if (!first) {
|
||||
url.append("&");
|
||||
}
|
||||
url.append(entry.getKey()).append("=").append(entry.getValue());
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
return WPS365ApiUtil.httpRequest("GET", url.toString(), accessToken, null);
|
||||
} catch (Exception e) {
|
||||
log.error("获取文件列表失败", e);
|
||||
throw new RuntimeException("获取文件列表失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getFileInfo(String accessToken, String fileToken) {
|
||||
try {
|
||||
String url = wps365Config.getApiBaseUrl() + "/files/" + fileToken;
|
||||
return WPS365ApiUtil.httpRequest("GET", url, accessToken, null);
|
||||
} catch (Exception e) {
|
||||
log.error("获取文件信息失败 - fileToken: {}", fileToken, e);
|
||||
throw new RuntimeException("获取文件信息失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject updateCells(String accessToken, String fileToken, int sheetIdx, String range, List<List<Object>> values) {
|
||||
try {
|
||||
// WPS365 KSheet API: /api/v1/openapi/ksheet/:file_token/sheets/:sheet_idx/cells
|
||||
String url = wps365Config.getApiBaseUrl() + "/openapi/ksheet/" + fileToken + "/sheets/" + sheetIdx + "/cells";
|
||||
|
||||
// 构建请求体
|
||||
JSONObject requestBody = new JSONObject();
|
||||
requestBody.put("range", range);
|
||||
|
||||
// 将values转换为JSONArray
|
||||
JSONArray valuesArray = new JSONArray();
|
||||
if (values != null) {
|
||||
for (List<Object> row : values) {
|
||||
JSONArray rowArray = new JSONArray();
|
||||
if (row != null) {
|
||||
for (Object cell : row) {
|
||||
rowArray.add(cell != null ? cell : "");
|
||||
}
|
||||
}
|
||||
valuesArray.add(rowArray);
|
||||
}
|
||||
}
|
||||
requestBody.put("values", valuesArray);
|
||||
|
||||
String bodyStr = requestBody.toJSONString();
|
||||
log.debug("更新单元格数据 - url: {}, range: {}, values: {}", url, range, bodyStr);
|
||||
|
||||
return WPS365ApiUtil.httpRequest("POST", url, accessToken, bodyStr);
|
||||
} catch (Exception e) {
|
||||
log.error("更新单元格数据失败 - fileToken: {}, sheetIdx: {}, range: {}", fileToken, sheetIdx, range, e);
|
||||
throw new RuntimeException("更新单元格数据失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject readCells(String accessToken, String fileToken, int sheetIdx, String range) {
|
||||
try {
|
||||
// WPS365 KSheet API: GET /api/v1/openapi/ksheet/:file_token/sheets/:sheet_idx/cells
|
||||
String url = wps365Config.getApiBaseUrl() + "/openapi/ksheet/" + fileToken + "/sheets/" + sheetIdx + "/cells";
|
||||
if (range != null && !range.trim().isEmpty()) {
|
||||
url += "?range=" + java.net.URLEncoder.encode(range, "UTF-8");
|
||||
}
|
||||
|
||||
return WPS365ApiUtil.httpRequest("GET", url, accessToken, null);
|
||||
} catch (Exception e) {
|
||||
log.error("读取单元格数据失败 - fileToken: {}, sheetIdx: {}, range: {}", fileToken, sheetIdx, range, e);
|
||||
throw new RuntimeException("读取单元格数据失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getSheetList(String accessToken, String fileToken) {
|
||||
try {
|
||||
// WPS365 KSheet API: GET /api/v1/openapi/ksheet/:file_token/sheets
|
||||
String url = wps365Config.getApiBaseUrl() + "/openapi/ksheet/" + fileToken + "/sheets";
|
||||
return WPS365ApiUtil.httpRequest("GET", url, accessToken, null);
|
||||
} catch (Exception e) {
|
||||
log.error("获取工作表列表失败 - fileToken: {}", fileToken, e);
|
||||
throw new RuntimeException("获取工作表列表失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject createSheet(String accessToken, String fileToken, String sheetName) {
|
||||
try {
|
||||
// WPS365 KSheet API: POST /api/v1/openapi/ksheet/:file_token/sheets
|
||||
String url = wps365Config.getApiBaseUrl() + "/openapi/ksheet/" + fileToken + "/sheets";
|
||||
|
||||
JSONObject requestBody = new JSONObject();
|
||||
requestBody.put("name", sheetName);
|
||||
|
||||
String bodyStr = requestBody.toJSONString();
|
||||
return WPS365ApiUtil.httpRequest("POST", url, accessToken, bodyStr);
|
||||
} catch (Exception e) {
|
||||
log.error("创建数据表失败 - fileToken: {}, sheetName: {}", fileToken, sheetName, e);
|
||||
throw new RuntimeException("创建数据表失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject batchUpdateCells(String accessToken, String fileToken, int sheetIdx, List<Map<String, Object>> updates) {
|
||||
try {
|
||||
// WPS365 KSheet API: POST /api/v1/openapi/ksheet/:file_token/sheets/:sheet_idx/cells/batch
|
||||
String url = wps365Config.getApiBaseUrl() + "/openapi/ksheet/" + fileToken + "/sheets/" + sheetIdx + "/cells/batch";
|
||||
|
||||
JSONObject requestBody = new JSONObject();
|
||||
JSONArray updatesArray = new JSONArray();
|
||||
|
||||
if (updates != null) {
|
||||
for (Map<String, Object> update : updates) {
|
||||
JSONObject updateObj = new JSONObject();
|
||||
updateObj.put("range", update.get("range"));
|
||||
|
||||
// 将values转换为JSONArray
|
||||
@SuppressWarnings("unchecked")
|
||||
List<List<Object>> values = (List<List<Object>>) update.get("values");
|
||||
JSONArray valuesArray = new JSONArray();
|
||||
if (values != null) {
|
||||
for (List<Object> row : values) {
|
||||
JSONArray rowArray = new JSONArray();
|
||||
if (row != null) {
|
||||
for (Object cell : row) {
|
||||
rowArray.add(cell != null ? cell : "");
|
||||
}
|
||||
}
|
||||
valuesArray.add(rowArray);
|
||||
}
|
||||
}
|
||||
updateObj.put("values", valuesArray);
|
||||
updatesArray.add(updateObj);
|
||||
}
|
||||
}
|
||||
|
||||
requestBody.put("updates", updatesArray);
|
||||
String bodyStr = requestBody.toJSONString();
|
||||
|
||||
return WPS365ApiUtil.httpRequest("POST", url, accessToken, bodyStr);
|
||||
} catch (Exception e) {
|
||||
log.error("批量更新单元格数据失败 - fileToken: {}, sheetIdx: {}", fileToken, sheetIdx, e);
|
||||
throw new RuntimeException("批量更新单元格数据失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
package com.ruoyi.jarvis.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.jarvis.config.WPS365Config;
|
||||
import com.ruoyi.jarvis.domain.dto.WPS365TokenInfo;
|
||||
import com.ruoyi.jarvis.service.IWPS365OAuthService;
|
||||
import com.ruoyi.jarvis.util.WPS365ApiUtil;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* WPS365 OAuth授权服务实现类
|
||||
*
|
||||
* @author system
|
||||
*/
|
||||
@Service
|
||||
public class WPS365OAuthServiceImpl implements IWPS365OAuthService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WPS365OAuthServiceImpl.class);
|
||||
|
||||
/** Redis key前缀,用于存储用户token */
|
||||
private static final String TOKEN_KEY_PREFIX = "wps365:token:";
|
||||
|
||||
/** Token过期时间(秒),默认30天 */
|
||||
private static final long TOKEN_EXPIRE_TIME = 30 * 24 * 60 * 60;
|
||||
|
||||
@Autowired
|
||||
private WPS365Config wps365Config;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Override
|
||||
public String getAuthUrl(String state) {
|
||||
if (wps365Config == null) {
|
||||
throw new RuntimeException("WPS365配置未加载,请检查WPS365Config是否正确注入");
|
||||
}
|
||||
|
||||
String appId = wps365Config.getAppId();
|
||||
String redirectUri = wps365Config.getRedirectUri();
|
||||
String oauthUrl = wps365Config.getOauthUrl();
|
||||
|
||||
log.debug("获取授权URL - appId: {}, redirectUri: {}, oauthUrl: {}", appId, redirectUri, oauthUrl);
|
||||
|
||||
// 验证配置参数
|
||||
if (appId == null || appId.trim().isEmpty()) {
|
||||
throw new RuntimeException("WPS365应用ID未配置,请检查application.yml中的wps365.app-id配置");
|
||||
}
|
||||
if (redirectUri == null || redirectUri.trim().isEmpty()) {
|
||||
throw new RuntimeException("WPS365回调地址未配置,请检查application.yml中的wps365.redirect-uri配置");
|
||||
}
|
||||
|
||||
// 构建授权URL
|
||||
StringBuilder authUrl = new StringBuilder();
|
||||
authUrl.append(oauthUrl);
|
||||
authUrl.append("?client_id=").append(appId);
|
||||
try {
|
||||
String encodedRedirectUri = java.net.URLEncoder.encode(redirectUri, "UTF-8");
|
||||
authUrl.append("&redirect_uri=").append(encodedRedirectUri);
|
||||
} catch (java.io.UnsupportedEncodingException e) {
|
||||
log.error("URL编码失败", e);
|
||||
authUrl.append("&redirect_uri=").append(redirectUri);
|
||||
}
|
||||
authUrl.append("&response_type=code");
|
||||
// WPS365的scope,根据官方文档设置
|
||||
authUrl.append("&scope=file.readwrite,user.info");
|
||||
|
||||
// 添加state参数
|
||||
if (state == null || state.trim().isEmpty()) {
|
||||
state = UUID.randomUUID().toString();
|
||||
}
|
||||
authUrl.append("&state=").append(state);
|
||||
|
||||
String result = authUrl.toString();
|
||||
log.info("生成授权URL: {}", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WPS365TokenInfo getAccessTokenByCode(String code) {
|
||||
try {
|
||||
JSONObject result = WPS365ApiUtil.getAccessToken(
|
||||
wps365Config.getAppId(),
|
||||
wps365Config.getAppKey(),
|
||||
code,
|
||||
wps365Config.getRedirectUri(),
|
||||
wps365Config.getTokenUrl()
|
||||
);
|
||||
|
||||
// 解析响应并创建TokenInfo对象
|
||||
WPS365TokenInfo tokenInfo = new WPS365TokenInfo();
|
||||
tokenInfo.setAccessToken(result.getString("access_token"));
|
||||
tokenInfo.setRefreshToken(result.getString("refresh_token"));
|
||||
tokenInfo.setTokenType(result.getString("token_type"));
|
||||
tokenInfo.setExpiresIn(result.getInteger("expires_in"));
|
||||
tokenInfo.setScope(result.getString("scope"));
|
||||
tokenInfo.setUserId(result.getString("user_id"));
|
||||
|
||||
log.info("成功获取访问令牌 - userId: {}", tokenInfo.getUserId());
|
||||
|
||||
return tokenInfo;
|
||||
} catch (Exception e) {
|
||||
log.error("通过授权码获取访问令牌失败", e);
|
||||
throw new RuntimeException("获取访问令牌失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WPS365TokenInfo refreshAccessToken(String refreshToken) {
|
||||
try {
|
||||
JSONObject result = WPS365ApiUtil.refreshAccessToken(
|
||||
wps365Config.getAppId(),
|
||||
wps365Config.getAppKey(),
|
||||
refreshToken,
|
||||
wps365Config.getRefreshTokenUrl()
|
||||
);
|
||||
|
||||
// 解析响应并创建TokenInfo对象
|
||||
WPS365TokenInfo tokenInfo = new WPS365TokenInfo();
|
||||
tokenInfo.setAccessToken(result.getString("access_token"));
|
||||
tokenInfo.setRefreshToken(result.getString("refresh_token"));
|
||||
tokenInfo.setTokenType(result.getString("token_type"));
|
||||
tokenInfo.setExpiresIn(result.getInteger("expires_in"));
|
||||
tokenInfo.setScope(result.getString("scope"));
|
||||
tokenInfo.setUserId(result.getString("user_id"));
|
||||
|
||||
log.info("成功刷新访问令牌 - userId: {}", tokenInfo.getUserId());
|
||||
|
||||
return tokenInfo;
|
||||
} catch (Exception e) {
|
||||
log.error("刷新访问令牌失败", e);
|
||||
throw new RuntimeException("刷新访问令牌失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WPS365TokenInfo getCurrentToken() {
|
||||
// 这里需要根据实际业务获取当前用户ID
|
||||
// 暂时返回null,需要在Controller中处理用户ID获取逻辑
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveToken(String userId, WPS365TokenInfo tokenInfo) {
|
||||
if (userId == null || userId.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("用户ID不能为空");
|
||||
}
|
||||
if (tokenInfo == null || tokenInfo.getAccessToken() == null) {
|
||||
throw new IllegalArgumentException("Token信息不能为空");
|
||||
}
|
||||
|
||||
String key = TOKEN_KEY_PREFIX + userId;
|
||||
redisCache.setCacheObject(key, tokenInfo, (int) TOKEN_EXPIRE_TIME, TimeUnit.SECONDS);
|
||||
log.info("保存用户Token - userId: {}", userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearToken(String userId) {
|
||||
if (userId == null || userId.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String key = TOKEN_KEY_PREFIX + userId;
|
||||
redisCache.deleteObject(key);
|
||||
log.info("清除用户Token - userId: {}", userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTokenValid(WPS365TokenInfo tokenInfo) {
|
||||
if (tokenInfo == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否过期
|
||||
if (tokenInfo.isExpired()) {
|
||||
log.debug("Token已过期");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查必要字段
|
||||
if (tokenInfo.getAccessToken() == null || tokenInfo.getAccessToken().trim().isEmpty()) {
|
||||
log.debug("Token缺少accessToken");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户ID获取Token(从Redis)
|
||||
*/
|
||||
public WPS365TokenInfo getTokenByUserId(String userId) {
|
||||
if (userId == null || userId.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String key = TOKEN_KEY_PREFIX + userId;
|
||||
return redisCache.getCacheObject(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
package com.ruoyi.jarvis.util;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* WPS365 API工具类
|
||||
*
|
||||
* @author system
|
||||
*/
|
||||
public class WPS365ApiUtil {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WPS365ApiUtil.class);
|
||||
|
||||
// 静态初始化块:禁用系统代理
|
||||
static {
|
||||
System.setProperty("java.net.useSystemProxies", "false");
|
||||
System.clearProperty("http.proxyHost");
|
||||
System.clearProperty("http.proxyPort");
|
||||
System.clearProperty("https.proxyHost");
|
||||
System.clearProperty("https.proxyPort");
|
||||
log.info("已禁用系统代理设置,WPS365 API将直接连接");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取访问令牌
|
||||
*
|
||||
* @param appId 应用ID
|
||||
* @param appKey 应用密钥
|
||||
* @param code 授权码
|
||||
* @param redirectUri 回调地址
|
||||
* @param tokenUrl Token地址
|
||||
* @return 包含access_token和refresh_token的JSON对象
|
||||
*/
|
||||
public static JSONObject getAccessToken(String appId, String appKey, String code, String redirectUri, String tokenUrl) {
|
||||
try {
|
||||
// 构建请求参数
|
||||
StringBuilder params = new StringBuilder();
|
||||
params.append("grant_type=authorization_code");
|
||||
params.append("&client_id=").append(appId);
|
||||
params.append("&client_secret=").append(appKey);
|
||||
params.append("&code=").append(code);
|
||||
params.append("&redirect_uri=").append(java.net.URLEncoder.encode(redirectUri, "UTF-8"));
|
||||
|
||||
// 使用HttpURLConnection,不使用代理
|
||||
URL url = new URL(tokenUrl);
|
||||
java.net.Proxy proxy = java.net.Proxy.NO_PROXY;
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
conn.setDoOutput(true);
|
||||
conn.setDoInput(true);
|
||||
conn.setConnectTimeout(10000);
|
||||
conn.setReadTimeout(30000);
|
||||
|
||||
// 写入请求参数
|
||||
try (OutputStream os = conn.getOutputStream();
|
||||
OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8)) {
|
||||
osw.write(params.toString());
|
||||
osw.flush();
|
||||
}
|
||||
|
||||
// 读取响应
|
||||
int statusCode = conn.getResponseCode();
|
||||
BufferedReader reader;
|
||||
if (statusCode >= 200 && statusCode < 300) {
|
||||
reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
|
||||
} else {
|
||||
reader = new BufferedReader(new InputStreamReader(conn.getErrorStream(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
reader.close();
|
||||
|
||||
String responseStr = response.toString();
|
||||
log.info("获取访问令牌响应: statusCode={}, response={}", statusCode, responseStr);
|
||||
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
throw new RuntimeException("获取访问令牌失败: HTTP " + statusCode + ", response=" + responseStr);
|
||||
}
|
||||
|
||||
return JSON.parseObject(responseStr);
|
||||
} catch (Exception e) {
|
||||
log.error("获取访问令牌失败", e);
|
||||
throw new RuntimeException("获取访问令牌失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新访问令牌
|
||||
*
|
||||
* @param appId 应用ID
|
||||
* @param appKey 应用密钥
|
||||
* @param refreshToken 刷新令牌
|
||||
* @param refreshTokenUrl 刷新令牌地址
|
||||
* @return 包含新的access_token和refresh_token的JSON对象
|
||||
*/
|
||||
public static JSONObject refreshAccessToken(String appId, String appKey, String refreshToken, String refreshTokenUrl) {
|
||||
try {
|
||||
// 构建请求参数
|
||||
StringBuilder params = new StringBuilder();
|
||||
params.append("grant_type=refresh_token");
|
||||
params.append("&client_id=").append(appId);
|
||||
params.append("&client_secret=").append(appKey);
|
||||
params.append("&refresh_token=").append(refreshToken);
|
||||
|
||||
// 使用HttpURLConnection,不使用代理
|
||||
URL url = new URL(refreshTokenUrl);
|
||||
java.net.Proxy proxy = java.net.Proxy.NO_PROXY;
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
conn.setDoOutput(true);
|
||||
conn.setDoInput(true);
|
||||
conn.setConnectTimeout(10000);
|
||||
conn.setReadTimeout(30000);
|
||||
|
||||
// 写入请求参数
|
||||
try (OutputStream os = conn.getOutputStream();
|
||||
OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8)) {
|
||||
osw.write(params.toString());
|
||||
osw.flush();
|
||||
}
|
||||
|
||||
// 读取响应
|
||||
int statusCode = conn.getResponseCode();
|
||||
BufferedReader reader;
|
||||
if (statusCode >= 200 && statusCode < 300) {
|
||||
reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
|
||||
} else {
|
||||
reader = new BufferedReader(new InputStreamReader(conn.getErrorStream(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
reader.close();
|
||||
|
||||
String responseStr = response.toString();
|
||||
log.info("刷新访问令牌响应: statusCode={}, response={}", statusCode, responseStr);
|
||||
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
throw new RuntimeException("刷新访问令牌失败: HTTP " + statusCode + ", response=" + responseStr);
|
||||
}
|
||||
|
||||
return JSON.parseObject(responseStr);
|
||||
} catch (Exception e) {
|
||||
log.error("刷新访问令牌失败", e);
|
||||
throw new RuntimeException("刷新访问令牌失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送HTTP请求
|
||||
*
|
||||
* @param method 请求方法(GET/POST/PUT/DELETE)
|
||||
* @param url 请求URL
|
||||
* @param accessToken 访问令牌
|
||||
* @param body 请求体(JSON字符串,GET请求可为null)
|
||||
* @return 响应JSON对象
|
||||
*/
|
||||
public static JSONObject httpRequest(String method, String url, String accessToken, String body) {
|
||||
try {
|
||||
log.debug("发送HTTP请求: method={}, url={}", method, url);
|
||||
|
||||
URL requestUrl = new URL(url);
|
||||
java.net.Proxy proxy = java.net.Proxy.NO_PROXY;
|
||||
HttpURLConnection conn = (HttpURLConnection) requestUrl.openConnection(proxy);
|
||||
conn.setRequestMethod(method);
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
conn.setRequestProperty("Authorization", "Bearer " + accessToken);
|
||||
conn.setConnectTimeout(10000);
|
||||
conn.setReadTimeout(30000);
|
||||
|
||||
if ("POST".equals(method) || "PUT".equals(method) || "PATCH".equals(method)) {
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setDoOutput(true);
|
||||
|
||||
if (body != null && !body.isEmpty()) {
|
||||
try (OutputStream os = conn.getOutputStream();
|
||||
OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8)) {
|
||||
osw.write(body);
|
||||
osw.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 读取响应
|
||||
int statusCode = conn.getResponseCode();
|
||||
BufferedReader reader;
|
||||
if (statusCode >= 200 && statusCode < 300) {
|
||||
reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
|
||||
} else {
|
||||
reader = new BufferedReader(new InputStreamReader(conn.getErrorStream(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
reader.close();
|
||||
|
||||
String responseStr = response.toString();
|
||||
log.debug("HTTP响应: statusCode={}, response={}", statusCode, responseStr);
|
||||
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
throw new RuntimeException("HTTP请求失败: " + statusCode + ", response=" + responseStr);
|
||||
}
|
||||
|
||||
if (responseStr == null || responseStr.trim().isEmpty()) {
|
||||
return new JSONObject();
|
||||
}
|
||||
|
||||
return JSON.parseObject(responseStr);
|
||||
} catch (Exception e) {
|
||||
log.error("HTTP请求失败: method={}, url={}", method, url, e);
|
||||
throw new RuntimeException("HTTP请求失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user