简要分析:
接着上一节,我哦们分析一下对应的jsp页面可以发现,其请求的路径为/pic/uplaod/所以,我们可以联想到,我们的SpringMVC中的映射请求的页面的控制器与这个路径有关,我们在下面的定义中可以发现:
它与NESTYLES定义有关,并且了解到,其定义在commons.js中,所以,查看可以找到NEWSTYLES的定义:
可以发现,确实其请求路径为/pic/upload。对于这个我们就可以设计这个业务的逻辑了,一步步去实现这个图片上传的功能。
Servcie层的实现
首先,我们需要先进行service层的编写,我们定义一个PictureService接口,然后编写实现类去实现这个接口,接口中,定义了这个功能的函数,代码如下:
//接口的定义:
public interface PictureService {
PictureResult uploadFile(MultipartFile uploadFile) throws Exception;
}
//对应实现类
@Service
public class PictureServiceImpl implements PictureService{
@Value("${FTP_ADDRESS}")
private String FTP_ADDRESS;
@Value("${FTP_PORT}")
private Integer FTP_PORT;
@Value("${FTP_USERNAME}")
private String FTP_USERNAME;
@Value("${FTP_PASSWORD}")
private String FTP_PASSWORD;
@Value("${FTP_BASE_PATH}")
private String FTP_BASE_PATH;
@Value("${IMAGE_BASE_URL}")
private String IMAGE_BASE_URL;
@Override
public PictureResult uploadFile(MultipartFile uploadFile) throws Exception {
//上传文件功能实现
String path = savePicture(uploadFile);
//返回显示
PictureResult result = new PictureResult(0, IMAGE_BASE_URL + path);
return result;
}
private String savePicture(MultipartFile uploadFile){
String result = null;
try{
if(uploadFile.isEmpty())
return null;
String filePath = "/" + new SimpleDateFormat("yyyy").format(new Date())
+ "/" + new SimpleDateFormat("MM").format(new Date()) + "/"
+new SimpleDateFormat("dd").format(new Date());
//读取原始文件的名字
String originalFilename = uploadFile.getOriginalFilename();
//构建新的文件的名字
String newName = IDUtils.getImageName() + originalFilename.substring(originalFilename.lastIndexOf("."));
//转存文件,上传到ftp;
boolean fileflage = uploadFile.isEmpty();
FileInputStream uploads = (FileInputStream)uploadFile.getInputStream();
System.out.println(fileflage);
boolean flag = FtpUtil.uploadFile(FTP_ADDRESS, FTP_PORT, FTP_USERNAME, FTP_PASSWORD, FTP_BASE_PATH, filePath, newName,
uploads);
System.out.println(flag);
result = filePath + "/" + newName;
}catch(Exception e){
e.printStackTrace();
}
return result;
}
}
其主要的流程为,获取原始文件的后缀名,之后利用获取的年月日分级建立文件夹,之后,将其作为后面要返回的文件的路径中filepath,同时,应用新的文件名生成方式,生成新的文件名,在这里我们参考 IDUtils类的文件名的生成方式就可以理解文件名的命名方式,以及返回的url请求文件地址。
也许你会发现,我们使用了spring中的使用注解获取的具体的值的方式,所以,我们需要先定义一个properties文件,这是因为,这些东西不能够在程序进行硬编码,万一有变动更改起来很麻烦,所以,使用配置文件管理要更方便一些,之后,我们需要加载这个定义的文件,问题是,如何加载呢,这个可以在将这个文件放置在这个web工程的resources文件夹中的resource文件夹中,这个文件夹的创建过程在前面的章节中介绍过了。所以我们将文件命名为properties.properties:
#FTP的选相关配置
#FTP的ip地址
FTP_ADDRESS=192.168.59.128
FTP_PORT=21
FTP_USERNAME=ftpuser
FTP_PASSWORD=************
FTP_BASE_PATH=/home/ftpuser/ww/images
#图片服务器的相关配置
#服务器的基础的url配置
IMAGE_BASE_URL=http://192.168.59.128/images
因为我们要通过ftp服务上传文件,所以这个资源文件中定义了ftp和访问路径的很多配置,配置好了之后如何加载呢,还是这个问题,这个可以修改applicationContext-dao.xml文件中下面标签中的内容:
<context:property-placeholder location="classpath:resource/*.properties" />
这样,全部的资源配置文件就都会被加载,也就是说实现类中的Value可以获取到我们定义的配置文件的内容了,这样要比通过使用Properties类然后读取文件,设置属性要方便一些。
可以注意到,我们此处要实现的是包含多文件的上传,所以,在这里要使用springMVC中的MultipartFile类进行文件的获取,除此之外,还需要注意,在使用这个类的时候,在前端对应的地方要添加以下内容:enctype="multipart/form-data"来确保匿名上载文件的正确编码。除此之外,还需还要我们在springmvc.xml文件中配置多文件解析器,也即MultipartResolver。定义设置如下: <bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设定默认编码 -->
<property name="defaultEncoding" value="UTF-8"/>
<!-- 设定文件上传的最大值 -->
<property name="maxUploadSize" value="5242880"/>
</bean>
这样,对应的业务层的逻辑基本上就完成了,下面补充,构建新的文件名的IDUtils类,和返回结果类PictureResult类:
/**
* 各种id生成策略
*/
public class IDUtils {
/**
* 图片名生成
*/
public static String getImageName() {
//取当前时间的长整形值包含毫秒
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 getItemId() {
//取当前时间的长整形值包含毫秒
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;
}
}
public class PictureResult {
/**
* 上传图片返回值,成功:0 失败:1
*/
private Integer error;
/**
* 回显图片使用的url
*/
private String url;
/**
* 错误时的错误消息
*/
private String message;
public PictureResult(Integer state, String url) {
this.url = url;
this.error = state;
}
public PictureResult(Integer state, String url, String errorMessage) {
this.url = url;
this.error = state;
this.message = errorMessage;
}
public Integer getError() {
return error;
}
public void setError(Integer error) {
this.error = error;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Controller层的实现
这样,我们接下类可可以处理Controller层了:
结合我们前面的分析,可以将Controller层设计如下:
@Controller
@RequestMapping("/pic")
public class PictrueController {
@Autowired
private PictureService pictureService;
@RequestMapping("/upload")
@ResponseBody
public String pictureUpload(MultipartFile uploadFile) throws Exception{
PictureResult pictureResult = pictureService.uploadFile(uploadFile);
//为保证功能的兼容,需要将result转换成字符串
String json = JsonUtils.objectToJson(pictureResult);
return json;
}
}
其中,为了能够尽量的实现对多种浏览器的支持,我们尽量的将返回结果转换为字符串形式的,我们使用了JsonUtils工具,这个工具能够实现类型的转化,具体的转换可以参考下面的代码:
/**
* newstyles项目自定义响应结构
*/
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;
}
}
这样我们就可以实现了,然后测试,测试我们发现文件上传还是失败,无论是谷歌还是360,火狐等,这个问题也困绕初学者的我一天的时间,最后还是发现了问题的所在:
问题插曲:
因为我们定义的FTPUtils能够上传FileInputStream类型的文件流,所以当我们获取到SpringMVC中的MultipartFile类型的文件时,其类型还是MultipartFile,即使我们使用其getInputStream()方法获取的文件流返回类型是InputStream类型的:
所以,查看Java.io中有关InputStream类型的文档中,发现,FileInputStream类继承了IputStream类,所以,我们可以做一个类型转化,将InputStream类型转换为FileInputStream类型,这样测试上传就成功了。
除此之外,还应该注意服务器的文件夹的权限的问题,是否开启了足够的权限。再者应该注意Nginx服务器的配置问题,里面的配置是映射到对应的请求url,在这里我的映射路径为:
location /images/ {
root /home/ftpuser/ww; #服务器基础目录
autoindex on;
}
这样的话就可以通过url直接映射到对应的nginx文件夹中的iamges文件夹中,不必再从home目录开始了,样式为:http://主机名/images/对应文件,这样就可以进行对nginx服务器的内容进行访问了。
测试结果;
点击开始上传按钮后:
在之前的代码中也可以看到,当Kindeditor上传成功后,返回的信息是一个url请求地址,其实对于这个图片上传来说,其获取的为其在nginx服务器上的访问的url路径,可以看到正常回显了,同时查看服务器中是否有内容:
就可以看到本地服务器中已经暗战我们上面叙述的方式建立了文件夹,同时,上传了文件。