[Java]Spring下富文本编辑器UEditor的后端实现

2021-03-02 27点热度 0人点赞 0条评论

前言

最近项目用到了百度的富文本编辑器UEditor,功能还是挺强大的,主要看中了它的图片和文档上传功能,不过它的后台适配的程序还是比较老旧的jsp模式,且文档写:

警告

UEditor 所提供的所有后端代码都仅为 DEMO 作用

现在项目中使用了Spring的MVC,想用它适配,经过一番改造,实现了在Spring下的后端对接UEditor程序(只针对图片和文档的上传/列表这部分对接功能),其他功能照着API略加修改就好了。

实现

关于如何在前端整合(vue/iView中整合富文本编辑器ueditor并解决插入表格行列错位问题),请查看:https://blog.terrynow.com/2021/03/02/java-spring-ueditor-controller-implement/

新建UEditorController.java,可以修改下ue后端对接的路径,前端ueditor的serverUrl也修改成对应的即可。

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.Stream;

@RestController
@RequestMapping("/ue")
public class UEditorController extends BaseController {
    private static final String CONFIG = "config";
    private static final String UPLOAD_IMAGE = "uploadimage";
    private static final String UPLOAD_SCRAWL = "uploadscrawl";
    private static final String UPLOAD_VIDEO = "uploadvideo";
    private static final String UPLOAD_FILE = "uploadfile";
    private static final String CATCH_IMAGE = "catchimage";
    private static final String LIST_FILE = "listfile";
    private static final String LIST_IMAGE = "listimage";

    // resource下的ueditor.config.json,见下面
    @Value(value = "classpath:/ueditor.config.json")
    private org.springframework.core.io.Resource ueditorConfigJson;

    @Autowired
    private Config config;

