飞书工作台小组件开发流程(各种鉴权token介绍+公告栏小组件示例Java后端+飞书开发者工具前端)

CSDN 2024-09-18 11:33:01 阅读 63

一.鉴权知识(选取token)

飞书API调试台

1.飞书sdk-鉴权

a.自建应用获取 tenant_access_token

Tenant Access Token 代表使用应用的身份操作 OpenAPI,API 所能操作的数据资源范围受限于应用的身份所能操作的资源范围。如果你的业务逻辑不需要操作用户的数据资源,仅需操作应用自己拥有的资源(比如在应用自己的文档目录空间下创建云文档),则推荐使用 Tenant Access Token,无需额外申请授权。自建应用获取 tenant_access_token

tenant_access_token 和 app_access_token 的最大有效期是 2 小时。如果在有效期小于 30 分钟的情况下,调用本接口,会返回一个新的 tenant_access_token,这会同时存在两个有效的 tenant_access_token。

App ID:

1234567898ertyuio

App Secret :

1234567898ertyuio1234567898ertyuio

requestBody:

{

“app_id”: “1234567898ertyuio”,

“app_secret”: “1234567898ertyuio1234567898ertyuio”

}

b.自建应用获取 app_access_token

自建应用获取 app_access_token

c.自建应用获取 user_access_token自建应用获取user_access_token

2.如何选择使用哪种类型的 Token

在飞书开放平台上调用 OpenAPI 时,部分接口会同时支持 Tenant Access Token 和 User Access Token。本文主要介绍如何选择 Tenant Access Token 和 User Access Token。

Tenant Access Token、User Access Token、App Access Token 的详细介绍以及获取方式,参见 获取访问凭证。

Tenant Access Token

Tenant Access Token 代表使用应用的身份操作 OpenAPI,API 所能操作的数据资源范围受限于应用的身份所能操作的资源范围。

如果你的业务逻辑不需要操作用户的数据资源,仅需操作应用自己拥有的资源(比如在应用自己的文档目录空间下创建云文档),则推荐使用 Tenant Access Token,无需额外申请授权。

User Access Token

User Access Token 代表使用应用的使用者的身份操作 OpenAPI,API 所能操作的数据资源范围受限于用户的身份所能操作的资源范围。

如果你的业务逻辑需要操作用户的数据资源(例如需要在用户的文档目录空间下创建云文档),则推荐使用 User Access Token,无需额外申请授权。如果使用 Tenant Access Token,则需额外在资源层面为应用添加相应的授权。

以通讯录为例,如需使用 Tenant Access Token 调用通讯录,则需在开发者后台「权限管理」页面配置应用的通讯录权限范围;而使用 User Access Token 则无需单独配置通讯录权限范围,该范围遵循 User Access Token 所属的用户的通讯录权限范围。

b.获取user授权登录授权码

https://open.feishu.cn/document/common-capabilities/sso/api/obtain-oauth-code

[图片]

redirect_uri参数需要在管理后台添加到重定向url列表中,在访问时作为请求参数redirect_uri需要使用UrlEncode工具编码

UrlEnCode工具

http://www.urlencode.com.cn/

[图片]

https://open.feishu.cn/open-apis/authen/v1/authorize?app_id=cli_a6b06b01f5fa500c&redirect_uri=https%3A%2F%2Frwdls.feishu.cn%2Fdrive%2Fhome%2F&scope=bitable:app:readonly%20wiki:wiki&state=RANDOMSTATE

[图片]

默认本地用户登录;也可以其他用户扫码登录

路径参数scope:

用户授予app的权限,格式要求:不同scope由空格分隔,区分大小写的字符串。业务需要根据自己内部场景,自主拼接调用open-api所需的scope。scope详细说明请参考:申请API权限

示例:contact:contact bitable:app:readonly我们这个组件使用的是wiki:wiki访问权限示例:https://open.feishu.cn/open-apis/authen/v1/authorize?app_id=cli_a6b06b01f5fa500c&redirect_uri=https%3A%2F%2F127.0.0.1%2F&scope=wiki:wiki&state=RANDOMSTATE加密前:https://open.feishu.cn/open-apis/authen/v1/authorize?app_id=cli_a6b06b01f5fa500c&redirect_uri=https://127.0.0.1/&scope=wiki:wiki&state=RANDOMSTATE

重定向获取到url路径中的user授权登录授权码code

示例:https://127.0.0.1/?code=5c6hdd2dc53a442a84dd53fa5b8639cb&state=RANDOMSTATE

[图片]

c.获取userAccessToken=app_token+code

[图片]

d.刷新 user_access_token

user_access_token 的最大有效期是 2小时左右。当 user_access_token 过期时,可以调用本接口获取新的 user_access_token。

刷新后请更新本地user_access_token和refresh_token,不要继续使用旧值重复刷新。保证参数是最新值

2.使用user_access_token的解决鉴权调用方法

第一次使用重定向手动进行授权认证得到用户登录授权码,然后根据授权码+app_access_token得到user_access_token,以及refresh_token,后续使用代码逻辑将user_access_token使用refresh_token进行实时刷新,刷新后更新本地user_access_token和refresh_token,不要继续使用旧值重复刷新。保证参数是最新值。(暂不使用该方法)

3.使用tenant_access_token的解决鉴权调用方法

使用tenant access token调用时,请确认应用/机器人拥有部分知识空间的访问权限,否则返回列表容易为空。参阅如何将应用添加为知识库管理员(成员)。

->

通过添加群为知识库管理员(成员)方式(较容易)

在飞书 IM 中创建新群,将应用添加为该群机器人,知识库管理员在「知识空间设置」-> 「权限设置」->「添加管理员」中添加。

创建一个群聊,将各部门知识库负责人拉进群聊,将自建组件应用设置为群聊机器人,将该群聊设置为各知识空间成员即可。(后续协调)

如何将应用添加为知识库管理员(成员)?

添加应用为知识库管理员(成员)当前有两种方式:通过添加群为知识库管理员(成员)方式(较容易)在飞书 IM 中创建新群,将应用添加为该群机器人,知识库管理员在「知识空间设置」-> 「权限设置」->「添加管理员」中添加。通过 API 接口方式(较繁琐)

参考本页 问题2 中将应用添加知识空间成员的方式

[图片]

二.开发流程1.0(递归无数据库)

1.获取各种权限token

2.get知识空间-》空间子节点-》访问

1.先调用这个接口获取知识库下的知识空间的id

https://open.feishu.cn/document/server-docs/docs/wiki-v2/space/list

[图片]

2.然后获取所有的字节点

https://open.feishu.cn/document/server-docs/docs/wiki-v2/space-node/list

[图片]

3.在通过节点token去获取文档信息

https://open.feishu.cn/document/server-docs/docs/wiki-v2/space-node/get_node

https://open.feishu.cn/document/server-docs/docs/faq

4.通过url获取云文档资源(obj_type+obj_token获得)

