14---实现文件上传和下载(头像上传功能)

news/2025/2/22 10:12:26/

1、建Files表

  1. 接下来开始完成文件管理的内容,首先是数据库建Files表
DROP TABLE IF EXISTS `file`;CREATE TABLE `file` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',`name` varchar(255) DEFAULT NULL COMMENT '文件名称',`type` varchar(255) DEFAULT NULL COMMENT '文件类型',`size` bigint(20) DEFAULT NULL COMMENT '文件大小(kb)',`url` varchar(255) DEFAULT NULL COMMENT '下载链接',`is_delete` tinyint(1) DEFAULT '0' COMMENT '是否删除',`enable` tinyint(1) DEFAULT '1' COMMENT '是否禁用链接',`md5` varchar(255) DEFAULT NULL COMMENT '文件md5',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
  1. 在springboot中写实体类Files.java
@Data
@TableName("file")
public class Files {@TableId(type = IdType.AUTO)private Integer id;private String name;private String type;private Long size;private String url;private Boolean isDelete;private Boolean enable;private String md5;}
  1. FilesMapper.java
@Repository
public interface FileMapper extends BaseMapper<Files> {
}
  1. FilesService.java
@Service
public class FilesService extends ServiceImpl<FilesMapper, Files> {}

2、springboot实现文件上传

  1. 在配置文件中配置上传文件存入磁盘位置
files:upload:path: D:/IDEA/后台管理系统/files/
  1. FilesController.java
@RestController
@RequestMapping("/file")
public class FilesController {@Autowiredprivate FilesService fileService;@Value("${files.upload.path}")private String fileUploadPath;@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;//上传文件到磁盘file.transferTo(uploadFile);//获取文件的md5String md5 = SecureUtil.md5(uploadFile);//从数据库查询是否存在相同的记录Files dbFiles = getFileMd5(md5);if (dbFiles!=null){url=dbFiles.getUrl();//由于文件已经存在,所以删除刚才上传的重复文件uploadFile.delete();}else {//数据库不存在重复文件,则不删除刚才上传的文件url="http://localhost:8081/file/"+fileUuid;}//存储数据库Files saveFile = new Files();saveFile.setName(originalFilename);saveFile.setType(type);saveFile.setSize(size/1024);  //从b转化为kbsaveFile.setUrl(url);saveFile.setMd5(md5);fileService.save(saveFile);return url;}
}
  • 打开swagger测试 --http://localhost:8081/swagger-ui.html

bug:打开swagger页面是,如果出现弹窗,无法打开swagger,可能是因为页面被拦截器拦截了,去拦截器中设置一下放行swagger就行:

InterceptorConfig.java

 .excludePathPatterns("/user/login","/user/register","/**/export","/**/import","/file/**","/swagger-resources/**","/webjars/**","/v2/**","/swagger-ui.html/**");  //拦截除登录注册、导入导出以外请求,通过判断token是否合法来决定是否需要登录  ,放行swagger}
  • 测试

在这里插入图片描述

点击测试后,返回一串链接,即fileUuid,下面就写文件下载接口,来下载文件

在这里插入图片描述

3、springboot实现文件下载

 //文件下载接口  就是前面上传后返回的url:http://localhost:8081/file/{fileUuid}@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();}//通过文件的md5查询文件private Files getFileMd5(String md5){//查询文件的md5是否存在QueryWrapper<Files>queryWrapper = new QueryWrapper<>();queryWrapper.eq("md5",md5);List<Files> filesList = fileService.list(queryWrapper);return filesList.size()==0?null:filesList.get(0);}
  • 测试下载接口直接复制上面上传返回的链接,网站打开链接,便能启动下载。
http://localhost:8081/file/6b556321437b4c22ab61369cb920c142.jpg

在这里插入图片描述

4、Vue前端实现上传和下载

  1. 写Files页面

Files.vue

