作为web的富文本编辑器FCK大家应该不会陌生吧,最近项目中遇到一个问题,那就是每次重新启动服务器时通过FCK上传的
文件全丢失,这可能是服务器每次重启都要重新通过war部署应用程序所致。
目前有3中方案可以解决上述问题:
1、写个后台进程专门维护FCK上传的文件。
2、位FCK专门部署一个服务器。
3、重新实现FCK的上传和下载功能(是文件可以上传到任意指定的路径,非应用程序的相对路径)。
不知网络大侠门还有没有别的解决方案,大家功能学习。
在上述方案中,第一种方案主要是在服务重启前后移动文件夹来实现的,当文件大时维护起来比较耗时,也可能在移动过程中
由于外界原因导致文件丢失。第二种方案要为FCK搭建个服务器,维护起来比较麻烦。想来想去还是用第三种方案比较合适。
经过阅读FCK的源码,终于找到了实现方法。现就具体的实现写下来,供网友参考,有不对的,不够合理的地方还望指正。
首先我们完成把文上传到指定的目录中。为了能满足需求我们需要实现FCK的Connector接口。在这个接口中声明了这么几个方法需
要我们去实现:
init(final ServletContext servletContext);
fileUpload(final ResourceType type, final String currentFolder, final String fileName,final InputStream inputStream)
createFolder(final ResourceType type, final String currentFolder, final String newFolder)
getFiles(ResourceType type, String currentFolder)
getFolders(final ResourceType type, final String currentFolder)
下面是Connector接口的实现类ContextConnector
package com.bzlccn.oa.common.fck; import java.io.File; import java.io.FileFilter; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletContext; import net.fckeditor.connector.Connector; import net.fckeditor.connector.exception.FolderAlreadyExistsException; import net.fckeditor.connector.exception.InvalidCurrentFolderException; import net.fckeditor.connector.exception.InvalidNewFolderNameException; import net.fckeditor.connector.exception.WriteException; import net.fckeditor.handlers.ResourceType; import net.fckeditor.tool.UtilsFile; import org.apache.commons.io.IOUtils; import org.apache.commons.io.filefilter.DirectoryFileFilter; import org.apache.commons.io.filefilter.FileFileFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ContextConnector implements Connector { private final Logger logger = LoggerFactory.getLogger(ContextConnector.class); protected ServletContext servletContext; //初始化 public void init(final ServletContext servletContext) throws Exception { this.servletContext = servletContext; String defaultAbsolutePath = UtilsPath.getRealUserFilesAbsolutePath(); if (defaultAbsolutePath == null) { logger.error("The context root cannot be resolved against the local filesystem"); logger.info("Your servlet container/application server does not expand deployed war files"); logger .debug("Use another Connector implementation (e.g. LocalConnector) and consult http://www.fckeditor.net/forums/viewtopic.php?f=6&t=11568"); throw new NullPointerException("The real context root cannot be resolved against the local filesystem"); } } //把文件上传到我们指定的路径中,路径的获取在UtilsPath中实现。 public String fileUpload(final ResourceType type, final String currentFolder, final String fileName, final InputStream inputStream) throws InvalidCurrentFolderException, WriteException { String absolutePath = UtilsPath.getRealUserFilesAbsolutePath(); File typeDir = getOrCreateResourceTypeDir(absolutePath, type); File currentDir = new File(typeDir, currentFolder); if (!currentDir.exists() || !currentDir.isDirectory()) throw new InvalidCurrentFolderException(); File newFile = new File(currentDir, fileName); File fileToSave = UtilsFile.getUniqueFile(newFile.getAbsoluteFile()); try { IOUtils.copyLarge(inputStream, new FileOutputStream(fileToSave)); } catch (IOException e) { throw new WriteException(); } return fileToSave.getName(); } //创建文件夹 public void createFolder(final ResourceType type, final String currentFolder, final String newFolder) throws InvalidCurrentFolderException, InvalidNewFolderNameException, FolderAlreadyExistsException { String absolutePath = UtilsPath.getRealUserFilesAbsolutePath(); File typeDir = getOrCreateResourceTypeDir(absolutePath, type); File currentDir = new File(typeDir, currentFolder); if (!currentDir.exists() || !currentDir.isDirectory()) throw new InvalidCurrentFolderException(); File newDir = new File(currentDir, newFolder); if (newDir.exists()) throw new FolderAlreadyExistsException(); if (!newDir.mkdir()) throw new InvalidNewFolderNameException(); } //得到所有的文件 public List<Map<String, Object>> getFiles(ResourceType type, String currentFolder) throws InvalidCurrentFolderException { String absolutePath = UtilsPath.getRealUserFilesAbsolutePath(); File typeDir = getOrCreateResourceTypeDir(absolutePath, type); File currentDir = new File(typeDir, currentFolder); if (!currentDir.exists() || !currentDir.isDirectory()) throw new InvalidCurrentFolderException(); // collect files List<Map<String, Object>> files; Map<String, Object> fileMap; File[] fileList = currentDir.listFiles((FileFilter) FileFileFilter.FILE); files = new ArrayList<Map<String, Object>>(fileList.length); for (File file : fileList) { fileMap = new HashMap<String, Object>(2); fileMap.put(Connector.KEY_NAME, file.getName()); fileMap.put(Connector.KEY_SIZE, file.length()); files.add(fileMap); } return files; } //得到所有文件夹 public List<String> getFolders(final ResourceType type, final String currentFolder) throws InvalidCurrentFolderException { String absolutePath = UtilsPath.getRealUserFilesAbsolutePath(); File typeDir = getOrCreateResourceTypeDir(absolutePath, type); File currentDir = new File(typeDir, currentFolder); if (!currentDir.exists() || !currentDir.isDirectory()) throw new InvalidCurrentFolderException(); String[] fileList = currentDir.list(DirectoryFileFilter.DIRECTORY); return Arrays.asList(fileList); } //根据上传文件的类型创建目录。 protected static File getOrCreateResourceTypeDir(final String baseDir, final ResourceType type) { File dir = new File(baseDir, type.getPath()); if (!dir.exists()) dir.mkdirs(); return dir; } }
上述ContextConnector类中用到的UtilsPath工具类的代码如下:
package com.bzlccn.oa.common.fck; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.bzlccn.oa.common.util.Constants; import com.bzlccn.oa.common.util.OSUtil; import com.bzlccn.oa.common.util.exception.SystemConfigrationException; public class UtilsPath { private static final Logger logger = LoggerFactory.getLogger(UtilsPath.class); public static String getRealUserFilesAbsolutePath() { String path = ""; try { //需要判断所用操作系统,根据不同的系统设置路径。 if (OSUtil.isUnixLikeSystem() == true) { path = Constants.getPropertyOf(Constants.FCK_LINUX_PATH); } else { path = Constants.getPropertyOf(Constants.FCK_WINDOWS_PATH); } } catch (SystemConfigrationException e) { if (OSUtil.isUnixLikeSystem() == true) { path = "//usr/local/OA//fck"; } else { path = "c://oa//fck"; } logger.info("配置文件出错,使用默认路径("+path+")保存fck上传文件"); } return path; } }
为了使上传的文件跨平台(目前只支持liunx和windwos)还需要当前用的系统。工具类如下:
package com.bzlccn.oa.common.util; public class OSUtil { private static final String WINDOWS = "WINDOWS"; /** * 判断当前操作系统的类型 * * @return false means window system ,true means unix like system */ public static boolean isUnixLikeSystem(){ String name = System.getProperty(Constants.OS_NAME_KEY) ; if(name != null){ if(name.toUpperCase().indexOf(WINDOWS) == -1){ //it means it's unix like system return true; } } return false; } }
为了实现上传路径可灵活配置还需要写个读取配置文件的工具类:
package com.bzlccn.oa.common.util; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.PropertiesConfiguration; import com.bzlccn.oa.common.util.exception.SystemConfigrationException; public class Constants { //fck文件上传 public static final String FCK_WINDOWS_PATH="fck.windows.path"; public static final String FCK_LINUX_PATH="fck.linux.path"; static{ try { config = new PropertiesConfiguration("fckeditor.properties"); } catch (ConfigurationException e) { e.printStackTrace(); } } public static String getPropertyOf(String key) throws SystemConfigrationException{ if(config == null) { throw new SystemConfigrationException("can not configure system"); } return config.getString(key); } public static void main(String args[]) throws ConfigurationException, SystemConfigrationException{ System.out.println(getPropertyOf("port")); } }
到此为把文件上传到指定路径的功能已经基本实现,只要让FCK调用我们的ContextConnector类就可以了,怎么去让FCK调用我们自己
实现的这个ContextConnector类呢,只要配置一下配置文件就可以了。只要在FCK的配置文件中添加如下配置文件即可:
connector.impl=com.bzlccn.oa.common.fck.ContextConnector
下面就是把我们上传的文件怎么显示的问题。实现是这样的,首先我们要自己写个下载文件的servlet,具体代码如下:
package com.bzlccn.oa.common.fck; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.taglibs.standard.extra.spath.Path; public class FCKDownloadServlet extends HttpServlet { private static final long serialVersionUID = -5742008970929377161L; @Override protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } @Override protected void doPost(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { String path=request.getParameter("path"); request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); setResponseContentType(response,path); response.setHeader("Cache-Control", "no-cache"); if (path!=null) { path=UtilsPath.getRealUserFilesAbsolutePath()+path; }else{ path=this.getServletContext().getRealPath("/images/sign/no.JPG"); } InputStream is = new FileInputStream(path); ServletOutputStream servletOutputStream=response.getOutputStream(); // 循环取出流中的数据 byte[] b = new byte[100]; int len; try { while ((len = is.read(b)) > 0) { servletOutputStream.write(b, 0, len); } } catch (IOException e) { e.printStackTrace(); }finally{ if (servletOutputStream!=null) { servletOutputStream.flush(); servletOutputStream.close(); } if(is != null) is.close(); } } /** * 根据文件扩展名设置contenttype * @param eresponse * @param path */ private void setResponseContentType(HttpServletResponse response,String path){ String contentType="image/jpg;charset=UTF-8"; if (path!=null) { String exName=path.substring(path.lastIndexOf(".")+1); if ("bmp|gif|jpeg|jpg|png".indexOf(exName.toLowerCase())>=0) { contentType="image/"+exName+";charset=UTF-8"; } if ("swf|fla".indexOf(exName.toLowerCase())>=0) { contentType="application/x-shockwave-flash;charset=UTF-8"; } } response.setContentType(contentType); } }
为了让FCK显示图片的时候调用我们的FCKDownloadServlet还需去重载ServerRootPathBuilder类中的getUserFilesPath方法,实现如下
package com.bzlccn.oa.common.fck; import javax.servlet.http.HttpServletRequest; import net.fckeditor.requestcycle.impl.ServerRootPathBuilder; public class ContextPathBuilder extends ServerRootPathBuilder { @Override public String getUserFilesPath(final HttpServletRequest request) { return request.getContextPath().concat("/fck/download.servlet?path=");//"/fck/download.servlet?"要和web.xml中配置的FCKDownloadServlet路径一致。 } }
上述类中getUserFilesPath方法主要就是获取显示文件的路径。怎么让FCK调用我们的getUserFilesPath方法的,通要的还是要
向FCK配置文件中写入:
connector.userPathBuilderImpl = com.bzlccn.oa.common.fck.ContextPathBuilder
哈哈,这样就可以了,等等,还没有完呢,还得需要配置FCKDownloadServlet呢,在web.xml中需要输入:
<servlet>
<servlet-name>ConnectorServlet</servlet-name>
<servlet-class>net.fckeditor.connector.ConnectorServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>FCKDownloadServlet</servlet-name>
<servlet-class>com.bzlccn.oa.common.fck.FCKDownloadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ConnectorServlet</servlet-name>
<!-- Do not wrap this line otherwise Glassfish will fail to load this file -->
<url-pattern>/fckeditor/editor/filemanager/connectors/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>FCKDownloadServlet</servlet-name>
<url-pattern>/fck/download.servlet</url-pattern>
</servlet-mapping>
下面再把FCK的整体配置文件贴出来:
connector.userActionImpl=net.fckeditor.requestcycle.impl.EnabledUserAction
connector.impl=com.bzlccn.oa.common.fck.ContextConnector
connector.userPathBuilderImpl = com.bzlccn.oa.common.fck.ContextPathBuilder
fck.windows.path=c:/oa/fck
fck.linux.path=/home/oa/fck
还需要说明一点就是我们用的FCK的版本是2.6.5
写的有点匆忙,难免有不妥之处望指正,多拍砖。
实现效果如图: