在一个客户的前后端分离的管理系统中,客户希望要一个编辑功能丰富的富文本编辑器,前端人员翻来覆去最终找来了百度的UEditor 符合客户的要求,问题也随之而来,百度的这个富文本编辑器需要后端提供接口配置,前端人员就丢过来一个链接,说是需要后端配合提供一个,查看了百度官方提供UEditor 文档,看着挺简单的,官方提供的demo 是1.4.3.3 Jsp 版本 ,其中有两大坑
1、通过官方提供方式读取配置文件读取config.json 会报错,“后端未正确加载配置文件”
2、图片上传后会报错“找不到上传的文件资源”
坑1 的原因 因为springboot 是通过内置容器启动的,普通的通过根路径去读取文件是读取不到, 只能通过ClassPathResource 来读取
坑2的原因 因为 springboot 默认对文件上传进行拦截包装,通过HttpServletRequest 是读取不到文件,必须通过MultipartHttpServletRequest 对象获取

解决方案
通过反编译ueditor1.1.2.jar 得到源码,注意ueditor 自身依赖的jar不要忘记引入

 

 

 

pom 依赖

<!– 文件上传包 –>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<!– IO包 –>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>

<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20180813</version>
</dependency>

 

我们需要重写的两个重要的类ConfigManager和 upload下的BinaryUploader,一个读取配置文件,一个是保存上传资源

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 final static String SCRAWL_FILE_NAME = “scrawl”;
private final static 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;

if (contextPath.length() > 0) {
this.originalPath = this.rootPath + uri.substring(contextPath.length());
} else {
this.originalPath = this.rootPath + uri;
}

this.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<String, Object> conf = new HashMap<String, Object>();
String savePath = null;
switch (type) {

case ActionMap.UPLOAD_FILE:
conf.put(“isBase64”, “false”);
conf.put(“maxSize”, this.jsonConfig.getLong(“fileMaxSize”));
conf.put(“allowFiles”, this.getArray(“fileAllowFiles”));
conf.put(“fieldName”, this.jsonConfig.getString(“fileFieldName”));
savePath = this.jsonConfig.getString(“filePathFormat”);
break;

case ActionMap.UPLOAD_IMAGE:
conf.put(“isBase64”, “false”);
conf.put(“maxSize”, this.jsonConfig.getLong(“imageMaxSize”));
conf.put(“allowFiles”, this.getArray(“imageAllowFiles”));
conf.put(“fieldName”, this.jsonConfig.getString(“imageFieldName”));
savePath = this.jsonConfig.getString(“imagePathFormat”);
break;

case ActionMap.UPLOAD_VIDEO:
conf.put(“maxSize”, this.jsonConfig.getLong(“videoMaxSize”));
conf.put(“allowFiles”, this.getArray(“videoAllowFiles”));
conf.put(“fieldName”, this.jsonConfig.getString(“videoFieldName”));
savePath = this.jsonConfig.getString(“videoPathFormat”);
break;

case ActionMap.UPLOAD_SCRAWL:
conf.put(“filename”, ConfigManager.SCRAWL_FILE_NAME);
conf.put(“maxSize”, this.jsonConfig.getLong(“scrawlMaxSize”));
conf.put(“fieldName”, this.jsonConfig.getString(“scrawlFieldName”));
conf.put(“isBase64”, “true”);
savePath = this.jsonConfig.getString(“scrawlPathFormat”);
break;

case ActionMap.CATCH_IMAGE:
conf.put(“filename”, ConfigManager.REMOTE_FILE_NAME);
conf.put(“filter”, this.getArray(“catcherLocalDomain”));
conf.put(“maxSize”, this.jsonConfig.getLong(“catcherMaxSize”));
conf.put(“allowFiles”, this.getArray(“catcherAllowFiles”));
conf.put(“fieldName”, this.jsonConfig.getString(“catcherFieldName”) + “[]”);
savePath = this.jsonConfig.getString(“catcherPathFormat”);
break;

case ActionMap.LIST_IMAGE:
conf.put(“allowFiles”, this.getArray(“imageManagerAllowFiles”));
conf.put(“dir”, this.jsonConfig.getString(“imageManagerListPath”));
conf.put(“count”, this.jsonConfig.getInt(“imageManagerListSize”));
break;

case ActionMap.LIST_FILE:
conf.put(“allowFiles”, this.getArray(“fileManagerAllowFiles”));
conf.put(“dir”, this.jsonConfig.getString(“fileManagerListPath”));
conf.put(“count”, this.jsonConfig.getInt(“fileManagerListSize”));
break;

}

conf.put(“savePath”, savePath);
conf.put(“rootPath”, this.rootPath);

