Flask之封装ueditor

百度出品的ueditor是一款优秀的在线编辑器,官方给出来部分开发语言的使用案例,之前使用网友开发的python ueditor插件使用起来也要修改好些东西,对于ueditor不熟悉,也挺折腾,索性研究了下php版本的ueditor,照猫画虎写了一个基于flaskueditor

部署Flask

pip安装好flask,默认app.py代码如下:

1
2
3
4
5
6
7
8
9
from flask import Flask

app = Flask(__name__)
def hello():
return '世界,你好'


if __name__ == '__main__':
app.run()

添加编辑器视图路由

1
2
3
4
5
6
7
8
9
from flask import Flask, render_template

app = Flask(__name__)
def index():
return render_template('index.html')


if __name__ == '__main__':
app.run()

模板代码

模板代码直接拿百度提供的即可,js地址修改为自己的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<html>
<head>
<title>完整demo</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>


<style type="text/css">
.container{
width: 80%;
margin: 0 auto;
}
</head>
<body>
<div class="container">
<script id="container" name="content" type="text/plain">
这里写你的初始化内容
</script>
</div>


<!-- 配置文件 -->
<script type="text/javascript" src="/static/editor/ueditor.config.js"></script>
<!-- 编辑器源码文件 -->
<script type="text/javascript" src="/static/editor/ueditor.all.js"></script>
<!-- 实例化编辑器 -->
<script type="text/javascript">
var ue = UE.getEditor('container');
</script>

</body>
</html>

测试访问

正常访问,除了文件上传,其实这个编辑器就能正常使用了,但是上传功能肯定少不了的,这就要借助我们的后端语言去处理上传的文件了。

img

修改后端服务处理地址

修改ueditor.config.js中的后端服务处理地址,亦可以在模板文件中修改

1
2
3
4
5
6
7
8
<script type="text/javascript">
var ue = UE.getEditor('container',
{
serverUrl : '/upfile'
}
);
console.log(ue)
</script>

创建处理方法

1
2
@app.route('/upfile', methods=['GET', 'POST', 'options'])
def ueditor():

设置响应参数

设置返回的参数和相应类型,并读取后端处理上传文件的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 响应类型
mime_type = 'application/json'
# 用来返回的结果
result = {}
# 配置文件路径
config_file = os.path.join(app.static_folder, 'editor/config.json')
# 配置文件内容
CONFIG = {}
# 读取配置文件并格式化
with open(config_file, 'r', encoding='utf8') as f:
try:
# 去除配置文件中的注释并将json转为python中的字典
CONFIG = json.loads(re.sub(r'\/\*.*\*\/', '', f.read()))
except:
CONFIG = {}

# 判断用户动作
action = request.args.get('action')
# 上传配置
config = {}
# 提交的图片表单名称
field_name = ''
result = json.dumps(result)
res = make_response(result)
res.mimetype = mime_type
res.headers['Access-Control-Allow-Origin'] = '*'
res.headers['Access-Control-Allow-Headers'] = 'X-Requested-With,X_Requested_With'
return res

添加文件类型的处理方法

ueditor常见的文件处理类型有上传文件(图片、视频,文件),涂鸦上传、远程图片抓取,代码中已经详细注释了,故不另做解释。以下是完整的文件处理方法代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
from flask import Flask, request, render_template, make_response
import os
import json
import re
from uploader import Upload

app = Flask(__name__)
def index():
return render_template('index.html')
def ueditor():
# 响应类型
mime_type = 'application/json'
# 用来返回的结果
result = {}
# 配置文件路径
config_file = os.path.join(app.static_folder, 'editor/config.json')
# 配置文件内容
CONFIG = {}
# 读取配置文件并格式化
with open(config_file, 'r', encoding='utf8') as f:
try:
# 去除配置文件中的注释并将json转为python中的字典
CONFIG = json.loads(re.sub(r'\/\*.*\*\/', '', f.read()))
except:
CONFIG = {}

