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

 前言:

因快手文档混乱,官方社区技术交流仍有很多未解之谜,下面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;
    }

}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

探索光耦:光耦——不间断电源(UPS)系统中的安全高效卫士

在现代社会&#xff0c;不间断电源&#xff08;UPS&#xff09;系统已成为保障关键设备和数据安全的关键设施&#xff0c;广泛应用于企业数据中心、家庭电子设备等场景。UPS能在电力中断或波动时提供稳定电力&#xff0c;确保设备持续运行。而在这套系统中&#xff0c;光耦&…

深入理解AQS:并发编程中的利器及其在业务场景中的应用

1. 什么是AQS&#xff08;AbstractQueuedSynchronizer&#xff09;&#xff1f; AQS&#xff0c;全称为AbstractQueuedSynchronizer&#xff0c;是Java并发包中核心的基础框架&#xff0c;用于构建锁和同步器。它是java.util.concurrent.locks包中的基础组件&#xff0c;为多个…

屏幕画面卡住不动声音正常怎么办?电脑屏幕卡住不动解决方法

在数字时代&#xff0c;电脑作为我们日常生活与工作中不可或缺的伙伴&#xff0c;偶尔也会遇到一些小状况。其中&#xff0c;“屏幕画面卡住不动&#xff0c;但是声音依然正常”的情况就是一种常见的问题。本文将探讨这一现象的原因&#xff0c;并提供几种可能的解决方案&#…

FSCapture 9.3 | 全能截图与录屏解决方案。

FastStone Capture 是一款轻量级但功能全面的屏幕捕捉工具&#xff0c;能够轻松捕获并标注屏幕上的一切内容&#xff0c;包括窗口、对象、菜单、全屏、矩形/自由区域以及滚动窗口或网页。此外&#xff0c;它还支持录制屏幕活动、声音和网络摄像头&#xff0c;并将这些内容保存为…

[复现]比较6种股市趋势识别方

作者: Filippos Tzimopoulos 翻译整理&#xff1a;进击的小学生 文章名称: I compared 6 methods to identify the trend of the stock market. These are the results! code 与 datas &#xff0c;请转至获取&#xff01; 背景说明 通过识别市场方向&#xff0c;交易者可…

webAPI中的排他思想、自定义属性操作、节点操作(配大量案例练习)

一、排他操作 1.排他思想 如果有同一组元素&#xff0c;我们想要某一个元素实现某种样式&#xff0c;需要用到循环的排他思想算法&#xff1a; 1.所有的元素全部清除样式 2.给当前的元素设置样式 注意顺序能不能颠倒&#xff0c;首先清除全部样式&#xff0c;再设置自己当前的…

dyna批处理代码,无需蹲守,自行连续计算

用此代码即可 上面一行是模型位置 下面一行是DYNA模拟器位置 第三个框框是K文件名称 上述三者改成自己的 然后复制修改即可 复制几个就几个进行批处理

深入理解伪元素与伪类元素

在“探秘盒子浮动&#xff0c;破解高度塌陷与文字环绕难题&#xff0c;清除浮动成关键&#xff01;”中&#xff0c;我们讲到如果父盒由于各种原因未设置高度&#xff0c; 子盒的浮动会导致父盒的高度塌陷。为了解决高度塌陷的问题&#xff0c;我们可以添加伪元素。 一、伪元素…

Android 内存优化——常见内存泄露及优化方案

看到了一篇关于内存泄漏的文章后&#xff0c;就想着分享给大家&#xff0c;最后一起学习&#xff0c;一起进步&#xff1a; 如果一个无用对象&#xff08;不需要再使用的对象&#xff09;仍然被其他对象持有引用&#xff0c;造成该对象无法被系统回收&#xff0c;以致该对象在…

【element-tiptap】如何修改选中内容时的背景颜色?

前言&#xff1a;element-tiptap 用鼠标选中内容的时候&#xff0c;背景颜色跟系统设置的主题有关&#xff0c;比如的我的就是卡哇伊的pink&#xff0c;默认是淡蓝色 但是我们观察一下语雀&#xff0c;背景颜色是它规定好的颜色 这篇文章来探索一下&#xff0c;怎么自己规定选…

教学平台的智能化升级:Spring Boot应用

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

除GOF23种设计模式之简单工厂模式

文章目录 1. 简介2. 代码2.1 抽象类&#xff1a;Course.java2.2 产品A:JavaCourse.java2.3 产品B:PythonCourse.java2.4 工厂:CourseFactory.java2.5 测试&#xff1a;Test.java 3. 心得参考链接&#xff08;无&#xff09; 1. 简介 简单工厂模式(Simple Factory Patern):又称…

数据结构4——栈

1. 栈的概念及结构 栈的概念&#xff1a; 栈是一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则…

【SuperHotSwap】IDEA零配置热更新插件升级

往期往期插件v1.0.0发布的时候我发表了一篇文章&#xff0c;如下&#xff1a; 支持功能 如今插件迭代了数个版本&#xff0c;现在迎来了v1.9.0版本的重大升级。如下是支持功能。 支持功能是否支持说明MybatisXML热更新√Class热更新√增强功能需安装dcevm补丁。支持动态新增类…

git rebase的常用场景: 交互式变基, 变基和本地分支基于远端分支的变基

文章目录 作用应用场景场景一&#xff1a;交互式变基(合并同一条线上的提交记录) —— git rebase -i HEAD~2场景二&#xff1a;变基(合并分支) —— git rebase [其他分支名称]场景三&#xff1a;本地分支与远端分支的变基 作用 使git的提交记录变得更加简洁 应用场景 场景…

Java爬虫:获取数据的入门详解

在数字化时代&#xff0c;数据已成为最宝贵的资产之一。无论是市场研究、客户洞察还是产品开发&#xff0c;获取大量数据并从中提取有价值的信息变得至关重要。Java&#xff0c;作为一种成熟且功能强大的编程语言&#xff0c;为编写爬虫提供了强大的支持。Java爬虫可以帮助我们…

如何提高外贸网站在谷歌的收录速度?

外贸企业在进行网络推广时&#xff0c;经常遇到网站页面无法被谷歌快速收录的问题。即使你的网站内容优质、设计精美&#xff0c;如果没有被谷歌收录&#xff0c;就等于失去了被客户发现的机会&#xff0c;GSI谷歌快速收录服务就是为了解决这一问题而诞生的。它不仅能够帮助网站…

5G智慧医疗的实践先锋:SR830-E工业路由器的理性应用

在医疗科技日新月异的今天&#xff0c;5G技术无疑为智慧医疗注入了新的活力。然而&#xff0c;技术的进步不应仅停留在理论层面&#xff0c;更应该在实践中发挥其真正价值。今天&#xff0c;我们就来探讨SR830-E工业路由器如何在实际医疗场景中扮演关键角色&#xff0c;推动5G智…

vscode 远程linux服务器 连接git

vscode 远程linux服务器 连接git 1. git 下载2. git 配置1&#xff09;github 设置2&#xff09;与github建立连接linux端&#xff1a;创建密钥github端&#xff1a;创建ssh key 3. 使用1&#xff09;初始化repository2&#xff09;commit 输入本次提交信息&#xff0c;提交到本…

UE5 圆周运动、贝塞尔曲线运动、贝塞尔曲线点

圆周运动 贝塞尔曲线路径运动 蓝图函数库创建贝塞尔曲线点 // Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h" #include "Kismet/BlueprintFunctionLibrary.h" #include "MyBlu…