springboot整合ueditor

这篇文章记录下如何在springboot工程中使用ueditor,没有做前后端分离,ueditor的前端页面也在后端的springboot工程中。使用修改ueditor后端源码码的方式进行整合

一、分析

1.1 使用ueditor为什么需要后端工程的配合

ueditor是一个可以嵌入在前端页面上的富文本编辑器,但它的配置信息和上传图片,上传附件等这些功能的实现需要后端工程的支持,即前端向后端请求配置信息,上传图片。

ueditor官方提供了jsp版的后台代码。这个示例工程可以直接在tomcat中部署进行测试,就是把ueditor前端页面和jsp后端都部署在tomcat中,这种方式官网有详细示例,可以用来熟悉ueditor使用时的前后端交互过程。

1.2 ueditor的jsp版后端代码部分详解

首先给出一个ueditor发给后端的获取配置信息的请求的url示例:

http://localhost:8080/ueditor-demo/jsp/controller.jsp?action=config&&noCache=1589691598506

在官网下载ueditor的源码,其中有一个jsp文件夹,这里边放的就是jsp后端代码

lib目录中是其依赖的jar包,src中是工程源码,config.json是对ueditor进行配置的配置文件,controller.jsp是前端请求的入口。

controller.jsp中的主要内容如下:

	request.setCharacterEncoding( "utf-8" );
	response.setHeader("Content-Type" , "text/html");
	
	String rootPath = application.getRealPath( "/" );
	
	out.write( new ActionEnter( request, rootPath ).exec() );

可以看到所有请求的处理都是通过ActionEnter这个类中的exec方法处理的。从src目录中找到这个文件。

梳理下这个文件中的主要操作:

(1) 构造方法中实例化了配置文件管理对象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() );
		
	}

(2) exec方法调用了本类中的invoke方法,在invoke方法中根据前端请求参数action来判断到底是进行什么操作,这个action在构造方法中通过request获取,如上边的示例url中action=config

(3) 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;
		//1.把前台传递的action转换为actionCode
		int actionCode = ActionMap.getType( this.actionType );
		
		Map<String, Object> conf = null;
		//2.根据actionCode进行对应的操作:获取配置,上传图片等
		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();
		
	}

1.3 总结

在springboot工程中整合ueditor后端部分,我们可以直接拷贝官方提供的jsp后端的src目录中的源码,然后自己建一个Controll来处理来自ueditor的请求,并把所有的请求向jsp中那样使用ActionEnter类的exec方法处理。

二、整合ueditor到springboot

2.1 复制官方提供的源码,导入需要的依赖

把官方提供的jsp文件中的src目录复制到我们自己的工程中,根据根据jsp文件中lib目录下的jar包导入对应的依赖到我们的工程中。

lib目录中的jar包:


对应的导入如下的maven依赖

 <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>

        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.3</version>
        </dependency>

        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>

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

2.2创建一个Controller来处理ueditor的请求

@RestController
@RequestMapping("/ueditor")
public class UEditorController {

    @RequestMapping("/config")
    public String config(HttpServletRequest request, HttpServletResponse response){
        String userDir = System.getProperty("user.dir");
        return new ActionEnter(request, userDir).exec();
    }
}

这里的参数userDir我指定的是springboot工程打成jar包运行时这个jar包所在的目录,jsp示例中指定的是应用的根路径,这个可根据实际情况调整。

2.4 前端页面构建

在resources目录下新建static文件夹,因为放在这个目录下的内容工程启动时可以被直接访问,所以我们把官方提供的示例直接复制到这个目录下


这样在浏览器中直接访问index.html就可以访问到该页面,修改ueditor.config.js中的serverUrl指向我们定义的这个controller

 // 服务器统一请求接口路径
        , serverUrl: "ueditor/config"

2.5 配置文件读取位置的调整

现在启动工程已经可以访问到ueditor页面了,但现在因为在后端代码读取配置文件的路径下并没有配置文件,所以前台页面会提示后端配置错误,接下来我们研究下后端代码如何读取配置文件。