传递给前端的字段

spaceId+nodeToken(点击查看更多,直接转移到知识库主页)title+objType+objToken (访问url)objCreateTime||objEditTime(时间副标题,根据时间排序)

通过浏览器地址栏获取 token (以下红色部分)(注意: 拷贝时 URL 末尾可能多余的 “#”)

文件夹 folder_token: https://sample.feishu.cn/drive/folder/cSJe2JgtFFBwRuTKAJK6baNGUn0

文件 file_token:https://sample.feishu.cn/file/ndqUw1kpjnGNNaegyqDyoQDCLx1

文档 doc_token:https://sample.feishu.cn/docs/2olt0Ts4Mds7j7iqzdwrqEUnO7q

新版文档 document_id:https://sample.feishu.cn/docx/UXEAd6cRUoj5pexJZr0cdwaFnpd

电子表格 spreadsheet_token:https://sample.feishu.cn/sheets/MRLOWBf6J47ZUjmwYRsN8utLEoY

多维表格 app_token:https://sample.feishu.cn/base/Pc9OpwAV4nLdU7lTy71t6Kmmkoz

知识空间 space_id(知识库管理员打开设置页面):https://sample.feishu.cn/wiki/settings/7075377271827264924

知识库节点 node_token:https://sample.feishu.cn/wiki/sZdeQp3m4nFGzwqR5vx4vZksMoe

其他方法参考:

https://open.feishu.cn/document/server-docs/docs/faq#560bf735

三,测试类及模板

1.鉴权类测试类(a-d获取user_access_token)

a.获取app_access_token

@SpringBootTest

public class GetTenantAndUserAccessTest {

String appId = “cli_a6b06b01f5fa500c”;

String appSecret = “Yx1lpfjEzVuNyj4zSkZotd5mIZI38ePV”;

@Test

/**

获取TenantAccessToken && AppAccessToken的测试方法

*/

public void getTenantAndUserAccessToken01(){

//1. 创建一个RestTemplate对象,用于发送HTTP请求。

RestTemplate restTemplate = new RestTemplate();

String url = “https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal”;

//2.构造请求的URL和请求体。

MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();

requestBody.add(“app_id”, appId);

requestBody.add(“app_secret”, appSecret);

//3.创建一个HttpHeaders对象,设置请求头,包括Content-Type和Authorization等。

HttpHeaders headers = new HttpHeaders();

headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

//headers.setBearerAuth(“<your_access_token>”); // 替换为你的实际访问令牌

//4.创建一个HttpEntity对象,将请求体和请求头封装在一起。

HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(requestBody, headers);

//5.发送HTTP POST请求,并获取响应结果。

ResponseEntity responseEntity = restTemplate.postForEntity(url, requestEntity, String.class);

int statusCode = responseEntity.getStatusCodeValue();

//获取响应体

String responseBody = responseEntity.getBody();

// 处理响应结果

System.out.println("Status Code: " + statusCode);

System.out.println("Response Body: " + responseBody);

//将响应体转化为实体对象(hutool包)

AppAndTenantAccessTokenEntity appAndTenantAccessTokenEntity = JSONUtil.toBean(responseBody, AppAndTenantAccessTokenEntity.class);

System.out.println(appAndTenantAccessTokenEntity.toString());

}

}

b.获取user授权登录授权码

https://open.feishu.cn/document/common-capabilities/sso/api/obtain-oauth-code

[图片]

redirect_uri参数需要在管理后台添加到重定向url列表中,在访问时作为请求参数redirect_uri需要使用UrlEncode工具编码

UrlEnCode工具

http://www.urlencode.com.cn/

[图片]

https://open.feishu.cn/open-apis/authen/v1/authorize?app_id=cli_a6b06b01f5fa500c&redirect_uri=https%3A%2F%2Frwdls.feishu.cn%2Fdrive%2Fhome%2F&scope=bitable:app:readonly%20wiki:wiki&state=RANDOMSTATE

[图片]

默认本地用户登录;也可以其他用户扫码登录

路径参数scope:

用户授予app的权限,格式要求:不同scope由空格分隔,区分大小写的字符串。业务需要根据自己内部场景,自主拼接调用open-api所需的scope。scope详细说明请参考:申请API权限

示例:contact:contact bitable:app:readonly我们这个组件使用的是wiki:wiki访问权限示例:https://open.feishu.cn/open-apis/authen/v1/authorize?app_id=cli_a6b06b01f5fa500c&redirect_uri=https%3A%2F%2F127.0.0.1%2F&scope=wiki:wiki&state=RANDOMSTATE加密前:https://open.feishu.cn/open-apis/authen/v1/authorize?app_id=cli_a6b06b01f5fa500c&redirect_uri=https://127.0.0.1/&scope=wiki:wiki&state=RANDOMSTATE

重定向获取到url路径中的user授权登录授权码code

示例:https://127.0.0.1/?code=5c6hdd2dc53a442a84dd53fa5b8639cb&state=RANDOMSTATE

[图片]

c.获取userAccessToken=app_token+code

[图片]

d.刷新 user_access_token

user_access_token 的最大有效期是 2小时左右。当 user_access_token 过期时,可以调用本接口获取新的 user_access_token。

刷新后请更新本地user_access_token和refresh_token,不要继续使用旧值重复刷新。保证参数是最新值

2.使用user_access_token的解决鉴权调用方法

第一次使用重定向手动进行授权认证得到用户登录授权码,然后根据授权码+app_access_token得到user_access_token,以及refresh_token,后续使用代码逻辑将user_access_token使用refresh_token进行实时刷新,刷新后更新本地user_access_token和refresh_token,不要继续使用旧值重复刷新。保证参数是最新值。(暂不使用该方法)

3.使用tenant_access_token的解决鉴权调用方法

使用tenant access token调用时,请确认应用/机器人拥有部分知识空间的访问权限,否则返回列表容易为空。参阅如何将应用添加为知识库管理员(成员)。

->

通过添加群为知识库管理员(成员)方式(较容易)

在飞书 IM 中创建新群,将应用添加为该群机器人,知识库管理员在「知识空间设置」-> 「权限设置」->「添加管理员」中添加。

创建一个群聊,将各部门知识库负责人拉进群聊,将自建组件应用设置为群聊机器人,将该群聊设置为各知识空间成员即可。(后续协调)

[图片]

4.获取知识库节点列表及信息测试类

@SpringBootTest

public class GetSpaceTest {

String appId = “cli_a6b06b01f5fa500c”;

String appSecret = “Yx1lpfjEzVuNyj4zSkZotd5mIZI38ePV”;

/**

获取知识空间列表方法

*/

@Test

public void getSpacesTest() throws Exception {

// 构建client

Client client = Client.newBuilder(appId, appSecret).build();

// 创建请求对象

ListSpaceReq req=new ListSpaceReq();

// 发起请求

ListSpaceResp resp = client.wiki().space().list(req);

// 处理服务端错误

if(!resp.success()) {

System.out.println(String.format(“code:%s,msg:%s,reqId:%s”, resp.getCode(), resp.getMsg(), resp.getRequestId()));

return;

}

// 业务数据处理

String str = Jsons.DEFAULT.toJson(resp.getData().getItems());//json对象数组字符串

ObjectMapper objectMapper = new ObjectMapper();

try {

// 将JSON对象数组字符串转换为List<Map<String, Object>>对象

List jsonArray = objectMapper.readValue(str, List.class);

// 打印JSON数组

System.out.println(jsonArray);

} catch (Exception e) {

e.printStackTrace();

}

}

/**

获取知识库节点信息方法

*/

@Test

public void getSpaceInfoTest() throws Exception {

// 构建client

Client client = Client.newBuilder(appId, appSecret).build();

// 创建请求对象

GetSpaceReq req = GetSpaceReq.newBuilder()

.spaceId(“7332059900107243522”)

.lang(“en”)

.build();

// 发起请求

GetSpaceResp resp = client.wiki().space().get(req);

// 处理服务端错误

if(!resp.success()) {

System.out.println(String.format(“code:%s,msg:%s,reqId:%s”, resp.getCode(), resp.getMsg(), resp.getRequestId()));

return;

}

// 业务数据处理

System.out.println(Jsons.DEFAULT.toJson(resp.getData()));

}

}

5.知识空间子节点信息列表查询测试类

/**

知识空间子节点信息测试类

*/

@SpringBootTest

public class GetSpaceNodeTest {

String appId = “cli_a6b06b01f5fa500c”;

String appSecret = “Yx1lpfjEzVuNyj4zSkZotd5mIZI38ePV”;

/**

知识空间子节点列表

@throws Exception

*/

@Test

public void GetSpaceNodeList() throws Exception {

// 构建client

Client client = Client.newBuilder(appId, appSecret).build();

// 创建请求对象

ListSpaceNodeReq req = ListSpaceNodeReq.newBuilder()

.spaceId(“7332059900107243522”)

.build();

// 发起请求

ListSpaceNodeResp resp = client.wiki().spaceNode().list(req);

// 处理服务端错误

if(!resp.success()) {

System.out.println(String.format(“code:%s,msg:%s,reqId:%s”, resp.getCode(), resp.getMsg(), resp.getRequestId()));

return;

}

// 业务数据处理

String str = Jsons.DEFAULT.toJson(resp.getData().getItems());//json对象数组字符串

ObjectMapper objectMapper = new ObjectMapper();

List list=new ArrayList<>();

try {

// 将JSON对象数组字符串转换为List对象

list = objectMapper.readValue(str,new TypeReference<List>(){});

// 打印解析后的实体对象列表

System.out.println(list);

} catch (Exception e) {

e.printStackTrace();

}

for (SpaceNodeEntity spaceNodeEntity:list){

System.out.println(“title=”+spaceNodeEntity.getTitle()+" obj_token=“+spaceNodeEntity.getObjToken()+” obj_type="+spaceNodeEntity.getObjType());

}

}

}

四.开发问题

1.开启若依后台使用postman测试

出现

[图片]

解决办法一:

1.在security配置文件SecurityConfig.java中放开对请求的controller控制器地址的鉴权

[图片]

解决办法二:

将若依前端打开,点击一个接口复制Authorization和Cookie放入请求头

[图片]

五.完成代码

1.后端1.0(递归,无存储)

a.controller代码

/**

知识库文档展示飞书小组件开发

@author 赵阿龙

@date 2024-05-09

*/

@RestController

@Slf4j

public class LarkBlockController {

@Autowired

private LarkBlockService larkBlockService;

/**

获取知识空间列表方法

@return List 知识空间列表对象集合

@throws Exception

*/

@GetMapping(“/block/space/list”)

public String getSpaceList() throws Exception {

List spaceEntityList=larkBlockService.getSpaceList();

// return spaceEntityList;

return JSONUtil.toJsonStr(spaceEntityList);

}

/**

获取知识空间节点列表信息方法

@return Object 知识空间节点列表信息json对象

@throws Exception

*/

@GetMapping(“/block/space/node/list”)

public Object getAllNodeInfoBySpaceId() throws Exception {

Object nodeListJson=larkBlockService.getAllNodeInfoBySpaceId();

return nodeListJson;

}

}

b.service接口

@Service

public interface LarkBlockService {

/**

获取知识空间列表方法@return List 知识空间列表对象集合@throws Exception

*/

List getSpaceList() throws Exception;

/**

获取知识空间节点列表信息方法@return Object 知识空间节点列表信息json对象@throws Exception

*/

Object getAllNodeInfoBySpaceId() throws Exception;

}

c.ServiceImpl代码

@Service

@Slf4j

public class LarkBlockServiceImpl implements LarkBlockService {

/**

获取知识空间列表方法

@return List 知识空间列表对象集合

@throws Exception

*/

@Override

public List getSpaceList() throws Exception {

// 构建client

Client client = Client.newBuilder(UserInfo.APP_ID, UserInfo.APP_SECRET).build();

// 创建请求对象

ListSpaceReq req=new ListSpaceReq();

// 发起请求

ListSpaceResp resp = client.wiki().space().list(req);

// 处理服务端错误

if(!resp.success()) {

System.out.println(String.format(“code:%s,msg:%s,reqId:%s”, resp.getCode(), resp.getMsg(), resp.getRequestId()));

return null;

}

// 业务数据处理

String jsonStr = Jsons.DEFAULT.toJson(resp.getData().getItems());//json对象数组字符串

ObjectMapper objectMapper = new ObjectMapper();

List spaceEntityList=new ArrayList<>();

try {

// 将JSON对象数组字符串转换为List<Map<String, Object>>对象

spaceEntityList = objectMapper.readValue(jsonStr, List.class);

//将所有知识空间id,放到spaceIdList集合中

// for (SpaceEntity spaceEntity:spaceEntityList){

// SpaceIdListDto temp=new SpaceIdListDto(spaceEntity.getSpaceId(),spaceEntity.getName());

// spaceIdList.add(temp);

// }

// 打印JSON数组

System.err.println("知识空间列表: "+spaceEntityList);code>

} catch (Exception e) {

e.printStackTrace();

}

//返回知识空间列表json数组字符串

return spaceEntityList;

}

/**

获取知识空间节点列表信息方法

@return String 知识空间节点列表信息json对象

@throws Exception

*/

@Override

public Object getAllNodeInfoBySpaceId() throws Exception {

//获取知识空间列表

List records = getSpaceList();

//使用ObjectMapper解决

//创建一个ObjectMapper

ObjectMapper mapper = new ObjectMapper();

//SpaceEntity就是需要的类型对象

List spaceEntityList= mapper.convertValue(records, new TypeReference<List>() {});

// Map<String,List> map=new HashMap<>();

for(SpaceEntity spaceEntity : spaceEntityList){

//存储知识空间列表所有节点及子节点

List<SpaceNodeEntity> spaceNodeEntityList=new ArrayList<>();

// 构建client

Client client = Client.newBuilder(UserInfo.APP_ID, UserInfo.APP_SECRET).build();

// 创建请求对象

ListSpaceNodeReq req = ListSpaceNodeReq.newBuilder()

.spaceId(spaceEntity.getSpaceId())

.parentNodeToken("")

.build();

// 发起请求

ListSpaceNodeResp resp = client.wiki().spaceNode().list(req);

// 处理服务端错误

if(!resp.success()) {

System.out.println(String.format("code:%s,msg:%s,reqId:%s", resp.getCode(), resp.getMsg(), resp.getRequestId()));

return null;

}

// 业务数据处理

String str = Jsons.DEFAULT.toJson(resp.getData().getItems());//json对象数组字符串

ObjectMapper objectMapper = new ObjectMapper();

List<SpaceNodeEntity> list=new ArrayList<>();

try {

// 将JSON对象数组字符串转换为List<SpaceNodeEntity>对象

list = objectMapper.readValue(str,new TypeReference<List<SpaceNodeEntity>>(){});

} catch (Exception e) {

e.printStackTrace();

}

for (SpaceNodeEntity spaceNode:list){

//遍历+递归田间知识空间节点。

spaceNodeEntityList.add(spaceNode);

if(spaceNode.isHasChild()){

//递归获取所有知识空间文档

searchAllNodes(spaceNodeEntityList,spaceNode.getNodeToken(),spaceNode);

}

}

//按照创建时间对所有知识空间节点进行从大到小排序

Collections.sort(spaceNodeEntityList, (a, b) -> b.getObjCreateTime().compareTo(a.getObjCreateTime()));

//对排序后的结果进行截取,获得最近更新的前十条数据返回json对象

spaceNodeEntityList = spaceNodeEntityList.subList(0, 10);

//将排序后的10条节点数据加入知识空间

spaceEntity.setNodeData(spaceNodeEntityList);

// //根据知识空间id设定map

// map.put(spaceEntity.getSpaceId(),JSONUtil.toJsonStr(spaceNodeEntityList));

// //根据知识空间name设定map

// map.put(spaceEntity.getName(),spaceNodeEntityList);

System.err.println("spaceEntity.getName()知识库节点列表: "+JSONUtil.toJsonStr(spaceNodeEntityList));

}

//数据处理

Map<String,List> resMap=new HashMap<>();

resMap.put(“spaceData”,spaceEntityList);

return JSONUtil.parse(JSONUtil.toJsonStr(resMap));

}

/**

*

* @param spaceNodeEntityList 知识空间列表所有节点及子节点集合

* @param parentNodeToken 父节点token

* @param spaceNodeEntity 节点

* @throws Exception

*/

@CountTime

public void searchAllNodes(List<SpaceNodeEntity> spaceNodeEntityList, String parentNodeToken, SpaceNodeEntity spaceNodeEntity) throws Exception {

// 构建client

Client client = Client.newBuilder(UserInfo.APP_ID, UserInfo.APP_SECRET).build();

// 创建请求对象

ListSpaceNodeReq req = ListSpaceNodeReq.newBuilder()

.spaceId(spaceNodeEntity.getSpaceId())//获取每一个知识空间下的所有node

.parentNodeToken(parentNodeToken)

.build();

// 发起请求

ListSpaceNodeResp resp = client.wiki().spaceNode().list(req);

// 处理服务端错误

if(!resp.success()) {

System.out.println(String.format("code:%s,msg:%s,reqId:%s", resp.getCode(), resp.getMsg(), resp.getRequestId()));

return ;

}

// 业务数据处理

String str = Jsons.DEFAULT.toJson(resp.getData().getItems());//json对象数组字符串

ObjectMapper objectMapper = new ObjectMapper();

List<SpaceNodeEntity> list=new ArrayList<>();

try {

// 将JSON对象数组字符串转换为List<SpaceNodeEntity>对象

list = objectMapper.readValue(str,new TypeReference<List<SpaceNodeEntity>>(){});

} catch (Exception e) {

e.printStackTrace();

}

for(SpaceNodeEntity spaceNode:list){

//遍历+递归田间知识空间节点。

spaceNodeEntityList.add(spaceNode);

if(spaceNode.isHasChild()){

//判断该节点是否有子节点,若有递归加入集合中

searchAllNodes(spaceNodeEntityList,spaceNode.getNodeToken(),spaceNode);

}

}

// System.out.println(1);

}

}

2.后端2.0(加数据库+目录)

LarkBlockAppController

/**

知识库文档展示飞书小组件开发

@author 赵阿龙

@date 2024-05-09

*/

@RestController

@Slf4j

public class LarkBlockAppController {

@Autowired

private LarkBlockAppService larkBlockAppService;

@Autowired

private SpaceEntityMapper spaceEntityMapper;

@Autowired

private SpaceNodeEntityMapper spaceNodeEntityMapper;

/**

获取知识空间列表方法

@return List 知识空间列表对象集合

@throws Exception

*/

@GetMapping(“/app/block/space/list”)

public String getSpaceList() throws Exception {

List spaceEntityList=larkBlockAppService.getSpaceList();

// return spaceEntityList;

return JSONUtil.toJsonStr(spaceEntityList);

}

/**

获取知识空间节点列表信息方法

@return Object 知识空间节点列表信息json对象

@throws Exception

*/

@GetMapping(“/app/block/space/node/list”)

public Object getAllNodeInfoBySpaceId() throws Exception {

Object nodeListJson=larkBlockAppService.getAllNodeInfoBySpaceId();

return nodeListJson;

}

}

LarkBlockAppServiceImpl

/**

知识库文档展示飞书小组件开发

@author 赵阿龙

@date 2024-05-09

/

@Service

@Slf4j

public class LarkBlockAppServiceImpl implements LarkBlockAppService {

@Autowired

private SpaceEntityMapper spaceEntityMapper;

@Autowired

private SpaceNodeEntityMapper spaceNodeEntityMapper;

/*

获取知识空间列表方法

@return List 知识空间列表对象集合

@throws Exception

*/

@Override

public List getSpaceList() throws Exception {

//1.先从数据库查询直属库列表数据

//1.1 若有,返回数据库中的数据

List spaceEntityListBySql=spaceEntityMapper.selectAll();

System.out.println(spaceEntityListBySql);

if(spaceEntityListBySql.size()>0){

return spaceEntityListBySql;

}

//1.2 若没有,执行如下操作。

// 构建client

Client client = Client.newBuilder(UserInfo.APP_ID, UserInfo.APP_SECRET).build();

System.out.println(“=====================================”);

// 创建请求对象

ListSpaceReq req=new ListSpaceReq();

// 发起请求

ListSpaceResp resp = client.wiki().space().list(req);

// 处理服务端错误

if(!resp.success()) {

System.out.println(String.format(“code:%s,msg:%s,reqId:%s”, resp.getCode(), resp.getMsg(), resp.getRequestId()));

return null;

}

// 业务数据处理

String jsonStr = Jsons.DEFAULT.toJson(resp.getData().getItems());//json对象数组字符串

ObjectMapper objectMapper = new ObjectMapper();

List records=new ArrayList<>();

try {

// 将JSON对象数组字符串转换为List<Map<String, Object>>对象

records = objectMapper.readValue(jsonStr, List.class);

// 打印JSON数组

System.err.println("知识空间列表: "+records);

} catch (Exception e) {

e.printStackTrace();

}

//使用ObjectMapper解决

//创建一个ObjectMapper

ObjectMapper mapper = new ObjectMapper();

//SpaceEntity就是需要的类型对象

List spaceEntityList= mapper.convertValue(records, new TypeReference<List>() {});

//批量插入知识空间列表

spaceEntityMapper.batchInsert(spaceEntityList);

//返回知识空间列表json数组字符串

return spaceEntityList;

}

/**

获取知识空间节点列表信息方法

@return String 知识空间节点列表信息json对象

@throws Exception

*/

@Override

public Object getAllNodeInfoBySpaceId() throws Exception {

//获取知识空间列表

List spaceEntityList = getSpaceList();

//查询数据库中的所有节点

//1.1 如果查询到节点(返回数据库中的节点)

List nodeEntityList=spaceNodeEntityMapper.selectAll();

if(nodeEntityList!=null && nodeEntityList.size()>0){

//遍历知识空间查询数据库中该知识空间下的最新的十条节点

for (SpaceEntity se:spaceEntityList){

List spaceNodeEntityList = spaceNodeEntityMapper.selectNodesById(se.getSpaceId());

if(spaceNodeEntityList==null || spaceNodeEntityList.size()<=0){

//如果没有查询到节点,查询下面的接口并入库

break;

}

//按照创建时间对所有知识空间节点进行从大到小排序

Collections.sort(spaceNodeEntityList, (a, b) -> b.getObjCreateTime().compareTo(a.getObjCreateTime()));

se.setNodeData(spaceNodeEntityList);

}

//数据处理

//将数据存放在map集合中

Map<String,List<SpaceEntity>> resMap=new HashMap<>();

resMap.put("spaceData",spaceEntityList);

return JSONUtil.parse(JSONUtil.toJsonStr(resMap));

}

//1.2 如果没有查询到,执行下面查询操作

for(SpaceEntity spaceEntity : spaceEntityList){

//存储知识空间列表所有节点及子节点

List<SpaceNodeEntity> spaceNodeEntityList=new ArrayList<>();

// 构建client

Client client = Client.newBuilder(UserInfo.APP_ID, UserInfo.APP_SECRET).build();

// 创建请求对象

ListSpaceNodeReq req = ListSpaceNodeReq.newBuilder()

.spaceId(spaceEntity.getSpaceId())

.parentNodeToken("")

.build();

// 发起请求

ListSpaceNodeResp resp = client.wiki().spaceNode().list(req);

// 处理服务端错误

if(!resp.success()) {

System.out.println(String.format("code:%s,msg:%s,reqId:%s", resp.getCode(), resp.getMsg(), resp.getRequestId()));

return null;

}

// 业务数据处理

String str = Jsons.DEFAULT.toJson(resp.getData().getItems());//json对象数组字符串

ObjectMapper objectMapper = new ObjectMapper();

List<SpaceNodeEntity> list=new ArrayList<>();

try {

// 将JSON对象数组字符串转换为List<SpaceNodeEntity>对象

list = objectMapper.readValue(str,new TypeReference<List<SpaceNodeEntity>>(){});

} catch (Exception e) {

e.printStackTrace();

}

for (SpaceNodeEntity spaceNode:list){

//设置当前节点的目录

spaceNode.setCatalogue(""+spaceEntity.getName());

//遍历+递归田间知识空间节点。

spaceNodeEntityList.add(spaceNode);

if(spaceNode.isHasChild()){

//递归获取所有知识空间文档

searchAllNodes(spaceNodeEntityList,spaceNode.getNodeToken(),spaceNode);

}

}

//按照创建时间对所有知识空间节点进行从大到小排序

Collections.sort(spaceNodeEntityList, (a, b) -> b.getObjCreateTime().compareTo(a.getObjCreateTime()));

//对排序后的结果进行截取,获得最近更新的前十条数据返回json对象

spaceNodeEntityList = spaceNodeEntityList.subList(0, 10);

//将知识空间节点实体中的时间戳转化为时间

for(SpaceNodeEntity s:spaceNodeEntityList){

s.setObjCreateTime(convertTime(s.getObjCreateTime()));

s.setNodeCreateTime(convertTime(s.getNodeCreateTime()));

s.setObjEditTime(convertTime(s.getObjEditTime()));

}

//批量将最新的十条数据入库

spaceNodeEntityMapper.batchInsert(spaceNodeEntityList);

//将排序后的10条节点数据加入知识空间

spaceEntity.setNodeData(spaceNodeEntityList);

System.err.println(spaceEntity.getName()+"知识库节点列表: "+JSONUtil.toJsonStr(spaceNodeEntityList));

}

//数据处理

//将数据存放在map集合中

Map<String,List> resMap=new HashMap<>();

resMap.put(“spaceData”,spaceEntityList);

return JSONUtil.parse(JSONUtil.toJsonStr(resMap));

}

/**

*

@param spaceNodeEntityList 知识空间列表所有节点及子节点集合

@param parentNodeToken 父节点token

@param spaceNodeEntity 节点

@throws Exception

*/

@CountTime

public void searchAllNodes(List spaceNodeEntityList, String parentNodeToken, SpaceNodeEntity spaceNodeEntity) throws Exception {

// Thread.sleep(1000);

// 构建client

Client client = Client.newBuilder(UserInfo.APP_ID, UserInfo.APP_SECRET).build();

// 创建请求对象

ListSpaceNodeReq req = ListSpaceNodeReq.newBuilder()

.spaceId(spaceNodeEntity.getSpaceId())//获取每一个知识空间下的所有node

.parentNodeToken(parentNodeToken)

.build();

// 发起请求

ListSpaceNodeResp resp = client.wiki().spaceNode().list(req);

// 处理服务端错误

if(!resp.success()) {

System.out.println(String.format(“code:%s,msg:%s,reqId:%s”, resp.getCode(), resp.getMsg(), resp.getRequestId()));

return ;

}

// 业务数据处理

String str = Jsons.DEFAULT.toJson(resp.getData().getItems());//json对象数组字符串

ObjectMapper objectMapper = new ObjectMapper();

List list=new ArrayList<>();

try {

// 将JSON对象数组字符串转换为List对象

list = objectMapper.readValue(str,new TypeReference<List>(){});

} catch (Exception e) {

e.printStackTrace();

}

for(SpaceNodeEntity spaceNode:list){

//设置当前节点的目录

spaceNode.setCatalogue(spaceNodeEntity.getCatalogue()+" > "+spaceNodeEntity.getTitle());

//遍历+递归田间知识空间节点。

spaceNodeEntityList.add(spaceNode);

if(spaceNode.isHasChild()){

//判断该节点是否有子节点,若有递归加入集合中

searchAllNodes(spaceNodeEntityList,spaceNode.getNodeToken(),spaceNode);

}

}

// System.out.println(1);

}

public String convertTime(String createTime){

// 假设我们有一个时间戳,单位是毫秒

long timestamp = Long.parseLong(createTime)*1000L; // 这是一个示例时间戳

// 创建一个Date对象,其时间等于时间戳表示的时间

Date date = new Date(timestamp);

// 创建一个SimpleDateFormat对象来定义日期时间的格式

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

// 使用SimpleDateFormat对象将Date对象转换为字符串

String formattedDate = sdf.format(date);

// 打印转换后的日期时间

// System.out.println(formattedDate);

return formattedDate;

}

}