return conf;

}

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 = this.readFile(getConfigPath());
//###### start 使用自己本地静态读取配置文件config.json
ClassPathResource classPathResource = new ClassPathResource(ConfigManager.configFileName);
String configContent = readFile(classPathResource.getInputStream());
//##### end
try {
JSONObject jsonConfig = new JSONObject(configContent);
this.jsonConfig = jsonConfig;
} catch (Exception e) {
this.jsonConfig = null;
}

}

private String getConfigPath() {
return this.parentPath + File.separator + ConfigManager.configFileName;
}

private String[] getArray(String key) {

JSONArray jsonArray = this.jsonConfig.getJSONArray(key);
String[] result = new String[jsonArray.length()];

for (int i = 0, len = jsonArray.length(); i < len; i++) {
result[i] = jsonArray.getString(i);
}

return result;

}

/**
* 重载一个读取配置文件的方法
*
* @param inputStream
* @return
* @throws IOException
*/
private String readFile(InputStream inputStream) throws IOException {

StringBuilder builder = new StringBuilder();

try {
InputStreamReader reader = new InputStreamReader(inputStream, “UTF-8”);
BufferedReader bfReader = new BufferedReader(reader);

String tmpContent = null;

while ((tmpContent = bfReader.readLine()) != null) {
builder.append(tmpContent);
}

bfReader.close();

} catch (UnsupportedEncodingException e) {
// 忽略
}

return this.filter(builder.toString());

}

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 e) {
// 忽略
}

return this.filter(builder.toString());

}

private String filter(String input) {
return input.replaceAll(“/\\*[\\s\\S]*?\\*/”, “”);

}

}

BinaryUploader 文件上传处理类

public class BinaryUploader {
static Logger logger = LoggerFactory.getLogger(BinaryUploader.class);

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, AppInfo.NOT_MULTIPART_CONTENT);
}

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, AppInfo.NOTFOUND_UPLOAD_DATA);
// }

// start 修改
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
MultipartFile multipartFile = multipartRequest.getFile(conf.get(“fieldName”).toString());
if (multipartFile == null) {
return new BaseState(false, AppInfo.NOTFOUND_UPLOAD_DATA);
}
// end
String savePath = (String) conf.get(“savePath”);
//String originFileName = fileStream.getName();
String originFileName = multipartFile.getOriginalFilename();//修改
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, AppInfo.NOT_ALLOW_FILE_TYPE);
}
savePath = PathFormat.parse(savePath, originFileName);
String physicalPath = conf.get(“rootPath”) + savePath;
logger.info(“BinaryUploader physicalPath:{},savePath:{}”, physicalPath, savePath);
//InputStream is = fileStream.openStream();
InputStream is = multipartFile.getInputStream(); //修改
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, AppInfo.PARSE_REQUEST_ERROR);
} catch (IOException e) {
}
return new BaseState(false, AppInfo.IO_ERROR);
}

private static boolean validType(String type, String[] allowTypes) {
List<String> list = Arrays.asList(allowTypes);

return list.contains(type);
}
}

将config.json 放入springboot项目的resources 下,下面是官方提供的配置

/* 前后端通信相关的配置,注释只允许使用多行方式 */
{
/* 上传图片配置项 */
“imageActionName”: “uploadimage”, /* 执行上传图片的action名称 */
“imageFieldName”: “upfile”, /* 提交的图片表单名称 */
“imageMaxSize”: 2048000, /* 上传大小限制,单位B */
“imageAllowFiles”: [“.png”, “.jpg”, “.jpeg”, “.gif”, “.bmp”], /* 上传图片格式显示 */
“imageCompressEnable”: true, /* 是否压缩图片,默认是true */
“imageCompressBorder”: 1600, /* 图片压缩最长边限制 */
“imageInsertAlign”: “none”, /* 插入的图片浮动方式 */
“imageUrlPrefix”: “”, /* 图片访问路径前缀 */
“imagePathFormat”: “/ueditor/jsp/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}”, /* 上传保存路径,可以自定义保存路径和文件名格式 */
/* {filename} 会替换成原文件名,配置这项需要注意中文乱码问题 */
/* {rand:6} 会替换成随机数,后面的数字是随机数的位数 */
/* {time} 会替换成时间戳 */
/* {yyyy} 会替换成四位年份 */
/* {yy} 会替换成两位年份 */
/* {mm} 会替换成两位月份 */
/* {dd} 会替换成两位日期 */
/* {hh} 会替换成两位小时 */
/* {ii} 会替换成两位分钟 */
/* {ss} 会替换成两位秒 */
/* 非法字符 \ : * ? ” < > | */
/* 具请体看线上文档: fex.baidu.com/ueditor/#use-format_upload_filename */

