文章目录

  • 前言
  • 一、项目现阶段部分效果演示
  • 二、项目数据库部分表设计
    • 2.1. 创建user表、file表、logs表等sql语句
    • 2.2 FileMapper.xml和FileMapper接口
  • 三、项目前端页面开发
    • 3.1. 前端主要页面html部分源码:
    • 3.2. javascript部分
  • 四、后端接口开发
    • 4.1. 文件工具类
    • 4.2. restful接口(restcontroller类)
    • 4.3. FileService文件服务接口
  • 五、总结
  • 六、更新

前言

因为开发中很多时候都需要上传和下载文件,所以想开发出一个模块,用于文件的上传和下载,自然而然也就想到了网盘,因为是个人开发,所以版本项目应该会持续迭代,优化,此外,技术栈是springboot+mybatis+jquery+bootstrap。以下是第一版本的个人网盘项目介绍,主要介绍开发的过程,以及一些源码的分享。


一、项目现阶段部分效果演示

1.登录(浏览器输入http://localhost:8080/):

2.登录成功后进入个人网盘页面(默认加载视频文件列表):

3.文件上传:

4.音视频预览:

5.图片预览:


6.文档预览,包括office文档的预览功能,支持的格式有(.txt,.xml,.html,.jsp,.java,doc,docx,ppt,xlsx等):


word文档预览:


excel文档预览:


ppt预览:


其它文档预览:

7.删除,分享,下载等操作:

二、项目数据库部分表设计

2.1. 创建user表、file表、logs表等sql语句

--  角色表
create table if not exists role(role_id int primary key not null auto_increment comment '角色ID',role_name varchar(128) unique not null comment '角色名称'
);-- 用户表
create table if not exists user (user_id int primary key not null auto_increment comment '用户ID,主键自增',user_name varchar(32) not null unique comment '用户名',password varchar(128) not null comment '密码',real_name varchar(128) default null comment '真实姓名',email varchar(128) default null comment '电子邮箱',telephone char(11) default null comment '手机号码',introduction varchar(300) default null comment '个人简介',address varchar(128) default null comment '住址',sex char(2) default '男' not null comment '性别',picture varchar(128)    default null comment '用户头像存放路径',is_active int default null comment '状态,0:未激活;1:正常;2:禁用',disk_size varchar(128) default '10G' comment '网盘大小',used_size varchar(128) default '0' comment '已用大小',create_time datetime default null comment '创建时间',role_id int comment '角色',foreign key(role_id) references role(role_id)
);
-- 文件表
create table if not exists file (file_id int primary key not null auto_increment comment '文件ID,主键自增',file_name varchar(300) default null comment '文件名',file_path varchar(300) default null comment '存储路径',file_size varchar(200) default null comment '文件大小',file_type varchar(120) default null comment '文件类型',download_counts integer default null comment '文件下载次数',upload_time datetime default null comment '上传时间',modify_time datetime default null comment '修改时间',is_delete int comment '是否被删除',is_folder int comment '是否是文件夹',file_md5 varchar(300) default null comment'文件md5',parent_id int default null comment '父级目录ID',user_id int not null comment '用户id',foreign key (user_id) references user(user_id),foreign key (parent_id) references file(file_id)
);
-- 分享文件表
create table if not exists sharefiles(share_id int primary key not null auto_increment comment '文件分享ID,主键自增',share_link varchar(300) not null comment '文件分享链接',is_cancel int default null comment '是否取消分享',share_time datetime default null comment '分享时间',file_id int comment '文件ID',foreign key (file_id) references file(file_id)
);
-- 日志表
create table if not exists logs(log_id int primary key not null auto_increment comment '日志Id主键自增',message varchar(300) default null comment '消息名称',operation varchar(300) default null comment '操作',operate_time datetime default null comment '操作日期',exception varchar(300) default null comment '异常信息',log_level varchar(300) default null comment '日志级别',user_id int not null comment '用户ID',foreign key(user_id) references user(user_id)
);

2.2 FileMapper.xml和FileMapper接口

FileMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.filemanager.mapper.FileMapper"><!--一对一级联,即一个文件对应一个用户 --><resultMap type="File" id="FileMap"><id property="file_id" column="file_id"/><result property="file_name" column="file_name"/><result property="file_path" column="file_path"/><result property="file_size" column="file_size"/><result property="file_type" column="file_type"/><result property="download_counts" column="download_counts"/><result property="upload_time" column="upload_time"/><result property="is_delete" column="is_delete"/><!-- 映射User属性 --><association property="user" javaType="com.filemanager.entity.User"><id property="user_id" column="user_id"/><result property="user_name" column="user_name"/><result property="password" column="password"/><result property="sex" column="sex"/></association></resultMap><select id="countFileNum" resultType="int">select count(*) from file</select><select id="findFakeDeleteFiles" parameterType="int" resultMap="FileMap">select * from file f,user u where u.user_id = #{user_id} and u.user_id = f.user_id and f.is_delete =1</select><select id="findAllByUserId" parameterType="int" resultMap="FileMap">select * from file f,user u where u.user_id = #{user_id} and u.user_id = f.user_id and f.is_delete =0</select><select id="findAllByType" resultMap="FileMap">select * from file f,user uwhere f.user_id = #{user_id} and f.file_type = #{file_type} and f.user_id = u.user_id and f.is_delete =0</select><select id="findById" parameterType="int" resultMap="FileMap">select * from file f,user uwhere f.file_id =#{file_id} and u.user_id = f.user_id and f.is_delete =0</select><select id="getMaxId" resultType="int">select max(file_id) from file</select><insert id="addFile" parameterType="com.filemanager.entity.File">insert into file(file_id,file_name,file_path,file_size,file_type,download_counts,upload_time,is_delete,user_id)values(#{file_id},#{file_name},#{file_path},#{file_size},#{file_type},#{download_counts},#{upload_time},#{is_delete},#{user.user_id});</insert><delete id="deleteFile" parameterType="int" >delete from file where file_id = #{file_id}</delete><update id="fakeDeleteFile" parameterType="int" >update fileset is_delete=#{is_delete}where file_id = #{file_id} </update><update id="updateFile" parameterType="com.filemanager.entity.File" >update fileset file_id = #{file_id},file_name = #{file_name},file_path = #{file_path},file_path = #{file_path},file_size = #{file_size},file_type = #{file_type},download_counts = #{download_counts},upload_time = #{upload_time},is_delete=#{is_delete},user_id = #{user.user_id}where file_id = #{file_id} </update></mapper>

FileMapper接口:

/*** */
package com.filemanager.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.filemanager.entity.File;
/*** @author chenyujie* @Time   2020年8月19日下午6:40:26*/
@Mapper    //指定这是一个操作数据库的mapper,
//使用这个注解之后,可以不再启动类上加上@MapperScan; 当然加上@MapperScan之后,也可以不用这个注解
public interface FileMapper {List<File> findFakeDeleteFiles(int user_id);//查询某个用户放入回收站的文件List<File> findAllByUserId(int user_id);//查找某个用户的所有文件,不包括放入回收站的文件List<File> findAllByType(@Param("user_id")int user_id,@Param("file_type")String file_type);//查询用户某个类型的所有文件,不包括放入回收站的文件File findById(int file_id);//根据文件id查找文件,不包括放入回收站的文件public int addFile(File file);//增加文件public int deleteFile(int file_id);//删除单个文件,真正从数据库中删除public int deleteFiles(int user_id);//删除某用户的所有文件,真正从数据库中删除public int fakeDeleteFile(int file_id,int is_delete);//将文件设置为已删除或者不删除,等价于放入回收站或者移出回收站,而不是真正的删除public int updateFile(File file);//更新文件public int getMaxId();public int countFileNum();//统计数据库中文件的数量
}

三、项目前端页面开发

3.1. 前端主要页面html部分源码:

<body style="margin-top:30px ; height:500px; overflow:scroll;" ><div class="container" style="margin-top:30px ;"><div class="row"><div class="col-md-2 column"><figure class="figure"><img alt="140x140" src="v3/default3.jpg" class="figure-img img-fluid rounded-circle" /><figcaption class="figure-caption text-center">admin</figcaption></figure><ul class="nav nav-pills flex-column" ><li class="nav-item"><a class="nav-link active" href="#" id="video"  value="video" onclick="selectType(this.id)">视频</a></li><li class="nav-item"><a class="nav-link" href="#" id="audio"  value="audio" onclick="selectType(this.id)">音频</a></li><li class="nav-item"><a class="nav-link" href="#" id="documents"  value="documents" onclick="selectType(this.id)">文档</a></li><li class="nav-item"><a class="nav-link" href="#" id="image"  value="image" onclick="selectType(this.id)">图片</a></li><li class="nav-item"><a class="nav-link " href="#" id="other"  value="other" onclick="selectType(this.id)">其它</a></li><li class="nav-item"><a class="nav-link " href="#" id="favorites"  value="favorites" onclick="selectType(this.id)">收藏</a></li><li class="nav-item"><a class="nav-link " href="#" id="recycle"  value="recycle" onclick="selectType(this.id)">回收站</a></li><li class="nav-item"><a class="nav-link " href="#" id="site_resources"  value="site_resources" onclick="selectType(this.id)">站内资源</a></li><li class="nav-item"><a class="nav-link " id="exit"  value="exit" onclick="selectType(this.id)" href="#">退出</a></li></ul></div><div class="col-md-10 column" id="file_scan_div"><h3>我的云盘</h3><!-- 下面部分是模态框预览视频div,放在此处是为了能够更好的再页面显示 --><button type="button" hidden="hidden" class="btn btn-primary" id="myButton" data-toggle="modal" data-target=".bs-example-modal-lg">播放视频</button><div class="modal fade bs-example-modal-lg modal-body" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" id="myModal"  ><div class="modal-dialog modal-lg" role="document"><!-- 模态框头部 --><div class="modal-header"><h5 class="modal-title text-warning" id="showResourceName">模态框头部</h5><button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button></div><!-- modal content--><div class="modal-content"><div class="modal-body" width="auto" height="500px" style="padding:0" id="modal-body"><video id = "videoPlay" width="100%" height="500px" controls="controls"  preload="none"  ><source src=""  type="video/mp4"><source src="" type="video/ogg"><source src="" type="video/webm">你的浏览器不支持该视频播放!请换过浏览器或者升级!</video></div></div></div></div><!--模态框div--- --><!-- 非模态框部分div --><div id="unmodal_div"><form><div class="input-group mb-3"><input  type="text" id="lastName" class="form-control" placeholder="输入查询内容/文件链接"><button id="search" class="btn btn-outline-success my-2 my-sm-0" type="button">查询</button><button onclick="upload()" class="btn btn-outline-success my-2 my-sm-0" type="button">上传</button></div>  <div id="static_button" class="btn-group" role="group" aria-label="Basic example" style="display: none;"><button type="button" class="btn btn-secondary">分享</button><button type="button" class="btn btn-secondary" onclick='downloadAllfiles()'>下载</button><button type="button" class="btn btn-secondary" onclick="deleteAllSelectedFiles()">删除</button></div></form>      <br> <table id="files_table" class="table table-hover"><thead><tr><th><div class="custom-control custom-checkbox check_box" ><input type="checkbox" class="custom-control-input" id="checkboxAll" onchange="selectAll()"><label class="custom-control-label" for="checkboxAll"></label></div>                          </th><th class="fileName">文件名</th><th>大小</th><th>下载次数</th><th class="date_time">上传时间</th><th>预览</th><th style="width:150px">分享/下载/删除</th></tr></thead><tbody></tbody></table><div class="row justify-content-center"><div class="align-self-center"><ul class="pagination"><li class="page-item"><a class="page-link" href="#">上一页</a></li><li class="page-item active"><a class="page-link" href="#">1</a></li><li class="page-item"><a class="page-link" href="#">2</a></li><li class="page-item"><a class="page-link" href="#">3</a></li><li class="page-item"><a class="page-link" href="#">下一页</a></li></ul></div></div></div></div><div id="file_upload_div" style="display: none;" ><form  enctype="multipart/form-data"><h3>文件上传:</h3><br><button id="scanFile" type="button" class="btn btn-info" onclick="selectFile()">浏览</button><button id="uploadFile" type="button" class="btn btn-info" onclick="uploadFiles()">上传</button><br><h4>已选文件:</h4><br><input id="hidden_input" type="file" style="display: none;"/><table class="table table-hover" id="display_files"><tbody></tbody></table></form></div></div></div><div class="jumbotron text-center" style="margin-bottom:0"><p>有问题请发邮件至@2023748976@qq.com</p></div>
</body>
</html>

3.2. javascript部分

    <script>var files;//文件对象var type = "video";//左侧栏里面的类型var lastLinkId = 'video';//左侧栏里面上个点击的链接id//文件删除部分function fakeDeleteFile(id){var index = parseInt(id.split('_')[2]);var file_id = files[index].file_id;$.ajax({type: "delete",url: "http://localhost:8080/api/v1/files/gabage/"+file_id,success: function (response) {        alert("删除"+files[index].file_name+"成功!");}});}//删除所有选中的文件function deleteAllSelectedFiles(){var files_id = new Array();//此处只能这样定义,然后用Push添加数据for(var i =0;i<files.length;i++){if(document.getElementById("checkbox"+i).checked == true){files_id.push(files[i].file_id);}}console.log(files_id);$.ajax({type: "delete",url: "http://localhost:8080/api/v1/files/gabages/"+files_id,traditional:true, //默认false,加入traditional防止深度序列化success: function (response) {        alert("批量删除成功!");}});}//文件下载部分jsfunction downloadFile(id){var index = parseInt(id.split('_')[2]);var file_id = files[index].file_id;window.location.href= "http://localhost:8080/api/v1/files/singleFile/"+file_id;}//下载当前所有文件function downloadAllfiles(){for(var i =0;i<files.length;i++){if(document.getElementById("checkbox"+i).checked == true){var file_id = files[i].file_id;window.location.href= "http://localhost:8080/api/v1/files/singleFile/"+file_id;}}}//初始化页面数据$(document).ready(function(){searchFiles();}); //选左侧边框function selectType(id){type = $('#'+id).attr('value');$('#'+id).attr('class','nav-link active');$('#'+lastLinkId).attr('class','nav-link');lastLinkId = id;//记录点击的链接idshowScanFileDiv();//显示浏览文件信息divif(type == 'favorites'){//收藏searchFiles();}else if(type == 'recycle'){//回收站searchFiles();}else if(type == 'site_resources'){//站内资源searchFiles();}else if(type == 'exit'){//退出searchFiles();}else if(type == 'image'){//音视频,文档,图片,其它searchImageInfo();}else{searchFiles();}}//选择全部复选框function selectAll(){//将所有的下标选上for(let i=0;i<files.length;i++){// $('#checkbox'+i+'').checked = true;//此句无法生效document.getElementById("checkbox"+i).checked = document.getElementById("checkboxAll").checked;}//使隐藏的三个按钮显现,或者隐藏if(document.getElementById("checkboxAll").checked == true)$("#static_button").attr("style","display:block;");//显示div;else$("#static_button").attr("style","display:none;");//隐藏div}//点击左侧链接后触发查询函数function searchFiles(){var functionType;if(type == 'documents'){functionType = 'preViewDocuments(this.id)';}else if(type =='video'|| type =='audio'){functionType = 'previewVideo(this.id)';}else{functionType = 'preViewDocuments(this.id)';//functionType = 'unPreview()';}$.ajax({type: "get",url: "http://localhost:8080/api/v1/files/type?user_id="+1+"&file_type="+type,success: function (response) {        files = response;//先清空表格内容$("#files_table tbody").html("");//再往table 中添加数据let tbody;for(let i =0;i<files.length;i++){tbody +="<tr>";tbody += "<td> <div class ='custom-control custom-checkbox'><input type='checkbox' class='custom-control-input' id='"+'checkbox'+i+ "'><label class='custom-control-label' for='"+'checkbox'+i+ "'></label> </div> </td>" + " <td>"+files[i].file_name+"</td>"+" <td>"+files[i].file_size+"</td>"+" <td>"+files[i].download_counts+"</td>"+" <td>"+dateToString(files[i].upload_time)+"</td>"+" <td>"+"<button id='"+"preview_"+i+"' class='btn btn-link' οnclick='"+functionType+"'>预览</button>"+"</td>"+" <td>"+"<div class='dropdown show'>"+ "<button  class='btn  btn-link dropdown-toggle' type='button' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'>操作</button >" +"<div class='dropdown-menu' aria-labelledby='dropdownMenuLink'>"+"<button  class='dropdown-item' type='button' id='"+'dropdown_share_'+i+"'>分享</button >"+    "<button  class='dropdown-item' type='button' id='"+'dropdown_download_'+i+"' οnclick='downloadFile(this.id)'>下载</button >"+"<button  class='dropdown-item' type='button' id='"+'dropdown_delete_'+i+"' οnclick='fakeDeleteFile(this.id)'>删除</button >"+"</div>"+"</div>"+"</td>";}$('#files_table tbody').append(tbody);}});}//无法预览的类型function unPreview() {alert('该类型暂时无法预览!');return;}//将date型转换为string function dateToString(date_time){ var datetime = new Date(date_time);var year = datetime.getFullYear();var month = datetime.getMonth()+1;//js从0开始取 var date = datetime.getDate(); var hour = datetime.getHours(); var minutes = datetime.getMinutes(); var second = datetime.getSeconds();if(month<10){month = "0" + month;}if(date<10){date = "0" + date;}if(hour <10){hour = "0" + hour;}if(minutes <10){minutes = "0" + minutes;}if(second <10){second = "0" + second ;}var time = year+"-"+month+"-"+date+" "+hour+":"+minutes+":"+second; return time;}//文件上传部分js函数var filesArray = [];//存放文件数组//用于切换div,显示上传页面function upload(){hideScanFileDiv();}//隐藏上传div,显示浏览文件divfunction showScanFileDiv(){$("#file_upload_div").attr("style","display:none;");//隐藏div$("#file_scan_div").attr("style","display:block;");//显示div;}//显示上传div,隐藏浏览文件divfunction hideScanFileDiv(){$("#file_scan_div").attr("style","display:none;");//隐藏div$("#file_upload_div").attr("style","display:block;");//显示div;}function selectFile(){//间接调用input = file,目的是改变默认按钮名字$('#hidden_input').click();}//真正的input= file被点击事件$("#hidden_input").change(function (even) {// 获取inputvar fileName = $(this).val();var file = $(this)[0].files[0];//是否选择了文件if (fileName != '') {filesArray.push(file);//保存选择的文件// $("#display_files").append("<div><p>" + fileName + "</p></div>");$('#display_files tbody').append("<tr><td>"+fileName+"</td></tr>");}});//上传文件function uploadFiles(){var form = new FormData();//获取user信息var user = {user_id:1,user_name:'admin',password:'123456',sex:'男',files:null};//遍历数据,手动注入formDatafor (var i = 0; i < filesArray.length; i++) {let tempFile = filesArray[i];if(tempFile != "-1"){//-1表示被删除了form.append("files", tempFile);}}form.append("user", JSON.stringify(user));$.ajax({type: 'POST',url: 'http://localhost:8080/api/v1/files/multipleFiles',data: form,processData: false,     // 告诉jquery要传输data对象,必须设置,保证你提交的数据和后台返回是同步的contentType: false,     // 告诉jquery不需要增加请求头对于contentType的设置success: function (arg) {alert('上传成功!');//清空之前的文件选择信息$("#display_files tbody").html("");filesArray = [];}})}//视频预览部分js函数//当点击按钮的时候,将video的src更换function previewVideo(id){$('#unmodal_div').attr("style","display:none;");//隐藏非模态框div//根据出入的按钮id获取files数组下标 var index = id.split('_');var file_id = files[parseInt(index[1])].file_id;//清空模态框$('#modal-body').html('');//添加video控件$('#modal-body').append("<video id = 'videoPlay' width='100%'' height='500px' controls='controls'  preload='none' >"+"<source  type='video/mp4'> <source type='video/ogg'> <source  type='video/webm'>"+"你的浏览器不支持该视频播放!请换过浏览器或者升级!</video>");$('#videoPlay').attr('src','http://localhost:8080/api/v1/files/videos/'+file_id);//显示视频标题document.getElementById('showResourceName').innerHTML = '视频名称:'+files[parseInt(index[1])].file_name;//触发模态框点击$('#myButton').click();}$('#myModal').on('hidden.bs.modal', function (e) {//模态框隐藏的时候,暂停视频播放var myVideo = document.getElementById("videoPlay");   //获取视频video$("#unmodal_div").attr("style","display:block;");//显示非模态框div;if(myVideo != null){myVideo.pause();//$('#myModal').modal('dispose');}})//图片处理js//查找图片信息function searchImageInfo(){$.ajax({type: "get",url: "http://localhost:8080/api/v1/files/type?user_id="+1+"&file_type="+type,success: function (response) {        files = response;//先清空表格内容$("#files_table tbody").html("");//再往table 中添加数据let tbody;for(let i =0;i<files.length;i++){tbody +="<tr>";tbody += "<td> <div class ='custom-control custom-checkbox'><input type='checkbox' class='custom-control-input' id='"+'checkbox'+i+ "'><label class='custom-control-label' for='"+'checkbox'+i+ "'></label> </div> </td>" + " <td>"+files[i].file_name+"</td>"+" <td>"+files[i].file_size+"</td>"+" <td>"+files[i].download_counts+"</td>"+" <td>"+dateToString(files[i].upload_time)+"</td>"+" <td>"+"<img src='http://localhost:8080/api/v1/files/images/"+files[i].file_id+"' id=' "+"image_"+i+"' οnclick='dealImageClick(this.id)' class='img-fluid' alt='Responsive image'>"+"</td>"+//此处加载图片" <td>"+"<div class='dropdown show'>"+ "<button  class='btn  btn-link dropdown-toggle' type='button' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'>操作</button >" +"<div class='dropdown-menu' aria-labelledby='dropdownMenuLink'>"+"<button  class='dropdown-item' type='button' id='"+'dropdown_share_'+i+"'>分享</button >"+    "<button  class='dropdown-item' type='button' id='"+'dropdown_download_'+i+"' οnclick='downloadFile(this.id)'>下载</button >"+"<button  class='dropdown-item' type='button' id='"+'dropdown_delete_'+i+"' οnclick='fakeDeleteFile(this.id)'>删除</button >"+"</div>"+"</div>"+"</td>";}$('#files_table tbody').append(tbody);}});}//图片点击事件处理function dealImageClick(imageId){var index  = imageId.split('_')[1];$('#modal-body').html('');//清空模态框body中的内容$('#unmodal_div').attr("style","display:none;");//隐藏非模态框div$('#modal-body').append("<img src='http://localhost:8080/api/v1/files/images/"+files[parseInt(index)].file_id+"' class='img-fluid' width='100%' height='500px' alt='Responsive image'>");//显示图片标题document.getElementById('showResourceName').innerHTML = '图片名称:'+files[parseInt(index)].file_name;//触发模态框点击$('#myButton').click();}//文档显示js函数function preViewDocuments(id){var index  = id.split('_')[1];$('#modal-body').html('');//清空模态框body中的内容var file_id = files[parseInt(index)].file_id;var documentString ='';$.ajax({type: "GET",url: "http://localhost:8080/api/v1/files/text/"+file_id,dataType: "text",success: function (response) {     documentString = response;if(documentString == 'can not preview'){//文件类型无法预览unPreview();}else{$('#unmodal_div').attr("style","display:none;");//隐藏非模态框div//下面模态框的设置必须放在下面,不然会失效,因为ajax是异步发送消息的$('#modal-body').append("<div class='text-light bg-dark' style='height:500px; overflow:scroll;' <textarea id='textcontent'>>"+"</textarea></div>");document.getElementById('textcontent').innerText = ""+documentString;//显示文档标题document.getElementById('showResourceName').innerHTML = '文档名称:'+files[parseInt(index)].file_name;//触发模态框点击$('#myButton').click();}}})}</script>

四、后端接口开发

4.1. 文件工具类

public class FileTools {/*** 存储文件到系统* @param file 文件,path 文件路径* @return 文件名*/public static String storeFile(MultipartFile file,String path) {String fileName = StringUtils.cleanPath(file.getOriginalFilename());Path fileStorageLocation = Paths.get(path).toAbsolutePath().normalize();try {if(fileName.contains("..")) {throw new FileException("文件路径出错...." + fileName);}Path targetLocation = fileStorageLocation.resolve(fileName);Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);return fileName;} catch (IOException ex) {throw new FileException("存储文件失败 ...." + fileName + "请重试", ex);}}/*** 加载文件* @param path 文件路径* @return 文件资源*/public static Resource loadFileAsResource(String file_name) {try {Path filePath = Paths.get(file_name).toAbsolutePath().normalize();String fileName = filePath.getFileName().toString();Resource resource = new UrlResource(filePath.toUri());if(resource.exists()) {return resource;} else {throw new FileException("文件不存在 " + fileName);}} catch (MalformedURLException  ex) {throw new FileException("文件找不到 ...." + ex);}}/*** 根据文件后缀判断文件类型* @param 文件名* @return 该文件所属类型*/public static String transforType(String fileName) {if(fileName == null){fileName = "emptyFileName";return fileName;}else {//获取文件后缀名并转化为小写,用于后续比较String fileType = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length()).toLowerCase();//创建图片类型数组String img[] = { "bmp", "jpg", "jpeg", "png", "tiff", "gif", "pcx", "tga", "exif", "fpx", "svg", "psd","cdr", "pcd", "dxf", "ufo", "eps", "ai", "raw", "wmf" };for(int i = 0; i < img.length; i++){if (img[i].equals(fileType)) {return "image";}}//创建文档类型数组String document[] = { "txt", "doc", "docx", "xls","xlsx", "htm", "html", "jsp", "rtf", "wpd", "pdf", "ppt"};for (int i = 0; i < document.length; i++) {if (document[i].equals(fileType)) {return "documents";}}// 创建视频类型数组String video[] = { "mp4", "avi", "mov", "wmv", "asf", "navi", "3gp", "mkv", "f4v", "rmvb", "webm" };for (int i = 0; i < video.length; i++) {if (video[i].equals(fileType)) {return "video";}}// 创建音频类型数组String music[] = { "mp3", "wma", "wav", "mod", "ra", "cd", "md", "asf", "aac", "vqf", "ape", "mid", "ogg","m4a", "vqf" };for (int i = 0; i < music.length; i++) {if (music[i].equals(fileType)) {return "audio";}}}return "other";}/*** 根据文件字节大小,转换成相应的B,KB,MB,GB* @param 文件的字节大小* @return 转换单位后的文件大小*/public static String transforSize(long size) {//获取到的size bites,表示字节大小int GB = 1024 * 1024 * 1024;//定义GB的计算常量int MB = 1024 * 1024;//定义MB的计算常量int KB = 1024;//定义KB的计算常量DecimalFormat df = new DecimalFormat("0.00");//格式化小数String resultSize = "";if (size / GB >= 1) {//如果当前Byte的值大于等于1GBresultSize = df.format(size / (float) GB) + "GB";} else if (size / MB >= 1) {//如果当前Byte的值大于等于1MBresultSize = df.format(size / (float) MB) + "MB";} else if (size / KB >= 1) {//如果当前Byte的值大于等于1KBresultSize = df.format(size / (float) KB) + "KB";} else {resultSize = size + "B";}return resultSize;}/*** 读取文本文件,返回一个字符串* @param 文件路径* @return 文本内容*/public static String readText(String fileName) {StringBuffer stringList = new StringBuffer();System.out.println(fileName);try {File file = new File(fileName);if (!file.exists()) {System.out.println("该文件不存在!");return stringList.toString();}InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "UTF-8");BufferedReader in = new BufferedReader(isr); String str;while ((str = in.readLine()) != null) {//str = str.replace(" ", "&nbsp");stringList.append(str+"\n");//为了在前端正常显示,加上换行和空格转换符号,如果需要在后端显示,则去掉,注意:前端如果用innerText//显示的话,则不需要在后端加}in.close();} catch (IOException e) {System.out.println("文件为空,读取出错");}return stringList.toString();}/*** 从文件中按行读取,返回List<String>* @param 文件路径* @return 文本内容List<String>*/public static List<String> readStringList(String fileName){List<String> stringList = new ArrayList<String>();try {File file = new File(fileName);if (!file.exists()) {System.out.println("该文件不存在!");return stringList;}InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "UTF-8");BufferedReader in = new BufferedReader(isr); String str;while ((str = in.readLine()) != null) {stringList.add(str);}in.close();} catch (IOException e) {System.out.println("文件为空,读取出错");}return stringList;}/*** 判断哪些是可以预览,也就是读取的文本文件* @param 文件路径* @return 是否可以读取*/public static boolean canPreView(String fileName) {//获取文件后缀名并转化为小写,用于后续比较String fileType = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length()).toLowerCase();// 创建可以预览文本类型数组String canPreviewArray[] = { "txt", "java", "xml", "py","lrc","sql","html","jsp"};for (int i = 0; i < canPreviewArray.length; i++) {if (canPreviewArray[i].equals(fileType)) {return true;}}return false;}
}

4.2. restful接口(restcontroller类)

@RestController
@CrossOrigin
@RequestMapping("/api/v1/files")
public class FileRestController {private static final Logger logger = LoggerFactory.getLogger(FileRestController.class);private static String filePath = "D:/aplus";@Autowiredprivate FileService fileService;/*** 根据用户id查询所有文件* @param 用户id* @return 该用户所有的文件信息*/@RequestMapping(value="/{user_id}", method = RequestMethod.GET, produces = "application/json")public List<File> findAll(@PathVariable int user_id){return fileService.findAllByUserId(user_id);}/*** 根据用户id和文件的类型查询所有文件* @param 用户id,文件类型 file_type* @return 此类型所有文件信息*/@RequestMapping(value="/type", method = RequestMethod.GET, produces = "application/json")public List<File> find1(int user_id,String file_type){//       List<File> files = fileService.findAllByType(user_id, file_type);//转换日期格式,实际考虑,此处也可不转换,而是在前端页面显示的时候再转换,另外,存入数据库中的时候也可以转换后再存,即将数据库中字段改成字符串,而不是Date,可以节省查询时间
//        for(int i=0;i<files.size();i++) {//          files.get(i).setUpload_time(TimeTransforTools.transforDateToString(files.get(i).getUpload_time()););
//        }return fileService.findAllByType(user_id, file_type);}/*** 单个文件上传,接收前台上传的文件和用户信息* @param file 文件,user 用户信息字符串* @return 上传后的文件信息*/@RequestMapping(value = "/singleFile", method = RequestMethod.POST, produces = "application/json")public File uploadSingleFile(@RequestParam("file") MultipartFile file,String user){JSONObject jsonObjects =  JSONObject.fromObject(user.toString());//java 转jsonUser tempUser = (User) JSONObject.toBean(jsonObjects,User.class);//json转换成java对象//将文件存入系统中String fileName = FileTools.storeFile(file,filePath);//根据文件名后缀,划分文件类型String file_type = FileTools.transforType(fileName);//将字节为单位的文件大小转换成合适的单位,比如B,KB,MB,GBString file_size = FileTools.transforSize(file.getSize());//生成file对象,存入数据库中File tempFile = new File(fileService.getMaxId()+1, file.getOriginalFilename(), filePath+"/"+file.getOriginalFilename(),file_size, file_type, 0, new Date(),0,tempUser);fileService.addFile(tempFile);return tempFile;}/*** 多个文件上传,接收前台上传的文件和用户信息* @param files 文件数组,user 用户信息字符串* @return 上传后的所有文件信息*/@RequestMapping(value = "/multipleFiles", method = RequestMethod.POST, produces = "application/json")public List<File> uploadMultipleFiles(@RequestParam("files") MultipartFile[] files,String user) {List<File> list = new ArrayList<>();if (files != null) {for(int i=0;i<files.length;i++) {File uploadFileResponse = uploadSingleFile(files[i],user);list.add(uploadFileResponse);}}return list;}/*** 单个文件下载* @param 文件id* @return 文件资源* @throws IOException */@RequestMapping(value = "/singleFile/{file_id}", method = RequestMethod.GET, produces = "application/json")public ResponseEntity<Resource> downloadSingleFile(@PathVariable int file_id, HttpServletRequest request) throws IOException {String fileName = fileService.findById(file_id).getFile_path();Resource resource = FileTools.loadFileAsResource(fileName);String contentType = null;try {request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());} catch (IOException e) {logger.info("Could not determine file type.");}if(contentType == null) {contentType = "application/octet-stream";}return ResponseEntity.ok().contentType(MediaType.parseMediaType(contentType)).header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" +resource.getFilename()+ "\"").body(resource);}/*** 获取文本文件内容,并且返回字符串* @param * @return 上传后的所有文件信息*/@RequestMapping(value = "/text/{file_id}", method = RequestMethod.GET)public String getTextContent(@PathVariable int file_id) {File tempFile =fileService.findById(file_id);String file_path = tempFile.getFile_path();if(FileTools.canPreView(file_path)) {return FileTools.readText(file_path);}return "can not preview";}/*** 将文件放入回收站,即不是真正的删除* @param 文件id* @return */@RequestMapping(value = "/gabage/{file_id}", method = RequestMethod.DELETE)public int fakeDeleteFile(@PathVariable int file_id) {return fileService.fakeDeleteFile(file_id, 1);}/*** 将文件批量放入回收站,即不是真正的删除* @param 文件id数组* @return */@RequestMapping(value = "/gabages/{files_id}", method = RequestMethod.DELETE)public int fakeDeleteFiles(@PathVariable int[] files_id) {for(int i=0;i<files_id.length;i++) {fileService.fakeDeleteFile(files_id[i], 1);}return 1;}/*** 将文件拿出回收站* @param 文件id* @return 上传后的所有文件信息*/@RequestMapping(value = "/gabage/{file_id}", method = RequestMethod.PUT)public int cancelDeleteFile(@PathVariable int file_id) {return fileService.fakeDeleteFile(file_id, 0);}/*** 将文件批量拿出回收站* @param 文件id数组* @return 上传后的所有文件信息*/@RequestMapping(value = "/gabages/{files_id}", method = RequestMethod.PUT)public int cancelDeleteFiles(@PathVariable int[] files_id) {for(int i=0;i<files_id.length;i++) {fileService.fakeDeleteFile(files_id[i], 0);}return 1;}/*** 真正删除文件* @param * @return */@RequestMapping(value = "/{file_id}", method = RequestMethod.DELETE)public int realDeleteFile(@PathVariable int file_id) {return fileService.deleteFile(file_id);}/*** 真正批量删除文件* @param * @return */@RequestMapping(value = "/array/{files_id}", method = RequestMethod.DELETE)public int realDeleteFiles(@PathVariable int[] files_id) {for(int i=0;i<files_id.length;i++) {fileService.deleteFile(files_id[i]);}return 1;}
}

4.3. FileService文件服务接口

public interface FileService {List<File> findFakeDeleteFiles(int user_id);//查询某个用户放入回收站的文件List<File> findAllByUserId(int user_id);//查找某个用户的所有文件,不包括放入回收站的文件List<File> findAllByType(@Param("user_id")int user_id,@Param("file_type")String file_type);//查询用户某个类型的所有文件,不包括放入回收站的文件File findById(int file_id);//根据文件id查找文件,不包括放入回收站的文件public int addFile(File file);//增加文件public int deleteFile(int file_id);//删除单个文件,真正从数据库中删除public int deleteFiles(int user_id);//删除某用户的所有文件,真正从数据库中删除public int fakeDeleteFile(int file_id,int is_delete);//将文件设置为已删除或者不删除,等价于放入回收站或者移出回收站,而不是真正的删除public int updateFile(File file);//更新文件public int getMaxId();
}

五、总结

这次花了四五天时间开发这个第一版网盘,考虑下次迭代将站内资源模块引入,然后可以接入各种资源模块,同时优化界面,并且实现offce文档的预览。代码届时会上传至github,以上就是个人网盘的开发流程。

六、更新


2020.9.5
更新文件存储方式:按照用户名创建子目录
考虑到之前文件都是存放在统一文件夹下,当文件数量过大的时候,会导致检索速度变得非常慢,所以创建子集目录可以加快检索速度。


2020.9.10
新增功能点:文件分享链接生成
首先将用户的id等参数采用MD5加密或者其它加密,然后生成一个参数追加到链接后面,同时设置提取密码,也可以设置不要提取密码,如果不设置提取密码,该文件默认共享到站内资源,数据库文件表中需要加上文件分享的加密后参数,因为MD5是不可逆的,所以不会暴露出用户信息,其次,当别人输入链接的时候,再输入正确的密码,则可以正确的提取该文件资源。


2020.9.15
新增功能点:能够预览office(doc,docx,xls,xlsx,ppt)文件
在后端将对应的office文档转换成pdf格式,然后将pdf格式文件流传递到前端页面,页面通过pdf.js显示pdf文件流内容

基于spring boot开发的个人网盘相关推荐

  1. 分布式 Spring Cloud 基于 Spring Boot 开发一整套

    Spring Boot的工程包括:  - Spring IO Platform:用于版本化应用程序的企业级分发.  - Spring Framework:用于事务管理.依赖注入.数据访问.消息传递和W ...

  2. 保姆级的一个基于spring boot开发的前后端分离商城教程

    前言 推荐一个基于spring boot开发前后端分离商城,有完整的代码笔记和视频教程,希望对正在找项目练手的同学有所帮助 本文资料文档领取(在文末) 一.项目背景 5中常见的电商模式 B2B .B2 ...

  3. 一个强大的开源的基于Spring Boot开发的Web支付系统项目,支持聚合码支付

    一个强大的开源的基于Spring Boot开发的Web支付系统项目,支持聚合码支付.

  4. springboot 微信太阳码_WxJava基于Spring Boot开发微信公众号手机注册码

    WxJava基于Spring Boot开发微信公众号手机注册码 Szx • 2019 年 05 月 18 日 第一步先下载官方Demo https://github.com/binarywang/we ...

  5. 开源oa_圈子哥推荐一款基于 Spring Boot 开发 OA 开源产品,学习/搞外快都是不二选择!...

    点击上方蓝字关注「程序员的技术圈子」 今天圈子哥给大家推荐一套Spring Boot 开发 OA系统,系统功能齐全,不管是用来学习或者搞外快都是不错的选择,clone下来吧! 办公自动化(OA)是面向 ...

  6. 一款基于 Spring Boot 开发 OA 开源产品

    点击上方 好好学java ,选择 星标 公众号重磅资讯,干货,第一时间送达今日推荐:分享一套基于SpringBoot和Vue的企业级中后台开源项目,这个项目有点哇塞!个人原创100W +访问量博客:点 ...

  7. 美观大气!一款基于 Spring Boot 开发 OA 开源产品

    办公自动化(OA)是面向组织的日常运作和管理,员工及管理者使用频率最高的应用系统,极大提高公司的办公效率. 1.项目介绍 oasys是一个OA办公自动化系统,使用Maven进行项目管理,基于sprin ...

  8. 基于 Spring Boot 开发 OA 开源产品

    办公自动化(OA)是面向组织的日常运作和管理,员工及管理者使用频率最高的应用系统,极大提高公司的办公效率. 1.项目介绍 oasys是一个OA办公自动化系统,使用Maven进行项目管理,基于sprin ...

  9. 基于Spring boot开发电子宿舍管理系统毕业设计源码132056

    摘  要 科技进步的飞速发展引起人们日常生活的巨大变化,电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用.信息时代的到来已成为不可阻挡的时尚潮流,人类发展的历史正进入一个新时代 ...

最新文章

  1. 一次因NAS存储故障引起的Linux系统恢复案例
  2. P2119 魔法阵(优化枚举,数学运算优化)难度⭐⭐⭐★
  3. 人脸对齐--Dense Face Alignment
  4. centos6.2关于tomcat远程不能访问的问题处理过程
  5. 【线索二叉树详解】数据结构06(java实现)
  6. jquery 手型 鼠标穿过时_专业电竞鼠标有什么独到之处?看完核技瑞你就知道了
  7. 【渝粤题库】广东开放大学 商务办公软件应用与实践 形成性考核
  8. 基于51单片机的万年历设计
  9. angular对象简单介绍
  10. 关于网站漏洞修复以及处理解决的相关问题解答
  11. ue4 材质翻转法线开关控制
  12. dell-inspiron 14r笔记本电脑除尘总结
  13. read函数、write函数
  14. java.sql.SQLException: Incorrect string value: '\xE6\x88\x91\xE7\x9A\x84...' for column 'groupName'
  15. 医院信息系统模块介绍
  16. 联合分析法(Python实现)
  17. clear 方法的解释及用法
  18. TCP拥塞控制算法BBR源码分析
  19. 台式计算机不显示,台式电脑开机显示器不显示怎么办
  20. AES256-GCM-NOPADDING加密解密(java)

热门文章

  1. AM1808的RBL UBL
  2. java int转float精度丢失问题
  3. 蓝桥杯单片机-第十二届省赛客观题
  4. CF烟雾头NVIDIA控制面板调节
  5. 安全多方计算基础——秘密分享的两种方案
  6. 量子计算机那一年可以,量子计算机有多厉害?目前已知有一个问题是只有它能解决的...
  7. 华为防火墙做单臂路由_防火墙做单臂路由实现VLAN间通信
  8. 行业云赋能产数融合,云服务加速行业渗透
  9. (转)iPhone 字体显示效果大全
  10. ajax complete写法,jquery ajax complete 方法