一、文件上传
创建数据库表
application.yml
server:
port: 9090
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/springboot?serverTimezone=GMT%2b8
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.springboot.entity
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
files:
upload:
path: D:/project/vue2/files/
Files
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("sys_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;
}
FileMapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.springboot.entity.Files;
public interface FileMapper extends BaseMapper<Files> {
}
FileController
@RestController
@RequestMapping("/file")
public class FileController {
@Value("${files.upload.path}")
private String fileUploadPath;
@Resource
private FileMapper fileMapper;
/**
* 文件上传接口
*
* @param file 前端传过来的文件
* @return
*/
@PostMapping("/upload")
public String upload(@RequestParam MultipartFile file) throws IOException {
String originalFilename = file.getOriginalFilename();
String type = FileUtil.extName(originalFilename);
Long size = file.getSize();
//先存储数据库
File uploadParentFile = new File(fileUploadPath);
//判断配置文件目录是否存在,若不存在则新创建一个文件目录
if (!uploadParentFile.exists()) {
uploadParentFile.mkdirs();
}
//定义一个文件唯一的标识码
String uuid = IdUtil.fastSimpleUUID();
String fileUUID = uuid+StrUtil.DOT+type;
//文件加后缀 StrUtil.DOT + type
File uploadFile = new File(fileUploadPath + fileUUID);
//把获取到的文件存储到磁盘目录中
file.transferTo(uploadFile);
//文件路径
String url = "http://localhost:9090/file/" + fileUUID;
//存储到数据库
Files saveFile = new Files();
saveFile.setName(originalFilename);
saveFile.setType(type);
//将size转成kb
saveFile.setSize(size/1024);
saveFile.setUrl(url);
fileMapper.insert(saveFile);
return url;
}
}
二、文件下载
FileController
/**
* 文件下载路径 "http://localhost:9090/file/{fileUUID}"
* @param fileUUID
* @param response
* @throws IOException
* */
@GetMapping("/{fileUUID}")
public void download(@PathVariable String fileUUID, HttpServletResponse response) throws Exception{
//根据文件的唯一标识码获取文件
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();
}
三、文件上传去重
修改数据库
Files
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("sys_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;
}
FileController
@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 = dbFiles.getUrl();
} else {
// 上传文件到磁盘
file.transferTo(uploadFile);
// 数据库若不存在重复文件,则不删除刚才上传的文件
url = "http://localhost:9090/file/" + fileUUID;
}
// 存储数据库
Files saveFile = new Files();
saveFile.setName(originalFilename);
saveFile.setType(type);
saveFile.setSize(size/1024);
saveFile.setUrl(url);
saveFile.setMd5(md5);
fileMapper.insert(saveFile);
return url;
}
private Files getFileByMd5(String md5) {
//查询文件的md5是否存在
QueryWrapper<Files> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("md5", md5);
List<Files> filesList = fileMapper.selectList(queryWrapper);
return filesList.size() == 0 ? null : filesList.get(0);
}
四、文件管理
router/index.js
{
path: '/',
component: () => import('../views/Manage.vue'),
redirect: "/home",
children: [
{path: 'user', name: '用户管理', component: () => import('../views/User.vue'),},
{path: 'home', name: '首页', component: () => import('../views/Home.vue'),},
{path: 'person', name: '个人信息', component: () => import('../views/Person.vue'),},
{path: 'file', name: '文件管理', component: () => import('../views/Files.vue'),}
]
},
Aside.vue
<el-menu-item index="/file">
<i class="el-icon-document"></i>
<span slot="title">文件管理</span>
</el-menu-item>
File.vue
<template>
<div>
<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="load">搜索</el-button>
<el-button type="warning" @click="reset">重置</el-button>
</div>
<div style="margin: 10px 0">
<el-upload action="http://localhost:9090/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-popconfirm
class="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-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="文件大小" width="120"></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 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-popconfirm
class="ml-5"
confirm-button-text='确定'
cancel-button-text='我再想想'
icon="el-icon-info"
icon-color="red"
title="您确定删除吗?"
@confirm="del(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, 5, 10, 20]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Files",
data() {
return {
tableData: [],
name: "",
multipleSelection: [],
pageNum:1,
pageSize:5,
total:0
}
}, created() {
this.load()
},
methods: {
load() {
this.request.get("/file/selectPage", {
params: {
pageNum: this.pageNum,
pageSize: this.pageSize,
name: this.name
}
}).then(res => {
//注意data
this.tableData = res.data.records
this.total = res.data.total
})
},
changeEnable(row){
this.request.post("/file/update",row).then(res =>{
if (res.code ==='200') {
this.$message.success("更新成功")
}
})
},
del(id) {
this.request.delete("/file/" + id).then(res => {
if (res.code==='200') {
this.$message.success("删除成功")
this.load()
} else {
this.$message.error("删除失败")
}
})
},
handleSelectionChange(val) {
console.log(val)
this.multipleSelection = val
},
delBatch() {
let ids = this.multipleSelection.map(v => v.id) // [{}, {}, {}] => [1,2,3]
this.request.post("/file/del/batch", ids).then(res => {
if (res.data) {
this.$message.success("批量删除成功")
this.load()
} else {
this.$message.error("批量删除失败")
}
})
},
reset() {
this.name = ""
this.load()
},
handleSizeChange(pageSize) {
console.log(pageSize)
this.pageSize = pageSize
this.load()
},
handleCurrentChange(pageNum) {
console.log(pageNum)
this.pageNum = pageNum
this.load()
},
handleFileUploadSuccess(res){
console.log(res)
this.load()
},
download(url){
window.open(url)
}
}
}
</script>
<style>
</style>
FileController
//更新
@PostMapping("/update")
public Result update(@RequestBody Files files) {
//新增或修改
return success(fileMapper.updateById(files));
}
//分页查询 mybatis-plus方式
@GetMapping("/selectPage")
public Result selectPage(@RequestParam(defaultValue = "") String name,
@RequestParam Integer pageSize,
@RequestParam Integer pageNum) {
IPage<Files> page = new Page<>(pageNum,pageSize);
QueryWrapper<Files> queryWrapper = new QueryWrapper<>();
//查询未删除记录
queryWrapper.eq("is_delete",false);
queryWrapper.orderByDesc("id");
if (!"".equals(name)) {
queryWrapper.like("name", name);
}
return Result.success(fileMapper.selectPage(page,queryWrapper));
}
//删除
@DeleteMapping("/{id}")
public Result delete(@PathVariable("id") Integer id) {
Files files = fileMapper.selectById(id);
files.setIsDelete(true);
fileMapper.updateById(files);
return success();
}
//批量删除
//批量删除
@PostMapping("/del/batch")
public Result deleteBatch(@RequestBody List<Integer> ids) {
//select * from sys_file where id in (id,id,id...)
QueryWrapper<Files> queryWrapper = new QueryWrapper<>();
queryWrapper.in("id",ids);
List<Files> filesList = fileMapper.selectList(queryWrapper);
for (Files file : filesList){
file.setIsDelete(true);
fileMapper.updateById(file);
}
return success();
}
五、头像上传功能实现
Person.vue
<template>
<el-card style="width: 500px;">
<el-form label-width="80px" size="small">
<el-upload
class="avatar-uploader"
action="http://localhost:9090/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" disabled autocomplete="off"></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 type="textarea" 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 => {
this.form = res
})
},
methods: {
async getUser() {
return (await this.request.get("/user/findUserName/" + this.user.username)).data
},
save() {
this.request.post("/user/saveUser", this.form).then(res => {
if (res.code === '200') {
this.$message.success("保存成功")
//更新浏览器存储的用户信息
this.getUser().then(res => {
res.token = JSON.parse(localStorage.getItem("user")).token
localStorage.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>
六、解决修改个人信息之后头像不显示问题
Manage.vue
<el-container>
<el-header style="border-bottom: 1px solid #ccc;">
<Header :collapseBtnClass="collapseBtnClass" :collapse="collapse" :user="user"/>
</el-header>
<el-main>
<!--表示当前页面的子路由会在 <router-view /> 里面展示-->
<router-view @refreshUser="getUser" />
</el-main>
</el-container>
user: {}
created() {
//从后台获取数据
this.getUser()
},
getUser() {
let username =localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")).username:""
//从后台获取数据
this.request.get("user/findUserName/"+username).then(res=>{
//重新赋值后台的最新数据
this.user = res.data
})
}
Header.vue
user:Object
Person.vue
save() {
this.request.post("/user/saveUser", this.form).then(res => {
if (res.code === '200') {
this.$message.success("保存成功")
//触发父级更新User的方法
this.$emit("refreshUser")
//更新浏览器存储的用户信息
this.getUser().then(res =>{
res.token = JSON.parse(localStorage.getItem("user")).token
localStorage.setItem("user",JSON.stringify(res))
})
} else {
this.$message.error("保存失败")
}
})
},
七、缩略图实现
File.vue
<el-table-column label="缩略图">
<template slot-scope="scope">
<img :src="scope.row.url" style="height: 50px"/>
</template>
</el-table-column>
点击图片放大功能:
<el-table-column label="缩略图">
<template slot-scope="scope">
//使用propover弹出框,一定要加slot="reference"
<el-popover placement="top-start" trigger="click">
<img :src="scope.row.url" style="width: 300px;height: 500px">
<img slot="reference" :src="scope.row.url" style="height: 50px"/>
</el-popover>
</template>
</el-table-column>