SpaceEntityMapper

@Mapper

public interface SpaceEntityMapper extends BaseMapper {

/**

* 查询所有知识空间列表

* @return

*/

@Select("SELECT * FROM t_space")

List<SpaceEntity> selectAll();

/**

* 插入数据

* @param spaceEntity

* @return

*/

// @Insert(“INSERT INTO t_space (name, description, space_id, space_type, visibility) VALUES(#{name}, #{description}, #{spaceId}, #{spaceType}, #{visibility})”)

int insert(SpaceEntity spaceEntity);

/**

* 批量插入数据

* @param spaceEntityList

*/

int batchInsert(@Param("list")List<SpaceEntity> spaceEntityList);

}

SpaceNodeEntityMapper

@Mapper

public interface SpaceNodeEntityMapper {

/**

* 查询所有知识空间节点列表

* @return

*/

@Select("SELECT * FROM t_spacenode")

List<SpaceNodeEntity> selectAll();

/**

* 根据知识空间id查询所有该知识空间节点列表

* @return

*/

@Select("SELECT * FROM t_spacenode where space_id = #{spaceId}")

List<SpaceNodeEntity> selectNodesById(String spaceId);

/**

* 插入数据

* @param spaceNodeEntity

*/

@Insert("INSERT INTO t_spacenode (space_id, node_token, obj_token, obj_type, parent_node_token, node_type, " +

"origin_node_token, origin_space_id, has_child, title, obj_create_time, obj_edit_time, node_create_time, " +

"creator, owner) " +

"VALUES (#{spaceId}, #{nodeToken}, #{objToken}, #{objType}, #{parentNodeToken}, #{nodeType}, " +

"#{originNodeToken}, #{originSpaceId}, #{hasChild}, #{title}, #{objCreateTime}, #{objEditTime}, #{nodeCreateTime}, " +

"#{creator}, #{owner})")

void insert(SpaceNodeEntity spaceNodeEntity);

}

