0


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

一.鉴权知识(选取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:

  1. 通过添加群为知识库管理员(成员)方式(较容易)
  • 在飞书 IM 中创建新群,将应用添加为该群机器人,知识库管理员在「知识空间设置」-> 「权限设置」->「添加管理员」中添加。 创建一个群聊,将各部门知识库负责人拉进群聊,将自建组件应用设置为群聊机器人,将该群聊设置为各知识空间成员即可。(后续协调)
  1. 如何将应用添加为知识库管理员(成员)?
  • 添加应用为知识库管理员(成员)当前有两种方式:
  • 通过添加群为知识库管理员(成员)方式(较容易)在飞书 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(时间副标题,根据时间排序)
  1. 通过浏览器地址栏获取 token (以下红色部分)(注意: 拷贝时 URL 末尾可能多余的 “#”)

[图片]

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:

  1. 通过添加群为知识库管理员(成员)方式(较容易)
  • 在飞书 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);} 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;
}
}
[图片]
后续改成一小时一次。

标签: 飞书 java 前端

本文转载自: https://blog.csdn.net/qq_59708493/article/details/139480481
版权归原作者 荒野大飞 所有, 如有侵权,请联系我们删除。

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

还没有评论