最近忙着毕业搬东西,还有搭建nginx环境什么的,巨忙也巨烦,每次再开展新的一个模块的时候总会出现一些神奇的错误,不过经过最近的努力,还是比较出色的完成了商品后台的商品上传(包括图片传)的功能,以下就是对之前的知识点总结:
为什么要用到linux呢?因为一般做项目的应该都知道,当一个网站需要反复的读取图片的时候,就不能把图片放在数据库中,比较好的选择就是先存入tomcat服务器,然后再上传到ftp上,通过nginx建立一个http,再通过http读取相应的图片文件,至于再深的我也没有再做进一步的了解,还处于会用的阶段。
需要搭建centos的话,我选择使用vm虚拟机来搭建,使用了centos7 32位系统,在安装过程中,要注意启动ens33的连接服务,否则可能会导致linux无法连接网络。在磁盘分配方面,/boot的内存大小不要小于200mb,默认的软件可以按照需求选择安装(我是使用了基础服务器的配置)。
由于linux的一些特性,导致在windows上复制的指令没办法粘贴到虚拟机中,有一个办法是使用vm自带的tools软件,而如果linux没有安装图形化界面的话,安装实在是显得过于麻烦,但linux如果安装了图形化界面总觉得有些不妥(不够逼格),所以就是使用远程连接软件进行操作。在安装完软件准备连接时,要先查看linux的ip,使用ifconfig查看ip,使用这个ip地址进行连接,成功连接后,就能开心的复制粘贴了。
nginx是一个高性能的反向代理服务器,如果单纯的使用ftp上传图片时,很有可能会暴露账号和密码信息,这是不安全的,所以通过nginx的反向代理来实现http的搭建。在安装前要注意需要yum gcc和c++的前置软件,还有另外四个我不记得了,可以查一下,如何具体安装就不在这里阐述了。安装完成后,需要配置conf配置文件,使用whereis nginx寻找根目录,使用vi进入nginx/conf/nginx.conf ,修改文件,首先更改权限用户,一般在第一行
user root;
然后在下面寻找到server,如果没有可以手动创建,增加location命令在里面写入
root /var/ftp/
这是为之后安装ftp的映射做准备。
之后保存退出编辑,在nginx的sbin目录中使用./nginx -t查看配置是否正确,如果正确,那么./nginx -s reload重新载入 就好。
vsftpd是一个可以运行在linux上的一个服务器,支持文件的上传和下载,具体安装不做具体阐述,需要说明的是需要在vsftpd的配置文件最后,注明需要修改的默认地址,例如之前在nginx配置的/var/ftp,并且使用chmod修改Var和ftp以及子目录的权限,这样有利于filezilla进行读写。
图片上传的环境搭好后,可以来完成后台的功能代码实现,首先我使用了bootstrap简单了实现了模态框的功能,为了实现添加商品的功能,在商品类目的选择上,我是用了ajax遍历类目数据库的形式,再基本的输入框都完成后,首先要做的就是实现图片的上传功能,这里可以使用现成的ftputil通用工具(是从别人那里拷的),代码如下:
package com.amake.ftphelper;
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;
/**
* -------------------------------------------
* Title : FtpUtil
* Description : ftp上传下载工具类
* Create on : 2017年4月4日 上午11:14:09
* Copyright (C) strongunion
* @author RICK
* 修改历史:
* 修改人 修改日期 修改描述
* -------------------------------------------
*/
public class FtpUtil {
/**
* Description: 向FTP服务器上传文件
* @param host FTP服务器hostname
* @param port FTP服务器端口
* @param username FTP登录账号
* @param password FTP登录密码
* @param basePath FTP服务器基础目录
* @param filePath FTP服务器文件存放路径。例如分日期存放:/2015/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);// 登录
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;
}
//
// public static void main(String[] args) {
// try {
// FileInputStream in=new FileInputStream(new File("C:\\Users\\RICK\\Pictures\\Saved Pictures\\fendou.jpg"));
// boolean flag = FtpUtil.uploadFile("192.168.2.37", 21, "ftpuser", "ftpuser", "/home/ftpuser/images", "/2017/01/16", "hello.jpg", in);
// System.out.println(flag);
// } catch (FileNotFoundException e) {
// e.printStackTrace();
// }
// }
}
然后开始分析图片的上传,在dao层中不涉及,所以可以不用对pojo和mapper类进行添加类文件,要考虑的是在service层中添加图片上传的事务管理,主要的逻辑为:连接ftp服务器,添加图片上传路径,修改图片的名字,合成http路径,将得到的值放入map中,代码如下:
package com.amake.simpleShop.service.Impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import com.amake.ftphelper.FtpUtil;
import com.amake.idhelper.IDUtil;
import com.amake.simpleShop.service.PictureService;
import com.amake.simpleshop.pojo.itemInfo;
@Service
public class PictureServiceImpl implements PictureService {
@Value("${FTP.ADDRESS}")
private String ftpAddress;
@Value("${FTP.USERNAME}")
private String ftpUsername;
@Value("${FTP.PASSWORD}")
private String ftpPassword;
@Value("${FTP.BASEPATH}")
private String ftpBasepath;
@Value("${FTP.PORT}")
private String ftpPort;
@Value("${IMG.URL}")
private String imgUrl;
@Override
public Map uploadPicture(MultipartFile uploadFile) {
// Map resultMap = new HashMap<>();
// FtpUtil ftpUtil = new FtpUtil();
// try {
// String oldName = uploadFile.getOriginalFilename();
// String newName = IDUtil.genImageName() +
// oldName.substring(oldName.lastIndexOf("."));
// String imgpath = new DateTime().toString("/yyyy/MM/dd");
// int port = 0 ;
// port = Integer.valueOf(ftpPort);
// boolean result = ftpUtil.uploadFile("192.xxx.xxx.xxx", 21, "ftpuser",
// "123456", "/var/ftp/image/", "/2018/6/24", "test.jpg",new FileInputStream(new
// File("G:\\壁纸\\test.jpg")));
// if(!result) {
// resultMap.put("error", 1);
// resultMap.put("message", "文件上传失败");
// return resultMap;
// }
// resultMap.put("error", 0);
// resultMap.put("url", imgUrl + imgpath+"/" + newName);
// } catch (Exception e) {
// resultMap.put("error", 1);
// resultMap.put("message", "文件上传发生异常");
// return resultMap;
// }
// return resultMap;
Map resultMap = new HashMap<>();
try {
FtpUtil ftpUtil = new FtpUtil();
String name = uploadFile.getOriginalFilename();
String newname = IDUtil.genImageName();
newname = newname + name.substring(name.lastIndexOf("."));
int port = Integer.valueOf(ftpPort);
String time = new DateTime().toString("/yyyy/MM/dd");
boolean result = ftpUtil.uploadFile(ftpAddress, port, ftpUsername, ftpPassword, ftpBasepath, time, newname,
uploadFile.getInputStream());
if (!result) {
resultMap.put("error", 1);
resultMap.put("message", "上传失败");
}
resultMap.put("error", "0");
resultMap.put("itmImg", imgUrl + time + newname);
return resultMap;
} catch (Exception e) {
resultMap.put("error", 1);
resultMap.put("message", "上传异常");
return resultMap;
}
}
}
其中没有直接把ftp的连接信息直接写入代码,因为文件上传属于通用类,而既然是通用类其配置也可能不一样,所以要把连接信息写入一个properties文件中,把springmvc中的配置文件加载扫描的文件名改为*.properties,这样就可以把所有配置文件全部读取到,使用@Value注解即可将配置信息注入到下面的字符串中。
之后是写controller层,要实现的逻辑就是接收文件调用service方法上传图片,并且返回一个json对象。
package com.amake.simpleshop.controller;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import com.amake.common.JsonUtils;
import com.amake.simpleShop.service.PictureService;
import com.amake.simpleshop.pojo.itemInfo;
@Controller
public class pictureController {
@Autowired
PictureService pictureService;
@RequestMapping("/picUpload")
@ResponseBody
public String pictureUpload(HttpServletRequest request) {
MultipartHttpServletRequest servletRequest = (MultipartHttpServletRequest) request;
MultipartFile multipartFile = servletRequest.getFile("img_group");
Map result = pictureService .uploadPicture(multipartFile);
String json = JsonUtils.objectToJson(result);
return json;
}
// @RequestMapping("/upload")
// @ResponseBody
// public String getParam2(@RequestParam(value = "name_group") String name_group) {
// return name_group;
// }
}
然后就是要在jsp页面写逻辑,使用ajax把数据发送到后端,使用post方法传一个json数据,这个时候需要注意的是,input中的name值要与pojo中的值相同,这样框架就可以把从表单中的值直接封装传给后台并保存到数据库。还有图片的路径获取有些麻烦的就是,需要把从前端获取到的json对象转换为json字符串,再传给后台,而后台这个时候接受到的url应该是一个%加两个16进制的数字,这个时候还需要使用Urlencode进行解码,但使用了这个解码之后还会报错,缺不影响正常使用,至于为什么报错还需要之后去解决一下,ajax的源码
$("#add_btn").click(function() {
$.post("/item/save",$("#type_form").serialize());
$.ajax({
url : "/picUpload",
type : 'POST',
data : new FormData($("#type_form")[0]),
dataType : 'JSON',
cache : false,
processData : false,
contentType : false,
success : function(result) {
var data = JSON.stringify(result.itmImg);
$.post("/item/pic",data);
$('#addItemModal').modal('hide');
to_page(maxPage);
console.log(JSON.stringify(result));
}
});
});
具体的实现可能还有些细节,等想到了再补充,这样就完成了添加+图片上传的功能模块实现。