# 判断用户动作
action = request.args.get('action')
# 上传配置
config = {}
# 提交的图片表单名称
field_name = ''
# 如果是页面初始化读取配置
if action == 'config':
result = CONFIG
# 如果是上传图片、涂鸦、视频、文件
elif action in ['uploadimage', 'uploadvideo', 'uploadfile']:

if action == 'uploadimage':
field_name = CONFIG.get('imageFieldName')
config = {
"pathFormat": CONFIG['imagePathFormat'],
"maxSize" : CONFIG['imageMaxSize'],
"allowFiles": CONFIG['imageAllowFiles']
}
elif action == 'uploadvideo':
field_name = CONFIG.get('videoFieldName')
config = {
"pathFormat": CONFIG['videoPathFormat'],
"maxSize": CONFIG['videoMaxSize'],
"allowFiles": CONFIG['videoAllowFiles']
}
else:
field_name = CONFIG.get('fileFieldName')
config = {
"pathFormat": CONFIG['filePathFormat'],
"maxSize": CONFIG['fileMaxSize'],
"allowFiles": CONFIG['fileAllowFiles']
}
# 判断文件是否真实
if field_name in request.files:
# 获取文件
file = request.files.get(field_name)
# 执行上传
upload_obj = Upload(file, config, app.static_folder)
result = upload_obj.get_file_info()
print('我已经执行上传要输出响应了')
else:
# 请求有误,报错
result['state'] = '小子,你又开始恶意了?'
elif action == 'uploadscrawl':
config = {
"pathFormat": CONFIG['scrawlPathFormat'],
"maxSize": CONFIG['scrawlMaxSize'],
# "allowFiles": CONFIG['scrawlAllowFiles'],
"oriName": "scrawl.png"
}
field_name = CONFIG['scrawlFieldName']
if field_name in request.form:
uploader = Upload(request.form[field_name], config, app.static_folder, 'base64')
result = uploader.get_file_info()
else:
result['state'] = '上传接口出错'
# 抓取远程图片
elif action == 'catchimage':

config = {
"pathFormat": CONFIG['catcherPathFormat'],
"maxSize": CONFIG['catcherMaxSize'],
"allowFiles": CONFIG['catcherAllowFiles'],
"oriName": "yuancheng.png"
}
field_name = CONFIG['catcherFieldName']
print('表单,你已经开始执行了啊', request.form)
# 用来存放远程图片
sources = ''
# 存放每个远程图片的返回信息
_res_lists = []
if '%s[]' % field_name in request.form:
sources = request.form.getlist('%s[]' % field_name)
for img_url in sources:
print('都有什么图片:', img_url)
uploader = Upload(img_url, config, app.static_folder, 'remote')
info = uploader.get_file_info()
_res_lists.append({
'state': info['state'],
'url': info['url'],
'original': info['original'],
'source': img_url,
})
result['state'] = 'SUCCESS' if len(_res_lists) > 0 else 'ERROR'
result['list'] = _res_lists

else:
result['state'] = '上传接口出错'
else:
result['state'] = '请求地址出错'

result = json.dumps(result)
res = make_response(result)
res.mimetype = mime_type
res.headers['Access-Control-Allow-Origin'] = '*'
res.headers['Access-Control-Allow-Headers'] = 'X-Requested-With,X_Requested_With'
return res


if __name__ == '__main__':
app.run()

文件处理类

上述代码中引用了文件处理类,用来出来用户上传文件的操作,主要分为上传文件、上传涂鸦、抓取远程图片三个主要处理功能,完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
import os
from werkzeug.utils import secure_filename
from flask import url_for
from uuid import uuid4
from pypinyin import lazy_pinyin
import base64
from urllib import request

