前言
最近项目用到了百度的富文本编辑器UEditor,功能还是挺强大的,主要看中了它的图片和文档上传功能,不过它的后台适配的程序还是比较老旧的jsp模式,且文档写:
UEditor 所提供的所有后端代码都仅为 DEMO 作用
实现
关于如何在前端整合(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"
] /* 列出的文件类型 */
}
文章评论
buy cialis online prescription Esanip Valtrex Discount Program receptor Cellular or nuclear protein that binds to a hormone so that a response can be elicited. Zexkxc cialis on sale in usa The answer here is probably again within the scope of our existing theories of matter. Cialis Iimfsx