    /**
     * TODO impl it: http://fex.baidu.com/ueditor/#dev-request_specification
     * 文档设置: http://fex.baidu.com/ueditor/#start-config
     */
    @RequestMapping
    public @ResponseBody
    JSONObject ueditor(@RequestParam("action") String action,
                       @RequestParam(value = "start", defaultValue = "0") long start,
                       @RequestParam(value = "size", defaultValue = "10") long size,
                       @RequestParam(value = "source", defaultValue = "") String source,
                       MultipartFile upfile/*, @RequestBody(required = false) String content*/) throws IOException {
        // TODO 可以根据配置文件,来设置ueditor的上传文件的存放路径
        File baseDir = new File("xxx");
        if (!baseDir.exists()) {
            baseDir.mkdirs();
        }
        JSONObject ueditorConfig = readConfig();
        switch (action) {
            case CONFIG:
                return ueditorConfig;
            case UPLOAD_IMAGE:
            case UPLOAD_VIDEO:
            case UPLOAD_FILE:
                String fileName = upfile == null ? null : upfile.getOriginalFilename();
                if (StringUtils.isEmpty(fileName)) {
                    return new JSONObject().put("state", "上传文件错误");
                }
                int lastDot = fileName.lastIndexOf(".");
                if (lastDot < 1) {
                    return new JSONObject().put("state", "上传文件错误");
                }
                String suffix = fileName.substring(lastDot).toLowerCase(Locale.ROOT);

                if (UPLOAD_IMAGE.equals(action)) {
                    List<String> allowedImageExts = allowedExts(ueditorConfig, "imageAllowFiles");
                    if (!allowedImageExts.contains(suffix)) {
                        return new JSONObject().put("state", "该文件不允许上传: " + fileName);
                    }
                } else if (UPLOAD_FILE.equals(action)) {
                    List<String> allowedFileExts = allowedExts(ueditorConfig, "fileAllowFiles");
                    if (!allowedFileExts.contains(suffix)) {
                        return new JSONObject().put("state", "该文件不允许上传: " + fileName);
                    }
                } else {//最后一个肯定是Video
                    List<String> allowedVideoExts = allowedExts(ueditorConfig, "videoAllowFiles");
                    if (!allowedVideoExts.contains(suffix)) {
                        return new JSONObject().put("state", "该文件不允许上传: " + fileName);
                    }
                }

                FileUtils.copyInputStreamToFile(upfile.getInputStream(), new File(baseDir, fileName));
                return new JSONObject().put("state", "SUCCESS").put("original", fileName)
                        .put("size", upfile.getSize()).put("title", fileName)
                        .put("type", suffix).put("url", "/ue/" + action.replace("upload", "") + "/" + fileName);
            case LIST_IMAGE:
                List<String> allowedImageExts = allowedExts(ueditorConfig, "imageAllowFiles");
                JSONArray imagesArray = new JSONArray();
                try (Stream<Path> paths = Files.list(baseDir.toPath())) {
                    paths.filter(p -> allowedImageExts.contains("." + FilenameUtils.getExtension(p.getFileName().toString())))
                            .skip(start).limit(size)
                            .forEach(p -> imagesArray.put(new JSONObject().put("url", "/ue/image/" + p.getFileName().toString())));
                }
                long imagesTotal = start + size + 1; //不实际清点全部的文件,简单的+1
                if (size > imagesArray.length()) { //如果返回的实际文件数少于size,那么文件数就是start + imagesArray.length()
                    imagesTotal = start + imagesArray.length();
                }
                return new JSONObject().put("list", imagesArray).put("state", "SUCCESS").put("start", start).put("total", imagesTotal);
            case LIST_FILE:
                List<String> allowedFileExts = allowedExts(ueditorConfig, "fileAllowFiles");
                JSONArray filesArray = new JSONArray();
                try (Stream<Path> paths = Files.list(baseDir.toPath())) {
                    paths.filter(p -> allowedFileExts.contains("." + FilenameUtils.getExtension(p.getFileName().toString())))
                            .skip(start).limit(size)
                            .forEach(p -> filesArray.put(new JSONObject().put("url", "/ue/file/" + p.getFileName().toString())));
                }
                long filesTotal = start + size + 1; //不实际清点全部的文件,简单的+1
                if (size > filesArray.length()) { //如果返回的实际文件数少于size,那么文件数就是start + imagesArray.length()
                    filesTotal = start + filesArray.length();
                }
                return new JSONObject().put("list", filesArray).put("state", "SUCCESS").put("start", start).put("total", filesTotal);
            case UPLOAD_SCRAWL:
//                if(StringUtils.isEmpty(content)) {
//                    return new JSONObject().put("state", "上传涂鸦错误");
//                }
//                int scrawlIndex = content.indexOf("upfile=");
//                if(scrawlIndex>0){
//                    String scrawlBase64 = content.substring(scrawlIndex+7);
//                    File scrawlFileName = new File(baseDir, System.currentTimeMillis()+".jpg");
//                    FileUtils.writeByteArrayToFile(scrawlFileName, Base64.decodeBase64(scrawlBase64));
//                    return new JSONObject().put("state", "SUCCESS").put("original", scrawlFileName.getName())
//                            .put("title", scrawlFileName.getName())
//                            .put("url", "/ue/image/" + scrawlFileName.getName());
//                }
            case CATCH_IMAGE:
                // 根据source解析出Image的URL,然后把Image图片抓取存放,然后按照格式返回
                //GET {
                //    "action": "catchimage",
                //     "source": [
                //     	"http://a.com/1.jpg",
                //        "http://a.com/2.jpg"
                //    ]
                //}
//                返回:
//            {
//                "state": "SUCCESS",
//                    "list": [{
//                "url": "upload/1.jpg",
//                        "source": "http://b.com/2.jpg",
//                        "state": "SUCCESS"
//            }, {
//                "url": "upload/2.jpg",
//                        "source": "http://b.com/2.jpg",
//                        "state": "SUCCESS"
//            }, ]
//            }
            default:
                return new JSONObject().put("state", "尚未实现");
//                throw new IllegalStateException("Request api:[/ue] param:[action] mismatching");
        }
    }

