springboot 整合 快手 移动应用 授权 发布视频 小黄车

news2024/10/22 11:56:37

 前言:

因快手文档混乱,官方社区技术交流仍有很多未解之谜,下面3种文档的定义先区分。

代码中的JSON相关工具均用hutool工具包

1.快手 移动双端 原生SDK 文档icon-default.png?t=O83Ahttps://mp.kuaishou.com/platformDocs/develop/mobile-app/ios.html

2.快手 Api 开放接口 文档icon-default.png?t=O83Ahttps://mp.kuaishou.com/platformDocs/openAbility/contentManagement/createAVideo.html

3.快手 Java 服务端SDK maven 依赖 文档icon-default.png?t=O83Ahttps://open.kuaishou.com/platform/openApi?menu=55

一、引入依赖

根据 3号 文档,虽然快手在JavaSDK中,封装了授权、用户信息、发布作品、直播等相关能力,但本次业务只涉及用户授权、发布视频,并且,SDK版的发布能力,不具备挂载小黄车的能力,所以只用到SDK中的授权能力。

            <dependency>
                <groupId>com.github.kwaiopen</groupId>
                <artifactId>kwai-open-sdk</artifactId>
                <version>1.0.6</version>
            </dependency>

二、信息配置

1.注册应用

快手有两个开放平台

①:快手开放平台——只涉及小程序

②:快手开放平台——5端统管

从 ② 进入创建开发者账户,并创建移动应用后提交审核。填写好ios和andriod信息,申请需要的权限

2.后端配置

yml中自定义参数

快手配置类
import com.github.kwai.open.api.KwaiOpenLiveApi;
import com.github.kwai.open.api.KwaiOpenOauthApi;
import com.github.kwai.open.api.KwaiOpenUserApi;
import com.github.kwai.open.api.KwaiOpenVideoApi;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;

/**
 * 快手配置类
 */
@Data
@Component
public class KuaishouConfig {

    /**
     * App
     */
    @Value("${kuaishou.appId}")
    private String appId;
    @Value("${kuaishou.appSecret}")
    private String appSecret;

    /**
     * 小程序
     */
    @Value("${kuaishou.appletId}")
    private String appletId;
    @Value("${kuaishou.appletSecret}")
    private String appletSecret;

    //快手服务端SDK接入- java版本
    //https://open.kuaishou.com/platform/openApi?menu=55
    //快手开放Api
   //https://mp.kuaishou.com/platformDocs/openAbility/contentManagement/createAVideo.html

    //发起上传Api
    private final String startUploadApi = "https://open.kuaishou.com/openapi/photo/start_upload";
    //上传视频Api
    private final String uploadApi = "http://{endpoint}/api/upload";
    public String getUploadApi(String endpoint) {
        return uploadApi.replace("{endpoint}", endpoint);
    }
    //发布视频Api
    private final String publishApi = "https://open.kuaishou.com/openapi/photo/publish";

    /**
     * oauth2.0协议的接口封装
     */
    private KwaiOpenOauthApi kwaiOpenOauthApi;

    /**
     * 获取用户信息的相关接口封装
     */
    private KwaiOpenUserApi kwaiOpenUserApi;

    /**
     * 发布内容能力的相关接口封装
     */
    private KwaiOpenVideoApi kwaiOpenVideoApi;

    /**
     * 直播能力的相关接口封装
     */
    private KwaiOpenLiveApi kwaiOpenLiveApi;

    /**
     * 初始化API接口实例,只执行一次,保证单例
     */
    @PostConstruct
    public void init() {
        this.kwaiOpenOauthApi = KwaiOpenOauthApi.init(appId);
        this.kwaiOpenUserApi = KwaiOpenUserApi.init(appId);
        this.kwaiOpenVideoApi = KwaiOpenVideoApi.init(appId);
        this.kwaiOpenLiveApi = KwaiOpenLiveApi.init(appId);
    }
}

三、实现

1.授权

前端部分跳转快手,指定scope权限,获取授权码自行实现

绑定第三方Controller
/**
 * 绑定第三方
 */
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/bound")
public class BoundThirdPartController extends BaseController {

    private final ISysUserService userService;

