SpringBoot 开发抖音开放平台获取用户的粉丝统计和短视频数据(二篇)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 最近有朋友问起我有没有做过抖音开放平台,让我有了些思考,其实之前做过的。虽然抖音APP很火,但是毕竟不像微信开放平台那样,已沉淀多年,基本上每个API只要肯用心查找,网上都有很多资料可以参考。而抖音开放平台则不然,刚面世不久,资料比较少。即使对于一个开发人员来说,接入第三方接口都大同小异,不会太难,但我还是想把这些记录下来,特别是遇到的坑,会列在下面,一起参考学习。限于水平有限,若有错误,不吝赐教哈。那么,我们就开始正文吧。

接着上一篇 SpringBoot 开发抖音开放平台获取用户的粉丝统计和短视频数据(一篇) 的内容,我们继续来说。

1、开发细节

1.1、选择资源中心 -> Open Api -> 账号授权及绑定 查看接口文档,

image.png

1.2、用户扫码授权,回调我们的接口,拿到code,再调用获取access_token的接口,也可以拿到用户对应的open_id,因为access_token是有时效性的,所以我们要做缓存,要在过期前先用refresh_token刷新延长access_token的有效期,又过期后只能让用户重新授权。

授权相关的service

    private static final String OAUTH_STATE_SESSION_KEY = "OAUTH_STATE_SESSION_KEY";
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Value("${redis.key.douyinTokenKeyPrefix}")
    private String douyinTokenKeyPrefix;
    @Value("${redis.key.douyinRefreshTokenKeyPrefix}")
    private String douyinRefreshTokenKeyPrefix;
    @Value("${redis.key.douyinClientTokenKey}")
    private String douyinClientTokenKey;

     /**
     * 获取授权码(code)
     * @param clientKey
     * @param redirectUri
     * @param state
     * @return
     */
    @Override
    public String qrcodeAuth(String clientKey,String redirectUri,String state) {
        String requestUrl = Urls.BASE_URL+String.format(Urls.PERSON_CONNECT_URL,clientKey,redirectUri,state);    
        ShiroUtils.setSessionAttribute(OAUTH_STATE_SESSION_KEY,state);
        logger.info("qrConnect requestUrl=" + requestUrl);
        return requestUrl;
    }

    /**
     * 获取access_token
     * @param request
     * @param clientKey
     * @param clientSecret
     * @return
     */
    @Override
    public TokenResult accessToken(HttpServletRequest request,String clientKey,String clientSecret) {
        String code = request.getParameter("code");
        String state = request.getParameter("state");
        Object sessionState = SecurityUtils.getSubject().getSession().getAttribute(OAUTH_STATE_SESSION_KEY);
        TokenResult token = new TokenResult();
        //校验state
        if (sessionState != null && state.equalsIgnoreCase(sessionState.toString())) {
            SecurityUtils.getSubject().getSession().removeAttribute(OAUTH_STATE_SESSION_KEY);
            String requestUrl = Urls.BASE_URL+String.format(Urls.ACCESS_TOKEN_URL,clientKey,clientSecret,code);
            JSONObject response = (CommonUtil.httpsRequestJson(requestUrl, "GET", null));
            JSONObject object = response.getJSONObject("data");
            logger.info("accessToken result=" + response);
            int errorCode = object.getInteger("error_code");
            String description = object.getString("description");
            if (errorCode == 0) {
                token.setErrorCode(0);
                token.setAccessToken(object.getString("access_token"));
                token.setExpiresIn(object.getInteger("expires_in"));
                token.setRefreshToken(object.getString("refresh_token"));
                token.setOpenId(object.getString("open_id"));
                token.setScope(object.getString("scope"));
            } else {
                token.setErrorCode(errorCode);
                token.setDescription(description);
            }
        } else {
            token.setErrorCode(500);
            token.setDescription("state校验失败");
        }
        return token;
    }

    /**
     * 刷新access_token
     * @param clientKey
     * @param refreshToken
     * @return
     */
    @Override
    public TokenResult refreshToken(String clientKey,String refreshToken) {
        String requestUrl = Urls.BASE_URL+String.format(Urls.REFRESH_TOKEN_URL,clientKey,refreshToken);
        JSONObject response = (CommonUtil.httpsRequestJson(requestUrl, "GET", null));
        JSONObject object = response.getJSONObject("data");
        logger.info("refreshToken result=" + response);
        int errorCode = object.getInteger("error_code");
        String description = object.getString("description");
        TokenResult token = new TokenResult();
        if (errorCode == 0) {
            token.setErrorCode(0);
            token.setAccessToken(object.getString("access_token"));
            token.setExpiresIn(object.getInteger("expires_in"));
            token.setRefreshToken(object.getString("refresh_token"));
            token.setOpenId(object.getString("open_id"));
            token.setScope(object.getString("scope"));
        } else {
            token.setErrorCode(errorCode);
            token.setDescription(description);
        }
        return token;
    }