/* 涂鸦图片上传配置项 */
“scrawlActionName”: “uploadscrawl”, /* 执行上传涂鸦的action名称 */
“scrawlFieldName”: “upfile”, /* 提交的图片表单名称 */
“scrawlPathFormat”: “/ueditor/jsp/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}”, /* 上传保存路径,可以自定义保存路径和文件名格式 */
“scrawlMaxSize”: 2048000, /* 上传大小限制,单位B */
“scrawlUrlPrefix”: “”, /* 图片访问路径前缀 */
“scrawlInsertAlign”: “none”,

/* 截图工具上传 */
“snapscreenActionName”: “uploadimage”, /* 执行上传截图的action名称 */
“snapscreenPathFormat”: “/ueditor/jsp/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}”, /* 上传保存路径,可以自定义保存路径和文件名格式 */
“snapscreenUrlPrefix”: “”, /* 图片访问路径前缀 */
“snapscreenInsertAlign”: “none”, /* 插入的图片浮动方式 */

/* 抓取远程图片配置 */
“catcherLocalDomain”: [“127.0.0.1”, “localhost”, “img.baidu.com”],
“catcherActionName”: “catchimage”, /* 执行抓取远程图片的action名称 */
“catcherFieldName”: “source”, /* 提交的图片列表表单名称 */
“catcherPathFormat”: “/ueditor/jsp/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}”, /* 上传保存路径,可以自定义保存路径和文件名格式 */
“catcherUrlPrefix”: “”, /* 图片访问路径前缀 */
“catcherMaxSize”: 2048000, /* 上传大小限制,单位B */
“catcherAllowFiles”: [“.png”, “.jpg”, “.jpeg”, “.gif”, “.bmp”], /* 抓取图片格式显示 */

/* 上传视频配置 */
“videoActionName”: “uploadvideo”, /* 执行上传视频的action名称 */
“videoFieldName”: “upfile”, /* 提交的视频表单名称 */
“videoPathFormat”: “/ueditor/jsp/upload/video/{yyyy}{mm}{dd}/{time}{rand:6}”, /* 上传保存路径,可以自定义保存路径和文件名格式 */
“videoUrlPrefix”: “”, /* 视频访问路径前缀 */
“videoMaxSize”: 102400000, /* 上传大小限制,单位B,默认100MB */
“videoAllowFiles”: [
“.flv”, “.swf”, “.mkv”, “.avi”, “.rm”, “.rmvb”, “.mpeg”, “.mpg”,
“.ogg”, “.ogv”, “.mov”, “.wmv”, “.mp4”, “.webm”, “.mp3”, “.wav”, “.mid”], /* 上传视频格式显示 */

/* 上传文件配置 */
“fileActionName”: “uploadfile”, /* controller里,执行上传视频的action名称 */
“fileFieldName”: “upfile”, /* 提交的文件表单名称 */
“filePathFormat”: “/ueditor/jsp/upload/file/{yyyy}{mm}{dd}/{time}{rand:6}”, /* 上传保存路径,可以自定义保存路径和文件名格式 */
“fileUrlPrefix”: “”, /* 文件访问路径前缀 */
“fileMaxSize”: 51200000, /* 上传大小限制,单位B,默认50MB */
“fileAllowFiles”: [
“.png”, “.jpg”, “.jpeg”, “.gif”, “.bmp”,
“.flv”, “.swf”, “.mkv”, “.avi”, “.rm”, “.rmvb”, “.mpeg”, “.mpg”,
“.ogg”, “.ogv”, “.mov”, “.wmv”, “.mp4”, “.webm”, “.mp3”, “.wav”, “.mid”,
“.rar”, “.zip”, “.tar”, “.gz”, “.7z”, “.bz2”, “.cab”, “.iso”,
“.doc”, “.docx”, “.xls”, “.xlsx”, “.ppt”, “.pptx”, “.pdf”, “.txt”, “.md”, “.xml”
], /* 上传文件格式显示 */

/* 列出指定目录下的图片 */
“imageManagerActionName”: “listimage”, /* 执行图片管理的action名称 */
“imageManagerListPath”: “/ueditor/jsp/upload/image/”, /* 指定要列出图片的目录 */
“imageManagerListSize”: 20, /* 每次列出文件数量 */
“imageManagerUrlPrefix”: “”, /* 图片访问路径前缀 */
“imageManagerInsertAlign”: “none”, /* 插入的图片浮动方式 */
“imageManagerAllowFiles”: [“.png”, “.jpg”, “.jpeg”, “.gif”, “.bmp”], /* 列出的文件类型 */