    /**
     * 绑定快手
     * @param bound
     * @return
     */
    @PostMapping("/kuaishou")
    public R<Void> boundKuaishou(@Validated @RequestBody KuaishouBound bound){
        SysUser user = userService.selectUserById(getUserId());
        if (StringUtils.isNotEmpty(user.getKuaishouOpenId())) {
            return R.fail("您已绑定过快手账号");
        }
        return toAjax(userService.boundKuaishou(bound, getUserId()));
    }
}
import lombok.Data;
import javax.validation.constraints.NotBlank;

@Data
public class KuaishouBound {
    /**
     * 快手授权码
     */
    @NotBlank(message = "快手授权码不能为空")
    private String kuaishouCode;
}
用户Service
@Slf4j
@RequiredArgsConstructor
@Service
public class SysUserServiceImpl implements ISysUserService{

    private final SysUserMapper baseMapper;
    private final IKuaishouService kuaishouService;

    @Override
    public boolean boundKuaishou(KuaishouBound bound, Long userId) {
        AccessTokenResponse response = kuaishouService.getKuaishouAccessToken(bound.getKuaishouCode());
        String openId = response.getOpenId();
        String accessToken = response.getAccessToken();
        Long expiresIn = response.getExpiresIn();
        //查看此openid是否有被绑定过
        SysUser old = baseMapper.selectOne(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getKuaishouOpenId, openId));
        if (ObjectUtil.isNotNull(old)) {
            //自己绑定过
            if (old.getUserId().equals(userId)) {
                throw new ServiceException("您已绑定该快手账户,请勿重复绑定!");
            }
            //别人绑定过
            throw new ServiceException("该快手已绑定到其他用户!");
        }
        RedisUtils.setCacheObject(CacheConstants.KUAISHOU_ACCESS_TOKEN + userId, accessToken, Duration.ofSeconds(expiresIn));
        //更新用户数据
        SysUser user = new SysUser();
        user.setUserId(userId);
        user.setKuaishouOpenId(openId);
        return baseMapper.updateById(user) > 0;
    }
}
快手Service
@Slf4j
@RequiredArgsConstructor
@Service
public class IKuaishouServiceImpl implements IKuaishouService {

    private final KuaishouConfig kuaishouConfig;

    /**
     * 获取快手AccessToken
     *
     * @param kuaishouCode 授权码
     */
    @Override
    public AccessTokenResponse getKuaishouAccessToken(String kuaishouCode) {
        try {
            AccessTokenRequest tokenRequest = new AccessTokenRequest(kuaishouCode, kuaishouConfig.getAppSecret());
            return kuaishouConfig.getKwaiOpenOauthApi().getAccessToken(tokenRequest);
        } catch (KwaiOpenException e) {
            throw new RuntimeException(e);
        }
    }

}

2.发布视频

文章开头说到的三种文档,都有各自的发布视频实现,这里选择第2种,Api的文档,因为只有Api接口中,可以带上小黄车的商品id。

但是! 不要高兴的太早!

这里的商品id,只能是发布视频的账号下的橱窗自建商品。

附上我与快手社区官方的交流

接受了这点,就可以看接下来的代码了。或者你不需要挂载小黄车的功能,可以考虑更方便的3号文档中的实现方式

 业务Service
//快手创建一个视频需要执行 发起上传、上传视频、发布视频 三个步骤
//1.发起上传
JSONObject startResult = kuaishouService.startUpload(userId);
//2.上传视频
String endpoint = startResult.get("endpoint", String.class);
String uploadToken = startResult.get("upload_token", String.class);
Boolean uploadResult = kuaishouService.uploadMp4(endpoint,
                                                 uploadToken,
                                                "mp4短视频 http url 地址");
//3.发布视频
JSONObject publishResult = kuaishouService.publishVideo(userId, 
                                                "封面图 http url 地址", 
                                                uploadToken, 
                                                "短视频标题 (示例#话题)", 
                                                "NOT_SPHERICAL_VIDEO", 
                                                "快手账户 快手小店 中 商品id");
