1
This commit is contained in:
426
doc/腾讯文档API_官方格式修复.md
Normal file
426
doc/腾讯文档API_官方格式修复.md
Normal file
@@ -0,0 +1,426 @@
|
||||
# 腾讯文档 API 官方格式修复
|
||||
|
||||
## 修复日期
|
||||
2025-11-05
|
||||
|
||||
## 问题来源
|
||||
根据[腾讯文档官方 API 文档](https://docs.qq.com/open/document/app/openapi/v3/sheet/get/get_range.html),发现之前对 Range 格式的理解有误。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 官方规范
|
||||
|
||||
### 1. Range 格式:A1 表示法
|
||||
|
||||
根据官方文档,range 参数使用 **A1 表示法**(Excel 格式),**不是**索引格式。
|
||||
|
||||
**官方示例**:
|
||||
```bash
|
||||
curl 'https://docs.qq.com/openapi/spreadsheet/v3/files/ABCDE123abcde/BB08J2/A10:D11' \
|
||||
--header 'Access-Token: {ACCESS_TOKEN}' \
|
||||
--header 'Open-Id: {OPEN_ID}' \
|
||||
--header 'Client-Id: {CLIENT_ID}'
|
||||
```
|
||||
|
||||
**正确格式**:
|
||||
- ✅ `A10:D11` - Excel 格式(A1 表示法)
|
||||
- ✅ `A2:Z2` - 表头行
|
||||
- ✅ `A3:Z203` - 数据行
|
||||
- ❌ `1,0,1,25` - 索引格式(错误)
|
||||
|
||||
---
|
||||
|
||||
### 2. 响应结构:data.gridData
|
||||
|
||||
根据官方文档,成功响应的结构为:
|
||||
|
||||
```json
|
||||
{
|
||||
"ret": 0,
|
||||
"msg": "Succeed",
|
||||
"data": {
|
||||
"gridData": {
|
||||
"columnMetadata": [],
|
||||
"rowMetadata": [],
|
||||
"rows": [
|
||||
{
|
||||
"values": [
|
||||
{
|
||||
"cellFormat": null,
|
||||
"cellValue": {
|
||||
"text": "单元格内容"
|
||||
},
|
||||
"dataType": "DATA_TYPE_UNSPECIFIED"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"startColumn": 0,
|
||||
"startRow": 9
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关键要点**:
|
||||
- ✅ 数据在 `data.gridData` 下(有两层包装)
|
||||
- ✅ 成功时 `ret: 0`
|
||||
- ✅ 错误时 `code != 0`
|
||||
|
||||
---
|
||||
|
||||
### 3. API 限制
|
||||
|
||||
根据官方文档,查询范围有以下限制:
|
||||
|
||||
| 限制项 | 最大值 |
|
||||
|-------|-------|
|
||||
| 查询范围行数 | ≤ 1000 |
|
||||
| 查询范围列数 | ≤ 200 |
|
||||
| 范围内总单元格数 | ≤ 10000 |
|
||||
|
||||
**我们的范围**:
|
||||
- `A3:Z203`:201行 × 26列 = 5226单元格 ✅ 符合限制
|
||||
- `A2:Z2`:1行 × 26列 = 26单元格 ✅ 符合限制
|
||||
|
||||
---
|
||||
|
||||
## 🔧 修复内容
|
||||
|
||||
### 修复 1:Range 格式(回到 A1 表示法)
|
||||
|
||||
#### TencentDocApiUtil.java
|
||||
**修改前**:
|
||||
```java
|
||||
// range格式:startRow,startColumn,endRow,endColumn(从0开始的索引)
|
||||
```
|
||||
|
||||
**修改后**:
|
||||
```java
|
||||
/**
|
||||
* @param range 范围,使用 A1 表示法(如:"A10:D11", "A1:Z100")
|
||||
* 根据官方文档:https://docs.qq.com/open/document/app/openapi/v3/sheet/get/get_range.html
|
||||
*/
|
||||
```
|
||||
|
||||
#### TencentDocController.java
|
||||
**修改前**(错误):
|
||||
```java
|
||||
int headerRowIndex = headerRow - 1;
|
||||
String headerRange = String.format("%d,0,%d,25", headerRowIndex, headerRowIndex);
|
||||
// 结果:"1,0,1,25"
|
||||
```
|
||||
|
||||
**修改后**(正确):
|
||||
```java
|
||||
String headerRange = String.format("A%d:Z%d", headerRow, headerRow);
|
||||
// 结果:"A2:Z2"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 修复 2:响应结构解析(支持 data.gridData)
|
||||
|
||||
#### TencentDocDataParser.java
|
||||
**新增支持**:
|
||||
```java
|
||||
// 方式1:检查是否有 data.gridData 字段(官方V3 API格式)
|
||||
JSONObject data = apiResponse.getJSONObject("data");
|
||||
if (data != null) {
|
||||
JSONObject gridData = data.getJSONObject("gridData");
|
||||
if (gridData != null) {
|
||||
return parseGridData(gridData);
|
||||
}
|
||||
}
|
||||
|
||||
// 方式2:检查是否有 gridData 字段(直接格式)
|
||||
JSONObject gridData = apiResponse.getJSONObject("gridData");
|
||||
if (gridData != null) {
|
||||
return parseGridData(gridData);
|
||||
}
|
||||
|
||||
// 方式3:检查是否有 values 字段(简单格式)
|
||||
JSONArray values = apiResponse.getJSONArray("values");
|
||||
if (values != null) {
|
||||
return values;
|
||||
}
|
||||
```
|
||||
|
||||
**兼容性**:支持三种响应格式
|
||||
1. 官方格式:`{ret, msg, data: {gridData}}`
|
||||
2. 简化格式:`{gridData}`
|
||||
3. 自定义格式:`{values}`
|
||||
|
||||
---
|
||||
|
||||
### 修复 3:错误响应检查
|
||||
|
||||
#### TencentDocServiceImpl.java
|
||||
**新增检查**:
|
||||
```java
|
||||
// 检查错误码(code字段)
|
||||
if (result.containsKey("code")) {
|
||||
Integer code = result.getInteger("code");
|
||||
if (code != null && code != 0) {
|
||||
String message = result.getString("message");
|
||||
throw new RuntimeException("腾讯文档API错误: " + message + " (code: " + code + ")");
|
||||
}
|
||||
}
|
||||
|
||||
// 检查业务返回码(ret字段)
|
||||
if (result.containsKey("ret")) {
|
||||
Integer ret = result.getInteger("ret");
|
||||
if (ret != null && ret != 0) {
|
||||
String msg = result.getString("msg");
|
||||
throw new RuntimeException("腾讯文档API业务错误: " + msg + " (ret: " + ret + ")");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**两种错误格式**:
|
||||
- 错误响应:`{code: 400001, message: "..."}`
|
||||
- 业务错误:`{ret: 1, msg: "..."}`(虽然官方成功是ret=0,但可能存在业务错误)
|
||||
|
||||
---
|
||||
|
||||
## 📊 修改对比表
|
||||
|
||||
| 项目 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| Range格式 | `1,0,1,25` | `A2:Z2` ✅ |
|
||||
| 表头range | `1,0,1,25` | `A2:Z2` ✅ |
|
||||
| 数据range | `2,0,202,25` | `A3:Z203` ✅ |
|
||||
| 响应解析 | 只支持 `gridData` | 支持 `data.gridData` ✅ |
|
||||
| 错误检查 | 只检查 `code` | 同时检查 `code` 和 `ret` ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 API 调用示例
|
||||
|
||||
### 完整的 API 请求
|
||||
|
||||
**读取表头(第2行)**:
|
||||
```
|
||||
GET https://docs.qq.com/openapi/spreadsheet/v3/files/DUW50RUprWXh2TGJK/BB08J2/A2:Z2
|
||||
Headers:
|
||||
Access-Token: {YOUR_ACCESS_TOKEN}
|
||||
Client-Id: {YOUR_CLIENT_ID}
|
||||
Open-Id: {YOUR_OPEN_ID}
|
||||
```
|
||||
|
||||
**读取数据(第3-203行)**:
|
||||
```
|
||||
GET https://docs.qq.com/openapi/spreadsheet/v3/files/DUW50RUprWXh2TGJK/BB08J2/A3:Z203
|
||||
Headers:
|
||||
Access-Token: {YOUR_ACCESS_TOKEN}
|
||||
Client-Id: {YOUR_CLIENT_ID}
|
||||
Open-Id: {YOUR_OPEN_ID}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 成功响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"ret": 0,
|
||||
"msg": "Succeed",
|
||||
"data": {
|
||||
"gridData": {
|
||||
"startRow": 1,
|
||||
"startColumn": 0,
|
||||
"rows": [
|
||||
{
|
||||
"values": [
|
||||
{
|
||||
"cellValue": {"text": "日期"},
|
||||
"dataType": "DATA_TYPE_UNSPECIFIED"
|
||||
},
|
||||
{
|
||||
"cellValue": {"text": "公司"},
|
||||
"dataType": "DATA_TYPE_UNSPECIFIED"
|
||||
},
|
||||
{
|
||||
"cellValue": {"text": "草号"},
|
||||
"dataType": "DATA_TYPE_UNSPECIFIED"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 错误响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400001,
|
||||
"message": "Req Parameters Range Validate error",
|
||||
"details": {
|
||||
"DebugInfo": {
|
||||
"traceId": "b92e6e2a1c1e4810bf8cfc70eabf7351"
|
||||
}
|
||||
},
|
||||
"internalCode": 0
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 修改文件清单
|
||||
|
||||
### 1. TencentDocApiUtil.java
|
||||
- ✅ 更新 `readSheetData` 方法注释
|
||||
- ✅ 说明 range 使用 A1 表示法
|
||||
- ✅ 添加官方文档链接
|
||||
|
||||
### 2. TencentDocController.java
|
||||
- ✅ 将 headerRange 改为 A1 格式
|
||||
- ✅ 将 dataRange 改为 A1 格式
|
||||
- ✅ 简化日志输出
|
||||
|
||||
### 3. TencentDocDataParser.java
|
||||
- ✅ 支持 `data.gridData` 格式(官方格式)
|
||||
- ✅ 保持对 `gridData` 和 `values` 的兼容
|
||||
- ✅ 添加详细的调试日志
|
||||
|
||||
### 4. TencentDocServiceImpl.java
|
||||
- ✅ 同时检查 `code` 和 `ret` 错误码
|
||||
- ✅ 分别处理 API 错误和业务错误
|
||||
- ✅ 添加官方文档链接
|
||||
|
||||
### 5. 新增文档
|
||||
- ✅ `腾讯文档API_官方格式修复.md` - 本文档
|
||||
|
||||
---
|
||||
|
||||
## 🚀 测试验证
|
||||
|
||||
### 请求参数
|
||||
|
||||
```json
|
||||
{
|
||||
"accessToken": "YOUR_ACCESS_TOKEN",
|
||||
"fileId": "DUW50RUprWXh2TGJK",
|
||||
"sheetId": "BB08J2",
|
||||
"headerRow": 2,
|
||||
"orderNoColumn": 2,
|
||||
"logisticsLinkColumn": 12
|
||||
}
|
||||
```
|
||||
|
||||
### 预期日志输出
|
||||
|
||||
```
|
||||
读取表头 - 行号: 2, range: A2:Z2
|
||||
读取数据行 - 行号: 3 ~ 203, range: A3:Z203
|
||||
使用 data.gridData 格式解析
|
||||
解析后的数据行数: 98
|
||||
数据结构(共 98 行,显示前 3 行):
|
||||
第 1 行(15列): ["日期","公司","草号",...,"物流单号","标记"]
|
||||
第 2 行(15列): ["3月10日","","JY20251032904",...,"",""]
|
||||
第 3 行(15列): ["3月10日","","JY20250309184",...,"6649902864",""]
|
||||
成功读取 98 行数据,开始处理...
|
||||
```
|
||||
|
||||
### 预期结果
|
||||
|
||||
```json
|
||||
{
|
||||
"msg": "物流链接填充成功",
|
||||
"code": 200,
|
||||
"data": {
|
||||
"startRow": 3,
|
||||
"endRow": 203,
|
||||
"filledCount": 10,
|
||||
"skippedCount": 85,
|
||||
"errorCount": 3,
|
||||
"message": "成功填充10个物流链接"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 重要提醒
|
||||
|
||||
### 1. Range 格式必须是 A1 表示法
|
||||
|
||||
**正确示例**:
|
||||
- ✅ `A1`
|
||||
- ✅ `A1:Z1`
|
||||
- ✅ `A2:Z2`
|
||||
- ✅ `A3:Z203`
|
||||
- ✅ `M3` (单个单元格)
|
||||
|
||||
**错误示例**:
|
||||
- ❌ `0,0,0,0` (索引格式)
|
||||
- ❌ `1,0,1,25` (索引格式)
|
||||
- ❌ `a1:z1` (小写,应该大写)
|
||||
|
||||
### 2. 响应格式有两种
|
||||
|
||||
**成功响应**:
|
||||
```json
|
||||
{
|
||||
"ret": 0,
|
||||
"msg": "Succeed",
|
||||
"data": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应**:
|
||||
```json
|
||||
{
|
||||
"code": 400001,
|
||||
"message": "...",
|
||||
"details": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### 3. API 限制
|
||||
|
||||
- 单次查询行数 ≤ 1000
|
||||
- 单次查询列数 ≤ 200
|
||||
- 总单元格数 ≤ 10000
|
||||
|
||||
如果超过限制,需要分批查询。
|
||||
|
||||
---
|
||||
|
||||
## 📚 官方文档链接
|
||||
|
||||
- [获取范围内的表格信息](https://docs.qq.com/open/document/app/openapi/v3/sheet/get/get_range.html) ⭐⭐⭐
|
||||
- [A1 表示法说明](https://docs.qq.com/open/document/app/openapi/v3/sheet/model/a1_notation.html)
|
||||
- [在线表格资源描述](https://docs.qq.com/open/document/app/openapi/v3/sheet/model/spreadsheet.html)
|
||||
- [批量更新接口](https://docs.qq.com/open/document/app/openapi/v3/sheet/batchupdate/update.html)
|
||||
|
||||
---
|
||||
|
||||
## ✅ 总结
|
||||
|
||||
### 关键修复点
|
||||
|
||||
1. ✅ **Range 格式**:从索引格式改回 A1 表示法(Excel 格式)
|
||||
2. ✅ **响应解析**:支持官方的 `data.gridData` 结构
|
||||
3. ✅ **错误检查**:同时检查 `code` 和 `ret` 两种错误格式
|
||||
4. ✅ **文档引用**:所有修改都基于官方文档
|
||||
|
||||
### 修改影响
|
||||
|
||||
- ✅ 完全符合官方 API 规范
|
||||
- ✅ 向后兼容(支持多种响应格式)
|
||||
- ✅ 更好的错误提示
|
||||
- ✅ 详细的日志记录
|
||||
|
||||
---
|
||||
|
||||
**文档版本**:1.0
|
||||
**创建时间**:2025-11-05
|
||||
**依据**:腾讯文档开放平台官方 API 文档
|
||||
**状态**:✅ 已修复并验证
|
||||
|
||||
@@ -509,11 +509,10 @@ public class TencentDocController extends BaseController {
|
||||
fileId, sheetId, startRow, endRow, lastMaxRow);
|
||||
|
||||
// 读取表格数据(先读取表头行用于识别列位置)
|
||||
// 腾讯文档V3 API的range格式:startRow,startColumn,endRow,endColumn(从0开始的索引)
|
||||
// 例如:第2行A列到Z列 = "1,0,1,25"(行索引从0开始,所以第2行是索引1)
|
||||
int headerRowIndex = headerRow - 1; // Excel行号转索引(第2行 = 索引1)
|
||||
String headerRange = String.format("%d,0,%d,25", headerRowIndex, headerRowIndex); // 读取A到Z列(0-25)
|
||||
log.info("读取表头 - Excel行号: {}, 索引行号: {}, range: {}", headerRow, headerRowIndex, headerRange);
|
||||
// 根据官方文档,使用 A1 表示法(Excel格式)
|
||||
// 参考:https://docs.qq.com/open/document/app/openapi/v3/sheet/get/get_range.html
|
||||
String headerRange = String.format("A%d:Z%d", headerRow, headerRow); // 例如:A2:Z2
|
||||
log.info("读取表头 - 行号: {}, range: {}", headerRow, headerRange);
|
||||
|
||||
JSONObject headerData = null;
|
||||
try {
|
||||
@@ -566,11 +565,9 @@ public class TencentDocController extends BaseController {
|
||||
}
|
||||
|
||||
// 读取数据行
|
||||
// 构建数据行的range(从startRow到endRow,A列到Z列)
|
||||
int startRowIndex = startRow - 1; // Excel行号转索引
|
||||
int endRowIndex = endRow - 1; // Excel行号转索引
|
||||
String range = String.format("%d,0,%d,25", startRowIndex, endRowIndex); // 读取A到Z列(0-25)
|
||||
log.info("开始读取数据行 - Excel行号: {} ~ {}, 索引: {} ~ {}, range: {}", startRow, endRow, startRowIndex, endRowIndex, range);
|
||||
// 使用 A1 表示法(Excel格式)
|
||||
String range = String.format("A%d:Z%d", startRow, endRow); // 例如:A3:Z203
|
||||
log.info("开始读取数据行 - 行号: {} ~ {}, range: {}", startRow, endRow, range);
|
||||
|
||||
JSONObject sheetData = null;
|
||||
try {
|
||||
|
||||
@@ -264,12 +264,26 @@ public class TencentDocServiceImpl implements ITencentDocService {
|
||||
log.info("API调用成功,原始返回结果: {}", result != null ? result.toJSONString() : "null");
|
||||
|
||||
// 检查API响应中的错误码
|
||||
if (result != null && result.containsKey("code")) {
|
||||
Integer code = result.getInteger("code");
|
||||
if (code != null && code != 0) {
|
||||
String message = result.getString("message");
|
||||
log.error("腾讯文档API返回错误 - code: {}, message: {}", code, message);
|
||||
throw new RuntimeException("腾讯文档API错误: " + message + " (code: " + code + ")");
|
||||
// 根据官方文档,成功响应包含 ret=0,错误响应包含 code!=0
|
||||
// 参考:https://docs.qq.com/open/document/app/openapi/v3/sheet/get/get_range.html
|
||||
if (result != null) {
|
||||
// 检查错误码(code字段)
|
||||
if (result.containsKey("code")) {
|
||||
Integer code = result.getInteger("code");
|
||||
if (code != null && code != 0) {
|
||||
String message = result.getString("message");
|
||||
log.error("腾讯文档API返回错误 - code: {}, message: {}", code, message);
|
||||
throw new RuntimeException("腾讯文档API错误: " + message + " (code: " + code + ")");
|
||||
}
|
||||
}
|
||||
// 检查业务返回码(ret字段)
|
||||
if (result.containsKey("ret")) {
|
||||
Integer ret = result.getInteger("ret");
|
||||
if (ret != null && ret != 0) {
|
||||
String msg = result.getString("msg");
|
||||
log.error("腾讯文档API业务错误 - ret: {}, msg: {}", ret, msg);
|
||||
throw new RuntimeException("腾讯文档API业务错误: " + msg + " (ret: " + ret + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -330,19 +330,20 @@ public class TencentDocApiUtil {
|
||||
|
||||
/**
|
||||
* 读取表格数据 - V3 API
|
||||
* 根据官方文档:https://docs.qq.com/open/document/app/openapi/v3/sheet/get/get_range.html
|
||||
*
|
||||
* @param accessToken 访问令牌
|
||||
* @param appId 应用ID
|
||||
* @param openId 开放平台用户ID
|
||||
* @param fileId 文件ID(在线表格的唯一标识)
|
||||
* @param sheetId 工作表ID(可从表格链接中获取,如 ?tab=BB08J2 中的 BB08J2)
|
||||
* @param range 范围,格式:startRow,startColumn,endRow,endColumn(从0开始,例如:"0,0,10,26"表示A1到Z11)
|
||||
* @param range 范围,使用 A1 表示法(如:"A10:D11", "A1:Z100")
|
||||
* @param apiBaseUrl API基础地址(默认:https://docs.qq.com/openapi/spreadsheet/v3)
|
||||
* @return 表格数据(JSON格式,包含gridData)
|
||||
* @return 表格数据(JSON格式,包含 data.gridData)
|
||||
*/
|
||||
public static JSONObject readSheetData(String accessToken, String appId, String openId, String fileId, String sheetId, String range, String apiBaseUrl) {
|
||||
// V3版本API路径格式:/openapi/spreadsheet/v3/files/{fileId}/{sheetId}/{range}
|
||||
// range格式:startRow,startColumn,endRow,endColumn(从0开始的索引)
|
||||
// range格式:A1表示法(Excel格式),如 A10:D11
|
||||
String apiUrl = String.format("%s/files/%s/%s/%s", apiBaseUrl, fileId, sheetId, range);
|
||||
log.info("读取表格数据 - fileId: {}, sheetId: {}, range: {}, apiUrl: {}", fileId, sheetId, range, apiUrl);
|
||||
return callApi(accessToken, appId, openId, apiUrl, "GET", null);
|
||||
|
||||
@@ -17,8 +17,9 @@ public class TencentDocDataParser {
|
||||
|
||||
/**
|
||||
* 解析腾讯文档 V3 API 返回的数据为简单的二维数组格式
|
||||
* 根据官方文档,响应格式为:{ "ret": 0, "msg": "Succeed", "data": { "gridData": {...} } }
|
||||
*
|
||||
* @param apiResponse API响应(可能包含 gridData 或 values 字段)
|
||||
* @param apiResponse API响应(可能包含 data.gridData、gridData 或 values 字段)
|
||||
* @return 二维数组格式的数据,每行是一个JSONArray
|
||||
*/
|
||||
public static JSONArray parseToSimpleArray(JSONObject apiResponse) {
|
||||
@@ -26,20 +27,32 @@ public class TencentDocDataParser {
|
||||
return new JSONArray();
|
||||
}
|
||||
|
||||
// 方式1:检查是否有 gridData 字段(V3 API 新格式)
|
||||
// 方式1:检查是否有 data.gridData 字段(官方V3 API格式)
|
||||
JSONObject data = apiResponse.getJSONObject("data");
|
||||
if (data != null) {
|
||||
JSONObject gridData = data.getJSONObject("gridData");
|
||||
if (gridData != null) {
|
||||
log.debug("使用 data.gridData 格式解析");
|
||||
return parseGridData(gridData);
|
||||
}
|
||||
}
|
||||
|
||||
// 方式2:检查是否有 gridData 字段(直接格式)
|
||||
JSONObject gridData = apiResponse.getJSONObject("gridData");
|
||||
if (gridData != null) {
|
||||
log.debug("使用 gridData 格式解析");
|
||||
return parseGridData(gridData);
|
||||
}
|
||||
|
||||
// 方式2:检查是否有 values 字段(简单格式)
|
||||
// 方式3:检查是否有 values 字段(简单格式)
|
||||
JSONArray values = apiResponse.getJSONArray("values");
|
||||
if (values != null) {
|
||||
log.debug("使用 values 格式解析");
|
||||
return values;
|
||||
}
|
||||
|
||||
// 如果都没有,返回空数组
|
||||
log.warn("API响应中既没有 gridData 也没有 values 字段,返回空数组");
|
||||
log.warn("API响应中既没有 data.gridData、gridData 也没有 values 字段,返回空数组");
|
||||
return new JSONArray();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user