springboot集成百度编辑器ueditor
ueditor是百度一款所见即所得编辑器,它支持丰富的富文本编辑场景,那么如何在springboot中集成呢。
1. 首先我们去ueditor git仓库下载最新版本的代码,https://github.com/fex-team/ueditor ,这里推荐选择最新的tag进行下载。下载完成后需要进行打包,这一步编辑器官网已经有详细说明
执行打包grunt命令时,可以传入编码和后台语言的参数 支持两种编码指定:--encode参数 utf8 (默认编码) gbk 提供四种后台语言支持:--server参数 php (默认语言) jsp net (代表.net后台) asp 例如:想要打包成编码是gbk,后台语言是asp版本,可执行命令: grunt --encode=gbk --server=asp
与springboot集成我们需要打包为server=jsp,打包完成后将目录中会生成dist/utf8-jsp文件夹,我们将该文件夹中的内容全部拷贝到前端页面所在的位置。将jsp下的文件拷贝至后端代码工程中

2.第一步我们已经完成了资源的打包,现在我们开始进行配置。ueditor中的配置信息是通过后端提供的接口进行动态读取,具体的配置信息在第一步打包生成的dist/utf8-jsp/jsp 下的config.json文件中,这里包含了编辑器操作对应后端接口actionName及一些动态属性。
3.统一后端服务接口
/** * 配置项主体。注意,此处所有涉及到路径的配置别遗漏URL变量。 */ window.UEDITOR_CONFIG = { //为编辑器实例添加一个路径,这个不能被注释 UEDITOR_HOME_URL: URL // 服务器统一请求接口路径 , serverUrl: URL + "jsp/controller.jsp"
这里后段接口默认是项目地址/jsp/controller.jsp , 与springboot集成则需要提供一个后端接口来统一处理请求
@Slf4j @RestController @RequestMapping("/editor") public class EditorController { /** * 配置 * * @param columnId * @return */ @RequestMapping({"/", ""}) public void index(HttpServletRequest request, HttpServletResponse response, String columnId) throws Exception { if (StringUtils.isBlank(columnId)) { log.error("请求中缺少columnId"); return; } DocumentContextUtil.Context context = DocumentContextUtil.getContext(columnId); List<String> imageTypes = context.getPicType().stream().map(item -> "." + item).collect(Collectors.toList()); List<String> fileTypes = context.getFileType().stream().map(item -> "." + item).collect(Collectors.toList()); String cmsDirectory = context.getCmsDirectory(); String uploadDirectory = context.getUploadDirectory(); EditorConf editConf = new EditorConf(context.getPicSize(), imageTypes, context.getFileSize(), fileTypes, cmsDirectory, uploadDirectory); response.getWriter().write(new ActionEnter(request, editConf).exec()); } }
上面代码是一个统一处理请求服务端地址,这里会通过ActionEnter来处理具体的请求
public ActionEnter(HttpServletRequest request, EditorConf conf) { this.request = request; this.actionType = request.getParameter("action"); this.configManager = ConfigManager.getInstance(conf); }
此处的请求中的action参数为编辑器对应操作的标识,与config.json中的xxxActionName对应。
这里我们有两种选择,1.直接复用这个config.json配置将son返回给前端 。 2.定义自己的配置信息通过json形式返回给前端。因为我们的配置在一般情况下不是固定的是动态的,需要根据某个参数去数据库查询得到,所以我们这里采用自定义配置方式
@Data public class EditorConf { private ImageConf imageConf; private ScrawlConf scrawlConf; private CatcherConf catcherConf; private VideoConf videoConf; private SnapScreenConf snapScreenConf; private FileConf fileConf; private AudioConf audioConf; private ImageManagerConf imageManagerConf; private FileManagerConf fileManagerConf; private String directory; private String uploadDirectory; public EditorConf(int imageSize, List<String> imageTypes, int fileSize, List<String> fileTypes, String directory, String uploadDirectory) { this.imageConf = new ImageConf(); this.imageConf.setImageMaxSize(imageSize * 1024); this.imageConf.setImageAllowFiles(imageTypes); this.scrawlConf = new ScrawlConf(); this.scrawlConf.setScrawlMaxSize(imageSize * 1024); this.catcherConf = new CatcherConf(); this.catcherConf.setCatcherMaxSize(imageSize * 1024); this.catcherConf.setCatcherAllowFiles(imageTypes); this.videoConf = new VideoConf(); this.videoConf.setVideoMaxSize(fileSize * 1024); this.snapScreenConf = new SnapScreenConf(); this.fileConf = new FileConf(); this.fileConf.setFileMaxSize(fileSize * 1024); this.fileConf.setFileAllowFiles(fileTypes); this.audioConf = new AudioConf(); this.imageManagerConf = new ImageManagerConf(); this.fileManagerConf = new FileManagerConf(); this.directory = directory; this.uploadDirectory = uploadDirectory; } public JSONObject getConfJson() { JSONObject json = new JSONObject(); json.putAll(getConfMap(this.imageConf)); json.putAll(getConfMap(this.scrawlConf)); json.putAll(getConfMap(this.catcherConf)); json.putAll(getConfMap(this.videoConf)); json.putAll(getConfMap(this.snapScreenConf)); json.putAll(getConfMap(this.audioConf)); json.putAll(getConfMap(this.imageManagerConf)); json.putAll(getConfMap(this.fileManagerConf)); return json; } private Map<String, Object> getConfMap(Object object) { return JSONObject.parseObject(JSONObject.toJSONString(object), new TypeReference<Map<String, Object>>() {{ }}); } /** * 上传图片配置项 */ @Data public static class ImageConf { /** * 执行上传图片的action名称 */ private String imageActionName = "uploadImage"; /** * 提交的图片表单名称 */ private String imageFieldName = "image"; /** * 上传大小限制,单位B */ private long imageMaxSize = 1024 * 1024; /** * 上传图片格式显示 */ private List<String> imageAllowFiles = Arrays.asList(".png", ".jpg", ".jpeg", ".gif", ".bmp"); /** * 是否压缩图片,默认是true */ private boolean imageCompressEnable = false; /** * 图片压缩最长边限制 */ private int imageCompressBorder = 1600; /** * 插入的图片浮动方式 */ private String imageInsertAlign = "none"; /** * 图片访问路径前缀 */ private String imageUrlPrefix = ""; /** * 上传保存路径,可以自定义保存路径和文件名格式 */ private String imagePathFormat = "/image/{yyyy}{mm}{dd}/{time}{rand:6}"; } /** * 涂鸦图片上传配置项 */ @Data public static class ScrawlConf { /** * 执行上传图片的action名称 */ private String scrawlActionName = "uploadScrawl"; /** * 提交的图片表单名称 */ private String scrawlFieldName = "scrawl"; /** * 上传大小限制,单位B */ private long scrawlMaxSize = 1024 * 1024; /** * 插入的图片浮动方式 */ private String scrawlInsertAlign = "none"; /** * 图片访问路径前缀 */ private String scrawlUrlPrefix = ""; /** * 上传保存路径,可以自定义保存路径和文件名格式 */ private String scrawlPathFormat = "/image/{yyyy}{mm}{dd}/{time}{rand:6}"; } /** * 截图工具上传 */ @Data public static class SnapScreenConf { /** * 执行上传图片的action名称 */ private String snapscreenActionName = "uploadImage"; /** * 插入的图片浮动方式 */ private String snapscreenInsertAlign = "none"; /** * 图片访问路径前缀 */ private String snapscreenUrlPrefix = ""; /** * 上传保存路径,可以自定义保存路径和文件名格式 */ private String snapscreenPathFormat = "/image/{yyyy}{mm}{dd}/{time}{rand:6}"; } /** * 抓取远程图片配置 */ @Data public static class CatcherConf { private List<String> catcherLocalDomain = Arrays.asList("127.0.0.1", "localhost", "img.baidu.com"); /** * 执行抓取远程图片的action名称 */ private String catcherActionName = "catchImage"; /** * 提交的图片列表表单名称 */ private String catcherFieldName = "source"; /** * 图片访问路径前缀 */ private String catcherUrlPrefix = ""; /** * 上传大小限制,单位B */ private long catcherMaxSize = 1024 * 1024; /** * 抓取图片格式显示 */ private List<String> catcherAllowFiles = Arrays.asList(".png", ".jpg", ".jpeg", ".gif", ".bmp"); /** * 上传保存路径,可以自定义保存路径和文件名格式 */ private String catcherPathFormat = "/image/{yyyy}{mm}{dd}/{time}{rand:6}"; } /** * 上传视频配置 */ @Data public static class VideoConf { /** * 执行上传视频的action名称 */ private String videoActionName = "uploadVideo"; /** * 提交的视频表单名称 */ private String videoFieldName = "video"; /** * 视频访问路径前缀 */ private String videoUrlPrefix = ""; /** * 上传大小限制,单位B,默认100MB */ private long videoMaxSize = 512 * 1024 * 1024 * 1024; /** * 上传视频格式显示 */ private List<String> videoAllowFiles = Arrays.asList(".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg", ".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid"); /** * 上传保存路径,可以自定义保存路径和文件名格式 */ private String videoPathFormat = "/video/{yyyy}{mm}{dd}/{time}{rand:6}"; } /** * 上传文件配置 */ @Data public static class FileConf { /** * 执行上传视频的action名称 */ private String fileActionName = "uploadFile"; /** * 提交的文件表单名称 */ private String fileFieldName = "file"; /** * 文件访问路径前缀 */ private String fileUrlPrefix = ""; /** * 上传大小限制,单位B,默认50MB */ private long fileMaxSize = 100 * 1024 * 1024 * 1024; /** * 上传文件格式显示 */ private List<String> fileAllowFiles = Arrays.asList(".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"); /** * 上传保存路径,可以自定义保存路径和文件名格式 */ private String filePathFormat = "/file/{yyyy}{mm}{dd}/{time}{rand:6}"; } /** * 上传音频配置 */ @Data public static class AudioConf { /** * 执行上传音频的action名称 */ private String audioActionName = "uploadAudio"; /** * 提交的文件表单名称 */ private String audioFieldName = "audio"; /** * 文件访问路径前缀 */ private String audioUrlPrefix = ""; /** * 上传大小限制,单位B,默认50MB */ private long audioMaxSize = 100 * 1024 * 1024 * 1024; /** * 上传音频格式显示 */ private List<String> audioAllowFiles = Arrays.asList(".mp3"); /** * 上传保存路径,可以自定义保存路径和文件名格式 */ private String audioPathFormat = "/file/{yyyy}{mm}{dd}/{time}{rand:6}"; } /** * 列出指定目录下的图片 */ @Data public static class ImageManagerConf { /** * 执行图片管理的action名称 */ private String imageManagerActionName = "listImage"; /** * 指定要列出图片的目录 */ private String imageManagerListPath = "{path}"; /** * 每次列出文件数量 */ private int imageManagerListSize = 20; /** * 图片访问路径前缀 */ private String imageManagerUrlPrefix = ""; /** * 插入的图片浮动方式 */ private String imageManagerInsertAlign = "none"; /** * 列出的文件类型 */ private List<String> imageManagerAllowFiles = Arrays.asList(".png", ".jpg", ".jpeg", ".gif", ".bmp"); } /** * 列出指定目录下的文件 */ @Data public static class FileManagerConf { /** * 执行文件管理的action名称 */ private String fileManagerActionName = "listFile"; /** * 指定要列出文件的目录 */ private String fileManagerListPath = "{path}"; /** * 文件访问路径前缀 */ private String fileManagerUrlPrefix = ""; /** * 每次列出文件数量 */ private int fileManagerListSize = 20; /** * 列出的文件类型 */ private List<String> fileManagerAllowFiles = Arrays.asList(".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转化为java的对象,这样我们就能给配置动态赋值并返回,比如项目中配置的上传文件目录、文件大小、文件类型等都可以在初始化配置的时候读去数据库配置进行赋值,当然需要将原来jsp下的代码做一些改造。
当准备好配置信息后进入ActionEnter进行具体的请求处理,编辑器后端处理的关键是ActionEnter中的invoke方法
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_AUDIO: 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(request, conf).capture(list); break; case ActionMap.LIST_IMAGE: conf = configManager.getConfig(actionCode); int pageNum = this.getStartIndex(); int pageSize = this.getSize(); String keyword = this.getKeyword(); int imageType = this.getImageType(); state = new ImageManager(conf).listFile(pageNum, pageSize, getSiteid(), keyword, imageType); break; case ActionMap.LIST_FILE: conf = configManager.getConfig(actionCode); int start = this.getStartIndex(); state = new FileManager(conf).listFile(start); break; }
我们将上传文件、拉取服务端图片/文件等不同的操作对应不同的处理类,下面是我们对上传文件处理类做改造后的代码
public class BinaryUploader { public static final State save(HttpServletRequest request, Map<String, Object> conf) { ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory()); boolean isAjaxUpload = request.getHeader("X_Requested_With") != null; if (isAjaxUpload) { upload.setHeaderEncoding("UTF-8"); } long maxSize = (long) conf.get("maxSize"); List<String> allowFiles = (List<String>) conf.get("allowFiles"); String fieldName = (String) conf.get("fieldName"); String uploadDirectory = (String) conf.get("uploadDirectory"); try { MultipartFile multipartFile = ((MultipartHttpServletRequest) request).getFile(fieldName); if (multipartFile == null) { return new BaseState(false, AppInfo.NOTFOUND_UPLOAD_DATA); } // 用来表示是不是编辑器中上传的图片 String orgName = multipartFile.getOriginalFilename(); String extName = FileUtil.getExtName(orgName); if (!FileUtil.allowFileType(extName, allowFiles)) { return new BaseState(false, AppInfo.NOT_ALLOW_FILE_TYPE); } InputStream inputStream = multipartFile.getInputStream(); int size = inputStream.available(); if (size > maxSize) { return new BaseState(false, AppInfo.MAX_SIZE); } //保存文件 String fileName = UUIDUtil.getUUID() + "." + extName; String targetFile = FileUtil.formatPath(uploadDirectory, DateUtil.formatDate(new Date(), DateUtil.YMD_), fileName); String physicalPath = DocumentContextUtil.formatPhysicalPath(targetFile); FileUtil.writeStreamToFile(physicalPath, inputStream); //记录 //获取图片信息 Pair<Integer, Integer> widthHeight = ImageUtil.getImageWidthHeight(physicalPath); State state = new BaseState(true); state.putInfo("width", widthHeight.getLeft()); state.putInfo("height", widthHeight.getRight()); state.putInfo("size", size); state.putInfo("title", fileName); state.putInfo("url", targetFile); state.putInfo("type", extName); return state; } catch (IOException e) { log.error("处理文件失败:{}", e.getMessage(), e); return new BaseState(false, AppInfo.PARSE_REQUEST_ERROR); } catch (Exception e) { log.error("处理文件失败:{}", e.getMessage(), e); } return new BaseState(false, AppInfo.IO_ERROR); } }
这里的参数conf就是上面自定义的配置对象转为map结构
4.前端代码:根据官网demo案例编写自己的编辑器初始化代码,需要吧后端服务地址修改为我们自己的服务地址,至此就能实现集成ueditor并实现配置信息动态获取
editorConfig: { UEDITOR_HOME_URL: '/editor/', serverUrl: 项目地址 + '/editor?columnId=' + this.columnId } this.editor = UE.getEditor(this.editorId, this.editorConfig); // 初始化UE

浙公网安备 33010602011771号