a.流程逻辑

https://rwdls.feishu.cn/sync/SaBMdOHnvs96EObef4ycIaYunuI

3.前端1.0(后面可能也不用改)

a.onShow,生命周期渲染页面

onShow() {

// Block 显示

const that=this;

//查询知识空间列表

// const requestTask = tt.request({

// “url”: “http://localhost:8080/block/space/list”,

// “data”: {

// “noncestr”: Date.now()

// },

// “header”: {

// “content-type”: “application/json”

// },

// “method”: “GET”,

// “dataType”: “json”,

// “responseType”: “text”,

// success(res) {

// console.log(JSON.stringify(res.data));

// info: JSON.stringify(res.data);

// that.setData({

// // spaceList: JSON.stringify(res.data , null, 2),

// // spaceList: JSON.stringify(res.data),

// spaceList: JSON.parse(JSON.stringify(res.data)),

// });

// },

// fail(res) {

// console.log(request fail: ${JSON.stringify(res.data)});

// }

// });

//查询知识空间所有节点列表

const requestTask = tt.request({

“url”: “http://localhost:8080/app/block/space/node/list”,

“data”: {

“noncestr”: Date.now()

},

“header”: {

“content-type”: “application/json”

},

“method”: “GET”,

“dataType”: “json”,

“responseType”: “text”,

success(res) {

console.log(JSON.stringify(res.data));

info: JSON.stringify(res.data);

that.setData({

// spaceAllData: JSON.stringify(res.data , null, 2),

// spaceAllData: JSON.stringify(res.data),

spaceAllData: JSON.parse(JSON.stringify(res.data)),

[‘boolArray[’ + 0 + ‘]’]: true

});

},

fail(res) {

console.log(request fail: ${JSON.stringify(res.data)});

}

});

},

