抖音实战~实现App端视频上传发布落库
Posted gblfy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了抖音实战~实现App端视频上传发布落库相关的知识,希望对你有一定的参考价值。
文章目录
一、API阅读
1. 选择或拍摄视频
找到uni.chooseVideo(OBJECT)
API,
选择或拍摄视频文件:https://uniapp.dcloud.net.cn/api/media/video.html#choosevideo
2. 云函数API~文件上传
用到的api:uniCloud.uploadFile(Object uploadFileOptions)
uniCloud API文档
3. 视频截帧
用途:视频截帧当视频封面
视频截帧阿里云服务
视频截帧服务
阿里云视频截帧
二、App端视频上传流程
2.1. 上传流程图
2.2. 流程简述
- 1.校验用户登录状态,未登录,则跳转登录页面进行登录,登录流程继续。
- 2.点击中间发布按钮
- 3.选择或拍摄视频,点击确认
- 4.携带文件信息跳转短视频发布页面
- 5.短视频发布页面加载时,接收视频文件信息,解析文件信息
- 6.配置文件路径和云端文件名称
- 7.调用云函数,执行短视频云端上传
- 8.短视频上传过程中,进度条根据实际上传进度动态展示
- 9.短视频上传完成,调用阿里云视频截帧服务,进行视频截帧封面制作
- 10.短视频信息封装
- 11.补充上传内容
- 12.发布短视频,调用后端接口服务
- 13.短视频文件信息,简要处理,执行落库处理
三、前端源码实战
3.1. 选择/拍摄短视频
// 监听中间按钮的点击事件
uni.onTabBarMidButtonTap(()=>
// 未登录不能发布视频
var myUserInfo = getApp().getUserInfoSession();
if (myUserInfo == null)
uni.showToast(
duration: 3000,
title: "请登录~",
icon: "none"
);
uni.navigateTo(
url: "../loginRegist/loginRegist",
animationType: "slide-in-bottom",
success()
me.loginWords = "请登录"
);
return;
// console.log('onTabBarMidButtonTap');
uni.switchTab(
url: "../me/me"
);
uni.chooseVideo(
sourceType: ['album'],
success(e)
uni.navigateTo(
url: "/pages/publish/publish?fileObjectEvent=" + JSON.stringify(e)
)
/**
* 或者采用uniCloud,在客户端完成上传,减少链路,因为文件大,通信链路和耗时是双倍的
*/
var times = new Date().getTime();
)
);
3.2. 短视频上传
// 当前页面加载时触发
onLoad(params)
var me = this;
this.statusBarHeight = system.statusBarHeight;
this.screenWidth = system.screenWidth;
// 上个页面传过来的文件事件对象,其中包含了相册中选择的视频内容
var fileObjectEvent = JSON.parse(params.fileObjectEvent);
var times = new Date().getTime();
uniCloud.uploadFile(
// 要上传的文件对象 ->获取视频临时路径
filePath: fileObjectEvent.tempFilePath,
// 使用阿里云时,cloudPath为云端文件名
//根据具体业务自定义
cloudPath: times + '.mp4',
// 进度条事件
onUploadProgress(progressEvent)
var percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
// 这个数值在视频上传过程中是动态变化的百分比,以此来展示上传的具体进度
me.percentCompleted = percentCompleted;
,
// 上传完成后的流程操作
success(f)
// 获取视频路径
var videoUrlTemp = f.filePath;
// 获取视频ID
var videoUrl = f.fileID;
// 获得视频参数
me.tempFilePath = videoUrlTemp;
me.videoUrl = videoUrl;
me.tempCover = videoUrl + "?x-oss-process=video/snapshot,t_1,f_jpg,ar_auto"; // 截帧
me.width = fileObjectEvent.width;
me.height = fileObjectEvent.height;
);
,
3.3. 进度条页面
<!-- 进度条 -->
<view style="marginTop:60rpx;display: flex;flex-direction: column;justify-content: center;"
v-if="percentCompleted != 100">
<progress :percent="percentCompleted" stroke-width="3" activeColor="#ef274d" backgroundColor="#F1F1F1"
:style="screenWidth: screenWidth + 'px'" />
<text style="color: #F1F1F1;font-size: 16px;text-align: center;margin-top: 20px;"
:style="screenWidth: screenWidth + 'px'">视频上传中~ 请耐心等待~~</text>
<image mode="aspectFit" src="../../static/images/loading-4.gif"
style="width: 600rpx;height: 600rpx;align-self: center;">
</view>
3.4. 补充短视频内容
<textarea class="vlog-content" placeholder-style="color: #9798a0;" placeholder="添加合适的标题内容" :value="title"
:model="title" maxlength="60" @input="typingContent" confirm-type="done"></textarea>
3.5. 视频发布
<view :class="'btn-publish':!publichTouched, 'btn-publish-touched': publichTouched"
style="margin-top: 30rpx;height: 90rpx;display: flex;justify-content: center;border-radius: 20px;"
@touchstart="touchstartPublich" @touchend="touchendPublich" @click="doPublich">
<text style="color: #e6e6e6;font-size: 18px;align-self: center;font-weight: 500;">发布 Vlog</text>
</view>
doPublich()
var me = this;
var userId = getApp().getUserInfoSession().id;
var vlog =
"vlogerId": userId,
"url": me.videoUrl,
"cover": me.tempCover,
"title": me.title,
"width": me.width,
"height": me.height
;
// 发布视频
var serverUrl = app.globalData.serverUrl;
uni.request(
method: "POST",
header:
headerUserId: userId,
headerUserToken: app.getUserSessionToken()
,
url: serverUrl + "/vlog/publish",
data: vlog,
success(result)
if (result.data.status == 200)
uni.showToast(
title: "发布成功!",
icon: "none"
)
uni.navigateBack(
delta: 1,
animationType: 'zoom-fade-in',
animationDuration: 1000
);
uni.switchTab(
url: "../me/me"
)
else
uni.showToast(
title: result.data.msg,
icon: "none",
duration: 3000
);
);
,
3.6. 视频预览
preview()
uni.navigateTo(
url: "/pages/publish/preview?videoUrl=" + this.videoUrl + "&width=" + this.width + "&height=" +
this.height,
animationType: 'slide-in-bottom',
animationDuration: 500
)
,
四、后端源码实战
4.1. 短视频发布
/**
* 发布vlog视频
*
* @param vlogBO
* @return
*/
@PostMapping("publish")
public GraceJSONResult publish(@RequestBody VlogBO vlogBO)
vlogService.createVlog(vlogBO);
return GraceJSONResult.ok();
4.2. 逻辑处理
/**
* 发布vlog视频
*
* @param vlogBO
*/
@Transactional
@Override
public void createVlog(VlogBO vlogBO)
//视频ID
String vid = sid.nextShort();
Vlog vlog = new Vlog();
BeanUtils.copyProperties(vlogBO, vlog);
vlog.setId(vid);
vlog.setLikeCounts(0);
vlog.setCommentsCounts(0);
vlog.setIsPrivate(YesOrNo.NO.type);
vlog.setCreatedTime(new Date());
vlog.setUpdatedTime(new Date());
vlogMapper.insert(vlog);
五、效果图鉴赏
5.1. 未登陆状态
5.2. 发布模式选择
5.3. 选择/录制短视频
5.4. 短视频发布
5.5. 查看已发布作品
5.6. 首页查看短视频
从零开始搭建仿抖音短视频APP-构建后端项目
项目持续创作中...
目录
聚合工程
构建父工程
项目采用meavn创建一个聚合工程,采用meavn的分层结构。构建一个父工程,
pom:代表总工程,用于管理的
配置完按常理,在Meavn中应该会有坐标,但这里没有。
我们这里用的dependencyManagement,是一种父子的关系,面向对象的一种思想。
使用dependencyManagement的目的是为了保证父工程的干净, 也就是说父工程他只负责管理依赖,以及依赖的版本,而不会导入额外的jar依赖。 如此一来父工程的职责就很单一了,而且也符合了面向对象开发的父子继承关系, 依赖的导入只有在各自的子工程中才会进行导入。
如果不想加,可以把所有的坐标,在我们的(顶级工程)父工程导入,也没有问题。
meavn的编译工具所采用的版本是jdk的1.8,作为一个插件使用。
构建子工程
创建子模块,
创建完成之后在父工程里会出现:
book-common 通用工程,包含一些工具类,枚举类,自定义异常,封装的公共方法。
book-model 模型工程,所有的子工程以及所有(微)服务工程所涉及到的实体类都可以再此处管理 比如:POJO, Bean, Entity, BO, VO, DTO, MO, EO...
book-mapper 数据层,操作数据库(DAO)
book-service 业务层
book-api 接口层,暴露接口,提供给前端调用
在父工程中检查:
在common中引入依赖:
注:这不能version,因为会覆盖父工程的版本。
依赖来源于Springboot中,它管理了很多的依赖:
最后建议在父工程中,install,它会把我们所有的jar包进行安装和校验。
构建接口工程并且暴露api接口
在我们构建好web工程后,要创建一个启动类来启动我们的工程:
此时,它会报错,因为之前在common中添加了依赖,而我们的项目是一层一层调用的。api依赖于service,service依赖于mapper,mapper依赖于model,model依赖于common,这样才会使得我们api里会有相应的依赖。
在model中:
在mapper中:
在service中:
在父工程进行一个install。
在启动类中:
接着要构建一个controll的包,再创建一个类,在里面写我们最简单的一个接口方法:
随后要创建一个配置文件,首先我们的服务端口号,要和我们之前的项目保持一致。
在里面进行一些配置:
启动项目:
访问hello:
我们配置的一个banner,可以是图片等...控制台的一个效果,可以在云服务台和日志里面看见。
现在表示已经可以对外体现我们的web能力了。
优雅Restful相应封装
我们和前端做对接时,会有一个统一的封装,也就是我们的接口请求的是什么样的状态,是否成功,有没有异常。
在common中:
现在使用gracejsonresult,即优雅的封装,不包含枚举。我们通过优雅的封装可以在
responsestatusEnum中做一个定义。
由于枚举在前端后端都是统一的,前端在捕获status时,会根据状态,把对应的内容展示出来,
或者前端可以不根据状态码直接获取。
如何去使用? 这里我们是请求成功的:
接着重启:
现在我们获得的是json的一个字符串,前端根据状态码进行判断。
如果这里设计请求失败,在这里传入一个枚举:
这就是我们优雅的一个封装。
Lombok与多环境profile
在common中引入一个依赖:
早期它是作为一个插件,需要独立安装。现在被springboot集成了。现在的开发根据能够自动集成。
如何使用呢?创建一个类,
按照以往需要创建很多get,set函数。
生成的太多没有必要,我们把方法去掉,只保留属性,
@data帮我们生成了方法,就可以调用了,
我们可以使用日志进行打印,只需要正当前的类里加一个注释:
重启,做个测试:
回到控制台:
三个级别的日志级别,可以在配置文件中设置:
我们有开发环境,生成环境,测试环境等,不同环境对于我们的代码是不同的,配置需要改变:
从配置来讲,只是端口号不同,在我们本身的配置里,注释端口号
在我们的dev配置文件中,删去相同的。
数据层
配置数据库逆向生成工具
接着要数据库逆向生成:
在navaciat中导入:
并且在父过程中添加模块:
MyMapper是一个工具,增对单表的增删改查,已经帮我们封装好了
配置文件我们将它当作工具使用 ,
这里要修改我们地方数据源,用户,密码等...
生成我们对应的文件:
将文件拷贝到mapper中
这里继承我们的MyMapper
接着把pojo复制到mapper中
配置mybatis整合SpingBoot
此时mybatis的依赖坐标还没有加入
发现还是会报错,因为我们复制时没有加入jar包的依赖 ,这时候展开我们的逆向工具,重新拷贝一份。
这里接口就能对标找到我们的Mapper:
我们还需要在配置文件中加入数据源:
开发环境:
加入通用mybatis的配置:
注:my.mapper一定要和本地做好匹配
最后在要添加我们的扫描器:
最后做一个全局的install,重新启动Application
这里完成了我们数据层的一个配置,但没有实质性的交互。
使用Knife4j实现接口文档
我们在真正开发的时候会有很多不同的请求方法,有些是不能通过浏览器请求的:
这里用到了一个软件postman:
我们在这里主要要讲的是一个文档工具:
通过这个工具结合springboot,增强我们的api文档。
项目介绍:
能够反映需要传入的参数,能够得到什么等...免去我们后端开发写文档,维护文档的尴尬局面。
在api的pom文件中加入依赖:
接着创建一个配置类:
通过注解扫描到容器中
我们现在要扫描的路径就是.contoller:
接着我们就能访问:
在主页中都会显示
与前端人员对接主要看的就是文档:
在调试中我们可以传入参数:
在后期开发也是可以通过这个来做测试的。
我们现在看到的是没有中文释义的,在controller可以注释:
请求就是我们的hello
以上是关于抖音实战~实现App端视频上传发布落库的主要内容,如果未能解决你的问题,请参考以下文章
抖音腾讯京东阿里等大厂性能优化方案总结(含项目实战分析及视频)
抖音腾讯京东阿里等大厂性能优化方案总结(含项目实战分析及视频)
抖音腾讯京东阿里等大厂性能优化方案总结(含项目实战分析及视频)