使用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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM