后端一共就三个接口,获取列表,获取视频,以及上传视频
//利用ftp技术,将文件上传到ftp服务器
@CrossOrigin(origins = "*") // 允许任何来源的请求
@PostMapping("/uploadFileToFTP")
//ftp 上传方法
public boolean uploadFile(@RequestParam("file") MultipartFile file,@RequestParam("batchId") String batchId) throws IOException {
boolean flag = uploadFile(FTPProperties.getServer(),FTPProperties.getPort(),FTPProperties.getUsername(),
FTPProperties.getPassword(),batchId,"video",file.getOriginalFilename(),file.getInputStream());
//根据反馈输出日志
if (flag){
log.info("上传成功,文件名为:"+file.getOriginalFilename()+"文件大小="+file.getSize()+"字节"+"MB="+file.getSize()/1024/1024+"MB");
}else {
log.error("上传失败,文件名为:"+file.getOriginalFilename()+"文件大小="+file.getSize()+"字节"+"MB="+file.getSize()/1024/1024+"MB");
}
return flag;
}
/**
* #!/bin/bash
*
* # 安装vsftpd
* sudo yum remove vsftpd -y
* sudo yum install vsftpd -y
*
* # 启动vsftpd服务
* sudo systemctl start vsftpd
*
* # 检查服务状态
* sudo systemctl status vsftpd
* 云服务器记得开端口
*
* 创建一个新用户
* 切记 必须在/etc/vsftpd/user_list
*
* 指定允许使用vsftpd的用户列表文件=》 也就是自己的用户名,要不然访问530错误
* @param url
* @param port
* @param username
* @param password
* @param path
* @param path1
* @param filename
* @param input
* @return
*/
public boolean uploadFile(String url,// FTP服务器hostname
int port,// FTP服务器端口
String username, // FTP登录账号
String password, // FTP登录密码
String path, // FTP服务器保存目录
String path1, // FTP服务器保存目录1
String filename, // 上传到FTP服务器上的文件名
InputStream input // 输入流
){
boolean success = false;
FTPClient ftp = new FTPClient();
ftp.setControlEncoding("UTF-8");
try {
int reply;
ftp.connect(url, port);// 连接FTP服务器
// 如果采用默认端口,可以使用ftp.connect(url)的方式直接连接FTP服务器
ftp.login(username, password);// 登录
reply = ftp.getReplyCode();// 获取服务器的响应码。
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return success;
}
ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
ftp.makeDirectory(path); //创建文件夹
ftp.changeWorkingDirectory(path); //切换到文件夹
// ftp.makeDirectory(path1);
// ftp.changeWorkingDirectory(path1);
boolean b = ftp.storeFile(filename, input);//最终默认上传到 /home/ftpuser 目录下 批次id目录内
if (b){
//异步写入日志
asynWriteVideoImportLog(path, filename, input);
}
input.close();
ftp.logout();
success = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return success;
}
@Async("normalThreadPool")
public void asynWriteVideoImportLog(String path, String filename, InputStream input) throws IOException {
//写入日志
FtpImportTableEntity ftpImportTableEntity = new FtpImportTableEntity();
ftpImportTableEntity.setFileName(filename);
ftpImportTableEntity.setAddress(path +"/"+ filename);
ftpImportTableEntity.setSize((int) input.available());
ftpImportTableEntity.setTester(Long.parseLong(filename.split("_")[1]));
ftpImportTableEntity.setTime(LocalDateTime.now());
ftpImportTableEntity.setBatch(Long.parseLong(path));
ftpImportTableService.save(ftpImportTableEntity);
}
@CrossOrigin(origins = "*") // 允许任何来源的请求
@GetMapping("/ftp/getVideo")
public void getVideo(HttpServletResponse response, @RequestParam String videoPath, @RequestParam String videoName) {
//创建FTPClient
FTPClient ftp = new FTPClient();
ftp.setControlEncoding("UTF-8");
try {
int reply;
ftp.connect(FTPProperties.getServer(), FTPProperties.getPort());// 连接FTP服务器
// 如果采用默认端口,可以使用ftp.connect(url)的方式直接连接FTP服务器
ftp.login(FTPProperties.getUsername(), FTPProperties.getPassword());// 登录
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
}
ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
ftp.changeWorkingDirectory(videoPath);//切换到文件保存目录
FTPFile[] ftpFiles = ftp.listFiles();//获取FTP服务器上的文件别表
InputStream in = null;
for (FTPFile file : ftpFiles) { //遍历文件列表
// 取得指定文件
if (file.getName().equals(videoName)) {
if (file.isFile()) {
String fileName = file.getName();
//获取文件流
in = ftp.retrieveFileStream(new String(fileName.getBytes("gbk"), "ISO-8859-1"));
//创建ByteArrayOutputStream
ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
int ch;
//循环将文件流写入ByteArrayOutputStream 中
while ((ch = in.read()) != -1) {
swapStream.write(ch);
}
//将ByteArrayOutputStream 转成byte[]
byte[] bytes = swapStream.toByteArray();
//通过输出流输出即可
response.getOutputStream().write(bytes);
in.close();
}
}
}
} catch (SocketException e) {
throw new RuntimeException(e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@CrossOrigin(origins = "*") // 允许任何来源的请求
@GetMapping("/ftp/getVideoList")
public List<VideoInfo> getVideoList() {
List<VideoInfo> videoList = new ArrayList<>();
// 连接FTP服务器
FTPClient ftp = new FTPClient();
ftp.setControlEncoding("UTF-8");
try {
int reply;
ftp.connect(FTPProperties.getServer(), FTPProperties.getPort());
ftp.login(FTPProperties.getUsername(), FTPProperties.getPassword());
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
}
FTPFile[] files = ftp.listFiles(); // 获取FTP服务器上所有文件
for (FTPFile file : files) {
listFilesRecursive(ftp, "/home/ftpuser", videoList);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
ftp.logout();
ftp.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
return videoList;
}
private void listFilesRecursive(FTPClient ftp, String path, List<VideoInfo> videoList) throws IOException {
FTPFile[] files = ftp.listFiles(path);
for (FTPFile file : files) {
if (file.isDirectory()) {
// 如果是文件夹,递归进入文件夹
listFilesRecursive(ftp, path + "/"+ file.getName() , videoList);
} else if (file.getName().toLowerCase().endsWith(".mp4")) {
// 处理视频文件
String videoPath = path ; // 视频文件路径
String videoName = file.getName(); // 视频文件名称
videoList.add(new VideoInfo(videoPath, videoName));
}
}
}
前端就是简单的html
上传:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Layui Upload Example</title>
<link rel="stylesheet" href="https://cdn.staticfile.org/layui/2.6.8/css/layui.css">
<script src="https://cdn.staticfile.org/layui/2.6.8/layui.js"></script>
<style>
.layui-progress-big {
width: 200px;
}
</style>
</head>
<body>
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-upload">
<button type="button" class="layui-btn layui-btn-normal" id="test-upload-testList">选择文件</button>(可同时选择多个文件,不能重名,视频大小不能超过300M)
<div class="layui-upload-list">
<table class="layui-table">
<thead>
<tr>
<th>文件名</th>
<th>大小</th>
<th>上传进度</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="test-upload-demoList"></tbody>
</table>
</div>
<button type="button" class="layui-btn" id="test-upload-testListAction" onclick="gbtxt()">开始上传</button>
</div>
</div>
</div>
</div>
<script>
layui.config({
base: '${ctxPath}/static/layuiadmin/' // 静态资源所在路径
}).extend({
index: 'lib/index' // 主入口模块
}).use(['element','form','layer'], function(){
var $ = layui.$
,form = layui.form
,upload = layui.upload;
var element = layui.element;
var demoListView = $('#test-upload-demoList');
// 在获取 id 变量之前,先检查是否找到 id 为 "carsid" 的元素
var carsidElement = document.getElementById("carsid");
var id = carsidElement ? carsidElement.value : 'default value' // 如果没有找到合适的元素,可以使用默认值
// var id = document.getElementById("carsid").value
,uploadListIns = upload.render({
elem: '#test-upload-testList',
url: 'http://localhost:8080/uploadFileToFTP',
accept: 'video',
data: {"id": id, "batchId": 1765649974278980867},
multiple: true,
auto: false,
bindAction: '#test-upload-testListAction',
before: function (obj, index) {
layer.load();
var n = 0;
timer = setInterval(function(){
n = n + Math.random() * 10 | 0;
if (n > 95){
n = 95;
clearInterval(timer);
}
element.progress('progress_' + index, n + '%');
}, 50 + Math.random() * 100);
},
choose: function(obj){
var btnname = document.getElementById("test-upload-testListAction");
btnname.innerHTML = "开始上传";
document.getElementById("test-upload-testListAction").disabled = false;
var files = this.files = obj.pushFile(); // 将每次选择的文件追加到文件队列
// 读取本地文件
obj.preview(function(index, file, result){
var tr = $(['<tr id="upload-'+ index +'">',
'<td>'+ file.name +'</td>',
'<td>'+ (file.size/1024).toFixed(1) +'kb</td>',
'<td><div class="layui-progress layui-progress-big" lay-filter="progress_'+index+'" lay-showPercent="true"><div class="layui-progress-bar" lay-percent="0%"></div></div></td>',
'<td>等待上传</td>',
'<td>',
'<button class="layui-btn layui-btn-mini test-upload-demo-reload layui-hide">重传</button>',
'<button class="layui-btn layui-btn-mini layui-btn-danger test-upload-demo-delete">删除</button>',
'</td>',
'</tr>'].join(''));
// 单个重传
tr.find('.test-upload-demo-reload').on('click', function(){
obj.upload(index, file);
});
// 删除
tr.find('.test-upload-demo-delete').on('click', function(){
delete files[index]; // 删除对应的文件
tr.remove();
uploadListIns.config.elem.next()[0].value = ''; // 清空 input file 值,以免删除后出现同名文件不可选
});
demoListView.append(tr);
});
},
allDone: function(obj) {
var btnname = document.getElementById("test-upload-testListAction");
btnname.innerHTML = "上传完成";
document.getElementById("test-upload-testListAction").disabled = true;
layer.closeAll();
},
done: function(res, index, upload){
element.progress('progress_' + index, '100%'); // 上传成功
//输出返回的res
console.log("res=",res)
if (res){
var tr = demoListView.find('tr#upload-'+ index)
,tds = tr.children();
tds.eq(3).html('<span style="color: #5FB878;">上传成功</span>');
tds.eq(4).html(''); // 清空操作
return delete this.files[index]; // 删除文件队列已经上传成功的文件
}else {
var tr = demoListView.find('tr#upload-'+ index)
,tds = tr.children();
tds.eq(3).html('<span style="color: #FF5722;">上传失败</span>');
tds.eq(4).find('.test-upload-demo-reload').removeClass('layui-hide'); // 显示重传
}
this.error(index, upload);
},
error: function(index, upload){
var tr = demoListView.find('tr#upload-'+ index)
,tds = tr.children();
tds.eq(3).html('<span style="color: #FF5722;">上传失败</span>');
tds.eq(4).find('.test-upload-demo-reload').removeClass('layui-hide'); // 显示重传
}
});
});
function gbtxt() {
var btnname = document.getElementById("test-upload-testListAction");
btnname.innerHTML = "上传中,请稍候...";
document.getElementById("test-upload-testListAction").disabled = true;
}
</script>
</body>
</html>
播放:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FTP Video Player</title>
<style>
/* 调整视频列表样式 */
#videoList {
list-style-type: none;
padding: 0;
margin: 20px 0;
display: flex;
flex-wrap: wrap;
}
#videoList li {
flex: 0 0 calc(33% - 20px); /* 计算三分之一宽度,减去间隔 */
margin: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
#videoList li a {
display: block;
text-decoration: none;
color: #333;
font-weight: bold;
padding: 10px;
}
#videoList li a:hover {
color: #0066cc;
}
.popup {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8); /* 半透明黑色背景 */
z-index: 9999;
}
.video-container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-width: 90%;
max-height: 90%;
}
.close-icon {
position: absolute;
top: 10px;
right: 10px;
color: #fff;
cursor: pointer;
}
</style>
</head>
<body>
<h1>FTP Video Player</h1>
<ul id="videoList"></ul>
<div class="popup" id="videoPopup">
<span class="close-icon" onclick="closeVideoPopup()">Close ✕</span>
<div class="video-container">
<video id="popupVideoPlayer" controls>
Your browser does not support the video tag.
</video>
</div>
</div>
<script>
fetch('http://localhost:8080/ftp/getVideoList') // 后端接口获取FTP上的视频文件列表
.then(response => response.json())
.then(data => {
const videoList = document.getElementById('videoList');
data.forEach(video => {
const li = document.createElement('li');
const link = document.createElement('a');
link.href = '#';
link.textContent = video.name;
link.addEventListener('click', () => openVideoPopup(video.path, video.name));
li.appendChild(link);
videoList.appendChild(li);
});
})
.catch(error => console.error(error));
function openVideoPopup(videoPath, videoName) {
const videoPopup = document.getElementById('videoPopup');
const popupVideoPlayer = document.getElementById('popupVideoPlayer');
popupVideoPlayer.src = `http://localhost:8080/ftp/getVideo?videoPath=${videoPath}&videoName=${videoName}`;
videoPopup.style.display = 'block';
}
function closeVideoPopup() {
const videoPopup = document.getElementById('videoPopup');
videoPopup.style.display = 'none';
}
</script>
</body>
</html>