b.js文件methods所有方法

methods: {

//选择是否渲染该知识空间中的最新节点

chose: function(e) {

// 通过 dataset 获取 data- 属性中设置的参数

const index = e.currentTarget.dataset.index;

console.log("参数 index: ", index);

// 处理逻辑

// 使用 setData 更新数组中对应索引的值

this.setData({

boolArray: Array(1000).fill(false),

['boolArray[' + index + ']']: true

});

console.log("参数 index: ", index);

},

//跳转链接函数

toHttp: function(e){

// 处理逻辑

// 使用 param1 更新链接

const param1 = e.currentTarget.dataset.param1;

const param2 = e.currentTarget.dataset.param2;

// 拼接 URL

const url = 'https://sample.feishu.cn/'+param1+'/' + param2;

console.log('openSchema 调用成功02', url);

tt.openSchema({

schema: url,

success (e) {

console.log('openSchema 调用成功', e.errMsg);

},

fail (e) {

console.log('openSchema 调用失败', e.errMsg);

},

complete (e) {

console.log('openSchema 调用结束', e.errMsg);

}

});

},

//跳转查看更多链接函数

toMoreHttp: function(e){

// 处理逻辑

// 使用 param 更新链接url

const param = e.currentTarget.dataset.param;

// 拼接 URL

const url = 'https://sample.feishu.cn/wiki/'+param;

console.log('openSchema 调用成功02', url);

tt.openSchema({

schema: url,

success (e) {

console.log('openSchema 调用成功', e.errMsg);

},

fail (e) {

console.log('openSchema 调用失败', e.errMsg);

},

complete (e) {

console.log('openSchema 调用结束', e.errMsg);

}

});

},

},

});

