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

打印 上一主题 下一主题

主题 1014|帖子 1014|积分 3042

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
一.鉴权知识(选取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);
      1. // 打印JSON数组
      2. 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);
      // }
      1. // 打印JSON数组
      2. System.err.println("知识空间列表: "+spaceEntityList);
      复制代码
      } 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<>();
  1.     for(SpaceEntity spaceEntity : spaceEntityList){
  2.         //存储知识空间列表所有节点及子节点
  3.         List<SpaceNodeEntity> spaceNodeEntityList=new ArrayList<>();
  4.         // 构建client
  5.         Client client = Client.newBuilder(UserInfo.APP_ID, UserInfo.APP_SECRET).build();
  6.         // 创建请求对象
  7.         ListSpaceNodeReq req = ListSpaceNodeReq.newBuilder()
  8.                 .spaceId(spaceEntity.getSpaceId())
  9.                 .parentNodeToken("")
  10.                 .build();
  11.         // 发起请求
  12.         ListSpaceNodeResp resp = client.wiki().spaceNode().list(req);
  13.         // 处理服务端错误
  14.         if(!resp.success()) {
  15.             System.out.println(String.format("code:%s,msg:%s,reqId:%s", resp.getCode(), resp.getMsg(), resp.getRequestId()));
  16.             return null;
  17.         }
  18.         // 业务数据处理
  19.         String str = Jsons.DEFAULT.toJson(resp.getData().getItems());//json对象数组字符串
  20.         ObjectMapper objectMapper = new ObjectMapper();
  21.         List<SpaceNodeEntity> list=new ArrayList<>();
  22.         try {
  23.             // 将JSON对象数组字符串转换为List<SpaceNodeEntity>对象
  24.             list = objectMapper.readValue(str,new TypeReference<List<SpaceNodeEntity>>(){});
  25.         } catch (Exception e) {
  26.             e.printStackTrace();
  27.         }
  28.         for (SpaceNodeEntity spaceNode:list){
  29.             //遍历+递归田间知识空间节点。
  30.             spaceNodeEntityList.add(spaceNode);
  31.             if(spaceNode.isHasChild()){
  32.                 //递归获取所有知识空间文档
  33.                 searchAllNodes(spaceNodeEntityList,spaceNode.getNodeToken(),spaceNode);
  34.             }
  35.         }
  36.         //按照创建时间对所有知识空间节点进行从大到小排序
  37.         Collections.sort(spaceNodeEntityList, (a, b) -> b.getObjCreateTime().compareTo(a.getObjCreateTime()));
  38.         //对排序后的结果进行截取,获得最近更新的前十条数据返回json对象
  39.         spaceNodeEntityList = spaceNodeEntityList.subList(0, 10);
  40.         //将排序后的10条节点数据加入知识空间
  41.         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));
  1. }
  2. /**
  3. *
  4. * @param spaceNodeEntityList 知识空间列表所有节点及子节点集合
  5. * @param parentNodeToken 父节点token
  6. * @param spaceNodeEntity 节点
  7. * @throws Exception
  8. */
  9. @CountTime
  10. public void searchAllNodes(List<SpaceNodeEntity> spaceNodeEntityList, String parentNodeToken, SpaceNodeEntity spaceNodeEntity) throws Exception {
  11.     // 构建client
  12.     Client client = Client.newBuilder(UserInfo.APP_ID, UserInfo.APP_SECRET).build();
  13.     // 创建请求对象
  14.     ListSpaceNodeReq req = ListSpaceNodeReq.newBuilder()
  15.             .spaceId(spaceNodeEntity.getSpaceId())//获取每一个知识空间下的所有node
  16.             .parentNodeToken(parentNodeToken)
  17.             .build();
  18.     // 发起请求
  19.     ListSpaceNodeResp resp = client.wiki().spaceNode().list(req);
  20.     // 处理服务端错误
  21.     if(!resp.success()) {
  22.         System.out.println(String.format("code:%s,msg:%s,reqId:%s", resp.getCode(), resp.getMsg(), resp.getRequestId()));
  23.         return ;
  24.     }
  25.     // 业务数据处理
  26.     String str = Jsons.DEFAULT.toJson(resp.getData().getItems());//json对象数组字符串
  27.     ObjectMapper objectMapper = new ObjectMapper();
  28.     List<SpaceNodeEntity> list=new ArrayList<>();
  29.     try {
  30.         // 将JSON对象数组字符串转换为List<SpaceNodeEntity>对象
  31.         list = objectMapper.readValue(str,new TypeReference<List<SpaceNodeEntity>>(){});
  32.     } catch (Exception e) {
  33.         e.printStackTrace();
  34.     }
  35.     for(SpaceNodeEntity spaceNode:list){
  36.         //遍历+递归田间知识空间节点。
  37.         spaceNodeEntityList.add(spaceNode);
  38.         if(spaceNode.isHasChild()){
  39.             //判断该节点是否有子节点,若有递归加入集合中
  40.             searchAllNodes(spaceNodeEntityList,spaceNode.getNodeToken(),spaceNode);
  41.         }
  42.     }
复制代码
// 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);
      1. // 打印JSON数组
      2. 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()));
      1.      se.setNodeData(spaceNodeEntityList);
      2. }
      3. //数据处理
      4. //将数据存放在map集合中
      5. Map<String,List<SpaceEntity>> resMap=new HashMap<>();
      6. resMap.put("spaceData",spaceEntityList);
      7. return JSONUtil.parse(JSONUtil.toJsonStr(resMap));
      复制代码
      }
      //1.2 如果没有查询到,实行下面查询操纵
      for(SpaceEntity spaceEntity : spaceEntityList){
      1. //存储知识空间列表所有节点及子节点
      2. List<SpaceNodeEntity> spaceNodeEntityList=new ArrayList<>();
      3. // 构建client
      4. Client client = Client.newBuilder(UserInfo.APP_ID, UserInfo.APP_SECRET).build();
      5. // 创建请求对象
      6. ListSpaceNodeReq req = ListSpaceNodeReq.newBuilder()
      7.          .spaceId(spaceEntity.getSpaceId())
      8.          .parentNodeToken("")
      9.          .build();
      10. // 发起请求
      11. ListSpaceNodeResp resp = client.wiki().spaceNode().list(req);
      12. // 处理服务端错误
      13. if(!resp.success()) {
      14.      System.out.println(String.format("code:%s,msg:%s,reqId:%s", resp.getCode(), resp.getMsg(), resp.getRequestId()));
      15.      return null;
      16. }
      17. // 业务数据处理
      18. String str = Jsons.DEFAULT.toJson(resp.getData().getItems());//json对象数组字符串
      19. ObjectMapper objectMapper = new ObjectMapper();
      20. List<SpaceNodeEntity> list=new ArrayList<>();
      21. try {
      22.      // 将JSON对象数组字符串转换为List<SpaceNodeEntity>对象
      23.      list = objectMapper.readValue(str,new TypeReference<List<SpaceNodeEntity>>(){});
      24. } catch (Exception e) {
      25.      e.printStackTrace();
      26. }
      27. for (SpaceNodeEntity spaceNode:list){
      28.      //设置当前节点的目录
      29.      spaceNode.setCatalogue(""+spaceEntity.getName());
      30.      //遍历+递归田间知识空间节点。
      31.      spaceNodeEntityList.add(spaceNode);
      32.      if(spaceNode.isHasChild()){
      33.          //递归获取所有知识空间文档
      34.          searchAllNodes(spaceNodeEntityList,spaceNode.getNodeToken(),spaceNode);
      35.      }
      36. }
      37. //按照创建时间对所有知识空间节点进行从大到小排序
      38. Collections.sort(spaceNodeEntityList, (a, b) -> b.getObjCreateTime().compareTo(a.getObjCreateTime()));
      39. //对排序后的结果进行截取,获得最近更新的前十条数据返回json对象
      40. spaceNodeEntityList = spaceNodeEntityList.subList(0, 10);
      41. //将知识空间节点实体中的时间戳转化为时间
      42. for(SpaceNodeEntity s:spaceNodeEntityList){
      43.      s.setObjCreateTime(convertTime(s.getObjCreateTime()));
      44.      s.setNodeCreateTime(convertTime(s.getNodeCreateTime()));
      45.      s.setObjEditTime(convertTime(s.getObjEditTime()));
      46. }
      47. //批量将最新的十条数据入库
      48. spaceNodeEntityMapper.batchInsert(spaceNodeEntityList);
      49. //将排序后的10条节点数据加入知识空间
      50. spaceEntity.setNodeData(spaceNodeEntityList);
      51. 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);
}
  1. public String convertTime(String createTime){
  2.     // 假设我们有一个时间戳,单位是毫秒
  3.     long timestamp = Long.parseLong(createTime)*1000L; // 这是一个示例时间戳
  4.     // 创建一个Date对象,其时间等于时间戳表示的时间
  5.     Date date = new Date(timestamp);
  6.     // 创建一个SimpleDateFormat对象来定义日期时间的格式
  7.     SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  8.     // 使用SimpleDateFormat对象将Date对象转换为字符串
  9.     String formattedDate = sdf.format(date);
  10.     // 打印转换后的日期时间
复制代码
// System.out.println(formattedDate);
return formattedDate;
}
}
SpaceEntityMapper
@Mapper
public interface SpaceEntityMapper extends BaseMapper {
  1. /**
  2. * 查询所有知识空间列表
  3. * @return
  4. */
  5. @Select("SELECT * FROM t_space")
  6. List<SpaceEntity> selectAll();
  7. /**
  8. * 插入数据
  9. * @param spaceEntity
  10. * @return
  11. */
复制代码
// @Insert(“INSERT INTO t_space (name, description, space_id, space_type, visibility) VALUES(#{name}, #{description}, #{spaceId}, #{spaceType}, #{visibility})”)
int insert(SpaceEntity spaceEntity);
  1. /**
  2. * 批量插入数据
  3. * @param spaceEntityList
  4. */
  5. int batchInsert(@Param("list")List<SpaceEntity> spaceEntityList);
复制代码
}
SpaceNodeEntityMapper
@Mapper
public interface SpaceNodeEntityMapper {
  1. /**
  2. * 查询所有知识空间节点列表
  3. * @return
  4. */
  5. @Select("SELECT * FROM t_spacenode")
  6. List<SpaceNodeEntity> selectAll();
  7. /**
  8. * 根据知识空间id查询所有该知识空间节点列表
  9. * @return
  10. */
  11. @Select("SELECT * FROM t_spacenode where space_id = #{spaceId}")
  12. List<SpaceNodeEntity> selectNodesById(String spaceId);
  13. /**
  14. * 插入数据
  15. * @param spaceNodeEntity
  16. */
  17. @Insert("INSERT INTO t_spacenode (space_id, node_token, obj_token, obj_type, parent_node_token, node_type, " +
  18.         "origin_node_token, origin_space_id, has_child, title, obj_create_time, obj_edit_time, node_create_time, " +
  19.         "creator, owner) " +
  20.         "VALUES (#{spaceId}, #{nodeToken}, #{objToken}, #{objType}, #{parentNodeToken}, #{nodeType}, " +
  21.         "#{originNodeToken}, #{originSpaceId}, #{hasChild}, #{title}, #{objCreateTime}, #{objEditTime}, #{nodeCreateTime}, " +
  22.         "#{creator}, #{owner})")
  23. 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: {
  1. //选择是否渲染该知识空间中的最新节点
  2. chose: function(e) {
  3.   // 通过 dataset 获取 data- 属性中设置的参数
  4.   const index = e.currentTarget.dataset.index;
  5.   console.log("参数 index: ", index);
  6.   // 处理逻辑
  7.   // 使用 setData 更新数组中对应索引的值
  8.   this.setData({
  9.     boolArray: Array(1000).fill(false),
  10.     ['boolArray[' + index + ']']: true
  11.   });
  12.   console.log("参数 index: ", index);
  13. },
  14. //跳转链接函数
  15. toHttp: function(e){
  16.   // 处理逻辑
  17.   // 使用 param1 更新链接
  18.   const param1 = e.currentTarget.dataset.param1;
  19.   const param2 = e.currentTarget.dataset.param2;
  20.   // 拼接 URL
  21.   const url = 'https://sample.feishu.cn/'+param1+'/' + param2;
  22.   console.log('openSchema 调用成功02', url);
  23.   tt.openSchema({
  24.     schema: url,
  25.     success (e) {
  26.       console.log('openSchema 调用成功', e.errMsg);
  27.     },
  28.     fail (e) {
  29.       console.log('openSchema 调用失败', e.errMsg);
  30.     },
  31.     complete (e) {
  32.       console.log('openSchema 调用结束', e.errMsg);
  33.     }
  34.   });
  35. },
  36. //跳转查看更多链接函数
  37. toMoreHttp: function(e){
  38.   // 处理逻辑
  39.   // 使用 param 更新链接url
  40.   const param = e.currentTarget.dataset.param;
  41.   // 拼接 URL
  42.   const url = 'https://sample.feishu.cn/wiki/'+param;
  43.   console.log('openSchema 调用成功02', url);
  44.   tt.openSchema({
  45.     schema: url,
  46.     success (e) {
  47.       console.log('openSchema 调用成功', e.errMsg);
  48.     },
  49.     fail (e) {
  50.       console.log('openSchema 调用失败', e.errMsg);
  51.     },
  52.     complete (e) {
  53.       console.log('openSchema 调用结束', e.errMsg);
  54.     }
  55.   });
  56. },
复制代码
},
});
c.ttml


     知识库  

     {{item.name}}   {{itemNode.title}}  {{item.description}}  {{itemNode.objCreateTime}}
  1. </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: {
  1. //选择是否渲染该知识空间中的最新节点
  2. chose: function(e) {
  3.   // requestTask;
  4.   
  5.   // 通过 dataset 获取 data- 属性中设置的参数
  6.   const index = e.currentTarget.dataset.index;
  7.   console.log("参数 index: ", index);
  8.   // 处理逻辑
  9.   // 使用 setData 更新数组中对应索引的值
  10.   this.setData({
  11.     boolArray: Array(1000).fill(false),
  12.     ['boolArray[' + index + ']']: true
  13.   });
  14.   console.log("参数 index: ", index);
  15.   tt.request({
  16.     "url": "http://localhost:8080/app/block/space/node/list",
  17.     "data": {
  18.         "noncestr": Date.now()
  19.     },
  20.     "header": {
  21.         "content-type": "application/json"
  22.     },
  23.     "method": "GET",
  24.     "dataType": "json",
  25.     "responseType": "text",
  26.     success(res) {
  27.       console.log(JSON.stringify(res.data));
  28.       info: JSON.stringify(res.data);
  29.       that.setData({
  30.         spaceAllData: JSON.parse(JSON.stringify(res.data)),
  31.         ['boolArray[' + 0 + ']']: true
  32.       });
  33.     },
  34.     fail(res) {
  35.       console.log(`request fail: ${JSON.stringify(res.data)}`);
  36.     }
  37. });
  38. },
复制代码
六,创建数据库
数据库表结构
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;
  1. @Autowired
  2. private SpaceNodeEntityMapper spaceNodeEntityMapper;
  3. @Autowired
  4. private LarkBlockAppService larkBlockAppService;
  5. @XxlJob("TEST")
  6. public void test() {
  7.     System.out.println(111);
  8. }
  9. @XxlJob("updateMysql")
  10. public void updateMysql() throws Exception {
  11.     //刷新数据
  12.     //1.1刷新知识库列表
  13.     List<SpaceEntity> spaceEntityList = refreshSpaceList();
  14.     //1.2刷新知识库节点列表
  15.     List<SpaceNodeEntity> spaceNodeEntityList = refreshSpaceNodeList();
  16.     //2.先清空数据库
  17.     spaceEntityMapper.deleteAll();
  18.     spaceNodeEntityMapper.deleteAll();
  19.     //批量插入知识空间列表
  20.     spaceEntityMapper.batchInsert(spaceEntityList);
  21.     //批量插入知识空间节点列表
  22.     spaceNodeEntityMapper.batchInsert(spaceNodeEntityList);
  23. }
  24. /**
  25. * 刷新知识库列表
  26. * @return
  27. */
  28. public List<SpaceEntity> refreshSpaceList() throws Exception {
  29.     // 构建client
  30.     Client client = Client.newBuilder(UserInfo.APP_ID, UserInfo.APP_SECRET).build();
  31.     System.out.println("=====================================");
  32.     // 创建请求对象
  33.     ListSpaceReq req=new ListSpaceReq();
  34.     // 发起请求
  35.     ListSpaceResp resp = client.wiki().space().list(req);
  36.     // 处理服务端错误
  37.     if(!resp.success()) {
  38.         System.out.println(String.format("code:%s,msg:%s,reqId:%s", resp.getCode(), resp.getMsg(), resp.getRequestId()));
  39.         return null;
  40.     }
  41.     // 业务数据处理
  42.     String jsonStr = Jsons.DEFAULT.toJson(resp.getData().getItems());//json对象数组字符串
  43.     ObjectMapper objectMapper = new ObjectMapper();
  44.     List<SpaceEntity> records=new ArrayList<>();
  45.     try {
  46.         // 将JSON对象数组字符串转换为List<Map<String, Object>>对象
  47.         records = objectMapper.readValue(jsonStr, List.class);
  48.         // 打印JSON数组
  49.         System.err.println("知识空间列表: "+records);
  50.     } catch (Exception e) {
  51.         e.printStackTrace();
  52.     }
  53.     //使用ObjectMapper解决
  54.     //创建一个ObjectMapper
  55.     ObjectMapper mapper = new ObjectMapper();
  56.     //SpaceEntity就是需要的类型对象
  57.     List<SpaceEntity> spaceEntityList= mapper.convertValue(records, new TypeReference<List<SpaceEntity>>() {});
  58.     //返回知识空间列表json数组字符串
  59.     return spaceEntityList;
  60. }
  61. /**
  62. * 刷新知识库节点列表
  63. * @return
  64. */
  65. public List<SpaceNodeEntity> refreshSpaceNodeList() throws Exception {
  66.     //获取知识空间列表
  67.     List<SpaceEntity> spaceEntityList = refreshSpaceList();
  68.     //1.2 如果没有查询到,执行下面查询操作
  69.     //存储该知识空间列表所有节点及子节点
  70.     List<SpaceNodeEntity> spaceAllNodeEntityList=new ArrayList<>();
  71.     for(SpaceEntity spaceEntity : spaceEntityList){
  72.         //存储该知识空间列表所有节点及子节点
  73.         List<SpaceNodeEntity> spaceNodeEntityList=new ArrayList<>();
  74.         // 构建client
  75.         Client client = Client.newBuilder(UserInfo.APP_ID, UserInfo.APP_SECRET).build();
  76.         // 创建请求对象
  77.         ListSpaceNodeReq req = ListSpaceNodeReq.newBuilder()
  78.                 .spaceId(spaceEntity.getSpaceId())
  79.                 .parentNodeToken("")
  80.                 .build();
  81.         // 发起请求
  82.         ListSpaceNodeResp resp = client.wiki().spaceNode().list(req);
  83.         // 处理服务端错误
  84.         if(!resp.success()) {
  85.             System.out.println(String.format("code:%s,msg:%s,reqId:%s", resp.getCode(), resp.getMsg(), resp.getRequestId()));
  86.             return null;
  87.         }
  88.         // 业务数据处理
  89.         String str = Jsons.DEFAULT.toJson(resp.getData().getItems());//json对象数组字符串
  90.         ObjectMapper objectMapper = new ObjectMapper();
  91.         List<SpaceNodeEntity> list=new ArrayList<>();
  92.         try {
  93.             // 将JSON对象数组字符串转换为List<SpaceNodeEntity>对象
  94.             list = objectMapper.readValue(str,new TypeReference<List<SpaceNodeEntity>>(){});
  95.         } catch (Exception e) {
  96.             e.printStackTrace();
  97.         }
  98.         for (SpaceNodeEntity spaceNode:list){
  99.             //遍历+递归田间知识空间节点。
  100.             spaceNodeEntityList.add(spaceNode);
  101.             if(spaceNode.isHasChild()){
  102.                 //递归获取所有知识空间文档
  103.                 searchAllNodes(spaceNodeEntityList,spaceNode.getNodeToken(),spaceNode);
  104.             }
  105.         }
  106.         //按照创建时间对所有知识空间节点进行从大到小排序
  107.         Collections.sort(spaceNodeEntityList, (a, b) -> b.getObjCreateTime().compareTo(a.getObjCreateTime()));
  108.         //对排序后的结果进行截取,获得最近更新的前十条数据返回json对象
  109.         spaceNodeEntityList = spaceNodeEntityList.subList(0, 10);
  110.         System.out.println(spaceEntity.getName()+"的知识库节点"+spaceNodeEntityList);
  111.         //将知识空间节点实体中的时间戳转化为时间
  112.         for(SpaceNodeEntity s:spaceNodeEntityList){
  113.             s.setObjCreateTime(convertTime(s.getObjCreateTime()));
  114.             s.setNodeCreateTime(convertTime(s.getNodeCreateTime()));
  115.             s.setObjEditTime(convertTime(s.getObjEditTime()));
  116.             spaceAllNodeEntityList.add(s);
  117.         }
  118.     }
  119.     return spaceAllNodeEntityList;
  120. }
  121. /**
  122. *
  123. * @param spaceNodeEntityList 知识空间列表所有节点及子节点集合
  124. * @param parentNodeToken 父节点token
  125. * @param spaceNodeEntity 节点
  126. * @throws Exception
  127. */
  128. @CountTime
  129. 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();
  1.     // 创建请求对象
  2.     ListSpaceNodeReq req = ListSpaceNodeReq.newBuilder()
  3.             .spaceId(spaceNodeEntity.getSpaceId())//获取每一个知识空间下的所有node
  4.             .parentNodeToken(parentNodeToken)
  5.             .build();
  6.     // 发起请求
  7.     ListSpaceNodeResp resp = client.wiki().spaceNode().list(req);
  8.     // 处理服务端错误
  9.     if(!resp.success()) {
  10.         System.out.println(String.format("code:%s,msg:%s,reqId:%s", resp.getCode(), resp.getMsg(), resp.getRequestId()));
  11.         return ;
  12.     }
  13.     // 业务数据处理
  14.     String str = Jsons.DEFAULT.toJson(resp.getData().getItems());//json对象数组字符串
  15.     ObjectMapper objectMapper = new ObjectMapper();
  16.     List<SpaceNodeEntity> list=new ArrayList<>();
  17.     try {
  18.         // 将JSON对象数组字符串转换为List<SpaceNodeEntity>对象
  19.         list = objectMapper.readValue(str,new TypeReference<List<SpaceNodeEntity>>(){});
  20.     } catch (Exception e) {
  21.         e.printStackTrace();
  22.     }
  23.     for(SpaceNodeEntity spaceNode:list){
  24.         //遍历+递归田间知识空间节点。
  25.         spaceNodeEntityList.add(spaceNode);
  26.         if(spaceNode.isHasChild()){
  27.             //判断该节点是否有子节点,若有递归加入集合中
  28.             searchAllNodes(spaceNodeEntityList,spaceNode.getNodeToken(),spaceNode);
  29.         }
  30.     }
复制代码
// System.out.println(1);
}
  1. public String convertTime(String createTime){
  2.     // 假设我们有一个时间戳,单位是毫秒
  3.     long timestamp = Long.parseLong(createTime)*1000L; // 这是一个示例时间戳
  4.     // 创建一个Date对象,其时间等于时间戳表示的时间
  5.     Date date = new Date(timestamp);
  6.     // 创建一个SimpleDateFormat对象来定义日期时间的格式
  7.     SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  8.     // 使用SimpleDateFormat对象将Date对象转换为字符串
  9.     String formattedDate = sdf.format(date);
  10.     // 打印转换后的日期时间
复制代码
// System.out.println(formattedDate);
return formattedDate;
}
}
[图片]
后续改成一小时一次。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

宝塔山

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表