授权相关的controller

    private String douyinTokenKeyPrefix;
    @Value("${redis.key.douyinRefreshTokenKeyPrefix}")
    private String douyinRefreshTokenKeyPrefix;
    @Value("${open.douyin.clientKey}")
    private String clientKey;
    @Value("${open.douyin.clientSecret}")
    private String clientSecret;

     /**
     * 抖音授权登录
     * @param anchorUuid
     * @param response
     * @throws IOException
     */
    @RequestMapping(value = "qrcodeAuth")
    public void qrcodeAuth(String anchorUuid, HttpServletResponse response) throws IOException {
        String redirectUrl = parameter.getSERVER_PATH() + "/mobile/douyin/authCallback";
        String state = UuidUtils.randomUUID() + "::" + anchorUuid;
        String requestUrl = oauthService.qrcodeAuth(clientKey, URLEncoder.encode(redirectUrl, "UTF-8"), state);
        response.sendRedirect(requestUrl);
    }
    
    /**
     * 抖音授权回调
     * @param request
     * @return
     */
    @RequestMapping(value = "authCallback")
    public void authCallback(HttpServletRequest request) {
        String state = request.getParameter("state");
        String anchorUuid = state.split("::")[1];
        TokenResult result = oauthService.accessToken(request, clientKey, clientSecret);
        if (result.getErrorCode() == 0) {
            String openId = result.getOpenId();
            String accessToken = result.getAccessToken();
            //保存accessToken等信息到缓存
            stringRedisTemplate.opsForValue().set(douyinTokenKeyPrefix + anchorUuid,
                    accessToken, 14, TimeUnit.DAYS);
            stringRedisTemplate.opsForValue().set(douyinRefreshTokenKeyPrefix + anchorUuid,
                    result.getRefreshToken(), 29, TimeUnit.DAYS);
            logger.info("accessToken===" + accessToken);
            anchorService.saveDouyin(accessToken, anchorUuid, openId);
        }
    }

1.3、根据access_token和open_id就可以获取到该用户的基本信息和粉丝统计数据

     * 获取用户信息
     * @param accessToken
     * @param openId
     * @return
     */
    @Override
    public JSONObject userInfo(String accessToken,String openId) {
        String requestUrl = Urls.BASE_URL+String.format(Urls.USERINFO_URL,accessToken,openId);
        JSONObject response = (CommonUtil.httpsRequestJson(requestUrl, "GET", null));
        JSONObject object = response.getJSONObject("data");
        logger.info("userInfo result=" + response);
        return object;
    }

     /**
     * 获取用户粉丝数据
     * @param accessToken
     * @param openId
     * @return
     */
    @Override
    public JSONObject fansData(String accessToken,String openId) {
        String requestUrl = Urls.BASE_URL+String.format(Urls.FANS_DATA_URL,accessToken,openId);
        JSONObject response = (CommonUtil.httpsRequestJson(requestUrl, "GET", null));
        JSONObject object = response.getJSONObject("data");
        logger.info("fansData result=" + response);
        return object;
    }

