项目实战第十一记
- 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 {@Resourceprivate IFileService fileService;@Value("${files.upload.path}")private String fileUploadPath;// 新增或者更新@PostMappingpublic 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();}@GetMappingpublic 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;// 获取文件的md5String md5 = SecureUtil.md5(file.getInputStream());// 从数据库查询是否存在相同的记录Files dbFiles = getFileByMd5(md5);if (dbFiles != null) { // 文件已存在,直接返回数据库里的urlurl = 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 {// 添加拦截器@Overridepublic 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"); // 放行静态文件}@Beanpublic 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.recordsthis.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,感谢!!不懂的可以在评论区评论,有空会及时回复。
文章会一直更新