http://secyaher.blog.163.com/blog/static/38955772012019528985/
关于CKEditor的使用,网络上有无数的文章,这里不再赘述。而关于java支持的文件上传功能,网络上同样有千千万万的文章,但是遍历十几二十篇就会发现,千篇一律的抄袭,各种爬虫程序带来的互联网信息垃圾给我们的信息获取带来很大的障碍。那些使用java开发CKEditor上传和文件浏览功能,千篇一律的使用的是Servlet的方式,传统的JSP下的模式,不管现在框架已经发展到何种境界,都是一样的照搬,抄袭,让人很无奈,其实这是一个很简单的功能,就是获取请求,上传文件,返回结果而已。
通常我们的项目会使用springMVC或者struts或者其他框架,来作为C的部分,说白了,也就是控制请求和执行流程。而如果我们在采用了spring或者struts 的系统中,还是去使用servlet配置urlmapping来拦截文件上传请求,那岂不是让人很别扭,那些copy文章的人,怎么就不去考虑考虑呢?
言归正传,ckeditor默认的图片插入,是不带“浏览图片”和“上传”标签的,所以需要手动配置一下,开启这个功能,而主要的猫腻,也就是这个配置的地方:
- // 图片上传配置
- config.filebrowserUploadUrl = 'upload.do?type=File';
- config.filebrowserImageUploadUrl = 'upload.do?type=Image';
- config.filebrowserFlashUploadUrl = 'upload.do?type=Flash';
- // 图片浏览配置
- config.filebrowserImageBrowseUrl = 'browerServer.do?type=image';
上面我们配置了路径,由于ckeditor并没有支持java的插件,所以这里也是需要我们自己开发的内容。上面的上传和浏览图片的请求,我们定义了自己的url,也就是上述.do结尾的部分,我的测试工程中使用的是springMVC,利用的是2.5以后的注解功能。
好了,这里配置了,那么作用是什么呢?打开ckeditor:
可以看到,上述多出了上传和浏览的功能,现在点击是没用的,因为我们还没开发具体的实现嘛
那么那些配置出了多出了这两个地方,还有什么作用呢?用firefox看看其上传部分的代码,可以很明显直到那个配置的url到底是什么用途了
其实就是一个上传form,那个action的url就是我们配置的而已,所以这里只要开发拦截这个请求并上传文件的功能就可以了嘛,多简单的事儿。
写代码前,看看我们的现状吧,我们可能会让这个图片上传到图片服务器去,但是呢,兜里尚未有足够的银子,而且这个图片暂时量不大,所以现阶段还是保存在应用的特定位置中,够无奈的吧,没办法,谁让咱么有特定的图片服务器呢,那么就下办法在本应用下作文章吧。我们采用一个upload/img的目录,来保存图片文件,以后要迁移到图片服务器也方便些。
但是问题来了,所有的图片都放到这个文件夹下,岂不是很庞大,而且一旦超过1000张,文件搜索速度是有点折磨的,那就咱想想办法吧,那就再建立一级目录,每一级下面最多放500张,如果当前文件夹下超过了500张,就重新建立一个文件夹,放入其中。这样目录就变成了三级的 upload/img/20100824 我们采用时间字符串来命名。
那就看看代码吧:
- package com.normandy.position.web;
- 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}/freemarker/upload/img/${'yyyyMMdd'}/
- * 每个文件夹下最多500个文件
- * </p>
- * @author mikko
- *
- */
- @Controller
- @RequestMapping("/upload.do")
- public class FileUploadController {
- protected final Logger logger = Logger
- .getLogger(FileUploadController.class);
- /** ~~~ 上传文件的保存路径 */
- private static final String FILE_UPLOAD_DIR = "/upload";
- /** ~~~ 上传文件的保存的下一级路径,标示存储类型 */
- private static final String FILE_UPLOAD_SUB_IMG_DIR = "/img";
- /** ~~~ 为了能让CKEDITOR加载到上传的图片,此处将位置限制在了freemarker下*/
- private static final String FOR_FREEMARKER_LOAD_DIR = "/freemarker";
- /** ~~~ 每个上传子目录保存的文件的最大数目 */
- 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 floder = buildFolder(request);
- if (null == floder) {
- 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);
- @SuppressWarnings("unchecked")
- 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 fileFix = StringUtils.substring(fileClientName,
- fileClientName.lastIndexOf(".") + 1);
- if (!StringUtils.equalsIgnoreCase(fileFix, "jpg")
- && !StringUtils.equalsIgnoreCase(fileFix, "jpeg")
- && !StringUtils.equalsIgnoreCase(fileFix, "bmp")
- && !StringUtils.equalsIgnoreCase(fileFix, "gif")
- && !StringUtils.equalsIgnoreCase(fileFix, "png")) {
- logger.error("上传文件的格式错误=" + fileFix);
- return;
- }
- if (logger.isInfoEnabled()) {
- logger.info("开始上传文件:" + file.getName());
- }
- // 为了客户端已经设置好了图片名称在服务器继续能够明确识别,这里不改名称
- File newfile = new File(floder, fileClientName);
- file.write(newfile);
- if (logger.isInfoEnabled()) {
- logger.info("上传文件结束,新名称:" + fileClientName + ".floder:"
- + newfile.getPath());
- }
- // 组装返回url,以便于ckeditor定位图片
- fileUrl = FOR_FREEMARKER_LOAD_DIR + FILE_UPLOAD_DIR + FILE_UPLOAD_SUB_IMG_DIR + File.separator+floder.getName()+ File.separator + newfile.getName();
- fileUrl = fileUrl.substring(1);// 去掉/freemarker的第一个/,否则ckeditor不识别
- fileUrl = StringUtils.replace(fileUrl, "//", "/");
- // 将上传的图片的url返回给ckeditor
- String callback = request.getParameter("CKEditorFuncNum");
- out.println("<mce:script type=/"text/javascript/"><!--
- ");
- out.println("window.parent.CKEDITOR.tools.callFunction("
- + callback + ",'" + fileUrl + "',''" + ")");
- out.println("
- // --></mce:script>");
- }
- out.flush();
- out.close();
- } catch (IOException e) {
- logger.error("上传文件发生异常!", e);
- } catch (FileUploadException e) {
- logger.error("上传文件发生异常!", e);
- } catch (Exception e) {
- logger.error("上传文件发生异常!", e);
- }
- return;
- }
- /**
- * 获取文件名称
- * @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_FREEMARKER_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改成你的项目名称
这样改完之后呢:
搞定了~