使用java spring开发ckeditor的文件上传功能(转)
说明:原帖提供的代码无法直接运行。本人在原帖基础上做了一些修改,修复了一些bug。
关于CKEditor的使用,网络上有无数的文章,这里不再赘述。而关于java支持的文件上传功能,网络上同样有千千万万的文章,但是遍历十几二 十篇就会发现,千篇一律的抄袭,各种爬虫程序带来的互联网信息垃圾给我们的信息获取带来很大的障碍。那些使用java开发CKEditor上传和文件浏览 功能,千篇一律的使用的是Servlet的方式,传统的JSP下的模式,不管现在框架已经发展到何种境界,都是一样的照搬,抄袭,让人很无奈,其实这是一 个很简单的功能,就是获取请求,上传文件,返回结果而已。
通常我们的项目会使用springMVC或者struts或者其他框架,来作为C的部分,说白了,也就是控制请求和执行流程。而如果我们在采用了 spring或者struts 的系统中,还是去使用servlet配置urlmapping来拦截文件上传请求,那岂不是让人很别扭,那些copy文章的人,怎么就不去考虑考虑呢?
言归正传,ckeditor默认的图片插入,是不带“浏览图片”和“上传”标签的,所以需要手动配置一下,开启这个功能,而主要的猫腻,也就是这个配置的地方:
config.language = 'zh-cn';
config.skin = 'kama';
// 图片上传配置
config.filebrowserUploadUrl = 'upload?type=File';
config.filebrowserImageUploadUrl = 'upload?type=Image';
config.filebrowserFlashUploadUrl = 'upload?type=Flash';
上面我们配置了路径,由于ckeditor并没有支持java的插件,所以这里也是需要我们自己开发的内容。上面的上传和浏览图片的请求,我们定义了自己的url,也就是上述.do结尾的部分,我的测试工程中使用的是springMVC,利用的是2.5以后的注解功能。
好了,这里配置了,那么作用是什么呢?打开ckeditor:
可以看到,上述多出了上传和浏览的功能,现在点击是没用的,因为我们还没开发具体的实现嘛
那么那些配置出了多出了这两个地方,还有什么作用呢?用firefox看看其上传部分的代码,可以很明显直到那个配置的url到底是什么用途了
其实就是一个上传form,那个action的url就是我们配置的而已,所以这里只要开发拦截这个请求并上传文件的功能就可以了嘛,多简单的事儿。
写代码前,看看我们的现状吧,我们可能会让这个图片上传到图片服务器去,但是呢,兜里尚未有足够的银子,而且这个图片暂时量不大,所以现阶段还是保 存在应用的特定位置中,够无奈的吧,没办法,谁让咱么有特定的图片服务器呢,那么就下办法在本应用下作文章吧。我们采用一个upload/img的目录, 来保存图片文件,以后要迁移到图片服务器也方便些。
但是问题来了,所有的图片都放到这个文件夹下,岂不是很庞大,而且一旦超过1000张,文件搜索速度是有点折磨的,那就咱想想办法吧,那就再建立一 级目录,每一级下面最多放500张,如果当前文件夹下超过了500张,就重新建立一个文件夹,放入其中。这样目录就变成了三级的 upload/img/20100824 我们采用时间字符串来命名。
那就看看代码吧:
此处代码已更新
package cn.taas.afdb.controller; import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; /**
* 上传图片
* <p>
* 为CKEDITOR定制的图片上传功能,后续可以扩展上传其他格式的文件
* 上传的文件的基础路径为: ${apache.home}/${project.name}/${project.name}/resources/upload/img/${'yyyyMMdd'}/
* 每个文件夹下最多500个文件
* </p>
*
*/
@Controller
@RequestMapping("/admin/upload")
public class AdminFileUploadController {
protected final Logger logger = Logger
.getLogger(AdminFileUploadController.class); private static final String FILE_UPLOAD_DIR = "/upload";
private static final String FILE_UPLOAD_SUB_IMG_DIR = "/img";
private static final String FOR_RESOURCES_LOAD_DIR = "/resources";
//每个上传子目录保存的文件的最大数目
private static final int MAX_NUM_PER_UPLOAD_SUB_DIR = 500;
//上传文件的最大文件大小
private static final long MAX_FILE_SIZE = 1024 * 1024 * 2;
//系统默认建立和使用的以时间字符串作为文件名称的时间格式
private static final String DEFAULT_SUB_FOLDER_FORMAT_AUTO = "yyyyMMdd";
//这里扩充一下格式,防止手动建立的不统一
private static final String DEFAULT_SUB_FOLDER_FORMAT_NO_AUTO = "yyyy-MM-dd"; @RequestMapping(method = RequestMethod.GET)
public void processUpload(ModelMap modelMap, HttpServletRequest request,
HttpServletResponse response) {
processUploadPost(modelMap, request, response);
return;
} @RequestMapping(method = RequestMethod.POST)
public void processUploadPost(ModelMap modelMap,
HttpServletRequest request, HttpServletResponse response) { // 判断提交的请求是否包含文件
boolean isMultipart = ServletFileUpload.isMultipartContent(request); if (!isMultipart) {
return;
} // 获取目录
File folder = buildFolder(request);
if (null == folder) {
return;
} try {
response.setContentType("text/html; charset=UTF-8");
response.setHeader("Cache-Control", "no-cache");
PrintWriter out = response.getWriter();
// 上传文件的返回地址
String fileUrl = ""; FileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload servletFileUpload = new ServletFileUpload(factory);
servletFileUpload.setFileSizeMax(MAX_FILE_SIZE); List<FileItem> fileitem = servletFileUpload.parseRequest(request); if (null == fileitem || 0 == fileitem.size()) {
return;
} Iterator<FileItem> fileitemIndex = fileitem.iterator();
if (fileitemIndex.hasNext()) {
FileItem file = fileitemIndex.next(); if (file.isFormField()) {
logger.error("上传文件非法!isFormField=true");
} String fileClientName = getFileName(file.getName());
String fileSuffix = StringUtils.substring(fileClientName,
fileClientName.lastIndexOf(".") + 1);
if (!StringUtils.equalsIgnoreCase(fileSuffix, "jpg")
&& !StringUtils.equalsIgnoreCase(fileSuffix, "jpeg")
&& !StringUtils.equalsIgnoreCase(fileSuffix, "bmp")
&& !StringUtils.equalsIgnoreCase(fileSuffix, "gif")
&& !StringUtils.equalsIgnoreCase(fileSuffix, "png")
&& !StringUtils.equalsIgnoreCase(fileSuffix, "txt")
&& !StringUtils.equalsIgnoreCase(fileSuffix, "doc")
&& !StringUtils.equalsIgnoreCase(fileSuffix, "docx")
&& !StringUtils.equalsIgnoreCase(fileSuffix, "xls")
&& !StringUtils.equalsIgnoreCase(fileSuffix, "xlsx")
&& !StringUtils.equalsIgnoreCase(fileSuffix, "csv")
&& !StringUtils.equalsIgnoreCase(fileSuffix, "ppt")
&& !StringUtils.equalsIgnoreCase(fileSuffix, "pptx")
&& !StringUtils.equalsIgnoreCase(fileSuffix, "pdf")
&& !StringUtils.equalsIgnoreCase(fileSuffix, "wps")
&& !StringUtils.equalsIgnoreCase(fileSuffix, "et")
&& !StringUtils.equalsIgnoreCase(fileSuffix, "dps")) {
logger.error("上传文件的格式错误=" + fileSuffix);
out.println("<script type=\"text/javascript\">alert('格式错误,仅支持jpg|jpeg|bmp|gif|png|txt|doc|docx|xls|xlsx|csv|ppt|pptx|pdf|wps|et|dps格式');</script>");
out.flush();
out.close();
return;
} if (logger.isInfoEnabled()) {
logger.info("开始上传文件:" + file.getName());
} String fileServerName = generateFileName(folder, fileSuffix);
// 为了客户端已经设置好了图片名称在服务器继续能够明确识别,这里不改名称
File newfile = new File(folder, fileServerName);
file.write(newfile); if (logger.isInfoEnabled()) {
logger.info("上传文件结束,新名称:" + fileServerName + ".floder:"
+ newfile.getPath());
} // 组装返回url,以便于ckeditor定位图片
fileUrl = FOR_RESOURCES_LOAD_DIR + FILE_UPLOAD_DIR + FILE_UPLOAD_SUB_IMG_DIR + "/" + folder.getName() + "/" + newfile.getName();
fileUrl = StringUtils.replace(fileUrl, "//", "/");
fileUrl = request.getContextPath() + fileUrl; // 将上传的图片的url返回给ckeditor
String callback = request.getParameter("CKEditorFuncNum");
out.println("<script type=\"text/javascript\">");
out.println("window.parent.CKEDITOR.tools.callFunction("
+ callback + ",'" + fileUrl + "',''" + ")");
out.println("</script>");
} out.flush();
out.close(); } catch (IOException e) {
logger.error("上传文件发生异常!", e);
} catch (FileUploadException e) {
logger.error("上传文件发生异常!", e);
} catch (Exception e) {
logger.error("上传文件发生异常!", e);
} return;
} private String generateFileName(File folder, String suffix) {
String filename;
File file;
Date date = new Date(System.currentTimeMillis());
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS");
String base = format.format(date);
filename = base + "." + suffix;
file = new File(filename);
int i = 1;
while (file.exists()) {
filename = String.format("%s_%d.%s", base, i, suffix);
i++;
}
return filename;
} /**
* 获取文件名称
* @param str
* @return
*/
private String getFileName(String str){
int index = str.lastIndexOf("//");
if(-1 != index){
return str.substring(index);
} else {
return str;
}
} /**
* 创建目录
*
* @return
*/
private File buildFolder(HttpServletRequest request) {
// 这里照顾一下CKEDITOR,由于ftl放置位置的原因,这里必须要在freemarker目录下才能被加载到图片,否则虽然可以正常上传和使用,但是
// 在控件中无法正常操作
String realPath = request.getSession().getServletContext()
.getRealPath(FOR_RESOURCES_LOAD_DIR); logger.error(realPath); // 一级目录,如果不存在,创建
File firstFolder = new File(realPath + FILE_UPLOAD_DIR);
if (!firstFolder.exists()) {
if (!firstFolder.mkdir()) {
return null;
}
} // 二级目录,如果不存在,创建
String folderdir = realPath + FILE_UPLOAD_DIR + FILE_UPLOAD_SUB_IMG_DIR;
if (logger.isDebugEnabled()) {
logger.debug("folderdir" + folderdir);
} if (StringUtils.isBlank(folderdir)) {
logger.error("路径错误:" + folderdir);
return null;
} File floder = new File(folderdir);
if (!floder.exists()) {
if (!floder.mkdir()) {
logger.error("创建文件夹出错!path=" + folderdir);
return null;
} }
// 再往下的文件夹都是以时间字符串来命名的,所以获取最新时间的文件夹即可
String[] files = floder.list();
if (null != files && 0 < files.length) {
// 含有子文件夹,则获取最新的一个
Date oldDate = null;
int index = -1;
for (int i = 0; i < files.length; i++) {
String fileName = files[i]; try {
Date thisDate = DateUtils.parseDate(fileName, new String[] {
DEFAULT_SUB_FOLDER_FORMAT_AUTO, DEFAULT_SUB_FOLDER_FORMAT_NO_AUTO });
if (oldDate == null) {
oldDate = thisDate;
index = i;
} else {
if (thisDate.after(oldDate)) {
// 保存最新的时间和数组中的下标
oldDate = thisDate;
index = i;
}
}
} catch (ParseException e) {
// 这里异常吃掉,不用做什么,如果解析失败,会建立新的文件夹,防止人为的建立文件夹导致的异常。
}
}// for // 判断当前最新的文件夹下是否已经存在了最大数目的图片
if (null != oldDate && -1 != index) {
File pointfloder = new File(folderdir + File.separator
+ files[index]);
if (!pointfloder.exists()) {
if (!pointfloder.mkdir()) {
logger.error("创建文件夹出错!path=" + folderdir);
return null;
}
} // 如果文件夹下的文件超过了最大值,那么也需要新建一个文件夹
String[] pointfloderFiles = pointfloder.list();
if (null != pointfloderFiles
&& MAX_NUM_PER_UPLOAD_SUB_DIR < pointfloderFiles.length) {
return buildNewFile(folderdir);
} return pointfloder;
} // 查找当前子文件夹失败,新建一个
return buildNewFile(folderdir);
} else {
// 不含有子文件夹,新建一个,通常系统首次上传会有这个情况
return buildNewFile(folderdir);
} } /**
* 创建一个新文件
* @param path
* @return
*/
private File buildNewFile(String path){
// 不含有子文件夹,新建一个,通常系统首次上传会有这个情况
File newFile = buildFileBySysTime(path);
if (null == newFile) {
logger.error("创建文件夹失败!newFile=" + newFile);
} return newFile;
} /**
* 根据当前的时间建立文件夹,时间格式yyyyMMdd
*
* @param path
* @return
*/
private File buildFileBySysTime(String path) {
DateFormat df = new SimpleDateFormat(DEFAULT_SUB_FOLDER_FORMAT_AUTO);
String fileName = df.format(new Date());
File file = new File(path + File.separator + fileName);
if (!file.mkdir()) {
return null;
}
return file;
}
}
这里我用的是注解@Controller的方式,如果你用的是struts,那就去配置一个action吧,都是很简单的事儿……
再啰嗦点别的,这里如果使用这样的方式开发完成以后,在eclipse里直接去run in server,你会发现上传没报错,但是返回的url根本不显示图片嘛,怎么回事儿?
是这样子的,在eclipse下有一个server的显示框,双击你建立的server,可以配置server的启动路径等内容,其中有一个需要我们特别关注的选项:
将来项目发布的时候,如果你直接将项目拷贝到tomcat里,也没啥子问题,但是测试阶段更为方便的,是在这里更改下部署路径等,让我们的上传功能跟上线之后一样生效。
上述的上传日志,会打印出:
D:/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp2/wtpwebapps/NormandyPosition/freemarker/upload/img/20100824/logo.jpg
对比下上图的内容,是不是你也明白了是怎么回事儿了,上述的其实是一个临时的目录,所以这里我们要更改一下:
把server path改成你的apache的安装目录,你可以直接选择第二个单选按钮
把deploy path改成你的项目名称
这样改完之后呢:
搞定了~
下篇咱想想怎么去弄一个多级目录的服务器的图片浏览功能
原帖地址:http://blog.csdn.net/quzishen/article/details/5834207
使用java spring开发ckeditor的文件上传功能(转)的更多相关文章
- Spring中MultipartHttpServletRequest实现文件上传
Spring中MultipartHttpServletRequest实现文件上传 转贴自:http://my.oschina.net/nyniuch/blog/185266 实现图片上传 用户必须能 ...
- 关于我使用spring mvc框架做文件上传时遇到的问题
非常感谢作者 原文:https://blog.csdn.net/lingirl/article/details/1714806 昨天尝试着用spring mvc框架做文件上传,犯了挺多不该犯的毛病问题 ...
- 利用spring的MultipartFile实现文件上传【原】
利用spring的MultipartFile实现文件上传 主要依赖jar包 spring-web-3.0.6.RELEASE.jar 用到 (org.springframework.web.multi ...
- Java 客户端操作 FastDFS 实现文件上传下载替换删除
FastDFS 的作者余庆先生已经为我们开发好了 Java 对应的 SDK.这里需要解释一下:作者余庆并没有及时更新最新的 Java SDK 至 Maven 中央仓库,目前中央仓库最新版仍旧是 1.2 ...
- Java进阶学习:将文件上传到七牛云中
Java进阶学习:将文件上传到七牛云中 通过本文,我们将讲述如何利用七牛云官方SDK,将我们的本地文件传输到其存储空间中去. JavaSDK:https://developer.qiniu.com/k ...
- Spring MVC-从零开始-文件上传(未完待续)
Spring MVC-从零开始-文件上传(未完待续)
- Java实现一个简单的文件上传案例
Java实现一个简单的文件上传案例 实现流程: 1.客户端从硬盘读取文件数据到程序中 2.客户端输出流,写出文件到服务端 3.服务端输出流,读取文件数据到服务端中 4.输出流,写出文件数据到服务器硬盘 ...
- [php基础]PHP.INI配置:文件上传功能配置教程
昨天分享了在PHP网站开发中如何在php.ini中配置实现session功能的PHP教程,今天继续分享在利用PHP实现文件上传功能时几点关键php.ini的配置. 说到在php.ini中的文件上传的配 ...
- 使用element的upload组件实现一个完整的文件上传功能(上)
说到标题就有点心塞了,前段时间项目上需要实现一个文件上传的功能,然后就咔咔的去用了element的upload组件,不用不知道一用吓一跳哇. 在使用的过程中遇到了很多让意想不到的问题,后来也因为时间问 ...
随机推荐
- eclipse.ini相关问题
一般新装的eclipse,在eclipse.ini文件中,有设置默认的内存信息,如果你要开发一个大的项目或者导入大的项目,那么,eclipse就会时不时报出这样的错误:An internal erro ...
- 修改Ubuntu系统的用户名和主机名
1.前言 当我们拿到别人拷贝的系统时,往往需要修改拷贝系统的密码.用户名和主机名,本文简单介绍在Ubuntu下如何进行相关配置文件的修改. 2.如何修改 (1)修改root的密码 运行下面的命令对对r ...
- BBC micro:bit引脚介绍
另外两个大引脚(3V和GND)是非常不同的! 注意 标记为3V和GND的引脚与电路板的电源相关,千万不要连接在一起. 电源输入:如果BBC micro:bit由USB或电池供电,则可以使用3V引脚作为 ...
- 用shell脚本批量进行xss跨站攻击请求
由于执行的xss攻击请求他多了,初步估计要执行83次,而且还要执行3篇,如果手工一个一个去执行,说出去,我还配叫自动化大师吗: 有鉴于此,边打算自己编写一个脚本进行批量执行: 而短脚本的编写,非she ...
- Channel概述
前言 前两篇文章介绍了NIO核心部分部分之一的缓冲区的相关内容,接下来我们继续学习NIO中另一个重要的核心部分--Channel(通道). 在学习这篇文章之前,先做下简单的说明,本文是一篇关于通道的概 ...
- oc的运行时系统
Objective-C is a class-based object system. Each object is an instance of some class; the object's i ...
- LINUX 下.NET Core 微服务部署实战
前言 最近一直在开发部署.也没有总结一下.从5月份开始出差到现在基本没有发过博客,哎,惭愧. 一直在弄微服务,后续会慢慢更新下面这个系列.欢迎各位大佬交流指点. 分布式理论专题 1..net core ...
- 阿里云RDS数据库sql server 导入数据并添加作业小结
在阿里云购买ECS服务器和RDS数据库时,要注意网络类型要一致,最好都是VPC,否则ECS不能在内网访问RDS,只能从外网访问:在RDS控制台左侧,数据库安全性的IP白名单中添加ECS外网IP:在数据 ...
- Go的基础类型
1. 命名 Go语言中的函数名.变量名.常量名.类型名.语句标号和包名等所有的命名,都遵循一个简单的命名规则:一个名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母.数字或 ...
- .net core web api 默认的模型验证
转载自 https://www.codercto.com/a/45938.html