一、centos7 安装sftp
1.安装 OpenSSH 服务:
sudo yum install openssh-server
2.启动 SSH 服务,并设置为开机启动:
sudo systemctl start sshd
sudo systemctl enable sshd
3.修改linux服务器的SFTP配置:
编辑/etc/ssh/sshd_config文件,注释其中的Subsystem sftp /usr/libexec/openssh/sftp-server,然后新增配置如下:
# 我的SFTP配置
Subsystem sftp internal-sftp # SFTP子系统使用内置的SFTP服务器
Match Group GSFTP # 以下配置仅适用于属于该组的用户
ChrootDirectory /Net_share # 目录限制
ForceCommand internal-sftp # 强制用户使用内置的SFTP服务器进行会话,而不允许执行其他命令
AllowTcpForwarding no # 禁止TCP转发
X11Forwarding no # 禁止X11转发
重启生效:
sudo service ssh restart # 或 systemctl restart sshd
3.创建一个新用户,用于SFTP连接(替换your_username为你想要的用户名),设置密码(替换sftpadmin为你想要的密码):
sudo adduser your_username
sudo passwd sftpadmin
注意:如果输入密码提示未知的用户,则把sftpadmin换成指定的用户,然后再设置新密码
4.创建一个目录,用于SFTP的根目录(替换/path/to/sftp_root为你想要的目录路径):
sudo mkdir -p /path/to/sftp_root
sudo chown root:root /path/to/sftp_root
sudo chmod 755 /path/to/sftp_root
5.创建用户的SFTP目录:
sudo mkdir -p /path/to/sftp_root/your_username
sudo chown your_username:your_username /path/to/sftp_root/your_username
sudo chmod 755 /path/to/sftp_root/your_username
6.重启 SSH 服务以应用更改:
sudo systemctl restart sshd
现在,用户 your_username 可以通过 SFTP 连接到服务器,只需要使用他们的常规用户名和密码。确保为用户设置了合适的权限和所需的目录结构。
二、java sftp工具类
1.工具类
(1)引入manven
<dependency>
<groupId>com.github.mwiede</groupId>
<artifactId>jsch</artifactId>
<version>0.2.18</version>
</dependency>
(2)sftp工具类
package com.dcqq.common.utils;
import cn.hutool.core.codec.Base64;
import com.google.common.base.Joiner;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import java.io.*;
import java.util.*;
/**
* SFTP工具类
*/
@Slf4j
public class SftpUtils {
private static Session sshSession = null;
private static String sftpRootDir = "/home/sftp/upload/";//根目录
private final static String host = "10.122.156.10";//ip
private final static Integer port = 22;//端口
private final static String username = "sftp_user";//用户名
private final static String password = "sftp1122";//密码
private final static int MAX_TIMES = 3;//重试次数
public static ChannelSftp connect() {
// 重试次数
int count = 0;
ChannelSftp sftp = SftpUtils.connect(host, port, username, password);
while (sftp == null && count < MAX_TIMES) {
count++;
log.error("第[" + count + "]次重试连接FTP服务器:" + host);
SftpUtils.sleep(RandomUtils.nextInt(3, 600));
sftp = SftpUtils.connect(host, port, username, password);
}
if (sftp == null) {
log.error("获取FTP连接失败:" + host);
}
return sftp;
}
/**
* 获取ChannelSftp
*/
public static ChannelSftp connect(String host, Integer port, String username, String password) {
ChannelSftp sftp = null;
try {
JSch jsch = new JSch();
jsch.getSession(username, host, port);
sshSession = jsch.getSession(username, host, port);
sshSession.setPassword(password);
Properties sshConfig = new Properties();
sshConfig.put("StrictHostKeyChecking", "no");
sshSession.setConfig(sshConfig);
sshSession.connect();
Channel channel = sshSession.openChannel("sftp");
channel.connect();
sftp = (ChannelSftp) channel;
SftpUtils.mkdirs(sftpRootDir, sftp);
SftpUtils.cd(sftpRootDir, sftp);
// System.out.println("连接成功");
return sftp;
} catch (Exception e) {
// e.printStackTrace();
log.error("连接FTP失败:" + host + ",异常信息:" + e);
}
return null;
}
public static boolean upload(File file, String directory, String fileName) {
InputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
return upload(inputStream, directory, fileName);
} catch (Exception e) {
e.printStackTrace();
log.error("FTP文件上传失败:" + directory + "/" + fileName + ",异常标题:" + e.getMessage());
} finally {
CloseUtils.closeQuietly(inputStream);
}
return false;
}
public static boolean upload(byte[] data, String directory, String fileName) {
ByteArrayInputStream inputStream = null;
try {
inputStream = new ByteArrayInputStream(data);
return upload(inputStream, directory, fileName);
} finally {
CloseUtils.closeQuietly(inputStream);
}
}
public static boolean upload(InputStream inputStream, String directory, String fileName) {
try {
// 重试次数
int count = 0;
boolean success = doUpload(inputStream, directory, fileName);
while (!success && count < MAX_TIMES) {
count++;
log.info("第[{}/{}]次重新上传文件:{}", count, MAX_TIMES, fileName);
SftpUtils.sleep(RandomUtils.nextInt(1, 300));
success = doUpload(inputStream, directory, fileName);
}
return success;
} finally {
CloseUtils.closeQuietly(inputStream);
}
}
private static boolean doUpload(InputStream inputStream, String directory, String fileName) {
ChannelSftp sftp = SftpUtils.connect();
if (sftp == null) {
return false;
}
try {
SftpUtils.mkdirs(directory, sftp);
sftp.put(inputStream, fileName, ChannelSftp.OVERWRITE);
return true;
} catch (Exception e) {
// throw new RuntimeException(e);
log.error("FTP文件上传失败:" + directory + "/" + fileName + ",异常标题:" + e.getMessage());
} finally {
SftpUtils.disconnect(sshSession);
SftpUtils.disconnect(sftp);
}
return false;
}
public static String downloadAsBase64(String fileName) {
ByteArrayOutputStream outputStream = download(fileName);
try {
if (outputStream == null || outputStream.toByteArray().length == 0) {
return "";
}
return Base64.encode(outputStream.toByteArray());
} finally {
CloseUtils.closeQuietly(outputStream);
}
}
public static ByteArrayOutputStream download(String fileName) {
if (StringUtils.isEmpty(fileName)) {
return null;
}
// 重试次数,最多6次
int count = 0;
ByteArrayOutputStream outputStream = doDownload(fileName);
while (outputStream == null && count < MAX_TIMES) {
count++;
log.info("第[{}/2]次重新下载文件:{}", count, fileName);
SftpUtils.sleep(RandomUtils.nextInt(1, 30));
outputStream = doDownload(fileName);
}
return outputStream;
}
public static ByteArrayOutputStream doDownload(String fileName) {
if (StringUtils.isEmpty(fileName)) {
return null;
}
ChannelSftp sftp = SftpUtils.connect();
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
sftp.get(fileName, outputStream);
return outputStream;
} catch (Exception e) {
// throw new RuntimeException(e);
log.error("FTP文件下载失败:" + fileName + ",异常标题:" + e.getMessage());
if ("No such file.".equals(e.getMessage())) {
return new ByteArrayOutputStream();
}
} finally {
SftpUtils.disconnect(sshSession);
SftpUtils.disconnect(sftp);
}
return null;
}
public static ChannelSftp cd(String directory, ChannelSftp sftp) {
try {
sftp.cd(directory);
} catch (Exception e) {
}
return sftp;
}
public static ChannelSftp mkdir(String directory, ChannelSftp sftp) {
try {
sftp.mkdir(directory);
} catch (Exception e) {
}
return sftp;
}
public static void disconnect(Session sshSession) {
if (sshSession != null) {
try {
sshSession.disconnect();
} catch (Exception e) {
}
}
}
public static void disconnect(ChannelSftp sftp) {
if (sftp != null) {
try {
sftp.disconnect();
} catch (Exception e) {
}
}
}
public static void mkdirs(String directory, ChannelSftp sftp) {
String[] dirs = directory.split("/");
for (int i = 0; i < dirs.length; i++) {
String d = dirs[i];
if (d.length() > 0) {
SftpUtils.mkdir(d, sftp);
SftpUtils.cd(d, sftp);
}
}
}
public static String datePath(String moduleName, Date dateTime) {
return Joiner.on("/").join(moduleName, DateFormatUtils.format(dateTime, "yyyy/MM/dd"));
}
public static String datePath(String moduleName) {
return Joiner.on("/").join(moduleName, DateFormatUtils.format(System.currentTimeMillis(), "yyyy/MM/dd"));
}
public static String fileFullPath(String filePath, String fileName) {
return Joiner.on("/").join(filePath, fileName);
}
/**
* 删除文件
*
* @param directory 要删除文件所在目录
* @param deleteFile 要删除的文件
* @throws Exception
*/
public void delete(String directory, String deleteFile) throws Exception {
ChannelSftp sftp = SftpUtils.connect();
if (sftp != null) {
sftp.cd(directory);
sftp.rm(deleteFile);
}
}
/**
* 列出目录下的文件
*
* @param directory 要列出的目录
* @return list 文件名列表
* @throws Exception
*/
@SuppressWarnings("unchecked")
public static List<String> listFiles(String directory) throws Exception {
ChannelSftp sftp = SftpUtils.connect();
Vector fileList;
List<String> fileNameList = new ArrayList<String>();
fileList = sftp.ls(directory);
Iterator it = fileList.iterator();
while (it.hasNext()) {
String fileName = ((ChannelSftp.LsEntry) it.next()).getFilename();
if (".".equals(fileName) || "..".equals(fileName)) {
continue;
}
fileNameList.add(fileName);
}
return fileNameList;
}
/**
* @param millis * the length of time to sleep in milliseconds
*/
public static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (Exception e) {
}
}
public static void main(String[] args) throws Exception {
//this.delete("/userReport/1/2022/ //删除
//显示目录
/*List<String> list = listFiles(sftpRootDir);
for (String s : list) {
System.out.println(s);
}*/
//上传文件
File reportFile = new File("C:\\图片\\1.png");
upload(reportFile, "upload", "1.png");
}
}
(3)相关代码
CloseUtils
package com.dcqq.common.utils;
import java.io.Closeable;
import java.util.zip.ZipInputStream;
/**
* 关闭文件流
*
*/
public class CloseUtils {
/**
* 关闭文件流
*
* @param closeables
*/
public static void closeQuietly(Closeable... closeables) {
if (closeables != null && closeables.length > 0) {
for (Closeable closeable : closeables) {
if (closeable != null) {
try {
closeable.close();
} catch (Exception e) {}
}
}
}
}
public static void closeEntryQuietly(ZipInputStream... closeables) {
if (closeables != null && closeables.length > 0) {
for (ZipInputStream closeable : closeables) {
if (closeable != null) {
try {
closeable.closeEntry();
} catch (Exception e) { }
}
}
}
}
}
2.使用
(1)controller层,CommFileManagerController 公共文件管理层
package com.dcqq.common;
import com.dcqq.system.service.IFileService;
import com.dcqq.web.controller.common.CommonController;
import io.swagger.annotations.Api;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 文件下载
*/
@RestController
@Api(tags = "文件下载")
@RequestMapping("/fileManager")
public class CommFileManagerController {
private static final Logger log = LoggerFactory.getLogger(CommFileManagerController .class);
@Autowired
private IFileService fileService;
@Autowired
private ServerConfig serverConfig;
/**
* 文件上传
*/
@ApiOperation(value = "文件上传", notes = "文件上传")
@PostMapping("/uploadFile")
public AjaxResult uploadFile(MultipartFile file) {
FileForm form = new FileForm();
form.setUrl(serverConfig.getUrl());
return fileService.uploadFile(file, form);
}
/**
* 文件读取
* @param request
* @param response
* @throws IOException
* 访问示例:https://test.tt.com.cn/prod-api/fileManager/upload/2024/06/18/1_20240618103039A005.png
*/
@GetMapping(value = "/upload/**")
public void upload(HttpServletRequest request, HttpServletResponse response){
log.info("============upload");
fileService.download(request,response);
}
}
(2)service业务层
IFileService
package com.dcqq.system.service;
import com.dcqq.common.core.domain.AjaxResult;
import com.dcqq.form.FileForm;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 文件
*
* @author dcqq
* @date 2024-04-03
*/
public interface IFileService {
/**
* 文件上传
* @param file
* @return
*/
AjaxResult uploadFile(MultipartFile file, FileForm form);
/**
* 文件下载
* @param request
* @param response
*/
void download(HttpServletRequest request, HttpServletResponse response);
}
FileServiceImpl
package com.dcqq.common;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.net.URLDecoder;
import com.dcqq.common.core.domain.AjaxResult;
import com.dcqq.common.utils.*;
import com.dcqq.common.utils.file.MimeTypeUtils;
import com.dcqq.form.FileForm;
import com.dcqq.system.service.IFileService;
import org.apache.commons.io.FilenameUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* 文件
*
* @author dcqq
* @date 2024-04-03
*/
@Service
public class FileServiceImpl implements IFileService {
public AjaxResult uploadFile(MultipartFile file, FileForm form) {
try {
String url = form.getUrl()+"/prod-api"+"/fileManager/upload";
String dir=DateUtils.datePath();
//获取文件后缀名
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
if (StringUtils.isEmpty(extension)) {
extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
}
//获取文件名+后缀
String fileName = file.getOriginalFilename()+"."+extension;
SftpUtils.upload(file.getBytes(), dir, fileName);
AjaxResult ajax = AjaxResult.success();
url=url+dir+fileName;
ajax.put("url", url);
ajax.put("newFileName", fileName);
ajax.put("originalFilename", file.getOriginalFilename());
return ajax;
} catch (Exception e) {
return AjaxResult.error(e.getMessage());
}
}
@Override
public void download(HttpServletRequest request, HttpServletResponse response) {
try {
String requestURI = URLDecoder.decode(request.getRequestURI(), StandardCharsets.UTF_8);
// ContentType,即告诉客户端所发送的数据属于什么类型
String ext = FileNameUtil.extName(requestURI);
MediaTypeEnum mediaType = MediaTypeEnum.byCode(ext);
response.setContentType(mediaType.getName());
String ftpPath = StringUtils.substringAfter(requestURI, "/download/");
ByteArrayOutputStream out = SftpUtils.download(ftpPath);
response.getOutputStream().write(out.toByteArray());
response.flushBuffer();
CloseUtils.closeQuietly(out, response.getOutputStream());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
(3)相关代码
MediaTypeEnum
package com.dcqq.common.utils;
import lombok.Getter;
import org.springframework.http.MediaType;
import java.util.HashMap;
import java.util.Map;
/**
* 媒体类型
*/
@Getter
public enum MediaTypeEnum {
PNG("png", MediaType.IMAGE_PNG_VALUE),
JPG("jpg", "image/jpg"),
JPEG("jpeg", MediaType.IMAGE_JPEG_VALUE),
PDF("pdf", MediaType.APPLICATION_PDF_VALUE),
ZIP("zip", "application/zip"),
DOCX("docx", MediaType.APPLICATION_OCTET_STREAM_VALUE),
STREAM("other", MediaType.APPLICATION_OCTET_STREAM_VALUE),
XML("xml", MediaType.APPLICATION_XML_VALUE),
JSON("json", MediaType.APPLICATION_JSON_VALUE)
;
private String code;
private String name;
private static Map<String, MediaTypeEnum> cacheEnum = new HashMap<>();
static {
for(MediaTypeEnum dictEnum:MediaTypeEnum.values()){
cacheEnum.put(dictEnum.code, dictEnum);
}
}
public static MediaTypeEnum byCode(String code){
if(cacheEnum.containsKey(code)){
return cacheEnum.get(code);
}
return STREAM;
}
MediaTypeEnum(String code, String name){
this.code = code;
this.name = name;
}
}
ServerConfig
package com.dcqq.framework.config;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import com.dcqq.common.utils.ServletUtils;
/**
* 服务相关配置
*
* @author ruoyi
*/
@Component
public class ServerConfig {
/**
* 获取完整的请求路径,包括:域名,端口,上下文访问路径
*
* @return 服务地址
*/
public String getUrl() {
HttpServletRequest request = ServletUtils.getRequest();
return getDomain(request);
}
public static String getDomain(HttpServletRequest request) {
StringBuffer url = request.getRequestURL();
String contextPath = request.getServletContext().getContextPath();
return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();
}
}
FileForm
package com.dcqq.form;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class FileForm {
@ApiModelProperty("请求路径")
private String url;
}