用户信息接口没有返回该用户的粉丝数,倒是在粉丝统计数据接口那边返回来粉丝数,可以在这边拿到粉丝数存到用户表,结合前端开发,把数据传给前端就可以显示出来了。这边叫了一个漂亮的小姐姐授权了,下面有短视频截图,你们就说好不好看吧。

用户基本信息

image.png

粉丝年龄分布、区域分布和性别分布

image.png

粉丝活跃分布

image.png

粉丝设备分布

image.png

粉丝兴趣分布

image.png

1.4、根据access_token和open_id就可以获取到该用户所有的抖音短视频数据

     * 该接口用于分页获取用户所有视频的数据。返回的数据是实时的。
     * 列出已发布的视频
     * @param accessToken
     * @param openId
     * @param cursor
     * @param count
     * @return
     */
    @Override
    public JSONObject videoList(String accessToken,String openId,Long cursor,Integer count) {
        String requestUrl = Urls.BASE_URL+String.format(Urls.VIDEO_LIST_URL,accessToken,openId,cursor,count);
        JSONObject response = (CommonUtil.httpsRequestJson(requestUrl, "GET", null));
        logger.info("videoList result=" + response);
        return response;
    }

和粉丝数一样,开放平台没有提供接口直接获取用户的作品数、点赞数、总评论数、总分享数、平均点赞数、平均评论数、平均分享数,所以我们在获取到所有视频的时候要根据每条视频返回来的相应字段计算出这些数据再存到数据库,结合前端开发,把数据传给前端就可以显示出来了。

这里不得不吐槽一下,像粉丝数、作品数、点赞数、总评论数、总分享数等这些和用户相关的字段应该统计出来在用户信息那个接口就要返回来的,这样能给开发者省了很多时间,而且更符合常理,不知道抖音是怎么想的。

5、总结

5.1、看完这些代码之后,其实也不难,和对接其他第三方接口一样,只要照着文档写,总能调出结果来。现在看到的抖音开放平台文档是更新过的,看上去会比之前要好些,不管是版面、注意点还是参数注释都有改进,虽然还是没提供demo下载,但是增加了几种语言的接口调用样例虽然没有什么实际的作用,但是手心手背都是肉,还是知足些吧。

image.png

5.2、第一次对接新的第三方接口基本都有坑,大部分时候我们都寄希望于踩过坑的前人能够填好这些坑,给后来的人一些参考,少走弯路,节省时间,提高效率。起初抖音官方在飞书建了开放平台技术讨论群,可以在里面问问题,但是没多久上线了工单平台,要开发者有问题就提工单,就关掉了飞书群。有过对接第三方开发经验的应该都有感触,提交工单的途径来问问题的效率有多慢。下面就列出一些在开发过程中遇到的坑,小伙伴们感受一波。

问题:当时对接的时候,修改回调域名需要重新审核,不知道现在平台改过来没有。

解决:所以为了保险起见,还是一开始就填正确,这点微信开放平台修改回调域名不需要审核。

问题:在做OAuth 2.0授权时,scope传入多个,像这样scope=aweme.share,hotsearch,enterprise.data,user_info,fans.list,following.list,fans.data,video.create,video.delete,video.data,video.list,video.comment,总是报“权限非法”,我去掉一个就可以,我试了多次后猜想应该是scope太长了,最后一个权限被抖音截掉了(比如video.comment被截断变成了video.com,而video.com确实不是完整的权限,所以就报权限非法的错误)。

image.png

解决:然后向平台反应了,果然这是他们的一个bug,现在已经修复了。

问题:接口不稳定,有时候可以,有时候不可以。

解决:所有的接口路径,最后面都要加上“/”,比如/fans/data/,这个不知道为什么,是问了抖音工作人员给出的解决方案。

问题:调用授权二维码的时候,如果因为自身业务需要在用户扫码确认授权后回调我们的接口那边携带自己的参数,要注意,不能在回调接口的路径上拼接参数,因为回调那边获取不到,比如回调接口路径是/mobile/douyin/authCallback,不能这样携带参数/mobile/douyin/authCallback?userId=36781631,这应该也是被抖音限制了吧,但是做微信扫码授权就可以这样传参。