class Upload:
state_info = ''
state_map = [ # 上传状态映射表,国际化用户需考虑此处数据的国际化
"SUCCESS", # 上传成功标记,在UEditor中内不可改变,否则flash判断会出错
"文件大小超出 upload_max_filesize 限制",
"文件大小超出 MAX_FILE_SIZE 限制",
"文件未被完整上传",
"没有文件被上传",
"上传文件为空",
]

state_error = {
"ERROR_TMP_FILE": "临时文件错误",
"ERROR_TMP_FILE_NOT_FOUND": "找不到临时文件",
"ERROR_SIZE_EXCEED": "文件大小超出网站限制",
"ERROR_TYPE_NOT_ALLOWED": "文件类型不允许",
"ERROR_CREATE_DIR": "目录创建失败",
"ERROR_DIR_NOT_WRITEABLE": "目录没有写权限",
"ERROR_FILE_MOVE": "文件保存时出错",
"ERROR_FILE_NOT_FOUND": "找不到上传文件",
"ERROR_WRITE_CONTENT": "写入文件内容错误",
"ERROR_UNKNOWN": "未知错误",
"ERROR_DEAD_LINK": "链接不可用",
"ERROR_HTTP_LINK": "链接不是http链接",
"ERROR_HTTP_CONTENTTYPE": "链接contentType不正确"
}

# 初始化函数
# 要处理的文件对象;处理该对象的配置参数;文件保存的目录
def __init__(self, file, config, save_folder, __type='up_file'):
self.file = file
self.config = config
self.save_folder = save_folder
self.__type = __type
if self.__type == 'remote':
self.save_remote()
elif self.__type == 'base64':
self.up_base64()
else:
self.up_file()
def save_remote(self):
# 获取文件内容
_file = request.urlopen(self.file).read()
# 文件大小
self.file_size = len(_file)
# 原始文件名
self.ori_name = self.config['oriName']
# 文件后缀
self.file_type = self.get_file_type()
# 生成新的文件名含路径
self.full_name = self.get_full_name()
# 文件保存路径完整绝对路径
self.file_path = self.get_file_path()
# 判断文件大小是否超出限制
if not self.check_size():
self.state_info = self.get_error('ERROR_SIZE_EXCEED')
return

# 获取保存路径
dir_name = os.path.dirname(self.file_path)
# 判断保存路径是否存在
if not os.path.exists(dir_name):
# 创建保存路径,创建失败则报错
try:
os.makedirs(dir_name)
except:
self.state_info = self.get_error('ERROR_CREATE_DIR')
# 判断保存路径如果不允许写入则直接报错
elif not os.access(dir_name, os.W_OK):
self.state_info = self.get_error('ERROR_DIR_NOT_WRITEABLE')
return
# 开始保存文件
try:
with open(self.file_path, 'wb') as f:
f.write(_file)
self.state_info = self.state_map[0]
except:
self.state_info = self.get_error('ERROR_FILE_MOVE')
return
def up_base64(self):
im = base64.b64decode(self.file)
# 文件大小
self.file_size = len(im)
# 原始文件名
self.ori_name = self.config['oriName']
# 文件后缀
self.file_type = self.get_file_type()
# 生成新的文件名含路径
self.full_name = self.get_full_name()
# 文件保存路径完整绝对路径
self.file_path = self.get_file_path()

# 判断文件大小是否超出限制
if not self.check_size():
self.state_info = self.get_error('ERROR_SIZE_EXCEED')
return

# 获取保存路径
dir_name = os.path.dirname(self.file_path)
# 判断保存路径是否存在
if not os.path.exists(dir_name):
# 创建保存路径,创建失败则报错
try:
os.makedirs(dir_name)
except:
self.state_info = self.get_error('ERROR_CREATE_DIR')
# 判断保存路径如果不允许写入则直接报错
elif not os.access(dir_name, os.W_OK):
self.state_info = self.get_error('ERROR_DIR_NOT_WRITEABLE')
return
# 开始保存文件
try:
with open(self.file_path, 'wb') as f:
f.write(im)
self.state_info = self.state_map[0]
except:
self.state_info = self.get_error('ERROR_FILE_MOVE')
return
# 上传文件
def up_file(self):
'''
上传文件的主处理方法
:return:
'''
# 文件大小
self.file_size = self.get_file_size()
# 原始文件名
self.ori_name = self.get_ori_name()
# 文件后缀
self.file_type = self.get_file_type()
# 生成新的文件名含路径
self.full_name = self.get_full_name()
# 文件保存路径完整绝对路径
self.file_path = self.get_file_path()

