FTP 服务器
FTP 是 File Transfer Protocol(文件传输协议)的英文简称,而中文简称为“文传协议”。用于 Internet 上的控制文件的双向传输。同时,它也是一个应用程序(Application)。 基于不同的操作系统有不同的 FTP 应用程序,而所有这些应用程序都遵守同一种协议以传输文件。
在 FTP 的使用当中,用户经常遇到两个概念:"下载"(Download)和"上传"(Upload)。"下载"文件就是从远程主机拷贝文件至自己的计算机上;"上传"文件就是将文件从自己的计算机中拷贝至远程主机上。用 Internet 语言来说,用户可通过客户机程序向(从)远程主机上传(下载)文件。
VSFTPD 简介
vsftpd 是“very secure FTP daemon”的缩写,安全性是它的一个最大的特点。vsftpd是一个 UNIX 类操作系统上运行的服务器的名字,它可以运行在诸如 Linux、BSD、Solaris、HP-UNIX 等系统上面,是一个完全免费的、开放源代码的 ftp 服务器软件,支持很多其他的FTP 服务器所不支持的特征。
项目中图片服务器:
在传统的单体架构项目中,可以在 web 项目中添加一个文件夹,来存放上传的图片。
例如在工程的根目录 WebRoot 下创建一个 images 文件夹用于保存已上传的图片。
优点:使用方便,便于管理
缺点:
1、如果是分布式环境中图片引用会出现问题。
2、图片的下载会给服务器增加额外的压力
安装 VSFTP
1 安装 vsftpd 组件
yum -y install vsftpd
安装完后,有/etc/vsftpd/vsftpd.conf 文件,是 vsftp 的配置文件。
2 添加一个 Linux 用户
此用户就是用来登录 ftp 服务器用的。
useradd ftpuser
这样一个用户建完,可以用这个登录。登录后默认的路径为 /home/ftpuser.
3 给用户添加密码。
passwd ftpuser
输入两次密码后修改密码。
4 防火墙开启 21 端口
因为 ftp 默认的端口为 21,而 centos 默认是没有开启的,所以要修改 iptables 文件
vim /etc/sysconfig/iptables
在行上面有 22 -j ACCEPT 下面另起一行输入跟那行差不多的,只是把 22 换成 21,然后:
wq 保存。
还要运行下,重启 iptables
service iptables restart
5 修改 selinux
外网是可以访问上去了,可是发现没法返回目录(使用 ftp 的主动模式,被动模式还是无法访问),也上传不了,因为 selinux 作怪了。
修改 selinux:
执行以下命令查看状态:
[root@bogon ~]# getsebool -a | grep ftp
执行上面命令,再返回的结果看到两行都是 off,代表,没有开启外网的访问
[root@bogon ~]# setsebool -P allow_ftpd_full_access on
[root@bogon ~]# setsebool -P ftp_home_dir on
修改/etc/vsftpd/vsftpd.conf 文件:
重启 ftp 服务:
service vsftpd restart
设置开机启动 vsftpd ftp 服务
chkconfig vsftpd on
图片上传:
1 使用 FileZilla 上传图片
使用 FTP 协议访问图片服务器
2.1FTP 协议的 URL 格式
ftp://username:userpassword@IP/路径/图片名称
在 VSFTPD 的插件中。不允许这样访问。必须要有登录环节。
FTPClient 工具
3.1FTPClient 介绍
FTPClient 是 Apache 提供的一个开源的基于 JAVA 语言的 FTP 客户端工具。
FTPClient 位于 Apache 的 commons-net 项目中。
三个工具类 :
com.bjsxt.commons.FtpUtil
package com.bjsxt.commons;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
/**
* ftp上传下载工具类
*/
public class FtpUtil {
/**
* Description: 向FTP服务器上传文件
* @param host FTP服务器hostname
* @param port FTP服务器端口
* @param username FTP登录账号
* @param password FTP登录密码
* @param basePath FTP服务器基础目录
* @param filePath FTP服务器文件存放路径。例如分日期存放:/2018/01/01。文件的路径为basePath+filePath
* @param filename 上传到FTP服务器上的文件名
* @param input 输入流
* @return 成功返回true,否则返回false
*/
public static boolean uploadFile(String host, int port, String username, String password, String basePath,
String filePath, String filename, InputStream input) {
boolean result = false;
FTPClient ftp = new FTPClient();
try {
int reply;
ftp.connect(host, port);// 连接FTP服务器
// 如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器
ftp.login(username, password);// 登录
ftp.enterLocalPassiveMode();
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return result;
}
//切换到上传目录
if (!ftp.changeWorkingDirectory(basePath+filePath)) {
//如果目录不存在创建目录
String[] dirs = filePath.split("/");
String tempPath = basePath;
for (String dir : dirs) {
if (null == dir || "".equals(dir)) continue;
tempPath += "/" + dir;
if (!ftp.changeWorkingDirectory(tempPath)) {
if (!ftp.makeDirectory(tempPath)) {
return result;
} else {
ftp.changeWorkingDirectory(tempPath);
}
}
}
}
//设置上传文件的类型为二进制类型
ftp.setFileType(FTP.BINARY_FILE_TYPE);
//上传文件
if (!ftp.storeFile(filename, input)) {
return result;
}
input.close();
ftp.logout();
result = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return result;
}
/**
* Description: 从FTP服务器下载文件
* @param host FTP服务器hostname
* @param port FTP服务器端口
* @param username FTP登录账号
* @param password FTP登录密码
* @param remotePath FTP服务器上的相对路径
* @param fileName 要下载的文件名
* @param localPath 下载后保存到本地的路径
* @return
*/
public static boolean downloadFile(String host, int port, String username, String password, String remotePath,
String fileName, String localPath) {
boolean result = false;
FTPClient ftp = new FTPClient();
try {
int reply;
ftp.connect(host, port);
// 如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器
ftp.login(username, password);// 登录
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return result;
}
ftp.changeWorkingDirectory(remotePath);// 转移到FTP服务器目录
FTPFile[] fs = ftp.listFiles();
for (FTPFile ff : fs) {
if (ff.getName().equals(fileName)) {
File localFile = new File(localPath + "/" + ff.getName());
OutputStream is = new FileOutputStream(localFile);
ftp.retrieveFile(ff.getName(), is);
is.close();
}
}
ftp.logout();
result = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return result;
}
}
com.bjsxt.commons.IDUtils
package com.bjsxt.commons;
import java.util.Random;
import java.util.UUID;
/**
* 各种id生成策略
* @version 1.0
*/
public class IDUtils {
/**
* 图片名生成
*/
public static String genImageName() {
//取当前时间的长整形值包含毫秒
long millis = System.currentTimeMillis();
//long millis = System.nanoTime();
//加上三位随机数
Random random = new Random();
int end3 = random.nextInt(999);
//如果不足三位前面补0
String str = millis + String.format("%03d", end3);
return str;
}
/**
* 商品id生成
*/
public static long genItemId() {
//取当前时间的长整形值包含毫秒
long millis = System.currentTimeMillis();
//long millis = System.nanoTime();
//加上两位随机数
Random random = new Random();
int end2 = random.nextInt(99);
//如果不足两位前面补0
String str = millis + String.format("%02d", end2);
long id = new Long(str);
return id;
}
}
com.bjsxt.commons.JsonUtils
package com.bjsxt.commons;
import java.util.List;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* JSON转换工具类
*/
public class JsonUtils {
// 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* 将对象转换成json字符串。
* <p>Title: pojoToJson</p>
* <p>Description: </p>
* @param data
* @return
*/
public static String objectToJson(Object data) {
try {
String string = MAPPER.writeValueAsString(data);
return string;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
/**
* 将json结果集转化为对象
*
* @param jsonData json数据
* @param clazz 对象中的object类型
* @return
*/
public static <T> T jsonToPojo(String jsonData, Class<T> beanType) {
try {
T t = MAPPER.readValue(jsonData, beanType);
return t;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 将json数据转换成pojo对象list
* <p>Title: jsonToList</p>
* <p>Description: </p>
* @param jsonData
* @param beanType
* @return
*/
public static <T>List<T> jsonToList(String jsonData, Class<T> beanType) {
JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);
try {
List<T> list = MAPPER.readValue(jsonData, javaType);
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
一个实现类一个接口 :
实现类:PicUploadServiceImpl.java:
package com.bjsxt.service.impl;
import com.bjsxt.commons.FtpUtil;
import com.bjsxt.commons.IDUtils;
import com.bjsxt.service.PicUploadService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.SimpleFormatter;
@Service
public class PicUploadServiceImpl implements PicUploadService {
@Value("${FTP_HOST}")
private String FTP_HOST;
@Value("${FTP_PORT}")
private int FTP_PORT;
@Value("${FTP_USERNAME}")
private String FTP_USERNAME;
@Value("${FTP_PASSWORD}")
private String FTP_PASSWORD;
@Value("${FTP_BASEPATH}")
private String FTP_BASEPATH;
@Value("${HTTP_BASE_PATH}")
private String HTTP_BASE_PATH;
@Override
public Map<String, Object> fileUpload(MultipartFile filename) {
Map<String,Object> map=new HashMap<>();
Date date=new Date();
try {
SimpleDateFormat sdf=new SimpleDateFormat("/yyyy/MM/dd");
String path=sdf.format(date);
String newFileName= IDUtils.genImageName()+filename.getOriginalFilename().substring(filename.getOriginalFilename().lastIndexOf("."));
boolean flag = FtpUtil.uploadFile(this.FTP_HOST, this.FTP_PORT, this.FTP_USERNAME, this.FTP_PASSWORD, this.FTP_BASEPATH, path, newFileName, filename.getInputStream());
if (flag){
map.put("error",0);
map.put("url",this.HTTP_BASE_PATH+path+newFileName);
}else {
map.put("error",1);
map.put("message","上传失败~");
}
}catch (Exception e){
map.put("error",1);
map.put("message","上传失败~");
e.printStackTrace();
}
return map;
}
}
接口:
package com.bjsxt.service;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
public interface PicUploadService {
Map<String,Object> fileUpload(MultipartFile filename);
}
控制层
com.bjsxt.web.controller.PicUploadController
package com.bjsxt.web.controller;
import com.bjsxt.commons.JsonUtils;
import com.bjsxt.service.PicUploadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
@Controller
@RequestMapping("/pic")
public class PicUploadController {
@Autowired
PicUploadService picUploadService;
/**
* 图片上传
*/
@RequestMapping("/upload")
@ResponseBody
public String fileUpload(MultipartFile fileName){
Map<String, Object> map = picUploadService.fileUpload(fileName);
return JsonUtils.objectToJson(map);
}
}
com.bjsxt.web.controller.ContentController
package com.bjsxt.web.controller;
import com.bjsxt.commons.JsonUtils;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.print.attribute.standard.Media;
import java.util.HashMap;
import java.util.Map;
@Controller
@RequestMapping("/content")
public class ContentController {
/**
* 内容保存
*
*/
@RequestMapping(value = "/save",produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String saveContent(String desc){
System.out.println(desc);
Map<String,Object> map=new HashMap<>();
map.put("status",200);
return JsonUtils.objectToJson(map);
}
}
index.xml
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2019/9/26
Time: 15:15
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>首页</title>
<script type="text/javascript" src="/js/kindeditor/kindeditor.js"></script>
<script type="text/javascript" src="/js/kindeditor/lang/zh_CN.js"></script>
<script type="text/javascript" src="/js/jquery-1.7.2.js"></script>
<script type="text/javascript">
$(function(){
var obj;
KindEditor.ready(function(K) {
obj= K.create('#text_id', {
uploadJson : '/pic/upload',
filePostName:'fileName',
dir:"image"
});
});
//给按钮添加点击事件
$("#but").click(function(){
//将KindEditor中的数据同步到textarea中
obj.sync();
//通过ajax方式提交表单 serialize()作用:将表单中的数据序列化为key=value&key=value.....
$.post("/content/save",$("#myForm").serialize(),function(data){
if(data.status == 200){
alert("提交成功");
}else{
alert("提交失败~~");
}
});
});
});
</script>
</head>
<body>
<form id="myForm">
<textarea rows="20" cols="20" id="text_id" name="desc"></textarea>
<input type="button" value="OK" id="but"/>
</form>
</body>
</html>