1、需求
使用百度的富文本编辑器,发送附件上传功能不太好使,点击附件上传的时候,报错【后端配置项没有正确加载,上传功能不能正常使用】,通过检查,发现是前端发送加载配置的请求,不能得到正确的配置请求结果导致的。官方提供的后端程序的版本也是比较老的,使用的还是jsp技术,因此,对ueditor的后端程序进行重构,来修复ueditor的上传附件功能。
2、接口分析
重构的话,实际上就是重写这些接口,重写之前,需要捋清楚,都有哪些接口,奉上前端程序的请求源码:
setTimeout(function(){
try{
me.options.imageUrl && me.setOpt('serverUrl', me.options.imageUrl.replace(/^(.*[\/]).+([\.].+)$/, '$1controller$2'));
var configUrl = me.getActionUrl('config'),
isJsonp = utils.isCrossDomainUrl(configUrl);
/* 发出ajax请求 */
me._serverConfigLoaded = false;
configUrl && UE.ajax.request(configUrl,{
'method': 'GET',
'dataType': isJsonp ? 'jsonp':'',
'onsuccess':function(r){
try {
var config = isJsonp ? r:eval("("+r.responseText+")");
utils.extend(me.options, config);
me.fireEvent('serverConfigLoaded');
me._serverConfigLoaded = true;
} catch (e) {
showErrorMsg(me.getLang('loadconfigFormatError'));
}
},
'onerror':function(){
showErrorMsg(me.getLang('loadconfigHttpError'));
}
});
} catch(e){
showErrorMsg(me.getLang('loadconfigError'));
}
});
在上面获取serverUrl的配置文件所在的是ueditor.config.js,配置的内容如下:
服务器统一请求接口路径
serverUrl: "/cms/UEditor/action"
完成上面的配置,页面初始化的时候,就会请求上面配置的服务端地址了,与在服务端定义好对应的controller。
@Controller
@RequestMapping("/cms/UEditor")
public class UEditorController extends BaseController {
/**
* ueditor资源路径
*/
@Resource
private IUEditorService iuEditorService;
/**
* ueditor action
* 调用地址: /cms/UEditor/action
*/
@RequestMapping(value = "/action")
@ResponseBody
public String action(UEditorModel uEditorModel) {
final String action = uEditorModel.getAction();
if (UEditorAction.CONFIG.getActionCode().equals(action)) {
return iuEditorService.loadConfig();
} else if (UEditorAction.UPLOAD_FILE.getActionCode().equals(action)) {
return iuEditorService.saveFile(uEditorModel.getUpfile());
}
return null;
}
}
当前的方法的返回值是String类型,看源码之后,发现,其返回值实际上是json对象转的字符串,所以这个controller的方法中,需要对请求参数进行判断,根据参数返回不通的对象转的json,参数类型与返回的对象类型的对应关系,当时不是我猜的,我是根据源码找的;这里奉上参数类型与返回对象类型对应关系的源码,这个源码出自ueditor的jsp版本的服务端源码。
public String invoke() {
if (this.actionType != null && ActionMap.mapping.containsKey(this.actionType)) {
if (this.configManager != null && this.configManager.valid()) {
State state = null;
int actionCode = ActionMap.getType(this.actionType);
Map<String, Object> conf = null;
switch(actionCode) {
case 0:
return this.configManager.getAllConfig().toString();
case 1:
case 2:
case 3:
case 4:
conf = this.configManager.getConfig(actionCode);
state = (new Uploader(this.request, conf)).doExec();
break;
case 5:
conf = this.configManager.getConfig(actionCode);
String[] list = this.request.getParameterValues((String)conf.get("fieldName"));
state = (new ImageHunter(conf)).capture(list);
break;
case 6:
case 7:
conf = this.configManager.getConfig(actionCode);
int start = this.getStartIndex();
state = (new FileManager(conf)).listFile(start);
}
return state.toJSONString();
} else {
return (new BaseState(false, 102)).toJSONString();
}
} else {
return (new BaseState(false, 101)).toJSONString();
}
}
public final class ActionMap {
public static final Map<String, Integer> mapping = new HashMap<String, Integer>() {
{
this.put("config", 0);
this.put("uploadimage", 1);
this.put("uploadscrawl", 2);
this.put("uploadvideo", 3);
this.put("uploadfile", 4);
this.put("catchimage", 5);
this.put("listfile", 6);
this.put("listimage", 7);
}
};
public static final int CONFIG = 0;
public static final int UPLOAD_IMAGE = 1;
public static final int UPLOAD_SCRAWL = 2;
public static final int UPLOAD_VIDEO = 3;
public static final int UPLOAD_FILE = 4;
public static final int CATCH_IMAGE = 5;
public static final int LIST_FILE = 6;
public static final int LIST_IMAGE = 7;
public ActionMap() {
}
public static int getType(String key) {
return (Integer)mapping.get(key);
}
}
通过上面的源码,知道他是判读了下参数,当传入的参数是config的时候,直接返回后端配置文件内容(config.json),否则的话,返回一个叫State生产的对象或其子类。
找到给接口的其中一个实现,如下:
public class MultiState implements State {
private boolean state = false;
private String info = null;
private Map<String, Long> intMap = new HashMap();
private Map<String, String> infoMap = new HashMap();
private List<String> stateList = new ArrayList();
public MultiState(boolean state) {
this.state = state;
}
public MultiState(boolean state, String info) {
this.state = state;
this.info = info;
}
public MultiState(boolean state, int infoKey) {
this.state = state;
this.info = AppInfo.getStateInfo(infoKey);
}
public boolean isSuccess() {
return this.state;
}
public void addState(State state) {
this.stateList.add(state.toJSONString());
}
public void putInfo(String name, String val) {
this.infoMap.put(name, val);
}
public String toJSONString() {
String stateVal = this.isSuccess() ? AppInfo.getStateInfo(0) : this.info;
StringBuilder builder = new StringBuilder();
builder.append("{\"state\": \"" + stateVal + "\"");
Iterator iterator = this.intMap.keySet().iterator();
while(iterator.hasNext()) {
stateVal = (String)iterator.next();
builder.append(",\"" + stateVal + "\": " + this.intMap.get(stateVal));
}
iterator = this.infoMap.keySet().iterator();
while(iterator.hasNext()) {
stateVal = (String)iterator.next();
builder.append(",\"" + stateVal + "\": \"" + (String)this.infoMap.get(stateVal) + "\"");
}
builder.append(", list: [");
iterator = this.stateList.iterator();
while(iterator.hasNext()) {
builder.append((String)iterator.next() + ",");
}
if (this.stateList.size() > 0) {
builder.deleteCharAt(builder.length() - 1);
}
builder.append(" ]}");
return Encoder.toUnicode(builder.toString());
}
public void putInfo(String name, long val) {
this.intMap.put(name, val);
}
}
发现这个对象内并没有清楚的定义各种属性,而是通过定义HashMap来实现对其属性数据的存储,那么,要搞清楚state内部的元素,就只有跟踪用过它地方,进行反推了。
下面截取源码中往这个对象中进行塞值的地方,然后将其聚合,形成我要返回对象中包含的属性。
state.putInfo("url", PathFormat.format(savePath));
state.putInfo("type", suffix);
state.putInfo("original", "");
state.putInfo("size", (long)data.length);
state.putInfo("title", file.getName());
private boolean state = false;
通过对上面属性的聚合,最终完成重构后端接口上传file时,点击上传按钮的请求的返回结果对象的字符串的拼接;
同时需要说明一点,源码中,处理上传文件的方式是将文件保存到服务器本地,而我这里是将文件传输给oss进行存储。下面是我的处理文件上传的接口的逻辑程序:
@Override
public String saveFile(MultipartFile upFile) {
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 上传Byte数组。
byte[] content;
try {
content = upFile.getBytes();
ossClient.putObject(bucketName, objectName + File.separator + upFile.getOriginalFilename(), new ByteArrayInputStream(content));
// 关闭OSSClient。
final String url = String.format("https://%s.%s/%s%s%s", bucketName, endpoint, objectName, File.separator, upFile.getOriginalFilename());
JSONObject jsonObject = new JSONObject();
jsonObject.put("url", url);
jsonObject.put("size", upFile.getSize());
jsonObject.put("title", upFile.getName());
jsonObject.put("type", upFile.getContentType());
jsonObject.put("original", upFile.getOriginalFilename());
jsonObject.put("state", "SUCCESS");
return jsonObject.toJSONString();
} catch (IOException e) {
log.error("OSS upload file occur IO exception ~!", e);
} finally {
ossClient.shutdown();
}
return null;
}
3、实现效果
对上上面工作进行简单总结:
1、前端配置文件修改配置serverURL到我新写的服务地址;
2、在服务接口中,设置请求参数,当请求参数是config的时候,服务config.json配置文件,将内容以字符串的方式返回;如果是文件类型的,则将文件存储到oss上,并根据state属性,组装返回对象,将其转化成json字符串返回。
处理之前的效果:
![](https://img.haomeiwen.com/i17414643/ecca881aaedc938d.png)
处理之后的效果
![](https://img.haomeiwen.com/i17414643/7001a50d7ef40148.png)
![](https://img.haomeiwen.com/i17414643/8118e4e81fc8f58e.png)
![](https://img.haomeiwen.com/i17414643/cfe25581e67fcc10.png)
![](https://img.haomeiwen.com/i17414643/632da1ea037bbd76.png)
网友评论