当前位置: 首页 > news >正文

springboot + Vue前后端项目(第十一记)

项目实战第十一记

  • 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,感谢!!不懂的可以在评论区评论,有空会及时回复。
文章会一直更新

相关文章:

  • ArcGIS中离线发布路径分析服务,并实现小车根据路径进行运动
  • 【Spring Boot】在项目中使用Spring AI
  • Vue.js功能实现博客
  • Golang使用HTTP框架zdpgo_resty实现文件下载
  • [Linux打怪升级之路]-进程和线程
  • Web基础考点
  • vue中axios的使用
  • faster_whisper语音识别
  • jvm的类加载
  • 『USB3.0Cypress』FPGA开发(3)GPIF II短包零包时序分析
  • next.js 服务端组件 -客户端组件
  • 游戏子弹类python设计与实现详解
  • Java进阶学习笔记20——枚举
  • Windows安装并启动Redis服务端(zip包)
  • 三生随记——山洞之谜
  • Apache Pulsar 2.1 重磅发布
  • HashMap剖析之内部结构
  • iOS 颜色设置看我就够了
  • js算法-归并排序(merge_sort)
  • Spring Boot快速入门(一):Hello Spring Boot
  • Spring Cloud Alibaba迁移指南(一):一行代码从 Hystrix 迁移到 Sentinel
  • spring学习第二天
  • 阿里研究院入选中国企业智库系统影响力榜
  • 大数据与云计算学习:数据分析(二)
  • 关于Java中分层中遇到的一些问题
  • 基于Volley网络库实现加载多种网络图片(包括GIF动态图片、圆形图片、普通图片)...
  • 基于遗传算法的优化问题求解
  • 删除表内多余的重复数据
  • -- 数据结构 顺序表 --Java
  • 一个项目push到多个远程Git仓库
  • 深度学习之轻量级神经网络在TWS蓝牙音频处理器上的部署
  • MPAndroidChart 教程:Y轴 YAxis
  • 继 XDL 之后,阿里妈妈开源大规模分布式图表征学习框架 Euler ...
  • 小白应该如何快速入门阿里云服务器,新手使用ECS的方法 ...
  • ‌内网穿透技术‌总结
  • $.ajax,axios,fetch三种ajax请求的区别
  • (52)只出现一次的数字III
  • (c语言)strcpy函数用法
  • (Redis使用系列) Springboot 实现Redis 同数据源动态切换db 八
  • (zhuan) 一些RL的文献(及笔记)
  • (动态规划)5. 最长回文子串 java解决
  • (翻译)Quartz官方教程——第一课:Quartz入门
  • (剑指Offer)面试题34:丑数
  • (六)激光线扫描-三维重建
  • (一)pytest自动化测试框架之生成测试报告(mac系统)
  • (转)真正的中国天气api接口xml,json(求加精) ...
  • *上位机的定义
  • .ai域名是什么后缀?
  • .net wcf memory gates checking failed
  • .Net(C#)常用转换byte转uint32、byte转float等
  • .Net(C#)自定义WinForm控件之小结篇
  • .NET/C# 使用 ConditionalWeakTable 附加字段(CLR 版本的附加属性,也可用用来当作弱引用字典 WeakDictionary)
  • .NET项目中存在多个web.config文件时的加载顺序
  • /proc/interrupts 和 /proc/stat 查看中断的情况
  • @cacheable 是否缓存成功_让我们来学习学习SpringCache分布式缓存,为什么用?