项目结构
为简化结构 只保留Controller、Service层
环境准备
SpringBoot相关依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath/>
</parent>
<profiles>
<profile>
<id>dev</id>
<properties>
<profileActive>dev</profileActive>
<serverPort>2203</serverPort>
</properties>
<activation>
<!-- 默认启用的是dev环境配置-->
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>test</id>
<properties>
<profileActive>test</profileActive>
<serverPort>2203</serverPort>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<profileActive>prod</profileActive>
<serverPort>2203</serverPort>
</properties>
<!-- <activation>
<activeByDefault>true</activeByDefault>
</activation> -->
</profile>
</profiles>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<hibernate.version>5.2.3.Final</hibernate.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.49</version>
</dependency>
<!--使用 @ConfigurationProperties注解时添加此依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
<!--会使用到FileUtils把文件转成二进制流-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies>
启动类
@SpringBootApplication
public class MySpringBootApplication {
public static void main(String[] args) {
/**
* 不加上下面这个if判断的话,在IDE中运行没有问题;但是如果打成jar包,通过bat文件启动,会报一个null异常
*/
if (AuthConfigFactory.getFactory() == null) {
AuthConfigFactory.setFactory(new AuthConfigFactoryImpl());
}
SpringApplication.run(MySpringBootApplication.class, args);
}
}
控制层Controller
@RestController
public class MyController {
@Autowired
FileService fileService;
/**
* 多文件的上传
* @param files 上传的文件集合
* @return
*/
@PostMapping(value = "/importFile")
public String importFile(@RequestParam("files") MultipartFile[] files) {
return fileService.importFile(files);
}
/**
* 单个文件的下载
* @param filePath 要下载的文件全路径(目录 + 文件名)
* @param response
* @return
*/
@PostMapping(value = "/downloadFile")
public String downloadFile(@RequestParam String filePath,HttpServletResponse response) {
return fileService.downloadFile(filePath,response);
}
/**
* 多个文件打包zip下载
* @param filePathArray 以逗号分割的多文件的全路径集合
* @param zipFileName 下载的压缩包名(以.zip格式结尾)
* @return
*/
@PostMapping(value = "/downloadMultiFile")
public ResponseEntity downloadMultiFile(@RequestParam String filePathArray, String zipFileName) {
return fileService.downloadMultiFile(filePathArray.split(","),zipFileName);
}
}
业务层Service
@Service
@Slf4j
public class FileService {
@Autowired
FtpUtil ftpUtil;
public String importFile(MultipartFile[] files){
try {
// 文件名集合
StringBuilder fileNames = new StringBuilder();
// 获取ftp服务器连接
ChannelSftp channel = ftpUtil.getChannel();
if(files.length > 0){
for(MultipartFile file:files){
if(file.getSize() > 0){
// 拼接UUID文件名 获取文件原本的文件名:file.getOriginalFilename()
String fileType = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
String fileName = UUID.randomUUID().toString().replace("-","") + fileType;
ftpUtil.uploadFile(channel,ftpUtil.getDirectory(),file.getInputStream(),fileName);
log.info("已上传文件:"+ fileName);
fileNames.append(ftpUtil.getDirectory()).append("/").append(fileName).append(",");
}
}
}
ftpUtil.disConnect(channel);
fileNames.deleteCharAt(fileNames.length()-1);
// 省略存储文件路径到数据库的步骤
return "已上传完成的文件集合:"+fileNames;
} catch (IOException e) {
e.printStackTrace();
return "上传失败";
}
}
public String downloadFile(String filePath , HttpServletResponse response) {
try {
if(StringUtils.isEmpty(filePath)){
log.info("下载的文件路径为空:"+filePath);
throw new Exception();
}
ChannelSftp channel = ftpUtil.getChannel();
ftpUtil.download(channel,filePath,response);
return "下载成功!";
}catch (Exception e) {
e.printStackTrace();
return "下载失败!";
}
}
public ResponseEntity downloadMultiFile(String[] filePathArray, String zipFileName) {
try{
if(filePathArray == null || StringUtils.isEmpty(zipFileName)){
log.info("下载的文件路径或文件名异常:"+ Arrays.toString(filePathArray));
throw new Exception();
}
ChannelSftp channel = ftpUtil.getChannel();
return ftpUtil.download(channel,filePathArray,zipFileName);
} catch (Exception e) {
e.printStackTrace();
return new ResponseEntity("文件下载异常", HttpStatus.valueOf("500"));
}
}
}
配置文件
默认使用dev环境
application.properties:
spring.profiles.active=@profileActive@
application-dev.properties:
ftp.server.host = 192.168.0.54
ftp.server.port = 22
ftp.server.username = ***
ftp.server.password = ***
# 存储上传文件的文件夹路径
ftp.server.directory = /home/template
上传下载工具类
__ Springboot1.5以上版本,在使用 @ConfigurationProperties注解的时候会提示:“Spring Boot Configuration Annotation Processor not found in classpath”,解决方案是在POM文件中增加spring-boot-configuration-processor依赖__
@Component
@ConfigurationProperties(prefix = "ftp.server")
@NoArgsConstructor
@Data
@Slf4j
public class FtpUtil {
private static final Logger LOG = LoggerFactory.getLogger(FtpUtil.class);
private String host;
private Integer port;
private String username;
private String password;
private String directory; // 文件存储的文件夹路径
private ChannelSftp sftp = null;
/**
* 连接 sftp 服务器
* @return
*/
public ChannelSftp getChannel(){
Session session = null;
Channel channel = null;
try {
LOG.info("-->sftp连接开始>>>>>> " + host + ":" + port + " >>>username=" + username);
JSch jsch = new JSch();
jsch.getSession(username, host, port);
session = jsch.getSession(username, host, port);
session.setPassword(password);
Properties sshConfig = new Properties();
sshConfig.put("StrictHostKeyChecking", "no");
session.setConfig(sshConfig);
session.connect();
LOG.info("Session connected!");
channel = session.openChannel("sftp");
channel.connect();
sftp = (ChannelSftp)channel;
LOG.info("get Channel success!");
} catch (JSchException e) {
LOG.info("get Channel failed!", e);
}
return sftp;
}
/**
* @param sftp ftp连接
* @param dir 上传的目录
* @param file 上传的文件
* @return
*/
public String uploadFile(ChannelSftp sftp, String dir, InputStream file, String fileName) {
String result = "";
try {
log.info("上传文件存储的路径:"+dir);
sftp.cd(dir);
if (file != null) {
sftp.put(file, fileName);
result = "上传成功!";
} else {
result = "文件为空!不能上传!";
}
} catch (Exception e) {
LOG.info("上传失败!", e);
result = "上传失败!";
}finally {
closeStream(file,null);
}
return result;
}
/**
* 下载文件
*
* @param directory
* 下载目录
* @param downloadFile
* 下载的文件
* @param saveFile
* 存在本地的路径
* @param sftp
*/
public String download(String directory, String downloadFile,
String saveFile, ChannelSftp sftp) {
String result = "";
try {
sftp.cd(directory);
sftp.get(downloadFile, saveFile);
result = "下载成功!";
} catch (Exception e) {
result = "下载失败!";
LOG.info("下载失败!", e);
;
}
return result;
}
/**
* 下载单个文件
* @param sftp
* @param filePath 文件所在目录及文件名(文件的全路径)
* @param response
*/
public void download(ChannelSftp sftp, String filePath, HttpServletResponse response) {
OutputStream out = null;
try {
String directory = "";
String fileName = "";
if(!StringUtils.isEmpty(filePath) && filePath.contains("/")){
directory = filePath.substring(0,filePath.lastIndexOf("/"));
fileName = filePath.substring(filePath.lastIndexOf("/")+1);
}else {
log.info("要下载的文件路径异常:"+filePath);
throw new Exception();
}
sftp.cd(directory);
// 将文件名为filename的文件下载到本地默认路径 /
sftp.get(fileName,"/");
File file = new File(fileName);
FileInputStream inputStream = new FileInputStream(file);
out = response.getOutputStream();
int b = 0;
byte[] buffer = new byte[512];
while (b != -1){
b = inputStream.read(buffer);
out.write(buffer,0,b);
}
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
out.flush();
out.close();
// 删除下载到本地的临时文件
if(!file.delete()){
log.info("临时文件未成功删除");
}
inputStream.close();
disConnect(sftp);
} catch (Exception e) {
if (LOG.isInfoEnabled()) {
LOG.info("文件下载出现异常,[{}]", e);
}
throw new RuntimeException("文件下载出现异常,[{}]", e);
} finally {
closeStream(null,out);
}
}
/**
* 多文件下载
* @param sftp
* @param filePathArray 文件的全路径数组(文件所在目录及文件名)
* @param zipFileName 压缩文件名
*/
public ResponseEntity<byte[]> download(ChannelSftp sftp, String[] filePathArray,String zipFileName){
ZipOutputStream zipOutStream = null;
FileInputStream zipSource = null;
BufferedInputStream bufferStream = null;
File zipFile = null;
try {
// 存放服务器上zip文件的目录
String zipPath = "/"+zipFileName;
// 创建本地临时zip文件
zipFile = new File(zipPath);
// 构造最终压缩包的输出流
zipOutStream = new ZipOutputStream(new FileOutputStream(zipFile));
for(String filePath:filePathArray){
log.info("要下载的文件全路径:"+filePath);
String directory = "";
String fileName = "";
if(!StringUtils.isEmpty(filePath) && filePath.contains("/")){
directory = filePath.substring(0,filePath.lastIndexOf("/"));
fileName = filePath.substring(filePath.lastIndexOf("/")+1);
}else {
log.info("要下载的文件路径异常:"+filePath);
throw new Exception();
}
sftp.cd(directory);
// 将文件名为filename的文件下载到本地默认路径 /
sftp.get(fileName,"/");
File file = new File(fileName);
if(file.exists()){
//将需要压缩的文件格式化为输入流
zipSource = new FileInputStream(file);
}
//在压缩目录中文件的名字
ZipEntry zipEntry = new ZipEntry(fileName);
//定位该压缩条目位置,开始写入文件到压缩包中
zipOutStream.putNextEntry(zipEntry);
bufferStream = new BufferedInputStream(zipSource, 1024 * 10);
int read = 0;
byte[] buf = new byte[1024 * 10];
while((read = bufferStream.read(buf, 0, 1024 * 10)) != -1)
{
zipOutStream.write(buf, 0, read);
}
bufferStream.close();
// 删除下载到本地的临时文件
if(!file.delete()){
log.info("临时文件未成功删除");
}
}
disConnect(sftp);
zipOutStream.close();
zipOutStream.flush();
HttpHeaders headers = new HttpHeaders();
// 压缩文件名转码
zipFileName = new String(zipFileName.getBytes(),"utf-8");
headers.setContentDispositionFormData("attachment", zipFileName);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 写回压缩文件
return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(zipFile),headers, HttpStatus.CREATED);
} catch (Exception e) {
if (LOG.isInfoEnabled()) {
LOG.info("文件下载出现异常,[{}]", e);
}
throw new RuntimeException("文件下载出现异常,[{}]", e);
} finally {
//判断系统压缩文件是否存在,该压缩文件通过流输出给客户端后删除该压缩文件
if(zipFile.exists()){
zipFile.delete();
}
}
}
/**
* 断掉连接
*/
public void disConnect(ChannelSftp sftp) {
try {
sftp.disconnect();
sftp.getSession().disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 关闭流
* @param outputStream
*/
private void closeStream(InputStream inputStream,OutputStream outputStream) {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(inputStream != null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 创建文件夹
*
* @param sftp
* @param dir
* 文件夹名称
*/
public void mkdir(ChannelSftp sftp, String dir) {
try {
sftp.mkdir(dir);
System.out.println("创建文件夹成功!");
} catch (SftpException e) {
System.out.println("创建文件夹失败!");
e.printStackTrace();
}
}
/**
* 删除文件
*
* @param directory
* 要删除文件所在目录
* @param deleteFile
* 要删除的文件
* @param sftp
*/
public String delete(String directory, String deleteFile, ChannelSftp sftp) {
String result = "";
try {
sftp.cd(directory);
sftp.rm(deleteFile);
result = "删除成功!";
} catch (Exception e) {
result = "删除失败!";
LOG.info("删除失败!", e);
}
return result;
}
private void closeChannel(Channel channel) {
if (channel != null) {
if (channel.isConnected()) {
channel.disconnect();
}
}
}
private void closeSession(Session session) {
if (session != null) {
if (session.isConnected()) {
session.disconnect();
}
}
}
public void closeAll(ChannelSftp sftp, Channel channel, Session session) {
try {
closeChannel(sftp);
closeChannel(channel);
closeSession(session);
} catch (Exception e) {
LOG.info("closeAll", e);
}
}
public Channel getChannel(Session session) {
Channel channel = null;
try {
channel = session.openChannel("sftp");
channel.connect();
LOG.info("get Channel success!");
} catch (JSchException e) {
LOG.info("get Channel fail!", e);
}
return channel;
}
}
使用PostMan测试文件上传下载
1. 文件上传
测试上传 txt 、xls 、png 三种格式文件,成功后返回文件路径
查看ftp服务器:
2. 多文件下载
(单文件下载不再测试)
此处传入的压缩包名字格式以zip结尾,代码中未控制
使用中文作为压缩包名会出现乱码情况,暂未解决
点击 save to a file:
根据需求改动存入ftp服务器的文件名,本例使用UUID