vim简介
vim是一款功能强大的文本编辑器,也是早年vi编辑器的加强版,它的最大特色就是使用命令进行编辑,完全脱离了鼠标的操作,因工作及生活用电脑都习惯Linux系统很久了,对于vim很多命令有时还是容易忘记,所以今天进行的汇总和记录
高效的vim命令功能图所示:
vim 工作模式
- 命令模式
- 编辑模式
- 末行模式
注意点:编辑模式和末行模式之间不能直接进行切换,都需要经过命令模式来完成
命令模式指令
- 常用命令
命令 | 描述 |
---|---|
u | 撤销回退操作 |
Ctrl+r | 反撤销 |
yy | 复制 |
p | 粘贴 |
v | 可视化单个字符选中操作 |
V | 可视化按行选中操作 |
:/搜索的内容 | 搜索指定内容 |
G | 回到末行 |
gg | 回到第一行 |
- 扩展命令
命令 | 描述 |
---|---|
数字+yy | 复制当前行之后的多行 |
数字+dd | 剪切当前行之后的多行 |
>> | 往右缩进 |
<< | 往左缩进 |
:%s/要替换的内容/替换后的内容/g | 全局替换 |
:开始行数,结束行数s/要替换的内容/替换后的内容 | 局部替换 |
. | 重复上一次命令操作 |
shift+6、I | 回到当前行的行首 |
shift+4、A | 回到当前行的行末 |
ctrl+f | 下一屏 |
ctrl+b | 上一屏 |
末行模式指令
命令 | 描述 |
---|---|
:%s/要替换的内容/替换后的内容/g | 全局替换 |
:开始行数,结束行数s/要替换的内容/替换后的内容 | 局部替换 |
vim寄存器
vim寄存器和系统剪贴板不是一个东西,顾名思义,vim寄存器的数据作用域仅限于vim本地,甚至如果开多个vim窗口,每个窗口都有一套自己完整的寄存器,互相不影响。而系统剪贴板作为系统级别的全局变量,默认是不能混用的。
使用如下命令查看寄存器
:reg [register_name] 查看所有寄存器
:reg [register_name] 查看指定寄存器
-
" 寄存器
未命名的寄存器,是 Vim的默认寄存器,例如删除、复制等操作的内容都会被保存到这里。 -
0-9寄存器
表示数字寄存器,是 Vim 用来保存最近复制、删除等操作的内容,其中 0 号寄存器保存的是最近一次的操作内容。 -
a-zA-Z寄存器
表示用户寄存器,Vim 不会读写这部分寄存器 -
寄存器
剪切板寄存器,关联系统剪切板,保存在这个寄存器中的内容可以被系统其他程序访问,也可以通过这个寄存器访问其他程序保存到剪切板中的内容。
打通+寄存器和系统剪贴板的映射
vim专门提供了"+寄存器作为对系统剪贴板的映射,可以理解成自动把"+寄存器的内容再复制一份到系统剪贴板,前提是你得把clipboard属性设置成打开,打开以后用"+y命令把内容复制到和系统剪贴板关联的寄存器"+上。而y只是复制到默认无名寄存器""上。
- 判断Vim是否支持系统剪贴板
hushow@hushow:~$ vim --version | grep clipboard
-clipboard +jumplist +persistent_undo +vartabs
+eval +mouse_gpm +syntax -xterm_clipboard
如果结果里找到+clipboard,恭喜你当前Vim支持系统剪贴板,如果开头的是-clipboard,说明你的vim不支持系统剪切板,需要先重新安装vim操作,如下:
linux:
sudo apt install vim-gtk
MacOS:
brew install vim
效果:
hushow@hushow:~$ vim --version | grep clipboard
+clipboard +jumplist +persistent_undo +vartabs
+eval +mouse_gpm +syntax +xterm_clipboard
+寄存器和系统剪贴板实例
注:命令行模式下执行以下命令
-
复制当前行到系统剪贴板
"+yy
-
将系统剪贴板内容粘贴到当前位置
"+p
命令说明:
命令 | 描述 |
---|---|
"nyw | 复制当前单词到 n 号剪切板(双引号开始) |
"np | 粘贴 n 号剪切板内容到当前位置后 |
"+Y | 复制当前行到系统剪切板 |
"+nY | 复制当前行往下 n 行到系统剪切板 |
"+p | 粘贴系统剪切板内容到当前位置后 |
修改默认寄存器为为+寄存器
如果想偷懒用y直接把内容复制到系统剪贴板,需要到vim配置文件.vimrc里加一行属性。用下面命令开始配置,
vim ~/.vimrc
set clipboard=unnamed
现在你的y,d,x,p已经能和 ctrl-c和ctrl-v 一个效果,并且能互相混用。
前言
作为一名技术工作者,经常要使用的数据库连接工具,然自己一直使用基于linux国产桌面,而navicat并无原生linux版本,wine版的navicat破解问题一直导致我没有用上,所以自己在linux上用过很多数据库工具,如mysql workbench、DataGrip、pgAdmin等,确实都不如navicat方便,经研究navicat在linux上破解比window麻烦许多,今天周末休息,索性准备解决这个难题,并同步记录下,以便后续使用。
1. 下载navicat15
wget http://www.navicat.com.cn/download/direct-download?product=navicat15-premium-cs.AppImage&location=1
2. 解压AppImage文件
-
2.1 使用解压工具解开
-
2.2 使用挂载方式解开
sudo mkdir /home/hushow/navicatTemp sudo mount -o loop /hushow/tmp/navicat15-premium-cs.AppImage /home/hushow/navicatTemp cp -r /home/hushow/navicatTemp /home/hushow/navicat sudo umount /home/hushow/navicatTemp rm -rf /home/hushow/navicatTemp
3. 编译工具及写入破解程序
-
3.1 准备源码编译工具
sudo apt-get install libssl-dev build-essential libidn11-dev libidn11 rapidjson-dev openssl cmake
-
3.2 编译安装keystone
cd /home/hushow/tmp/ git clone https://github.com/keystone-engine/keystone.git cd keystone mkdir build cd build ../make-share.sh sudo make install sudo ldconfig
-
3.3 编译navicat-keygen
cd /home/hushow/tmp git clone -b linux --single-branch https://gitee.com/andisolo/navicat-keygen.git cd navicat-keygen make all
出现错误1
compilation terminated.
In file included from ./navicat-patcher/PatchSolutions.hpp:4,
from ./navicat-patcher/main.cpp:12:
./navicat-patcher/CapstoneDisassembler.hpp:2:10: fatal error: capstone/capstone.h: 没有那个文件或目录
解决
sudo apt install libcapstone-dev
- 3.4 执行navicat-patcher写入破解文件
完成以上操作,将会在/home/youName/navicat-keygen/bin/
这个目录中看到 keygen/patcher
cd /home/hushow/tmp/navicat-keygen/bin/
./navicat-patcher /media/hushow/work/tmp/navicat15-premium-cs
4. 重新打包AppImage文件
-
4.1 下载appimage打包工具
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
-
4.2 分配执行权限
chmod +x appimagetool-x86_64.AppImage
-
4.3 打包
./appimagetool-x86_64.AppImage /home/hushow/tmp/navicat /media/hushow/work/tools/navicat15-premium-cs.AppImage
5. 破解激活
以下流程是从navicat-keygen工具中获取许可证密码,填入navicat注册页以生成请求码,再将请求码回填到navicat-keygen中,最后生成激活码填入navicat完成破解
5.1 生成Serial number(序列号)
-
5.1.1 执行序列号生成命令
cd /home/hushow/tmp/navicat-keygen/bin ./navicat-keygen --text ./RegPrivateKey.pem
-
5.1.2 按提示序列号生成过程:
hushow@hushow-pc:~/tmp/navicat-keygen/bin$ ./navicat-keygen --text ./RegPrivateKey.pem
**********************************************************
* Navicat Keygen (Linux) by @DoubleLabyrinth *
* Version: 1.0 *
**********************************************************
[*] Select Navicat product:
0. DataModeler
1. Premium
2. MySQL
3. PostgreSQL
4. Oracle
5. SQLServer
6. SQLite
7. MariaDB
8. MongoDB
9. ReportViewer
(Input index)> 1
Serial number
[*] Select product language:
0. English
1. Simplified Chinese
2. Traditional Chinese
3. Japanese
4. Polish
5. Spanish
6. French
7. German
8. Korean
9. Russian
10. Portuguese
(Input index)> 1
[*] Input major version number:
(range: 0 ~ 15, default: 12)> 15
[*] Serial number:
NAVI-DZDN-25VJ-5NIK
[*] Your name: hushow
[*] Your organization: hushowly
[*] Input request code in Base64: (Double press ENTER to end)
注意:此处不要直接双击回车与不要关闭,后续需要回填请求码,才能双击回车
- 5.1.3 得到序列号
以上的Serial number值即为序列号: NAVI-DZDN-25VJ-5NIK
5.2 生成请求码
-
5.2.1 启动navcat
运行以上打包好的navicat15-premium-cs.AppImage,点击帮助,然后点注册,在序列号那个页面放着
-
5.2.2 进入手动激活环节
操作: 先断网,将Serial numbe填到navicat的激活页面上,点击激活一定要在断网下操作,否则无法进行手机激活环节
-
5.2.3 进入请求码和激活码窗口
在手动激活环节,点手动激活按钮,在以下弹出框中可以获取请求码注,窗口不要关闭,还需要回填后续的激活码
-
5.2.4 获取请求码
copy以上窗口中的请求码SjbJLwnEWFBhTZR9xMBmr88tmdNWi/2mRPjPXTkyyH0CwFQ0kcKF3kqdBcbkfMg1XbDAzX2l3mGI2rJSpGbEdLe/2qursnNQtWpIAFz/B2OtZTWQYyou27mK8hDiKT0SnfDTh+vl8mp5jKU7DIP1M9mOZSrVU7rEpm926oAZXLMVpjj/gdvszZOTQvnx4oeFrz3Zbv3NdGJO3qlRbxYF25Xz+/MkfGLe5y8d68bbWmhuG8sRroUakQBvPNAwmkv+zVN1il8MOqWmpDdLTsUgo+G0YumpoCwkKwk0huEQrCYs2SDzTMTODNJ3UXbqgszTmpHm1AnhsjpH5SMgd0+eAw==
5.3 生成激活码
将请求码粘贴到4.1.2章节中的控制台,两次回车生成激活动
粘贴建议不要使用快捷键
过程如下
[*] Input request code in Base64: (Double press ENTER to end)
SjbJLwnEWFBhTZR9xMBmr88tmdNWi/2mRPjPXTkyyH0CwFQ0kcKF3kqdBcbkfMg1XbDAzX2l3mGI2rJSpGbEdLe/2qursnNQtWpIAFz/B2OtZTWQYyou27mK8hDiKT0SnfDTh+vl8mp5jKU7DIP1M9mOZSrVU7rEpm926oAZXLMVpjj/gdvszZOTQvnx4oeFrz3Zbv3NdGJO3qlRbxYF25Xz+/MkfGLe5y8d68bbWmhuG8sRroUakQBvPNAwmkv+zVN1il8MOqWmpDdLTsUgo+G0YumpoCwkKwk0huEQrCYs2SDzTMTODNJ3UXbqgszTmpHm1AnhsjpH5SMgd0+eAw==
[*] Request Info:
{"K":"NAVLDG6RHB7ABXNO", "DI":"93CA1D2AFAB85F3F7DA4", "P":"linux"}
[*] Response Info:
{"K":"NAVLDG6RHB7ABXNO","DI":"93CA1D2AFAB85F3F7DA4","N":"hushow","O":"hushowly","T":1661587708}
[*] Activation Code:
zDbDzRYp/0LuLg59It3FCI8OuCS4GT/o4MDRbtCoSSieu2WdDUSNRul97urCxmVz8M9EkID9NRFHlg1BXXcSEUlCgQe67bGFUdDGX2X/FqeZkK0vDJhjUxIbS5njBpLUmdIcKnJrT7wc6unQBiALLrakEM8ovzTL+2gbX9nh5OtCRk/jkdynsk/kea/e1LgxDjw/zqXzssO/DBHvi5wv6S1PezZp8ztZy6t8EH/pMTnRTrZOL2aIeUfldYg70bT46mbC8Rjfoha1Q3tCOtpTVR/z1p/5ndMyRDuT+syWL9KhlTpkpbt6QN3N5QgrR6aLkEiE4o+zUQ9O1fGOTp41mg==
Activation Code即为最终激活码
5.5 完成破解使用
将以上激活码填入4.2.3章节的窗口中,点击OK,完成激活
6 开启使用模式
关闭navicat,恢复网络,开启navicat,完成激活
作者: 阿虎
获51CTO原创精华贴:https://ost.51cto.com/posts/14185
1. 简介
随着移动互联网的普及和快速发展,传统本地化的文档能力显得越来越不能满足需求了,由其是在协同办公及在线教学等场景上尤为突出,要实现文档的在线预览方案有很多,笔者根据经验汇总如下:
一、将文档转换为图片来预览
二、将文档转换为PDF来浏览
https://mozilla.github.io/pdf.js/
https://mozilla.github.io/pdf.js/web/viewer.html
三、将文档转换为HTML来浏览
四、搭建Office Online Server服务
https://docs.microsoft.com/zh-cn/officeonlineserver/office-online-server
五、使用第三方付费SAAS服务
https://wwo.wps.cn/docs
https://docs.qq.com/home/open
https://developer.qiniu.com/dora/10173/the-document-preview
六、开源方案
https://gitee.com/kekingcn/file-online-preview
https://gitee.com/macplus/WDA
https://github.com/ekoz/kbase-doc
2. 方案
本人也是在公司业务快速发展的基础上,为了解决用户可以通过不同的设备随时随地快速的对文档进行预览的需求场景,主导打造和封装了统一的文档转码和预览能力,主要采用方案一的方式将上传的文档预先转换成图片,再进行预览和展示;此方案主要的好处是使用简单、兼容更多环境、预览速度快,无需加载插件等,众多开源方案也是采用此方式,整体方案实现如下:
3. 功能一览
如果此刻,您也需要打造同款能力,请继续看下去,相信以下几方面内容可以快速帮到您
4. LibreOffice介绍
LibreOffice 是一款自由、免费的跨平台办公套件,它拥有所有你想要的办公软件套件的生产力功能,使其成为微软 Office 或谷歌套件的流行的开源替代品,LibreOffice 默认使用开放文档格式 (ODF),但也能很好地支持 doc/docx、xls/xlsx 等专有文档格式
https://www.libreoffice.org
https://www.libreoffice.org/discover/libreoffice-vs-openoffice/
4.1 LibreOffice安装
https://www.libreoffice.org/get-help/install-howto/
注:为了文档转换过程中中文字体问题,此处同时也安装了中文字体包
-
下载程序及语言包
wget https://download.documentfoundation.org/libreoffice/stable/7.3.4/deb/x86_64/LibreOffice_7.3.4_Linux_x86-64_deb.tar.gz && wget https://download.documentfoundation.org/libreoffice/stable/7.3.4/deb/x86_64/LibreOffice_7.3.4_Linux_x86-64_deb_langpack_zh-CN.tar.gz
-
解压文件
mkdir LibreOffice && tar -zxvf LibreOffice_7.3.4_Linux_x86-64_deb.tar.gz -C LibreOffice && tar -zxvf LibreOffice_7.3.4_Linux_x86-64_deb_langpack_zh-CN.tar.gz -C LibreOffice
-
安装程序及语言包
sudo dpkg -i LibreOffice/LibreOffice_7.3.4.*_Linux_x86-64_deb/DEBS/*.deb && sudo dpkg -i LibreOffice/LibreOffice_7.3.4.*_Linux_x86-64_deb_langpack_zh-CN/DEBS/*.deb
-
为了程序方便调用和运行,建立相应路径软链接
ln -s /opt/libreoffice7.3/program/soffice /usr/local/bin/libreoffice
-
安装成功效果
hushow@hushow-pc:~/tmp$ libreoffice --version LibreOffice 7.3.4.2 728fec16bd5f605073805c3c9e7c4212a0120dc5 hushow@hushow-pc:~/tmp$ libreoffice7.3 --version LibreOffice 7.3.4.2 728fec16bd5f605073805c3c9e7c4212a0120dc5
4.2 LibreOffice命令行转换工具
LibreOffice除了提供可视化的文件功有外,还提供了命令行文档工具,
你可以用命令行方式将文件转换为多种格式,包括 PDF、HTML、DOC、DOCX、EPUB、纯文本等。
-
单个word转pdf格式
libreoffice --convert-to pdf dev.docx --outdir ./target
├── dev.docx └── target └── dev.pdf
-
将word转odt格式
开放文档格式(OpenDocument Format,简称ODF)是一种规范,基于XML的文件格式libreoffice --convert-to odt dev.docx --outdir ./target
├── dev.docx └── target └── dev.odt
-
将word转html格式
libreoffice --convert-to "html:XHTML Writer File:UTF8" dev.docx --outdir ./target
├── dev.docx └── target └── dev.html
-
批量转换
libreoffice --convert-to pdf *.docx --outdir ./target
-
更多高级功有自行查阅
man libreoffice
5. 程序如何进行文档转码
在java中完成文档转码,需要依赖libreoffice、JODConverter库、PDFBox等工具,下面对具体环节进行介绍
示例下载地址:https://gitee.com/hushow/learning-demo/tree/master/spring-jodconverter-demo
5.1 文档转换成PDF
要实现文档转PDF,肯定是要用到LibreOffice工具,,但是好处就是开源的JODConverter Java库已经帮我们封装了LibreOffice相关功能,开箱即用,使用过程如下:
更多用法参考https://github.com/sbraconnier/jodconverter/blob/master/jodconverter-samples
-
5.1.1 引入JODConverter依赖
<dependency> <groupId>org.jodconverter</groupId> <artifactId>jodconverter-spring-boot-starter</artifactId> <version>4.4.2</version> </dependency> <dependency> <groupId>org.jodconverter</groupId> <artifactId>jodconverter-core</artifactId> <version>4.4.2</version> </dependency> <dependency> <groupId>org.jodconverter</groupId> <artifactId>jodconverter-local</artifactId> <version>4.4.2</version> </dependency>
-
5.1.2 application.yaml配置参数
jodconverter: local: #开关 enabled: true #libreoffice安装主目录 officeHome: /opt/libreoffice7.2 # 最大任务进程数 100个 max-tasks-per-process: 10 # 开启多个LibreOffice进程 ,2003,2004 port-numbers: 2003,2004 #任务执行超时时间为 taskExecutionTimeout: 33
-
5.1.3 编码转换
/** * @Description: 文档转PDF测试 * @Author: hushow@foxmail.com * @Date: 2022/06/23 **/ @SpringBootTest @Slf4j class DocToPDFTest { @Autowired private DocumentConverter converter; @Test void docToPDFTest() throws FileNotFoundException, OfficeException { File srcDocFile = new File("/home/hushow/tmp/dev.docx"); String targetExtension="pdf"; DocumentFormat documentFormat = DefaultDocumentFormatRegistry.getFormatByExtension(targetExtension); File targetPdfFile = new File(srcDocFile.getParent()+File.separator+"dev.pdf"); converter.convert(srcDocFile).to(targetPdfFile).as(documentFormat).execute(); } }
-
5.1.4 转换结果:
5.2 PDF转换成图片
因为libraoffice无法将文档直接转换成完整的图片,所以需要借助其它工具将pdf再次转换成图片,本方案选用PDFBox工具,它是一个Apache开源的Java库,支持PDF文档的开发和转换。使用这个库,可以开发创建,转换和操作PDF文档的Java程序,同时支持jar命令行操作pdf,主要功能如下:
-
5.2.1 添加依赖
<dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>2.0.12</version> </dependency> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox-tools</artifactId> <version>2.0.12</version> </dependency>
-
5.2.2 代码转换
/** * @Description: PDF转图片测试 * @Author: hushow@foxmail.com * @Date: 2022/06/23 **/ @SpringBootTest @Slf4j class DemoApplicationTests { @Test void pdfToImageTest() { PDDocument doc = null; try { File srcPdffile = new File("/home/hushow/tmp/dev.pdf"); doc = PDDocument.load(srcPdffile); String imageFileSuffix = "png"; int pageCount = doc.getNumberOfPages(); PDFRenderer pdfRenderer = new PDFRenderer(doc); String imageDir = "/home/hushow/tmp/dev"; for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) { String imgUrl = imageDir + File.separator + "dev" + "-" + (pageIndex+1) + imageFileSuffix; BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 105, ImageType.RGB); ImageIOUtil.writeImage(image, imgUrl, 105); } }catch (Exception e){ log.error(e.getMessage(), e); }finally { if(null != doc) { try { doc.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
-
5.2.3 运行结果
6 预览页面封装和定制
将文档转换成一系列的图片后,通过统一存储组件SDK,将图片存储到相应的存储服务器,为每张图片提供可仿问地址,接下来根据项目业务需求,定制相应的预览页面,最终达到文档在线预览完整方案,场景效果如下:
作者: 阿虎
获51CTO原创精华贴:https://ost.51cto.com/posts/13826
1. 什么是视频点播系统
视频点播是二十世纪90年代发展起来的,英文称为“Video on Demand”,简称“VOD”,它泛指一类能在用户需要时随时提供交互式视频服务的业务,即“想看什么就看什么,想什么时候看就什么时候看”。
为了实现这目的,视频点播放系统为用户提供包括音视频采集上传、音视频存储、自动化转码处理、加速播放、媒体资源管理等能力,如下图:
采集端
通过各种音视频智能设备,自动或手动获取业务场景中的视频数据,将视频流通过相关SDK或API推送给点播服务;
服务端
接收到视频流后,对其进行存储、转码切片、打水印、内容分发等加工处理,以提供快速、稳定、流畅、全新的视频体验
播放端
处理过的视频,经过内容分发网络(CDN)进行分发,解码后最终在用户的终端设备上播放。
2. 为什么要自建点播能力
-
自研视频处理能力,是一个强有力的竞争优势
近几年随着移动互联及5G网络普及,音视频技术在国内发展非常之快,各领域都有着广泛的运用场景,拥有自己的视频处理能力,是一个强有力的竞争优势。 -
视频处理技术越来越成熟和大众化
随着类似于ffmpeg等框架视频处理技术越来越成熟和大众化,低成本快速打造自己的私有化点播能力而不再依第三方云厂商成为可能。 -
为项目节省掉第三方云点播费用
依赖第三方厂商云点播服务和长期的按需付费是项目的一个痛点
随着业务项目越来越多,第三方点播费用成本越发明显,团队决定自研点播技术,在笔者主导下进行通用的点播能力封装打造,最终在众多项目得到落地复用,达到了不错的效果,相关功能如下:
3. HLS协议概述
在进行点播方案搭建前,要了解流行的HLS协议,常用的流媒体协议主要有 HTTP 渐进下载和基于 RTSP/RTP 的实时流媒体协议,这二种基本是完全不同的东西,目前比较方便又好用的是用 HTTP 渐进下载方法。在这个中 apple 公司的HLS(HTTP Live Streaming)是这个方面的代表,HTTP Live Streaming的工作原理是将整个视频切割成一个个小的可以通过 HTTP 下载的媒体文件(TS),然后提供一个配套的媒体列表文件(M3U8)给客户端,让客户端顺序地拉取这些媒体文件播放, 来实现看上去是在播放一条流的效果。HLS 目前广泛地应用于点播和直播领域
什么是HLS https://ottverse.com/hls-http-live-streaming-how-does-it-work/
HLS优势
-
客户端支持简单,只需要支持 HTTP 请求即可,HTTP 协议无状态,只需要按顺序下载媒体片段即可。
-
使用 HTTP 协议网络兼容性好, HTTP 数据包也可以方便地通过防火墙或者代理服务器,CDN 支持良好。
-
终端支持性好,Apple 的全系列产品支持,现在Android 也加入了对 HLS 的支持。
-
自带多码率自适应,Apple 在提出 HLS 时,就已经考虑了码流自适应的问题。
-
由于是切割成一个个小片段,播放起来更加流畅,并且较好的支持视频快进和回退
4. 音视频开发利器ffmpeg
HLS协议做视频点播有那么多优势,但如何来实现?FFmpeg 是一个强大的开源音视频处理工具,它为开发者提供了丰富的音视频处理的调用接口,我们可以使用 FFmpeg 来进行多种格式音频和视频的录制、转换、视频流内容处理功能,笔者也正是得益于ffmpeg的音视频处理能力才能为项目打造自己的点播能力。
我在进行自研点播能力设计前,对传说已久的ffmpeg工具以下几方面进了详细预研和打通
4.1 FFmpeg的安装
FFmpeg是跨平台工具,linux下可以通过如下命令进行安装:
sudo apt-get install ffmpeg
安装后,FFmpeg提供了三个主要的命令行应用程序,在 bin 目录中:
├── bin
│ ├── ffmpeg # 用于音视频编解码
│ ├── ffplay # 用于播放
│ ├── ffprobe # 用于分析视频文件
4.2 FFmpeg转码切片
HLS 是当下直播回放和部分实时直播场景最常使用的协议,它对应的媒体格式是 M3U8 + TS,将源视频转M3U8和TS文件的这个过程其实就是实现点播的转码和切片,如下调用命令:
ffmpeg -y -i field.mp4 -force_key_frames "expr:gte(t,n_forced*5)" -hls_time 5 -vf format=pix_fmts=yuv420p,scale=1280:720 -r 25 -vcodec h264 -acodec aac ty.m3u8
参数说明:
参数 | 说明 |
---|---|
-y | 输出覆盖目标文件 |
-i field.mp4 | 输入视频 |
-force_key_frames "expr:gte(t,n_forced*5)" | 每5秒设置一次关键帧 |
-hls_time 5 | 切片时间间隔 |
-vf format=pix_fmts=yuv420p,scale=1280:720 | 设定图像过滤链像素格式及大小 |
-r 25 | 设置帧率25 |
-vcodec h264 | 设置视频编码h264 |
-acodec aac | 设置音频编码acc |
执行效果:
4.3 获取转码后的视频时长及大小
在具体业务场景下,经常需要显示视频的时间或大小,这个时候就需要点播服务在转码切片过程中,同时也需要拿到视频时及大小等视频属性,通过如下命令获取:
ffprobe -loglevel warning -show_format -of json ty.m3u8
4.4 为视频自动截预览图
在点播系统中,对所有播放的视频资源至少都需要一张预览图的,所以在转码完毕后,直接对m3u8文件进行抽帧截图,命令如下:
ffmpeg -i ty.m3u8 -y -f image2 -ss 1 -vframes 1 ty.png
参数说明:
参数 | 说明 |
---|---|
-i ty.m3u8 | 输入视频 |
-y | 输出覆盖目标文件 |
-f | 输出格式为图片 |
-ss 1 | 从视频第1秒处开始截图 |
-vframes 1 | 只截取一帧 |
ty.png | 输出的文件名 |
5. 点播服务整体架构设计
对自研点播服务中需要的音视频流处理技术打通后,就可以开始整个点播服务的设计了,笔者主要从以下几方面进行思考和设计:
- 业务接入点播简便快捷
- 转码切片任务全生命周期管理
- 轻量级,支持快速部署,普通服务器即可稳定运行
- 支持集群部署,节点扩容,在高并发下支持转码切片节点无缝扩容
- 异常中断恢复,当任务节点异常中断后,可以自行恢复及重试
6. 点播任务状态图设计
- CREATE: 任务刚刚创建,DB记录数据,什么还没开始做
- WAITING: 任务被推送至消息队列,等候处理
- DOING:任务消息被具体转码节点消费,已开启转码线程
- SUCCESS:转码成功
- FAILED:转码失败(达到多次重试限制或超时)
- RETRY:单次处理失败,还未达到失败重试上限和超时,可以再次进入WAITING状态
7. 视频转码java-sdk
为了简化使用,JAVA-SDK内置了SpringBoot自动装配机制,开关打开就支自动装箱IOC容器
当然为了非SpringBoot项目同时也是支持手工实例化方式。
-
添加依赖
<dependency> <groupId>com.talkweb.ssop</groupId> <artifactId>ssop-sdk-vod</artifactId> <version>1.0.1-SNAPSHOT</version> </dependency>
-
参数配置
ssop: vod: # 开关 enable: true # 应用id,业务自己和SSO集成的时候对应的clientId,需要先提供给转码相关人员进行应用转码接入配置 appClientId: 9cd96cc9-231a-4362-b654-0283e02dadba
-
发起转码切片任务和查询转码结果
发起转码切片任务前,需要通过存储java-sdk或js-sdk先上传视频文件
@Api(value = "视频转码demo", tags = "视频转码demo")
@RestController
@RequestMapping("/{version}/demo")
public class VideoDemoController {
@Resource
VodClient vodClient;
/**
* 视频转码
* @param fileId
* @return
*/
@ApiVersion(1)
@ApiOperation(value = "发起视频转码", notes = "发起视频转码")
@RequestMapping("/encode")
public Result<ConvertTaskDO<VodConvertResultDO>> encode(@RequestParam("fileId") String fileId) {
//发起视频转码,可以使用fileId和taskId查询转码状态
ConvertTaskDO<VodConvertResultDO> convertTaskDO = vodClient.encode(fileId);
return Result.success(convertTaskDO);
}
/**
* 查询视频转码结果
* @param fileId
* @return
*/
@ApiVersion(1)
@ApiOperation(value = "查询最新的视频转码任务信息", notes = "查询最新的视频转码任务信息")
@RequestMapping("/getVideoByFileId")
public Result<ConvertTaskDO<VodConvertResultDO>> getVideoByFileId(@RequestParam("fileId") String fileId) {
//同一个fileId可以转码多次,所以可能会有多个转码结果
ConvertTaskDO<VodConvertResultDO> convertTaskDO = vodClient.getVideoByFileId(fileId);
return Result.success(convertTaskDO);
}
}
8. 在线播放示例
将查询到的转码切片结果m3u8地址,放到前端的播放器中效果如下:
作者:阿虎
获51CTO原创精华贴:https://ost.51cto.com/posts/13412
1. 背景
在项目的建设过程中,遇到越来越多的第三方私有或公有化存储云服务,有阿里 OSS、华为云 OBS、七牛云 Kodo 等,各存储厂商对接标准备、数据、SDK 尽不相同,这对项目的实施和运维带来了很大的挑战,在和团队沟通和调研过程中发现项目存在以下主要痛点
:
- 项目中附件上传下载实现五花八门,代码难以维护
- 存储参数配置凌乱,无法统一维护和配置,运维头大
- 项目无法快速接入不同第三方云存储,业务要大改一通且维护不同的代码分支
- 上传下载功能(前后端)常被业务重复开发,浪费资源
- 通过网关、nginx 等简单包装代理方式处理第三方存储,丢失 CDN 及高可用性,浪费流量、计算时间等资源
所以急需为各项目开发一套统一的存储接入标准,屏蔽各厂商接入差异,增强业务快速实施及产品化的能力,为了解决以上问题,团队内部设计和打造了统一存储接入组件,至目前为止,已在众多项目落地实践,并得到大家的好评,同时也希望更多的小伙伴可以加入使用,后续也会准备将核心技术组件部分开源出来
2. 存储组件简介
统一文件存储组件是一个面向业务应用的文件存储抽象层,以插拔方式集各主流文件存储技术和数据于一身,目前已支持华为云 OBS、阿里云 OSS、Minio分布式存储、自研单机存储等,通过不同语言提供标准化的接口和使用方式,简便快捷的为业务开发支撑文件上传、下载、安全处理等能力
3. 存储组件特点
- 存储统一配置,高效管控
- 统一接入标准,快捷简便
- 业务快速复用,降本提效
- 内置主流存储,开箱即用
- 插拔式的设计,快速扩展
- 多维权限策略,安全可靠
- 基于项目沉淀,成熟稳定
4. 功能一览
5. 整体设计
统一存储组件整体为了满足项目业务简便、轻量、扩展灵活、安全、性能等原则来设计,主要职责是按标准进行接入,文件流不直接经过存储服务,这为业务大大节省了在性能、时间、资源上的消耗。
6. 上传资源场景
下图展示了一个前端浏览器上传资源的场景,描述是如何通过JS-SDK触发上传动作,拉取配置,获取凭证、推流等环节的;在设计JS-SDK前我针对各大主流对象存储平台进行了分析研究,最后将整个上传业务流程以及返回的数据模型进行了抽象,形成了一套标准的逻辑体系。
7. 下载私有资源场景
下载资源分两种,一种是公开资源,一种是私有资源,对于私有资源,业界的处理都是通过临时授权地址对资源进行下载或浏览的,所以关键问题就是要通过文件的标识去拿到这个临时仿问地址,同时业务需要考虑这个临时仿问地址的过期时间,以及为了安全需要通过后台服务调用JAVA-SDK才能拿到这个临时仿问地址。
8 应用存储接入配置
在项目使用统一存储前,需要在统一存储配置管理中添加配置,为应用分配存储类型(HUAWEI/TALKWEB/ALIYUN/MINIO)以及相关的第三方存储配置信息
9. java-sdk使用三步曲
为了简化使用,JAVA-SDK内置了SpringBoot自动装配机制,开关打开就支自动装箱IOC容器
当然为了非SpringBoot项目同时也是支持手工实例化方式。
9.1 添加依赖
<dependency>
<groupId>com.talkweb.ssop</groupId>
<artifactId>ssop-sdk-storage</artifactId>
<version>1.0.2-SNAPSHOT</version>
</dependency>
9.2. 参数配置
ssop:
storage:
# 开关
enable: true
# 应用id,业务自己和SSO集成的时候对应的clientId,需要先提供给存储相关人员进行应用存储接入配置
appClientId: 9cd96cc9-231a-4362-b654-0283e02daxxx
appClientSecret: 9cd96cc9-231a-4362-b654-0283e02daxxx
9.3 使用示例
/**
* demo
* @author hushowly@foxmail.com
* @date 2021/3/17 上午10:02
**/
@Api(value = "spring控制器上传示例", tags = "上传示例")
@RestController
@RequestMapping("/{version}/fileDemo")
public class VideoDemoController {
@Resource
StorageClient storageClient;
@PostMapping("/uploadFile1")
public StorageFileDO uploadFile(@RequestParam("file") MultipartFile multipartFile) throws Exception {
StorageFileDO storageFileDO = storageClient.upload(multipartFile.getInputStream(), multipartFile.getOriginalFilename(), multipartFile.getSize(), false);
return storageFileDO;
}
@PostMapping("/uploadFile2")
public StorageFileDO uploadFile(@RequestParam("file") MultipartFile multipartFile) throws Exception {
String prefixPath = "moduleA/dirA";
StorageFileDO storageFileDO = storageClient.upload(multipartFile.getInputStream(), multipartFile.getOriginalFilename(), prefixPath, multipartFile.getSize(), false);
return storageFileDO;
}
}
10. js-sdk 小试牛刀
JS-SDK使用Webpack+NodeJs+TypeScript构建基于浏览器umd规范lib包,兼容不同环境下的稳定运行
同时,此处有一个优化设计,JS-SDK内部会根据不同存储厂商动态加载相应的JS逻辑并缓存,避免JS-SDK过于臃肿的同时也使得SDK可扩展、上传逻辑独立等特点。
10.1 引入封装好的 sdk 文件
建议项目保持 SDK 发布版本文件名不要修改,方便后续技术支持和升级
vue项目引用方式有多种
<script src="./talkweb-main-sdk-2.0.0.js"></script>
10.2 初始化上传对象
//参数准备:
appId: 当前应用的Id,不可为空。
storageEndPoint: 统一存储服务接口地址,不可为空。
token: 请求存储服务时header中需要的token凭证,可以为空
timeout: 上传请求超时秒数,可以为空,默认300秒(大文件,建议使用分片上传方法)
//实例化上传对象
let talkwebStorage = new talkwebStorage.StorageClient( appId, storageEndPoint, token, 3000)
10.3 单文件上传
//上传参数,具体内容如下:
var fileParams= {
file: file, //需要上传的文件file,不可为空。
fileName: fileName, //文件名,不可为空。
prefixPath: prefixPath, //文件路径,当前应用在桶中的相对路径,会体现在下载地址中,可为空,为空时,sdk自动生成一个随机目录。
isPrivate: false //文件是否为公开资源或私有资源 true 为私有, false 为公开资源,不可为空。
//上传进度回调
progressCallback: function(transferredAmount, totalAmount){
console.log("已上传大小:"+transferredAmount+ " 总大小:"+totalAmount + " 进度:"+(transferredAmount*100/totalAmount))
}
}
//调用上传方法
talkwebStorage.upload( fileParams ).then(res => {
console.log(res);
console.log("上传成功");
}).catch(err => {
console.log(err);
console.log("上传失败");
})
10.4 断点续传方法
-
实例化对象
参考以上10.2 -
以断点续传方式上传(分片上传)
talkwebStorage.uploadByResume(fileParams).then(res => { console.log(res); }).catch(err => { console.log(err); })
-
暂停上传
talkwebStorage.suspend();
-
恢复上传
talkwebStorage.restart().then((res) => { console.log(res); }).catch((res) => { console.log(res); alert("上传失败"); });
老文件服务升级新存储方案及工作评估
1. 问题
- 任意文件上传漏洞
- obs数据未授权能访问
- 原有上传下载未标准化,后续难以维护
- 旧功能迭代,依赖新版本存储的业务功能无法上线,需为项目开单独分支维护
项目渗透测试提出问题:
2. 改造必要性
- 存储的资源数据没有鉴权,线上大量业务资源数据存在被非法扫描出来的风险
- 有价值的视频资源和文档资源被盗链风险
- 用户的隐私数据被暴露风险
- 据了解,目前大量项目的附件资源数据都是公网暴露的
- 时间越长,积累业务就越多,后续再来改造工作量就更大
所以,项目很有改造必要,望各位负责人重视
3. 公开和私有资源上传方案
4. 私有资源仿问方案
5. 历史资源数据增加安全保护方案
分析业务,整理出需要改为私有资源
的资源数据
影响:对应的部分业务代码,需要改造,按以上3章节中方案,根据fileId或relativeUrl 获取资源临时仿问地址
6. 老存储服务升级方案
-
方案一,老文件服务替换为新存储服务(推荐)
以前使用用文件接口、文档转码接口、视频转码接口都要改造迁移到新的存储方案上来 -
方案二,新老存储并行运行
部分业务有资源安全问题的模块需要对接新的存储方案
工作计划及工作量估算
Spring Feign大文件上传踩坑记
引入依赖
<dependencies>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form</artifactId>
<version>3.8.0</version>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
<version>3.8.0</version>
</dependency>
</dependencies>
编写Feign Client
/**
* @author hushow
* @Descrption
* @date 2022/03/23 10:25
*/
@FeignClient(name = "${hushow.feign.service-name:ability-service}", contextId = "ability-lfile", configuration = StorageLocalFeignService.FeignUploadConfig.class)
public interface StorageLocalFeignService {
class FeignUploadConfig {
@Bean
public Encoder springEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
@Bean
Request.Options feignOptions() {
return new Request.Options(2000, TimeUnit.SECONDS, -1, TimeUnit.SECONDS, true);
}
}
/**
* 文件上传
* hushowly
* @param bucketName
* @param file
* @param fileName
* @param relativeUrl
* @param isPrivate
* @return
*/
@PostMapping(value = "/ability/v1/lfile/uploadFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
Result<StorageFileResp> uploadFile(@RequestParam(value = "bucketName", required = true) String bucketName,
@RequestPart(value = "file", required = true) MultipartFile file,
@RequestParam(value = "fileName", required = true) String fileName,
@RequestParam(value = "relativeUrl", required = false) String relativeUrl,
@RequestParam(value = "isPrivate", required = false) Boolean isPrivate);
客户端调用
StreamMultipartFile streamMultipartFile = new StreamMultipartFile(key, fileName, inputStream, fileSize, mimeTypeStr);
Result<StorageFileResp> response = storageLocalFeignService.uploadFile(bucketName, streamMultipartFile, fileName, key, isPrivate);
上传和下载时超时的坑
-
第一步,FeignUploadConfig配置超时
@Bean Request.Options feignOptions() { return new Request.Options(2000, TimeUnit.SECONDS, -1, TimeUnit.SECONDS, true); }
-
结果发现不起作用,调试源码后发现,配置文件中feign超时配置优先级高,通过单独配置上传feign解决问是:
feign: client: config: default: loggerLevel: FULL connectTimeout: 2000 readTimeout: 2000 ability-lfile: loggerLevel: FULL connectTimeout: 2000 readTimeout: -1
-
feign超时配置逻辑源码位置(FeignClientFactoryBean类)
protected void configureFeign(FeignContext context, Feign.Builder builder) { FeignClientProperties properties = this.applicationContext .getBean(FeignClientProperties.class); if (properties != null) { if (properties.isDefaultToProperties()) { # 读取类加载的时候认配置 configureUsingConfiguration(context, builder); # 如果存在使用属性文件中默认配置,则覆盖 configureUsingProperties( properties.getConfig().get(properties.getDefaultConfig()), builder); # 如果存在属性文件特定上contextId配置则覆盖配置 configureUsingProperties(properties.getConfig().get(this.contextId), builder); } else { configureUsingProperties( properties.getConfig().get(properties.getDefaultConfig()), builder); configureUsingProperties(properties.getConfig().get(this.contextId), builder); configureUsingConfiguration(context, builder); } } else { configureUsingConfiguration(context, builder); } }
尝试上传大文件后,出现OOM坑
经分析,feign暂时不支持大文件,github有人上报,至今未修复
分析源码后,发现竟读取字节数组到内存中! 本打算自己理写实现,发现feign上层根本未考虑Stream方式设计,所以暂时未打通feign上传方案,使restTemplate方案进行了代替
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3236)
at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:118)
at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93)
at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:153)
at java.io.OutputStream.write(OutputStream.java:75)
at feign.form.multipart.Output.write(Output.java:65)
at feign.form.multipart.Output.write(Output.java:53)
at feign.form.multipart.AbstractWriter.write(AbstractWriter.java:37)
at feign.form.MultipartFormContentProcessor.process(MultipartFormContentProcessor.java:87)
at feign.form.FormEncoder.encode(FormEncoder.java:105)
at feign.form.spring.SpringFormEncoder.encode(SpringFormEncoder.java:84)
MinIO简介
MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。
MinIO是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL
http://docs.minio.org.cn/docs/
https://docs.min.io/docs/minio-quickstart-guide.html
优点
- 安装部署(运维简单)
- 单机部署和分布式
- 统一对象存储控制管理
- 丰富的SDK支持
- AWS S3标准兼容
- 可扩展性
缺点
- MinIO不支持动态增加节点(增加节点需重启集群)
- 官方js-sdk仅适用于基于node-js服务端开发,不支持browser下上传.
- 针对minio自己包装分片和断点续传js-sdk
社区版本免费
安装
docker run -d -p 9000:9000 --name minio \
-v /media/hushow/doc/minio-data/data:/data \
-v /media/hushow/doc/minio-data/config:/root/.minio \
minio/minio server /data --console-address ":9001"
启动成功
控制台
- 对象维护
- 权限管理
- 凭证管理
- 参数配置
基于临时凭证流程
控制台申请永久凭证(配好权限)
后端JAVA-SDK上传
官方示例 https://github.com/minio/minio-java/tree/release/examples
-
添加依赖
<dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.3.8</version> </dependency>
-
上传代码
@Test public void uploadTempCredentialsTest() throws Exception { String bucketName = "demo"; String ak = "AJEDFDFDFDFDFDF"; String sk="SJEDFDFDFDFDKKK"; String endpoint = "http://172.17.0.2:9000"; Provider provider = new AssumeRoleProvider( endpoint, // STS endpoint usually point to MinIO server. ak, // Access key. sk, // Secret key. 2000, // Duration seconds if available. null, // Policy if available. null, // Region if available. null, // Role ARN if available. null, // Role session name if available. null, // External ID if available. null); MinioClient minioClient = MinioClient.builder().credentialsProvider(provider).endpoint(endpoint).build(); String fileUrl = "classpath:xx.mp4"; File file = ResourceUtils.getFile(fileUrl); String key = "private/temp/"+file.getName(); ObjectWriteResponse uploadResponse = minioClient.putObject(PutObjectArgs.builder() .bucket(bucketName).object(key) .stream(new FileInputStream(file), file.length(), 10*1024*1024).build()); log.info("uploadResponse:", JSONObject.toJSONString(uploadResponse)); log.info("key:"+key); }
前端JS-SDK上传
官方示例 https://github.com/minio/minio-js/tree/master/examples
- 从后台获取临时凭证
@Test
public void getTempCredentialsTest() throws Exception {
String bucketName = "demo";
String ak = "AJEDFDFDFDFDFDF";
String sk="SJEDFDFDFDFDKKK";
String endpoint = "http://172.17.0.2:9000";
Provider provider =
new AssumeRoleProvider(
endpoint, // STS endpoint usually point to MinIO server.
ak, // Access key.
sk, // Secret key.
2000, // Duration seconds if available.
null, // Policy if available.
null, // Region if available.
null, // Role ARN if available.
null, // Role session name if available.
null, // External ID if available.
null);
Credentials credential = provider.fetch();
log.info("ak:{} sk:{}, st:{}", credential.accessKey(), credential.secretKey(), credential.sessionToken());
}
-
执行上传
var s3Client = new minio.Client({ endPoint: '172.17.0.2', port: 9000, useSSL: false, accessKey: 'B3I5CKGMCJGC4HI53AFO', secretKey: 'CRpCehNFCw+MLbjnVtwuUjBZWBPx1t05A7OyMxG5', sessionToken:'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiJCM0k1Q0tHTUNKR0M0SEk1M0FGTyIsImV4cCI6MTY1MDQ1NzkzNywicGFyZW50IjoiQUpFREZERkRGREZERkRGIn0.sm6MmFizJNUWoROatJoDAWT-l32cnUht6PP6JctVTKVnVGPSNwAm1x-P0MjTIwYQ00-0Kb5trte0hvc4OjVXcQ' }) s3Client.PutObject('demo', 'error.txt', '/media/hushow/work/wk/wk-ssop-capability/ssop-storage-js-sdk/README.md', function(e) { if (e) { return console.log(e) } console.log("Success") })
js-sdk浏览器上传问题:
[Vue warn]: Error in v-on handler: "TypeError: _fs.default.stat is not a function"
found in
---> <Upload> at packages/upload/src/upload.vue
<ElUpload> at packages/upload/src/index.vue
<MinioUpload> at src/views/minio-upload.vue
<App> at src/App.vue
<Root>
warn @ vue.runtime.esm.js:619
logError @ vue.runtime.esm.js:1884
globalHandleError @ vue.runtime.esm.js:1879
handleError @ vue.runtime.esm.js:1839
invokeWithErrorHandling @ vue.runtime.esm.js:1862
invoker @ vue.runtime.esm.js:2179
original._wrapper @ vue.runtime.esm.js:6917
vue.runtime.esm.js:1888 TypeError: _fs.default.stat is not a function
at _async.default.waterfall.size (minio.js:1148)
at nextTask (async.mjs:5781)
at Object.waterfall (async.mjs:5792)
at Object.awaitable [as waterfall] (async.mjs:205)
at Client.fPutObject (minio.js:1148)
at Client.<anonymous> (helpers.js:97)
at VueComponent.handleUpload (minio-upload.vue:106)
at VueComponent.post (element-ui.common.js:29381)
at VueComponent.upload (element-ui.common.js:29336)
at element-ui.common.js:29300
排查发现,mini js-sdk只能在后端跑:
mini在浏览器使用方案minio-js-browser:
https://github.com/minio/minio-js/issues/855
https://codesandbox.io/s/react-minio-js-example-9xtgo
https://github.com/harshavardhana/minio-js-browser-upload
https://github.com/prakashsvmx/minio-js-web-browser-example
运行minio-js-web-browser-example代码,大文件分片上传效果如下:
s3 js-sdk替代方案
https://github.com/davideliason/s3upload
https://github.com/davideliason/s3upload/blob/master/index.html
http://docs.minio.org.cn/docs/master/how-to-use-aws-sdk-for-javascript-with-minio-server<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0" /> <title>s3 Upload</title> <script src="https://sdk.amazonaws.com/js/aws-sdk-2.19.0.min.js"></script> </head> <body> <input type="file" id="file-chooser" /> <button id="upload-button">Upload to S3</button> <div id="results"></div>
<script type="text/javascript">
// set variables for click handlers
var fileChooser = document.getElementById('file-chooser');
var button = document.getElementById('upload-button');
var results = document.getElementById('results');
button.addEventListener('click', function () {
var file = fileChooser.files[0];
debugger;
if (file) {
AWS.config.update({
"accessKeyId": "AJEDFDFDFDFDFDF",
"secretAccessKey": "SJEDFDFDFDFDKKK",
"s3ForcePathStyle": true,
"region": "us-west-2",
"endpoint":"http://172.17.0.2:9000",
"signatureVersion":"v4"
});
var s3 = new AWS.S3();
var params = {
Bucket: 'demo',
Key: file.name,
ContentType: file.type,
Body: file,
ACL: 'public-read'
};
s3.putObject(params, function (err, res) {
if (err) {
results.innerHTML = ("Error uploading data: ", err);
} else {
results.innerHTML = ("Successfully uploaded data");
}
});
} else {
results.innerHTML = 'Nothing to upload.';
}
}, false);
</script>
</body>
</html>