c.ttml

知识库

{ {item.name}}

{ {itemNode.title}}

{ {item.description}}

{ {itemNode.objCreateTime}}

</view>

d,ttss @import './common/button.ttss';

.block {

box-sizing: border-box;

padding: 8px 16px;

display: flex;

flex-direction: column;

}

.block-title {

margin-bottom: 10px;

flex-shrink: 0;

}

.btn-style {

width: 110px; /* 或者您希望的宽度 /

height: 40px; / 或者您希望的高度 /

background-color: rgb(158, 158, 216); / 设置背景为蓝色 /

color: white; / 设置文字颜色为白色,以便在蓝色背景上可见 /

border-radius: 5px; / 可选,给按钮边缘添加圆角 /

padding: 0; / 移除内边距,因为我们使用flexbox来控制子元素位置 /

font-size: 14px; / 可选,调整文字大小 /

display: flex; / 使用flex布局 /

justify-content: center; / 水平居中子元素 /

align-items: center; / 垂直居中子元素 /

/ 如果需要,可以移除下面这行,因为默认情况下,按钮不会有边框 /

border: none; / 移除边框,如果有的话 /

/ 如果需要,可以添加过渡效果或其他样式 */

}

.spaceName {

font-size: 13px;

}

.menu-container {

display: flex;

overflow-x: auto;

white-space: nowrap;

}

