springboot + extjs6 +kindeditor富文本编辑器整合+ 上传图片解决前后端分离跨域问题步骤
最近在项目中遇到使用富文本编辑器的情况,尝试使用ueditor、wangeditor都以上传图片跨域失败告终,只有kindeditor成功。
基本环境:springboot、extjs6、前后端分离(前端网址:http://localhost:1841 后端网址:http://localhost:9191)
现对整过程介绍如下:
一、Extjs 整合kindeditor
1.引入js
<script type="text/javascript" charset="utf-8" src="resources/js/kindeditor/kindeditor-all.js"></script>
<script type="text/javascript" charset="utf-8" src="resources/js/kindeditor/lang/zh-CN.js"> </script>
2.在extjs 中添加kindeditor控件KindEditor.js:
文件放在extjs6路径如下:ext\packages\ux\classic\src\KindEditor.js
Ext.define('Ext.ux.KindEditor', {
extend: 'Ext.form.field.TextArea',
alias: 'widget.kindeditor',//xtype名称
initComponent: function () {
this.html = "<textarea id='" + this.getId() + "-input' name='" + this.name + "'></textarea>";
this.callParent(arguments);
var callback = "http://localhost:1841/redirect.html";
this.on("boxready", function (t) {
this.inputEL = Ext.get(this.getId() + "-input");
this.editor = KindEditor.create('textarea[name="' + this.name + '"]', {
height: t.getHeight()-18,//有底边高度,需要减去
width: t.getWidth() - t.labelWidth,//宽度需要减去label的宽度
basePath: '/resources/js/kindeditor/',
uploadJson: 'http://localhost:9191/KindEditorUpmethod?callBackPath='+callback,//路径自己改一下
uploadJsonMultiimage:'http://localhost:9191/KindEditorUpmethodMulti?callBackPath='+callback,//路径自己改一下
//fileManagerJson: '/Content/Plugin/kindeditor-4.1.5/asp.net/file_manager_json.ashx',//这里可以多设一个多图片上传路径
resizeType: 0,
wellFormatMode: true,
//newlineTag: 'br',
//allowFileManager: true,
allowPreviewEmoticons: true,
allowImageUpload: true,
items: [
'source', '|', 'undo', 'redo', '|', 'justifyleft', 'justifycenter', 'justifyright',
'justifyfull', 'insertorderedlist', 'insertunorderedlist', '|',
'formatblock', 'fontname', 'fontsize', '|', 'forecolor', 'bold',
'italic', 'underline', 'lineheight', '|', 'image', 'multiimage',
'table', 'emoticons',
'link', 'unlink', 'fullscreen'
]
});
});
this.on("resize", function (t, w, h) {
this.editor.resize(w - t.labelWidth, h-18);
});
},
setValue: function (value) {
if (this.editor) {
this.editor.html(value);
}
},
reset: function () {
if (this.editor) {
this.editor.html('');
}
},
setRawValue: function (value) {
if (this.editor) {
this.editor.text(value);
}
},
getValue: function () {
if (this.editor) {
return this.editor.html();
} else {
return ''
}
},
getRawValue: function () {
if (this.editor) {
return this.editor.text();
} else {
return ''
}
}
});
3.在ext的编辑窗口中调用:
{
bind: {
fieldLabel: '{i18n.content1}'
},
name: "content",
// id: "content",
xtype: 'kindeditor',
ref:'content',
//anchor: '-20',
margin: '0 5 5 5',
id:'noticecontent',
height: 250,
width: 400
}
4.ext的controller中保存:
saveNotice:function(button){
//新增点击保存时触发
var me=this;
var win=button.up('window');
var form=win.down('form').getForm();
// var vendorValue=form.findField('vendor').getRawValue();
// form.findField('vendor').setValue(vendorValue);
var au= win.down('hiddenfield[name=au]').getValue();
var Url='';
if(au=='a')
{
Url='sys/notice/saveNotice';
}
else
{
Url='sys/notice/updateNotice';
}
var objvalues=form.getValues();
objvalues.content=form.findField('content').getValue(); //重新给content赋值,默认用form.getValues(),不包含html格式,需单独再赋一遍值,下次编辑时html格式才能正常显示
if (form.isValid()) {
Ext.Ajax.request({
url:Url,
params: objvalues,
method:'POST',
success: function(response){
var o = Ext.decode(response.responseText.toString());
if (o.success == true) {
var main=Ext.ComponentQuery.query('noticelist',null,true);
main.getViewModel().getStore("noticeStore").load();
win.close();
}else{
Ext.Msg.alert("警告", o.message);
}
}
});
}
}
注意:
var objvalues=form.getValues();
objvalues.content=form.findField('content').getValue(); //重新给content赋值,默认用form.getValues(),不包含html格式,需单独再赋一遍值,下次编辑时html格式才能正常显示
Extjs端代码基本完成。
5.创建redirect.html 一定要要和这个页面平级,redirect.html的作用是解决frame域的问题 。
借鉴网址:https://blog.csdn.net/weixin_38658548/article/details/105288953
这个页面不要操作直接粘贴就行了
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>ie </title>
<script type="text/javascript">
function getParameter(val) {
var uri = decodeURI(window.location.search);
var re = new RegExp("" + val + "=([^&?]*)", "ig");
return ((uri.match(re)) ? (uri.match(re)[0].substr(val.length + 1)) : null);
}
var upload_callback = function() {
var error = getParameter("error");
error = parseInt(error)
var dataObject;
if(error==0){
var url = getParameter("url");
dataObject = {"error": error, "url": url};
}else{
var message = getParameter("message");
dataObject = {"error": error, "message": message};
}
var data = JSON.stringify(dataObject)
document.getElementsByTagName("body")[0].innerHTML = '<pre>' + data + '</pre>';
}
</script>
</head>
<body onload="upload_callback();">
</body>
</html>
放到前端的根目录:http://localhost:1841/redirect.html
二、后台部分
1.mavaen依赖
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1</version>
</dependency>
2.路径处理:
遇到问题参考文章:https://blog.csdn.net/drose29/article/details/89351919
做如下处理:
主要来处理后端程序(localhost:9191),允许前端extjs框架(localhost:1841)来访问上传的图片路径()。设置静态资源路径:
好像springboot默认以static文件夹为默认路径,但还是要在配置文件中设置一下:
filePath=D:\\workspace\\xianshangkuaidi\\api\\express-web\\src\\main\\resources\\static
spring.mvc.static-path-pattern=/**
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,file:${filePath}
访问路径:http://localhost:9191/static
2.controller部分:
package com.gc.express.module.common.controller;
import com.alibaba.fastjson.JSONObject;
import org.apache.tomcat.util.http.fileupload.FileItem;
import org.apache.tomcat.util.http.fileupload.FileItemFactory;
import org.apache.tomcat.util.http.fileupload.FileUploadException;
import org.apache.tomcat.util.http.fileupload.RequestContext;
import org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by ldb on 2017/4/9.
*/
@Controller
@RestController
@CrossOrigin
public class KindEditorController {
@ResponseBody
@CrossOrigin(allowCredentials = "true")
@RequestMapping("/KindEditorUpmethod")
public JSONObject uploadMethod(@RequestParam String callBackPath, @RequestParam(value = "imgFile", required = false) MultipartFile file, HttpServletRequest request, HttpServletResponse response) throws FileUploadException, IOException {
String scheme = request.getScheme();//http
String serverName = request.getServerName();//localhost
// int serverPort = request.getServerPort();//8080
// String contextPath = request.getContextPath();//项目名
//String url = scheme + "://" + serverName + ":" + serverPort + contextPath;//http://127.0.0.1:8080/test
String url = scheme + "://" + serverName + ":9191";//http://127.0.0.1:8080
String referer = request.getHeader("referer");
Pattern p = Pattern.compile("([a-z]*:(//[^/?#]+/[^/?#]*/)?)", Pattern.CASE_INSENSITIVE);
Matcher mathcer = p.matcher(referer);
JSONObject msgMap = new JSONObject();
if (mathcer.find()) {
String htmlheader = mathcer.group();// 请求来源
response.setContentType("text/html;charset=UTF-8");
String savePath = "D://workspace/xianshangkuaidi/api/express-web/src/main/resources/static/";
String saveUrl = request.getContextPath();
//定义允许上传的文件扩展名
HashMap<String, String> extMap = new HashMap<String, String>();
extMap.put("image", "gif,jpg,jpeg,png,bmp");
extMap.put("flash", "swf,flv");
extMap.put("media", "swf,flv,mp3,wav,wma,wmv,mid,avi,mpg,asf,rm,rmvb");
extMap.put("file", "doc,docx,xls,xlsx,ppt,htm,html,txt,zip,rar,gz,bz2");
//最大文件大小
long maxSize = 100000000;
response.setContentType("text/html; charset=UTF-8");
if (!ServletFileUpload.isMultipartContent(request)) {
System.out.println("请选择文件。");
response.sendRedirect(getError(htmlheader, "请选择文件.", callBackPath));
return null;
}
//检查目录
File uploadDir = new File(savePath);
if (!uploadDir.isDirectory()) {
System.out.println("上传目录不存在。");
response.sendRedirect(getError(htmlheader, "上传目录不存在。", callBackPath));
return null;
}
//检查目录写权限
if (!uploadDir.canWrite()) {
System.out.println("上传目录没有写权限。");
response.sendRedirect(getError(htmlheader, "上传目录没有写权限。", callBackPath));
return null;
}
String dirName = request.getParameter("dir");
if (dirName == null) {
dirName = "image";
}
if (!extMap.containsKey(dirName)) {
System.out.println("目录名不正确。");
response.sendRedirect(getError(htmlheader, "目录名不正确。", callBackPath));
return null;
}
//创建文件夹
savePath += dirName + "/";
saveUrl += dirName + "/";
File saveDirFile = new File(savePath);
if (!saveDirFile.exists()) {
saveDirFile.mkdirs();
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String ymd = sdf.format(new Date());
savePath += ymd + "/";
saveUrl += ymd + "/";
File dirFile = new File(savePath);
if (!dirFile.exists()) {
dirFile.mkdirs();
}
String fileName = file.getOriginalFilename();
long fileSize = file.getSize();
// if (!item.isFormField()) {
//检查文件大小
if (file.getSize() > maxSize) {
System.out.println("上传文件大小超过限制。");
response.sendRedirect(getError(htmlheader, "上传文件大小超过限制。", callBackPath));
return null;
}
//检查扩展名
String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
if (!Arrays.<String>asList(extMap.get(dirName).split(",")).contains(fileExt)) {
System.out.println("上传文件扩展名是不允许的扩展名。\n只允许" + extMap.get(dirName) + "格式。");
response.sendRedirect(getError(htmlheader, "上传文件扩展名是不允许的扩展名。\n只允许" + extMap.get(dirName) + "格式。", callBackPath));
return null;
}
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
String newFileName = df.format(new Date()) + "_" + new Random().nextInt(1000) + "." + fileExt;
try {
File uploadedFile = new File(savePath, newFileName);
OutputStream os = new FileOutputStream(uploadedFile);
InputStream inputStream = file.getInputStream();
byte[] buf = new byte[1024];
int length = 0;
while ((length = inputStream.read(buf)) != -1) {
os.write(buf, 0, length);
}
inputStream.close();
os.close();
} catch (Exception e) {
System.out.println("上传文件失败。");
response.sendRedirect(getError(htmlheader, "上传文件失败。", callBackPath));
return null;
}
//Map<String, Object> msgMap = new HashMap<String, Object>();
//遍历选择的图片
msgMap.put("error", 0);
// msgMap.put("url", "");
msgMap.put("message", "");
String urlString = "";
//根据自己实际情况做修改
//urlString = "http://localhost:63342/Beginner_admin/" + callBackPath + "?error=0&url=" + "http://192.168.2.158:8080/file/" + saveUrl + newFileName;
//urlString = htmlheader + callBackPath + "?error=0&url=" + url+"/file/" + saveUrl + newFileName;
urlString = callBackPath + "?error=0&url=" + url + "/" + saveUrl + newFileName;
System.out.println(urlString);
// response.sendRedirect(urlString);
msgMap.put("url", url + "/" + saveUrl + newFileName);
if (request.getParameter("single") != null && request.getParameter("single") != "" && "y".equals(request.getParameter("single"))) {
//request.getParameter("single") single :y 是在KindEditor 源码中个给单个图片上传添加的标志, 批量上传没有添加。
//单个图片上传,需要跳转网址,用来支持跨域
response.sendRedirect(urlString);
} else {
//多个图片上传,没有跳转网址,直接返回JSONObject数据,就可完成上传
return msgMap;
}
}
return null;
}
private String getError(String htmluel, String message, String callBackPath) throws UnsupportedEncodingException {
Map<String, Object> msg = new HashMap<String, Object>();
msg.put("error", 1);
msg.put("message", message);
String urlString = htmluel + callBackPath + "?error=1&message=" + URLEncoder.encode(message, "UTF-8");
return urlString;
}
}
注意:
这段代码中增加了一段代码,用来区分是单个图片上传还是多个图片上传,single 参数是我自己在kindeditor-all.js源代码中增加的,用来区分单图和多图,多图没有添加参数。
if (request.getParameter("single") != null && request.getParameter("single") != "" && "y".equals(request.getParameter("single"))) {
//request.getParameter("single") single :y 是在KindEditor 源码中个给单个图片上传添加的标志, 批量上传没有添加。
//单个图片上传,需要跳转网址,用来支持跨域
response.sendRedirect(urlString);
} else {
//多个图片上传,没有跳转网址,直接返回JSONObject数据,就可完成上传
return msgMap;
}
这里很奇怪, 单个图片上传,需要跳转网址,用来支持跨域,多个图片上传,不需要跳转网址跨域,直接返回JSONObject数据,就可完成上传,困扰了很长时间。
kindeditor-all.js修改如下:
var html = [
'<div style="padding:20px;">',
'<div class="tabs"></div>',
'<div class="tab1" style="display:none;">',
'<div class="ke-dialog-row">',
'<label for="remoteUrl" style="width:60px;">' + lang.remoteUrl + '</label>',
'<input type="text" id="remoteUrl" class="ke-input-text" name="url" value="" style="width:200px;" /> ',
'<span class="ke-button-common ke-button-outer">',
'<input type="button" class="ke-button-common ke-button" name="viewServer" value="' + lang.viewServer + '" />',
'</span>',
'</div>',
'<div class="ke-dialog-row">',
'<label for="remoteWidth" style="width:60px;">' + lang.size + '</label>',
lang.width + ' <input type="text" id="remoteWidth" class="ke-input-text ke-input-number" name="width" value="" maxlength="4" /> ',
lang.height + ' <input type="text" class="ke-input-text ke-input-number" name="height" value="" maxlength="4" /> ',
'<img class="ke-refresh-btn" src="' + imgPath + 'refresh.png" width="16" height="16" alt="" style="cursor:pointer;" title="' + lang.resetSize + '" />',
'</div>',
'<div class="ke-dialog-row">',
'<label style="width:60px;">' + lang.align + '</label>',
'<input type="radio" name="align" class="ke-inline-block" value="" checked="checked" /> <img name="defaultImg" src="' + imgPath + 'align_top.gif" width="23" height="25" alt="" />',
' <input type="radio" name="align" class="ke-inline-block" value="left" /> <img name="leftImg" src="' + imgPath + 'align_left.gif" width="23" height="25" alt="" />',
' <input type="radio" name="align" class="ke-inline-block" value="right" /> <img name="rightImg" src="' + imgPath + 'align_right.gif" width="23" height="25" alt="" />',
'</div>',
'<div class="ke-dialog-row">',
'<label for="remoteTitle" style="width:60px;">' + lang.imgTitle + '</label>',
'<input type="text" id="remoteTitle" class="ke-input-text" name="title" value="" style="width:200px;" />',
'</div>',
'</div>',
'<div class="tab2" style="display:none;">',
'<iframe name="' + target + '" style="display:none;"></iframe>',
'<form class="ke-upload-area ke-form" method="post" enctype="multipart/form-data" target="' + target + '" action="' + K.addParam(uploadJson, 'dir=image&single=y') + '">',
'<div class="ke-dialog-row">',
hiddenElements.join(''),
'<label style="width:60px;">' + lang.localUrl + '</label>',
'<input type="text" name="localUrl" class="ke-input-text" tabindex="-1" style="width:200px;" readonly="true" /> ',
'<input type="button" class="ke-upload-button" value="' + lang.upload + '" />',
'</div>',
'</form>',
'</div>',
'</div>'
].join('');
以上修改即完成。
还有一种方法单图和多图在配置时使用不同的url,这里只做备选项:
kindeditor-all.js修改如下:
后台controller中也要相应修改,单图一个方法,多图一个方法。
到此为止,所有步骤基本完成。
三、测试
因为前后台分离,跨域问题几经周折,终于完成。