之前写过一版本的ueditor的使用方式,感觉后来ueditor升级很快,转眼间又升级了,今天有一个人问这块相关的问题,正好又熟悉下。
从代码来看,rootPath其实就是项目的根路径,构建了ActionEnter,并调用了exec函数。
首先最基本的用法我就不讲了,只讲文件上传的这块。
首先,文件上传这块和之前的变化很大,先慢慢的讲讲用法。
1. java版本的在jsp目录的文件结构如下:
![](https://i-blog.csdnimg.cn/blog_migrate/014aa82bfff75678841f1af321b79f17.png)
从这地方可以看出来,有一个controller.jsp, 一个config.json,一堆jar文件, 这个和之前版本是不一致的。
2. maven工程的jar包的引用
如果没有使用jar包,很容易,直接copy文件就可以,但是maven的方式,这个jar又在网上没有,索幸maven提供了system方式的依赖方式:
< dependency> < groupId> com.baidu.ueditor </groupId > < artifactId> ueditor </artifactId > < version> 1.1.1 </version > < scope> system </scope > < systemPath> ${basedir}/ src/main/webapp /WEB-INF/lib/ ueditor-1.1.1.jar </systemPath > </ dependency>maven的jar包的放置位置如下:
![](https://i-blog.csdnimg.cn/blog_migrate/15c22d60ca0bb7d15697fa82f1b1ef94.png)
其他的jar我就不多讲了,都很容易找。
3. controller.jsp文件阅读
<%@ page language="java" contentType="text/html; charset=UTF-8"
import="com.baidu.ueditor.ActionEnter"
pageEncoding="UTF-8"%>
<%@ page trimDirectiveWhitespaces="true" %>
<%
request.setCharacterEncoding( "utf-8" );
response.setHeader("Content-Type" , "text/html");
String rootPath = application.getRealPath( "/" );
out.write( new ActionEnter( request, rootPath ).exec() );
%>
从代码来看,rootPath其实就是项目的根路径,构建了ActionEnter,并调用了exec函数。
我们来看下ActionEnter的代码:
package com.baidu.ueditor;
import com.baidu.ueditor.define.ActionMap;
import com.baidu.ueditor.define.BaseState;
import com.baidu.ueditor.define.State;
import com.baidu.ueditor.hunter.FileManager;
import com.baidu.ueditor.hunter.ImageHunter;
import com.baidu.ueditor.upload.Uploader;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.json.JSONObject;
public class ActionEnter
{
private HttpServletRequest request = null;
private String rootPath = null;
private String contextPath = null;
private String actionType = null;
private ConfigManager configManager = null;
public ActionEnter(HttpServletRequest request, String rootPath)
{
this.request = request;
this.rootPath = rootPath;
// 对action进行赋值。
this.actionType = request.getParameter("action");
this.contextPath = request.getContextPath();
// 构建configManager类
this.configManager = ConfigManager.getInstance(this.rootPath, this.contextPath, request.getRequestURI());
}
public String exec()
{
// 这个是处理jsonp的形式,一般都是不跨域的。
String callbackName = this.request.getParameter("callback");
if (callbackName != null)
{
if (!validCallbackName(callbackName)) {
return new BaseState(false, 401).toJSONString();
}
return callbackName + "(" + invoke() + ");";
}
return invoke();
}
public String invoke()
{
// 判断action是否合法,如果不合法返回一个非法状态
if ((this.actionType == null) || (!ActionMap.mapping.containsKey(this.actionType))) {
return new BaseState(false, 101).toJSONString();
}
// 如果找不到configManager也报错
if ((this.configManager == null) || (!this.configManager.valid())) {
return new BaseState(false, 102).toJSONString();
}
State state = null;
// 取得actionCode
int actionCode = ActionMap.getType(this.actionType);
Map conf = null;
switch (actionCode)
{
case 0:
return this.configManager.getAllConfig().toString();
case 1:
case 2:
case 3:
case 4:
// 处理上传文件
conf = this.configManager.getConfig(actionCode);
state = new Uploader(this.request, conf).doExec();
break;
case 5:
conf = this.configManager.getConfig(actionCode);
String[] list = this.request.getParameterValues((String)conf.get("fieldName"));
// 处理在线编辑
state = new ImageHunter(conf).capture(list);
break;
case 6:
case 7:
conf = this.configManager.getConfig(actionCode);
int start = getStartIndex();
// 处理文件list
state = new FileManager(conf).listFile(start);
}
return state.toJSONString();
}
public int getStartIndex()
{
String start = this.request.getParameter("start");
try
{
return Integer.parseInt(start); } catch (Exception e) {
}
return 0;
}
public boolean validCallbackName(String name)
{
if (name.matches("^[a-zA-Z_]+[\\w0-9_]*$")) {
return true;
}
return false;
}
}
我们慢慢的来看这个函数:首先在构造函数里面调用了request.getContextPath()和request.getRequestURI()函数。
假设我们的项目的contextPath为:test,那么下面两个函数的返回值则如下:
request.getContextPath /test
request.getRequestURI /test/resources/ueditor/jsp/controller.jsp
我们还是先来看下ConfigManager类吧。
package com.baidu.ueditor;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONObject;
public final class ConfigManager
{
private final String rootPath;
private final String originalPath;
private final String contextPath;
private static final String configFileName = "config.json";
private String parentPath = null;
private JSONObject jsonConfig = null;
private static final String SCRAWL_FILE_NAME = "scrawl";
private static final String REMOTE_FILE_NAME = "remote";
private ConfigManager(String rootPath, String contextPath, String uri)
throws FileNotFoundException, IOException
{
rootPath = rootPath.replace("\\", "/");
this.rootPath = rootPath;
this.contextPath = contextPath;
// 这个地方要特别注意,originalPath其实就是controller.jsp所在的路径
if (contextPath.length() > 0)
this.originalPath = (this.rootPath + uri.substring(contextPath.length()));
else {
this.originalPath = (this.rootPath + uri);
}
initEnv();
}
public static ConfigManager getInstance(String rootPath, String contextPath, String uri)
{
try
{
return new ConfigManager(rootPath, contextPath, uri); } catch (Exception e) {
}
return null;
}
public boolean valid()
{
return this.jsonConfig != null;
}
public JSONObject getAllConfig()
{
return this.jsonConfig;
}
public Map<String, Object> getConfig(int type)
{
Map conf = new HashMap();
String savePath = null;
// 根据不同的code来解析config.json的配置文件
switch (type)
{
case 4:
conf.put("isBase64", "false");
conf.put("maxSize", Long.valueOf(this.jsonConfig.getLong("fileMaxSize")));
conf.put("allowFiles", getArray("fileAllowFiles"));
conf.put("fieldName", this.jsonConfig.getString("fileFieldName"));
savePath = this.jsonConfig.getString("filePathFormat");
break;
case 1:
conf.put("isBase64", "false");
conf.put("maxSize", Long.valueOf(this.jsonConfig.getLong("imageMaxSize")));
conf.put("allowFiles", getArray("imageAllowFiles"));
conf.put("fieldName", this.jsonConfig.getString("imageFieldName"));
savePath = this.jsonConfig.getString("imagePathFormat");
break;
case 3:
conf.put("maxSize", Long.valueOf(this.jsonConfig.getLong("videoMaxSize")));
conf.put("allowFiles", getArray("videoAllowFiles"));
conf.put("fieldName", this.jsonConfig.getString("videoFieldName"));
savePath = this.jsonConfig.getString("videoPathFormat");
break;
case 2:
conf.put("filename", "scrawl");
conf.put("maxSize", Long.valueOf(this.jsonConfig.getLong("scrawlMaxSize")));
conf.put("fieldName", this.jsonConfig.getString("scrawlFieldName"));
conf.put("isBase64", "true");
savePath = this.jsonConfig.getString("scrawlPathFormat");
break;
case 5:
conf.put("filename", "remote");
conf.put("filter", getArray("catcherLocalDomain"));
conf.put("maxSize", Long.valueOf(this.jsonConfig.getLong("catcherMaxSize")));
conf.put("allowFiles", getArray("catcherAllowFiles"));
conf.put("fieldName", this.jsonConfig.getString("catcherFieldName") + "[]");
savePath = this.jsonConfig.getString("catcherPathFormat");
break;
case 7:
conf.put("allowFiles", getArray("imageManagerAllowFiles"));
conf.put("dir", this.jsonConfig.getString("imageManagerListPath"));
conf.put("count", Integer.valueOf(this.jsonConfig.getInt("imageManagerListSize")));
break;
case 6:
conf.put("allowFiles", getArray("fileManagerAllowFiles"));
conf.put("dir", this.jsonConfig.getString("fileManagerListPath"));
conf.put("count", Integer.valueOf(this.jsonConfig.getInt("fileManagerListSize")));
}
conf.put("savePath", savePath);
conf.put("rootPath", this.rootPath);
return conf;
}
// 加载config.json配置文件
private void initEnv()
throws FileNotFoundException, IOException
{
File file = new File(this.originalPath);
if (!file.isAbsolute()) {
file = new File(file.getAbsolutePath());
}
this.parentPath = file.getParent();
String configContent = readFile(getConfigPath());
try
{
JSONObject jsonConfig = new JSONObject(configContent);
this.jsonConfig = jsonConfig;
} catch (Exception e) {
this.jsonConfig = null;
}
}
private String getConfigPath()
{
return this.parentPath + File.separator + "config.json";
}
private String[] getArray(String key)
{
JSONArray jsonArray = this.jsonConfig.getJSONArray(key);
String[] result = new String[jsonArray.length()];
int i = 0; for (int len = jsonArray.length(); i < len; i++) {
result[i] = jsonArray.getString(i);
}
return result;
}
// 读取config.json里面的内容
private String readFile(String path)
throws IOException
{
StringBuilder builder = new StringBuilder();
try
{
InputStreamReader reader = new InputStreamReader(new FileInputStream(path), "UTF-8");
BufferedReader bfReader = new BufferedReader(reader);
String tmpContent = null;
while ((tmpContent = bfReader.readLine()) != null) {
builder.append(tmpContent);
}
bfReader.close();
}
catch (UnsupportedEncodingException localUnsupportedEncodingException)
{
}
return filter(builder.toString());
}
private String filter(String input)
{
return input.replaceAll("/\\*[\\s\\S]*?\\*/", "");
}
}
我们再来看
Uploader函数,其实很简单:
package com.baidu.ueditor.upload;
import com.baidu.ueditor.define.State;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
public class Uploader
{
private HttpServletRequest request = null;
private Map<String, Object> conf = null;
public Uploader(HttpServletRequest request, Map<String, Object> conf) {
this.request = request;
this.conf = conf;
}
public final State doExec() {
String filedName = (String)this.conf.get("fieldName");
State state = null;
if ("true".equals(this.conf.get("isBase64")))
state = Base64Uploader.save(this.request.getParameter(filedName),
this.conf);
else {
state = BinaryUploader.save(this.request, this.conf);
}
return state;
}
}
这个很好理解,我们接着来看BinaryUploader类:
package com.baidu.ueditor.upload;
import com.baidu.ueditor.PathFormat;
import com.baidu.ueditor.define.BaseState;
import com.baidu.ueditor.define.FileType;
import com.baidu.ueditor.define.State;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
public class BinaryUploader
{
// 使用fileupload来处理文件上传
public static final State save(HttpServletRequest request, Map<String, Object> conf)
{
FileItemStream fileStream = null;
boolean isAjaxUpload = request.getHeader("X_Requested_With") != null;
if (!ServletFileUpload.isMultipartContent(request)) {
return new BaseState(false, 5);
}
ServletFileUpload upload = new ServletFileUpload(
new DiskFileItemFactory());
if (isAjaxUpload) {
upload.setHeaderEncoding("UTF-8");
}
try
{
FileItemIterator iterator = upload.getItemIterator(request);
while (iterator.hasNext()) {
fileStream = iterator.next();
if (!fileStream.isFormField())
break;
fileStream = null;
}
if (fileStream == null) {
return new BaseState(false, 7);
}
String savePath = (String)conf.get("savePath");
String originFileName = fileStream.getName();
String suffix = FileType.getSuffixByFilename(originFileName);
originFileName = originFileName.substring(0,
originFileName.length() - suffix.length());
savePath = savePath + suffix;
long maxSize = ((Long)conf.get("maxSize")).longValue();
if (!validType(suffix, (String[])conf.get("allowFiles"))) {
return new BaseState(false, 8);
}
savePath = PathFormat.parse(savePath, originFileName);
String physicalPath = (String)conf.get("rootPath") + savePath;
// 调用存储类来处理文件存储
InputStream is = fileStream.openStream();
State storageState = StorageManager.saveFileByInputStream(is,
physicalPath, maxSize);
is.close();
if (storageState.isSuccess()) {
storageState.putInfo("url", PathFormat.format(savePath));
storageState.putInfo("type", suffix);
storageState.putInfo("original", originFileName + suffix);
}
return storageState;
} catch (FileUploadException e) {
return new BaseState(false, 6);
} catch (IOException localIOException) {
}
return new BaseState(false, 4);
}
private static boolean validType(String type, String[] allowTypes) {
List list = Arrays.asList(allowTypes);
return list.contains(type);
}
}
StorageManager我们就不看了,无非就是做一些文件存储的一些事情,下面我们来分析下这种实现方式的问题。
最后我稍微总结下看这个代码得收获和对作者的建议:
- 从这个地方来看,无法将图片放置在外部路径,因为这种实现就决定了只能放到项目路径下,这个最大的问题就是,有可能不小心,重新上下线,内容全部丢了。
- 从实现来看,大量的使用静态调用,基本上无法二次开发,不能灵活的继承它来处理个性化的东西,比如如果存储到fastDFS里面,这个就需要改里面的代码,不能通过扩展的方式来进行
- config.json里面的配置项转换的时候,进行了重命名,这个地方就要求读者要记两个变量名,比如:imagePathFormat变成了savePath, 感觉好像挺好理解,但是这种明显不是好的方式,如果里面存在一个这个逻辑,最好显式的说明,而不是硬编码
- 源代码不开放,无法进行扩展和修改,建议作者开发这个jar到github里面,社区一块维护