首页 前端知识 第三方网页应用对接飞书Java版

第三方网页应用对接飞书Java版

2024-07-22 00:07:52 前端知识 前端哥 945 446 我要收藏

具体开发流程在飞书官方文档都有,大家可以参考这个链接:开发文档 - 飞书开放平台飞书开发文档中包含丰富多样的开发指南、教程和示例,让开发者获得愉悦、高效的应用开发体验。https://open.feishu.cn/document/uYjL24iN/uITO4IjLykDOy4iM5gjM

看完文档大家对开发流程都有了初步了解,鉴权就是整个流程中最关键的,看明白这张图你离成功就不远了。

其实后端要做的就是根据前端传来的需要鉴权的url,结合自己的App ID 和App Secret分别去请求得到对应access_token,ticket,最终生成signature返回给前端。请求的参数和方式在官方文档都有详细说明,这里不多赘述。

后端的部分代码:

/**

 * 使用 App ID 和 App Secret 获取 tenant_access_token;使用 tenant_access_token 获取 jsapi_ticket;
 * 使用jsapi_ticket、随机字符串、当前时间戳、当前鉴权的网页URL 生成签名signature
 */
@RestController
@Slf4j
public class AuthController {
    @Autowired
    protected ObjectMapper mapper;

    @Autowired
    HttpRequestExecutor executor;

    private static String TENANT_ACCESS_TOKEN_URI="https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal";
    private static String JSAPI_TICKET_URI = "https://open.feishu.cn/open-apis/jssdk/ticket/get";
    private static String APP_ID ="cli_a49d44672462500d";
    private static String APP_SECRET="NiEZR2I5b2Jjr4brLKwmWgFugrIUNYQW";
    private static String NONCE_STR="13oEviLbrTo458A3NjrOwS70oTOXVOAm";

  private static String APP_ACCESS_TOKEN_URI="https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal";

    private static String USER_ACCESS_TOKEN_URI="https://open.feishu.cn/open-apis/authen/v1/access_token";

    private static String USER_INFO_URI="https://open.feishu.cn/open-apis/authen/v1/user_info";

    
    @GetMapping("/get_config_parameters")
    public Map get_config_parameters(@RequestParam(required = true)String url) throws JsonProcessingException {
         //接入方前端传来的需要鉴权的网页url
        // 初始化Auth类时获取的jsapi_ticket
        String ticket = get_ticket();
        //  当前时间戳,毫秒级
        String timestamp = String.valueOf(System.currentTimeMillis());
        log.info("timestamp:{}",timestamp);
        //jsapi_ticket={}&noncestr={}&timestamp={}&url={}
        String  verify_str =String.format("jsapi_ticket=%s&noncestr=%s&timestamp=%s&url=%s",ticket,NONCE_STR,timestamp,url);
        log.info("verify_str:{}",verify_str);
        //对字符串做sha1加密,得到签名signature
        Digester md5 = new Digester(DigestAlgorithm.SHA1);
        String signature = md5.digestHex(verify_str);
        log.info("signature:{}",signature);
        Map<String,String> map = new HashMap();
        map.put("appid",APP_ID);
        map.put("signature",signature);
        map.put("noncestr",NONCE_STR);
        map.put("timestamp",timestamp);
        map.put("url",url);
        return map;
    }

    @GetMapping("/callback")
    public Map callback(@RequestParam(required = true)String code) throws JsonProcessingException {
        log.info("前端传回来的code:{}",code);
        UserInfoEntity userInfo = get_user_info(code);
        log.info("用户信息获取成功!用户信息:{}",userInfo);
        Map<String,String> map = new HashMap();
        map.put("name", userInfo.getName());
        map.put("en_name", userInfo.getEn_name());
        map.put("avatar_url", userInfo.getAvatar_url());
        map.put("avatar_thumb", userInfo.getAvatar_thumb());
        map.put("avatar_middle", userInfo.getAvatar_middle());
        map.put("avatar_big", userInfo.getAvatar_big());
        map.put("open_id", userInfo.getOpen_id());
        map.put("union_id", userInfo.getUnion_id());
        map.put("email", userInfo.getEmail());
        map.put("enterprise_email", userInfo.getEnterprise_email());
        map.put("user_id", userInfo.getUser_id());
        map.put("mobile", userInfo.getMobile());
        map.put("tenant_key", userInfo.getTenant_key());
        return map;
    }

