在一个客户的前后端分离的管理系统中,客户希望要一个编辑功能丰富的富文本编辑器,前端人员翻来覆去最终找来了百度的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