<template><div><div style="padding: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="load">搜索</el-button><el-button  type="warning" @click="reset">重置</el-button></div><div style="margin:10px 0"><el-upload action="http://localhost:8081/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-popconfirmclass="ml-5"confirm-button-text='确定'cancel-button-text='我再想想'icon="el-icon-info"icon-color="red"title="您确定要删除这些内容吗?"@confirm="delBatch"><el-button type="danger"  slot="reference">批量删除<i class="el-icon-remove-outline"></i></el-button></el-popconfirm></div><el-table :data="tableData" border stripe :header-cell-calss-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 slot-scope="scope"><el-button type="primary" @click="download(scope.row.url)">下载</el-button></template></el-table-column><el-table-column prop="enable" label="启用"><template slot-scope="scope"><el-switch v-model="scope.row.enable" 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 slot-scope="scope" ><el-popconfirmclass="ml-5"confirm-button-text='确定'cancel-button-text='我再想想'icon="el-icon-info"icon-color="red"title="您确定要删除吗?"@confirm="handleDelete(scope.row.id)"><el-button type="danger" slot="reference">删除<i class="el-icon-remove-outline"></i></el-button></el-popconfirm></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, 4, 6, 10]":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:'',multipleSelection:[],pageNum:1,pageSize:10,total:0}},created(){this.load()},methods:{load(){this.request.get("/file/page",{params:{pageNum:this.pageNum,pageSize:this.pageSize,name:this.name}}).then(res=>{console.log(res)this.tableData=res.data.recordsthis.total=res.data.total })},changeEnable(row){this.request.post("/file/update",row).then(res =>{if(res.code==='200'){this.$message.success("操作成功");}})},handleDelete(id){this.request.delete("/file/" + id).then(res=>{if(res.code==='200'){this.$message.success("删除成功!")this.load()}else{this.$message.error("删除失败!")}})},delBatch(){let ids=this.multipleSelection.map(v => v.id)  //把对象数组转化为id数组【1,2,3】this.request.post("/file/del/batch",ids).then(res=>{if(res.code==='200'){this.$message.success("批量删除成功!")this.load()}else{this.$message.error("批量删除失败!")}})},handleSelectionChange(val){this.multipleSelection=val},reset(){this.name=""this.load()},handleSizeChange(pageSize){this.pageSize=pageSizethis.load()},handleCurrentChange(pageNum){this.pageNum=pageNumthis.load()},handleFileUploadSuccess(res){console.log(res)this.load()},download(url){window.open(url)}}
}
</script><style scoped></style>
  1. 注册路由
  {path:'file',name:'文件管理',component:()=>import('../views/Files.vue')}

写在home的子路由下

  1. 实现增删改查功能,代码在上面Files.vue里

  2. 去后端写文件增删改查接口

FilesController

 //分页查询@GetMapping("/page")public Result findPage(@RequestParam Integer pageNum,@RequestParam Integer pageSize,@RequestParam(defaultValue = "") String name){return Result.success(fileService.findPage(pageNum,pageSize,name));}//删除@DeleteMapping("/{id}")public Result delete(@PathVariable Integer id){Files files = fileService.getById(id);files.setIsDelete(true);fileService.updateById(files);return Result.success();}//批量删除@PostMapping ("/del/batch")public Result deleteBatch(@RequestBody List<Integer>ids){QueryWrapper<Files>queryWrapper=new QueryWrapper<>();queryWrapper.in("id",ids);List<Files> files = fileService.list(queryWrapper);for (Files file :files ) {file.setIsDelete(true);fileService.updateById(file);}return Result.success();}//更新一个字段@PostMapping("/update")public Result save(@RequestBody Files files){return Result.success(fileService.updateById(files));}

FilesService

 public IPage<Files> findPage(Integer pageNum,Integer pageSize,String  name) {QueryWrapper<Files> queryWrapper=new QueryWrapper<>();//查询未删除的记录queryWrapper.eq("is_delete",false);queryWrapper.orderByDesc("id");if (! "".equals(name)){queryWrapper.like("name",name);}return this.page(new Page<>(pageNum,pageSize),queryWrapper);}
  1. 在页面上测试上传下载,增删改查功能

在这里插入图片描述

测试功能正常,成功~

5、实现用户头像上传

  1. 首先需要在数据库user表字段中添加一个avatarUrl的字段

在这里插入图片描述

  1. 在实体类User中添加avatarUrl属性对应
public class User {@TableId(type = IdType.AUTO)private Integer id;private String username;
//密码可以不展示, @JsonIgnore 意思就是给前端传数据时忽略某个字段@JsonIgnoreprivate String password;private String nickname;private String email;private String phone;private String address;private String avatarUrl;
}
  1. 在前端Header页面中修改,昵称旁边要有头像的位置

Header.vue

 <div style="display:inline-block"><img :src="user.avatarUrl" alt=""style="width:30px;border-radius: 50%;position:relative;top: 10px; right: 5px"><span>{{user.nickname}}</span><i class="el-icon-arrow-down" style="margin-left:5px"></i></div>
  1. 在Person.vue里,添加上传头像功能
<template><el-card style="width:500px;padding:20px;"><el-form label-width="80px" size="small"><el-uploadclass="avatar-uploader"action="http://localhost:8081/file/upload":show-file-list="false":on-success="handleAvatarSuccess"><img v-if="form.avatarUrl" :src="form.avatarUrl" class="avatar"><i v-else class="el-icon-plus avatar-uploader-icon"></i></el-upload><el-form-item label="用户名" ><el-input v-model="form.username" autocomplete="off" disabled ></el-input></el-form-item><el-form-item label="昵称" ><el-input v-model="form.nickname" autocomplete="off"></el-input></el-form-item><el-form-item label="邮箱" ><el-input v-model="form.email" autocomplete="off"></el-input></el-form-item><el-form-item label="电话" ><el-input v-model="form.phone" autocomplete="off"></el-input></el-form-item><el-form-item label="地址" ><el-input v-model="form.address" autocomplete="off"></el-input></el-form-item><el-form-item><el-button type="primary" @click="save">确 定</el-button></el-form-item></el-form></el-card>
</template><script>
export default{name:"Person",data(){return{form:{},user:localStorage.getItem("user")?JSON.parse(localStorage.getItem("user")):{}}},created(){this.getUser().then(res=>{console.log(res)this.form=res})},methods:{async getUser(){return (await this.request.get("/user/username/"+this.user.username)).data},save(){this.request.post("/user",this.form).then(res=>{if(res.code==='200'){this.$message.success("保存成功!")//更新浏览器存储的用户信息this.getUser().then(res=>{res.token=JSON.parse(localStorage.getItem("user")).tokenlocalStorage.setItem("user",JSON.stringify(res))})}else{this.$message.error("保存失败!")}})},handleAvatarSuccess(res){this.form.avatarUrl=res}}
}</script><style>.avatar-uploader{text-align: center;padding-bottom: 10px;}.avatar-uploader .el-upload {border: 1px dashed #d9d9d9;border-radius: 6px;cursor: pointer;position: relative;overflow: hidden;}.avatar-uploader .el-upload:hover {border-color: #409EFF;}.avatar-uploader-icon {font-size: 28px;color: #8c939d;width: 138px;height: 138px;line-height: 138px;text-align: center;}.avatar {width: 138px;height: 138px;display: block;}
</style>
  1. 测试

在这里插入图片描述

上传一张新的图片,并保存,刷新页面,看头像是否更新。更新则成功完成!(记住要和数据库files表中已经有的图片不一样)


http://www.ppmy.cn/news/8960.html

相关文章

数字调制系列:如何理解IQ ?

最近在筹划写一系列关于数字IQ 调制的短文&#xff0c;以帮助初学者能够更好地理解和掌握。虽然IQ 调制技术已经非常广泛地应用于各种无线通信应用中&#xff0c;但是究其细节&#xff0c;仍有很多人存在疑惑&#xff0c;尤其对于初学者。作者从事测试工作多年&#xff0c;对IQ…

深入理解Linux网络:第三章-内核是如何与用户进程协作的

本章主要讲用户进程接收并处理数据&#xff0c;主要是介绍&#xff0c;同步阻塞和多路IO复用方案。 同步阻塞 一个进程维护一个链接&#xff0c;同时为了等待数据到来需要阻塞进程&#xff0c;还要切换进程上下文。 创建Socket进入内核态&#xff0c;开始 recv data没有当前s…

代码随想录算法训练营No7 |LeetCode454.四数相加II 383. 赎金信15. 三数之和18. 四数之和

哈希表2今天开始哈希表第二天做题&#xff0c;今天感觉有点难度&#xff01; 454.四数相加II 给你四个整数数组 nums1、nums2、nums3 和 nums4 &#xff0c;数组长度都是 n &#xff0c;请你计算有多少个元组 (i, j, k, l) 能满足&#xff1a; 0 < i, j, k, l < nnums…

Java 并发编程知识总结【五】

6. 线程中断与 LockSupport 6.1 线程中断机制 大厂&#xff08;蚂蚁金服&#xff09;面试题&#xff1a; 什么是中断&#xff1f; 首先&#xff0c;一个线程不应该由其他线程来强制中断或停止&#xff0c;而是应该由线程自己自行停止。所以&#xff0c;Thread.stop, Thread.…

阴道菌群——贯穿女性一生

阴道微生物组是一个复杂而动态的微生态系统&#xff0c;在女性月经周期和女性的一生中不断发生波动。 在过去几年中&#xff0c;对阴道微生物群关注随着测序技术的发展和应用逐渐广泛和突出&#xff0c;有关以往传统正常和异常阴道微生物组的知识也发生了变化。培养技术可能不再…

Java ——jdk(复习)

jdk 文件&#xff1a; bin 该路径下存放了各种工具命令&#xff0c; 其中重要的有javac 和Java conf&#xff1a; 改路径下存放了相关配置文件 include&#xff1a; 该路径下存放了一些平台特定的头文件 jmods; 该路径下存放了各种模块。 legal&#xff1a; 该路径下存放了各模…

java 枚举类型

1. Java 枚举类型 枚举类型是 Java 中一种类型&#xff0c;它允许您将一组常量声明在单独的类型中。枚举类型的每个实例都是这个类型的唯一实例。 例如&#xff0c;您可以使用枚举类型来表示四个季节&#xff1a; public enum Season {SPRING, SUMMER, FALL, WINTER }您可以…

【iOS】内存管理

文章目录前言理解引用计数引用计数原理属性存取方法中的内存管理自动释放池保留环以ARC简化引用计数使用ARC时必须遵守的命名规则变量的内存管理语义ARC如何清理实例变量覆写内存管理的方法在dealloc方法中只释放应用并解除监听编写“异常安全代码”时留意内存管理问题以弱引用…