使用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组件,不用不知道一用吓一跳哇. 在使用的过程中遇到了很多让意想不到的问题,后来也因为时间问 ...
随机推荐
- 【Gamma阶段】第八次Scrum Meeting
冰多多团队-Gamma阶段第八次Scrum会议 工作情况 团队成员 已完成任务 待完成任务 卓培锦 编辑器风格切换(添加夜间模式) UI界面手势切换 牛雅哲 添加pytorch训练dict和ssh工具 ...
- [Gamma] 发布说明
[Gamma] 发布说明 发布网址为http://60.205.230.0 新功能前瞻 团队合作:支持多人合作完成项目 项目进度管理:便于监控项目进度 站内信系统:团队合作与审核需要 已知BUG修复 ...
- python 字典转成对象
database = { "key1": { 'period':999, "data": { 'a':1, 'b':2, } }, "key2&quo ...
- JOI徽章
[题目描述] 日本信息学奥赛委员会为了应援将要去台湾参加 IOI 的选手们,打算制作一面新的 JOI 旗帜 .JOI 旗帜为由 M 行 N 列的 M*N 个正方形组 成的图形,每个正方形里写有 J,O ...
- 修改ssh服务器默认端口号
1.查看当前ssh服务器端口号 在修改ssh服务器的端口号之前,首先查看ssh服务器监听的端口号,使用netstat命令: $ sudo netstat -tunlp | grep "ssh ...
- go 语言的一个赋值操作
最近在看client-go源码,在源码的\tools\caches\store.go文件中有一行代码不得其解(如下标黄内容),它将一个struct赋值给了一个interface type Store ...
- Java8 新特性 默认方法
默认方法为什么出现 默认方法的出现是因为在java8设计的过程中,因为加入了Lamdba表达式,和函数式接口,所以在非常多的接口里面要加入新的方法,但是如果在接口里面直接加入新的方法,那么以前写的所有 ...
- 第一周第二部分 coursera.org
即使J(,)=,也不能是完美估计,因为其他数据可能存在误差 取任何颜色并沿着“圆”走,就可以得到相同的成本函数值,右图三个点的J(,)相同 越靠近圆心,J(,)越小 梯度下降算法可以将代价函数J()最 ...
- sql 语言--- DML,DDL,DQL,DCL,TCL,CCL
结构化查询语言(Structured Query Language)简称SQL 是一种特殊目的的编程语言,是一种数据库查询和程序设计语言,用于存取数据以及查询.更新和管理 ...
- Sitecore 内容版本设计
Sitecore内容变化的跟踪显着偏离既定规范.了解Sitecore中版本控制和工作流程的细节,该产品是对这些发布工具的回答. 在出版界,实时跟踪内容变化很常见,可能是由于Microsoft Word ...