为什么需要二次开发服务端
把ueditor按官网给的方法集成到FDP项目,ueditor的功能都已可正常使用了。
ueditor是富文本编辑器,功能重点在js客户端这边,目前客户端功能够用够强,无需二次开发。
ueditor的服务端不是他的重点,提供的服务代码更多是演示性的代码,不完全能满足项目的要求,所以要二次开发服务端。
服务端二次开发的目标
- 文件存储:与FDP的“文件存储服务”对接,可把文件存储到“文件存储服务”中。 “文件存储服务”的具体实现有多种,如阿里云OSS、本地文件夹、NFS、FTP、FastDFS。
- 上传图片:有权限认证,上传时带token才可上传,禁止匿名上传文件。
- 访问控制:普通图片可匿名访问。存储在“安全区”的身份证图片,带token才可访问。
- 缩略图:要与FDP的实时生成缩略图功能相结合。
- 分布式存储:文件分布式存储,由“文件存储服务”解决分布式存储。
- 分布式部署:在大型系统架构中,由多个子系统组成,upload子系统专负责上传下载,upload系统要支持分布式部署。
ueditor默认存储方式是,按config.json的配置,把文件存储在服务器的磁盘上,目录结构类似于:/ueditor/jsp/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}。这块是二次开发改造的重点。
停用的功能
ueditor服务端支持以下功能。
- 上传图片
- 涂鸦图片上传
- 抓取远程图片
- 列出指定目录下的图片
- 上传视频
本次二开只覆盖“上传图片”功能,其它功能都停用。如:涂鸦图片、抓取远程图片、列出指定目录下的图片,目前不需要这些功能,在客户端都不调用此功能。如果以后需要,请接着开发。
问:为什么不自己写一个简单、干净的服务端,而要二次开发?
答:自已写新的服务端工作量太多,ueditor的客户端与服务端通信时有内部约定好的数据传输格式,想都承接下来是有难度的。
下面记录二次开发的重点内容。
服务端的源代码
在 UEditor的初次安装 文档中,以准备了服务羰的源代码,下面将修改其中几个文件,实现二次开发。
在页面上使用UEditor编辑器
具体使用方法,请参看ueditor的官方文档与演示,需要js的知识,内容较多。以下只是一个简单的演示。
<div class="controls"> <!-- 加载编辑器的容器 --> <script type="text/plain" id="container" name="introduction">${detail.introduction}</script> <!-- 配置文件 --> <script type="text/javascript" src="${ctxStatic}/baiduUEditor1.4.3.2/ueditor.config.js"></script> <!-- 编辑器源码文件 --> <script type="text/javascript" src="${ctxStatic}/baiduUEditor1.4.3.2/ueditor.all.min.js"></script> <!-- 实例化编辑器 --> <script type="text/javascript"> var ue = UE.getEditor('container'); </script> </div>
UEditor客户端-配置服务端的接口路径
编辑/shop-web-static/src/main/webapp/static/baiduUEditor1.4.3.2/ueditor.config.js
UEditor服务端-编辑ueditor_upload_config.json文件
使用UEditor原生的config.json文件,改名为ueditor_upload_config.json文件,放入项目的resources目录,项目的配置文件都应放在这里。
目的:访问图片时,走FDP的FileStorageDownloadServlet,可实现访问控制与实现生成缩略图。
第11行 "imageUrlPrefix": "", /* 图片访问路径前缀 */ 替换为 "imageUrlPrefix": "####", /* 图片访问路径前缀,####会被Global.getConfig("filestorage.dir")替换 */
开发UEditorController
UEditorController是ueditor服务端的入口,客户端会来调用。
com.sicheng.upload.fileupload.web.UEditorController
- 统一上传入口,UEditor客户端向服务端的/upload/ueditorServer.htm接口上传文件。
- 替代controller.jsp,只留一个入口。
- 实现token验证
@Controller public class UEditorController extends BaseController { /** * 处理上传的入口方法 */ @RequestMapping(value = "/upload/ueditorServer") public void upload(HttpServletRequest request, HttpServletResponse response) throws IOException { //验证accessKey,合格才可上传 String accessKey=request.getParameter("accessKey"); if(StringUtils.isBlank(accessKey) || (!AccessKey.verification(accessKey)) ){ response.setCharacterEncoding( "utf-8" ); response.setHeader("Content-Type" , "text/html"); PrintWriter out = response.getWriter(); out.write( new BaseState(false, AppInfo.ACCESSKEY_ERROR).toJSONString()); return ; } //以下代码是ueditor自带的,原样保留在这里,未做改动 response.setCharacterEncoding( "utf-8" ); response.setHeader("Content-Type" , "text/html"); String rootPath = request.getContextPath(); PrintWriter out = response.getWriter(); out.write( new ActionEnter( R.getRequest(), rootPath ).exec() ); } }
停用controller.jsp
删除controller.jsp,入口不再走controller.jsp,改走UEditorController,upload系统的所有Controller所有一块,方便管理。
开发MyCommonsMultipartResolver
目标:让ueditor的服务端与spring和睦相处
自定义MyCommonsMultipartResolver类: public class MyCommonsMultipartResolver extends CommonsMultipartResolver
重写父类的isMultipart方法
目标:对指定的url,不使用spring的CommonsMultipartResolver来处理上传,放过,由后面的业务程序自行处理。
理由:因本项目中使用了ueditor,接收上传的服务端,是一套独立,不能让spring来参与处理,否则会有冲突的。
原理:org.springframework.web.multipart.commons.CommonsMultipartResolver的源代码,其中有个public boolean isMultipart(HttpServletRequest request)方法,此方法控制着Spring是否对用户的Request进行文件上传处理,于是自定义一个我们自己的CommonsMultipartResolver,继承自org.springframework.web.multipart.commons.CommonsMultipartResolver,并重写其中的public boolean isMultipart(HttpServletRequest request),在方法中对当前的request进行判断,如果是ueditor上传专用的url,则返回false,告知Spring不需要再对当前的request进行文件上传处理;如果不是,则直接调用父类的isMultipart方法。
配置MyCommonsMultipartResolver
修改/shop-web-upload/src/main/resources/spring-mvc-upload.xml文件,加入以下内容,让MyCommonsMultipartResolver工作起来。
<bean id="multipartResolver" class="com.sicheng.upload.component.MyCommonsMultipartResolver"> <!-- <property name="maxUploadSize" value="1048576000" /> --> <property name="maxUploadSize" value="${web.maxUploadSize}" /> <property name="excludeUrls"> <list> <value>/upload/ueditorServer.htm</value><!-- 为了百度富文本编辑器ueditor而放过此url --> </list> </property> </bean>
修改ConfigManager.java
com.baidu.ueditor.ConfigManager
指明配置文件名
private static final String configFileName = "ueditor_upload_config.json" ;
添加 insertPathVar方法
/** * 替换变量 赵磊 * 使用Global.getConfig("filestorage.dir")替换#### * * @param configContent json配置文件 * @param contextPath2 应用的根路径 contextPath * @return */ private String insertPathVar(String configContent, String contextPath2) { String path=""; if(contextPath!=null && contextPath.length()>0){ path=contextPath2 + Global.getConfig("filestorage.dir"); }else{ path=Global.getConfig("filestorage.dir"); } configContent=configContent.replaceAll("####", path); return configContent; }
添加getConfigPath方法
/** * 这方法是拼全config.json的绝对路径。 赵磊 * UEditor官方给的方案是:parentPath是controller.jsp 的目录路径,所以限制config.json 必须和controller.jsp在同一个目录下。 * 赵磊的替代方案:用spring controller代替controller.jsp,在类路径下查找config.json。 */ private String getConfigPath () { //获取类的根路径classes/(重要) URL url = ConfigManager.class.getResource("/"); String rootClassPath=url.getPath();//如果路径有空格会使用 %20替代 try { // %20 转换为 空格(方案二) rootClassPath = java.net.URLDecoder.decode(rootClassPath, "UTF-8"); } catch (UnsupportedEncodingException e1) { e1.printStackTrace(); } System.out.println(rootClassPath); String path=rootClassPath + File.separator + ConfigManager.configFileName; return path; }
替换
####
filestorage.dir是文件存储的目录(当使用本地存储方案时),可通过Global.getConfig("filestorage.dir")获取。配置在fdp.properties文件。
由于filestorage.dir可被修改,想只修改一次,所有这里有获取并替换。
修改AppInfo.java
com.baidu.ueditor.define.AppInfo
加入
public static final int ACCESSKEY_ERROR = 403;
put( AppInfo. ACCESSKEY_ERROR , "accessKey\u9a8c\u8bc1\u5931\u8d25" );
修改 BinaryUploader.java
注释了80行
修改StorageManager.java
com.baidu.ueditor.upload.StorageManager
StorageManager类负责文件存储工作,在这里与FDP的“文件存储服务”对接,实现把文件写入“文件存储服务”的目的。
本类的3个核心方法,都做了修改,是修改量最大的一个文件。具体修改因较多,请看源码吧。