本章目的是讲解图片服务器及其运用的使用,主讲解个别要点,细节没有全列出来,如果是未搭建过相关服务的小白白,建议先去参考下其他健全的讲解内容,你所希望的搭建一套完整体系流程,本篇可能不太适合。
事先安装docker和docker-compose这里就不说了,
我们希望在nginx下能直接拉取图片,所以ftp的上传的映射路径要和nginx的映射路径一起,方便取图
直接展示拉取配置如下:
vsftpd:
restart: always
image: fauria/vsftpd
volumes:
- /root/docker-data/vsftpd:/home/vsftpd
ports:
- "20:20"
- "21:21"
- "21100-21110:21100-21110"
environment:
FTP_USER: XXX
FTP_PASS: XXX
PASV_MIN_PORT: 21100
PASV_MAX_PORT: 21110
nginx:
restart: always
image: jwilder/nginx-proxy:latest
ports:
- "80:80"
- "443:443"
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
- /var/run/docker.sock:/tmp/docker.sock:ro
- /root/docker-data/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- /root/docker-data/nginx/site-enabled:/etc/nginx/site-enabled
- /root/docker-data/vsftpd/XXX:/usr/local/images:ro
以上镜像配置是个人配置,镜像的挂载请根据个人情况修改配置。
注:/root/docker-data/vsftpd/XXX ,XXX是ftp镜像里面配置的登陆用户名。下面会说到为什么用这个。
镜像拉取后,使用启动命令
docker-compose up -d
然后在服务器测试下链接
ftp xxx.xxx.xxx.xxx
出现 bash: ftp: command not found 是因为linux还没安装ftp服务,运行如下命令就行
yum install -y ftp
再次链接后,会要求输入用户名和密码,正确后会显示 Login successful. 算是搭建完成。
现在开始外网访问测试下,如果是自己搭建的虚拟机,记得要关闭防火墙或是开放对应的访问端口,不然远程链接不上。
我这里使用的是阿里云的ESC,需要到安全组配置端口,端口范围就是我们的20,21和浮动端口21100-21110。
我是配置了多个端口限制,如果服务太多又想全部链接的话,直接创建端口范围从 1024/65535 可一次搞定,但感觉不安全。
这里使用网页版登陆刷新下
表示配置访问成功。
注:现在看到的这个文件目录,虽然是“/” , 但在服务器上存在的位置如下
容器的位置:/home/vsftpd/xxx
宿主主机的位置:/root/docker-data/vsftpd/xxx
xxx是登录用户名,镜像挂载配置,ftp默认把登录用户所在的目录设为主目录
然后我们再配置nginx.conf,添加如下配置
location /images/ {
root /usr/local;
autoindex_exact_size off;
autoindex_localtime on;
charset utf-8;
}
参数说明:
(1) root /usr/local;: 添加图片目录映射,映射目录为/usr/local/images/
(2) autoindex on;:在Nginx下默认是不允许列出整个目录的。如需此功能,将该项设置为on
(3) autoindex_exact_size off;:默认为on,显示出文件的确切大小,单位是bytes,改为off后,显示出文件的大概大小,单位是kB或者MB或者GB
(4) autoindex_localtime on;:默认为off,显示的文件时间为GMT时间, 注意:改为on后,显示的文件时间为文件的服务器时间
(5) charset utf-8,gbk;:设置编码(防止中文乱码),可以设置对全局生效或者部分路径生效
因为ftp上次的图片文件映射到宿主机,随后又挂载到nginx内,所以当你访问http://XXX.XXX.XXX.XX/images/123.jpg时,等同于访问 /usr/local/images/123.jpg
重启动nginx镜像,让服务生效
docker restart nginx
图片服务器就搭好了。
接下来就是用代码上传下我们的图片,先建立一个基础的springboot服务,这里也直接跳过。
引入主要依赖:
<!-- 图片缩放 -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
</dependency>
<!-- net -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
<!-- ftp -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-ftp</artifactId>
<version>2.24.2</version>
</dependency>
编写两个工具类:一个图片工具类,一个FTP工具类
/**
* 图片工具类
* @author leopard
*
*/
public class ImageUtil {
/**
* 简单上传图片,不做任何处理
*
* @param imagePath 目标文件夹路径
* @param imageName 图片名称
* @param inputStream 输出流
* @return
*/
public static ImageResult uploadImage(String imagePath, String imageName, InputStream inputStream) {
BufferedInputStream inBuff = null;
BufferedOutputStream outBuff = null;
if (!"/".equals(imagePath.substring(imagePath.length() - 1, imagePath.length()))
&& !"\\".equals(imagePath.substring(imagePath.length() - 1, imagePath.length()))) {
imagePath += "/";
}
File realFile = new File(imagePath);
// 不存在文件夹则创建
if (!realFile.exists())
realFile.mkdirs();
try {
// 新建文件输入流并对它进行缓冲
inBuff = new BufferedInputStream(inputStream);
// 新建文件输出流并对它进行缓冲
outBuff = new BufferedOutputStream(new FileOutputStream(imagePath + "/" + imageName));
// 缓冲数组
byte[] b = new byte[1024 * 5];
int len;
while ((len = inBuff.read(b)) != -1) {
outBuff.write(b, 0, len);
}
// 刷新此缓冲的输出流
outBuff.flush();
} catch (FileNotFoundException a) {
a.printStackTrace();
return ImageResult.error("目标文件夹找不到");
} catch (IOException e) {
e.printStackTrace();
return ImageResult.error("操作流异常!");
} finally {
// 关闭流
try {
if (inBuff != null)
inBuff.close();
if (outBuff != null)
outBuff.close();
} catch (IOException c) {
c.printStackTrace();
}
}
StringBuffer result = new StringBuffer(imagePath);
result.append(imageName);
return ImageResult.success(result.toString(), null);
}
/**
* 图片名生成
*/
public static String genImageName() {
// 取当前时间的长整形值包含毫秒
long millis = System.currentTimeMillis();
// 加上三位随机数
int end3 = MathUtil.getNumRandom(3);
// 如果不足三位前面补0
String str = millis + String.format("%03d", end3);
return str;
}
/**
* 保存图片并且生成缩略图
*
* @param imagePath 目标文件夹路径
* @param imageName 图片名称
* @param inputStream 输出流
* @param fileSize 文件大小
* @param width 期望宽度
* @param height 期望高度
* @return
*/
public static ImageResult uploadFileAndCreateThumbnail(String imagePath, String imageName, InputStream inputStream,
long fileSize, int width, int height) {
if (width == 0 || height == 0) {
return ImageResult.error("请设置缩略图宽高!");
}
// 保存源文件
ImageResult uploadImage = uploadImage(imagePath, imageName, inputStream);
if (!SystemCodeAndMsg.SUCCESS.code().equals(uploadImage.getCode())) {
return uploadImage;
}
/*** 缩略图begin */
// 源图片路径
StringBuffer imagePathBuffer = new StringBuffer(imagePath);
// 如果最后不存在斜杆,则自行添加
if (!"/".equals(imagePath.substring(imagePath.length() - 1, imagePath.length()))
&& !"\\".equals(imagePath.substring(imagePath.length() - 1, imagePath.length()))) {
imagePathBuffer.append("/");
}
String filePathName = imagePathBuffer.append(imageName).toString();
// 目标图片(移除后缀 并改名 xxx.jpg
// ->xxxx-small)不含后缀路径,目的在下面的outputFormat(jpg),转化为固定的jpg格式文件
StringBuffer thumbnailFilePathName = new StringBuffer(filePathName.substring(0, filePathName.lastIndexOf(".")))
.append("-small");
thumbnailFilePathName = thumbnailFilePathName.append(".jpg");
String disposeUrl = thumbnailFilePathName.toString();
try {
Builder<File> builderFile = Thumbnails.of(filePathName);
// 压缩200KB
if (fileSize > 200 * 1024) {
builderFile.outputQuality((200 * 1024f) / fileSize);
}
// 按自定义大小比例缩放
builderFile.forceSize(width, height).toFile(disposeUrl);
} catch (Exception e1) {
return ImageResult.error(e1.getMessage());
}
/*** 缩略图end */
return ImageResult.success(uploadImage.getUrl(), disposeUrl);
}
}
/**
* ftp工具类
*
* @author leopard
*
*/
public class FtpUtil {
/**
* Description: 向FTP服务器上传文件
*
* @param host FTP服务器ip
* @param port FTP服务器端口
* @param username FTP登录账号
* @param password FTP登录密码
* @param basePath FTP服务器基础目录,/images --> /home/vsftpd/${FTP登录账号 }/images
* @param filePath FTP服务器文件存放路径。例如分日期存放:/2018/05/28。文件的路径为basePath+filePath
* @param filename 上传到FTP服务器上的文件名
* @param input 输入流
* @return
*/
public static FtpResult uploadFileToImage(String host, int port, String username, String password, String basePath,
String filePath, String filename, InputStream inputStream, long fileSize, int width, int height) {
String imageTempPath = ClassUtils.getDefaultClassLoader().getResource("").getPath() + "/imageTemp";
ImageResult uploadFileAndCreateThumbnail = ImageUtil.uploadFileAndCreateThumbnail(imageTempPath, filename,
inputStream, fileSize, width, height);
if (!SystemCodeAndMsg.SUCCESS.code().equals(uploadFileAndCreateThumbnail.getCode())) {
return new FtpResult(uploadFileAndCreateThumbnail);
}
FTPClient ftp = new FTPClient();
String imagePath = basePath + filePath;
StringBuffer initUrl = new StringBuffer(imagePath); // 原文件上传路径
StringBuffer disposeUrl = new StringBuffer(imagePath); // 处理后文件上传路径
try {
int reply;
ftp.connect(host, port);// 连接FTP服务器
// 如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器
ftp.login(username, password);// 登录
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return FtpResult.error("ftp远程登录失败!");
}
// 切换到上传目录--changeWorkingDirectory()方法类似linux的cd命令
if (!ftp.changeWorkingDirectory(imagePath)) {
// 如果目录不存在创建目录
String[] dirs = imagePath.split("/");
String tempPath = "/";
for (String dir : dirs) {
if (null == dir || "".equals(dir)) {
continue;
}
tempPath += "/" + dir;
if (!ftp.changeWorkingDirectory(tempPath)) {
//makeDirectory()方法类似linux的mkdir命令
if (!ftp.makeDirectory(tempPath)) {
return FtpResult.error("ftp远程创建文件夹失败!");
} else {
ftp.changeWorkingDirectory(tempPath);
}
}
}
}
// 设置为被动模式
ftp.enterLocalPassiveMode();
// 设置上传文件的类型为二进制类型
ftp.setFileType(FTP.BINARY_FILE_TYPE);
String initImageFileUrl = uploadFileAndCreateThumbnail.getUrl();
String disposeImageFileUrl = uploadFileAndCreateThumbnail.getDisposeUrl();
// 上传原文件
if (StringUtils.isNotBlank(initImageFileUrl)) {
File initImageFile = new File(initImageFileUrl);
if (!ftp.storeFile(filename, new FileInputStream(initImageFile))) {
return FtpResult.error("ftp远程上传原文件失败!");
}
initUrl.append(filename);
// initImageFile.delete();//所在进程运行中,删除不了
}
// 上传压缩文件
if (StringUtils.isNotBlank(disposeImageFileUrl)) {
File disposeImageFile = new File(disposeImageFileUrl);
String disposeFileName = disposeImageFileUrl.substring(disposeImageFileUrl.lastIndexOf("/") + 1,
disposeImageFileUrl.length());
if (!ftp.storeFile(disposeFileName, new FileInputStream(disposeImageFile))) {
return FtpResult.error("ftp远程上传压缩文件失败!");
}
disposeUrl.append(disposeFileName);
// disposeImageFile.delete();//所在进程运行中,删除不了
}
inputStream.close();
ftp.logout();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return FtpResult.success(initUrl.toString(), disposeUrl.toString());
}
}
FTP工具类问题检测,一般都看reply的数值,200<= reply
如:一开始,工具类里得局部参数tempPath不是从“ / ”,而是从“/temp”,开始,那拼接出入的“/images”,就变成“/temp/images”,
makeDirectory() 方法也无法一次性创建两层目录。进入源码或是打印出来,可以看到reply 的数组为550.
注:本章主要是运用搭建图片服务器和上传图片为主,所以FTP工具类被个人改动为主为图片服务,如要上传其他类型文件,可自行修改。
测试代码块:
@RequestMapping(value = "/savePic", method = RequestMethod.POST)
@ResponseBody
public JSONObject sendSimple(MultipartFile uploadFile) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", SystemCodeAndMsg.WEB_SUCCESS.code());
jsonObject.put("msg", SystemCodeAndMsg.WEB_SUCCESS.msg());
// 判断上传图片是否为空
if (null == uploadFile || uploadFile.isEmpty()) {
jsonObject.put("msg", "文件不能为空");
return jsonObject;
}
if (uploadFile.getSize() >= 5 * 1024 * 1024) {
jsonObject.put("msg", "文件不能大于5M");
return jsonObject;
}
// 取文件扩展名
String ext = FilenameUtils.getExtension(uploadFile.getOriginalFilename());
String imageName = ImageUtil.genImageName() + "." + ext;
// 文件在服务器的存放路径,应该使用日期分隔的目录结构
DateTime dateTime = new DateTime();
String filePath = dateTime.toString("yyyy/MM/dd");
String IMAGE_BASE_URL = "H:";
String imagePath = IMAGE_BASE_URL + "/images/" + filePath;
try {
//本地图片上传方式
// ImageResult uploadFileAndCreateThumbnail = ImageUtil.uploadFileAndCreateThumbnail(imagePath, imageName,
// uploadFile.getInputStream(), uploadFile.getSize(), 400, 400);
// jsonObject.put("msg", uploadFileAndCreateThumbnail.getUrl());
//远程FTP图片服务器上传
FtpResult uploadFile2 = FtpUtil.uploadFileToImage("xxx.leopardxxx.com", 21, "xxx", "xxx", "/images/",
filePath, imageName, uploadFile.getInputStream() ,uploadFile.getSize() , 400 , 400);
jsonObject.put("msg", uploadFile2.getDisposeUrl());
} catch (Exception e) {
e.printStackTrace();
return jsonObject;
}
// 返回结果,生成一个可以访问到图片的url返回
return jsonObject;
}
文章内的 FtpResult 和 ImageResult 两个只是回调参数封装,还有表单功能,这里不做展示。