    public String get_ticket() throws JsonProcessingException {
        String access_token = getTenant_access_token();
        BasicHeader access_tokenHeader =new BasicHeader("Authorization","Bearer "+access_token);
        String str = executor.postForObjectAsString(JSAPI_TICKET_URI,null,access_tokenHeader);
        log.info("get_ticket:{}",str);
        //{"code":0,"data":{"expire_in":7200,"ticket":"g1043lbL6RLVEDXVKY2GCSAG2GUFJQR3EWQUSEOC"},"msg":"ok"}
        TicketVo ticketVo = mapper.readValue(str, TicketVo.class);
        log.info("ticketVo:{}",ticketVo);
        return ticketVo.getData().getTicket();
    }
    public String getTenant_access_token() throws JsonProcessingException {
        TokenRequestEntity requestEntity = new TokenRequestEntity();
        requestEntity.setApp_id(APP_ID);
        requestEntity.setApp_secret(APP_SECRET);
        log.info("access_token_param:{}",requestEntity);
        String res = executor.postForObjectAsString2(TENANT_ACCESS_TOKEN_URI,requestEntity);
        //2023-03-21 11:36:36.109  INFO 20724 --- [io-3000-exec-10] com.taikang.controller.AuthController    : access_token_result:{"code":0,"expire":7200,"msg":"ok","tenant_access_token":"t-g1043lbAZDEFM2F7UTYARU2U5JK2ODZ7LIAQ6ZWA"}
        log.info("access_token_result:{}",res);
        AccessTokenResultVo accessTokenResultVo= mapper.readValue(res, AccessTokenResultVo.class);
        log.info("accessTokenResultVo:{}",accessTokenResultVo);
        return accessTokenResultVo.getTenant_access_token();
    }
    public String getApp_access_token() throws JsonProcessingException {
        TokenRequestEntity requestEntity = new TokenRequestEntity();
        requestEntity.setApp_id(APP_ID);
        requestEntity.setApp_secret(APP_SECRET);
        log.info("access_token_param:{}",requestEntity);
        String res = executor.postForObjectAsString2(APP_ACCESS_TOKEN_URI,requestEntity);
        log.info("access_token_result:{}",res);
        AppAccessTokenResultVo appAccessTokenResultVo= mapper.readValue(res, AppAccessTokenResultVo.class);
        log.info("accessTokenResultVo:{}",appAccessTokenResultVo);
        return appAccessTokenResultVo.getApp_access_token();
    }
    public UserInfoEntity get_user_info(String code) throws JsonProcessingException {
        String app_access_token = getApp_access_token();
        log.info("app_access_token:{}",app_access_token);
        BasicHeader access_tokenHeader =new BasicHeader("Authorization","Bearer "+app_access_token);
        UserAccessTokenRequestEntity userAccessTokenRequestEntity = new UserAccessTokenRequestEntity();
        String grant_type = "authorization_code";
        userAccessTokenRequestEntity.setCode(code);
        userAccessTokenRequestEntity.setGrant_type(grant_type);
        String str = executor.postForObjectAsString(USER_ACCESS_TOKEN_URI,userAccessTokenRequestEntity,access_tokenHeader);
        UserAccessTokenResponseEntity userAccessTokenResponse = mapper.readValue(str, UserAccessTokenResponseEntity.class);
        log.info("userAccessTokenResponse:{}",userAccessTokenResponse);
        String user_access_token = userAccessTokenResponse.getData().getAccess_token();
        log.info("user_access_token:{}",user_access_token);
        BasicHeader user_access_tokenHeader =new BasicHeader("Authorization","Bearer "+user_access_token);
        UserInfoResponseEntity userInfoResponse = executor.getForObject(USER_INFO_URI,UserInfoResponseEntity.class,user_access_tokenHeader);
        return userInfoResponse.getData();
    }
    
}

前端的部分代码:

index.html文件

<!DOCTYPE html>

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>网页应用鉴权</title>
    <link rel="stylesheet" href="/public/index.css" />
    <script src="/js/jquery.js"></script>
    <!-- 引入 JSSDK -->
    <!-- JS 文件版本在升级功能时地址会变化,如有需要(比如使用新增的 API),请重新引用「网页应用开发指南」中的JSSDK链接,确保你当前使用的JSSDK版本是最新的。-->
    <script
      type="text/javascript"
      src="https://lf1-cdn-tos.bytegoofy.com/goofy/lark/op/h5-js-sdk-1.5.19.js"
    ></script>
    <!-- 在页面上添加VConsole方便调试-->
    <script src="/js/vconsole.min.js"></script>
    <script>

      var vConsole = new window.VConsole();

    </script>
  </head>

  <body>
    <div>
      <div class="img">
        <!-- 头像 -->
        <div id="img_div" class="img_div"></div>
        <span class="text_hello">Hello FEISHU</span>
        <!-- 名称 -->
        <div id="hello_text_name" class="text_hello_name"></div>
        <!-- 欢迎语 -->
        <div id="hello_text_welcome" class="text_hello_welcome"></div>
      </div>
      <!-- 飞书icon -->
      <div class="icon"><img src="public/svg/icon.svg" /></div>
