From 24026a069156ba8579105165f488865780d67941 Mon Sep 17 00:00:00 2001 From: Colorful Date: Mon, 3 Jun 2019 20:21:23 +0800 Subject: [PATCH] Feat/file upload (#55) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: upgrade-deps-version (#48) * chore: 升级依赖包版本号 * fix: 修改requirements.txt的依赖版本 * chore: 升级核心库版本号 * 修改readme文件 * chore: upgrade requirements.txt * chore: 更新为bate版本 * feat: 新增文件上传API * feat: 改建文件上传 * fix: 删除多余的模块导入 * fix: 修改文件上传返回的key值 * feat: 增加文件上传默认配置 * chore: 更新核心库版本 * fix: 修复win系统在cmd下执行初始化插件脚本的编码问题 * fix: 删除LocalUpload下upload的多余参数 --- Pipfile | 2 +- README.md | 6 ++-- app/api/cms/__init__.py | 2 ++ app/api/cms/file.py | 20 +++++++++++++ app/api/cms/user.py | 13 ++++++++- app/app.py | 2 +- app/extensions/file/config.py | 9 ++++++ app/extensions/file/local_uploader.py | 41 +++++++++++++++++++++++++++ app/validators/forms.py | 6 ++++ code.md | 6 ++++ plugin_init.py | 6 ++-- requirements.txt | 2 +- 12 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 app/api/cms/file.py create mode 100644 app/extensions/file/config.py create mode 100644 app/extensions/file/local_uploader.py diff --git a/Pipfile b/Pipfile index eef60d5..ceda59a 100644 --- a/Pipfile +++ b/Pipfile @@ -12,7 +12,7 @@ Flask = "==1.0.2" Flask-SQLAlchemy = "==2.3.2" Flask-WTF = "==0.14.2" Flask-Cors = "==2.1.0" -Lin-CMS = "==0.1.1b1" +Lin-CMS = "==0.1.1b3" [dev-packages] pytest = "*" diff --git a/README.md b/README.md index 6c68274..541afdb 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ Lin-CMS 是林间有风团队经过大量项目实践所提炼出的一套**内 ## 最新版本 -核心库:0.1.1b1 +核心库:0.1.1b3 -示例工程:0.1.0-beta.1 +示例工程:0.1.0-beta.2 ### 文档地址 @@ -165,4 +165,4 @@ pipenv shell ## 下个版本开发计划 -- [ ] 调整 jwt 机制,增强灵活性 +- [ ] 系统访问日志、错误日志 diff --git a/app/api/cms/__init__.py b/app/api/cms/__init__.py index 559d701..af09b63 100644 --- a/app/api/cms/__init__.py +++ b/app/api/cms/__init__.py @@ -13,9 +13,11 @@ def create_cms(): from .admin import admin_api from .user import user_api from .log import log_api + from .file import file_api from .test import test_api admin_api.register(cms) user_api.register(cms) log_api.register(cms) + file_api.register(cms) test_api.register(cms) return cms diff --git a/app/api/cms/file.py b/app/api/cms/file.py new file mode 100644 index 0000000..ff91447 --- /dev/null +++ b/app/api/cms/file.py @@ -0,0 +1,20 @@ +""" + :copyright: © 2019 by the Lin team. + :license: MIT, see LICENSE for more details. +""" +from flask import request, jsonify +from lin import login_required +from lin.redprint import Redprint + +from app.extensions.file.local_uploader import LocalUploader + +file_api = Redprint('file') + + +@file_api.route('/', methods=['POST']) +@login_required +def post_file(): + files = request.files + uploader = LocalUploader(files) + ret = uploader.upload() + return jsonify(ret) diff --git a/app/api/cms/user.py b/app/api/cms/user.py index 32b3f71..7c1b061 100644 --- a/app/api/cms/user.py +++ b/app/api/cms/user.py @@ -17,7 +17,8 @@ from lin.redprint import Redprint from app.libs.error_code import RefreshException -from app.validators.forms import LoginForm, RegisterForm, ChangePasswordForm, UpdateInfoForm +from app.validators.forms import LoginForm, RegisterForm, ChangePasswordForm, UpdateInfoForm, \ + AvatarUpdateForm user_api = Redprint('user') @@ -136,6 +137,16 @@ def get_allowed_apis(): return jsonify(user) +@user_api.route('/avatar', methods=['PUT']) +@login_required +def set_avatar(): + form = AvatarUpdateForm().validate_for_api() + user = get_current_user() + with db.auto_commit(): + user.avatar = form.avatar.data + return Success(msg='更新头像成功') + + def _register_user(form: RegisterForm): with db.auto_commit(): # 注意:此处使用挂载到manager上的user_model,不可使用默认的User diff --git a/app/app.py b/app/app.py index 56ccbd1..d985b35 100644 --- a/app/app.py +++ b/app/app.py @@ -26,7 +26,7 @@ def create_tables(app): def create_app(register_all=True): - app = Flask(__name__) + app = Flask(__name__, static_folder='./assets') app.config.from_object('app.config.setting') app.config.from_object('app.config.secure') if register_all: diff --git a/app/extensions/file/config.py b/app/extensions/file/config.py new file mode 100644 index 0000000..420e194 --- /dev/null +++ b/app/extensions/file/config.py @@ -0,0 +1,9 @@ +# 文件相关配置 +FILE = { + "STORE_DIR": 'app/assets', + "SINGLE_LIMIT": 1024 * 1024 * 2, + "TOTAL_LIMIT": 1024 * 1024 * 20, + "NUMS": 10, + "INCLUDE": set([]), + "EXCLUDE": set([]) +} diff --git a/app/extensions/file/local_uploader.py b/app/extensions/file/local_uploader.py new file mode 100644 index 0000000..b80637c --- /dev/null +++ b/app/extensions/file/local_uploader.py @@ -0,0 +1,41 @@ +from flask import current_app +from werkzeug.utils import secure_filename + +from lin.core import File +from lin.file import Uploader + + +class LocalUploader(Uploader): + + def upload(self): + ret = [] + site_domain = current_app.config.get('SITE_DOMAIN')\ + if current_app.config.get('SITE_DOMAIN') else 'http://127.0.0.1:5000' + for single in self._file_storage: + file_md5 = self._generate_md5(single.read()) + single.seek(0) + exists = File.query.filter_by(md5=file_md5).first() + if exists: + ret.append({ + "key": single.name, + "id": exists.id, + "url": site_domain + '/assets/' + exists.path + }) + else: + absolute_path, relative_path, real_name = self._get_store_path(single.filename) + secure_filename(single.filename) + single.save(absolute_path) + file = File.create_file( + name=real_name, + path=relative_path, + extension=self._get_ext(single.filename), + size=self._get_size(single), + md5=file_md5, + commit=True + ) + ret.append({ + "key": single.name, + "id": file.id, + "url": site_domain + '/assets/' + file.path + }) + return ret diff --git a/app/validators/forms.py b/app/validators/forms.py index 2ef9359..a1f8328 100644 --- a/app/validators/forms.py +++ b/app/validators/forms.py @@ -141,6 +141,12 @@ class UpdateUserInfoForm(Form): ]) +class AvatarUpdateForm(Form): + avatar = StringField('头像', validators=[ + DataRequired(message='请输入头像url') + ]) + + class BookSearchForm(Form): q = StringField(validators=[DataRequired(message='必须传入搜索关键字')]) # 前端的请求参数中必须携带`q` diff --git a/code.md b/code.md index 7b8bcd9..8df5be1 100644 --- a/code.md +++ b/code.md @@ -26,6 +26,12 @@ 10100 refresh token 获取失败 +10110 文件体积过大 + +10120 文件数量过多 + +10130 文件扩展名不符合规范 + 20000 werkzeug 中的HTTP EXCEPTION,error_code统一为1007,前端应读取msg ## 项目使用的状态码 diff --git a/plugin_init.py b/plugin_init.py index c135439..53478ea 100644 --- a/plugin_init.py +++ b/plugin_init.py @@ -144,12 +144,12 @@ def __update_setting(self, new_setting): sub_str = 'PLUGIN_PATH = ' + self.__format_setting(final_setting) setting_path = self.app.config.root_path + '/config/setting.py' - with open(setting_path, 'r') as f: + with open(setting_path, 'r', encoding='UTF-8') as f: content = f.read() pattern = 'PLUGIN_PATH = \{([\s\S]*)\}+.*?' result = re.sub(pattern, sub_str, content) - with open(setting_path, 'w+') as f: + with open(setting_path, 'w+', encoding='UTF-8') as f: f.write(result) def __get_all_plugins(self): @@ -255,7 +255,7 @@ def __generate_plugin_graph(self): '.', '/').replace('app', '') requirements_path = self.app.config.root_path + \ plugin_path + '/requirements.txt' - with open(requirements_path, 'r') as f: + with open(requirements_path, 'r', encoding='UTF-8') as f: while True: # 正则匹配requirements的每一行的信息 diff --git a/requirements.txt b/requirements.txt index a4a5391..fc8cd9b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ Flask-WTF==0.14.2 idna==2.6 itsdangerous==1.1.0 Jinja2==2.10 -Lin-CMS==0.1.1b1 +Lin-CMS==0.1.1b3 MarkupSafe==1.1.1 pipfile==0.0.2 PyJWT==1.7.1