解决:扫码的时候为了安全需要传入一个随机数state,可以在state后面拼接我们的业务参数,然后在回调那边获取到state后截取。

山水有相逢,来日皆可期,谢谢阅读,我们再会

我手中的金箍棒,上能通天,下能探海

上一篇:SpringBoot 开发抖音开放平台获取用户的粉丝统计和短视频数据(一篇)

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3月前
|
XML Java 数据格式
SpringBoot入门(8) - 开发中还有哪些常用注解
SpringBoot入门(8) - 开发中还有哪些常用注解
73 0
|
1月前
|
前端开发 Java API
SpringBoot整合Flowable【06】- 查询历史数据
本文介绍了Flowable工作流引擎中历史数据的查询与管理。首先回顾了流程变量的应用场景及其局限性,引出表单在灵活定制流程中的重要性。接着详细讲解了如何通过Flowable的历史服务API查询用户的历史绩效数据,包括启动流程、执行任务和查询历史记录的具体步骤,并展示了如何将查询结果封装为更易理解的对象返回。最后总结了Flowable提供的丰富API及其灵活性,为后续学习驳回功能做了铺垫。
56 0
SpringBoot整合Flowable【06】- 查询历史数据
|
13天前
|
Java 关系型数据库 MySQL
SpringBoot 通过集成 Flink CDC 来实时追踪 MySql 数据变动
通过详细的步骤和示例代码,您可以在 SpringBoot 项目中成功集成 Flink CDC,并实时追踪 MySQL 数据库的变动。
106 43
|
4月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
354 2
|
22天前
|
监控 Java 应用服务中间件
SpringBoot是如何简化Spring开发的,以及SpringBoot的特性以及源码分析
Spring Boot 通过简化配置、自动配置和嵌入式服务器等特性,大大简化了 Spring 应用的开发过程。它通过提供一系列 `starter` 依赖和开箱即用的默认配置,使开发者能够更专注于业务逻辑而非繁琐的配置。Spring Boot 的自动配置机制和强大的 Actuator 功能进一步提升了开发效率和应用的可维护性。通过对其源码的分析,可以更深入地理解其内部工作机制,从而更好地利用其特性进行开发。
42 6
|
28天前
|
Java 应用服务中间件 API
【潜意识Java】javaee中的SpringBoot在Java 开发中的应用与详细分析
本文介绍了 Spring Boot 的核心概念和使用场景,并通过一个实战项目演示了如何构建一个简单的 RESTful API。
38 5
|
28天前
|
前端开发 Java 数据库连接
Java后端开发-使用springboot进行Mybatis连接数据库步骤
本文介绍了使用Java和IDEA进行数据库操作的详细步骤,涵盖从数据库准备到测试类编写及运行的全过程。主要内容包括: 1. **数据库准备**:创建数据库和表。 2. **查询数据库**:验证数据库是否可用。 3. **IDEA代码配置**:构建实体类并配置数据库连接。 4. **测试类编写**:编写并运行测试类以确保一切正常。
52 2
|
2月前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
150 13
|
1月前
|
存储 前端开发 Java
SpringBoot整合Flowable【05】- 使用流程变量传递业务数据
本文介绍了如何使用Flowable的流程变量来管理绩效流程中的自定义数据。首先回顾了之前的简单绩效流程,指出现有流程缺乏分数输入和保存步骤。接着详细解释了流程变量的定义、分类(运行时变量和历史变量)及类型。通过具体代码示例展示了如何在绩效流程中插入全局和局部流程变量,实现各节点打分并维护分数的功能。最后总结了流程变量的使用场景及其在实际业务中的灵活性,并承诺将持续更新Flowable系列文章,帮助读者更好地理解和应用Flowable。 简要来说,本文通过实例讲解了如何利用Flowable的流程变量功能优化绩效评估流程,确保每个环节都能记录和更新分数,同时提供了全局和局部变量的对比和使用方法。
84 0
|
2月前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
127 1

热门文章

最新文章