.menu-item {

display: inline-block;

margin-right: 1px; /* 间隔 */

}

.data-container {

padding-top: 5px; /* 可以根据需要设置与菜单的间隔 /

}

/ 样式化整个盒子 /

.box {

border: 1px solid #ccc; / 边框样式,根据需要自定义 /

padding: 10px;

position: relative; / 设置相对位置,以便时间戳可以绝对定位 /

box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); / 盒子阴影,可选 /

border-radius: 5px; / 可选,给按钮边缘添加圆角 /

/ 如果需要,可以移除下面这行,因为默认情况下,按钮不会有边框 /

border: none; / 移除边框,如果有的话 */

background-color: #f5f5f5; /* 举例,这是一个浅灰色背景 /

transition: background-color 0.3s ease; / 平滑过渡效果 */

}

.box:hover {

background-color: #caccf0; /* 鼠标悬停时的背景色,举例,这是一个稍深的灰色 */

}

/* 标题样式 /

.title {

font-size: 1.3em; / 增大字号 /

font-weight: bold; / 加粗 /

margin-bottom: 5px; / 标题下边距 */

font-style:initial;

}

/* 内容样式,根据需要自定义 /

.content {

/ 自定义样式 */

font-size: 12px;

}

/* 时间戳样式 /

.timestamp {

font-size: 0.9em; / 缩小字体 /

position: absolute;

bottom: 10px;

right: 10px; / 将时间戳定位到右下角 /

}

.box-link {

color: inherit; / 保持原有文本颜色 /

text-decoration: none; / 去除下划线 /

display: block; / 将a标签设置为块级元素,以使整个区域可点击 /

width: 100%; / 确保链接占据整个.box的宽度 /

height: 100%; / 确保链接占据整个.box的高度 */

}

/* 如果你想要点击效果,可以添加以下样式 /

.box-link:active .box {

background-color: #c7e0f3; / 轻轻按下时的背景色变化 */

}

/* CSS样式 */

.more-link-container {

position: fixed;

bottom: 10px;

left: 10px;

}

.more-link {

color: #ffffff; /* 白色文字 /

background-color: #0000ff; / 蓝色背景 /

padding: 10px 20px;

text-decoration: none; / 去除下划线 /

border-radius: 5px; / 圆角边框 */

font-size: 16px;

}

.more-link:hover {

background-color: #00008b; /* 鼠标悬停时的背景色深蓝色 */

}

4.前端2.0(增加点击按钮刷新逻辑)

js文件中的methods