// 4.得到的publishResult 结果,进行业务处理
…………
…………
快手Service
    @Override
    public JSONObject startUpload(Long userId) {
        //获取用户授权的快手token
        String accessToken = RedisUtils.getCacheObject(CacheConstants.KUAISHOU_ACCESS_TOKEN + userId);
        if (StringUtils.isEmpty(accessToken)) {
            throw new ServiceException("快手授权过期");
        }
        String result = HttpRequest.post(kuaishouConfig.getStartUploadApi() + "?access_token=" + accessToken + "&app_id=" + kuaishouConfig.getAppId())
            .execute().body();
        /*结果示例
        {
           "result": 1
        }
        */
        JSONObject json = JSONUtil.parseObj(result);
        if (json.get("result", Integer.class) != 1) {
            throw new ServiceException("向快手发起上传请求失败,请稍后再试");
        }
        return json;
    }

    @Override
    public Boolean uploadMp4(String endpoint, String uploadToken, String fileUrl) {
        //此接口的视频上传,只接受二进制,url转二进制
        byte[] bytes = FileUtils.urlToByteArray(fileUrl);
        String result = HttpRequest.post(kuaishouConfig.getUploadApi(endpoint) + "?upload_token=" + uploadToken)
            .header("Content-Type", "video/mp4")
            .body(bytes)
            .execute().body();
        /*结果示例
        {
           "result": 1
        }
        */
        if (!JSONUtil.isTypeJSON(result)) {
            log.error("快手上传视频失败,{}", result);
            throw new ServiceException("上传视频失败");
        }
        return JSONUtil.parseObj(result).get("result", Integer.class) == 1;
    }

    @Override
    public JSONObject publishVideo(Long userId, String coverImg, String uploadToken, String skitsTitle, String panoramicParams, Integer productId) {
        //获取用户授权的快手token
        String accessToken = RedisUtils.getCacheObject(CacheConstants.KUAISHOU_ACCESS_TOKEN + userId);
        if (StringUtils.isEmpty(accessToken)) {
            throw new ServiceException("快手授权过期");
        }
        //上传封面又只接受File文件,主打一个混乱🤬🤬🤬
        File file = FileUtils.urlToFile(coverImg, "jpg");
        String body = HttpRequest.post(kuaishouConfig.getPublishApi() + "?access_token=" + accessToken + "&app_id=" + kuaishouConfig.getAppId() + "&upload_token=" + uploadToken)
            .header("Content-Type", "multipart/form-data")
            .form("cover", file)//封面图(10MB内)
            .form("caption", skitsTitle)//标题
            //.form("stereo_type", panoramicParams)//全景视频参数
            //.form("merchant_product_id", productId)//需要挂载小黄车的商品ID
            .execute().body();
        /* 结果示例
        {
            "result": 1,
            "video_info": {
                //pending代表作品还在处理中,true时没有下面的play_url等参数
                "pending": true,
                "caption": "#测试1 #测试2 #测试3",
                "view_count": 0,
                "comment_count": 0,
                "like_count": 0,
                "cover": "",
                "play_url": "",
                "photo_id": "3xf4z2c8d9awkgg",
                "create_time": 1728634351884
            }
        }*/
        JSONObject result = JSONUtil.parseObj(body);
        if (result.get("result", Integer.class) != 1) {
            log.error("快手发布视频失败,{}", body);
            throw new ServiceException("视频分享失败,请稍后再试");
        }
        return JSONUtil.parseObj(result.get("video_info", JSONObject.class));
    }
FileUtils工具类
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

/**
 * 文件处理工具类
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class FileUtils extends FileUtil {

    /**
     * url转二进制
     *
     * @param url
     * @return
     */
    public static byte[] urlToByteArray(String url) {
        //通过URL 流 下载 文件的二进制数据
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        try {
            HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
            urlConnection.setConnectTimeout(5000);
            urlConnection.setRequestMethod("GET");
            InputStream inputStream = urlConnection.getInputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = inputStream.read(buffer)) != -1) {
                outStream.write(buffer, 0, len);
            }
            //关闭输入流
            inputStream.close();
        } catch (Exception e) {
            log.error("短剧资源转二进制异常:{}", e.getMessage());
        }
        byte[] data = new byte[0];
        data = outStream.toByteArray();
        if (data.length == 0) {
            log.error("短剧资源二进制数据大小为0");
            throw new ServiceException("短剧资源异常");
        }
        return data;
    }

    /**
     * url转File
     *
     * @param coverImg
     * @param fileType
     * @return
     */
    public static File urlToFile(String coverImg, String fileType) {
        File file = new File("temp/" + IdUtil.fastSimpleUUID() + "." + fileType);
        try {
            URL url = new URL(coverImg);
            org.apache.commons.io.FileUtils.copyURLToFile(url, file);
        } catch (Exception e) {
            log.error("文件转换异常:{}", e.getMessage());
            throw new ServiceException("文件转换异常");
        }
        return file;
    }

}

