项目实战第十一记
- 1.写在前面
- 2. 文件上传和下载后端
- 2.1 数据库编写
- 2.2 工具类CodeGenerator生成代码
- 2.2.1 FileController
- 2.2.2 application.yml
- 2.2.3 拦截器InterceptorConfig 放行
- 3 文件上传和下载前端
- 3.1 File.vue页面编写
- 3.2 路由配置
- 3.3 Aside.vue
- 最终效果图
- 总结
- 写在最后
1.写在前面
本篇主要讲解文件的上传和下载,进行前后端的实现
2. 文件上传和下载后端
2.1 数据库编写
CREATE TABLE `sys_file` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文件名称',
`type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文件类型',
`size` bigint(20) DEFAULT NULL COMMENT '文件大小(kb)',
`url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '下载链接',
`md5` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文件md5',
`is_delete` tinyint(1) DEFAULT '0' COMMENT '是否删除',
`enable` tinyint(1) DEFAULT '0' COMMENT '是否禁用链接',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
2.2 工具类CodeGenerator生成代码
其他的正常生成,需要application.yml改动和controller编写。需要注意的是File在java中重名了,换成Files即可(其他名称也行)
2.2.1 FileController
package com.ppj.controller;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ppj.entity.Files;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.List;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ppj.common.Result;
import com.ppj.service.IFileService;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
/**
* <p>
* 前端控制器
* </p>
*
* @author ppj
* @since 2024-05-21
*/
@RestController
@RequestMapping("/file")
public class FileController {
@Resource
private IFileService fileService;
@Value("${files.upload.path}")
private String fileUploadPath;
// 新增或者更新
@PostMapping
public Result save(@RequestBody Files file) {
fileService.saveOrUpdate(file);
return Result.success();
}
@DeleteMapping("/{fileIds}")
public Result delete(@PathVariable Integer[] fileIds) {
fileService.removeByIds(Arrays.asList(fileIds));
return Result.success();
}
@GetMapping
public Result findAll() {
return Result.success(fileService.list());
}
@GetMapping("/page")
public Result findPage(@RequestParam Integer pageNum,
@RequestParam Integer pageSize,
@RequestParam String name) {
QueryWrapper<Files> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name",name);
// queryWrapper.orderByDesc("id");
return Result.success(fileService.page(new Page<>(pageNum, pageSize), queryWrapper));
}
/**
* 文件上传接口
* @param file 前端传递过来的文件
* @return
* @throws IOException
*/
@PostMapping("/upload")
public String upload(@RequestParam MultipartFile file) throws IOException {
String originalFilename = file.getOriginalFilename();
String type = FileUtil.extName(originalFilename);
long size = file.getSize();
// 定义一个文件唯一的标识码
String uuid = IdUtil.fastSimpleUUID();
String fileUUID = uuid + StrUtil.DOT + type;
File uploadFile = new File(fileUploadPath + fileUUID);
// 判断配置的文件目录是否存在,若不存在则创建一个新的文件目录
File parentFile = uploadFile.getParentFile();
if(!parentFile.exists()) {
parentFile.mkdirs();
}
String url;
// 获取文件的md5
String md5 = SecureUtil.md5(file.getInputStream());
// 从数据库查询是否存在相同的记录
Files dbFiles = getFileByMd5(md5);
if (dbFiles != null) { // 文件已存在,直接返回数据库里的url
url = dbFiles.getUrl();
} else { // 文件不存在才生成url,保存数据至数据库
// 上传文件到磁盘
file.transferTo(uploadFile);
// 数据库若不存在重复文件,则不删除刚才上传的文件
url = "http://localhost:9000/file/" + fileUUID;
// 存储数据库
Files saveFile = new Files();
saveFile.setName(originalFilename);
saveFile.setType(type);
saveFile.setSize(size/1024);
saveFile.setUrl(url);
saveFile.setMd5(md5);
fileService.saveOrUpdate(saveFile);
}
return url;
}
/**
* 通过文件的md5查询文件
* @param md5
* @return
*/
private Files getFileByMd5(String md5) {
// 查询文件的md5是否存在
QueryWrapper<Files> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("md5", md5);
Files one = fileService.getOne(queryWrapper);
return one != null ? one : null;
}
/**
* 文件下载接口 http://localhost:9090/file/{fileUUID}
* @param fileUUID
* @param response
* @throws IOException
*/
@GetMapping("/{fileUUID}")
public void download(@PathVariable String fileUUID, HttpServletResponse response) throws IOException {
// 根据文件的唯一标识码获取文件
File uploadFile = new File(fileUploadPath + fileUUID);
// 设置输出流的格式
ServletOutputStream os = response.getOutputStream();
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileUUID, "UTF-8"));
response.setContentType("application/octet-stream");
// 读取文件的字节流
os.write(FileUtil.readBytes(uploadFile));
os.flush();
os.close();
}
// 开启和禁用其实就是更新
@PostMapping("/update")
public Result changeEnable(@RequestBody Files files){
return fileService.saveOrUpdate(files)?Result.success():Result.error();
}
}
2.2.2 application.yml
# 上传文件大小
spring:
servlet:
multipart:
max-file-size: 10MB
# 文件存储路径
files:
upload:
path: D:\实战项目\前后端分离\后台管理系统演示\files\
2.2.3 拦截器InterceptorConfig 放行
package com.ppj.config;
import com.ppj.config.interceptor.JwtInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
// 添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor())
.addPathPatterns("/**") // 拦截所有请求,通过判断token是否合法来决定是否需要登录
.excludePathPatterns("/user/login", "/user/register", "/*/export", "/*/import","/file/**",
"/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/api", "/api-docs", "/api-docs/**")
.excludePathPatterns( "/*/*.html", "/*/*.js", "/*/*.css", "/*/*.woff", "/*/*.ttf"); // 放行静态文件
}
@Bean
public JwtInterceptor jwtInterceptor() {
return new JwtInterceptor();
}
}
重点
- 为了避免存储重复的文件,使用md5作为文件唯一标识码
- 上传时要生成并存储这个文件的url,便于后面的下载和预览等等
3 文件上传和下载前端
3.1 File.vue页面编写
<template>
<div>
<div style="margin: 10px 0">
<el-input style="width: 200px" placeholder="请输入名称" suffix-icon="el-icon-search" v-model="name"></el-input>
<el-button class="ml-5" type="primary" @click="getList">搜索</el-button>
<el-button type="warning" @click="resetQuery">重置</el-button>
</div>
<div style="margin: 10px 0">
<el-upload action="http://localhost:9000/file/upload" :show-file-list="false" :on-success="handleFileUploadSuccess" style="display: inline-block">
<el-button type="primary" class="ml-5">上传文件 <i class="el-icon-top"></i></el-button>
</el-upload>
<el-button type="danger" class="ml-5" :disabled="multiple" @click="handleDelete">批量删除 <i class="el-icon-remove-outline"></i></el-button>
</div>
<el-table :data="tableData" border stripe :header-cell-class-name="'headerBg'" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="id" label="ID" width="80"></el-table-column>
<el-table-column prop="name" label="文件名称"></el-table-column>
<el-table-column prop="type" label="文件类型"></el-table-column>
<el-table-column prop="size" label="文件大小(kb)"></el-table-column>
<el-table-column label="下载">
<template v-slot="scope">
<el-button type="primary" @click="download(scope.row.url)">下载</el-button>
</template>
</el-table-column>
<!-- el-switch回显问题 -->
<el-table-column label="启用">
<template v-slot="scope">
<el-switch v-model="scope.row.enable"
:active-value="1"
:inactive-value="0"
active-color="#13ce66" inactive-color="#ccc" @change="changeEnable(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center">
<template v-slot="scope">
<el-button type="danger" @click="handleDelete(scope.row)">删除 <i class="el-icon-remove-outline"></i></el-button>
</template>
</el-table-column>
</el-table>
<div style="padding: 10px 0">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-sizes="[2, 5, 10, 20]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
</div>
</template>
<script>
export default {
name: "File",
data() {
return {
tableData: [],
name: '',
multiple: true,
pageNum: 1,
pageSize: 10,
total: 0,
ids: [],
}
},
created() {
this.getList()
},
methods: {
getList() {
this.request.get("/file/page", {
params: {
pageNum: this.pageNum,
pageSize: this.pageSize,
name: this.name,
}
}).then(res => {
this.tableData = res.data.records
this.total = res.data.total
})
},
handleSizeChange(val) {
this.pageSize = val;
},
handleCurrentChange(val) {
this.pageNum = val;
this.getList();
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id);
this.single = selection.length != 1;
this.multiple = !selection.length;
},
// 重置按钮
resetQuery(){
this.name = '';
this.getList();
},
changeEnable(row) {
this.request.post("/file/update", row).then(res => {
if (res.code === '200') {
this.$message.success("启用成功")
}
})
},
// 删除
handleDelete(row){
let _this = this;
const fileIds = row.id || this.ids;
this.$confirm('是否确认删除文件编号为"' + fileIds + '"的数据项?', '删除文件', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
_this.request.delete("/file/"+fileIds).then(res=>{
if(res.code === "200" || res.code === 200){
_this.$message.success("删除成功")
}else {
_this.$message.error("删除失败")
}
this.getList();
})
}).catch(() => {
});
},
handleFileUploadSuccess(res) {
// console.log(res)
this.getList()
},
download(url) {
window.open(url)
}
}
}
</script>
<style scoped>
</style>
3.2 路由配置
{
path: 'file',
name: '文件管理',
component: () => import('../views/File.vue'),
meta: {
title: '文件管理'
}
},
3.3 Aside.vue
<el-menu-item index="/file">
<i class="el-icon-files"></i>
<span slot="title">文件管理</span>
</el-menu-item>
最终效果图
总结
- 轻松实现文件上传和下载的前后端
写在最后
如果此文对您有所帮助,请帅戈靓女们务必不要吝啬你们的Zan,感谢!!不懂的可以在评论区评论,有空会及时回复。
文章会一直更新