<!--      <div class="icon"><img width="80%" src="https://sf3-cn.feishucdn.com/obj/open-platform-opendoc/33e4ae2ff215314046c51ee1d3008d89_p1QpEy0jkK.png"/></div>-->

    </div>
    <script src="/public/index.js"></script>

    <script>
      const login_info = '{{ login_info }}';
      console.log("login info: ", login_info);
      if (login_info == "alreadyLogin") {
        const user_info = JSON.parse('{{ user_info | tojson | safe }}');
        console.log("user: ", user_info.name);
        $('document').ready(showUser(user_info))
      } else {
        $('document').ready(apiAuth())
      }
    </script>
  </body>
</html>

index.js文件

let lang = window.navigator.language;

$("document").ready(apiAuth());

function apiAuth() {
  console.log("start apiAuth");
  if (!window.h5sdk) {
    console.log("invalid h5sdk");
    alert("please open in feishu");
    return;
  }

  // 调用config接口的当前网页url
  const UR =  window.location.href.split('?')[0].split('#')[0];
  console.log("重定向url为:", UR);
  const url = encodeURIComponent(location.href.split("#")[0]);
  console.log("接入方前端将需要鉴权的url发给接入方服务端,url为:", url);
  // 向接入方服务端发起请求,获取鉴权参数(appId、timestamp、nonceStr、signature)
  fetch(`/get_config_parameters?url=${url}`)
    .then((response) =>
      response.json().then((res) => {
        console.log(
          "接入方服务端返回给接入方前端的结果(前端调用config接口的所需参数):", res
        );
    console.log( "1");
        // 通过error接口处理API验证失败后的回调
        window.h5sdk.error((err) => {
          throw ("h5sdk error:", JSON.stringify(err));
        });
       console.log( "2");
        // 调用config接口进行鉴权
        window.h5sdk.config({
          appId: res.appid,
          timestamp: res.timestamp,
          nonceStr: res.noncestr,
          signature: res.signature,
          url:res.url,
          jsApiList: [],
          //鉴权成功回调
          onSuccess: (res) => {
            console.log( "3");
            console.log(`config success: ${JSON.stringify(res)}`);
          },
          //鉴权失败回调
          onFail: (err) => {
          console.log( "42");
        throw `config failed: ${JSON.stringify(err)}`;
          }
        });

        console.log( "4");
        // 完成鉴权后,便可在 window.h5sdk.ready 里调用 JSAPI
        window.h5sdk.ready(() => {

          // 调用 JSAPI tt.requestAuthCode 获取 authorization code
          tt.requestAuthCode({
            appId: res.appid,
            // 获取成功后的回调
            success(res) {
              console.log("getAuthCode succeed");
              console.log("res.code",res.code);
              //authorization code 存储在 res.code
              // 此处通过 fetch 把 code 传递给接入方服务端 Route: callback,并获得user_info
              // 服务端 Route: callback 的具体内容请参阅服务端模块 server.py 的callback() 函数
              fetch(`/callback?code=${res.code}`).then(response2 => response2.json().then(res2 => {
                    console.log("getUserInfo succeed");
                      console.log("获取到的userInfo:",res2);
                    // 示例 Demo 中单独定义的函数 showUser,用于将用户信息展示在前端页面上
                    showUser(res2);}
                  )
              ).catch(function (e) {console.error(e)})
            },
            // 获取失败后的回调
            fail(err) {
              console.log(`getAuthCode failed, err:`, JSON.stringify(err));
            }
          })


          // 调用 showToast API 弹出全局提示框,详细文档参见https://open.feishu.cn/document/uAjLw4CM/uYjL24iN/block/api/showtoast
          tt.showToast({
            title: "鉴权成功",
            icon: "success",
            duration: 3000,
            success(res) {
              console.log("showToast 调用成功", res.errMsg);
            },
            fail(res) {
              console.log("showToast 调用失败", res.errMsg);
            },
            complete(res) {
              console.log("showToast 调用结束", res.errMsg);
            },
          });

        });
      })
    )
    .catch(function (e) {
      console.error(e);
    });
}

function showUser(res) {
  // 展示用户信息
  $('#img_div').html(`<img src="${res.avatar_url}" width="100%" height=""100%/>`);
  $('#hello_text_name').text(lang === "zh_CN" || lang === "zh-CN" ? `${res.name}` : `${res.en_name}`);
  $('#hello_text_welcome').text(lang === "zh_CN" || lang === "zh-CN" ? "欢迎使用飞书" : "welcome to Feishu");
}

我在进行调试的时候,前端提示了上图这个错误,最后才发现JSSDK虽然引入了,但是注入失败,但考虑到官方的demo出错的概率很小,我又去官方的开发文档里找,发现了下面这句话。

于是我在飞书应用里重新尝试打开创建的应用,鉴权成功!

想要直接体验效果的小伙伴可以直接下载飞书官方的demo

第三方网页应用对接飞书demo(python版):开发文档 - 飞书开放平台

转载请注明出处或者链接地址:https://www.qianduange.cn//article/14176.html
标签
飞书
评论
发布的文章

TEGG学习总结

2024-08-07 00:08:45

ajax笔记二

2024-03-12 01:03:25

jQuery 密码验证

2024-08-07 00:08:10

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!