    /**
     * 读取图片
     */
//    @RequestMapping(value = "/image/{name}.{ext}", method = RequestMethod.GET, headers = "Accept=image/*", produces = "image/jpg")
    @GetMapping("/image/{name}.{ext}")
    public @ResponseBody
    BufferedImage image(@PathVariable String name, @PathVariable String ext) throws Exception {
        String fileName = name + "." + ext;
        // TODO 可以根据配置文件,来设置ueditor的上传文件的存放路径
        File baseDir = new File("xxx");
        File imageFile = new File(baseDir, fileName);
        if (!imageFile.exists()) { // 文件不存在
            throw new HttpClientErrorException(HttpStatus.NOT_FOUND);
        }
        return ImageIO.read(imageFile);
//        this.readImage(name + "." + ext, response);
    }

    @RequestMapping(value = "/file/{name}.{ext}", method = RequestMethod.GET)
    public ResponseEntity<ByteArrayResource> file(
            @PathVariable("name") String name, @PathVariable("ext") String ext) throws Exception {
        String fileName = name + "." + ext;
        // TODO 可以根据配置文件,来设置ueditor的上传文件的存放路径
        File baseDir = new File("xxx");
        File file = new File(baseDir, fileName);
        if (!file.exists()) { // 文件不存在
            throw new HttpClientErrorException(HttpStatus.NOT_FOUND);
        }

        Path path = Paths.get(file.getAbsolutePath());
        ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path));

        //解决输出中文乱码问题(如果fileName有中文的话)
        String percentEncodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
                .replaceAll("\\+", "%20");

        HttpHeaders headers = new HttpHeaders();
        String contentDispositionValue = "attachment; filename=" +
                percentEncodedFileName + "; filename*=utf-8''" + percentEncodedFileName;
        headers.add(HttpHeaders.CONTENT_DISPOSITION, contentDispositionValue);
        return ResponseEntity.ok()
                .headers(headers)
                .contentLength(file.length())
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(resource);
    }

    /**
     * 读取配置信息
     */
    private JSONObject readConfig() throws IOException {
        StringBuilder sb = new StringBuilder();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(ueditorConfigJson.getInputStream()));
        while (bufferedReader.ready()) {
            sb.append(bufferedReader.readLine());
        }
        // 字符串过滤(过滤注释信息、空格)
        String config = sb.toString().replaceAll("/\\*[\\s\\S]*?\\*/", "").replace(" ", "");
        return new JSONObject(config);
    }

    private List<String> allowedExts(JSONObject jsonObject, String key) {
        JSONArray jsonArray = jsonObject.getJSONArray(key);
        List<String> allowedExts = new ArrayList<>();
        for (int i = 0; i < jsonArray.length(); i++) {
            allowedExts.add(jsonArray.getString(i));
        }
        return allowedExts;
    }

}

以上代码

FileUtils.copyInputStreamToFile

用到 apache commons.io maven引入(如果不需要,可以用原生的文件相关API代替):

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-io</artifactId>
    <version>3.9</version>
</dependency>

关于介绍到的Spring提供文件下载,并解决中文乱码的相关详细解析,请参考:https://blog.terrynow.com/2021/03/01/java-springboot-offer-file-download-include-chinese-file-name/

关于Spring中,ResponseBody直接返回JsonObject 就能输出JsonString的详细解析,请参考:https://blog.terrynow.com/2021/02/26/spring-boot-controller-responsebody-object-to-json-string/

ueditor.config.json文件

/* 前后端通信相关的配置,注释只允许使用多行方式 */
{
    /* 上传图片配置项 */
    "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"
    ] /* 列出的文件类型 */

}

 

admin

这个人很懒,什么都没留下

文章评论

*

code