为了弄清混乱的快手开发,和根本没有官方技术回答,整理不易。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2213502.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

每日OJ题_牛客_chika和蜜柑_TopK_C++_Java

目录 牛客_chika和蜜柑_TopK 题目解析 C代码 Java代码 牛客_chika和蜜柑_TopK chika和蜜柑 (nowcoder.com) 描述&#xff1a; chika很喜欢吃蜜柑。每个蜜柑有一定的酸度和甜度&#xff0c;chika喜欢吃甜的&#xff0c;但不喜欢吃酸的。 一共有n个蜜柑&am…

万物皆可浮雕,comfyui一键图片转浮雕

一键生成图片浮雕效果&#xff1a;ComfyUI工作流指南 在图像处理和艺术创作领域&#xff0c;生成浮雕效果的图片是一种既独特又吸引人的表现手法。使用ComfyUI工作流&#xff0c;可以一键生成灰度图、深度图和浮雕图&#xff0c;大大简化了复杂的图像处理过程。本文将详细介绍…

嵌入式C语言之结构体封装函数

嵌入式C语言之结构体封装函数 Chapter1 嵌入式C语言之结构体封装函数结构体封装函数的作用结构体封装函数的应用结构体封装函数的好处举例1举例2举例3 Chapter1 嵌入式C语言之结构体封装函数 原文链接&#xff1a;https://blog.csdn.net/qq_43416206/article/details/13140531…

spring:Springboot3使用模版引擎thymeleaf

文章目录 介绍语法1、文本替换2、属性替换3、条件判断4. 列表循环5. 表单处理 基本示例视图解析机制视图解析器的默认配置为什么用Controller可以&#xff0c;用RestController就只是返回字符串 介绍 Thymeleaf 是一个现代的服务器端 Java 模板引擎&#xff0c;用于在服务器端…

掌握这几款在线音频剪辑工具,轻松成为音频处理达人!

在数字时代&#xff0c;音频剪辑已经成为一项必备技能。无论是制作短视频、播客&#xff0c;还是进行音乐创作&#xff0c;一款好用的音频剪辑工具都能助你事半功倍。今天&#xff0c;我们就来为大家推荐几款实用的在线音频剪辑工具&#xff0c;让你轻松成为音频处理达人&#…

救命!后悔没早点读,自学Python,这本书永远的神,经典又好懂!

这是一本对新手来说很友好的入门书&#xff0c;这本是今年才出的新版&#xff0c;之前的两个版本在某瓣都是9分以上了。专为初学者设计&#xff0c;同时也适合有编程经验的读者。该书由Eric Matthes编写&#xff0c;内容涵盖Python基础语法、编程概念以及丰富的实践项目。 全书…

【工具变量】文明城市评选DID(2000-2023年)

数据简介&#xff1a; 随着城市化的不断推进和全球城际竞争的日益激烈&#xff0c;城市品牌成为争夺优质资源、推动城市可持续发展的重要战略工具。通过关注城市品牌建设&#xff0c;不仅可以刺激本地企业家更多地进行创新活动&#xff0c;为企业家创新活动提供更好的营商环境…

自回溯天线:实现波束自动跟踪的智能天线系统

自回溯天线:实现波束自动跟踪的智能天线系统 1. 引言 自回溯天线是一种能够自动将接收到的信号发射回信号源方向的智能天线系统。它基于相位共轭原理,无需复杂的信号处理和控制系统,就能实现波束的自动跟踪。 自回溯天线技术依靠纯模拟方式实现&#xff0c;通过共轭模块对入射…

vue3中如何更改当前类的文件名称

首先&#xff0c;使用script指定文件名称 <template><div class"person"><h2>姓名&#xff1a;{{ name }}</h2><h2>年龄&#xff1a;{{ age }}</h2><button click"showTel">查看联系方式</button><bu…

