1、建Files表
- 接下来开始完成文件管理的内容,首先是数据库建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;
- 在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;
}
- FilesMapper.java
@Repository
public interface FileMapper extends BaseMapper<Files> {
}
- FilesService.java
@Service
public class FilesService extends ServiceImpl<FilesMapper, Files> {
}
2、springboot实现文件上传
- 在配置文件中配置上传文件存入磁盘位置
files:
upload:
path: D:/IDEA/后台管理系统/files/
- FilesController.java
@RestController
@RequestMapping("/file")
public class FilesController {
@Autowired
private 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);
//获取文件的md5
String 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转化为kb
saveFile.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前端实现上传和下载
- 写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-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-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-popconfirm
class="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.records
this.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=pageSize
this.load()
},
handleCurrentChange(pageNum){
this.pageNum=pageNum
this.load()
},
handleFileUploadSuccess(res){
console.log(res)
this.load()
},
download(url){
window.open(url)
}
}
}
</script>
<style scoped>
</style>
- 注册路由
{
path:'file',
name:'文件管理',
component:()=>import('../views/Files.vue')
}
写在home的子路由下
-
实现增删改查功能,代码在上面Files.vue里
-
去后端写文件增删改查接口
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);
}
- 在页面上测试上传下载,增删改查功能
测试功能正常,成功~
5、实现用户头像上传
- 首先需要在数据库user表字段中添加一个avatarUrl的字段
- 在实体类User中添加avatarUrl属性对应
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
private String username;
//密码可以不展示, @JsonIgnore 意思就是给前端传数据时忽略某个字段
@JsonIgnore
private String password;
private String nickname;
private String email;
private String phone;
private String address;
private String avatarUrl;
}
- 在前端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>
- 在Person.vue里,添加上传头像功能
<template>
<el-card style="width:500px;padding:20px;">
<el-form label-width="80px" size="small">
<el-upload
class="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")).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>
- 测试
上传一张新的图片,并保存,刷新页面,看头像是否更新。更新则成功完成!(记住要和数据库files表中已经有的图片不一样)