/* 列出指定目录下的文件 */
“fileManagerActionName”: “listfile”, /* 执行文件管理的action名称 */
“fileManagerListPath”: “/ueditor/jsp/upload/file/”, /* 指定要列出文件的目录 */
“fileManagerUrlPrefix”: “”, /* 文件访问路径前缀 */
“fileManagerListSize”: 20, /* 每次列出文件数量 */
“fileManagerAllowFiles”: [
“.png”, “.jpg”, “.jpeg”, “.gif”, “.bmp”,
“.flv”, “.swf”, “.mkv”, “.avi”, “.rm”, “.rmvb”, “.mpeg”, “.mpg”,
“.ogg”, “.ogv”, “.mov”, “.wmv”, “.mp4”, “.webm”, “.mp3”, “.wav”, “.mid”,
“.rar”, “.zip”, “.tar”, “.gz”, “.7z”, “.bz2”, “.cab”, “.iso”,
“.doc”, “.docx”, “.xls”, “.xlsx”, “.ppt”, “.pptx”, “.pdf”, “.txt”, “.md”, “.xml”
] /* 列出的文件类型 */

}

最后编写一个接口提供给富文本加载配置和上传文件,注意,上述的config.json中需要配置访问前缀,我们可以默认为空,通过下面动态加载的方式来设置,这样更好的能区分多环境配置

@Controller
@RequestMapping(“api/ueditor”)
public class UEditorController {

@Autowired
private CoreProperties coreProperties;

@RequestMapping(value = “config”, produces = “application/json”)
public void config(HttpServletRequest request, HttpServletResponse response) throws Exception {

ActionEnter actionEnter = new ActionEnter(request, coreProperties.getFile().getLocation());
response.setContentType(“application/json”);

String action = request.getParameter(“action”);

if (action.equals(“config”)) { //如果是加载配置项 动态添加资源访问前缀
ConfigManager configManager = actionEnter.getConfigManager();
JSONObject allConfig = configManager.getAllConfig();
allConfig.keySet().stream().filter(p->p.endsWith(“UrlPrefix”)).forEach(s->{
allConfig.put(s,coreProperties.getFile().getDomainUrlPrefix());
});
}

PrintWriter writer = response.getWriter();
writer.write(actionEnter.exec());
writer.flush();
writer.close();
}

}

ActionEnter 是ueditor 默认提供的一个处理,用来处理整个富文本的请求,默认不提供getConfigManager 重写其中代码

public class ActionEnter {

private HttpServletRequest request = null;

private String rootPath = null;
private String contextPath = null;

private String actionType = null;

private ConfigManager configManager = null;

/**
* 添加一个get方法用于外部获取配置对象
*
* @return
*/
public ConfigManager getConfigManager() {
return configManager;
}

public ActionEnter(HttpServletRequest request, String rootPath) {

this.request = request;
this.rootPath = rootPath;
this.actionType = request.getParameter(“action”);
this.contextPath = request.getContextPath();
this.configManager = ConfigManager.getInstance(this.rootPath, this.contextPath, request.getRequestURI());

}

public String exec() {

String callbackName = this.request.getParameter(“callback”);

if (callbackName != null) {

if (!validCallbackName(callbackName)) {
return new BaseState(false, AppInfo.ILLEGAL).toJSONString();
}

return callbackName + “(” + this.invoke() + “);”;

} else {
return this.invoke();
}

}

public String invoke() {

if (actionType == null || !ActionMap.mapping.containsKey(actionType)) {
return new BaseState(false, AppInfo.INVALID_ACTION).toJSONString();
}

if (this.configManager == null || !this.configManager.valid()) {
return new BaseState(false, AppInfo.CONFIG_ERROR).toJSONString();
}

State state = null;

int actionCode = ActionMap.getType(this.actionType);

Map<String, Object> conf = null;

switch (actionCode) {

case ActionMap.CONFIG:
return this.configManager.getAllConfig().toString();

case ActionMap.UPLOAD_IMAGE:
case ActionMap.UPLOAD_SCRAWL:
case ActionMap.UPLOAD_VIDEO:
case ActionMap.UPLOAD_FILE:
conf = this.configManager.getConfig(actionCode);
state = new Uploader(request, conf).doExec();
break;

case ActionMap.CATCH_IMAGE:
conf = configManager.getConfig(actionCode);
String[] list = this.request.getParameterValues((String) conf.get(“fieldName”));
state = new ImageHunter(conf).capture(list);
break;

case ActionMap.LIST_IMAGE:
case ActionMap.LIST_FILE:
conf = configManager.getConfig(actionCode);
int start = this.getStartIndex();
state = new FileManager(conf).listFile(start);
break;

}

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;

}

}

 

转载:https://blog.csdn.net/Nicolas12/article/details/100748801