目录
- 基于fastdfs文件服务
- nginx+fastdfs 启用slice,range进行下载。
- 基于springboot的应用服务器转发nginx服务的文件请求服务。
- vue使用asyn和await进行请求和合并文件
- 添加进度条
一、fastdfs文件服务
启动fastdfs服务命令:
- /usr/bin/fdfs_storaged /etc/fdfs/storage.conf start
- /usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf start
二、配置Nginx
手动安装nginx,因为要编译模块slice模块;
要求版本必须大于等于1.9.8。
① 下载nginx
# wget -c https://nginx.org/download/nginx-1.12.1.tar.gz
② 解压
# tar -zxvf nginx-1.12.1.tar.gz
# cd nginx-1.12.1
③ 使用默认配置 (指定安装目录:./configure --prefix=/usr/local/nginx)
# ./configure --with-http_slice_module
④ 编译、安装
# make
# make install
⑤ 启动nginx
# cd /usr/local/nginx/sbin/
# ./nginx
⑥ 配置nginx.conf
文件目录:/usr/local/nginx/conf/nginx.conf
gzip on;
proxy_cache_path /tmp/nginx/cache levels=1:2 keys_zone=cache:100m;
server {
........
location /group1/M00 {
alias /home/fdfs_storage/data;
slice 1m;
proxy_cache cache;
proxy_cache_key $uri$is_args$args$slice_range;
proxy_set_header Range $slice_range;
proxy_cache_valid 200 206 1h;
proxy_set_header Range $http_range;
#proxy_pass http://192.168.41.171:80;
}
........
}
三、Springboot的转发服务
写一个转发服务,如下:
参考:
private String targetAddr = "http://192.168.41.171";
/**
* 定义接口
**/
@RequestMapping(value = "/proxy", method = { RequestMethod.HEAD,RequestMethod.GET})
public void splitDownload(@RequestParam String deptCode,HttpServletRequest request,
HttpServletResponse response);
//实现类,请根据你的要求修改,deptCode是项目所需要的,你可以删除这个参数
public void splitDownload(String deptCode,HttpServletRequest request, HttpServletResponse response) {
// String url = URLDecoder.decode(request.getRequestURL().toString(), "UTF-8");
try {
URI uri = new URI(request.getRequestURI());
String path = uri.getPath();
String query = request.getQueryString();
String target = targetAddr + path.replace("/proxy", "");
if (query != null && !query.equals("") && !query.equals("null")) {
target = target + "?" + query;
}
target = "http://192.168.41.171/group1/M00/00/13/wKgpq2ZZKp2ABhbMDZ6Pj1m7yF0717.zip";
URI newUri = new URI(target);
// 执行代理查询
String methodName = request.getMethod();
HttpMethod httpMethod = HttpMethod.resolve(methodName);
if (httpMethod == null) {
return;
}
InputStream stream = null;
String contentType = request.getContentType();
// 兼容文件上传的请求
if (contentType != null && contentType.startsWith("multipart/form-data")) {
MultipartHttpServletRequest mulReq = (MultipartHttpServletRequest) request;
Map<String, MultipartFile> map = mulReq.getFileMap();
List<MultipartFile> valueList = new ArrayList<MultipartFile>(map.values());
MultiValueMap<String, Object> params = new LinkedMultiValueMap();
for (MultipartFile file : valueList) {
File newFile = File.createTempFile("temp", file.getOriginalFilename());
FileUtils.copyInputStreamToFile(file.getInputStream(), newFile);
FileSystemResource resource = new FileSystemResource(newFile);
params.add(file.getName(), resource);
}
RestTemplate restTemplate = new RestTemplate();
// 设置请求头
HttpHeaders headers = new HttpHeaders();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
System.out.println(headerName + ":" + request.getHeader(headerName));
headers.set(headerName, request.getHeader(headerName));
}
// 手动设置请求头的token信息
headers.set("Authorization", request.getHeader("Authorization"));
// 用HttpEntity封装整个请求报文
HttpEntity<MultiValueMap<String, Object>> files = new HttpEntity<MultiValueMap<String, Object>>(params, headers);
String res = restTemplate.postForEntity(target, files, String.class).getBody();
InputStream is = new ByteArrayInputStream(res.getBytes("UTF-8"));
stream = is;
// 其他请求例如get post put delete都可使用
} else {
ClientHttpRequest delegate = new SimpleClientHttpRequestFactory().createRequest(newUri, httpMethod);
Enumeration<String> headerNames = request.getHeaderNames();
// 设置请求头
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
//System.out.println("所有参数:" + headerName + ":" + request.getHeader(headerName));
if(headerName.equalsIgnoreCase("range")){
System.out.println("range:分段大小"+ request.getHeader(headerName));
}
Enumeration<String> v = request.getHeaders(headerName);
List<String> arr = new ArrayList<>();
while (v.hasMoreElements()) {
arr.add(v.nextElement());
}
delegate.getHeaders().addAll(headerName, arr);
}
StreamUtils.copy(request.getInputStream(), delegate.getBody());
// 执行远程调用
ClientHttpResponse clientHttpResponse = delegate.execute();
response.setStatus(clientHttpResponse.getStatusCode().value());
// 设置响应头
clientHttpResponse.getHeaders().forEach((key, value) -> value.forEach(it -> {
response.setHeader(key, it);
}));
stream = clientHttpResponse.getBody();
}
// 将获取到的输入流再次输出到页面输出流中
StreamUtils.copy(stream, response.getOutputStream());
}catch(Exception e){
e.printStackTrace();
}
}
四、VUE的前端实现
获得文件大小:
export function getFileSize(data){
return request({
url: KHEVAL_URL + '/khRule/proxy',
method: 'HEAD',
data: data,
responseType: 'arraybuffer'
})
}
- 分块下载
export function downloadFile(options) {
return request({
url: KHEVAL_URL + '/khRule/proxy',
method: 'get',
isLoading: true,
responseType: 'arraybuffer',
headers:
{
'Range': `bytes=`+options,
}
})
}
async splitdownload(data) {
const CHUNK_SIZE = 1024 * 1024 * 10 // 每次下载10MB
this.showDownloadModal = true; // 开始下载前显示模态框
try {
await getFileSize().then(async (res) => {
// 获取所有响应头
const headers = res.headers;
// 访问特定的响应头,例如 'content-type'
const fileSize1 = headers['content-length'];
console.log("文件大小" + fileSize1);
let fileSize = Number(fileSize1)
let offset = 0;
let i = 0;
const fileStream = []
let totalChunks = Math.ceil(fileSize / CHUNK_SIZE);
this.downloadProgress = 0; // 初始化进度为0
while (offset < fileSize) {
const end = Math.min(offset + CHUNK_SIZE, fileSize)
const options = '' + offset + '-' + (end - 1) + '';
console.log("字段长度:" + options)
// 确保 downloadFile 返回一个包含 ArrayBuffer 的响应
const response = await downloadFile(options);
// 直接从响应的 data 属性获取 ArrayBuffer,并创建 Blob
const blob = new Blob([response.data], {type: 'application/zip;charset=UTF-8'}); // 根据实际文件类型调整MIME类型
fileStream.push(blob)
console.log("blob第" + i + "块,文件长度是:" + fileStream.length);
offset = end
// 更新进度
this.downloadProgress = Math.floor(((i + 1) / totalChunks) * 100);
i++
}
// 合并所有 Blob 片段
const mergedBlob = new Blob(fileStream, {type: 'application/zip;charset=UTF-8'});
ActionUtils.exportFile(
mergedBlob,
"heweiya22.zip"
);
// 创建下载链接并触发下载
// 下载完成后,确保进度为100%
this.downloadProgress = 100;
this.showDownloadModal = false;
// 下载完成后,可以在这里执行其他操作,比如提示用户下载完成
console.log("文件下载完成!");
this.$message({
showClose: true,
message: "文件下载完成!",
type: "success",
});
});
}catch (error) {
this.showDownloadModal = false;
this.$message({
showClose: true,
message: "文件在下载过程当中有错误产生,错误信息:"+error.message+",请重新下载!",
type: "error",
});
}
},
五、添加进度条
- 先写一个模态窗口前端
参考:Model.vue
<template>
<div v-if="showModal" class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">文件下载进度</slot>
</div>
<div class="modal-body">
<progress :value="value" max="100" class="progress"></progress>
<p>{{ message }}</p>
</div>
<div class="modal-footer">
<!-- <slot name="footer">-->
<!-- <button @click="closeModal">关闭</button>-->
<!-- </slot>-->
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
showModal: Boolean,
value: Number,
message: String
},
methods: {
closeModal() {
this.$emit('close');
}
}
};
</script>
<style scoped>
.modal-mask {
position: fixed;
z-index: 9999; /* 确保模态框在最前面 */
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5); /* 半透明背景 */
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
}
.modal-wrapper {
width: 30%; /* 或其他适合的宽度 */
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
overflow-y: auto;
display: flex;
flex-direction: column;
}
.modal-body{
border-bottom: 1px solid #eee;
background-color: #eee; /* 更改为你想要的颜色 */
padding: 20px; /* 可选,调整内边距 */
}
.modal-header {
border-bottom: 1px solid #eee;
color: #eee;
background-color: #026dc6; /* 更改为你想要的颜色 */
padding: 10px; /* 可选,调整内边距 */
}
.modal-footer {
border-top: 1px solid #eee;
text-align: right;
background-color: #026dc6; /* 更改为你想要的颜色 */
padding: 10px; /* 可选,调整内边距 */
}
.modal-body {
position: relative; /* 为进度条绝对定位提供上下文 */
overflow: hidden; /* 防止内容溢出 */
}
.progress {
position: absolute; /* 使用绝对定位 */
top: 10px; /* 调整位置 */
width: 95%; /* 确保进度条宽度为模态框宽度 */
height: 50px; /* 自定义进度条高度 */
margin-bottom: 0; /* 移除默认的外边距 */
}
/* 可能需要的其他样式 */
</style>
- 再在调用的页面添加模式窗口
<modal v-if="showDownloadModal"
:show-modal="showDownloadModal"
:value="downloadProgress"
:message="'已下载 '+downloadProgress+'%'"
@close="showDownloadModal = false">
<!-- 可以在这里自定义头部和底部内容 -->
</modal>
- 添加到下载逻辑里,显示下载进度
async splitdownload(data) {
const CHUNK_SIZE = 1024 * 1024 * 10 // 每次下载10MB
this.showDownloadModal = true; // 开始下载前显示模态框
try {
await getFileSize().then(async (res) => {
// 获取所有响应头
const headers = res.headers;
// 访问特定的响应头,例如 'content-type'
const fileSize1 = headers['content-length'];
console.log("文件大小" + fileSize1);
let fileSize = Number(fileSize1)
let offset = 0;
let i = 0;
const fileStream = []
let totalChunks = Math.ceil(fileSize / CHUNK_SIZE);
this.downloadProgress = 0; // 初始化进度为0
while (offset < fileSize) {
const end = Math.min(offset + CHUNK_SIZE, fileSize)
const options = '' + offset + '-' + (end - 1) + '';
console.log("字段长度:" + options)
// 确保 downloadFile 返回一个包含 ArrayBuffer 的响应
const response = await downloadFile(options);
// 直接从响应的 data 属性获取 ArrayBuffer,并创建 Blob
const blob = new Blob([response.data], {type: 'application/zip;charset=UTF-8'}); // 根据实际文件类型调整MIME类型
fileStream.push(blob)
console.log("blob第" + i + "块,文件长度是:" + fileStream.length);
offset = end
// 更新进度
this.downloadProgress = Math.floor(((i + 1) / totalChunks) * 100);
i++
}
// 合并所有 Blob 片段
const mergedBlob = new Blob(fileStream, {type: 'application/zip;charset=UTF-8'});
ActionUtils.exportFile(
mergedBlob,
"heweiya22.zip"
);
// 创建下载链接并触发下载
// 下载完成后,确保进度为100%
this.downloadProgress = 100;
this.showDownloadModal = false;
// 下载完成后,可以在这里执行其他操作,比如提示用户下载完成
console.log("文件下载完成!");
this.$message({
showClose: true,
message: "文件下载完成!",
type: "success",
});
});
}catch (error) {
this.showDownloadModal = false;
this.$message({
showClose: true,
message: "文件在下载过程当中有错误产生,错误信息:"+error.message+",请重新下载!",
type: "error",
});
}
}
注:修改原来的request调用方法,去掉
isLoading: true,
标签:vue,const,springboot,分块,request,nginx,new,下载,String From: https://blog.csdn.net/heweiyabeijing/article/details/140429399