# 判断文件大小是否超出限制
if not self.check_size():
self.state_info = self.get_error('ERROR_SIZE_EXCEED')
return
# 判断文件类型是否允许
if not self.check_type():
self.state_info = self.get_error('ERROR_TYPE_NOT_ALLOWED')
return
# 开始保存文件
self.save_file()
def get_ori_name(self):
'''
获取原始文件名(不支持中文,使用pypinyin将中文转为每个汉字首字母)
:return:
'''
return secure_filename(''.join(lazy_pinyin(self.file.filename, style=4)))
def get_file_size(self):
'''
获取文件大小(将指针移动到文件末尾,读取指针位置,即该文件的大小;
获得文件大小后还将指针移动到文件开始位置)。
:return: 文件大小
'''
self.file.seek(0, 2)
file_size = self.file.tell()
self.file.seek(0, 0)
return file_size

def save_file(self):
# 获取保存路径
dir_name = os.path.dirname(self.file_path)
# 判断保存路径是否存在
if not os.path.exists(dir_name):
# 创建保存路径,创建失败则报错
try:
os.makedirs(dir_name)
except:
self.state_info = self.get_error('ERROR_CREATE_DIR')
# 判断保存路径如果不允许写入则直接报错
elif not os.access(dir_name, os.W_OK):
self.state_info = self.get_error('ERROR_DIR_NOT_WRITEABLE')
return
# 开始保存文件
try:
self.file.save(self.file_path)
self.state_info = self.state_map[0]
except:
self.state_info = self.get_error('ERROR_FILE_MOVE')
return

# 文件错误提示
def get_error(self, error_code):
'''
上传错误输出
:param error_code: 错误代码
:return: 错误信息
'''
return self.state_error.get(error_code, '未知错误')

# 获取文件后缀
def get_file_type(self):
'''
获取文件类型
:return:返回文件后缀包含.
'''
return '.%s' % self.ori_name.split('.')[-1].lower()

# 生成新的文件名,含相对路径
def get_full_name(self):
'''
按照配置文件规则生成文件新地址
:return: 生成新的文件地址
'''
_format = self.config['pathFormat']
_format = _format.replace('{uuid}', str(uuid4()))

# 判断路径首字符是否是“/”,如果是则去除,便于拼接完整绝对路径
_format = _format[1:-1] if _format[0] == '/' else _format
return '%s%s' % (_format, self.get_file_type())

# 获取文件路径
def get_file_path(self):
'''
生成文件保存的完整绝对路径
:return: 文件完整绝对路径
'''
return os.path.join(self.save_folder, self.full_name)

# 验证文件类型是否允许
def check_type(self):
'''
判断上传的文件后缀是否在配置文件允许的后缀列表中
:return:
'''
return self.file_type in self.config['allowFiles']

# 验证文件大小是否超出设定
def check_size(self):
'''
判断文件大小是否超出配置文件中的文件大小单位:字节
:return:
'''
return self.file_size <= self.config['maxSize']

def get_file_info(self):
return {
'state': self.state_info,
'url': url_for('static', filename=self.full_name, _external=True),
'title': self.ori_name,
'original': self.ori_name,
'type': self.file_type,
'size': self.file_size,
}

总结

官方示例代码很简单明了,照着其他语言编写还是不难的,有些部分稍有不同,根据当前使用的语言修改该部分即可。