methods: {

//选择是否渲染该知识空间中的最新节点

chose: function(e) {

// requestTask;

// 通过 dataset 获取 data- 属性中设置的参数

const index = e.currentTarget.dataset.index;

console.log("参数 index: ", index);

// 处理逻辑

// 使用 setData 更新数组中对应索引的值

this.setData({

boolArray: Array(1000).fill(false),

['boolArray[' + index + ']']: true

});

console.log("参数 index: ", index);

tt.request({

"url": "http://localhost:8080/app/block/space/node/list",

"data": {

"noncestr": Date.now()

},

"header": {

"content-type": "application/json"

},

"method": "GET",

"dataType": "json",

"responseType": "text",

success(res) {

console.log(JSON.stringify(res.data));

info: JSON.stringify(res.data);

that.setData({

spaceAllData: JSON.parse(JSON.stringify(res.data)),

['boolArray[' + 0 + ']']: true

});

},

fail(res) {

console.log(`request fail: ${JSON.stringify(res.data)}`);

}

});

},

六,创建数据库

数据库表结构

t_space

CREATE TABLE t_space (

space_id varchar(255) NOT NULL COMMENT ‘主键 , 知识空间id’,

name varchar(255) DEFAULT NULL,

description varchar(255) DEFAULT NULL,

space_type varchar(255) DEFAULT NULL,

visibility varchar(255) DEFAULT NULL,

PRIMARY KEY (space_id)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

[图片]

t_spacenode

CREATE TABLE t_spacenode (

node_token varchar(255) NOT NULL,

space_id varchar(255) NOT NULL,

obj_token varchar(255) DEFAULT NULL,

obj_type varchar(255) DEFAULT NULL,

parent_node_token varchar(255) DEFAULT NULL,

node_type varchar(255) DEFAULT NULL,

origin_node_token varchar(255) DEFAULT NULL,

origin_space_id varchar(255) DEFAULT NULL,

has_child tinyint(1) DEFAULT NULL,

title varchar(255) DEFAULT NULL,

obj_create_time datetime DEFAULT NULL,

obj_edit_time datetime DEFAULT NULL,

node_create_time datetime DEFAULT NULL,

creator varchar(255) DEFAULT NULL,

owner varchar(255) DEFAULT NULL,

PRIMARY KEY (node_token) USING BTREE

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

[图片]

七,定时任务逻辑

https://rwdls.feishu.cn/sync/SaBMdOHnvs96EObef4ycIaYunuI

暂时无法在飞书文档外展示此内容

LarkBlock.java //定时任务逻辑代码

@Component

public class LarkBlock{

@Autowired

private SpaceEntityMapper spaceEntityMapper;

@Autowired

private SpaceNodeEntityMapper spaceNodeEntityMapper;

@Autowired

private LarkBlockAppService larkBlockAppService;

@XxlJob("TEST")

public void test() {

System.out.println(111);

}

@XxlJob("updateMysql")

public void updateMysql() throws Exception {

//刷新数据

//1.1刷新知识库列表

List<SpaceEntity> spaceEntityList = refreshSpaceList();

//1.2刷新知识库节点列表

List<SpaceNodeEntity> spaceNodeEntityList = refreshSpaceNodeList();

//2.先清空数据库

spaceEntityMapper.deleteAll();

spaceNodeEntityMapper.deleteAll();

//批量插入知识空间列表

spaceEntityMapper.batchInsert(spaceEntityList);

//批量插入知识空间节点列表

spaceNodeEntityMapper.batchInsert(spaceNodeEntityList);

}

/**

* 刷新知识库列表

* @return

*/

public List<SpaceEntity> refreshSpaceList() throws Exception {

// 构建client

Client client = Client.newBuilder(UserInfo.APP_ID, UserInfo.APP_SECRET).build();

System.out.println("=====================================");

// 创建请求对象

ListSpaceReq req=new ListSpaceReq();

// 发起请求

ListSpaceResp resp = client.wiki().space().list(req);

// 处理服务端错误

if(!resp.success()) {

System.out.println(String.format("code:%s,msg:%s,reqId:%s", resp.getCode(), resp.getMsg(), resp.getRequestId()));

return null;

}

// 业务数据处理

String jsonStr = Jsons.DEFAULT.toJson(resp.getData().getItems());//json对象数组字符串

ObjectMapper objectMapper = new ObjectMapper();

List<SpaceEntity> records=new ArrayList<>();

try {

// 将JSON对象数组字符串转换为List<Map<String, Object>>对象

records = objectMapper.readValue(jsonStr, List.class);

// 打印JSON数组

System.err.println("知识空间列表: "+records);

} catch (Exception e) {

e.printStackTrace();

}

//使用ObjectMapper解决

//创建一个ObjectMapper

ObjectMapper mapper = new ObjectMapper();

//SpaceEntity就是需要的类型对象

List<SpaceEntity> spaceEntityList= mapper.convertValue(records, new TypeReference<List<SpaceEntity>>() {});

//返回知识空间列表json数组字符串

return spaceEntityList;

}

/**

* 刷新知识库节点列表

* @return

*/

public List<SpaceNodeEntity> refreshSpaceNodeList() throws Exception {

//获取知识空间列表

List<SpaceEntity> spaceEntityList = refreshSpaceList();

//1.2 如果没有查询到,执行下面查询操作

//存储该知识空间列表所有节点及子节点

List<SpaceNodeEntity> spaceAllNodeEntityList=new ArrayList<>();

for(SpaceEntity spaceEntity : spaceEntityList){

//存储该知识空间列表所有节点及子节点

List<SpaceNodeEntity> spaceNodeEntityList=new ArrayList<>();

// 构建client

Client client = Client.newBuilder(UserInfo.APP_ID, UserInfo.APP_SECRET).build();

// 创建请求对象

ListSpaceNodeReq req = ListSpaceNodeReq.newBuilder()

.spaceId(spaceEntity.getSpaceId())

.parentNodeToken("")

.build();

// 发起请求

ListSpaceNodeResp resp = client.wiki().spaceNode().list(req);

// 处理服务端错误

if(!resp.success()) {

System.out.println(String.format("code:%s,msg:%s,reqId:%s", resp.getCode(), resp.getMsg(), resp.getRequestId()));

return null;

}

// 业务数据处理

String str = Jsons.DEFAULT.toJson(resp.getData().getItems());//json对象数组字符串

ObjectMapper objectMapper = new ObjectMapper();

List<SpaceNodeEntity> list=new ArrayList<>();

try {

// 将JSON对象数组字符串转换为List<SpaceNodeEntity>对象

list = objectMapper.readValue(str,new TypeReference<List<SpaceNodeEntity>>(){});

} catch (Exception e) {

e.printStackTrace();

}

for (SpaceNodeEntity spaceNode:list){

//遍历+递归田间知识空间节点。

spaceNodeEntityList.add(spaceNode);

if(spaceNode.isHasChild()){

//递归获取所有知识空间文档

searchAllNodes(spaceNodeEntityList,spaceNode.getNodeToken(),spaceNode);

}

}

//按照创建时间对所有知识空间节点进行从大到小排序

Collections.sort(spaceNodeEntityList, (a, b) -> b.getObjCreateTime().compareTo(a.getObjCreateTime()));

//对排序后的结果进行截取,获得最近更新的前十条数据返回json对象

spaceNodeEntityList = spaceNodeEntityList.subList(0, 10);

System.out.println(spaceEntity.getName()+"的知识库节点"+spaceNodeEntityList);

//将知识空间节点实体中的时间戳转化为时间

for(SpaceNodeEntity s:spaceNodeEntityList){

s.setObjCreateTime(convertTime(s.getObjCreateTime()));

s.setNodeCreateTime(convertTime(s.getNodeCreateTime()));

s.setObjEditTime(convertTime(s.getObjEditTime()));

spaceAllNodeEntityList.add(s);

}

}

return spaceAllNodeEntityList;

}

/**

*

* @param spaceNodeEntityList 知识空间列表所有节点及子节点集合

* @param parentNodeToken 父节点token

* @param spaceNodeEntity 节点

* @throws Exception

*/

@CountTime

public void searchAllNodes(List<SpaceNodeEntity> spaceNodeEntityList, String parentNodeToken, SpaceNodeEntity spaceNodeEntity) throws Exception {

// Thread.sleep(1000);

// 构建client

Client client = Client.newBuilder(UserInfo.APP_ID, UserInfo.APP_SECRET).build();

// 创建请求对象

ListSpaceNodeReq req = ListSpaceNodeReq.newBuilder()

.spaceId(spaceNodeEntity.getSpaceId())//获取每一个知识空间下的所有node

.parentNodeToken(parentNodeToken)

.build();

// 发起请求

ListSpaceNodeResp resp = client.wiki().spaceNode().list(req);

// 处理服务端错误

if(!resp.success()) {

System.out.println(String.format("code:%s,msg:%s,reqId:%s", resp.getCode(), resp.getMsg(), resp.getRequestId()));

return ;

}

// 业务数据处理

String str = Jsons.DEFAULT.toJson(resp.getData().getItems());//json对象数组字符串

ObjectMapper objectMapper = new ObjectMapper();

List<SpaceNodeEntity> list=new ArrayList<>();

try {

// 将JSON对象数组字符串转换为List<SpaceNodeEntity>对象

list = objectMapper.readValue(str,new TypeReference<List<SpaceNodeEntity>>(){});

} catch (Exception e) {

e.printStackTrace();

}

for(SpaceNodeEntity spaceNode:list){

//遍历+递归田间知识空间节点。

spaceNodeEntityList.add(spaceNode);

if(spaceNode.isHasChild()){

//判断该节点是否有子节点,若有递归加入集合中

searchAllNodes(spaceNodeEntityList,spaceNode.getNodeToken(),spaceNode);

}

}

// System.out.println(1);

}

public String convertTime(String createTime){

// 假设我们有一个时间戳,单位是毫秒

long timestamp = Long.parseLong(createTime)*1000L; // 这是一个示例时间戳

// 创建一个Date对象,其时间等于时间戳表示的时间

Date date = new Date(timestamp);

// 创建一个SimpleDateFormat对象来定义日期时间的格式

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

// 使用SimpleDateFormat对象将Date对象转换为字符串

String formattedDate = sdf.format(date);

// 打印转换后的日期时间

// System.out.println(formattedDate);

return formattedDate;

}

}

[图片]

后续改成一小时一次。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。