记web应用文件传输至指定服务器
1、事件背景。
web应用网页有个文件上传需求,之前用的某云服务器,现在改为文件资源服务器。
方案一:http,但有个问题,http需要请求方和接收方,也就是存文件的服务器也需要开发代码,这对于只需要满足存储来说,显得不适用。这里可参考:httpURLConnection,httpClient,还有其他实现类似底层socket协议传输文件。
方案二:FastDFS 专业的分布式资源管理系统,结合Nginx实现预览等功能。介绍:用FastDFS搭建文件管理系统
方案三:HFS 文件分享系统,搭建后选择需要分享到局域网或者配置外网后需要共享访问的资源目录,然后设置访问控制,如ip控制,文件操作控制,访问需要登录等,直接url就可以访问资源,并下载。网上资源不多,能找到的全的介绍不容易,全靠英文不靠谱的我找度娘,最终放弃,感觉上生产环境会有不可预料的问题。
方案四:ftp下面代码基于sftp完成传文件及其他操作。优点:不用双方服务器保存http通信,不用FastDFS复杂的配置,暂时选用。
2、开发sftp远程传文件
1).下载maven包
<dependency> <groupId>com.jcraft</groupId> <artifactId>jsch</artifactId> <version>0.1.55</version> </dependency>
2).编写util类
package com.liindata.fs.consumer.common.upload; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.util.Properties; import java.util.Vector; import javax.annotation.Resource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import com.jcraft.jsch.Channel; import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import com.jcraft.jsch.SftpException; public class FtpJSch { private static Log log = LogFactory.getLog(FtpJSch.class); private static ChannelSftp sftp = null; // 账号 private static String user = ""; // 主机ip private static String host = ""; // 密码 private static String password = null; //采用秘钥传输,避免更改密码后不能连接 // 端口 private static int port = 22; // 本地秘钥 private static String privateKey = "C:\\Users\\Administrator\\.ssh\\id_rsa"; // 超时时间 static private int timeout = 60000; //超时数,一分钟 // 上传地址 private static String directory = "/data/cdn/image/qr/"; //默认目录 // 下载目录 private static String saveFile = "c:\\download\\"; public static void main(String[] args) { // FtpJSch ftpJSch = new FtpJSch(); // FtpJSch ftp = getConnect(); // FtpJSch.setDirectory("自定义上传地址"); // ftpJSch.download("下载文件名"); // ftpJSch.upload("上传文件路径","上传文件名"); } /** * * @param user 用户名 * @param host 主机 * @param port 端口 * @param privateKey 秘钥 * @param timeout 超时时间 * @return */ public static FtpJSch getConnect() { log.info("sftp服务开始连接"); FtpJSch ftp = new FtpJSch(); try { JSch jsch = new JSch(); jsch.addIdentity(privateKey); //秘钥 // 获取sshSession 账号-ip-端口 Session sshSession = jsch.getSession(user, host, port); // 添加密码 // sshSession.setPassword(password); Properties sshConfig = new Properties(); // 严格主机密钥检查 sshConfig.put("StrictHostKeyChecking", "no"); sshSession.setConfig(sshConfig); sshSession.setTimeout(timeout); // 设置timeout时间 // 开启sshSession链接 sshSession.connect(); // 获取sftp通道 Channel channel = sshSession.openChannel("sftp"); // 开启 channel.connect(); sftp = (ChannelSftp) channel; } catch (Exception e) { log.error("method:getConnect,sftp服务连接异常"); e.printStackTrace(); } return ftp; } //断开连接 private static void disconnect() { try { if (sftp != null) { if (sftp.isConnected()) { sftp.disconnect(); } else if (sftp.isClosed()) { log.info(FtpJSch.class + " sftp is closed already"); } } } catch (Exception e) { log.error("关闭sftp连接失败", e); } try { if (sftp.getSession() != null) { if (sftp.getSession().isConnected()) { sftp.getSession().disconnect(); } } } catch (JSchException e) { log.error("关闭sftp连接失败", e); } } /** * * @param uploadFile 上传文件的路径 * * @return 服务器上文件名 * @throws SftpException * */ public String upload(String uploadFile, String fileName) { log.info("sftp服务开始上传-" + "文件路径:" + uploadFile + "文件名:" + fileName); File file = null; try { sftp.cd(FtpJSch.getDirectory()); file = new File(uploadFile); sftp.put(new FileInputStream(file), fileName); log.info("file:"+directory+fileName+" Success"); } catch (Exception e) { // 目录不存在,则创建文件夹 String[] dirs = directory.split("/"); String tempPath = ""; for (String dir : dirs) { if (null == dir || "".equals(dir)) continue; tempPath += "/" + dir; try { sftp.cd(tempPath); } catch (SftpException ex) { // 创建目录 try { sftp.mkdir(tempPath); sftp.cd(tempPath); } catch (SftpException e1) { e1.printStackTrace(); } } } }finally { //关闭通道 FtpJSch.disconnect(); } return file == null ? null : fileName; } /** * 文件目录=根目录+上传目录 * * @param basePath 根目录 * @param directory 上传目录 * @param sftpFileName 文件名 * @param input 输入流 * @throws SftpException */ public void uploadByInputStream(String directory, String sftpFileName, InputStream input) throws SftpException { log.info("sftp上传图片服务开始-" + "文件路径:" + directory + "文件名:" + sftpFileName); try { sftp.cd(FtpJSch.getDirectory()); } catch (SftpException e) { // 目录不存在,则创建文件夹 String[] dirs = directory.split("/"); String tempPath = ""; for (String dir : dirs) { if (null == dir || "".equals(dir)) continue; tempPath += "/" + dir; try { sftp.cd(tempPath); } catch (SftpException ex) { // 创建目录 sftp.mkdir(tempPath); sftp.cd(tempPath); } } } sftp.put(input, sftpFileName); // 上传文件 log.info("上传服务器成功!!!!!!!!!!"); FtpJSch.disconnect(); } /** * * 下载文件 * * @param directory 下载目录 * @param downloadFile 下载的文件名 * @param saveFile 存在本地的路径 * @param sftp * */ public void download(String downloadFileName) { log.info("sftp服务开始下载-" + "文件名称:" + downloadFileName); try { sftp.cd(FtpJSch.getDirectory()); File file = new File(saveFile + "/" + downloadFileName); if (!file.exists()) { file.getParentFile().mkdirs(); file.createNewFile(); } sftp.get(downloadFileName, new FileOutputStream(file)); } catch (Exception e) { log.error("sftp download 下载失败"); e.printStackTrace(); }finally { FtpJSch.disconnect(); } } /** * * 删除文件 * * @param deleteFile 要删除的文件名字 * @param sftp * */ public void delete(String deleteFile) { log.info("sftp开始删除图片:" + deleteFile); try { sftp.cd(FtpJSch.getDirectory()); sftp.rm(deleteFile); } catch (Exception e) { e.printStackTrace(); }finally { FtpJSch.disconnect(); } } /** * 列出目录下的文件 * * @param directory 要列出的目录 * @param sftp * @return * @throws SftpException * */ public Vector listFiles(String directory) throws SftpException { return sftp.ls(directory); } public static String getDirectory() { return directory; } public static void setDirectory(String directory) { FtpJSch.directory = directory; } public static String getHost() { return host; } public static void setHost(String host) { FtpJSch.host = host; } //删除本地文件 public static void deleteLocalFile(File file) { if(file.exists()) {//判断路径是否存在 if(file.isFile()){//boolean isFile():测试此抽象路径名表示的文件是否是一个标准文件。 file.delete(); }else{//不是文件,对于文件夹的操作 //保存 路径D:/1/新建文件夹2 下的所有的文件和文件夹到listFiles数组中 File[] listFiles = file.listFiles();//listFiles方法:返回file路径下所有文件和文件夹的绝对路径 for (File file2 : listFiles) { /* * 递归作用:由外到内先一层一层删除里面的文件 再从最内层 反过来删除文件夹 * 注意:此时的文件夹在上一步的操作之后,里面的文件内容已全部删除 * 所以每一层的文件夹都是空的 ==》最后就可以直接删除了 */ deleteLocalFile(file2); } } file.delete(); }else { System.out.println("该file路径不存在!!"); } } }
代码说明:可用user、password方式连接sftp,注释掉添加秘钥,然后打开设置密码,即可连接远程服务器。
方法2,ssh秘钥对建立sftp连接,安全的同时也避免远程服务器更改密码后连接不可用。
环境说明:本地window+远程Linux
1).本地window生产ssh秘钥
dos模式下,输入ssh-keygen -t rsa,即可生产一个公钥和一个秘钥。同理Linux环境下也可以同样的方式生成秘钥对
Enter file in which to save the key :直接回车(密钥对保存路径)
Enter passphrase :不需要输入密码直接回车(连接口令,可无)
Enter same passphrase again: 同样不需要输入密码直接回车
其中,公钥需要上传到远程服务器,后续利用私钥完成sftp连接。
最好提前把服务器ftp搭建起来,后续配置不需要更改,只需要添加
Linux下输入:cd /root/.ssh 进入
如果没有红框中的文件,touch authorized_keys 新建文件
然后把window下之前生成的公钥上传到这里
输入:cat id_rsa.pub >> /root/.ssh/authorized_keys 追加文件内容至文件里
输入:chmod 600 authorized_keys 赋予文件拥有者可读写,其他人不可读写
然后修改 /etc/ssh/sshd_conig文件 用vi 该路径 编辑文件,进入后按i进入写入模式,然后到文件最下面,添加一行
AuthorizedKeysFile .ssh/authorized_keys
然后重启ssh服务
service sshd restart
至此,window端和远程Linux ftp环境搭建完成。
过程中遇到的问题:
1.程序运行时时常提示d:...(本地文件)拒绝访问。
原因:file = new File(uploadFile) file需要读取文件,而不是目录,所以传入路径需要带文件名
2.对于upload方法,需要文件流或者文件,而网页端上传的文件通常是流,还没有存到文件,所以提供两种办法。
1)创建临时文件
//创建临时文件 File tmpDir = FileUtils.getTempDirectory(); String tmpFileName = ((Math.random() * 10000.0D) + "").replace(".", ""); File tmpFile = new File(tmpDir, tmpFileName);
然后
ftpJSch.upload(tmpFile.getAbsolutePath(), fileName);
2)将数据转为输入流
FileInputStream fis = new FileInputStream(tmpFile);
然后
ftpJSch.uploadByInputStream(dstFilePath, fileName, fis);
最后删除临时文件
//删除临时文件 FtpJSch.deleteLocalFile(tmpFile);
3.远程服务器通常是ip或者域名形式
如果是域名,需要设置ip对应关系,进入服务器hosts设置关联
另外通过ip或域名预览上传的文件,存在服务器访问问题,如404,需要通过Nginx设置静态资源访问目录,或者其他办法
未完待续》。。。。。。
补:sftp+nginx 完成文件上传和预览
4.nginx配置:修改后 /data/ftp/nginx/sbin/nginx -s reload 重新加载配置启动
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 88;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
location /upload/{
#root html;
alias /data/ftp/upload/;
autoindex on;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}
1)静态资源访问:
location /upload/{
#root html;
alias /data/ftp/upload/;
autoindex on;
}
浏览器访问http://ip:port/upload/../../file 可访问。注意alias和root的区别
alias:ip访问后nginx去
/data/ftp/upload/找文件
root:ip访问后nginx去
/data/ftp/upload/upload找文件
5.Linux端口配置
iptables 或者 firewall 开放相应的nginx访问端口,如上面配置的88端口,注意阿里云服务器通过这种方法配置后端口不会生效,可通过Telnet ip port 检测。解决:阿里云控制台服务器添加访问规则。