前边提到在ActionEnter的构造方法中创建了ConfigManager对象,通过ConfigManager来读取配置文件

ConfigManager的构造方法调用了initEnv方法,这个方法的代码如下

	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( this.getConfigPath() );
		
		try{
			JSONObject jsonConfig = new JSONObject( configContent );
			this.jsonConfig = jsonConfig;
		} catch ( Exception e ) {
			this.jsonConfig = null;
		}
		
	}

可以看到给readFile方法传入了一个路径返回了configContent,所以是这个readFile方法在读取配置文件,

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() );
		
	}

可以看到在这个方法中通过InputStreamReader在读取配置文件。所以我们可以把自己的配置文件放在resources目录下,通过类加载器返回输入流传给InputStreamReader,这样就可以读取到自己的配置文件

private String readFile () throws IOException {
		
		//更改读取后端配置文件的位置
		InputStream resourceAsStream = ConfigManager.class.getClassLoader().getResourceAsStream("config.json");
		StringBuilder builder = new StringBuilder();
		...省略其他

然后这个readFile方法就可以修改为不带参数的

2.6 上传图片未找到数据异常的处理

经过上边的处理后编辑器页面已经可以使用了,但如果上传图片会报未找到数据的异常,这是因为上传图片的请求被springmvc的MultipartResolver拦截到了并封装成了MultipartFile对象,所以请求中没有文件数据,这个MultipartResolver是springboot自动配置的StandardServletMultipartResolver。所以解决的办法是排除掉

springboot自动配置MultipartResolver,重写一个自己的MultipartResolver并在其中放行ueditor的上传请求。

(1) 排除springboot自动配置MultipartResolver

在application.yml中进行如下配置

spring:
  autoconfigure:
    #排除springboot对MultipartResolver的自动配置,使用自己的配置,在其中放行来自ueditor的上传请求
    exclude: org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration

(2) 创建一个自定义的MultipartResolver,继承StandardServletMultipartResolver

public class MyCommonsMultipartResolver extends StandardServletMultipartResolver {

    //springmvc对上传文件请求的处理,使其过滤掉ueditor的上传请求
    @Override
    public boolean isMultipart(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        if(requestURI.contains("/ueditor")){
            return false;
        }
        return super.isMultipart(request);
    }
}

(3) 把这个自定义类配置到spring容器中

   @Bean(name = "multipartResolver")
    public MultipartResolver multipartResolver(){
        return new MyCommonsMultipartResolver();
    }

这样图片上传功能就可以了,图片的保存路径是在创建ActionEnter时的第二个参数指定的路径,再加上config.json中指定的imagePathFormat,可以通过修改配置文件中的这个参数来改变图片的保存路径。

2.7 图片回显问题

上一步只是完成了图片的上传,但图片在编辑器中的回显依然存在问题,报找不到图片的错误,这是因为后端图片上传成功后给前端返回了一个url,前端会访问这个url获取图片。追踪源码会发现在BinaryUploader#save方法中

if (storageState.isSuccess()) {
				storageState.putInfo("url", PathFormat.format(savePath));
				storageState.putInfo("type", suffix);
				storageState.putInfo("original", originFileName + suffix);
			}

这个savePath就是上面config.json中的imagePathFormat再加上文件的保存名称而前端拿到这个url后,会直接请求这个地址:http://localhost:8080/url,比如按我的配置文件,这个url是/ueditor/upload/image/20200517/1589695937922096997.png

因为现在工程中不存在这样一个接口,所以获取不到图片。

这里给出一种解决方案,在工程中增加一个获取图片的接口,然后在这里把这个url指向获取图片的接口(文件名拼接在url上),这样前端就可以拿到上传的图片进行回显。

当然也可以修改ueditor的前端回显图片部分直接指向获取图片的接口,这样这里的url就可以只放一个参数名

三、总结

这篇文章记录了在springboot工程中如何整合ueditor,并没有进行前后端分离,导入了ueditor的jsp后端源码并进行了修改,没有修改ueditor的前端源码。