这里使用的是单机版的文件上传下载功能,如果是搞分布式,建议直接使用FastDFS
1.新建一个Springboot项目,在pom.xml中导入如下依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.5.RELEASEversion>
<relativePath/>
parent>
<groupId>com.cdgroupId>
<artifactId>springboot_filesartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>springboot_filesname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.4version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.19version>
dependency>
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.4version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
2.在static目录下面添加进去jquery的包,如下图所示
3.使用Navicat创建两张表,一张用户表,一张文件表,在这里说明一下,一个用户可以上传和下载多个多个文件,多个文件也可以是同一个用户的,因此这里,用户和文件是出于一对多的关系,建表如下
4. 对应的pojo层实体类代码如下
package com.cd.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@ToString
public class User {
private Integer id;
private String username;
private String password;
}
##2## 文件表对应实体类
package com.cd.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@ToString
public class UserFile {
private Integer id; //文件的id
private String oldFileName; //文件的原来的名字
private String newFileName; // 新文件名称
private String ext; //文件后缀
private String path; // 存储路径
private String size; // 文件大小
private String type; // 文件类型
private String isImg; // 是否是图片
private Integer downcounts; // 下载次数
private Date uploadTime; // 上传时间
private Integer userId; //用户外键
}
5. 对应dao层
package com.cd.dao;
import com.cd.pojo.User;
import org.springframework.stereotype.Component;
@Component
public interface UserDao {
//通过用户名和密码封装成一个对象去查询数据库
User login(User user);
}
package com.cd.dao;
import com.cd.pojo.UserFile;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public interface UserFileDao {
// 根据登陆用户的id查询文件列表
List<UserFile> fingByUserId(Integer id);
//保存文件到数据库
void save(UserFile userFile);
// 通过id找到要下载的文件
UserFile findById(Integer id);
// 更新了下载次数
void update(UserFile userFile);
// 删除数据库中的文件数据
void delete(Integer id);
}
6.对应业务service层(UserService)
package com.cd.service;
import com.cd.pojo.User;
public interface UserService {
//通过用户名和密码封装成一个对象去查询数据库
User login(User user);
}
对应实现类(UserServiceImpl)
package com.cd.service;
import com.cd.dao.UserDao;
import com.cd.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
// 登陆
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public User login(User user) {
return userDao.login(user);
}
}
对应UserFileService
package com.cd.service;
import com.cd.pojo.UserFile;
import java.util.List;
public interface UserFileService {
// 根据登陆用户的id查询文件列表
List<UserFile> fingByUserId(Integer id);
// 保存文件到数据库
void save(UserFile userFile);
//通过id找到要下载的文件
UserFile findById(Integer id);
// 更新了下载次数
void update(UserFile userFile);
// 删除数据库中的文件
void delete(Integer id);
}
对应实现类(UserFileServiceImpl)
package com.cd.service;
import com.cd.dao.UserFileDao;
import com.cd.pojo.UserFile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
@Service
@Transactional
public class UserFileServiceImpl implements UserFileService {
@Autowired
private UserFileDao userFileDao;
// 通过用户id查询文件信息
@Override
public List<UserFile> fingByUserId(Integer id) {
return userFileDao.fingByUserId(id);
}
//保存文件到数据库
@Override
public void save(UserFile userFile) {
//userFile.setIsImg() ? // 是否是图片 解决方案: 当类型中含有image时,说明当前类型一定是图片类型
String isImg = userFile.getType().startsWith("image")?"是":"否";
userFile.setIsImg(isImg);
userFile.setDowncounts(0);
userFile.setUploadTime(new Date());
userFileDao.save(userFile);
}
// 通过id找到要下载的文件
@Override
public UserFile findById(Integer id) {
return userFileDao.findById(id);
}
//更新了下载次数
@Override
public void update(UserFile userFile) {
userFileDao.update(userFile);
}
// 删除数据库中的文件数据
@Override
public void delete(Integer id) {
userFileDao.delete(id) ;
}
}
7. mapper配置文件如下
UserDaoMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cd.dao.UserDao">
<select id="login" parameterType="User" resultType="User">
select id,username,password
from t_user
where username=#{username}
and password=#{password}
select>
mapper>
UserFileDaoMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cd.dao.UserFileDao">
<select id="fingByUserId" parameterType="Integer" resultType="UserFile">
select id,oldFileName,newFileName,ext,path,size,type,isImg,downcounts,uploadTime,userId
from t_files
where userid=#{id}
select>
<insert id="save" parameterType="UserFile" useGeneratedKeys="true" keyProperty="id">
insert into t_files
values (#{id},#{oldFileName},#{newFileName},#{ext},#{path},#{size},
#{type},#{isImg},#{downcounts},#{uploadTime},#{userId})
insert>
<select id="findById" parameterType="Integer" resultType="UserFile">
select id,oldFileName,newFileName,ext,path,size,type,isImg,downcounts,uploadTime,userId
from t_files
where id = #{id}
select>
<update id="update" parameterType="UserFile">
update t_files set downcounts=#{downcounts} where id = #{id}
update>
<delete id="delete" parameterType="Integer">
delete from t_files where id = #{id}
delete>
mapper>
8. controller层
1.FileController ,该层主要用来处理用户文件上传,下载,删除,展示等这些请求
package com.cd.controller;
import com.cd.pojo.User;
import com.cd.pojo.UserFile;
import com.cd.service.UserFileService;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@Controller
@RequestMapping("files")
public class FileController {
@Autowired
private UserFileService userFileService;
/**
* 返回当前用户的所有文件列表---json格式
*/
@GetMapping("findAllJson")
@ResponseBody
public List<UserFile> findAllJson(HttpSession session,Model model){
// 在登陆的用户中获取用户的id
User user = (User)session.getAttribute("user");
// 根据用户id查询的文件信息
List<UserFile> userFiles = userFileService.fingByUserId(user.getId());
return userFiles ;
}
/**
* 上传文件处理,并保存文件信息到数据库中
*/
@PostMapping("upload")
public String upload(MultipartFile aaa,HttpSession session) throws IOException {
// 获取上传该文件的用户的id
User user = (User) session.getAttribute("user");
// 获取文件原始名称
String oldFileName = aaa.getOriginalFilename();
// 通过原始文件名获取文件后缀
String extension ="." + FilenameUtils.getExtension(aaa.getOriginalFilename());
//生成新的文件名称
String newFileName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+ UUID.randomUUID().toString().replace("-","") + extension;
// 文件的大小
Long size = aaa.getSize();
// 文件的类型
String type = aaa.getContentType();
// 处理根据日期生成目录
// 通过相对路径获取绝对路径
String realPath = ResourceUtils.getURL("classpath:").getPath() + "/static/files";
String dateFormat = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String dateDirPath = realPath + "/" + dateFormat;
File dateDir = new File(dateDirPath);
// 如果文件不存在,则创建
if(!dateDir.exists())dateDir.mkdirs();
// 处理文件上传
aaa.transferTo(new File(dateDir,newFileName));
// 将文件信息放入数据库保存
UserFile userFile = new UserFile();
userFile.setOldFileName(oldFileName).setNewFileName(newFileName).setExt(extension).setSize(String.valueOf(size))
.setType(type).setPath("/files/"+dateFormat).setUserId(user.getId());
userFileService.save(userFile);
return "redirect:/files/showAll" ;
}
/**
* 文件下载
* @param id
*/
@GetMapping("/download")
public void download(String openStyle,Integer id, HttpServletResponse response) throws IOException {
// 获取打开方式
openStyle = openStyle==null ? "attachment" : openStyle;
// 获取文件信息
UserFile userFile = userFileService.findById(id);
// 点击下载链接更新下载次数
if("attachment".equals(openStyle)){
userFile.setDowncounts(userFile.getDowncounts()+1);
userFileService.update(userFile);
}
//根据文件信息中文件的名字 和 文件存储路径获取文件输入流
String realPath = ResourceUtils.getURL("classpath:").getPath() + "/static" + userFile.getPath();
// 获取文件输入流
FileInputStream is = new FileInputStream(new File(realPath, userFile.getNewFileName()));
//附件下载
response.setHeader("content-disposition",openStyle+";fileName="+ URLEncoder.encode(userFile.getOldFileName(),"UTF-8"));
//获取响应输出流
ServletOutputStream os = response.getOutputStream();
// 文件拷贝
IOUtils.copy(is,os);
//优雅的关闭流
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
}
/**
* 展示所有文件信息
*/
@GetMapping("showAll")
public String findAll(HttpSession session, Model model){
//在登陆的session中获取用户的id
User user =(User) session.getAttribute("user");
//根据id查询到所有的文件信息
List<UserFile> userFiles = userFileService.fingByUserId(user.getId());
//存入作用域中
model.addAttribute("files",userFiles);
return "showAll" ;
}
/**
* 删除文件信息
* @param id
* @return
*/
@GetMapping("delete")
public String delete(Integer id) throws FileNotFoundException {
// 根据id 查询信息
UserFile userFile = userFileService.findById(id);
// 删除文件
String realPath = ResourceUtils.getURL("classpath:").getPath() + "/static" + userFile.getPath();
File file = new File(realPath,userFile.getNewFileName());
if(file.exists())file.delete(); // 立即删除
// 删除数据库中的数据
userFileService.delete(id);
return "redirect:/files/showAll";
}
}
2.UserController 用来处理用户登录跳转
package com.cd.controller;
import com.cd.pojo.User;
import com.cd.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpSession;
@Controller
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
/**
* 登陆方法
* @param user
* @param session
* @return
*/
@PostMapping("login")
public String login(User user, HttpSession session){
User userDB = userService.login(user);
if(userDB!=null){
session.setAttribute("user",userDB);
return "redirect:/files/showAll";
}else {
return "redirect:/index";
}
}
}
indexController: 用来处理跳转到首页请求
package com.cd.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexController {
@GetMapping("index")
public String toLogin(){
return "login";
}
}
application.properties配置文件
spring.application.name=files
server.port=8080
server.servlet.context-path=/files
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/userfiles?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
mybatis.type-aliases-package=com.cd.pojo
mybatis.mapper-locations=classpath:/mapper/*.xml
10 template目录下的代码如下(thymeleaf)
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
</head>
<body>
<h1>欢迎登陆文件上传系统</h1>
<form th:action="@{/user/login}" method="post">
用户名:<input type="username" name="username"> <br/>
密码: <input type="password" name="password"> <br/>
<input type="submit" value="登录">
</form>
</body>
</html>
showAll.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户文件列表展示页面</title>
<script th:src="@{/js/jquery-3.3.1.min.js}"></script>
<script>
$(function () {
var time;
$("#start").click(function () {
time = setInterval(function () {
$.get("[[@{/files/findAllJson}]]",function (res) {
// 遍历
$.each(res,function (index,file) {
$("#"+file.id).text(file.downcounts);
})
}) ;
},3000) ;
});
$("#stop").click(function () {
clearInterval(time)
})
});
</script>
</head>
<body>
<h1>欢迎:<span th:if="${session.user!=null}" th:text="${session.user.username}"/></h1>
<h3>文件列表</h3>
<button id="start">开启定时更新</button>
<button id="stop">结束定时更新</button>
<table border="1px">
<tr>
<th>ID</th>
<th>文件原始名称</th>
<th>文件的新名称</th>
<th>文件后缀</th>
<th>存储路径</th>
<th>文件大小</th>
<th>类型</th>
<th>是否是图片</th>
<th>下载次数</th>
<th>上传时间</th>
<th>操作</th>
</tr>
<tr th:each="file,fileStat:${files}">
<td><span th:text="${file.id}"/></td>
<td><span th:text="${file.oldFileName}"/></td>
<td><span th:text="${file.newFileName}"/></td>
<td><span th:text="${file.ext}"/></td>
<td><span th:text="${file.path}"/></td>
<td><span th:text="${file.size}"/></td>
<td><span th:text="${file.type}"/></td>
<td> <!--th:src="@{/}+'/'+ ${file.path}+'/'+${file.newFileName}"-->
<img th:if="${file.isImg}=='是'" style="width: 100px;height: 40px;" th:src="${#servletContext.contextPath}+${file.path}+'/'+${file.newFileName}" alt="">
<span th:if="${file.isImg}!='是'" th:text="${file.isImg}"/>
</td>
<td th:id="${file.id}"><span th:text="${file.downcounts}"/></td>
<td><span th:text="${#dates.format(file.uploadTime,'yyyy-MM-dd HH:mm:ss')}"/></td>
<td>
<a th:href="@{/files/download(id=${file.id})}">下载</a>
<a th:href="@{/files/download(id=${file.id},openStyle='inline')}">在线打开</a>
<a th:href="@{/files/delete(id=${file.id})}">删除</a>
</td>
</tr>
</table>
<hr>
<h3>上传文件</h3>
<form th:action="@{/files/upload}" method="post" enctype="multipart/form-data">
<input type="file" name="aaa"> <input type="submit" value="上传文件">
</form>
</body>
</html>