【含文档】基于Springboot+Vue的招投标管理系统(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定…

如何恢复误删除的照片?能恢复多少?

最近在旅行时不小心删除了相机里之前拍摄的照片&#xff0c;我尝试在相机上恢复&#xff0c;但是没有找到恢复选项。我还能恢复这些照片吗&#xff1f;求助&#xff01; 一、照片恢复的可能性 照片恢复的可能性取决于多个因素&#xff0c;包括数据是否被覆盖、存储介质的类型、…

C语言笔记 10

求前n项之和 程序设计 f(n)1… 起点终点数字已知&#xff0c;用for循环最合适 #include <stdio.h>int main() {int n;int i;double sum 0.0;scanf("%d", &n);for ( i1; i<n; i ) {sum 1.0/i;}pritnf("f(%d)%f\n", n, sum);return 0; } i…

宿舍离人自动断电控制系统的功能要求是什么?

宿舍离人自动断电系统&#xff0c;由石家庄光大远通电气有限公司匠心打造&#xff0c;专为集体公寓、学生宿舍等密集型居住空间量身定制的智能用电管理解决方案&#xff0c;其卓越性能与广泛适应性&#xff0c;如同智慧之网&#xff0c;精准覆盖并优化每一份用电需求。该系统不…

八卦GPT-5的一切

这篇超长文章——既是评论&#xff0c;也是探索——关于GPT-5 对最受期待的下一代 AI 模型的深入分析 但它不仅仅是关于GPT-5。 • 它涉及我们对下一代AI模型的期望。 • 它关于即将出现的令人兴奋的新功能&#xff08;如推理和代理&#xff09;。它不仅讨论GPT-5技术本身&…

人工智能领域科学问题

科学问题 目前中国在人工智能领域亟待解决的问题包括但不限于以下几个方面&#xff1a; 数据隐私和安全&#xff1a;随着人工智能技术的发展&#xff0c;个人数据的收集和使用越来越广泛&#xff0c;如何保护个人隐私和数据安全成为一个重要问题。 伦理和道德问题&#xff1…

初试PostgreSQL数据库

文章目录 一、PostgreSQL数据库概述1.1 PostgreSQL的历史1.2 PostgreSQL安装1.3 安装PostgreSQL二、PostgreSQL起步2.1 连接数据库2.1.1 SQL Shell2.1.2 执行SQL语句2.2 pgAdmin 42.2.1 打开pgAdmin 42.2.2 查找数据库2.2.3 打开查询工具2.2.4 执行SQL语句三、实战小结文章目录…

LLM-生成器判别器的实现

总结 首先&#xff0c;使用GPT模型获取每个词的生成概率 pLLMp_{LLM}pLLM​。然后&#xff0c;使用训练好的生成判别器&#xff0c;对每个可能的生成结果进行打分&#xff0c;得到 pθ(c∣x1:t)p_\theta(c|x_{1:t})pθ​(c∣x1:t​)。最后&#xff0c;结合两者的输出&#xff…

JAVA就业笔记6——第二阶段(3)

课程须知 A类知识&#xff1a;工作和面试常用&#xff0c;代码必须要手敲&#xff0c;需要掌握。 B类知识&#xff1a;面试会问道&#xff0c;工作不常用&#xff0c;代码不需要手敲&#xff0c;理解能正确表达即可。 C类知识&#xff1a;工作和面试不常用&#xff0c;代码不…

中科星图GVE(案例)——AI实现道路提取分析

目录 简介 函数 gve.Services.AI.roadExtraction(fromGridRes) 代码 结果 中科星图GVE&#xff08;案例&#xff09;——AI实现道路提取分析 简介 AI实现道路提取分析是指利用人工智能技术&#xff0c;通过对图像或地理数据的处理和分析&#xff0c;自动识别和提取道路信…

创新设计大师项骅:用卓越才华打造医疗科技新未来

项骅,这位在设计界声名鹊起的才俊,正准备在其璀璨的职业生涯中开启一个激动人心的新篇章。近日,他宣布即将进军医疗科技领域,这一决定在设计圈和医疗界引起了广泛关注。项骅计划以UX设计师的身份,致力于改善医疗服务的用户体验。谈到这个新挑战,他显得兴致勃勃:"我期待将我…