- 浏览: 6577 次
- 性别:
- 来自: 哈尔滨
最新评论
这几天我做的一个项目,要用到富文本编辑器,同时还要能在文本中插入图片。国内做的最成熟的富文本编辑器是百度的 UEditor,然而 UEditor 上传文件的功能是封装好的,开发者只要在配置文件内写一个 WebRoot 下的目录就可以自动上传,而 SAE 的服务 WebRoot 下的文件读写权限并没有开放,SAE 是通过另一个叫做 SAE Storage 的服务来实现保存文件的,所以 UEditor 自带的上传图片功能在 SAE 上完全无法使用。
我被这个矛盾坑的要死要活,又一时半会儿找不到别的合适的富文本编辑器,所以只好把 UEditor 内部封装的代码改了一些些来实现这个功能,以下是正文。
Ueditor JAVA版上传图片工作原理
UEditor 与后台的全部交互,都是通过 ueditor/jsp下的一个叫做 controller.jsp 来分派的。
每一次ueditor 与后台交互的请求,都会有一个参数 action来标记这次请求究竟是什么动作。其中上传图片的请求涉及到两个 action,分别是“config”和“uploadimage”,“config”是读取 ueditor 的配置文件,就是同一个目录下的 config.json 文件;“uploadimage”就是处理上传图片的 action。
controller.jsp 只有两句话,见图
其中的 ActionEnter 实例对象会处理具体的内部逻辑,上传图片成功后,这个对象的 exec 方法会返回一个如下格式的json 串.
ttile 是用在文本中,给 img 标签设定 title 属性用的,original 是指上传图片文件的原始文件名,url 是读取图片的 url 路径。但是正如大家看到的,这个 url 参数返回的并不是完整的 url 路径,完整的路径需要配合配置文件config.json里面的参数imageUrlPrefix拼接出来。
所以上传之后的图片完整路径就是:http://yourappname-youdomainname.stor.sinaapp.com/14110403960651.jpg
修改 controller.jsp 文件
这里我们需要做的事情就是在controller.jsp 文件里,把上传图片的 action 拦截,写成自己的逻辑,这里我建立了一个类叫做 UploadToStorage 用来处理上传图片。
当我在 UploadToStorage 这个类的 upload 方法里面通过 InputStream 读取 request 里面的文件内容时,惊人的发现居然读不出任何内容!我就头大的想撞墙死,明明已经获取了参数,可是 request 的内容却读不出来。后来翻了很多文章,才知道原来 request 的 inputStream 是只能读取一次的。一旦调用过 getParameter 这样的跟参数有关的方法,inputStream 就已经指向末尾而且不能被 reset,所以一定要在读取参数之前先把 inputStream 里面的内容读取来缓存好。(这部分请参考文章 http://www.tuicool.com/articles/rEreEb)
所以我把 controller.jsp 文件改成了这样
UploadToStorage 类的上传图片方法
废话不多说,上代码。
其中有大量的读取 request 内容的语句,这部分涉及到了 http 协议的相关内容,具体请参考这里 http://blog.csdn.net/yethyeth/article/details/1765925(在这里说一句抱歉,我无耻的把文章里的代码粘下来了,希望原作者不要介意)
当我把代码改到这个地步的时候,我在本地的 sae 环境上调试通过了。上传的图片文件已经能够顺利的保存在 sae storage 里面,然后我就欢欣鼓舞的把代码打包发到了 sae 服务器上,可是在线上环境上传图片还是不成功,firebug 控制台返回了一个错误语句。经过我的大量实验(绝对大量,量大的我想吐),分析得出应该是上传图片的同时还会发一个请求去获取 config.json 文件的内容,但是不知道为什么这个文件不能被读取出来(我到现在也不知道为什么,如果你知道,请告诉我)。所以我果断的把“config”这个 action 也拦截自己处理了。
加载 config.json 文件
首先是修改 controller.jsp 文件
然后是 UploadToStorage 类里面的方法:
顺利的把配置文件读取出来之后,果然豪不意外的可以上传图片了。
后记
感谢百度开源团队 UEditor,这个世界因为伟大的开源作者而变得越发美丽,希望这个世界能够对得起你们的付出。
感谢两位技术博客作者,再次列出他们的博客文章
关于 request 的 InputStream 读取次数问题:http://www.tuicool.com/articles/rEreEb
关于通过 request 的内容获取文件:http://blog.csdn.net/yethyeth/article/details/1765925
如果你照着以上内容写了可是依然没有能够实现上传图片,可以通过我的微博联系我 http://weibo.com/treagzhao
我被这个矛盾坑的要死要活,又一时半会儿找不到别的合适的富文本编辑器,所以只好把 UEditor 内部封装的代码改了一些些来实现这个功能,以下是正文。
Ueditor JAVA版上传图片工作原理
UEditor 与后台的全部交互,都是通过 ueditor/jsp下的一个叫做 controller.jsp 来分派的。
每一次ueditor 与后台交互的请求,都会有一个参数 action来标记这次请求究竟是什么动作。其中上传图片的请求涉及到两个 action,分别是“config”和“uploadimage”,“config”是读取 ueditor 的配置文件,就是同一个目录下的 config.json 文件;“uploadimage”就是处理上传图片的 action。
controller.jsp 只有两句话,见图
String rootPath = application.getRealPath("/"); out.println(new ActionEnter(request,rootPath).exec());
其中的 ActionEnter 实例对象会处理具体的内部逻辑,上传图片成功后,这个对象的 exec 方法会返回一个如下格式的json 串.
{ "state":"SUCCESS", "title":"14110403960651.jpg", "url":"14110403960651.jpg", "original":"1.jpg", "size":11098, "type":".jpg" }
ttile 是用在文本中,给 img 标签设定 title 属性用的,original 是指上传图片文件的原始文件名,url 是读取图片的 url 路径。但是正如大家看到的,这个 url 参数返回的并不是完整的 url 路径,完整的路径需要配合配置文件config.json里面的参数imageUrlPrefix拼接出来。
{ //..... //把这个属性的值改成自己sae storage 的路径 "imageUrlPrefix": "http://yourappname-youdomainname.stor.sinaapp.com/" //...... }
所以上传之后的图片完整路径就是:http://yourappname-youdomainname.stor.sinaapp.com/14110403960651.jpg
修改 controller.jsp 文件
这里我们需要做的事情就是在controller.jsp 文件里,把上传图片的 action 拦截,写成自己的逻辑,这里我建立了一个类叫做 UploadToStorage 用来处理上传图片。
String rootPath = application.getRealPath("/"); ActionEnter enter = new ActionEnter(request,rootPath); String result = enter.exec(); String action = request.getParameter("action"); if("uploadimage".equals(action)){ out.println(UploadToStorage.upload(request, response)); } else out.println(result);
当我在 UploadToStorage 这个类的 upload 方法里面通过 InputStream 读取 request 里面的文件内容时,惊人的发现居然读不出任何内容!我就头大的想撞墙死,明明已经获取了参数,可是 request 的内容却读不出来。后来翻了很多文章,才知道原来 request 的 inputStream 是只能读取一次的。一旦调用过 getParameter 这样的跟参数有关的方法,inputStream 就已经指向末尾而且不能被 reset,所以一定要在读取参数之前先把 inputStream 里面的内容读取来缓存好。(这部分请参考文章 http://www.tuicool.com/articles/rEreEb)
所以我把 controller.jsp 文件改成了这样
InputStream in = request.getInputStream(); ByteArrayOutputStream baOut = new ByteArrayOutputStream(); int n; while((n=in.read())!=-1){ //把 request 里面的内容全部读入字节缓存 baOut.write(n); } baOut.close(); //生成 request 内容的字节数组 byte[] b = baOut.toByteArray(); request.setCharacterEncoding("utf-8"); response.setHeader("Content-Type", "text/html"); String rootPath = application.getRealPath("/"); ActionEnter enter = new ActionEnter(request,rootPath); String result = enter.exec(); String action = request.getParameter("action"); if("uploadimage".equals(action)){ //这个地方把内容的字节数组传递进去,让方法处理文件 out.println(UploadToStorage.upload(request,b)); } else out.println(result);
UploadToStorage 类的上传图片方法
废话不多说,上代码。
package com.tastinglib.util; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.sf.json.JSONObject; import org.apache.commons.fileupload.DiskFileUpload; import org.apache.commons.fileupload.FileItem; import com.sina.sae.storage.SaeStorage; import com.sun.corba.se.impl.ior.WireObjectKeyTemplate; public class UploadToStorage { private static final int NONE = 0x10001; private static final int DATA_HEAD = 0X10002; private static final int FIELD_DATA = 0x10003; private static final int FILE_DATA = 0x10004; private static final String DOMAIN = "yourdomain"; /** * @category 上传图片方法 * @param request * http 请求 * @param requestContent * 已经读取出来的 request 请求的内容字节数组 * @return 符合 UEditor 返回格式的 json 串 */ public static String upload(HttpServletRequest request, byte[] requestContent) { String result = ""; try { request.setCharacterEncoding("utf-8"); // 根据自己的 app 生成 storage 实例 SaeStorage storage = new SaeStorage("youraccesskey", "youraccesssecret", "youraappname"); // 保存文件 result = getFile(requestContent, request, storage).toString(); } catch (UnsupportedEncodingException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return result; } /** * @category 保存文件并返回标准格式 json 串 * @param contentBytes * request 内容字节数组 * @param request * HttpServlet * @param storage * SAEStorage * @return json 串 * @throws IOException */ private static JSONObject getFile(byte[] contentBytes, HttpServletRequest request, SaeStorage storage) throws IOException { // 用来解析内容 int status = NONE; // 目前我自己的业务逻辑,一个请求只需要处理一个文件,所以我写成了只要上传一个文件就返回 boolean hasFile = false; // 最终返回的 json JSONObject obj = new JSONObject(); String headers = request.getHeader("Content-Type"); headers = new String(headers.getBytes(), "utf-8"); // 从这一下都是读取 request 内容的语句 int pos = headers.indexOf("boundary="); if (pos >= 0) { pos += "boundary=".length(); } String lastBoundary = headers.substring(pos) + "--"; String boundary = "--" + headers.substring(pos); String reqStr = new String(contentBytes); String line = null; StringReader strReader = new StringReader(reqStr); BufferedReader reader = new BufferedReader(strReader); String fileName = ""; while ((line = reader.readLine()) != null && !hasFile) { if (line.equalsIgnoreCase(lastBoundary)) break; switch (status) { case NONE: if (line.startsWith(boundary)) { // 如果读到分界符,则表示下一行一个表头 status = DATA_HEAD;// 状态设为表示表头信息 } break; case DATA_HEAD: pos = line.indexOf("filename="); if (pos > 0) { String temp = line; pos = line.indexOf("filename=") + "filename=".length() + 1; line = line.substring(pos, line.length() - 1); pos = line.lastIndexOf("//");// 转义字符 fileName = line.substring(pos + 1); pos = byteIndexOf(contentBytes, temp, 0);// 定位行 // 定位下一行,2表示一个回车和一个换行占2个字节 contentBytes = subBytes(contentBytes, pos + temp.getBytes().length + 2, contentBytes.length); // 再读一行信息,是这一部分数据的Content-type line = reader.readLine(); // 设置文件输入流,准备写文件 /** * 字节数组再往下一行,4表示两个回车换行占4个字节。本行(指Content-type行)的 * 回车换行2个字节,Content-type的下一行是回车换行表示的空行占2个字节 得到文件数据的起始位置 */ contentBytes = subBytes(contentBytes, line.getBytes().length + 4, contentBytes.length); // 定位文件数据的结尾 pos = byteIndexOf(contentBytes, boundary, 0); // 获取文件数据,pos-2是因为在文件数据和boundary之间有一回车换行表示的空行 contentBytes = subBytes(contentBytes, 0, pos - 2); // 将文件数据存盘 // fileOut.write(contentBytes); String storageFileName = System.currentTimeMillis() + fileName; storage.write(DOMAIN, storageFileName, contentBytes); obj.put("state", "SUCCESS"); obj.put("title", storageFileName); obj.put("url", storageFileName); obj.put("original", fileName); obj.put("size", contentBytes.length); int typePos = storageFileName.lastIndexOf("."); String fileType = storageFileName.substring(typePos); obj.put("type", fileType); // fileOut.close(); // 文件长度存入fileLength status = FILE_DATA; hasFile = true; } break; case FILE_DATA: while ((!line.startsWith(boundary)) && (!line.startsWith(lastBoundary))) line = reader.readLine(); if (line.startsWith(boundary)) status = DATA_HEAD; break; } } return obj; } /** * @param b * 要搜索的字节数组 * @param s * 要查找的字符串 * @param start * 搜索的起始位置 * @return 如果找到返回s的第一个字节在字节数组中的下标,否则返回-1 */ private static int byteIndexOf(byte[] b, String s, int start) { return byteIndexOf(b, s.getBytes(), start); } /** * @param b * 要搜索的字节数组 * @param s * 要查找的字节数组 * @param start * 搜索的起始位置 * @return 如果找到返回s的第一个字节在字节数组中的下标,否则返回-1 */ private static int byteIndexOf(byte[] b, byte[] s, int start) { int i; if (s.length == 0) return 0; int max = b.length - s.length; if (max < 0) return -1; else if (start > max) return -1; else if (start < 0) start = 0; search: for (i = start; i < max; i++) { if (b[i] == s[0]) { // 找到了s的第一个元素后比较剩余部分是否相等 int k = 1; while (k < s.length) { if (b[k + i] != s[k]) continue search; k++; } return i; } } return -1; } /** * 在一个字节数组中提取一个字节数组 */ private static byte[] subBytes(byte[] b, int from, int end) { byte[] result = new byte[end - from]; System.arraycopy(b, from, result, 0, end - from); return result; } /** * 在一个字节数组中提取一个字符串 */ private static String subBytesToString(byte[] b, int from, int end) { return new String(subBytes(b, from, end)); } public static byte[] intToBytes2(int num) { byte[] result = new byte[4]; result[0] = (byte) (num >>> 24);// 取最高8位放到0下标 result[1] = (byte) (num >>> 16);// 取次高8为放到1下标 result[2] = (byte) (num >>> 8); // 取次低8位放到2下标 result[3] = (byte) (num); // 取最低8位放到3下标 return result; } }
其中有大量的读取 request 内容的语句,这部分涉及到了 http 协议的相关内容,具体请参考这里 http://blog.csdn.net/yethyeth/article/details/1765925(在这里说一句抱歉,我无耻的把文章里的代码粘下来了,希望原作者不要介意)
当我把代码改到这个地步的时候,我在本地的 sae 环境上调试通过了。上传的图片文件已经能够顺利的保存在 sae storage 里面,然后我就欢欣鼓舞的把代码打包发到了 sae 服务器上,可是在线上环境上传图片还是不成功,firebug 控制台返回了一个错误语句。经过我的大量实验(绝对大量,量大的我想吐),分析得出应该是上传图片的同时还会发一个请求去获取 config.json 文件的内容,但是不知道为什么这个文件不能被读取出来(我到现在也不知道为什么,如果你知道,请告诉我)。所以我果断的把“config”这个 action 也拦截自己处理了。
加载 config.json 文件
首先是修改 controller.jsp 文件
String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()+ path + "/"; if("uploadimage".equals(action)){ out.println(UploadToStorage.upload(request,b)); } else if("config".equals(action)){ //把加载配置文件的这个分支也拦截自己处理 response.getWriter().println(UploadToStorage.readerUeditorConfig(basePath)); } else out.println(result1);
然后是 UploadToStorage 类里面的方法:
/** * @category 读取 config.json 文件 * @param basePath 根目录路径 * @return 文件内容字符串 */ public static String readerUeditorConfig(String basePath) { String result = ""; try { URL url = new URL(basePath + "ueditor/jsp/config.json"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setDoInput(true); conn.setDoInput(true); conn.setRequestProperty("Content-Type", "text/html"); conn.connect(); InputStream in = conn.getInputStream(); ByteArrayOutputStream baOut = new ByteArrayOutputStream(); int n; while ((n = in.read()) != -1) { baOut.write(n); } result = new String(baOut.toByteArray(), "utf-8"); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return result; }
顺利的把配置文件读取出来之后,果然豪不意外的可以上传图片了。
后记
感谢百度开源团队 UEditor,这个世界因为伟大的开源作者而变得越发美丽,希望这个世界能够对得起你们的付出。
感谢两位技术博客作者,再次列出他们的博客文章
关于 request 的 InputStream 读取次数问题:http://www.tuicool.com/articles/rEreEb
关于通过 request 的内容获取文件:http://blog.csdn.net/yethyeth/article/details/1765925
如果你照着以上内容写了可是依然没有能够实现上传图片,可以通过我的微博联系我 http://weibo.com/treagzhao
相关推荐
【上传图片加上域名前缀.txt】可能涉及到如何设置UEditor,使得上传的图片自动添加网站的域名前缀,这样图片链接才能在互联网上正确显示,而不仅仅是本地服务器上。 【一流素材网.url】可能是提供设计素材资源的...
百度控件ueditor图片上传到远程服务器解决方案 一、搭建ueditor环境 二、修改源码 三、重新编译源码 四、使用说明
【百度UEditor编辑器JSP版本详解】 UEditor是由百度公司旗下的Web前端研发部精心打造的一款强大而易用的富文本Web编辑器。其主要目标是提供一个轻量级、高性能且用户友好的编辑工具,使得用户在网页上能够轻松进行...
10. `ueditor1_4_3_2-utf8-php.zip`:这个文件可能是早期版本的UEditor,可能包含了PHP服务器端处理文件,用于处理UEditor上传的图片、文件等。 在PHP环境中集成UEditor,主要涉及以下步骤: 1. 将解压后的UEditor...
通过以上步骤,你就成功地将Spring Boot、Thymeleaf和百度Ueditor整合在一起,实现了文件上传功能。这个Demo不仅解决了后端配置问题,还为用户提供了一个功能完备的富文本编辑体验。在实际开发中,可以根据需求调整...
在Java环境中,这个JAR文件可能被用来集成UEditor到Web应用中,提供服务器端对UEditor上传图片等操作的支持。 **备注.txt** `备注.txt`文件通常用于存放开发者或维护者对项目的一些说明或注意事项。在这个上下文中...
百度富文本编辑器UEditor是一款由百度公司开发的开源富文本编辑器,它在1.4.3版本中提供了丰富的功能和优秀的用户体验,适用于网页内容编辑、论坛发帖、博客写作等多种场景。UEditor以其易用性、稳定性和可定制性...
百度ueditor编辑器百度ueditor编辑器百度ueditor编辑器百度ueditor编辑器百度ueditor编辑器百度ueditor编辑器百度ueditor编辑器百度ueditor编辑器百度ueditor编辑器百度ueditor编辑器
后端则主要处理数据传输和安全性问题,如图片、文件的上传和管理。 在配置方面,Ueditor提供了多种配置选项,允许开发者根据实际需求进行个性化设置。例如,你可以自定义编辑器的宽度和高度,控制工具栏按钮的显示...
安装时,只需按照官方提供的文档步骤,将相关文件上传至服务器并配置好相关参数,即可在网页中调用UEditor。 总的来说,百度UEditor是一款功能全面、性能稳定的在线编辑器,无论是对于个人博客还是企业级应用,都能...
- **兼容性**:确保所使用的UEditor版本与项目所依赖的浏览器兼容,特别是对于旧版本浏览器的兼容问题要提前考虑。 - **安全性**:在使用UEditor时,要注意防止XSS攻击,对用户输入进行适当的安全过滤和转义。 - ...
如果通过百度Ueditor直接将图片上传到服务器上,当你重新发布项目到服务器上,容易造成图片的丢失!为了防止该事件的发生,尽量将图片上传到磁盘上或者独立存图片的服务器上!通过修改百度Ueditor源码实现
3. **服务器端处理**:由于UEditor的文件上传等功能需要后端支持,因此开发者需要编写Java处理程序来接收并处理UEditor上传的文件,包括验证、存储和返回结果。 4. **安全考虑**:在使用UEditor时,需要防范XSS(跨...
总的来说,实现百度ueditor编辑器的Word导入功能涉及到对OOXML的理解,以及在ASP.NET环境中处理文件、解析XML、转换样式和处理图片等多个技术环节。通过这一功能,开发者可以提升网站内容编辑的用户体验,同时也需要...
三是处理好文件上传的问题,通常需要设置服务器端的接收接口,处理上传的图片、视频等资源。 关于图片上传,UEditor内置了图片管理功能,用户可以直接在编辑器内上传图片,同时也支持批量上传和图片裁剪。在实际...
最后,需要在jsp页面中添加 UEditor的容器和上传图片的按钮,并将上传图片的逻辑写在js文件中。 七、UEditor的优点 UEditor的优点是它具有强大的编辑功能和灵活的配置项,可以满足不同的需求。同时,UEditor也提供...
- **定制配置**:在UEditor的配置文件(如`ueditor.config.js`)中设置,添加对135编辑器样式的引用,确保在编辑器启动时加载这些样式。 - **实现接口交互**:可能需要编写JavaScript代码来实现UEditor与135编辑...
- **editor_config.js**:这是ueditor的配置文件,开发者可以根据项目需求在此文件中设置编辑器的各种参数,如默认字体、图片上传路径、工具栏选项等。 - **CHANGELOG.TXT**:版本变更日志,记录了ueditor每次更新的...
当在Tomcat这样的Java应用服务器上部署UEditor时,正确设置虚拟路径映射是确保其正常运行的关键步骤。 首先,我们需要了解什么是虚拟路径映射。在Web服务器或应用服务器中,虚拟路径是指不直接对应物理文件系统的...